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.
- package/LICENSE +21 -0
- package/README.md +605 -2
- package/dist/assets/fira-mono-medium-subset.d.ts +4 -0
- package/dist/assets/fira-mono-medium-subset.js +889 -0
- package/dist/assets/fira-mono-msdf-atlas.d.ts +13 -0
- package/dist/assets/fira-mono-msdf-atlas.js +6222 -0
- package/dist/camera-2d.d.ts +43 -0
- package/dist/camera-2d.js +75 -0
- package/dist/color-space.d.ts +32 -0
- package/dist/color-space.js +45 -0
- package/dist/curve.d.ts +16 -0
- package/dist/curve.js +46 -0
- package/dist/debug-draw-3d.d.ts +14 -0
- package/dist/debug-draw-3d.js +36 -0
- package/dist/frame-draw-list-2d.d.ts +177 -0
- package/dist/frame-draw-list-2d.js +378 -0
- package/dist/frame-draw-list.d.ts +413 -0
- package/dist/frame-draw-list.js +556 -0
- package/dist/geometry-generators.d.ts +101 -0
- package/dist/geometry-generators.js +481 -0
- package/dist/gpu-debug-draw-2d.d.ts +48 -0
- package/dist/gpu-debug-draw-2d.js +450 -0
- package/dist/gpu-debug-draw-3d.d.ts +52 -0
- package/dist/gpu-debug-draw-3d.js +892 -0
- package/dist/gpu-instancing.d.ts +34 -0
- package/dist/gpu-instancing.js +93 -0
- package/dist/gpu-text-3d.d.ts +98 -0
- package/dist/gpu-text-3d.js +514 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +20 -0
- package/dist/infinite-grid-2d.d.ts +73 -0
- package/dist/infinite-grid-2d.js +253 -0
- package/dist/infinite-grid-3d.d.ts +134 -0
- package/dist/infinite-grid-3d.js +407 -0
- package/dist/mesh-batch-3d.d.ts +10 -0
- package/dist/mesh-batch-3d.js +67 -0
- package/dist/shape2d-shader.d.ts +14 -0
- package/dist/shape2d-shader.js +288 -0
- package/dist/text-atlas.d.ts +176 -0
- package/dist/text-atlas.js +498 -0
- package/dist/text-font.d.ts +190 -0
- package/dist/text-font.js +261 -0
- package/dist/text-layout.d.ts +175 -0
- package/dist/text-layout.js +390 -0
- package/dist/text-msdf-baked.d.ts +41 -0
- package/dist/text-msdf-baked.js +117 -0
- package/dist/text-msdf-font.d.ts +85 -0
- package/dist/text-msdf-font.js +159 -0
- package/dist/text-msdf.d.ts +70 -0
- package/dist/text-msdf.js +242 -0
- package/dist/text-raster.d.ts +40 -0
- package/dist/text-raster.js +60 -0
- package/dist/text-sdf.d.ts +28 -0
- package/dist/text-sdf.js +119 -0
- package/dist/vector-math.d.ts +19 -0
- package/dist/vector-math.js +37 -0
- 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
|
+
}
|