@bloopjs/toodle 0.0.100
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.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/dist/Toodle.d.ts +304 -0
- package/dist/Toodle.d.ts.map +1 -0
- package/dist/colors/mod.d.ts +872 -0
- package/dist/colors/mod.d.ts.map +1 -0
- package/dist/coreTypes/Color.d.ts +7 -0
- package/dist/coreTypes/Color.d.ts.map +1 -0
- package/dist/coreTypes/Point.d.ts +8 -0
- package/dist/coreTypes/Point.d.ts.map +1 -0
- package/dist/coreTypes/Size.d.ts +5 -0
- package/dist/coreTypes/Size.d.ts.map +1 -0
- package/dist/coreTypes/Transform.d.ts +16 -0
- package/dist/coreTypes/Transform.d.ts.map +1 -0
- package/dist/coreTypes/Vec2.d.ts +8 -0
- package/dist/coreTypes/Vec2.d.ts.map +1 -0
- package/dist/coreTypes/mod.d.ts +6 -0
- package/dist/coreTypes/mod.d.ts.map +1 -0
- package/dist/docs/snippets/add-remove-children.d.ts +1 -0
- package/dist/docs/snippets/basic-quad.d.ts +1 -0
- package/dist/docs/snippets/filter-linear.d.ts +1 -0
- package/dist/docs/snippets/filter-nearest.d.ts +1 -0
- package/dist/docs/snippets/flipxy.d.ts +1 -0
- package/dist/docs/snippets/hello-text.d.ts +1 -0
- package/dist/docs/snippets/jumbo-textures.d.ts +1 -0
- package/dist/docs/snippets/layer.d.ts +1 -0
- package/dist/docs/snippets/layout-edges.d.ts +1 -0
- package/dist/docs/snippets/layout-screen-and-world-space.d.ts +1 -0
- package/dist/docs/snippets/postprocess.d.ts +1 -0
- package/dist/docs/snippets/quad-size-scale.d.ts +1 -0
- package/dist/docs/snippets/quickstart.d.ts +1 -0
- package/dist/docs/snippets/repeat-texture-loading.d.ts +1 -0
- package/dist/docs/snippets/screen-shaders.d.ts +1 -0
- package/dist/docs/snippets/shader-color-flash.d.ts +1 -0
- package/dist/docs/snippets/shader-default.d.ts +1 -0
- package/dist/docs/snippets/shader-fill.d.ts +1 -0
- package/dist/docs/snippets/shapes-line.d.ts +1 -0
- package/dist/docs/snippets/sprite-region.d.ts +1 -0
- package/dist/docs/snippets/text-alignment.d.ts +1 -0
- package/dist/docs/snippets/text-shrink-to-fit.d.ts +1 -0
- package/dist/docs/snippets/text-word-wrap.d.ts +1 -0
- package/dist/docs/snippets/texture-bundles-prebaked.d.ts +1 -0
- package/dist/docs/snippets/texture-bundles.d.ts +1 -0
- package/dist/docs/snippets/transforms.d.ts +1 -0
- package/dist/docs/snippets/transparent-cropping.d.ts +1 -0
- package/dist/examples/0-hello.d.ts +1 -0
- package/dist/examples/1-hello.d.ts +1 -0
- package/dist/examples/1-hello.d.ts.map +1 -0
- package/dist/examples/1-quad.d.ts +1 -0
- package/dist/examples/10-resize.d.ts +1 -0
- package/dist/examples/10-resize.d.ts.map +1 -0
- package/dist/examples/11-bundle-test.d.ts +1 -0
- package/dist/examples/11-bundle-test.d.ts.map +1 -0
- package/dist/examples/12-transparent-pixel-cropping.d.ts +1 -0
- package/dist/examples/12-transparent-pixel-cropping.d.ts.map +1 -0
- package/dist/examples/13-crop.d.ts +1 -0
- package/dist/examples/13-crop.d.ts.map +1 -0
- package/dist/examples/14-bundle-bench.d.ts +1 -0
- package/dist/examples/14-bundle-bench.d.ts.map +1 -0
- package/dist/examples/15-text-layer.d.ts +1 -0
- package/dist/examples/15-text-layer.d.ts.map +1 -0
- package/dist/examples/16-jumbo-texture.d.ts +1 -0
- package/dist/examples/16-jumbo-textures.d.ts +1 -0
- package/dist/examples/16-screen-shader.d.ts +1 -0
- package/dist/examples/16-screen-shader.d.ts.map +1 -0
- package/dist/examples/17-lighting.d.ts +1 -0
- package/dist/examples/17-lighting.d.ts.map +1 -0
- package/dist/examples/17-translations.d.ts +1 -0
- package/dist/examples/18-blur.d.ts +1 -0
- package/dist/examples/19-postprocess.d.ts +1 -0
- package/dist/examples/19-screenshader.d.ts +1 -0
- package/dist/examples/2-shapes.d.ts +1 -0
- package/dist/examples/2-shapes.d.ts.map +1 -0
- package/dist/examples/3-shader.d.ts +1 -0
- package/dist/examples/3-shader.d.ts.map +1 -0
- package/dist/examples/4-shader-bench.d.ts +1 -0
- package/dist/examples/4-shader-bench.d.ts.map +1 -0
- package/dist/examples/5-z.d.ts +1 -0
- package/dist/examples/5-z.d.ts.map +1 -0
- package/dist/examples/6-atlas.d.ts +1 -0
- package/dist/examples/6-atlas.d.ts.map +1 -0
- package/dist/examples/7-text.d.ts +1 -0
- package/dist/examples/7-text.d.ts.map +1 -0
- package/dist/examples/8-text-bench.d.ts +1 -0
- package/dist/examples/8-text-bench.d.ts.map +1 -0
- package/dist/examples/9-alignment.d.ts +1 -0
- package/dist/examples/9-alignment.d.ts.map +1 -0
- package/dist/examples/main.d.ts +1 -0
- package/dist/examples/main.d.ts.map +1 -0
- package/dist/examples/util.d.ts +82 -0
- package/dist/examples/util.d.ts.map +1 -0
- package/dist/limits.d.ts +23 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/math/angle.d.ts +13 -0
- package/dist/math/angle.d.ts.map +1 -0
- package/dist/math/matrix.d.ts +26 -0
- package/dist/math/matrix.d.ts.map +1 -0
- package/dist/math/mod.d.ts +3 -0
- package/dist/math/mod.d.ts.map +1 -0
- package/dist/mod.d.ts +17 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +19665 -0
- package/dist/mod.js.map +41 -0
- package/dist/postprocess.d.ts +10 -0
- package/dist/postprocess.d.ts.map +1 -0
- package/dist/scene/Batcher.d.ts +20 -0
- package/dist/scene/Batcher.d.ts.map +1 -0
- package/dist/scene/Camera.d.ts +16 -0
- package/dist/scene/Camera.d.ts.map +1 -0
- package/dist/scene/JumboQuadNode.d.ts +29 -0
- package/dist/scene/JumboQuadNode.d.ts.map +1 -0
- package/dist/scene/QuadNode.d.ts +159 -0
- package/dist/scene/QuadNode.d.ts.map +1 -0
- package/dist/scene/RenderComponent.d.ts +11 -0
- package/dist/scene/RenderComponent.d.ts.map +1 -0
- package/dist/scene/SceneNode.d.ts +300 -0
- package/dist/scene/SceneNode.d.ts.map +1 -0
- package/dist/scene/mod.d.ts +5 -0
- package/dist/scene/mod.d.ts.map +1 -0
- package/dist/screen/mod.d.ts +2 -0
- package/dist/screen/mod.d.ts.map +1 -0
- package/dist/screen/resolution.d.ts +5 -0
- package/dist/screen/resolution.d.ts.map +1 -0
- package/dist/shaders/EngineUniform.d.ts +9 -0
- package/dist/shaders/EngineUniform.d.ts.map +1 -0
- package/dist/shaders/IShader.d.ts +15 -0
- package/dist/shaders/IShader.d.ts.map +1 -0
- package/dist/shaders/QuadShader.d.ts +18 -0
- package/dist/shaders/QuadShader.d.ts.map +1 -0
- package/dist/shaders/ShaderDescriptor.d.ts +7 -0
- package/dist/shaders/ShaderDescriptor.d.ts.map +1 -0
- package/dist/shaders/mod.d.ts +6 -0
- package/dist/shaders/mod.d.ts.map +1 -0
- package/dist/shaders/parser.d.ts +8 -0
- package/dist/shaders/parser.d.ts.map +1 -0
- package/dist/shaders/postprocess/blur.d.ts +3 -0
- package/dist/shaders/postprocess/blur.d.ts.map +1 -0
- package/dist/shaders/postprocess/mod.d.ts +17 -0
- package/dist/shaders/postprocess/mod.d.ts.map +1 -0
- package/dist/shaders/samplers.d.ts +3 -0
- package/dist/shaders/samplers.d.ts.map +1 -0
- package/dist/shaders/wgsl/example.wgsl.d.ts +3 -0
- package/dist/shaders/wgsl/example.wgsl.d.ts.map +1 -0
- package/dist/shaders/wgsl/hello.wgsl.d.ts +3 -0
- package/dist/shaders/wgsl/hello.wgsl.d.ts.map +1 -0
- package/dist/shaders/wgsl/helloInstanced.wgsl.d.ts +3 -0
- package/dist/shaders/wgsl/helloInstanced.wgsl.d.ts.map +1 -0
- package/dist/shaders/wgsl/quad.wgsl.d.ts +3 -0
- package/dist/shaders/wgsl/quad.wgsl.d.ts.map +1 -0
- package/dist/src/Toodle.d.ts +303 -0
- package/dist/src/Toodle.d.ts.map +1 -0
- package/dist/src/colors/mod.d.ts +871 -0
- package/dist/src/coreTypes/Color.d.ts +6 -0
- package/dist/src/coreTypes/Color.d.ts.map +1 -0
- package/dist/src/coreTypes/Point.d.ts +7 -0
- package/dist/src/coreTypes/Point.d.ts.map +1 -0
- package/dist/src/coreTypes/Size.d.ts +4 -0
- package/dist/src/coreTypes/Size.d.ts.map +1 -0
- package/dist/src/coreTypes/Transform.d.ts +15 -0
- package/dist/src/coreTypes/Transform.d.ts.map +1 -0
- package/dist/src/coreTypes/Vec2.d.ts +7 -0
- package/dist/src/coreTypes/Vec2.d.ts.map +1 -0
- package/dist/src/coreTypes/mod.d.ts +5 -0
- package/dist/src/coreTypes/mod.d.ts.map +1 -0
- package/dist/src/limits.d.ts +22 -0
- package/dist/src/limits.d.ts.map +1 -0
- package/dist/src/math/angle.d.ts +12 -0
- package/dist/src/math/angle.d.ts.map +1 -0
- package/dist/src/math/matrix.d.ts +25 -0
- package/dist/src/math/matrix.d.ts.map +1 -0
- package/dist/src/math/mod.d.ts +2 -0
- package/dist/src/math/mod.d.ts.map +1 -0
- package/dist/src/mod.d.ts +16 -0
- package/dist/src/mod.d.ts.map +1 -0
- package/dist/src/postprocess.d.ts +10 -0
- package/dist/src/postprocess.d.ts.map +1 -0
- package/dist/src/scene/Batcher.d.ts +19 -0
- package/dist/src/scene/Batcher.d.ts.map +1 -0
- package/dist/src/scene/Camera.d.ts +15 -0
- package/dist/src/scene/Camera.d.ts.map +1 -0
- package/dist/src/scene/JumboQuadNode.d.ts +28 -0
- package/dist/src/scene/QuadNode.d.ts +158 -0
- package/dist/src/scene/QuadNode.d.ts.map +1 -0
- package/dist/src/scene/RenderComponent.d.ts +10 -0
- package/dist/src/scene/RenderComponent.d.ts.map +1 -0
- package/dist/src/scene/SceneNode.d.ts +299 -0
- package/dist/src/scene/SceneNode.d.ts.map +1 -0
- package/dist/src/scene/mod.d.ts +4 -0
- package/dist/src/scene/mod.d.ts.map +1 -0
- package/dist/src/screen/mod.d.ts +1 -0
- package/dist/src/screen/mod.d.ts.map +1 -0
- package/dist/src/screen/resolution.d.ts +4 -0
- package/dist/src/screen/resolution.d.ts.map +1 -0
- package/dist/src/shaders/EngineUniform.d.ts +8 -0
- package/dist/src/shaders/EngineUniform.d.ts.map +1 -0
- package/dist/src/shaders/IShader.d.ts +14 -0
- package/dist/src/shaders/IShader.d.ts.map +1 -0
- package/dist/src/shaders/QuadShader.d.ts +17 -0
- package/dist/src/shaders/QuadShader.d.ts.map +1 -0
- package/dist/src/shaders/ShaderDescriptor.d.ts +6 -0
- package/dist/src/shaders/ShaderDescriptor.d.ts.map +1 -0
- package/dist/src/shaders/mod.d.ts +5 -0
- package/dist/src/shaders/mod.d.ts.map +1 -0
- package/dist/src/shaders/parser.d.ts +7 -0
- package/dist/src/shaders/parser.d.ts.map +1 -0
- package/dist/src/shaders/postprocess/blur.d.ts +2 -0
- package/dist/src/shaders/postprocess/mod.d.ts +16 -0
- package/dist/src/shaders/postprocess/postprocess.d.ts +8 -0
- package/dist/src/shaders/postprocess/util.d.ts +2 -0
- package/dist/src/shaders/samplers.d.ts +2 -0
- package/dist/src/shaders/samplers.d.ts.map +1 -0
- package/dist/src/shaders/wgsl/example.wgsl.d.ts +2 -0
- package/dist/src/shaders/wgsl/example.wgsl.d.ts.map +1 -0
- package/dist/src/shaders/wgsl/hello.wgsl.d.ts +2 -0
- package/dist/src/shaders/wgsl/hello.wgsl.d.ts.map +1 -0
- package/dist/src/shaders/wgsl/helloInstanced.wgsl.d.ts +2 -0
- package/dist/src/shaders/wgsl/helloInstanced.wgsl.d.ts.map +1 -0
- package/dist/src/shaders/wgsl/quad.wgsl.d.ts +2 -0
- package/dist/src/shaders/wgsl/quad.wgsl.d.ts.map +1 -0
- package/dist/src/text/FontPipeline.d.ts +13 -0
- package/dist/src/text/FontPipeline.d.ts.map +1 -0
- package/dist/src/text/MsdfFont.d.ts +81 -0
- package/dist/src/text/MsdfFont.d.ts.map +1 -0
- package/dist/src/text/TextFormatting.d.ts +18 -0
- package/dist/src/text/TextFormatting.d.ts.map +1 -0
- package/dist/src/text/TextNode.d.ts +18 -0
- package/dist/src/text/TextNode.d.ts.map +1 -0
- package/dist/src/text/TextShader.d.ts +14 -0
- package/dist/src/text/TextShader.d.ts.map +1 -0
- package/dist/src/text/mod.d.ts +3 -0
- package/dist/src/text/mod.d.ts.map +1 -0
- package/dist/src/text/shaping.d.ts +38 -0
- package/dist/src/text/shaping.d.ts.map +1 -0
- package/dist/src/text/text.wgsl.d.ts +2 -0
- package/dist/src/text/text.wgsl.d.ts.map +1 -0
- package/dist/src/textures/AssetManager.d.ts +181 -0
- package/dist/src/textures/AssetManager.d.ts.map +1 -0
- package/dist/src/textures/NewTextureComputeShader.d.ts +28 -0
- package/dist/src/textures/TextureComputeShader.d.ts +20 -0
- package/dist/src/textures/TextureComputeShader.d.ts.map +1 -0
- package/dist/src/textures/crop.wgsl.d.ts +2 -0
- package/dist/src/textures/mod.d.ts +1 -0
- package/dist/src/textures/mod.d.ts.map +1 -0
- package/dist/src/textures/pixel-scraping.wgsl.d.ts +2 -0
- package/dist/src/textures/pixel-scraping.wgsl.d.ts.map +1 -0
- package/dist/src/textures/texture-processing.wgsl.d.ts +2 -0
- package/dist/src/textures/types.d.ts +176 -0
- package/dist/src/textures/types.d.ts.map +1 -0
- package/dist/src/textures/util.d.ts +7 -0
- package/dist/src/textures/util.d.ts.map +1 -0
- package/dist/src/utils/assert.d.ts +1 -0
- package/dist/src/utils/assert.d.ts.map +1 -0
- package/dist/src/utils/boilerplate.d.ts +10 -0
- package/dist/src/utils/boilerplate.d.ts.map +1 -0
- package/dist/src/utils/error.d.ts +7 -0
- package/dist/src/utils/error.d.ts.map +1 -0
- package/dist/src/utils/mod.d.ts +2 -0
- package/dist/src/utils/mod.d.ts.map +1 -0
- package/dist/src/utils/pool.d.ts +22 -0
- package/dist/src/utils/pool.d.ts.map +1 -0
- package/dist/test/math/matrix.test.d.ts +1 -0
- package/dist/test/scene/Batcher.test.d.ts +1 -0
- package/dist/test/scene/SceneNode.test.d.ts +1 -0
- package/dist/test/shader/parser.test.d.ts +1 -0
- package/dist/text/FontPipeline.d.ts +14 -0
- package/dist/text/FontPipeline.d.ts.map +1 -0
- package/dist/text/MsdfFont.d.ts +82 -0
- package/dist/text/MsdfFont.d.ts.map +1 -0
- package/dist/text/TextFormatting.d.ts +19 -0
- package/dist/text/TextFormatting.d.ts.map +1 -0
- package/dist/text/TextNode.d.ts +19 -0
- package/dist/text/TextNode.d.ts.map +1 -0
- package/dist/text/TextShader.d.ts +15 -0
- package/dist/text/TextShader.d.ts.map +1 -0
- package/dist/text/mod.d.ts +4 -0
- package/dist/text/mod.d.ts.map +1 -0
- package/dist/text/shaping.d.ts +39 -0
- package/dist/text/shaping.d.ts.map +1 -0
- package/dist/text/text.wgsl.d.ts +3 -0
- package/dist/text/text.wgsl.d.ts.map +1 -0
- package/dist/textures/AssetManager.d.ts +182 -0
- package/dist/textures/AssetManager.d.ts.map +1 -0
- package/dist/textures/TextureComputeShader.d.ts +21 -0
- package/dist/textures/TextureComputeShader.d.ts.map +1 -0
- package/dist/textures/mod.d.ts +2 -0
- package/dist/textures/mod.d.ts.map +1 -0
- package/dist/textures/pixel-scraping.wgsl.d.ts +3 -0
- package/dist/textures/pixel-scraping.wgsl.d.ts.map +1 -0
- package/dist/textures/types.d.ts +177 -0
- package/dist/textures/types.d.ts.map +1 -0
- package/dist/textures/util.d.ts +8 -0
- package/dist/textures/util.d.ts.map +1 -0
- package/dist/utils/assert.d.ts +2 -0
- package/dist/utils/assert.d.ts.map +1 -0
- package/dist/utils/boilerplate.d.ts +11 -0
- package/dist/utils/boilerplate.d.ts.map +1 -0
- package/dist/utils/error.d.ts +8 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/mod.d.ts +3 -0
- package/dist/utils/mod.d.ts.map +1 -0
- package/dist/utils/pool.d.ts +23 -0
- package/dist/utils/pool.d.ts.map +1 -0
- package/package.json +47 -0
- package/src/Toodle.ts +853 -0
- package/src/colors/mod.ts +151 -0
- package/src/coreTypes/Color.ts +1 -0
- package/src/coreTypes/Point.ts +7 -0
- package/src/coreTypes/Size.ts +4 -0
- package/src/coreTypes/Transform.ts +16 -0
- package/src/coreTypes/Vec2.ts +7 -0
- package/src/coreTypes/mod.ts +5 -0
- package/src/globals.d.ts +4 -0
- package/src/limits.ts +23 -0
- package/src/math/angle.ts +17 -0
- package/src/math/matrix.ts +99 -0
- package/src/math/mod.ts +2 -0
- package/src/mod.ts +22 -0
- package/src/scene/Batcher.ts +61 -0
- package/src/scene/Camera.ts +69 -0
- package/src/scene/JumboQuadNode.ts +219 -0
- package/src/scene/QuadNode.ts +403 -0
- package/src/scene/RenderComponent.ts +12 -0
- package/src/scene/SceneNode.ts +668 -0
- package/src/scene/mod.ts +4 -0
- package/src/screen/mod.ts +1 -0
- package/src/screen/resolution.ts +1 -0
- package/src/shaders/EngineUniform.ts +11 -0
- package/src/shaders/IShader.ts +20 -0
- package/src/shaders/QuadShader.ts +288 -0
- package/src/shaders/ShaderDescriptor.ts +6 -0
- package/src/shaders/mod.ts +5 -0
- package/src/shaders/parser.ts +221 -0
- package/src/shaders/postprocess/blur.ts +245 -0
- package/src/shaders/postprocess/mod.ts +71 -0
- package/src/shaders/samplers.ts +13 -0
- package/src/shaders/wgsl/example.wgsl.ts +24 -0
- package/src/shaders/wgsl/hello.wgsl.ts +62 -0
- package/src/shaders/wgsl/helloInstanced.wgsl.ts +46 -0
- package/src/shaders/wgsl/quad.wgsl.ts +140 -0
- package/src/text/FontPipeline.ts +212 -0
- package/src/text/MsdfFont.ts +190 -0
- package/src/text/TextFormatting.ts +28 -0
- package/src/text/TextNode.ts +82 -0
- package/src/text/TextShader.ts +223 -0
- package/src/text/mod.ts +8 -0
- package/src/text/shaping.ts +280 -0
- package/src/text/text.wgsl.ts +149 -0
- package/src/textures/AssetManager.ts +746 -0
- package/src/textures/TextureComputeShader.ts +434 -0
- package/src/textures/mod.ts +1 -0
- package/src/textures/pixel-scraping.wgsl.ts +131 -0
- package/src/textures/types.ts +182 -0
- package/src/textures/util.ts +352 -0
- package/src/utils/assert.ts +5 -0
- package/src/utils/boilerplate.ts +110 -0
- package/src/utils/error.ts +14 -0
- package/src/utils/mod.ts +2 -0
- package/src/utils/pool.ts +42 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { WgslReflect } from "wgsl_reflect";
|
|
2
|
+
import type { SceneNode } from "../scene/SceneNode";
|
|
3
|
+
import type { EngineUniform } from "../shaders/EngineUniform";
|
|
4
|
+
import type { IShader } from "../shaders/IShader";
|
|
5
|
+
import type { FontPipeline } from "./FontPipeline";
|
|
6
|
+
import type { MsdfFont } from "./MsdfFont";
|
|
7
|
+
import { findLargestFontSize, measureText, shapeText } from "./shaping";
|
|
8
|
+
import { DEFAULT_FONT_SIZE, TextNode } from "./TextNode";
|
|
9
|
+
import msdfShader from "./text.wgsl";
|
|
10
|
+
|
|
11
|
+
const deets = new WgslReflect(msdfShader);
|
|
12
|
+
const struct = deets.structs.find((s) => s.name === "TextBlockDescriptor");
|
|
13
|
+
if (!struct) {
|
|
14
|
+
throw new Error("FormattedText struct not found");
|
|
15
|
+
}
|
|
16
|
+
const textDescriptorInstanceSize = struct.size;
|
|
17
|
+
|
|
18
|
+
export class TextShader implements IShader {
|
|
19
|
+
#device: GPUDevice;
|
|
20
|
+
#pipeline: GPURenderPipeline;
|
|
21
|
+
#bindGroups: GPUBindGroup[] = [];
|
|
22
|
+
#font: MsdfFont;
|
|
23
|
+
#maxCharCount: number;
|
|
24
|
+
#engineUniformsBuffer: GPUBuffer;
|
|
25
|
+
#descriptorBuffer: GPUBuffer;
|
|
26
|
+
#textBlockBuffer: GPUBuffer;
|
|
27
|
+
#cpuDescriptorBuffer: Float32Array;
|
|
28
|
+
#cpuTextBlockBuffer: Float32Array;
|
|
29
|
+
#instanceIndex = 0;
|
|
30
|
+
#textBlockOffset = 0;
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
device: GPUDevice,
|
|
34
|
+
pipeline: FontPipeline,
|
|
35
|
+
font: MsdfFont,
|
|
36
|
+
colorFormat: GPUTextureFormat,
|
|
37
|
+
instanceCount: number,
|
|
38
|
+
) {
|
|
39
|
+
this.#device = device;
|
|
40
|
+
this.#font = font;
|
|
41
|
+
this.#pipeline = pipeline.pipeline;
|
|
42
|
+
this.#maxCharCount = pipeline.maxCharCount;
|
|
43
|
+
|
|
44
|
+
this.#descriptorBuffer = device.createBuffer({
|
|
45
|
+
label: "msdf text descriptor buffer",
|
|
46
|
+
size: textDescriptorInstanceSize * instanceCount,
|
|
47
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.#cpuDescriptorBuffer = new Float32Array(
|
|
51
|
+
(instanceCount * textDescriptorInstanceSize) /
|
|
52
|
+
Float32Array.BYTES_PER_ELEMENT,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
this.#cpuTextBlockBuffer = new Float32Array(
|
|
56
|
+
instanceCount * this.maxCharCount * 4,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
this.#engineUniformsBuffer = device.createBuffer({
|
|
60
|
+
label: "msdf view projection matrix",
|
|
61
|
+
size: Float32Array.BYTES_PER_ELEMENT * 12,
|
|
62
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.#textBlockBuffer = device.createBuffer({
|
|
66
|
+
label: "msdf text buffer",
|
|
67
|
+
size:
|
|
68
|
+
instanceCount * this.maxCharCount * 4 * Float32Array.BYTES_PER_ELEMENT,
|
|
69
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// create uniform bind groups
|
|
73
|
+
this.#bindGroups.push(pipeline.fontBindGroup);
|
|
74
|
+
|
|
75
|
+
this.#bindGroups.push(
|
|
76
|
+
device.createBindGroup({
|
|
77
|
+
label: "msdf text bind group",
|
|
78
|
+
layout: pipeline.pipeline.getBindGroupLayout(1),
|
|
79
|
+
entries: [
|
|
80
|
+
{
|
|
81
|
+
binding: 0,
|
|
82
|
+
resource: { buffer: this.#descriptorBuffer },
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
binding: 1,
|
|
86
|
+
resource: { buffer: this.#textBlockBuffer },
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const engineUniformsBindGroup = device.createBindGroup({
|
|
93
|
+
label: "msdf text uniforms bind group",
|
|
94
|
+
layout: pipeline.pipeline.getBindGroupLayout(2),
|
|
95
|
+
entries: [
|
|
96
|
+
{
|
|
97
|
+
binding: 0,
|
|
98
|
+
resource: { buffer: this.#engineUniformsBuffer },
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
});
|
|
102
|
+
this.#bindGroups.push(engineUniformsBindGroup);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
startFrame(device: GPUDevice, uniform: EngineUniform): void {
|
|
106
|
+
device.queue.writeBuffer(
|
|
107
|
+
this.#engineUniformsBuffer,
|
|
108
|
+
0,
|
|
109
|
+
uniform.viewProjectionMatrix as Float32Array,
|
|
110
|
+
);
|
|
111
|
+
this.#instanceIndex = 0;
|
|
112
|
+
this.#textBlockOffset = 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
processBatch(renderPass: GPURenderPassEncoder, nodes: SceneNode[]): number {
|
|
116
|
+
if (nodes.length === 0) return 0;
|
|
117
|
+
|
|
118
|
+
renderPass.setPipeline(this.#pipeline);
|
|
119
|
+
for (let i = 0; i < this.#bindGroups.length; i++) {
|
|
120
|
+
renderPass.setBindGroup(i, this.#bindGroups[i]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const node of nodes) {
|
|
124
|
+
if (!(node instanceof TextNode)) {
|
|
125
|
+
console.error(node);
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Tried to use TextShader on something that isn't a TextNode: ${node}`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const text = node.text;
|
|
131
|
+
const formatting = node.formatting;
|
|
132
|
+
const measurements = measureText(this.#font, text, formatting.wordWrap);
|
|
133
|
+
|
|
134
|
+
const textBlockSize = 4 * text.length;
|
|
135
|
+
|
|
136
|
+
// Calculate the buffer offset to get the current TextBlockDescriptor
|
|
137
|
+
const textDescriptorOffset =
|
|
138
|
+
(this.#instanceIndex * textDescriptorInstanceSize) /
|
|
139
|
+
Float32Array.BYTES_PER_ELEMENT;
|
|
140
|
+
|
|
141
|
+
// Shape text and pack to the buffer...
|
|
142
|
+
this.#cpuDescriptorBuffer.set(node.matrix, textDescriptorOffset);
|
|
143
|
+
|
|
144
|
+
// Color
|
|
145
|
+
this.#cpuDescriptorBuffer.set(
|
|
146
|
+
[node.tint.r, node.tint.g, node.tint.b, node.tint.a],
|
|
147
|
+
textDescriptorOffset + 12,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Font Size
|
|
151
|
+
const size = node.size ?? measurements;
|
|
152
|
+
const fontSize = formatting.shrinkToFit
|
|
153
|
+
? findLargestFontSize(this.#font, text, size, formatting)
|
|
154
|
+
: formatting.fontSize;
|
|
155
|
+
const actualFontSize = fontSize || DEFAULT_FONT_SIZE;
|
|
156
|
+
this.#cpuDescriptorBuffer[textDescriptorOffset + 16] = actualFontSize;
|
|
157
|
+
|
|
158
|
+
// Alignment and dimensions
|
|
159
|
+
this.#cpuDescriptorBuffer[textDescriptorOffset + 17] =
|
|
160
|
+
formatting.align === "center" ? 0 : measurements.width;
|
|
161
|
+
this.#cpuDescriptorBuffer[textDescriptorOffset + 18] =
|
|
162
|
+
measurements.height;
|
|
163
|
+
|
|
164
|
+
// Text block buffer offset
|
|
165
|
+
// the shader at text.wgsl.ts is expecting an index into the text block buffer,
|
|
166
|
+
// which is an array<vec4f> hence the division by 4
|
|
167
|
+
this.#cpuDescriptorBuffer[textDescriptorOffset + 19] =
|
|
168
|
+
this.#textBlockOffset / 4;
|
|
169
|
+
|
|
170
|
+
shapeText(
|
|
171
|
+
this.#font,
|
|
172
|
+
text,
|
|
173
|
+
size,
|
|
174
|
+
actualFontSize,
|
|
175
|
+
formatting,
|
|
176
|
+
this.#cpuTextBlockBuffer,
|
|
177
|
+
this.#textBlockOffset,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Write instance data
|
|
181
|
+
this.#device.queue.writeBuffer(
|
|
182
|
+
this.#descriptorBuffer,
|
|
183
|
+
textDescriptorOffset * Float32Array.BYTES_PER_ELEMENT,
|
|
184
|
+
this.#cpuDescriptorBuffer,
|
|
185
|
+
textDescriptorOffset,
|
|
186
|
+
textDescriptorInstanceSize / Float32Array.BYTES_PER_ELEMENT,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
this.#device.queue.writeBuffer(
|
|
190
|
+
this.#textBlockBuffer,
|
|
191
|
+
this.#textBlockOffset * Float32Array.BYTES_PER_ELEMENT,
|
|
192
|
+
this.#cpuTextBlockBuffer,
|
|
193
|
+
this.#textBlockOffset,
|
|
194
|
+
textBlockSize,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
this.#textBlockOffset += textBlockSize;
|
|
198
|
+
|
|
199
|
+
// Draw text
|
|
200
|
+
renderPass.draw(
|
|
201
|
+
4,
|
|
202
|
+
measurements.printedCharCount,
|
|
203
|
+
4 * this.#instanceIndex,
|
|
204
|
+
0,
|
|
205
|
+
);
|
|
206
|
+
this.#instanceIndex++;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return nodes.length;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
endFrame(): void {
|
|
213
|
+
// No cleanup needed
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
get font() {
|
|
217
|
+
return this.#font;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
get maxCharCount() {
|
|
221
|
+
return this.#maxCharCount;
|
|
222
|
+
}
|
|
223
|
+
}
|
package/src/text/mod.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// references:
|
|
2
|
+
// https://tchayen.com/drawing-text-in-webgpu-using-just-the-font-file
|
|
3
|
+
// https://github.com/Chlumsky/msdfgen/issues/22#issuecomment-234958005
|
|
4
|
+
// https://github.com/pixijs/pixijs/blob/dev/src/scene/text-bitmap/utils/getBitmapTextLayout.ts#L20
|
|
5
|
+
|
|
6
|
+
export type { TextOptions } from "./TextNode";
|
|
7
|
+
export { TextNode } from "./TextNode";
|
|
8
|
+
export { TextShader } from "./TextShader";
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import type { Size } from "../coreTypes/Size";
|
|
2
|
+
import { type MsdfChar, type MsdfFont, WhitespaceKeyCodes } from "./MsdfFont";
|
|
3
|
+
import type { TextFormatting, WordWrapOptions } from "./TextFormatting";
|
|
4
|
+
|
|
5
|
+
// shaping is responsible for cpu-side text shaping:
|
|
6
|
+
// - measuring a text block
|
|
7
|
+
// - calculating the glyph quads for the text block
|
|
8
|
+
// - applying word wrap, shrink-to-fit, justification and other formatting options
|
|
9
|
+
// - returning the text measurements and glyph quads
|
|
10
|
+
const TAB_SPACES = 4;
|
|
11
|
+
|
|
12
|
+
export interface MsdfTextMeasurements {
|
|
13
|
+
/** The width of the text block in em units. */
|
|
14
|
+
width: number;
|
|
15
|
+
/** The height of the text block in em units. */
|
|
16
|
+
height: number;
|
|
17
|
+
/** The width of each line in em units. */
|
|
18
|
+
lineWidths: number[];
|
|
19
|
+
/** The number of lines in the text block. */
|
|
20
|
+
lineCount: number;
|
|
21
|
+
/** The number of characters printed in the text block. */
|
|
22
|
+
printedCharCount: number;
|
|
23
|
+
|
|
24
|
+
/** All words in the text block */
|
|
25
|
+
words: Word[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type Glyph = {
|
|
29
|
+
char: MsdfChar;
|
|
30
|
+
offset: [number, number];
|
|
31
|
+
line: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type Word = {
|
|
35
|
+
glyphs: Glyph[];
|
|
36
|
+
width: number;
|
|
37
|
+
startX: number;
|
|
38
|
+
startY: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function shapeText(
|
|
42
|
+
font: MsdfFont,
|
|
43
|
+
text: string,
|
|
44
|
+
blockSize: Size,
|
|
45
|
+
fontSize: number,
|
|
46
|
+
formatting: TextFormatting,
|
|
47
|
+
textArray: Float32Array,
|
|
48
|
+
initialFloatOffset = 0,
|
|
49
|
+
debug = false,
|
|
50
|
+
) {
|
|
51
|
+
let offset = initialFloatOffset;
|
|
52
|
+
|
|
53
|
+
const measurements = measureText(font, text, formatting.wordWrap);
|
|
54
|
+
const alignment = formatting.align || "left";
|
|
55
|
+
const em2px = fontSize / font.lineHeight;
|
|
56
|
+
// currently there is a code path that sets blockSize.width to measurements.width (em)
|
|
57
|
+
// if blockSize is not explicitly set (px)
|
|
58
|
+
const hackHasExplicitBlock = blockSize.width !== measurements.width;
|
|
59
|
+
|
|
60
|
+
let debugData: any[] | null = null;
|
|
61
|
+
if (debug) {
|
|
62
|
+
debugData = [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const word of measurements.words) {
|
|
66
|
+
for (const glyph of word.glyphs) {
|
|
67
|
+
let lineOffset = 0;
|
|
68
|
+
if (alignment === "center") {
|
|
69
|
+
lineOffset =
|
|
70
|
+
measurements.width * -0.5 -
|
|
71
|
+
(measurements.width - measurements.lineWidths[glyph.line]) * -0.5;
|
|
72
|
+
} else if (alignment === "right") {
|
|
73
|
+
const blockSizeEm = blockSize.width / em2px;
|
|
74
|
+
|
|
75
|
+
const delta = measurements.width - measurements.lineWidths[glyph.line];
|
|
76
|
+
|
|
77
|
+
lineOffset =
|
|
78
|
+
(hackHasExplicitBlock ? blockSizeEm / 2 : measurements.width / 2) -
|
|
79
|
+
measurements.width +
|
|
80
|
+
delta;
|
|
81
|
+
} else if (alignment === "left") {
|
|
82
|
+
const blockSizeEm = blockSize.width / em2px;
|
|
83
|
+
|
|
84
|
+
lineOffset = hackHasExplicitBlock
|
|
85
|
+
? -blockSizeEm / 2
|
|
86
|
+
: -measurements.width / 2;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (debug && debugData) {
|
|
90
|
+
debugData.push({
|
|
91
|
+
line: glyph.line,
|
|
92
|
+
word: word.glyphs.map((g) => g.char.char).join(""),
|
|
93
|
+
glyph: glyph.char.char,
|
|
94
|
+
startX: word.startX,
|
|
95
|
+
glyphX: glyph.offset[0],
|
|
96
|
+
advance: glyph.char.xadvance,
|
|
97
|
+
lineOffset,
|
|
98
|
+
startY: word.startY,
|
|
99
|
+
glyphY: glyph.offset[1],
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
textArray[offset] = word.startX + glyph.offset[0] + lineOffset;
|
|
103
|
+
textArray[offset + 1] = word.startY + glyph.offset[1];
|
|
104
|
+
textArray[offset + 2] = glyph.char.charIndex;
|
|
105
|
+
|
|
106
|
+
offset += 4;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (debug && debugData) {
|
|
111
|
+
console.table(debugData);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Measure the text and return measurements for block + each glyph.
|
|
117
|
+
* @param font - The font to use.
|
|
118
|
+
* @param text - The text to measure
|
|
119
|
+
* @param wordWrap - Maximum word length before word wrapping
|
|
120
|
+
* @returns The measurements of the text in em units.
|
|
121
|
+
*/
|
|
122
|
+
export function measureText(
|
|
123
|
+
font: MsdfFont,
|
|
124
|
+
text: string,
|
|
125
|
+
wordWrap?: WordWrapOptions,
|
|
126
|
+
): MsdfTextMeasurements {
|
|
127
|
+
let maxWidth = 0;
|
|
128
|
+
const lineWidths: number[] = [];
|
|
129
|
+
|
|
130
|
+
let textOffsetX = 0;
|
|
131
|
+
let textOffsetY = 0;
|
|
132
|
+
let line = 0;
|
|
133
|
+
let printedCharCount = 0;
|
|
134
|
+
let nextCharCode = text.charCodeAt(0);
|
|
135
|
+
let word: Word = { glyphs: [], width: 0, startX: 0, startY: 0 };
|
|
136
|
+
|
|
137
|
+
const words: Word[] = [];
|
|
138
|
+
|
|
139
|
+
for (let i = 0; i < text.length; i++) {
|
|
140
|
+
const isLastLetter = i === text.length - 1;
|
|
141
|
+
|
|
142
|
+
const charCode = nextCharCode;
|
|
143
|
+
nextCharCode = i < text.length - 1 ? text.charCodeAt(i + 1) : -1;
|
|
144
|
+
|
|
145
|
+
switch (charCode) {
|
|
146
|
+
case WhitespaceKeyCodes.HorizontalTab:
|
|
147
|
+
insertSpaces(TAB_SPACES);
|
|
148
|
+
break;
|
|
149
|
+
case WhitespaceKeyCodes.Newline:
|
|
150
|
+
flushLine();
|
|
151
|
+
flushWord();
|
|
152
|
+
break;
|
|
153
|
+
case WhitespaceKeyCodes.CarriageReturn:
|
|
154
|
+
break;
|
|
155
|
+
case WhitespaceKeyCodes.Space:
|
|
156
|
+
insertSpaces(1);
|
|
157
|
+
break;
|
|
158
|
+
default: {
|
|
159
|
+
const advance = font.getXAdvance(charCode, nextCharCode);
|
|
160
|
+
if (
|
|
161
|
+
wordWrap &&
|
|
162
|
+
wordWrap.breakOn === "character" &&
|
|
163
|
+
textOffsetX + advance > wordWrap.emWidth
|
|
164
|
+
) {
|
|
165
|
+
if (word.startX === 0) {
|
|
166
|
+
flushWord();
|
|
167
|
+
} else {
|
|
168
|
+
lineWidths.push(textOffsetX - word.width);
|
|
169
|
+
line++;
|
|
170
|
+
maxWidth = Math.max(maxWidth, textOffsetX);
|
|
171
|
+
textOffsetX = word.width;
|
|
172
|
+
textOffsetY -= font.lineHeight;
|
|
173
|
+
word.startX = 0;
|
|
174
|
+
word.startY = textOffsetY;
|
|
175
|
+
word.glyphs.forEach((g) => {
|
|
176
|
+
g.line = line;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
word.glyphs.push({
|
|
181
|
+
char: font.getChar(charCode),
|
|
182
|
+
offset: [word.width, 0],
|
|
183
|
+
line,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (isLastLetter) {
|
|
187
|
+
flushWord();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
word.width += advance;
|
|
191
|
+
textOffsetX += advance;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
lineWidths.push(textOffsetX);
|
|
197
|
+
maxWidth = Math.max(maxWidth, textOffsetX);
|
|
198
|
+
|
|
199
|
+
const lineCount = lineWidths.length;
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
width: maxWidth,
|
|
203
|
+
height: lineCount * font.lineHeight,
|
|
204
|
+
lineWidths,
|
|
205
|
+
lineCount,
|
|
206
|
+
printedCharCount,
|
|
207
|
+
words,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
function flushWord() {
|
|
211
|
+
printedCharCount += word.glyphs.length;
|
|
212
|
+
words.push(word);
|
|
213
|
+
word = {
|
|
214
|
+
glyphs: [],
|
|
215
|
+
width: 0,
|
|
216
|
+
startX: textOffsetX,
|
|
217
|
+
startY: textOffsetY,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function flushLine() {
|
|
222
|
+
lineWidths.push(textOffsetX);
|
|
223
|
+
line++;
|
|
224
|
+
maxWidth = Math.max(maxWidth, textOffsetX);
|
|
225
|
+
textOffsetX = 0;
|
|
226
|
+
textOffsetY -= font.lineHeight;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function insertSpaces(spaces: number) {
|
|
230
|
+
if (spaces < 1) spaces = 1;
|
|
231
|
+
textOffsetX += font.getXAdvance(WhitespaceKeyCodes.Space) * spaces;
|
|
232
|
+
if (wordWrap?.breakOn === "word" && textOffsetX >= wordWrap.emWidth) {
|
|
233
|
+
flushLine();
|
|
234
|
+
}
|
|
235
|
+
flushWord();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export function findLargestFontSize(
|
|
239
|
+
font: MsdfFont,
|
|
240
|
+
text: string,
|
|
241
|
+
size: Size,
|
|
242
|
+
formatting: TextFormatting,
|
|
243
|
+
): number | undefined {
|
|
244
|
+
if (!formatting.fontSize) {
|
|
245
|
+
throw new Error("fontSize is required for shrinkToFit");
|
|
246
|
+
}
|
|
247
|
+
if (!formatting.shrinkToFit) {
|
|
248
|
+
throw new Error("shrinkToFit is required for findLargestFontSize");
|
|
249
|
+
}
|
|
250
|
+
// Binary search to find largest font size that fits
|
|
251
|
+
const minSize = formatting.shrinkToFit.minFontSize;
|
|
252
|
+
const maxSize = formatting.shrinkToFit.maxFontSize ?? formatting.fontSize;
|
|
253
|
+
const maxLines = formatting.shrinkToFit.maxLines ?? Number.POSITIVE_INFINITY;
|
|
254
|
+
|
|
255
|
+
const threshold = 0.5;
|
|
256
|
+
let low = minSize;
|
|
257
|
+
let high = maxSize;
|
|
258
|
+
|
|
259
|
+
while (high - low > threshold) {
|
|
260
|
+
// Stop when we get close enough
|
|
261
|
+
const testSize = (low + high) / 2;
|
|
262
|
+
const testMeasure = measureText(font, text, formatting.wordWrap);
|
|
263
|
+
|
|
264
|
+
const padding = formatting.shrinkToFit.padding ?? 0;
|
|
265
|
+
|
|
266
|
+
const scaledWidth = testMeasure.width * (testSize / font.lineHeight);
|
|
267
|
+
const scaledHeight = testMeasure.height * (testSize / font.lineHeight);
|
|
268
|
+
const fitsWidth = scaledWidth <= size.width - size.width * padding;
|
|
269
|
+
const fitsHeight = scaledHeight <= size.height - size.height * padding;
|
|
270
|
+
const fitsLines = testMeasure.lineCount <= maxLines;
|
|
271
|
+
|
|
272
|
+
if (fitsWidth && fitsHeight && fitsLines) {
|
|
273
|
+
low = testSize;
|
|
274
|
+
} else {
|
|
275
|
+
high = testSize;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return low;
|
|
280
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export default /*wgsl*/ `
|
|
2
|
+
// Adapted from: https://webgpu.github.io/webgpu-samples/?sample=textRenderingMsdf
|
|
3
|
+
|
|
4
|
+
// Quad vertex positions for a character
|
|
5
|
+
const pos = array(
|
|
6
|
+
vec2f(0, -1),
|
|
7
|
+
vec2f(1, -1),
|
|
8
|
+
vec2f(0, 0),
|
|
9
|
+
vec2f(1, 0),
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
// Debug colors for visualization
|
|
13
|
+
const debugColors = array(
|
|
14
|
+
vec4f(1, 0, 0, 1),
|
|
15
|
+
vec4f(0, 1, 0, 1),
|
|
16
|
+
vec4f(0, 0, 1, 1),
|
|
17
|
+
vec4f(1, 1, 1, 1),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// Vertex input from GPU
|
|
21
|
+
struct VertexInput {
|
|
22
|
+
@builtin(vertex_index) vertex: u32,
|
|
23
|
+
@builtin(instance_index) instance: u32,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Output from vertex shader to fragment shader
|
|
27
|
+
struct VertexOutput {
|
|
28
|
+
@builtin(position) position: vec4f,
|
|
29
|
+
@location(0) texcoord: vec2f,
|
|
30
|
+
@location(1) debugColor: vec4f,
|
|
31
|
+
@location(2) @interpolate(flat) instanceIndex: u32,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Metadata for a single character glyph
|
|
35
|
+
struct Char {
|
|
36
|
+
texOffset: vec2f, // Offset to top-left in MSDF texture (pixels)
|
|
37
|
+
texExtent: vec2f, // Size in texture (pixels)
|
|
38
|
+
size: vec2f, // Glyph size in ems
|
|
39
|
+
offset: vec2f, // Position offset in ems
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Metadata for a text block
|
|
43
|
+
struct TextBlockDescriptor {
|
|
44
|
+
transform: mat3x3f, // Text transform matrix (model matrix)
|
|
45
|
+
color: vec4f, // Text color
|
|
46
|
+
fontSize: f32, // Font size
|
|
47
|
+
blockWidth: f32, // Total width of text block
|
|
48
|
+
blockHeight: f32, // Total height of text block
|
|
49
|
+
bufferPosition: f32 // Index and length in textBuffer
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Font bindings
|
|
53
|
+
@group(0) @binding(0) var fontTexture: texture_2d<f32>;
|
|
54
|
+
@group(0) @binding(1) var fontSampler: sampler;
|
|
55
|
+
@group(0) @binding(2) var<storage> chars: array<Char>;
|
|
56
|
+
@group(0) @binding(3) var<uniform> fontData: vec4f; // Contains line height (x)
|
|
57
|
+
|
|
58
|
+
// Text bindings
|
|
59
|
+
@group(1) @binding(0) var<storage> texts: array<TextBlockDescriptor>;
|
|
60
|
+
@group(1) @binding(1) var<storage> textBuffer: array<vec4f>; // Each vec4: xy = glyph pos, z = char index
|
|
61
|
+
|
|
62
|
+
// Global uniforms
|
|
63
|
+
@group(2) @binding(0) var<uniform> viewProjectionMatrix: mat3x3f;
|
|
64
|
+
|
|
65
|
+
// Vertex shader
|
|
66
|
+
@vertex
|
|
67
|
+
fn vertexMain(input: VertexInput) -> VertexOutput {
|
|
68
|
+
// Because the instance index is used for character indexing, we are
|
|
69
|
+
// overloading the vertex index to store the instance of the text metadata.
|
|
70
|
+
//
|
|
71
|
+
// I.e...
|
|
72
|
+
// Vertex 0-4 = Instance 0, Vertex 0-4
|
|
73
|
+
// Vertex 4-8 = Instance 1, Vertex 0-4
|
|
74
|
+
// Vertex 8-12 = Instance 2, Vertex 0-4
|
|
75
|
+
let vertexIndex = input.vertex % 4;
|
|
76
|
+
let textIndex = input.vertex / 4;
|
|
77
|
+
|
|
78
|
+
let text = texts[textIndex];
|
|
79
|
+
let textElement = textBuffer[u32(text.bufferPosition) + input.instance];
|
|
80
|
+
let char = chars[u32(textElement.z)];
|
|
81
|
+
|
|
82
|
+
let lineHeight = fontData.x;
|
|
83
|
+
let textWidth = text.blockWidth;
|
|
84
|
+
let textHeight = text.blockHeight;
|
|
85
|
+
|
|
86
|
+
// Center text vertically; origin is mid-height
|
|
87
|
+
let offset = vec2f(0, -textHeight / 2);
|
|
88
|
+
|
|
89
|
+
// Glyph position in ems (quad pos * size + per-char offset)
|
|
90
|
+
let emPos = pos[vertexIndex] * char.size + char.offset + textElement.xy - offset;
|
|
91
|
+
let charPos = emPos * (text.fontSize / lineHeight);
|
|
92
|
+
|
|
93
|
+
var output: VertexOutput;
|
|
94
|
+
let transformedPosition = viewProjectionMatrix * text.transform * vec3f(charPos, 1);
|
|
95
|
+
|
|
96
|
+
output.position = vec4f(transformedPosition, 1);
|
|
97
|
+
output.texcoord = pos[vertexIndex] * vec2f(1, -1);
|
|
98
|
+
output.texcoord *= char.texExtent;
|
|
99
|
+
output.texcoord += char.texOffset;
|
|
100
|
+
output.debugColor = debugColors[vertexIndex];
|
|
101
|
+
output.instanceIndex = textIndex;
|
|
102
|
+
return output;
|
|
103
|
+
|
|
104
|
+
// To debug - hardcode quad in bottom right quarter of the screen:
|
|
105
|
+
// output.position = vec4f(pos[input.vertex], 0, 1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Signed distance function sampling for MSDF font rendering
|
|
109
|
+
fn sampleMsdf(texcoord: vec2f) -> f32 {
|
|
110
|
+
let c = textureSample(fontTexture, fontSampler, texcoord);
|
|
111
|
+
return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Fragment shader
|
|
115
|
+
// Anti-aliasing technique by Paul Houx
|
|
116
|
+
// more details here:
|
|
117
|
+
// https://github.com/Chlumsky/msdfgen/issues/22#issuecomment-234958005
|
|
118
|
+
@fragment
|
|
119
|
+
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
|
|
120
|
+
let text = texts[input.instanceIndex];
|
|
121
|
+
|
|
122
|
+
// pxRange (AKA distanceRange) comes from the msdfgen tool.
|
|
123
|
+
let pxRange = 4.0;
|
|
124
|
+
let texSize = vec2f(textureDimensions(fontTexture, 0));
|
|
125
|
+
|
|
126
|
+
let dx = texSize.x * length(vec2f(dpdxFine(input.texcoord.x), dpdyFine(input.texcoord.x)));
|
|
127
|
+
let dy = texSize.y * length(vec2f(dpdxFine(input.texcoord.y), dpdyFine(input.texcoord.y)));
|
|
128
|
+
|
|
129
|
+
let toPixels = pxRange * inverseSqrt(dx * dx + dy * dy);
|
|
130
|
+
let sigDist = sampleMsdf(input.texcoord) - 0.5;
|
|
131
|
+
let pxDist = sigDist * toPixels;
|
|
132
|
+
|
|
133
|
+
let edgeWidth = 0.5;
|
|
134
|
+
let alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
|
|
135
|
+
|
|
136
|
+
if (alpha < 0.001) {
|
|
137
|
+
discard;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let msdfColor = vec4f(text.color.rgb, text.color.a * alpha);
|
|
141
|
+
return msdfColor;
|
|
142
|
+
|
|
143
|
+
// Debug options:
|
|
144
|
+
// return text.color;
|
|
145
|
+
// return input.debugColor;
|
|
146
|
+
// return vec4f(1, 0, 1, 1); // hardcoded magenta
|
|
147
|
+
// return textureSample(fontTexture, fontSampler, input.texcoord);
|
|
148
|
+
}
|
|
149
|
+
`;
|