vgpu-render 0.0.0-stage → 0.2.0

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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +605 -2
  3. package/dist/assets/fira-mono-medium-subset.d.ts +4 -0
  4. package/dist/assets/fira-mono-medium-subset.js +889 -0
  5. package/dist/assets/fira-mono-msdf-atlas.d.ts +13 -0
  6. package/dist/assets/fira-mono-msdf-atlas.js +6222 -0
  7. package/dist/camera-2d.d.ts +43 -0
  8. package/dist/camera-2d.js +75 -0
  9. package/dist/color-space.d.ts +32 -0
  10. package/dist/color-space.js +45 -0
  11. package/dist/curve.d.ts +16 -0
  12. package/dist/curve.js +46 -0
  13. package/dist/debug-draw-3d.d.ts +14 -0
  14. package/dist/debug-draw-3d.js +36 -0
  15. package/dist/frame-draw-list-2d.d.ts +177 -0
  16. package/dist/frame-draw-list-2d.js +378 -0
  17. package/dist/frame-draw-list.d.ts +413 -0
  18. package/dist/frame-draw-list.js +556 -0
  19. package/dist/geometry-generators.d.ts +101 -0
  20. package/dist/geometry-generators.js +481 -0
  21. package/dist/gpu-debug-draw-2d.d.ts +48 -0
  22. package/dist/gpu-debug-draw-2d.js +450 -0
  23. package/dist/gpu-debug-draw-3d.d.ts +52 -0
  24. package/dist/gpu-debug-draw-3d.js +892 -0
  25. package/dist/gpu-instancing.d.ts +34 -0
  26. package/dist/gpu-instancing.js +93 -0
  27. package/dist/gpu-text-3d.d.ts +98 -0
  28. package/dist/gpu-text-3d.js +514 -0
  29. package/dist/index.d.ts +21 -0
  30. package/dist/index.js +20 -0
  31. package/dist/infinite-grid-2d.d.ts +73 -0
  32. package/dist/infinite-grid-2d.js +253 -0
  33. package/dist/infinite-grid-3d.d.ts +134 -0
  34. package/dist/infinite-grid-3d.js +407 -0
  35. package/dist/mesh-batch-3d.d.ts +10 -0
  36. package/dist/mesh-batch-3d.js +67 -0
  37. package/dist/shape2d-shader.d.ts +14 -0
  38. package/dist/shape2d-shader.js +288 -0
  39. package/dist/text-atlas.d.ts +176 -0
  40. package/dist/text-atlas.js +498 -0
  41. package/dist/text-font.d.ts +190 -0
  42. package/dist/text-font.js +261 -0
  43. package/dist/text-layout.d.ts +175 -0
  44. package/dist/text-layout.js +390 -0
  45. package/dist/text-msdf-baked.d.ts +41 -0
  46. package/dist/text-msdf-baked.js +117 -0
  47. package/dist/text-msdf-font.d.ts +85 -0
  48. package/dist/text-msdf-font.js +159 -0
  49. package/dist/text-msdf.d.ts +70 -0
  50. package/dist/text-msdf.js +242 -0
  51. package/dist/text-raster.d.ts +40 -0
  52. package/dist/text-raster.js +60 -0
  53. package/dist/text-sdf.d.ts +28 -0
  54. package/dist/text-sdf.js +119 -0
  55. package/dist/vector-math.d.ts +19 -0
  56. package/dist/vector-math.js +37 -0
  57. package/package.json +80 -4
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Instancing plumbing shared by the debug-draw and text renderers.
3
+ *
4
+ * Both renderers have the same shape: one `instances()` collection per primitive kind, an
5
+ * indirect draw whose arguments a one-thread compute shader refreshes each frame, and a
6
+ * transient mesh that only has to supply a vertex count. Keeping the helpers here means the
7
+ * two renderers cannot drift apart on trim, publish or indirect-argument bookkeeping.
8
+ */
9
+ import { type Compute, type Gpu, type StorageBuffer } from "vgpu";
10
+ import type { InstanceCollection, InstanceId } from "vgpu/scene";
11
+ /**
12
+ * Shared WGSL for depth handling, ported from `debug_line_3d_common.gdshaderinc`.
13
+ *
14
+ * `apply_depth_bias` keeps the reference's exponential form so a `-0.001` bias behaves
15
+ * like reference and reference (`depth * exp2(-bias * log2(clip.w / depth))`). `depth_bias`
16
+ * is baked into the source instead of a uniform: the reference sets it once per scene
17
+ * and it keeps the camera uniform layout identical across all pipelines.
18
+ */
19
+ export declare function depthBiasHelpers(depthBias: number): string;
20
+ export declare function identity(): Float32Array;
21
+ /** Instances carry no per-instance transform; the vertex stage places them from attributes. */
22
+ export declare function world(collection: InstanceCollection, id: InstanceId): void;
23
+ /** Removes the instances past `count` and reports whether the collection shrank. */
24
+ export declare function trim(collection: InstanceCollection, ids: InstanceId[], count: number): boolean;
25
+ export declare function indirectArgs(buffer: StorageBuffer, vertexCount: number, instanceCount: number): void;
26
+ /**
27
+ * Copies the current vertex/instance counts into a draw's indirect buffer. The counts live in
28
+ * a uniform so the pass needs no CPU-side buffer writes: one dispatch per draw rewrites the
29
+ * four `drawIndirect` words.
30
+ */
31
+ export declare const INDIRECT_SHADER: import("vgpu").ShaderSource;
32
+ export declare function indirectCompute(gpu: Gpu): Compute;
33
+ /** A six-vertex quad whose corners the vertex stage derives from `vertex_index`. */
34
+ export declare function zeroGeometry(gpu: Gpu): import("vgpu").Geometry;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Instancing plumbing shared by the debug-draw and text renderers.
3
+ *
4
+ * Both renderers have the same shape: one `instances()` collection per primitive kind, an
5
+ * indirect draw whose arguments a one-thread compute shader refreshes each frame, and a
6
+ * transient mesh that only has to supply a vertex count. Keeping the helpers here means the
7
+ * two renderers cannot drift apart on trim, publish or indirect-argument bookkeeping.
8
+ */
9
+ import { compute, geometry } from "vgpu";
10
+ import { prepareShader } from "@vgpu/wgsl/prepare";
11
+ /** Epsilon floors of the reference depth-bias formula (`debug_line_3d_common.gdshaderinc`). */
12
+ const DEPTH_BIAS_EPSILON = 0.000488;
13
+ const NEAR_CLIP_EPSILON = 0.000488;
14
+ /**
15
+ * Shared WGSL for depth handling, ported from `debug_line_3d_common.gdshaderinc`.
16
+ *
17
+ * `apply_depth_bias` keeps the reference's exponential form so a `-0.001` bias behaves
18
+ * like reference and reference (`depth * exp2(-bias * log2(clip.w / depth))`). `depth_bias`
19
+ * is baked into the source instead of a uniform: the reference sets it once per scene
20
+ * and it keeps the camera uniform layout identical across all pipelines.
21
+ */
22
+ export function depthBiasHelpers(depthBias) {
23
+ return `
24
+ const DEPTH_BIAS: f32 = ${depthBias};
25
+ const DEPTH_BIAS_EPSILON: f32 = ${DEPTH_BIAS_EPSILON};
26
+ const NEAR_CLIP_EPSILON: f32 = ${NEAR_CLIP_EPSILON};
27
+
28
+ fn apply_depth_bias(clip: vec4f) -> f32 {
29
+ let bias = clamp(DEPTH_BIAS, -1.0, 1.0);
30
+ if (bias >= 0.0) {
31
+ return clip.z * (1.0 - bias);
32
+ }
33
+ let safe_depth = max(clip.z, DEPTH_BIAS_EPSILON);
34
+ let depth_ratio = max(clip.w / safe_depth - DEPTH_BIAS_EPSILON, DEPTH_BIAS_EPSILON);
35
+ return clip.z * exp2(-bias * log2(depth_ratio));
36
+ }
37
+
38
+ fn clip_near_plane_t(a: vec4f, b: vec4f) -> f32 {
39
+ if (a.z > a.w && b.z <= b.w) {
40
+ let distance_a = a.z - a.w;
41
+ let distance_b = b.z - b.w;
42
+ return clamp(distance_a / (distance_a - distance_b) + NEAR_CLIP_EPSILON, 0.0, 1.0);
43
+ }
44
+ return 0.0;
45
+ }
46
+ `;
47
+ }
48
+ export function identity() {
49
+ return new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
50
+ }
51
+ /** Instances carry no per-instance transform; the vertex stage places them from attributes. */
52
+ export function world(collection, id) {
53
+ collection.setWorld(id, identity());
54
+ }
55
+ /** Removes the instances past `count` and reports whether the collection shrank. */
56
+ export function trim(collection, ids, count) {
57
+ let changed = false;
58
+ for (let index = ids.length - 1; index >= count; index--) {
59
+ collection.remove(ids.pop());
60
+ changed = true;
61
+ }
62
+ return changed;
63
+ }
64
+ export function indirectArgs(buffer, vertexCount, instanceCount) {
65
+ buffer.write(new Uint32Array([vertexCount, instanceCount, 0, 0]));
66
+ }
67
+ /**
68
+ * Copies the current vertex/instance counts into a draw's indirect buffer. The counts live in
69
+ * a uniform so the pass needs no CPU-side buffer writes: one dispatch per draw rewrites the
70
+ * four `drawIndirect` words.
71
+ */
72
+ export const INDIRECT_SHADER = prepareShader(`
73
+ struct Params { vertexCount: u32, instanceCount: u32 }
74
+ @group(0) @binding(0) var<uniform> params: Params;
75
+ @group(0) @binding(1) var<storage, read_write> args: array<u32>;
76
+ @compute @workgroup_size(1)
77
+ fn cs_main() {
78
+ args[0] = params.vertexCount;
79
+ args[1] = params.instanceCount;
80
+ args[2] = 0u;
81
+ args[3] = 0u;
82
+ }
83
+ `);
84
+ export function indirectCompute(gpu) {
85
+ return compute(gpu, INDIRECT_SHADER);
86
+ }
87
+ /** A six-vertex quad whose corners the vertex stage derives from `vertex_index`. */
88
+ export function zeroGeometry(gpu) {
89
+ return geometry(gpu, {
90
+ buffers: [{ attributes: { position: "float32x3" }, data: new Float32Array(18) }],
91
+ vertexCount: 6,
92
+ });
93
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * GPU text renderer.
3
+ *
4
+ * One instanced quad per glyph, sampling a glyph atlas page, exactly the shape reference
5
+ * sprite text takes: `TextPipeline` lays the glyphs out, `FontAtlas` holds the rasterised
6
+ * coverage, and the render pass instantiates one quad per `PositionedGlyph`
7
+ * (`the reference implementation`, `the reference implementation`).
8
+ *
9
+ * The atlas' pages are the layers of one 2D array texture, so growing past a single page costs no
10
+ * extra draw call: the instance carries the layer it samples, and `GlyphAtlas` recycles pages whose
11
+ * glyphs are no longer requested. Only the rectangles written since the last upload are copied, and
12
+ * each mip level is re-sent for the region that maps to it.
13
+ *
14
+ * The atlas stores a tintable alpha mask (RGB = 255, A = coverage) like reference `FontAtlas`,
15
+ * so the fragment stage only has to multiply the coverage into the span colour's alpha. When
16
+ * the glyph source encodes a distance field instead, the fragment stage reconstructs the edge
17
+ * from it, which keeps the glyph sharp at any magnification. Three
18
+ * placement spaces share the pipeline and are selected per instance:
19
+ *
20
+ * Layout and the atlas both work in font pixels and the two world spaces scale the result by
21
+ * `pixelSize`. That split is the reference `Label3D.font_size` / `pixel_size` pair, and it is
22
+ * what lets a block keep a fixed world size while its glyphs are rasterised at any resolution.
23
+ *
24
+ * - `world-plane` builds the block on a fixed local XY plane, so it foreshortens with the
25
+ * camera the way a reference `Label3D` does.
26
+ * - `world-billboard` builds it from the camera's own right/up axes, read out of the view
27
+ * matrix, so the block always faces the camera.
28
+ * - `screen` places it at a top-left origin pixel position with no projection at all.
29
+ *
30
+ * Depth is tested but never written. World text is therefore occluded by geometry in front of
31
+ * it, while screen text sits at the near plane and always wins; not writing depth keeps a
32
+ * glyph's translucent ink box from clipping the glyphs drawn after it.
33
+ */
34
+ import { type FrameComputePass, type FramePass, type Gpu } from "vgpu";
35
+ import type { TextCommand } from "./frame-draw-list.js";
36
+ import { type TextMeasurer } from "./text-layout.js";
37
+ import type { GlyphRasterizer } from "./text-font.js";
38
+ /** Both halves of a font: metrics for layout and pixels for the atlas. */
39
+ export type TextFontSource = GlyphRasterizer & TextMeasurer;
40
+ export type GpuText3DOptions = {
41
+ readonly font: TextFontSource;
42
+ /** Maximum glyph instances. Defaults to 16384. */
43
+ readonly capacity?: number;
44
+ /**
45
+ * Atlas page size. Must be a multiple of 64 so a page row is a multiple of WebGPU's 256 byte
46
+ * `bytesPerRow` alignment. Defaults to 512, reference minimum atlas size.
47
+ */
48
+ readonly atlasSize?: number;
49
+ /**
50
+ * Atlas pages, one array-texture layer each. A glyph that lands in a later page costs no extra
51
+ * draw call, and the atlas is never "full" until every page is. Defaults to four 512 px pages'
52
+ * worth of texels split across the page size chosen: 4 pages at 512 px, one at 1024 px.
53
+ */
54
+ readonly pages?: number;
55
+ /**
56
+ * What a glyph that no page can hold costs. `"skip"` (the default) drops it and counts it in
57
+ * `skippedGlyphs`, which keeps a live frame drawing; `"throw"` raises instead, which is what a test
58
+ * or a tool that would rather fail loudly asks for.
59
+ */
60
+ readonly onFull?: "throw" | "skip";
61
+ /** Depth bias baked into the shader, matching the reference primitive bias. */
62
+ readonly depthBias?: number;
63
+ /**
64
+ * Mip levels for the atlas page, including level 0. A chain is what stops distant text from
65
+ * shimmering, but the packing padding shrinks with every level, so a deep chain eventually
66
+ * bleeds between neighbours. Defaults to 4, which takes a 512 px page down to 64 px.
67
+ */
68
+ readonly mipLevels?: number;
69
+ };
70
+ export declare class GpuText3D {
71
+ #private;
72
+ constructor(gpu: Gpu, options: GpuText3DOptions);
73
+ /**
74
+ * Glyphs dropped so far because every page was full. Non-zero means the atlas is too small for the
75
+ * frame — raise `atlasSize` or `pages` — and characters are quietly missing until it is fixed.
76
+ */
77
+ get skippedGlyphs(): number;
78
+ /**
79
+ * Lays out every block, fills the glyph atlas, and refreshes the instance buffer.
80
+ *
81
+ * `viewportSize` is what turns a world block's font pixels into screen pixels, and that ratio is
82
+ * what the raster rung follows. A caller with no target to measure (the text-reference page) can
83
+ * leave it out; world blocks then fall back to one pixel per unit and only lose sharpness.
84
+ */
85
+ prepare(commands: readonly TextCommand[], camera: {
86
+ readonly viewProjection: ArrayLike<number>;
87
+ readonly view: ArrayLike<number>;
88
+ readonly viewportSize?: readonly [number, number];
89
+ }): void;
90
+ /**
91
+ * Refreshes this draw's indirect arguments inside an already-open compute pass, so a frame
92
+ * only needs one pass for every indirect draw in it.
93
+ */
94
+ dispatchIndirect(pass: FrameComputePass): void;
95
+ draw(pass: FramePass): void;
96
+ get glyphCount(): number;
97
+ destroy(): void;
98
+ }