@liquid-glass-forge/webgl 0.0.0-stage → 0.1.0-rc.1
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/NOTICE +7 -0
- package/README.md +18 -2
- package/dist/atlas.d.ts +111 -0
- package/dist/atlas.js +610 -0
- package/dist/dom.d.ts +169 -0
- package/dist/dom.js +226 -0
- package/dist/effects.d.ts +17 -0
- package/dist/effects.js +17 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/shaders.d.ts +4 -0
- package/dist/shaders.js +69 -0
- package/package.json +41 -4
package/dist/dom.d.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { SourceRect } from '@liquid-glass-forge/core';
|
|
2
|
+
import type { GlassRenderer, RenderRequest, FrameOutput, CpuRendererOptions } from '@liquid-glass-forge/dom';
|
|
3
|
+
import { WebGLAtlas, type WebGLAtlasOptions } from './atlas.js';
|
|
4
|
+
import { type WebGLEffects } from './effects.js';
|
|
5
|
+
export interface OpticalRendererOptions {
|
|
6
|
+
readonly backend?: 'webgl' | 'cpu' | 'auto';
|
|
7
|
+
readonly fallback?: 'none' | 'cpu' | 'material';
|
|
8
|
+
readonly atlas?: WebGLAtlasOptions;
|
|
9
|
+
readonly cpu?: CpuRendererOptions;
|
|
10
|
+
readonly effects?: WebGLEffects;
|
|
11
|
+
readonly surface?: Readonly<{
|
|
12
|
+
radius?: number;
|
|
13
|
+
bevel?: number;
|
|
14
|
+
strength?: number;
|
|
15
|
+
}> | ((request: RenderRequest) => Readonly<{
|
|
16
|
+
radius?: number;
|
|
17
|
+
bevel?: number;
|
|
18
|
+
strength?: number;
|
|
19
|
+
}>);
|
|
20
|
+
readonly sourceRect?: SourceRect | ((request: RenderRequest) => SourceRect);
|
|
21
|
+
readonly quality?: Readonly<{
|
|
22
|
+
maxWidth?: number;
|
|
23
|
+
maxHeight?: number;
|
|
24
|
+
maxPixels?: number;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
/** Optional DOM integration. Core pixel-frame callers and forced CPU retain their
|
|
28
|
+
* old contract. Native scene batches present to prepared stable targets before
|
|
29
|
+
* yielding; standalone canvas frames retain owned backing until release(). */
|
|
30
|
+
export declare class OpticalRenderer implements GlassRenderer {
|
|
31
|
+
#private;
|
|
32
|
+
constructor(options?: OpticalRendererOptions);
|
|
33
|
+
get atlas(): WebGLAtlas | undefined;
|
|
34
|
+
get diagnostics(): Readonly<{
|
|
35
|
+
requestedBackend: "auto" | "cpu" | "webgl";
|
|
36
|
+
effectiveBackend: "cpu" | "material" | "webgl";
|
|
37
|
+
fallbackReason: string | undefined;
|
|
38
|
+
disposed: boolean;
|
|
39
|
+
initialized: boolean;
|
|
40
|
+
gpu: Readonly<{
|
|
41
|
+
backend: 'webgl';
|
|
42
|
+
contextType: "webgl2";
|
|
43
|
+
state: "disposed" | "lost" | "new" | "ready" | "unavailable";
|
|
44
|
+
failure: string | undefined;
|
|
45
|
+
restores: number;
|
|
46
|
+
handleGeneration: number;
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
sourceWidth: number;
|
|
50
|
+
sourceHeight: number;
|
|
51
|
+
renderCount: number;
|
|
52
|
+
batchCount: number;
|
|
53
|
+
uploadCount: number;
|
|
54
|
+
targetBytes: number;
|
|
55
|
+
requestedAttributes: Readonly<{
|
|
56
|
+
alpha: true;
|
|
57
|
+
premultipliedAlpha: true;
|
|
58
|
+
preserveDrawingBuffer: false;
|
|
59
|
+
antialias: false;
|
|
60
|
+
depth: false;
|
|
61
|
+
stencil: false;
|
|
62
|
+
}>;
|
|
63
|
+
attributes: WebGLContextAttributes | null | undefined;
|
|
64
|
+
pool: Readonly<{
|
|
65
|
+
contexts: number;
|
|
66
|
+
leased: number;
|
|
67
|
+
idle: number;
|
|
68
|
+
cap: 4;
|
|
69
|
+
}>;
|
|
70
|
+
resources: Readonly<{
|
|
71
|
+
contexts: 0 | 1;
|
|
72
|
+
textures: number;
|
|
73
|
+
framebuffers: number;
|
|
74
|
+
programs: number;
|
|
75
|
+
shaders: number;
|
|
76
|
+
buffers: 0;
|
|
77
|
+
vertexArrays: 0;
|
|
78
|
+
}>;
|
|
79
|
+
ownedImageBytes: number;
|
|
80
|
+
lastBatch: Readonly<{
|
|
81
|
+
dirty: number;
|
|
82
|
+
gpuSnapshots: number;
|
|
83
|
+
uploadMs: number;
|
|
84
|
+
drawMs: number;
|
|
85
|
+
snapshotMs: number;
|
|
86
|
+
copyMs: number;
|
|
87
|
+
batchMs: number;
|
|
88
|
+
completedAt: readonly number[];
|
|
89
|
+
}>;
|
|
90
|
+
sourceDecoding: "calibrated-srgb-texture" | "explicit-shader";
|
|
91
|
+
decodeProbeMaxByteError: number | undefined;
|
|
92
|
+
colorConvention: "straight sRGB upload; manual linear-premultiplied sampling/blur; calibrated premultiplied sRGB byte presentation";
|
|
93
|
+
storageProfile: "nearest" | "upward";
|
|
94
|
+
}> | undefined;
|
|
95
|
+
cpu: Readonly<{
|
|
96
|
+
backend: 'cpu';
|
|
97
|
+
execution: "main" | "worker";
|
|
98
|
+
disposed: boolean;
|
|
99
|
+
initialized: boolean;
|
|
100
|
+
requested: Readonly<{
|
|
101
|
+
width: number;
|
|
102
|
+
height: number;
|
|
103
|
+
}> | undefined;
|
|
104
|
+
effective: Readonly<{
|
|
105
|
+
width: number;
|
|
106
|
+
height: number;
|
|
107
|
+
}> | undefined;
|
|
108
|
+
requestedWidth: number | undefined;
|
|
109
|
+
requestedHeight: number | undefined;
|
|
110
|
+
effectiveWidth: number | undefined;
|
|
111
|
+
effectiveHeight: number | undefined;
|
|
112
|
+
qualityReduced: boolean;
|
|
113
|
+
reasons: readonly string[];
|
|
114
|
+
quality: Required<{
|
|
115
|
+
readonly maxPixels?: number;
|
|
116
|
+
readonly maxWidth?: number;
|
|
117
|
+
readonly maxHeight?: number;
|
|
118
|
+
readonly maxUpdatesPerSecond?: number;
|
|
119
|
+
readonly maxSourcePixels?: number;
|
|
120
|
+
}>;
|
|
121
|
+
timings: (import("@liquid-glass-forge/cpu").CpuTimings & {
|
|
122
|
+
readonly captureMs: number;
|
|
123
|
+
readonly rateWaitMs: number;
|
|
124
|
+
readonly presentationWaitMs: number;
|
|
125
|
+
}) | undefined;
|
|
126
|
+
surface: Readonly<Required<import("@liquid-glass-forge/dom").CpuSurfaceOptions>> | undefined;
|
|
127
|
+
executor: Readonly<{
|
|
128
|
+
execution: "main" | "worker";
|
|
129
|
+
inFlight: 0 | 1;
|
|
130
|
+
pending: 0 | 1;
|
|
131
|
+
mainCachedBytes: number;
|
|
132
|
+
workerCachedBytes: number;
|
|
133
|
+
sharedKernelBytes: number;
|
|
134
|
+
fixedKernelReservationBytes: number;
|
|
135
|
+
pooledBytes: number;
|
|
136
|
+
inputPooledBytes: number;
|
|
137
|
+
inputPooledBuffers: number;
|
|
138
|
+
leasedBytes: number;
|
|
139
|
+
reservedBytes: number;
|
|
140
|
+
lostBytes: number;
|
|
141
|
+
completedBatches: number;
|
|
142
|
+
discardedBatches: number;
|
|
143
|
+
disposed: boolean;
|
|
144
|
+
fallbackReason?: string;
|
|
145
|
+
}> | undefined;
|
|
146
|
+
}> | undefined;
|
|
147
|
+
quality: {
|
|
148
|
+
maxWidth: number;
|
|
149
|
+
maxHeight: number;
|
|
150
|
+
maxPixels: number;
|
|
151
|
+
};
|
|
152
|
+
size: Readonly<{
|
|
153
|
+
requestedWidth: number;
|
|
154
|
+
requestedHeight: number;
|
|
155
|
+
width: number;
|
|
156
|
+
height: number;
|
|
157
|
+
}> | undefined;
|
|
158
|
+
leasedBytes: number;
|
|
159
|
+
leasedCanvases: number;
|
|
160
|
+
pooledCanvases: number;
|
|
161
|
+
}>;
|
|
162
|
+
initialize(signal: AbortSignal): () => void;
|
|
163
|
+
render(request: RenderRequest, signal: AbortSignal): FrameOutput | Promise<FrameOutput>;
|
|
164
|
+
renderBatch(requests: readonly RenderRequest[], signal: AbortSignal): readonly FrameOutput[] | Promise<readonly FrameOutput[]>;
|
|
165
|
+
releaseSurface(id: string): void;
|
|
166
|
+
dispose(): void;
|
|
167
|
+
}
|
|
168
|
+
export declare const createWebGLRenderer: (options?: Omit<OpticalRendererOptions, 'backend' | 'fallback'>) => OpticalRenderer;
|
|
169
|
+
export declare const createOpticalRenderer: (options: OpticalRendererOptions) => OpticalRenderer;
|
package/dist/dom.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { MAX_PIXELS } from '@liquid-glass-forge/core';
|
|
2
|
+
import { createCpuRenderer } from '@liquid-glass-forge/dom';
|
|
3
|
+
import { WebGLAtlas, WebGLError } from './atlas.js';
|
|
4
|
+
import { effects } from './effects.js';
|
|
5
|
+
function dataObject(value, keys, name) {
|
|
6
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || 'then' in value || Reflect.ownKeys(value).some(k => typeof k !== 'string' || !keys.includes(k)))
|
|
7
|
+
throw new WebGLError('INVALID_OPTIONS', 'Invalid ' + name);
|
|
8
|
+
}
|
|
9
|
+
function surfaceData(value) {
|
|
10
|
+
dataObject(value, ['radius', 'bevel', 'strength'], 'surface');
|
|
11
|
+
for (const [k, v] of Object.entries(value))
|
|
12
|
+
if (v !== undefined && (typeof v !== 'number' || !Number.isFinite(v) || v < (k === 'bevel' ? Number.MIN_VALUE : 0) || v > 1_000_000))
|
|
13
|
+
throw new WebGLError('INVALID_OPTIONS', 'Invalid surface ' + k);
|
|
14
|
+
return Object.freeze({ ...value });
|
|
15
|
+
}
|
|
16
|
+
function rectData(value) {
|
|
17
|
+
dataObject(value, ['x', 'y', 'width', 'height'], 'sourceRect');
|
|
18
|
+
for (const k of ['x', 'y', 'width', 'height']) {
|
|
19
|
+
const v = value[k];
|
|
20
|
+
if (typeof v !== 'number' || !Number.isFinite(v) || v < (k === 'x' || k === 'y' ? 0 : Number.MIN_VALUE) || v > MAX_PIXELS)
|
|
21
|
+
throw new WebGLError('INVALID_OPTIONS', 'Invalid sourceRect ' + k);
|
|
22
|
+
}
|
|
23
|
+
return Object.freeze({ ...value });
|
|
24
|
+
}
|
|
25
|
+
/** Optional DOM integration. Core pixel-frame callers and forced CPU retain their
|
|
26
|
+
* old contract. Native scene batches present to prepared stable targets before
|
|
27
|
+
* yielding; standalone canvas frames retain owned backing until release(). */
|
|
28
|
+
export class OpticalRenderer {
|
|
29
|
+
#options;
|
|
30
|
+
#backend;
|
|
31
|
+
#fallback;
|
|
32
|
+
#quality;
|
|
33
|
+
#atlas;
|
|
34
|
+
#cpu;
|
|
35
|
+
#cleanup;
|
|
36
|
+
#signal;
|
|
37
|
+
#disposed = false;
|
|
38
|
+
#generation = 0;
|
|
39
|
+
#revision = 0;
|
|
40
|
+
#effective = 'material';
|
|
41
|
+
#reason;
|
|
42
|
+
#lastGPU;
|
|
43
|
+
#tiles = new Map();
|
|
44
|
+
#leased = new Set();
|
|
45
|
+
#pool = [];
|
|
46
|
+
#leasedBytes = 0;
|
|
47
|
+
#mode;
|
|
48
|
+
#size;
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
if (!options || typeof options !== 'object' || Array.isArray(options) || Reflect.ownKeys(options).some(k => typeof k !== 'string' || !['backend', 'fallback', 'atlas', 'cpu', 'effects', 'surface', 'sourceRect', 'quality'].includes(k)))
|
|
51
|
+
throw new WebGLError('INVALID_OPTIONS', 'Invalid optical renderer options');
|
|
52
|
+
const backend = options.backend === undefined ? 'webgl' : options.backend, fallback = options.fallback === undefined ? 'none' : options.fallback;
|
|
53
|
+
if (!['webgl', 'cpu', 'auto'].includes(backend) || !['none', 'cpu', 'material'].includes(fallback) || (backend === 'auto' && options.fallback === undefined) || (backend !== 'auto' && fallback !== 'none'))
|
|
54
|
+
throw new WebGLError('INVALID_OPTIONS', 'Auto policy requires explicit fallback; forced backends never downgrade');
|
|
55
|
+
effects(options.effects);
|
|
56
|
+
new WebGLAtlas(options.atlas);
|
|
57
|
+
const quality = options.quality === undefined ? {} : options.quality;
|
|
58
|
+
dataObject(quality, ['maxWidth', 'maxHeight', 'maxPixels'], 'quality');
|
|
59
|
+
const q = { maxWidth: quality.maxWidth === undefined ? 2048 : quality.maxWidth, maxHeight: quality.maxHeight === undefined ? 2048 : quality.maxHeight, maxPixels: quality.maxPixels === undefined ? MAX_PIXELS : quality.maxPixels };
|
|
60
|
+
if (!Object.values(q).every(v => Number.isSafeInteger(v) && v >= 1 && v <= MAX_PIXELS))
|
|
61
|
+
throw new WebGLError('INVALID_OPTIONS', 'Invalid quality limits');
|
|
62
|
+
// Use the existing data-only CPU validator, including exact fallback settings.
|
|
63
|
+
if (options.cpu !== undefined) {
|
|
64
|
+
const validator = createCpuRenderer(options.cpu);
|
|
65
|
+
validator.dispose();
|
|
66
|
+
}
|
|
67
|
+
const cpu = options.cpu === undefined ? undefined : Object.freeze({ ...options.cpu, ...(options.cpu.effects ? { effects: Object.freeze({ ...options.cpu.effects, ...(options.cpu.effects.tint ? { tint: Object.freeze([...options.cpu.effects.tint]) } : {}), ...(options.cpu.effects.light ? { light: Object.freeze([...options.cpu.effects.light]) } : {}) }) } : {}), ...(options.cpu.executor ? { executor: Object.freeze({ ...options.cpu.executor }) } : {}), ...(options.cpu.quality ? { quality: Object.freeze({ ...options.cpu.quality }) } : {}), ...(options.cpu.surface && typeof options.cpu.surface !== 'function' ? { surface: surfaceData(options.cpu.surface) } : {}), ...(options.cpu.sourceRect && typeof options.cpu.sourceRect !== 'function' ? { sourceRect: rectData(options.cpu.sourceRect) } : {}) });
|
|
68
|
+
this.#options = { ...options, ...(cpu ? { cpu } : {}), ...(options.atlas ? { atlas: Object.freeze({ ...options.atlas }) } : {}), ...(options.surface === undefined ? {} : { surface: typeof options.surface === 'function' ? options.surface : surfaceData(options.surface) }), ...(options.sourceRect === undefined ? {} : { sourceRect: typeof options.sourceRect === 'function' ? options.sourceRect : rectData(options.sourceRect) }), ...(options.effects ? { effects: Object.freeze({ ...options.effects, ...(options.effects.tint ? { tint: Object.freeze([...options.effects.tint]) } : {}), ...(options.effects.light ? { light: Object.freeze([...options.effects.light]) } : {}) }) } : {}) };
|
|
69
|
+
this.#quality = Object.freeze(q);
|
|
70
|
+
this.#backend = backend;
|
|
71
|
+
this.#fallback = fallback;
|
|
72
|
+
}
|
|
73
|
+
get atlas() { return this.#atlas; }
|
|
74
|
+
get diagnostics() { return Object.freeze({ requestedBackend: this.#backend, effectiveBackend: this.#effective, fallbackReason: this.#reason, disposed: this.#disposed, initialized: !!this.#cleanup, gpu: this.#atlas?.diagnostics ?? this.#lastGPU, cpu: this.#cpu?.diagnostics, quality: this.#quality, size: this.#size, leasedBytes: this.#leasedBytes, leasedCanvases: this.#leased.size, pooledCanvases: this.#pool.length }); }
|
|
75
|
+
#makeCPU() { return createCpuRenderer({ ...this.#options.cpu, ...(this.#options.surface ? { surface: this.#options.surface } : {}), ...(this.#options.effects ? { effects: this.#options.effects } : {}), ...(this.#options.sourceRect ? { sourceRect: this.#options.sourceRect } : {}) }); }
|
|
76
|
+
initialize(signal) {
|
|
77
|
+
if (this.#disposed)
|
|
78
|
+
throw new WebGLError('DISPOSED', 'Renderer disposed');
|
|
79
|
+
if (signal.aborted)
|
|
80
|
+
throw new WebGLError('ABORTED', 'Initialization aborted');
|
|
81
|
+
this.#cleanup?.();
|
|
82
|
+
const generation = ++this.#generation;
|
|
83
|
+
this.#signal = signal;
|
|
84
|
+
this.#mode = undefined;
|
|
85
|
+
this.#reason = undefined;
|
|
86
|
+
let cpuCleanup, done = false;
|
|
87
|
+
const fallback = (cause) => { this.#reason = cause instanceof WebGLError ? cause.code : 'WEBGL_FAILED'; if (this.#atlas) {
|
|
88
|
+
this.#atlas.dispose();
|
|
89
|
+
this.#lastGPU = this.#atlas.diagnostics;
|
|
90
|
+
this.#atlas = undefined;
|
|
91
|
+
} if (this.#fallback === 'none')
|
|
92
|
+
throw cause; if (this.#fallback === 'cpu') {
|
|
93
|
+
this.#cpu = this.#makeCPU();
|
|
94
|
+
cpuCleanup = this.#cpu.initialize(signal);
|
|
95
|
+
this.#effective = 'cpu';
|
|
96
|
+
}
|
|
97
|
+
else
|
|
98
|
+
this.#effective = 'material'; };
|
|
99
|
+
try {
|
|
100
|
+
if (this.#backend === 'cpu') {
|
|
101
|
+
this.#cpu = this.#makeCPU();
|
|
102
|
+
cpuCleanup = this.#cpu.initialize(signal);
|
|
103
|
+
this.#effective = 'cpu';
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
this.#atlas = new WebGLAtlas(this.#options.atlas);
|
|
107
|
+
try {
|
|
108
|
+
this.#atlas.initialize();
|
|
109
|
+
this.#effective = 'webgl';
|
|
110
|
+
}
|
|
111
|
+
catch (cause) {
|
|
112
|
+
fallback(cause);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (cause) {
|
|
117
|
+
this.#atlas?.dispose();
|
|
118
|
+
this.#atlas = undefined;
|
|
119
|
+
this.#cpu?.dispose();
|
|
120
|
+
this.#cpu = undefined;
|
|
121
|
+
this.#signal = undefined;
|
|
122
|
+
throw cause;
|
|
123
|
+
}
|
|
124
|
+
const cleanup = () => { if (done)
|
|
125
|
+
return; done = true; signal.removeEventListener('abort', cleanup); if (generation !== this.#generation)
|
|
126
|
+
return; this.#generation++; cpuCleanup?.(); this.#cpu?.dispose(); this.#cpu = undefined; if (this.#atlas) {
|
|
127
|
+
this.#atlas.dispose();
|
|
128
|
+
this.#lastGPU = this.#atlas.diagnostics;
|
|
129
|
+
this.#atlas = undefined;
|
|
130
|
+
} this.#tiles.clear(); for (const c of this.#pool)
|
|
131
|
+
c.width = c.height = 0; this.#pool = []; this.#signal = undefined; this.#cleanup = undefined; };
|
|
132
|
+
this.#cleanup = cleanup;
|
|
133
|
+
signal.addEventListener('abort', cleanup, { once: true });
|
|
134
|
+
if (signal.aborted)
|
|
135
|
+
cleanup();
|
|
136
|
+
return cleanup;
|
|
137
|
+
}
|
|
138
|
+
#current(signal) { if (this.#disposed)
|
|
139
|
+
throw new WebGLError('DISPOSED', 'Renderer disposed'); if (!this.#cleanup)
|
|
140
|
+
throw new WebGLError('NOT_INITIALIZED', 'Initialize renderer'); if (signal.aborted || this.#signal?.aborted)
|
|
141
|
+
throw new WebGLError('ABORTED', 'Render aborted'); }
|
|
142
|
+
#bounded(request) { if (!Number.isSafeInteger(request.width) || !Number.isSafeInteger(request.height) || request.width < 1 || request.height < 1 || request.width * request.height > MAX_PIXELS)
|
|
143
|
+
throw new WebGLError('SIZE_UNSUPPORTED', 'Invalid requested size'); const q = this.#quality, s = Math.min(1, q.maxWidth / request.width, q.maxHeight / request.height, Math.sqrt(q.maxPixels / request.width / request.height)); let w = Math.max(1, Math.floor(request.width * s)), h = Math.max(1, Math.floor(request.height * s)); if (w * h > q.maxPixels) {
|
|
144
|
+
if (w >= h)
|
|
145
|
+
w = Math.max(1, Math.floor(q.maxPixels / h));
|
|
146
|
+
else
|
|
147
|
+
h = Math.max(1, Math.floor(q.maxPixels / w));
|
|
148
|
+
} this.#size = Object.freeze({ requestedWidth: request.width, requestedHeight: request.height, width: w, height: h }); return { width: w, height: h }; }
|
|
149
|
+
#canvas(width, height) { const bytes = width * height * 4; if (this.#leasedBytes + bytes > 16 * 1024 ** 2 || this.#leased.size >= 16)
|
|
150
|
+
throw new WebGLError('LEASE_LIMIT', 'Outstanding canvas frames exceed bounds'); const c = this.#pool.pop() ?? document.createElement('canvas'); c.width = width; c.height = height; this.#leased.add(c); this.#leasedBytes += bytes; const generation = this.#generation; let released = false; return { canvas: c, release: () => { if (released)
|
|
151
|
+
return; released = true; this.#leased.delete(c); this.#leasedBytes -= bytes; if (!this.#disposed && generation === this.#generation && this.#pool.length < 2)
|
|
152
|
+
this.#pool.push(c);
|
|
153
|
+
else
|
|
154
|
+
c.width = c.height = 0; } }; }
|
|
155
|
+
render(request, signal) { const result = this.renderBatch([request], signal); return result instanceof Promise ? result.then(values => values[0]) : result[0]; }
|
|
156
|
+
renderBatch(requests, signal) {
|
|
157
|
+
this.#current(signal);
|
|
158
|
+
if (!Array.isArray(requests) || !requests.length || requests.length > 16 || requests.reduce((n, r) => n + r.width * r.height, 0) > MAX_PIXELS)
|
|
159
|
+
throw new WebGLError('BATCH_LIMIT', 'Core scene frame bounds apply');
|
|
160
|
+
if (this.#effective === 'cpu')
|
|
161
|
+
return this.#cpuBatch(requests, signal);
|
|
162
|
+
if (this.#effective === 'material')
|
|
163
|
+
return requests.map(r => { const size = this.#bounded(r); return { ...size, data: new Uint8ClampedArray(size.width * size.height * 4) }; });
|
|
164
|
+
const first = requests[0], source = first.snapshot.source, tiles = new Map(this.#tiles), leases = [];
|
|
165
|
+
try {
|
|
166
|
+
const frames = requests.map((r, i) => {
|
|
167
|
+
if (r.snapshot.source !== source || r.snapshot.generation !== first.snapshot.generation)
|
|
168
|
+
throw new WebGLError('INVALID_BATCH', 'A batch shares one source snapshot');
|
|
169
|
+
const mode = r.prepareTarget ? 'scene' : 'manual';
|
|
170
|
+
if (this.#mode && this.#mode !== mode)
|
|
171
|
+
throw new WebGLError('INVALID_OPTIONS', 'One renderer belongs to one scene or standalone owner');
|
|
172
|
+
this.#mode = mode;
|
|
173
|
+
if (mode === 'manual' && i === 0)
|
|
174
|
+
tiles.clear();
|
|
175
|
+
const size = this.#bounded(r), id = r.id ?? 'manual_' + i;
|
|
176
|
+
const surfaceOptions = surfaceData(typeof this.#options.surface === 'function' ? this.#options.surface(r) : (this.#options.surface ?? {}));
|
|
177
|
+
const rect = this.#options.sourceRect === undefined ? undefined : rectData(typeof this.#options.sourceRect === 'function' ? this.#options.sourceRect(r) : this.#options.sourceRect);
|
|
178
|
+
const surface = { id, ...size, ...surfaceOptions, ...(rect ? { sourceRect: rect } : {}) };
|
|
179
|
+
const lease = r.prepareTarget ? undefined : this.#canvas(size.width, size.height);
|
|
180
|
+
if (lease)
|
|
181
|
+
leases.push(lease);
|
|
182
|
+
const target = r.prepareTarget ? r.prepareTarget(size.width, size.height) : lease.canvas;
|
|
183
|
+
tiles.set(id, { surface, target });
|
|
184
|
+
return r.prepareTarget ? { ...size, presentedTarget: target } : { ...size, canvas: target, release: lease.release };
|
|
185
|
+
});
|
|
186
|
+
this.#current(signal);
|
|
187
|
+
this.#atlas.update(source, [...tiles.values()], { generation: first.snapshot.generation, revision: ++this.#revision, dirtyIds: requests.map((r, i) => r.id ?? 'manual_' + i), ...(this.#options.effects ? { effects: this.#options.effects } : {}), signal });
|
|
188
|
+
this.#tiles = tiles;
|
|
189
|
+
return frames;
|
|
190
|
+
}
|
|
191
|
+
catch (cause) {
|
|
192
|
+
leases.forEach(l => l.release());
|
|
193
|
+
if (this.#backend !== 'auto' || this.#fallback === 'none' || signal.aborted || !(cause instanceof WebGLError) || ['INVALID_OPTIONS', 'INVALID_BATCH', 'INVALID_TARGET', 'BATCH_LIMIT', 'LEASE_LIMIT', 'STALE'].includes(cause.code))
|
|
194
|
+
throw cause;
|
|
195
|
+
this.#reason = cause instanceof WebGLError ? cause.code : 'WEBGL_FAILED';
|
|
196
|
+
if (this.#atlas) {
|
|
197
|
+
this.#atlas.dispose();
|
|
198
|
+
this.#lastGPU = this.#atlas.diagnostics;
|
|
199
|
+
this.#atlas = undefined;
|
|
200
|
+
}
|
|
201
|
+
this.#tiles.clear();
|
|
202
|
+
if (this.#fallback === 'cpu') {
|
|
203
|
+
this.#cpu = this.#makeCPU();
|
|
204
|
+
this.#cpu.initialize(this.#signal);
|
|
205
|
+
this.#effective = 'cpu';
|
|
206
|
+
return this.#cpuBatch(requests, signal);
|
|
207
|
+
}
|
|
208
|
+
this.#effective = 'material';
|
|
209
|
+
return this.renderBatch(requests, signal);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async #cpuBatch(requests, signal) { const frames = []; try {
|
|
213
|
+
for (const r of requests)
|
|
214
|
+
frames.push(await this.#cpu.render(r, signal));
|
|
215
|
+
return frames;
|
|
216
|
+
}
|
|
217
|
+
catch (cause) {
|
|
218
|
+
frames.forEach(f => f.release?.());
|
|
219
|
+
throw cause;
|
|
220
|
+
} }
|
|
221
|
+
releaseSurface(id) { this.#tiles.delete(id); }
|
|
222
|
+
dispose() { if (this.#disposed)
|
|
223
|
+
return; this.#disposed = true; this.#cleanup?.(); }
|
|
224
|
+
}
|
|
225
|
+
export const createWebGLRenderer = (options = {}) => new OpticalRenderer({ ...options, backend: 'webgl' });
|
|
226
|
+
export const createOpticalRenderer = (options) => new OpticalRenderer(options);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface WebGLEffects {
|
|
2
|
+
readonly model: 'rounded-material-v1';
|
|
3
|
+
readonly blurRadius?: number;
|
|
4
|
+
readonly dispersion?: number;
|
|
5
|
+
readonly tint?: readonly [number, number, number, number];
|
|
6
|
+
readonly rim?: number;
|
|
7
|
+
readonly specular?: number;
|
|
8
|
+
readonly light?: readonly [number, number];
|
|
9
|
+
}
|
|
10
|
+
export declare function effects(input?: WebGLEffects): Readonly<{
|
|
11
|
+
blur: number;
|
|
12
|
+
dispersion: number;
|
|
13
|
+
rim: number;
|
|
14
|
+
specular: number;
|
|
15
|
+
tint: readonly [number, number, number, number];
|
|
16
|
+
light: readonly [number, number];
|
|
17
|
+
}>;
|
package/dist/effects.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { decodeSrgb, ContractError } from '@liquid-glass-forge/core';
|
|
2
|
+
export function effects(input) {
|
|
3
|
+
if (input !== undefined && (!input || typeof input !== 'object' || Array.isArray(input) ||
|
|
4
|
+
Reflect.ownKeys(input).some(k => typeof k !== 'string' || !['model', 'blurRadius', 'dispersion', 'tint', 'rim', 'specular', 'light'].includes(k)) || input.model !== 'rounded-material-v1'))
|
|
5
|
+
throw new ContractError('Invalid WebGL material');
|
|
6
|
+
const blur = input?.blurRadius === undefined ? 0 : input.blurRadius, dispersion = input?.dispersion === undefined ? 0 : input.dispersion, rim = input?.rim === undefined ? 0 : input.rim, specular = input?.specular === undefined ? 0 : input.specular;
|
|
7
|
+
if (!Number.isInteger(blur) || blur < 0 || blur > 16 || ![dispersion, rim, specular].every(v => typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 1))
|
|
8
|
+
throw new ContractError('Invalid WebGL effect range');
|
|
9
|
+
const tint = input?.tint === undefined ? [0, 0, 0, 0] : input.tint, light = input?.light === undefined ? [-Math.SQRT1_2, -Math.SQRT1_2] : input.light;
|
|
10
|
+
if (!Array.isArray(tint) || tint.length !== 4 || !tint.every(v => Number.isFinite(v) && v >= 0 && v <= 1) || !Array.isArray(light) || light.length !== 2 || !light.every(Number.isFinite))
|
|
11
|
+
throw new ContractError('Invalid tint or light');
|
|
12
|
+
const scale = Math.max(Math.abs(light[0]), Math.abs(light[1]));
|
|
13
|
+
if (!scale)
|
|
14
|
+
throw new ContractError('Light must be nonzero');
|
|
15
|
+
const x = light[0] / scale, y = light[1] / scale, length = Math.hypot(x, y);
|
|
16
|
+
return Object.freeze({ blur, dispersion, rim, specular, tint: [decodeSrgb(tint[0]), decodeSrgb(tint[1]), decodeSrgb(tint[2]), tint[3]], light: [x / length, y / length] });
|
|
17
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './atlas.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const VERTEX = "#version 300 es\nvoid main(){vec2 p=vec2(float((gl_VertexID<<1)&2),float(gl_VertexID&2));gl_Position=vec4(p*2.0-1.0,0,1);}";
|
|
2
|
+
export declare const OPTICS = "#version 300 es\nprecision highp float;\nprecision highp int;\nuniform highp sampler2D uSource;\nuniform highp sampler2D uEncoding;\nuniform vec2 uSize,uOrigin,uLight;\nuniform vec4 uRect,uTint;\nuniform float uRadius,uBevel,uStrength,uDispersion,uRim,uSpecular;\nuniform bool uPrepared,uQuantizeUp,uSourceLinear,uCalibration;\nout vec4 result;\nfloat encodeByte(float v){int i=min(int(v*4096.0),4095);vec3 e=texelFetch(uEncoding,ivec2(i&63,i>>6),0).rgb;return e.r+(v>=e.b?1.0:(v<e.g?-1.0:0.0));}\nfloat decodeByte(float v){return v<=.04045?v/12.92:pow((v+.055)/1.055,2.4);}\nvec4 texel(ivec2 p){vec4 c=texelFetch(uSource,p,0);return uPrepared?c:(uSourceLinear?vec4(c.rgb*c.a,c.a):vec4(vec3(decodeByte(c.r),decodeByte(c.g),decodeByte(c.b))*c.a,c.a));}\nvec4 sampleAt(vec2 p){ivec2 s=textureSize(uSource,0);p=clamp(p,vec2(0.0),vec2(float(s.x-1),float(s.y-1)));ivec2 lo=ivec2(floor(p)),hi=min(lo+1,s-1);vec2 t=fract(p);\nif(t.x==0.0&&t.y==0.0)return texel(lo);\nreturn (texel(lo)*(1.0-t.x)+texel(ivec2(hi.x,lo.y))*t.x)*(1.0-t.y)+(texel(ivec2(lo.x,hi.y))*(1.0-t.x)+texel(hi)*t.x)*t.y;}\nvoid main(){\nif(uCalibration){vec4 c=texelFetch(uSource,ivec2(int(gl_FragCoord.x),0),0);float n=floor(c.r*65535.0+.5);result=vec4(floor(n/256.0)/255.0,mod(n,256.0)/255.0,0,1);return;}\nvec2 local=gl_FragCoord.xy-uOrigin,center=vec2(local.x,uSize.y-local.y),p=center-uSize*.5;\nvec2 q=abs(p)-(uSize*.5-uRadius),a=max(q,0.0);float len=length(a),d=len+min(max(q.x,q.y),0.0)-uRadius;\nfloat mask=clamp(.5-d,0.0,1.0);if(mask==0.0){result=vec4(0.0);return;}\nfloat w=clamp(1.0+d/uBevel,0.0,1.0);w*=w;\nvec2 n=w>0.0?(len>0.0?a/len*sign(p):(q.x>q.y?vec2(sign(p.x),0.0):vec2(0.0,sign(p.y)))):vec2(0.0);\nvec2 scale=uRect.zw/uSize,base=uRect.xy+center*scale-.5,delta=n*uStrength*w*scale;\n// An exact source texel with all optional passes disabled is byte identity.\n// Avoid decode/interpolate/encode work only where bilinear weights are exactly zero.\nif(!uPrepared&&!uSourceLinear&&uDispersion==0.0&&uTint.a==0.0&&uRim==0.0&&uSpecular==0.0){\n vec2 at=clamp(base+delta,vec2(0.0),vec2(float(textureSize(uSource,0).x-1),float(textureSize(uSource,0).y-1)));\n if(all(equal(fract(at),vec2(0.0)))){vec4 raw=texelFetch(uSource,ivec2(at),0);float alpha=floor(raw.a*mask*255.0+.5);ivec3 bytes=ivec3(floor(raw.rgb*255.0+.5));ivec3 premult=(bytes*int(alpha)+(uQuantizeUp?254:127))/255;result=alpha==0.0?vec4(0.0):vec4(vec3(premult)/255.0,alpha/255.0);return;}\n}\nvec4 c=sampleAt(base+delta);float alpha=floor(c.a*mask*255.0+.5);\nif(alpha==0.0){result=vec4(0.0);return;}\nvec3 rgb=c.a>0.0?c.rgb/c.a:vec3(0.0);\nif(uDispersion>0.0){vec4 r=sampleAt(base+delta*(1.0+uDispersion)),b=sampleAt(base+delta*(1.0-uDispersion));rgb.r=r.a>0.0?r.r/r.a:0.0;rgb.b=b.a>0.0?b.b/b.a:0.0;}\nif(uTint.a>0.0)rgb=mix(rgb,uTint.rgb,uTint.a);\nif(w>0.0&&(uRim>0.0||uSpecular>0.0)){float l=max(dot(n,uLight),0.0);l*=l;l*=l;l*=l;float h=clamp(w*(uRim+uSpecular*l),0.0,1.0);rgb=clamp(rgb+(1.0-rgb)*h,0.0,1.0);}\nivec3 bytes=ivec3(encodeByte(rgb.r),encodeByte(rgb.g),encodeByte(rgb.b));\nivec3 premult=(bytes*int(alpha)+(uQuantizeUp?254:127))/255;\nresult=vec4(vec3(premult)/255.0,alpha/255.0);\n}";
|
|
3
|
+
export declare const BLUR = "#version 300 es\nprecision highp float;\nprecision highp int;\nuniform highp sampler2D uSource;\nuniform highp sampler2D uEncoding;\nuniform ivec2 uAxis;\nuniform int uRadius;\nuniform bool uPrepared,uSourceLinear;\nout vec4 result;\nfloat decodeByte(float v){return v<=.04045?v/12.92:pow((v+.055)/1.055,2.4);}\nvoid main(){ivec2 p=ivec2(gl_FragCoord.xy),size=textureSize(uSource,0);vec4 sum=vec4(0.0);\nfor(int k=-16;k<=16;k++){if(abs(k)<=uRadius){vec4 c=texelFetch(uSource,clamp(p+uAxis*k,ivec2(0),size-1),0);sum+=uPrepared?c:(uSourceLinear?vec4(c.rgb*c.a,c.a):vec4(vec3(decodeByte(c.r),decodeByte(c.g),decodeByte(c.b))*c.a,c.a));}}\nresult=sum/float(2*uRadius+1);\n}";
|
|
4
|
+
export declare function encodingTextureData(): Float32Array;
|
package/dist/shaders.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export const VERTEX = `#version 300 es
|
|
2
|
+
void main(){vec2 p=vec2(float((gl_VertexID<<1)&2),float(gl_VertexID&2));gl_Position=vec4(p*2.0-1.0,0,1);}`;
|
|
3
|
+
// Same versioned equations as CONTRACTS.md and PHASE-5.md. The scalar oracle
|
|
4
|
+
// remains independent; production never obtains pixels from a GPU framebuffer.
|
|
5
|
+
// Explicit float constructors avoid ANGLE/D3D11 generating an ambiguous HLSL
|
|
6
|
+
// vec2_ctor_int overload for mixed integer and floating-point arguments.
|
|
7
|
+
export const OPTICS = `#version 300 es
|
|
8
|
+
precision highp float;
|
|
9
|
+
precision highp int;
|
|
10
|
+
uniform highp sampler2D uSource;
|
|
11
|
+
uniform highp sampler2D uEncoding;
|
|
12
|
+
uniform vec2 uSize,uOrigin,uLight;
|
|
13
|
+
uniform vec4 uRect,uTint;
|
|
14
|
+
uniform float uRadius,uBevel,uStrength,uDispersion,uRim,uSpecular;
|
|
15
|
+
uniform bool uPrepared,uQuantizeUp,uSourceLinear,uCalibration;
|
|
16
|
+
out vec4 result;
|
|
17
|
+
float encodeByte(float v){int i=min(int(v*4096.0),4095);vec3 e=texelFetch(uEncoding,ivec2(i&63,i>>6),0).rgb;return e.r+(v>=e.b?1.0:(v<e.g?-1.0:0.0));}
|
|
18
|
+
float decodeByte(float v){return v<=.04045?v/12.92:pow((v+.055)/1.055,2.4);}
|
|
19
|
+
vec4 texel(ivec2 p){vec4 c=texelFetch(uSource,p,0);return uPrepared?c:(uSourceLinear?vec4(c.rgb*c.a,c.a):vec4(vec3(decodeByte(c.r),decodeByte(c.g),decodeByte(c.b))*c.a,c.a));}
|
|
20
|
+
vec4 sampleAt(vec2 p){ivec2 s=textureSize(uSource,0);p=clamp(p,vec2(0.0),vec2(float(s.x-1),float(s.y-1)));ivec2 lo=ivec2(floor(p)),hi=min(lo+1,s-1);vec2 t=fract(p);
|
|
21
|
+
if(t.x==0.0&&t.y==0.0)return texel(lo);
|
|
22
|
+
return (texel(lo)*(1.0-t.x)+texel(ivec2(hi.x,lo.y))*t.x)*(1.0-t.y)+(texel(ivec2(lo.x,hi.y))*(1.0-t.x)+texel(hi)*t.x)*t.y;}
|
|
23
|
+
void main(){
|
|
24
|
+
if(uCalibration){vec4 c=texelFetch(uSource,ivec2(int(gl_FragCoord.x),0),0);float n=floor(c.r*65535.0+.5);result=vec4(floor(n/256.0)/255.0,mod(n,256.0)/255.0,0,1);return;}
|
|
25
|
+
vec2 local=gl_FragCoord.xy-uOrigin,center=vec2(local.x,uSize.y-local.y),p=center-uSize*.5;
|
|
26
|
+
vec2 q=abs(p)-(uSize*.5-uRadius),a=max(q,0.0);float len=length(a),d=len+min(max(q.x,q.y),0.0)-uRadius;
|
|
27
|
+
float mask=clamp(.5-d,0.0,1.0);if(mask==0.0){result=vec4(0.0);return;}
|
|
28
|
+
float w=clamp(1.0+d/uBevel,0.0,1.0);w*=w;
|
|
29
|
+
vec2 n=w>0.0?(len>0.0?a/len*sign(p):(q.x>q.y?vec2(sign(p.x),0.0):vec2(0.0,sign(p.y)))):vec2(0.0);
|
|
30
|
+
vec2 scale=uRect.zw/uSize,base=uRect.xy+center*scale-.5,delta=n*uStrength*w*scale;
|
|
31
|
+
// An exact source texel with all optional passes disabled is byte identity.
|
|
32
|
+
// Avoid decode/interpolate/encode work only where bilinear weights are exactly zero.
|
|
33
|
+
if(!uPrepared&&!uSourceLinear&&uDispersion==0.0&&uTint.a==0.0&&uRim==0.0&&uSpecular==0.0){
|
|
34
|
+
vec2 at=clamp(base+delta,vec2(0.0),vec2(float(textureSize(uSource,0).x-1),float(textureSize(uSource,0).y-1)));
|
|
35
|
+
if(all(equal(fract(at),vec2(0.0)))){vec4 raw=texelFetch(uSource,ivec2(at),0);float alpha=floor(raw.a*mask*255.0+.5);ivec3 bytes=ivec3(floor(raw.rgb*255.0+.5));ivec3 premult=(bytes*int(alpha)+(uQuantizeUp?254:127))/255;result=alpha==0.0?vec4(0.0):vec4(vec3(premult)/255.0,alpha/255.0);return;}
|
|
36
|
+
}
|
|
37
|
+
vec4 c=sampleAt(base+delta);float alpha=floor(c.a*mask*255.0+.5);
|
|
38
|
+
if(alpha==0.0){result=vec4(0.0);return;}
|
|
39
|
+
vec3 rgb=c.a>0.0?c.rgb/c.a:vec3(0.0);
|
|
40
|
+
if(uDispersion>0.0){vec4 r=sampleAt(base+delta*(1.0+uDispersion)),b=sampleAt(base+delta*(1.0-uDispersion));rgb.r=r.a>0.0?r.r/r.a:0.0;rgb.b=b.a>0.0?b.b/b.a:0.0;}
|
|
41
|
+
if(uTint.a>0.0)rgb=mix(rgb,uTint.rgb,uTint.a);
|
|
42
|
+
if(w>0.0&&(uRim>0.0||uSpecular>0.0)){float l=max(dot(n,uLight),0.0);l*=l;l*=l;l*=l;float h=clamp(w*(uRim+uSpecular*l),0.0,1.0);rgb=clamp(rgb+(1.0-rgb)*h,0.0,1.0);}
|
|
43
|
+
ivec3 bytes=ivec3(encodeByte(rgb.r),encodeByte(rgb.g),encodeByte(rgb.b));
|
|
44
|
+
ivec3 premult=(bytes*int(alpha)+(uQuantizeUp?254:127))/255;
|
|
45
|
+
result=vec4(vec3(premult)/255.0,alpha/255.0);
|
|
46
|
+
}`;
|
|
47
|
+
export const BLUR = `#version 300 es
|
|
48
|
+
precision highp float;
|
|
49
|
+
precision highp int;
|
|
50
|
+
uniform highp sampler2D uSource;
|
|
51
|
+
uniform highp sampler2D uEncoding;
|
|
52
|
+
uniform ivec2 uAxis;
|
|
53
|
+
uniform int uRadius;
|
|
54
|
+
uniform bool uPrepared,uSourceLinear;
|
|
55
|
+
out vec4 result;
|
|
56
|
+
float decodeByte(float v){return v<=.04045?v/12.92:pow((v+.055)/1.055,2.4);}
|
|
57
|
+
void main(){ivec2 p=ivec2(gl_FragCoord.xy),size=textureSize(uSource,0);vec4 sum=vec4(0.0);
|
|
58
|
+
for(int k=-16;k<=16;k++){if(abs(k)<=uRadius){vec4 c=texelFetch(uSource,clamp(p+uAxis*k,ivec2(0),size-1),0);sum+=uPrepared?c:(uSourceLinear?vec4(c.rgb*c.a,c.a):vec4(vec3(decodeByte(c.r),decodeByte(c.g),decodeByte(c.b))*c.a,c.a));}}
|
|
59
|
+
result=sum/float(2*uRadius+1);
|
|
60
|
+
}`;
|
|
61
|
+
export function encodingTextureData() {
|
|
62
|
+
const data = new Float32Array(64 * 64 * 4);
|
|
63
|
+
const decode = (v) => v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4;
|
|
64
|
+
for (let i = 0; i < 4096; i++) {
|
|
65
|
+
const v = (i + .5) / 4096, byte = Math.round((v <= .0031308 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - .055) * 255);
|
|
66
|
+
data.set([byte, byte ? decode((byte - .5) / 255) : -1, byte < 255 ? decode((byte + .5) / 255) : 2, 0], i * 4);
|
|
67
|
+
}
|
|
68
|
+
return data;
|
|
69
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liquid-glass-forge/webgl",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Experimental shared-atlas WebGL2 optics and optional vanilla integration",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20.19.0"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./dom": {
|
|
19
|
+
"types": "./dist/dom.d.ts",
|
|
20
|
+
"import": "./dist/dom.js",
|
|
21
|
+
"default": "./dist/dom.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"NOTICE",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@liquid-glass-forge/core": "0.1.0-rc.1",
|
|
32
|
+
"@liquid-glass-forge/cpu": "0.1.0-rc.1",
|
|
33
|
+
"@liquid-glass-forge/dom": "0.1.0-rc.1"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/namipsg/liquid-glass-forge.git",
|
|
38
|
+
"directory": "packages/webgl"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
}
|
|
43
|
+
}
|