webgraphiclibrary 2.0.0-beta.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.md +21 -0
- package/README.md +247 -0
- package/dist/core.d.ts +14 -0
- package/dist/core.js +57 -0
- package/dist/core.js.map +1 -0
- package/dist/fbo.d.ts +46 -0
- package/dist/fbo.js +221 -0
- package/dist/fbo.js.map +1 -0
- package/dist/gl-D_qLxkHO.d.ts +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +221 -0
- package/dist/index.js.map +1 -0
- package/docs/assets/fbo-workflow.png +0 -0
- package/docs/assets/fbo-workflow.svg +57 -0
- package/docs/screenshots/code-snippet.png +0 -0
- package/docs/screenshots/fbo-workflow-card.png +0 -0
- package/docs/screenshots/terminal-verification.png +0 -0
- package/examples/fbo-postprocess/README.md +15 -0
- package/package.json +84 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ahmerhh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# webgraphiclibrary
|
|
2
|
+
|
|
3
|
+
Small, type-safe WebGL utilities for people who still like working close to the graphics API.
|
|
4
|
+
|
|
5
|
+
webgraphiclibrary is not a scene graph, renderer, game engine, or replacement for Three.js. It is a set of focused wrappers around WebGL resources that are easy to reason about, easy to clean up, and small enough to drop into rendering experiments, post-processing pipelines, teaching projects, and custom engines.
|
|
6
|
+
|
|
7
|
+
The first v2 module is the framebuffer wrapper. It turns the noisy WebGL framebuffer setup flow into a small lifecycle API while keeping the underlying WebGL objects available when you need direct control.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Project snapshots
|
|
12
|
+
|
|
13
|
+
The screenshots below are generated with Playwright from this repository's current API examples and verification commands.
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install webgraphiclibrary@beta
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add webgraphiclibrary@beta
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Imports
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { Framebuffer } from "webgraphiclibrary/fbo";
|
|
35
|
+
import { WebGLError } from "webgraphiclibrary/core";
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The package uses subpath exports so each module stays explicit. The current beta exports:
|
|
39
|
+
|
|
40
|
+
- `webgraphiclibrary/fbo`
|
|
41
|
+
- `webgraphiclibrary/core`
|
|
42
|
+
|
|
43
|
+
## Framebuffer quick start
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { Framebuffer } from "webgraphiclibrary/fbo";
|
|
47
|
+
|
|
48
|
+
const canvas = document.querySelector("canvas");
|
|
49
|
+
if (!(canvas instanceof HTMLCanvasElement)) {
|
|
50
|
+
throw new Error("Canvas element was not found.");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const gl = canvas.getContext("webgl");
|
|
54
|
+
if (gl === null) {
|
|
55
|
+
throw new Error("WebGL is not available.");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const fbo = new Framebuffer(gl, {
|
|
59
|
+
width: 512,
|
|
60
|
+
height: 512,
|
|
61
|
+
depth: true
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
fbo.withBound(() => {
|
|
65
|
+
gl.viewport(0, 0, fbo.width, fbo.height);
|
|
66
|
+
gl.clearColor(0, 0, 0, 1);
|
|
67
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
68
|
+
|
|
69
|
+
// Draw the off-screen scene here.
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
73
|
+
gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
|
|
74
|
+
|
|
75
|
+
// Draw a fullscreen quad here and sample fbo.texture in the fragment shader.
|
|
76
|
+
|
|
77
|
+
fbo.dispose();
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Framebuffer API
|
|
81
|
+
|
|
82
|
+
### `new Framebuffer(gl, options)`
|
|
83
|
+
|
|
84
|
+
Creates a color framebuffer target backed by a `WebGLTexture`.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const fbo = new Framebuffer(gl, {
|
|
88
|
+
width: 1024,
|
|
89
|
+
height: 1024,
|
|
90
|
+
depth: true
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Options:
|
|
95
|
+
|
|
96
|
+
| Option | Type | Default | Notes |
|
|
97
|
+
| ---------------- | --------- | ------------------ | -------------------------------------------- |
|
|
98
|
+
| `width` | `number` | required | Positive integer width in pixels |
|
|
99
|
+
| `height` | `number` | required | Positive integer height in pixels |
|
|
100
|
+
| `internalFormat` | `number` | `gl.RGBA` | Texture internal format |
|
|
101
|
+
| `format` | `number` | `gl.RGBA` | Texture data format |
|
|
102
|
+
| `type` | `number` | `gl.UNSIGNED_BYTE` | Texture data type |
|
|
103
|
+
| `minFilter` | `number` | `gl.LINEAR` | Texture minification filter |
|
|
104
|
+
| `magFilter` | `number` | `gl.LINEAR` | Texture magnification filter |
|
|
105
|
+
| `wrapS` | `number` | `gl.CLAMP_TO_EDGE` | Horizontal texture wrapping |
|
|
106
|
+
| `wrapT` | `number` | `gl.CLAMP_TO_EDGE` | Vertical texture wrapping |
|
|
107
|
+
| `depth` | `boolean` | `false` | Adds a `DEPTH_COMPONENT16` renderbuffer |
|
|
108
|
+
| `stencil` | `boolean` | `false` | Adds a combined `DEPTH_STENCIL` renderbuffer |
|
|
109
|
+
|
|
110
|
+
### Properties
|
|
111
|
+
|
|
112
|
+
| Property | Type | Notes |
|
|
113
|
+
| -------------- | ------------------------------------------------- | --------------------------------- |
|
|
114
|
+
| `gl` | `WebGLRenderingContext \| WebGL2RenderingContext` | Context passed to the constructor |
|
|
115
|
+
| `width` | `number` | Current framebuffer width |
|
|
116
|
+
| `height` | `number` | Current framebuffer height |
|
|
117
|
+
| `framebuffer` | `WebGLFramebuffer` | Underlying framebuffer object |
|
|
118
|
+
| `texture` | `WebGLTexture` | Color attachment texture |
|
|
119
|
+
| `renderbuffer` | `WebGLRenderbuffer \| null` | Depth or depth-stencil storage |
|
|
120
|
+
| `disposed` | `boolean` | `true` after disposal |
|
|
121
|
+
|
|
122
|
+
### Methods
|
|
123
|
+
|
|
124
|
+
#### `bind()`
|
|
125
|
+
|
|
126
|
+
Binds the framebuffer so future draw calls write into `texture`.
|
|
127
|
+
|
|
128
|
+
#### `unbind()`
|
|
129
|
+
|
|
130
|
+
Binds the default screen framebuffer.
|
|
131
|
+
|
|
132
|
+
#### `withBound(render)`
|
|
133
|
+
|
|
134
|
+
Binds the framebuffer, runs the callback, and unbinds in a `finally` block. This keeps render passes compact and still makes the WebGL state change explicit.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
fbo.withBound(() => {
|
|
138
|
+
renderScene();
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### `resize({ width, height })`
|
|
143
|
+
|
|
144
|
+
Reallocates texture and renderbuffer storage while keeping the same framebuffer object.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
fbo.resize({ width: canvas.width, height: canvas.height });
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### `resizeToCanvas(canvas)`
|
|
151
|
+
|
|
152
|
+
Convenience wrapper for matching a framebuffer to a canvas backing-store size.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
fbo.resizeToCanvas(canvas);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### `readPixels()`
|
|
159
|
+
|
|
160
|
+
Reads the color attachment into a `Uint8Array` of length `width * height * 4`.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const pixels = fbo.readPixels();
|
|
164
|
+
const firstPixel = pixels.slice(0, 4);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### `dispose()`
|
|
168
|
+
|
|
169
|
+
Deletes the framebuffer, color texture, and optional renderbuffer. Disposal is idempotent, so repeated calls are safe.
|
|
170
|
+
|
|
171
|
+
## Compatibility alias
|
|
172
|
+
|
|
173
|
+
The v2 API prefers the descriptive `Framebuffer` name, but the shorter `FBO` alias is exported too:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { FBO } from "webgraphiclibrary/fbo";
|
|
177
|
+
|
|
178
|
+
const target = new FBO(gl, { width: 512, height: 512 });
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Error behavior
|
|
182
|
+
|
|
183
|
+
The library throws early for invalid usage:
|
|
184
|
+
|
|
185
|
+
- non-WebGL context values
|
|
186
|
+
- non-integer or non-positive dimensions
|
|
187
|
+
- failed WebGL resource allocation
|
|
188
|
+
- incomplete framebuffer status
|
|
189
|
+
- use after `dispose()`
|
|
190
|
+
|
|
191
|
+
Base WebGL-related failures extend `WebGLError`. Use-after-dispose failures throw `DisposedResourceError`.
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
pnpm install
|
|
197
|
+
pnpm lint
|
|
198
|
+
pnpm typecheck
|
|
199
|
+
pnpm test
|
|
200
|
+
pnpm build
|
|
201
|
+
pnpm screenshots
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`pnpm prepublishOnly` runs the full local release check: lint, typecheck, tests, and build.
|
|
205
|
+
|
|
206
|
+
`pnpm screenshots` regenerates the README screenshots with Playwright.
|
|
207
|
+
|
|
208
|
+
## npm package
|
|
209
|
+
|
|
210
|
+
The current release target is the beta package:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npm publish --tag beta
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The package ships compiled ESM output, TypeScript declarations, README assets, and examples.
|
|
217
|
+
|
|
218
|
+
## Roadmap
|
|
219
|
+
|
|
220
|
+
The older WebGraphicLibrary packages are being rebuilt around the same small, typed-resource approach. The next modules are:
|
|
221
|
+
|
|
222
|
+
- shader compilation
|
|
223
|
+
- program linking and uniform helpers
|
|
224
|
+
- typed vertex/index buffers
|
|
225
|
+
- texture upload helpers
|
|
226
|
+
- texture display debugging utilities
|
|
227
|
+
|
|
228
|
+
Sprite-style helpers are intentionally outside the first v2 scope. They fit better as examples built on top of the low-level modules.
|
|
229
|
+
|
|
230
|
+
## Related repositories
|
|
231
|
+
|
|
232
|
+
This repository is the new home for the v2 work. The original package repositories are still useful for history and comparison:
|
|
233
|
+
|
|
234
|
+
| Repository | Notes |
|
|
235
|
+
| -------------------------------------------------------------------------------------------------------- | -------------------------------- |
|
|
236
|
+
| [WebGraphicLibrary-fixedbaseoperator](https://github.com/ahmerhabib/WebGraphicLibrary-fixedbaseoperator) | Original framebuffer/FBO package |
|
|
237
|
+
| [WebGraphicLibrary-texture-display](https://github.com/ahmerhabib/WebGraphicLibrary-texture-display) | Texture display helper |
|
|
238
|
+
| [WebGraphicLibrary-buffer](https://github.com/ahmerhabib/WebGraphicLibrary-buffer) | WebGL buffer wrapper |
|
|
239
|
+
| [WebGraphicLibrary-sprite](https://github.com/ahmerhabib/WebGraphicLibrary-sprite) | Sprite template package |
|
|
240
|
+
| [webgraphiclibrary-program](https://github.com/ahmerhabib/webgraphiclibrary-program) | WebGL program wrapper |
|
|
241
|
+
| [WebGraphicLibrary-texture](https://github.com/ahmerhabib/WebGraphicLibrary-texture) | WebGL texture wrapper |
|
|
242
|
+
| [WebGraphicLibrary-context](https://github.com/ahmerhabib/WebGraphicLibrary-context) | Canvas context helper |
|
|
243
|
+
| [WebGraphicLibrary-shader](https://github.com/ahmerhabib/WebGraphicLibrary-shader) | WebGL shader wrapper |
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { G as GLContext, g as getFramebufferStatusMessage, i as isWebGL2, a as isWebGLContext } from './gl-D_qLxkHO.js';
|
|
2
|
+
|
|
3
|
+
declare function assertPositiveIntegerDimension(name: "width" | "height", value: number): number;
|
|
4
|
+
|
|
5
|
+
declare function assertNotDisposed(resourceName: string, disposed: boolean): void;
|
|
6
|
+
|
|
7
|
+
declare class WebGLError extends Error {
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
declare class DisposedResourceError extends WebGLError {
|
|
11
|
+
constructor(resourceName: string);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { DisposedResourceError, WebGLError, assertNotDisposed, assertPositiveIntegerDimension };
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// packages/core/src/dimensions.ts
|
|
2
|
+
function assertPositiveIntegerDimension(name, value) {
|
|
3
|
+
if (!Number.isInteger(value)) {
|
|
4
|
+
throw new TypeError(`${name} must be an integer.`);
|
|
5
|
+
}
|
|
6
|
+
if (value <= 0) {
|
|
7
|
+
throw new RangeError(`${name} must be greater than 0.`);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// packages/core/src/errors.ts
|
|
13
|
+
var WebGLError = class extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "WebGLError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var DisposedResourceError = class extends WebGLError {
|
|
20
|
+
constructor(resourceName) {
|
|
21
|
+
super(`${resourceName} has been disposed.`);
|
|
22
|
+
this.name = "DisposedResourceError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// packages/core/src/disposable.ts
|
|
27
|
+
function assertNotDisposed(resourceName, disposed) {
|
|
28
|
+
if (disposed) {
|
|
29
|
+
throw new DisposedResourceError(resourceName);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// packages/core/src/gl.ts
|
|
34
|
+
function isWebGLContext(value) {
|
|
35
|
+
if (value === null || typeof value !== "object") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const candidate = value;
|
|
39
|
+
return typeof candidate.createFramebuffer === "function" && typeof candidate.bindFramebuffer === "function" && typeof candidate.checkFramebufferStatus === "function";
|
|
40
|
+
}
|
|
41
|
+
function isWebGL2(gl) {
|
|
42
|
+
return "texStorage2D" in gl && typeof gl.texStorage2D === "function";
|
|
43
|
+
}
|
|
44
|
+
function getFramebufferStatusMessage(gl, status) {
|
|
45
|
+
const statuses = /* @__PURE__ */ new Map([
|
|
46
|
+
[gl.FRAMEBUFFER_COMPLETE, "Framebuffer is complete."],
|
|
47
|
+
[gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT, "Framebuffer has an incomplete attachment."],
|
|
48
|
+
[gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT, "Framebuffer has no image attached to it."],
|
|
49
|
+
[gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS, "Framebuffer attachments have mismatched dimensions."],
|
|
50
|
+
[gl.FRAMEBUFFER_UNSUPPORTED, "Framebuffer configuration is unsupported by this context."]
|
|
51
|
+
]);
|
|
52
|
+
return statuses.get(status) ?? `Unknown framebuffer status: ${status}.`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { DisposedResourceError, WebGLError, assertNotDisposed, assertPositiveIntegerDimension, getFramebufferStatusMessage, isWebGL2, isWebGLContext };
|
|
56
|
+
//# sourceMappingURL=core.js.map
|
|
57
|
+
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../packages/core/src/dimensions.ts","../packages/core/src/errors.ts","../packages/core/src/disposable.ts","../packages/core/src/gl.ts"],"names":[],"mappings":";AAAO,SAAS,8BAAA,CAA+B,MAA0B,KAAA,EAAuB;AAC9F,EAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA,EAAG;AAC5B,IAAA,MAAM,IAAI,SAAA,CAAU,CAAA,EAAG,IAAI,CAAA,oBAAA,CAAsB,CAAA;AAAA,EACnD;AAEA,EAAA,IAAI,SAAS,CAAA,EAAG;AACd,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,EAAG,IAAI,CAAA,wBAAA,CAA0B,CAAA;AAAA,EACxD;AAEA,EAAA,OAAO,KAAA;AACT;;;ACVO,IAAM,UAAA,GAAN,cAAyB,KAAA,CAAM;AAAA,EACpC,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,YAAA;AAAA,EACd;AACF;AAEO,IAAM,qBAAA,GAAN,cAAoC,UAAA,CAAW;AAAA,EACpD,YAAY,YAAA,EAAsB;AAChC,IAAA,KAAA,CAAM,CAAA,EAAG,YAAY,CAAA,mBAAA,CAAqB,CAAA;AAC1C,IAAA,IAAA,CAAK,IAAA,GAAO,uBAAA;AAAA,EACd;AACF;;;ACVO,SAAS,iBAAA,CAAkB,cAAsB,QAAA,EAAyB;AAC/E,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,MAAM,IAAI,sBAAsB,YAAY,CAAA;AAAA,EAC9C;AACF;;;ACEO,SAAS,eAAe,KAAA,EAAoC;AACjE,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,QAAA,EAAU;AAC/C,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,SAAA,GAAY,KAAA;AAClB,EAAA,OACE,OAAO,SAAA,CAAU,iBAAA,KAAsB,UAAA,IACvC,OAAO,UAAU,eAAA,KAAoB,UAAA,IACrC,OAAO,SAAA,CAAU,sBAAA,KAA2B,UAAA;AAEhD;AAEO,SAAS,SAAS,EAAA,EAA6C;AACpE,EAAA,OAAO,cAAA,IAAkB,EAAA,IAAM,OAAO,EAAA,CAAG,YAAA,KAAiB,UAAA;AAC5D;AAEO,SAAS,2BAAA,CAA4B,IAAe,MAAA,EAAwB;AACjF,EAAA,MAAM,QAAA,uBAAe,GAAA,CAAoB;AAAA,IACvC,CAAC,EAAA,CAAG,oBAAA,EAAsB,0BAA0B,CAAA;AAAA,IACpD,CAAC,EAAA,CAAG,iCAAA,EAAmC,2CAA2C,CAAA;AAAA,IAClF,CAAC,EAAA,CAAG,yCAAA,EAA2C,0CAA0C,CAAA;AAAA,IACzF,CAAC,EAAA,CAAG,iCAAA,EAAmC,qDAAqD,CAAA;AAAA,IAC5F,CAAC,EAAA,CAAG,uBAAA,EAAyB,2DAA2D;AAAA,GACzF,CAAA;AAED,EAAA,OAAO,QAAA,CAAS,GAAA,CAAI,MAAM,CAAA,IAAK,+BAA+B,MAAM,CAAA,CAAA,CAAA;AACtE","file":"core.js","sourcesContent":["export function assertPositiveIntegerDimension(name: \"width\" | \"height\", value: number): number {\n if (!Number.isInteger(value)) {\n throw new TypeError(`${name} must be an integer.`);\n }\n\n if (value <= 0) {\n throw new RangeError(`${name} must be greater than 0.`);\n }\n\n return value;\n}\n","export class WebGLError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebGLError\";\n }\n}\n\nexport class DisposedResourceError extends WebGLError {\n constructor(resourceName: string) {\n super(`${resourceName} has been disposed.`);\n this.name = \"DisposedResourceError\";\n }\n}\n","import { DisposedResourceError } from \"./errors\";\n\nexport function assertNotDisposed(resourceName: string, disposed: boolean): void {\n if (disposed) {\n throw new DisposedResourceError(resourceName);\n }\n}\n","export type GLContext = WebGLRenderingContext | WebGL2RenderingContext;\n\ntype WebGLLike = {\n createFramebuffer: unknown;\n bindFramebuffer: unknown;\n checkFramebufferStatus: unknown;\n};\n\nexport function isWebGLContext(value: unknown): value is GLContext {\n if (value === null || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Partial<WebGLLike>;\n return (\n typeof candidate.createFramebuffer === \"function\" &&\n typeof candidate.bindFramebuffer === \"function\" &&\n typeof candidate.checkFramebufferStatus === \"function\"\n );\n}\n\nexport function isWebGL2(gl: GLContext): gl is WebGL2RenderingContext {\n return \"texStorage2D\" in gl && typeof gl.texStorage2D === \"function\";\n}\n\nexport function getFramebufferStatusMessage(gl: GLContext, status: number): string {\n const statuses = new Map<number, string>([\n [gl.FRAMEBUFFER_COMPLETE, \"Framebuffer is complete.\"],\n [gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT, \"Framebuffer has an incomplete attachment.\"],\n [gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT, \"Framebuffer has no image attached to it.\"],\n [gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS, \"Framebuffer attachments have mismatched dimensions.\"],\n [gl.FRAMEBUFFER_UNSUPPORTED, \"Framebuffer configuration is unsupported by this context.\"]\n ]);\n\n return statuses.get(status) ?? `Unknown framebuffer status: ${status}.`;\n}\n"]}
|
package/dist/fbo.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { G as GLContext } from './gl-D_qLxkHO.js';
|
|
2
|
+
|
|
3
|
+
interface FramebufferOptions {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
internalFormat?: number;
|
|
7
|
+
format?: number;
|
|
8
|
+
type?: number;
|
|
9
|
+
minFilter?: number;
|
|
10
|
+
magFilter?: number;
|
|
11
|
+
wrapS?: number;
|
|
12
|
+
wrapT?: number;
|
|
13
|
+
depth?: boolean;
|
|
14
|
+
stencil?: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface FramebufferResizeOptions {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}
|
|
20
|
+
type FramebufferCanvasSize = Pick<HTMLCanvasElement, "width" | "height">;
|
|
21
|
+
declare class Framebuffer {
|
|
22
|
+
readonly gl: GLContext;
|
|
23
|
+
readonly framebuffer: WebGLFramebuffer;
|
|
24
|
+
readonly texture: WebGLTexture;
|
|
25
|
+
readonly renderbuffer: WebGLRenderbuffer | null;
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
private readonly options;
|
|
29
|
+
private isDisposed;
|
|
30
|
+
constructor(gl: GLContext, options: FramebufferOptions);
|
|
31
|
+
get disposed(): boolean;
|
|
32
|
+
bind(): void;
|
|
33
|
+
unbind(): void;
|
|
34
|
+
withBound<T>(render: () => T): T;
|
|
35
|
+
resize(options: FramebufferResizeOptions): void;
|
|
36
|
+
resizeToCanvas(canvas: FramebufferCanvasSize): void;
|
|
37
|
+
readPixels(): Uint8Array;
|
|
38
|
+
dispose(): void;
|
|
39
|
+
private createRenderbuffer;
|
|
40
|
+
private configureAttachments;
|
|
41
|
+
private allocateTextureStorage;
|
|
42
|
+
private allocateRenderbufferStorage;
|
|
43
|
+
private assertComplete;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { Framebuffer as FBO, Framebuffer, type FramebufferCanvasSize, type FramebufferOptions, type FramebufferResizeOptions };
|
package/dist/fbo.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// packages/core/src/dimensions.ts
|
|
2
|
+
function assertPositiveIntegerDimension(name, value) {
|
|
3
|
+
if (!Number.isInteger(value)) {
|
|
4
|
+
throw new TypeError(`${name} must be an integer.`);
|
|
5
|
+
}
|
|
6
|
+
if (value <= 0) {
|
|
7
|
+
throw new RangeError(`${name} must be greater than 0.`);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// packages/core/src/errors.ts
|
|
13
|
+
var WebGLError = class extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "WebGLError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var DisposedResourceError = class extends WebGLError {
|
|
20
|
+
constructor(resourceName) {
|
|
21
|
+
super(`${resourceName} has been disposed.`);
|
|
22
|
+
this.name = "DisposedResourceError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// packages/core/src/disposable.ts
|
|
27
|
+
function assertNotDisposed(resourceName, disposed) {
|
|
28
|
+
if (disposed) {
|
|
29
|
+
throw new DisposedResourceError(resourceName);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// packages/core/src/gl.ts
|
|
34
|
+
function isWebGLContext(value) {
|
|
35
|
+
if (value === null || typeof value !== "object") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const candidate = value;
|
|
39
|
+
return typeof candidate.createFramebuffer === "function" && typeof candidate.bindFramebuffer === "function" && typeof candidate.checkFramebufferStatus === "function";
|
|
40
|
+
}
|
|
41
|
+
function getFramebufferStatusMessage(gl, status) {
|
|
42
|
+
const statuses = /* @__PURE__ */ new Map([
|
|
43
|
+
[gl.FRAMEBUFFER_COMPLETE, "Framebuffer is complete."],
|
|
44
|
+
[gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT, "Framebuffer has an incomplete attachment."],
|
|
45
|
+
[gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT, "Framebuffer has no image attached to it."],
|
|
46
|
+
[gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS, "Framebuffer attachments have mismatched dimensions."],
|
|
47
|
+
[gl.FRAMEBUFFER_UNSUPPORTED, "Framebuffer configuration is unsupported by this context."]
|
|
48
|
+
]);
|
|
49
|
+
return statuses.get(status) ?? `Unknown framebuffer status: ${status}.`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// packages/fbo/src/framebuffer.ts
|
|
53
|
+
var Framebuffer = class {
|
|
54
|
+
gl;
|
|
55
|
+
framebuffer;
|
|
56
|
+
texture;
|
|
57
|
+
renderbuffer;
|
|
58
|
+
width;
|
|
59
|
+
height;
|
|
60
|
+
options;
|
|
61
|
+
isDisposed = false;
|
|
62
|
+
constructor(gl, options) {
|
|
63
|
+
if (!isWebGLContext(gl)) {
|
|
64
|
+
throw new TypeError("gl must be a WebGL rendering context.");
|
|
65
|
+
}
|
|
66
|
+
this.gl = gl;
|
|
67
|
+
this.width = assertPositiveIntegerDimension("width", options.width);
|
|
68
|
+
this.height = assertPositiveIntegerDimension("height", options.height);
|
|
69
|
+
this.options = {
|
|
70
|
+
internalFormat: options.internalFormat ?? gl.RGBA,
|
|
71
|
+
format: options.format ?? gl.RGBA,
|
|
72
|
+
type: options.type ?? gl.UNSIGNED_BYTE,
|
|
73
|
+
minFilter: options.minFilter ?? gl.LINEAR,
|
|
74
|
+
magFilter: options.magFilter ?? gl.LINEAR,
|
|
75
|
+
wrapS: options.wrapS ?? gl.CLAMP_TO_EDGE,
|
|
76
|
+
wrapT: options.wrapT ?? gl.CLAMP_TO_EDGE,
|
|
77
|
+
depth: options.depth ?? false,
|
|
78
|
+
stencil: options.stencil ?? false
|
|
79
|
+
};
|
|
80
|
+
const framebuffer = gl.createFramebuffer();
|
|
81
|
+
const texture = gl.createTexture();
|
|
82
|
+
if (framebuffer === null) {
|
|
83
|
+
throw new WebGLError("Failed to create framebuffer.");
|
|
84
|
+
}
|
|
85
|
+
if (texture === null) {
|
|
86
|
+
gl.deleteFramebuffer(framebuffer);
|
|
87
|
+
throw new WebGLError("Failed to create framebuffer texture.");
|
|
88
|
+
}
|
|
89
|
+
this.framebuffer = framebuffer;
|
|
90
|
+
this.texture = texture;
|
|
91
|
+
this.renderbuffer = this.createRenderbuffer();
|
|
92
|
+
this.configureAttachments();
|
|
93
|
+
this.assertComplete();
|
|
94
|
+
this.unbind();
|
|
95
|
+
}
|
|
96
|
+
get disposed() {
|
|
97
|
+
return this.isDisposed;
|
|
98
|
+
}
|
|
99
|
+
bind() {
|
|
100
|
+
assertNotDisposed("Framebuffer", this.isDisposed);
|
|
101
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);
|
|
102
|
+
}
|
|
103
|
+
unbind() {
|
|
104
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
|
|
105
|
+
}
|
|
106
|
+
withBound(render) {
|
|
107
|
+
this.bind();
|
|
108
|
+
try {
|
|
109
|
+
return render();
|
|
110
|
+
} finally {
|
|
111
|
+
this.unbind();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
resize(options) {
|
|
115
|
+
assertNotDisposed("Framebuffer", this.isDisposed);
|
|
116
|
+
this.width = assertPositiveIntegerDimension("width", options.width);
|
|
117
|
+
this.height = assertPositiveIntegerDimension("height", options.height);
|
|
118
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);
|
|
119
|
+
this.allocateTextureStorage();
|
|
120
|
+
if (this.renderbuffer !== null) {
|
|
121
|
+
this.allocateRenderbufferStorage();
|
|
122
|
+
}
|
|
123
|
+
this.assertComplete();
|
|
124
|
+
this.unbind();
|
|
125
|
+
}
|
|
126
|
+
resizeToCanvas(canvas) {
|
|
127
|
+
this.resize({ width: canvas.width, height: canvas.height });
|
|
128
|
+
}
|
|
129
|
+
readPixels() {
|
|
130
|
+
assertNotDisposed("Framebuffer", this.isDisposed);
|
|
131
|
+
const pixels = new Uint8Array(this.width * this.height * 4);
|
|
132
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);
|
|
133
|
+
this.gl.readPixels(
|
|
134
|
+
0,
|
|
135
|
+
0,
|
|
136
|
+
this.width,
|
|
137
|
+
this.height,
|
|
138
|
+
this.options.format,
|
|
139
|
+
this.options.type,
|
|
140
|
+
pixels
|
|
141
|
+
);
|
|
142
|
+
this.unbind();
|
|
143
|
+
return pixels;
|
|
144
|
+
}
|
|
145
|
+
dispose() {
|
|
146
|
+
if (this.isDisposed) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.gl.deleteFramebuffer(this.framebuffer);
|
|
150
|
+
this.gl.deleteTexture(this.texture);
|
|
151
|
+
if (this.renderbuffer !== null) {
|
|
152
|
+
this.gl.deleteRenderbuffer(this.renderbuffer);
|
|
153
|
+
}
|
|
154
|
+
this.isDisposed = true;
|
|
155
|
+
}
|
|
156
|
+
createRenderbuffer() {
|
|
157
|
+
if (!this.options.depth && !this.options.stencil) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const renderbuffer = this.gl.createRenderbuffer();
|
|
161
|
+
if (renderbuffer === null) {
|
|
162
|
+
throw new WebGLError("Failed to create framebuffer renderbuffer.");
|
|
163
|
+
}
|
|
164
|
+
return renderbuffer;
|
|
165
|
+
}
|
|
166
|
+
configureAttachments() {
|
|
167
|
+
const gl = this.gl;
|
|
168
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
169
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
170
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.options.minFilter);
|
|
171
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.options.magFilter);
|
|
172
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this.options.wrapS);
|
|
173
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this.options.wrapT);
|
|
174
|
+
this.allocateTextureStorage();
|
|
175
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
|
|
176
|
+
if (this.renderbuffer !== null) {
|
|
177
|
+
this.allocateRenderbufferStorage();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
allocateTextureStorage() {
|
|
181
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
|
|
182
|
+
this.gl.texImage2D(
|
|
183
|
+
this.gl.TEXTURE_2D,
|
|
184
|
+
0,
|
|
185
|
+
this.options.internalFormat,
|
|
186
|
+
this.width,
|
|
187
|
+
this.height,
|
|
188
|
+
0,
|
|
189
|
+
this.options.format,
|
|
190
|
+
this.options.type,
|
|
191
|
+
null
|
|
192
|
+
);
|
|
193
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, null);
|
|
194
|
+
}
|
|
195
|
+
allocateRenderbufferStorage() {
|
|
196
|
+
if (this.renderbuffer === null) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const attachment = this.options.stencil ? this.gl.DEPTH_STENCIL_ATTACHMENT : this.gl.DEPTH_ATTACHMENT;
|
|
200
|
+
const storage = this.options.stencil ? this.gl.DEPTH_STENCIL : this.gl.DEPTH_COMPONENT16;
|
|
201
|
+
this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.renderbuffer);
|
|
202
|
+
this.gl.renderbufferStorage(this.gl.RENDERBUFFER, storage, this.width, this.height);
|
|
203
|
+
this.gl.framebufferRenderbuffer(
|
|
204
|
+
this.gl.FRAMEBUFFER,
|
|
205
|
+
attachment,
|
|
206
|
+
this.gl.RENDERBUFFER,
|
|
207
|
+
this.renderbuffer
|
|
208
|
+
);
|
|
209
|
+
this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, null);
|
|
210
|
+
}
|
|
211
|
+
assertComplete() {
|
|
212
|
+
const status = this.gl.checkFramebufferStatus(this.gl.FRAMEBUFFER);
|
|
213
|
+
if (status !== this.gl.FRAMEBUFFER_COMPLETE) {
|
|
214
|
+
throw new WebGLError(getFramebufferStatusMessage(this.gl, status));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export { Framebuffer as FBO, Framebuffer };
|
|
220
|
+
//# sourceMappingURL=fbo.js.map
|
|
221
|
+
//# sourceMappingURL=fbo.js.map
|
package/dist/fbo.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../packages/core/src/dimensions.ts","../packages/core/src/errors.ts","../packages/core/src/disposable.ts","../packages/core/src/gl.ts","../packages/fbo/src/framebuffer.ts"],"names":[],"mappings":"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","file":"fbo.js","sourcesContent":["export function assertPositiveIntegerDimension(name: \"width\" | \"height\", value: number): number {\n if (!Number.isInteger(value)) {\n throw new TypeError(`${name} must be an integer.`);\n }\n\n if (value <= 0) {\n throw new RangeError(`${name} must be greater than 0.`);\n }\n\n return value;\n}\n","export class WebGLError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebGLError\";\n }\n}\n\nexport class DisposedResourceError extends WebGLError {\n constructor(resourceName: string) {\n super(`${resourceName} has been disposed.`);\n this.name = \"DisposedResourceError\";\n }\n}\n","import { DisposedResourceError } from \"./errors\";\n\nexport function assertNotDisposed(resourceName: string, disposed: boolean): void {\n if (disposed) {\n throw new DisposedResourceError(resourceName);\n }\n}\n","export type GLContext = WebGLRenderingContext | WebGL2RenderingContext;\n\ntype WebGLLike = {\n createFramebuffer: unknown;\n bindFramebuffer: unknown;\n checkFramebufferStatus: unknown;\n};\n\nexport function isWebGLContext(value: unknown): value is GLContext {\n if (value === null || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Partial<WebGLLike>;\n return (\n typeof candidate.createFramebuffer === \"function\" &&\n typeof candidate.bindFramebuffer === \"function\" &&\n typeof candidate.checkFramebufferStatus === \"function\"\n );\n}\n\nexport function isWebGL2(gl: GLContext): gl is WebGL2RenderingContext {\n return \"texStorage2D\" in gl && typeof gl.texStorage2D === \"function\";\n}\n\nexport function getFramebufferStatusMessage(gl: GLContext, status: number): string {\n const statuses = new Map<number, string>([\n [gl.FRAMEBUFFER_COMPLETE, \"Framebuffer is complete.\"],\n [gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT, \"Framebuffer has an incomplete attachment.\"],\n [gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT, \"Framebuffer has no image attached to it.\"],\n [gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS, \"Framebuffer attachments have mismatched dimensions.\"],\n [gl.FRAMEBUFFER_UNSUPPORTED, \"Framebuffer configuration is unsupported by this context.\"]\n ]);\n\n return statuses.get(status) ?? `Unknown framebuffer status: ${status}.`;\n}\n","import {\n WebGLError,\n assertNotDisposed,\n assertPositiveIntegerDimension,\n getFramebufferStatusMessage,\n isWebGLContext\n} from \"../../core/src/index\";\nimport type { GLContext } from \"../../core/src/index\";\n\nexport interface FramebufferOptions {\n width: number;\n height: number;\n internalFormat?: number;\n format?: number;\n type?: number;\n minFilter?: number;\n magFilter?: number;\n wrapS?: number;\n wrapT?: number;\n depth?: boolean;\n stencil?: boolean;\n}\n\nexport interface FramebufferResizeOptions {\n width: number;\n height: number;\n}\n\nexport type FramebufferCanvasSize = Pick<HTMLCanvasElement, \"width\" | \"height\">;\n\ntype TextureOptions = Required<\n Pick<\n FramebufferOptions,\n \"internalFormat\" | \"format\" | \"type\" | \"minFilter\" | \"magFilter\" | \"wrapS\" | \"wrapT\"\n >\n> &\n Required<Pick<FramebufferOptions, \"depth\" | \"stencil\">>;\n\nexport class Framebuffer {\n public readonly gl: GLContext;\n public readonly framebuffer: WebGLFramebuffer;\n public readonly texture: WebGLTexture;\n public readonly renderbuffer: WebGLRenderbuffer | null;\n public width: number;\n public height: number;\n\n private readonly options: TextureOptions;\n private isDisposed = false;\n\n constructor(gl: GLContext, options: FramebufferOptions) {\n if (!isWebGLContext(gl)) {\n throw new TypeError(\"gl must be a WebGL rendering context.\");\n }\n\n this.gl = gl;\n this.width = assertPositiveIntegerDimension(\"width\", options.width);\n this.height = assertPositiveIntegerDimension(\"height\", options.height);\n this.options = {\n internalFormat: options.internalFormat ?? gl.RGBA,\n format: options.format ?? gl.RGBA,\n type: options.type ?? gl.UNSIGNED_BYTE,\n minFilter: options.minFilter ?? gl.LINEAR,\n magFilter: options.magFilter ?? gl.LINEAR,\n wrapS: options.wrapS ?? gl.CLAMP_TO_EDGE,\n wrapT: options.wrapT ?? gl.CLAMP_TO_EDGE,\n depth: options.depth ?? false,\n stencil: options.stencil ?? false\n };\n\n const framebuffer = gl.createFramebuffer() as WebGLFramebuffer | null;\n const texture = gl.createTexture() as WebGLTexture | null;\n\n if (framebuffer === null) {\n throw new WebGLError(\"Failed to create framebuffer.\");\n }\n\n if (texture === null) {\n gl.deleteFramebuffer(framebuffer);\n throw new WebGLError(\"Failed to create framebuffer texture.\");\n }\n\n this.framebuffer = framebuffer;\n this.texture = texture;\n this.renderbuffer = this.createRenderbuffer();\n\n this.configureAttachments();\n this.assertComplete();\n this.unbind();\n }\n\n public get disposed(): boolean {\n return this.isDisposed;\n }\n\n public bind(): void {\n assertNotDisposed(\"Framebuffer\", this.isDisposed);\n this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);\n }\n\n public unbind(): void {\n this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);\n }\n\n public withBound<T>(render: () => T): T {\n this.bind();\n\n try {\n return render();\n } finally {\n this.unbind();\n }\n }\n\n public resize(options: FramebufferResizeOptions): void {\n assertNotDisposed(\"Framebuffer\", this.isDisposed);\n this.width = assertPositiveIntegerDimension(\"width\", options.width);\n this.height = assertPositiveIntegerDimension(\"height\", options.height);\n\n this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);\n this.allocateTextureStorage();\n\n if (this.renderbuffer !== null) {\n this.allocateRenderbufferStorage();\n }\n\n this.assertComplete();\n this.unbind();\n }\n\n public resizeToCanvas(canvas: FramebufferCanvasSize): void {\n this.resize({ width: canvas.width, height: canvas.height });\n }\n\n public readPixels(): Uint8Array {\n assertNotDisposed(\"Framebuffer\", this.isDisposed);\n\n const pixels = new Uint8Array(this.width * this.height * 4);\n this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);\n this.gl.readPixels(\n 0,\n 0,\n this.width,\n this.height,\n this.options.format,\n this.options.type,\n pixels\n );\n this.unbind();\n return pixels;\n }\n\n public dispose(): void {\n if (this.isDisposed) {\n return;\n }\n\n this.gl.deleteFramebuffer(this.framebuffer);\n this.gl.deleteTexture(this.texture);\n\n if (this.renderbuffer !== null) {\n this.gl.deleteRenderbuffer(this.renderbuffer);\n }\n\n this.isDisposed = true;\n }\n\n private createRenderbuffer(): WebGLRenderbuffer | null {\n if (!this.options.depth && !this.options.stencil) {\n return null;\n }\n\n const renderbuffer = this.gl.createRenderbuffer() as WebGLRenderbuffer | null;\n if (renderbuffer === null) {\n throw new WebGLError(\"Failed to create framebuffer renderbuffer.\");\n }\n\n return renderbuffer;\n }\n\n private configureAttachments(): void {\n const gl = this.gl;\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);\n gl.bindTexture(gl.TEXTURE_2D, this.texture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.options.minFilter);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.options.magFilter);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this.options.wrapS);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this.options.wrapT);\n this.allocateTextureStorage();\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);\n\n if (this.renderbuffer !== null) {\n this.allocateRenderbufferStorage();\n }\n }\n\n private allocateTextureStorage(): void {\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n this.gl.texImage2D(\n this.gl.TEXTURE_2D,\n 0,\n this.options.internalFormat,\n this.width,\n this.height,\n 0,\n this.options.format,\n this.options.type,\n null\n );\n this.gl.bindTexture(this.gl.TEXTURE_2D, null);\n }\n\n private allocateRenderbufferStorage(): void {\n if (this.renderbuffer === null) {\n return;\n }\n\n const attachment = this.options.stencil\n ? this.gl.DEPTH_STENCIL_ATTACHMENT\n : this.gl.DEPTH_ATTACHMENT;\n const storage = this.options.stencil ? this.gl.DEPTH_STENCIL : this.gl.DEPTH_COMPONENT16;\n\n this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.renderbuffer);\n this.gl.renderbufferStorage(this.gl.RENDERBUFFER, storage, this.width, this.height);\n this.gl.framebufferRenderbuffer(\n this.gl.FRAMEBUFFER,\n attachment,\n this.gl.RENDERBUFFER,\n this.renderbuffer\n );\n this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, null);\n }\n\n private assertComplete(): void {\n const status = this.gl.checkFramebufferStatus(this.gl.FRAMEBUFFER);\n\n if (status !== this.gl.FRAMEBUFFER_COMPLETE) {\n throw new WebGLError(getFramebufferStatusMessage(this.gl, status));\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type GLContext = WebGLRenderingContext | WebGL2RenderingContext;
|
|
2
|
+
declare function isWebGLContext(value: unknown): value is GLContext;
|
|
3
|
+
declare function isWebGL2(gl: GLContext): gl is WebGL2RenderingContext;
|
|
4
|
+
declare function getFramebufferStatusMessage(gl: GLContext, status: number): string;
|
|
5
|
+
|
|
6
|
+
export { type GLContext as G, isWebGLContext as a, getFramebufferStatusMessage as g, isWebGL2 as i };
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// packages/core/src/dimensions.ts
|
|
2
|
+
function assertPositiveIntegerDimension(name, value) {
|
|
3
|
+
if (!Number.isInteger(value)) {
|
|
4
|
+
throw new TypeError(`${name} must be an integer.`);
|
|
5
|
+
}
|
|
6
|
+
if (value <= 0) {
|
|
7
|
+
throw new RangeError(`${name} must be greater than 0.`);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// packages/core/src/errors.ts
|
|
13
|
+
var WebGLError = class extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "WebGLError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var DisposedResourceError = class extends WebGLError {
|
|
20
|
+
constructor(resourceName) {
|
|
21
|
+
super(`${resourceName} has been disposed.`);
|
|
22
|
+
this.name = "DisposedResourceError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// packages/core/src/disposable.ts
|
|
27
|
+
function assertNotDisposed(resourceName, disposed) {
|
|
28
|
+
if (disposed) {
|
|
29
|
+
throw new DisposedResourceError(resourceName);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// packages/core/src/gl.ts
|
|
34
|
+
function isWebGLContext(value) {
|
|
35
|
+
if (value === null || typeof value !== "object") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const candidate = value;
|
|
39
|
+
return typeof candidate.createFramebuffer === "function" && typeof candidate.bindFramebuffer === "function" && typeof candidate.checkFramebufferStatus === "function";
|
|
40
|
+
}
|
|
41
|
+
function getFramebufferStatusMessage(gl, status) {
|
|
42
|
+
const statuses = /* @__PURE__ */ new Map([
|
|
43
|
+
[gl.FRAMEBUFFER_COMPLETE, "Framebuffer is complete."],
|
|
44
|
+
[gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT, "Framebuffer has an incomplete attachment."],
|
|
45
|
+
[gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT, "Framebuffer has no image attached to it."],
|
|
46
|
+
[gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS, "Framebuffer attachments have mismatched dimensions."],
|
|
47
|
+
[gl.FRAMEBUFFER_UNSUPPORTED, "Framebuffer configuration is unsupported by this context."]
|
|
48
|
+
]);
|
|
49
|
+
return statuses.get(status) ?? `Unknown framebuffer status: ${status}.`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// packages/fbo/src/framebuffer.ts
|
|
53
|
+
var Framebuffer = class {
|
|
54
|
+
gl;
|
|
55
|
+
framebuffer;
|
|
56
|
+
texture;
|
|
57
|
+
renderbuffer;
|
|
58
|
+
width;
|
|
59
|
+
height;
|
|
60
|
+
options;
|
|
61
|
+
isDisposed = false;
|
|
62
|
+
constructor(gl, options) {
|
|
63
|
+
if (!isWebGLContext(gl)) {
|
|
64
|
+
throw new TypeError("gl must be a WebGL rendering context.");
|
|
65
|
+
}
|
|
66
|
+
this.gl = gl;
|
|
67
|
+
this.width = assertPositiveIntegerDimension("width", options.width);
|
|
68
|
+
this.height = assertPositiveIntegerDimension("height", options.height);
|
|
69
|
+
this.options = {
|
|
70
|
+
internalFormat: options.internalFormat ?? gl.RGBA,
|
|
71
|
+
format: options.format ?? gl.RGBA,
|
|
72
|
+
type: options.type ?? gl.UNSIGNED_BYTE,
|
|
73
|
+
minFilter: options.minFilter ?? gl.LINEAR,
|
|
74
|
+
magFilter: options.magFilter ?? gl.LINEAR,
|
|
75
|
+
wrapS: options.wrapS ?? gl.CLAMP_TO_EDGE,
|
|
76
|
+
wrapT: options.wrapT ?? gl.CLAMP_TO_EDGE,
|
|
77
|
+
depth: options.depth ?? false,
|
|
78
|
+
stencil: options.stencil ?? false
|
|
79
|
+
};
|
|
80
|
+
const framebuffer = gl.createFramebuffer();
|
|
81
|
+
const texture = gl.createTexture();
|
|
82
|
+
if (framebuffer === null) {
|
|
83
|
+
throw new WebGLError("Failed to create framebuffer.");
|
|
84
|
+
}
|
|
85
|
+
if (texture === null) {
|
|
86
|
+
gl.deleteFramebuffer(framebuffer);
|
|
87
|
+
throw new WebGLError("Failed to create framebuffer texture.");
|
|
88
|
+
}
|
|
89
|
+
this.framebuffer = framebuffer;
|
|
90
|
+
this.texture = texture;
|
|
91
|
+
this.renderbuffer = this.createRenderbuffer();
|
|
92
|
+
this.configureAttachments();
|
|
93
|
+
this.assertComplete();
|
|
94
|
+
this.unbind();
|
|
95
|
+
}
|
|
96
|
+
get disposed() {
|
|
97
|
+
return this.isDisposed;
|
|
98
|
+
}
|
|
99
|
+
bind() {
|
|
100
|
+
assertNotDisposed("Framebuffer", this.isDisposed);
|
|
101
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);
|
|
102
|
+
}
|
|
103
|
+
unbind() {
|
|
104
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
|
|
105
|
+
}
|
|
106
|
+
withBound(render) {
|
|
107
|
+
this.bind();
|
|
108
|
+
try {
|
|
109
|
+
return render();
|
|
110
|
+
} finally {
|
|
111
|
+
this.unbind();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
resize(options) {
|
|
115
|
+
assertNotDisposed("Framebuffer", this.isDisposed);
|
|
116
|
+
this.width = assertPositiveIntegerDimension("width", options.width);
|
|
117
|
+
this.height = assertPositiveIntegerDimension("height", options.height);
|
|
118
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);
|
|
119
|
+
this.allocateTextureStorage();
|
|
120
|
+
if (this.renderbuffer !== null) {
|
|
121
|
+
this.allocateRenderbufferStorage();
|
|
122
|
+
}
|
|
123
|
+
this.assertComplete();
|
|
124
|
+
this.unbind();
|
|
125
|
+
}
|
|
126
|
+
resizeToCanvas(canvas) {
|
|
127
|
+
this.resize({ width: canvas.width, height: canvas.height });
|
|
128
|
+
}
|
|
129
|
+
readPixels() {
|
|
130
|
+
assertNotDisposed("Framebuffer", this.isDisposed);
|
|
131
|
+
const pixels = new Uint8Array(this.width * this.height * 4);
|
|
132
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);
|
|
133
|
+
this.gl.readPixels(
|
|
134
|
+
0,
|
|
135
|
+
0,
|
|
136
|
+
this.width,
|
|
137
|
+
this.height,
|
|
138
|
+
this.options.format,
|
|
139
|
+
this.options.type,
|
|
140
|
+
pixels
|
|
141
|
+
);
|
|
142
|
+
this.unbind();
|
|
143
|
+
return pixels;
|
|
144
|
+
}
|
|
145
|
+
dispose() {
|
|
146
|
+
if (this.isDisposed) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.gl.deleteFramebuffer(this.framebuffer);
|
|
150
|
+
this.gl.deleteTexture(this.texture);
|
|
151
|
+
if (this.renderbuffer !== null) {
|
|
152
|
+
this.gl.deleteRenderbuffer(this.renderbuffer);
|
|
153
|
+
}
|
|
154
|
+
this.isDisposed = true;
|
|
155
|
+
}
|
|
156
|
+
createRenderbuffer() {
|
|
157
|
+
if (!this.options.depth && !this.options.stencil) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const renderbuffer = this.gl.createRenderbuffer();
|
|
161
|
+
if (renderbuffer === null) {
|
|
162
|
+
throw new WebGLError("Failed to create framebuffer renderbuffer.");
|
|
163
|
+
}
|
|
164
|
+
return renderbuffer;
|
|
165
|
+
}
|
|
166
|
+
configureAttachments() {
|
|
167
|
+
const gl = this.gl;
|
|
168
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
169
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
170
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.options.minFilter);
|
|
171
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.options.magFilter);
|
|
172
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this.options.wrapS);
|
|
173
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this.options.wrapT);
|
|
174
|
+
this.allocateTextureStorage();
|
|
175
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
|
|
176
|
+
if (this.renderbuffer !== null) {
|
|
177
|
+
this.allocateRenderbufferStorage();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
allocateTextureStorage() {
|
|
181
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
|
|
182
|
+
this.gl.texImage2D(
|
|
183
|
+
this.gl.TEXTURE_2D,
|
|
184
|
+
0,
|
|
185
|
+
this.options.internalFormat,
|
|
186
|
+
this.width,
|
|
187
|
+
this.height,
|
|
188
|
+
0,
|
|
189
|
+
this.options.format,
|
|
190
|
+
this.options.type,
|
|
191
|
+
null
|
|
192
|
+
);
|
|
193
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, null);
|
|
194
|
+
}
|
|
195
|
+
allocateRenderbufferStorage() {
|
|
196
|
+
if (this.renderbuffer === null) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const attachment = this.options.stencil ? this.gl.DEPTH_STENCIL_ATTACHMENT : this.gl.DEPTH_ATTACHMENT;
|
|
200
|
+
const storage = this.options.stencil ? this.gl.DEPTH_STENCIL : this.gl.DEPTH_COMPONENT16;
|
|
201
|
+
this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.renderbuffer);
|
|
202
|
+
this.gl.renderbufferStorage(this.gl.RENDERBUFFER, storage, this.width, this.height);
|
|
203
|
+
this.gl.framebufferRenderbuffer(
|
|
204
|
+
this.gl.FRAMEBUFFER,
|
|
205
|
+
attachment,
|
|
206
|
+
this.gl.RENDERBUFFER,
|
|
207
|
+
this.renderbuffer
|
|
208
|
+
);
|
|
209
|
+
this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, null);
|
|
210
|
+
}
|
|
211
|
+
assertComplete() {
|
|
212
|
+
const status = this.gl.checkFramebufferStatus(this.gl.FRAMEBUFFER);
|
|
213
|
+
if (status !== this.gl.FRAMEBUFFER_COMPLETE) {
|
|
214
|
+
throw new WebGLError(getFramebufferStatusMessage(this.gl, status));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export { Framebuffer as FBO, Framebuffer };
|
|
220
|
+
//# sourceMappingURL=index.js.map
|
|
221
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../packages/core/src/dimensions.ts","../packages/core/src/errors.ts","../packages/core/src/disposable.ts","../packages/core/src/gl.ts","../packages/fbo/src/framebuffer.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export function assertPositiveIntegerDimension(name: \"width\" | \"height\", value: number): number {\n if (!Number.isInteger(value)) {\n throw new TypeError(`${name} must be an integer.`);\n }\n\n if (value <= 0) {\n throw new RangeError(`${name} must be greater than 0.`);\n }\n\n return value;\n}\n","export class WebGLError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebGLError\";\n }\n}\n\nexport class DisposedResourceError extends WebGLError {\n constructor(resourceName: string) {\n super(`${resourceName} has been disposed.`);\n this.name = \"DisposedResourceError\";\n }\n}\n","import { DisposedResourceError } from \"./errors\";\n\nexport function assertNotDisposed(resourceName: string, disposed: boolean): void {\n if (disposed) {\n throw new DisposedResourceError(resourceName);\n }\n}\n","export type GLContext = WebGLRenderingContext | WebGL2RenderingContext;\n\ntype WebGLLike = {\n createFramebuffer: unknown;\n bindFramebuffer: unknown;\n checkFramebufferStatus: unknown;\n};\n\nexport function isWebGLContext(value: unknown): value is GLContext {\n if (value === null || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Partial<WebGLLike>;\n return (\n typeof candidate.createFramebuffer === \"function\" &&\n typeof candidate.bindFramebuffer === \"function\" &&\n typeof candidate.checkFramebufferStatus === \"function\"\n );\n}\n\nexport function isWebGL2(gl: GLContext): gl is WebGL2RenderingContext {\n return \"texStorage2D\" in gl && typeof gl.texStorage2D === \"function\";\n}\n\nexport function getFramebufferStatusMessage(gl: GLContext, status: number): string {\n const statuses = new Map<number, string>([\n [gl.FRAMEBUFFER_COMPLETE, \"Framebuffer is complete.\"],\n [gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT, \"Framebuffer has an incomplete attachment.\"],\n [gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT, \"Framebuffer has no image attached to it.\"],\n [gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS, \"Framebuffer attachments have mismatched dimensions.\"],\n [gl.FRAMEBUFFER_UNSUPPORTED, \"Framebuffer configuration is unsupported by this context.\"]\n ]);\n\n return statuses.get(status) ?? `Unknown framebuffer status: ${status}.`;\n}\n","import {\n WebGLError,\n assertNotDisposed,\n assertPositiveIntegerDimension,\n getFramebufferStatusMessage,\n isWebGLContext\n} from \"../../core/src/index\";\nimport type { GLContext } from \"../../core/src/index\";\n\nexport interface FramebufferOptions {\n width: number;\n height: number;\n internalFormat?: number;\n format?: number;\n type?: number;\n minFilter?: number;\n magFilter?: number;\n wrapS?: number;\n wrapT?: number;\n depth?: boolean;\n stencil?: boolean;\n}\n\nexport interface FramebufferResizeOptions {\n width: number;\n height: number;\n}\n\nexport type FramebufferCanvasSize = Pick<HTMLCanvasElement, \"width\" | \"height\">;\n\ntype TextureOptions = Required<\n Pick<\n FramebufferOptions,\n \"internalFormat\" | \"format\" | \"type\" | \"minFilter\" | \"magFilter\" | \"wrapS\" | \"wrapT\"\n >\n> &\n Required<Pick<FramebufferOptions, \"depth\" | \"stencil\">>;\n\nexport class Framebuffer {\n public readonly gl: GLContext;\n public readonly framebuffer: WebGLFramebuffer;\n public readonly texture: WebGLTexture;\n public readonly renderbuffer: WebGLRenderbuffer | null;\n public width: number;\n public height: number;\n\n private readonly options: TextureOptions;\n private isDisposed = false;\n\n constructor(gl: GLContext, options: FramebufferOptions) {\n if (!isWebGLContext(gl)) {\n throw new TypeError(\"gl must be a WebGL rendering context.\");\n }\n\n this.gl = gl;\n this.width = assertPositiveIntegerDimension(\"width\", options.width);\n this.height = assertPositiveIntegerDimension(\"height\", options.height);\n this.options = {\n internalFormat: options.internalFormat ?? gl.RGBA,\n format: options.format ?? gl.RGBA,\n type: options.type ?? gl.UNSIGNED_BYTE,\n minFilter: options.minFilter ?? gl.LINEAR,\n magFilter: options.magFilter ?? gl.LINEAR,\n wrapS: options.wrapS ?? gl.CLAMP_TO_EDGE,\n wrapT: options.wrapT ?? gl.CLAMP_TO_EDGE,\n depth: options.depth ?? false,\n stencil: options.stencil ?? false\n };\n\n const framebuffer = gl.createFramebuffer() as WebGLFramebuffer | null;\n const texture = gl.createTexture() as WebGLTexture | null;\n\n if (framebuffer === null) {\n throw new WebGLError(\"Failed to create framebuffer.\");\n }\n\n if (texture === null) {\n gl.deleteFramebuffer(framebuffer);\n throw new WebGLError(\"Failed to create framebuffer texture.\");\n }\n\n this.framebuffer = framebuffer;\n this.texture = texture;\n this.renderbuffer = this.createRenderbuffer();\n\n this.configureAttachments();\n this.assertComplete();\n this.unbind();\n }\n\n public get disposed(): boolean {\n return this.isDisposed;\n }\n\n public bind(): void {\n assertNotDisposed(\"Framebuffer\", this.isDisposed);\n this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);\n }\n\n public unbind(): void {\n this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);\n }\n\n public withBound<T>(render: () => T): T {\n this.bind();\n\n try {\n return render();\n } finally {\n this.unbind();\n }\n }\n\n public resize(options: FramebufferResizeOptions): void {\n assertNotDisposed(\"Framebuffer\", this.isDisposed);\n this.width = assertPositiveIntegerDimension(\"width\", options.width);\n this.height = assertPositiveIntegerDimension(\"height\", options.height);\n\n this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);\n this.allocateTextureStorage();\n\n if (this.renderbuffer !== null) {\n this.allocateRenderbufferStorage();\n }\n\n this.assertComplete();\n this.unbind();\n }\n\n public resizeToCanvas(canvas: FramebufferCanvasSize): void {\n this.resize({ width: canvas.width, height: canvas.height });\n }\n\n public readPixels(): Uint8Array {\n assertNotDisposed(\"Framebuffer\", this.isDisposed);\n\n const pixels = new Uint8Array(this.width * this.height * 4);\n this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);\n this.gl.readPixels(\n 0,\n 0,\n this.width,\n this.height,\n this.options.format,\n this.options.type,\n pixels\n );\n this.unbind();\n return pixels;\n }\n\n public dispose(): void {\n if (this.isDisposed) {\n return;\n }\n\n this.gl.deleteFramebuffer(this.framebuffer);\n this.gl.deleteTexture(this.texture);\n\n if (this.renderbuffer !== null) {\n this.gl.deleteRenderbuffer(this.renderbuffer);\n }\n\n this.isDisposed = true;\n }\n\n private createRenderbuffer(): WebGLRenderbuffer | null {\n if (!this.options.depth && !this.options.stencil) {\n return null;\n }\n\n const renderbuffer = this.gl.createRenderbuffer() as WebGLRenderbuffer | null;\n if (renderbuffer === null) {\n throw new WebGLError(\"Failed to create framebuffer renderbuffer.\");\n }\n\n return renderbuffer;\n }\n\n private configureAttachments(): void {\n const gl = this.gl;\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);\n gl.bindTexture(gl.TEXTURE_2D, this.texture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.options.minFilter);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.options.magFilter);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this.options.wrapS);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this.options.wrapT);\n this.allocateTextureStorage();\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);\n\n if (this.renderbuffer !== null) {\n this.allocateRenderbufferStorage();\n }\n }\n\n private allocateTextureStorage(): void {\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n this.gl.texImage2D(\n this.gl.TEXTURE_2D,\n 0,\n this.options.internalFormat,\n this.width,\n this.height,\n 0,\n this.options.format,\n this.options.type,\n null\n );\n this.gl.bindTexture(this.gl.TEXTURE_2D, null);\n }\n\n private allocateRenderbufferStorage(): void {\n if (this.renderbuffer === null) {\n return;\n }\n\n const attachment = this.options.stencil\n ? this.gl.DEPTH_STENCIL_ATTACHMENT\n : this.gl.DEPTH_ATTACHMENT;\n const storage = this.options.stencil ? this.gl.DEPTH_STENCIL : this.gl.DEPTH_COMPONENT16;\n\n this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.renderbuffer);\n this.gl.renderbufferStorage(this.gl.RENDERBUFFER, storage, this.width, this.height);\n this.gl.framebufferRenderbuffer(\n this.gl.FRAMEBUFFER,\n attachment,\n this.gl.RENDERBUFFER,\n this.renderbuffer\n );\n this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, null);\n }\n\n private assertComplete(): void {\n const status = this.gl.checkFramebufferStatus(this.gl.FRAMEBUFFER);\n\n if (status !== this.gl.FRAMEBUFFER_COMPLETE) {\n throw new WebGLError(getFramebufferStatusMessage(this.gl, status));\n }\n }\n}\n"]}
|
|
Binary file
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="620" viewBox="0 0 1200 620" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">Framebuffer Object render workflow</title>
|
|
3
|
+
<desc id="desc">A visual walkthrough of rendering into an off-screen framebuffer texture and sampling it on the screen pass.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="screen" x1="0" y1="0" x2="1" y2="1">
|
|
6
|
+
<stop offset="0" stop-color="#14324a"/>
|
|
7
|
+
<stop offset="1" stop-color="#1d6f8d"/>
|
|
8
|
+
</linearGradient>
|
|
9
|
+
<linearGradient id="texture" x1="0" y1="0" x2="1" y2="1">
|
|
10
|
+
<stop offset="0" stop-color="#8bd3ff"/>
|
|
11
|
+
<stop offset="0.55" stop-color="#f0ca50"/>
|
|
12
|
+
<stop offset="1" stop-color="#df5b57"/>
|
|
13
|
+
</linearGradient>
|
|
14
|
+
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
|
|
15
|
+
<feDropShadow dx="0" dy="12" stdDeviation="12" flood-color="#0b1720" flood-opacity="0.18"/>
|
|
16
|
+
</filter>
|
|
17
|
+
</defs>
|
|
18
|
+
<rect width="1200" height="620" fill="#f7fafc"/>
|
|
19
|
+
<text x="72" y="78" font-family="Inter, Arial, sans-serif" font-size="34" font-weight="700" fill="#17212b">Framebuffer pass, then screen pass</text>
|
|
20
|
+
<text x="72" y="114" font-family="Inter, Arial, sans-serif" font-size="18" fill="#52616f">Render once into a texture, then use that texture in a second shader pass.</text>
|
|
21
|
+
|
|
22
|
+
<g filter="url(#shadow)">
|
|
23
|
+
<rect x="72" y="178" width="332" height="272" rx="18" fill="#ffffff" stroke="#d8e1e8"/>
|
|
24
|
+
<text x="104" y="226" font-family="Inter, Arial, sans-serif" font-size="22" font-weight="700" fill="#1d2a35">1. Off-screen target</text>
|
|
25
|
+
<rect x="104" y="258" width="268" height="142" rx="12" fill="#e9f4fb" stroke="#aac7d8"/>
|
|
26
|
+
<rect x="132" y="288" width="92" height="82" rx="8" fill="url(#texture)"/>
|
|
27
|
+
<rect x="246" y="288" width="92" height="82" rx="8" fill="#1f2933" opacity="0.86"/>
|
|
28
|
+
<text x="142" y="419" font-family="Inter, Arial, sans-serif" font-size="15" fill="#52616f">color texture</text>
|
|
29
|
+
<text x="253" y="419" font-family="Inter, Arial, sans-serif" font-size="15" fill="#52616f">depth buffer</text>
|
|
30
|
+
</g>
|
|
31
|
+
|
|
32
|
+
<path d="M430 314 C490 314 508 314 568 314" fill="none" stroke="#49758f" stroke-width="4" stroke-linecap="round"/>
|
|
33
|
+
<path d="M568 314 L546 300 L546 328 Z" fill="#49758f"/>
|
|
34
|
+
<text x="456" y="286" font-family="Inter, Arial, sans-serif" font-size="15" font-weight="700" fill="#34566a">sample texture</text>
|
|
35
|
+
|
|
36
|
+
<g filter="url(#shadow)">
|
|
37
|
+
<rect x="594" y="178" width="332" height="272" rx="18" fill="#ffffff" stroke="#d8e1e8"/>
|
|
38
|
+
<text x="626" y="226" font-family="Inter, Arial, sans-serif" font-size="22" font-weight="700" fill="#1d2a35">2. Screen framebuffer</text>
|
|
39
|
+
<rect x="626" y="258" width="268" height="142" rx="12" fill="url(#screen)"/>
|
|
40
|
+
<circle cx="707" cy="329" r="42" fill="#8bd3ff" opacity="0.9"/>
|
|
41
|
+
<circle cx="782" cy="329" r="42" fill="#f0ca50" opacity="0.9"/>
|
|
42
|
+
<circle cx="857" cy="329" r="42" fill="#df5b57" opacity="0.9"/>
|
|
43
|
+
<text x="672" y="419" font-family="Inter, Arial, sans-serif" font-size="15" fill="#52616f">post-process quad</text>
|
|
44
|
+
</g>
|
|
45
|
+
|
|
46
|
+
<g filter="url(#shadow)">
|
|
47
|
+
<rect x="970" y="178" width="158" height="272" rx="18" fill="#ffffff" stroke="#d8e1e8"/>
|
|
48
|
+
<text x="1001" y="226" font-family="Inter, Arial, sans-serif" font-size="22" font-weight="700" fill="#1d2a35">API</text>
|
|
49
|
+
<text x="1002" y="272" font-family="SFMono-Regular, Menlo, monospace" font-size="14" fill="#34566a">bind()</text>
|
|
50
|
+
<text x="1002" y="306" font-family="SFMono-Regular, Menlo, monospace" font-size="14" fill="#34566a">resize()</text>
|
|
51
|
+
<text x="1002" y="340" font-family="SFMono-Regular, Menlo, monospace" font-size="14" fill="#34566a">readPixels()</text>
|
|
52
|
+
<text x="1002" y="374" font-family="SFMono-Regular, Menlo, monospace" font-size="14" fill="#34566a">dispose()</text>
|
|
53
|
+
</g>
|
|
54
|
+
|
|
55
|
+
<rect x="72" y="506" width="1056" height="56" rx="14" fill="#e9f4fb" stroke="#c6d9e7"/>
|
|
56
|
+
<text x="104" y="541" font-family="SFMono-Regular, Menlo, monospace" font-size="17" fill="#1d4d64">fbo.bind() -> draw scene -> fbo.unbind() -> bind fbo.texture -> draw fullscreen quad</text>
|
|
57
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# FBO post-process workflow
|
|
2
|
+
|
|
3
|
+
This is the first visual acceptance target for webgraphiclibrary v2.
|
|
4
|
+
|
|
5
|
+
The intended workflow is:
|
|
6
|
+
|
|
7
|
+
1. Create a `Framebuffer` with a color texture and optional depth storage.
|
|
8
|
+
2. Render a scene while the framebuffer is bound.
|
|
9
|
+
3. Unbind the framebuffer.
|
|
10
|
+
4. Sample `framebuffer.texture` in a screen-space pass.
|
|
11
|
+
5. Resize and dispose the framebuffer with the rest of the render lifecycle.
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
This example starts as a documented workflow target. The next package slice will add the shader, program, buffer, and texture helpers needed for a complete browser demo without raw WebGL setup code in the example.
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "webgraphiclibrary",
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
|
+
"description": "Small, type-safe WebGL utilities for low-level rendering workflows.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "ahmerhabib",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/ahmerhabib/webgraphiclibrary.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ahmerhabib/webgraphiclibrary/issues"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/ahmerhabib/webgraphiclibrary#readme",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22"
|
|
19
|
+
},
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE.md",
|
|
25
|
+
"docs/assets",
|
|
26
|
+
"docs/screenshots",
|
|
27
|
+
"examples"
|
|
28
|
+
],
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./core": {
|
|
35
|
+
"types": "./dist/core.d.ts",
|
|
36
|
+
"import": "./dist/core.js"
|
|
37
|
+
},
|
|
38
|
+
"./fbo": {
|
|
39
|
+
"types": "./dist/fbo.d.ts",
|
|
40
|
+
"import": "./dist/fbo.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"clean": "rimraf dist packages/*/dist coverage",
|
|
46
|
+
"format": "prettier --write .",
|
|
47
|
+
"format:check": "prettier --check .",
|
|
48
|
+
"lint": "eslint .",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"test:watch": "vitest",
|
|
51
|
+
"typecheck": "tsc -p tsconfig.base.json --noEmit",
|
|
52
|
+
"prepublishOnly": "pnpm lint && pnpm typecheck && pnpm test && pnpm build",
|
|
53
|
+
"screenshots": "node scripts/capture-readme-screenshots.mjs"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@eslint/js": "^9.39.1",
|
|
57
|
+
"@types/node": "^22.19.1",
|
|
58
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
59
|
+
"eslint": "^9.39.1",
|
|
60
|
+
"playwright": "^1.61.1",
|
|
61
|
+
"prettier": "^3.7.4",
|
|
62
|
+
"rimraf": "^6.1.2",
|
|
63
|
+
"tsup": "^8.5.1",
|
|
64
|
+
"typescript": "^5.9.3",
|
|
65
|
+
"typescript-eslint": "^8.49.0",
|
|
66
|
+
"vitest": "^3.2.4"
|
|
67
|
+
},
|
|
68
|
+
"packageManager": "pnpm@10.33.0",
|
|
69
|
+
"main": "./dist/index.js",
|
|
70
|
+
"types": "./dist/index.d.ts",
|
|
71
|
+
"keywords": [
|
|
72
|
+
"webgl",
|
|
73
|
+
"framebuffer",
|
|
74
|
+
"fbo",
|
|
75
|
+
"graphics",
|
|
76
|
+
"rendering",
|
|
77
|
+
"typescript",
|
|
78
|
+
"offscreen-rendering"
|
|
79
|
+
],
|
|
80
|
+
"publishConfig": {
|
|
81
|
+
"registry": "https://registry.npmjs.org/",
|
|
82
|
+
"tag": "beta"
|
|
83
|
+
}
|
|
84
|
+
}
|