@absolutejs/rendering 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +88 -0
- package/README.md +55 -2
- package/dist/index.d.ts +14 -0
- package/dist/index.js +78 -0
- package/dist/renderTargetScope.d.ts +18 -0
- package/dist/renderTargetScope.js +47 -0
- package/package.json +44 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
**Licensor:** Alex Kahn
|
|
4
|
+
|
|
5
|
+
**Licensed Work:** @absolutejs/rendering (https://github.com/absolutejs/rendering)
|
|
6
|
+
|
|
7
|
+
**Change Date:** November 1, 2030
|
|
8
|
+
|
|
9
|
+
**Change License:** Apache License, Version 2.0
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Terms
|
|
14
|
+
|
|
15
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
16
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
17
|
+
Licensor may make an Additional Use Grant, permitting limited production use.
|
|
18
|
+
|
|
19
|
+
### Additional Use Grant
|
|
20
|
+
|
|
21
|
+
You may use the Licensed Work in production, provided your use does not include
|
|
22
|
+
any of the following:
|
|
23
|
+
|
|
24
|
+
1. **Offering a Competing Service.** You may not offer the Licensed Work, or
|
|
25
|
+
any derivative or substantial portion of it, to third parties as a hosted or
|
|
26
|
+
managed service that competes with a hosted rendering or 3D authoring service. This includes any
|
|
27
|
+
product whose primary value to its users is the functionality the Licensed
|
|
28
|
+
Work provides.
|
|
29
|
+
|
|
30
|
+
2. **Resale or Redistribution as a Standalone Product.** You may not sell,
|
|
31
|
+
license, or distribute the Licensed Work, or any derivative or fork of it,
|
|
32
|
+
as a standalone commercial product.
|
|
33
|
+
|
|
34
|
+
3. **Removal of Attribution.** Any derivative work, fork, or redistribution of
|
|
35
|
+
the Licensed Work must prominently credit AbsoluteJS and include a link to
|
|
36
|
+
the original project repository (https://github.com/absolutejs/rendering).
|
|
37
|
+
|
|
38
|
+
For clarity, the following uses are expressly permitted:
|
|
39
|
+
|
|
40
|
+
- Using the Licensed Work to build and operate your own applications, websites,
|
|
41
|
+
internal tools, or SaaS products (whether commercial or non-commercial), so
|
|
42
|
+
long as the Licensed Work itself is not the primary product you are selling.
|
|
43
|
+
- Using the Licensed Work as a dependency in commercial software you build and
|
|
44
|
+
sell, as long as the software is not itself a competing managed service of
|
|
45
|
+
the kind described in clause 1.
|
|
46
|
+
- Providing consulting, development, or professional services to clients using
|
|
47
|
+
the Licensed Work.
|
|
48
|
+
- Forking and modifying the Licensed Work for your own internal use, provided
|
|
49
|
+
attribution is maintained.
|
|
50
|
+
|
|
51
|
+
### Change Date and Change License
|
|
52
|
+
|
|
53
|
+
On the Change Date specified above, or on such other date as the Licensor may
|
|
54
|
+
specify by written notice, the Licensed Work will be made available under the
|
|
55
|
+
Change License (Apache License, Version 2.0). Until the Change Date, the terms
|
|
56
|
+
of this Business Source License 1.1 apply.
|
|
57
|
+
|
|
58
|
+
### Trademark
|
|
59
|
+
|
|
60
|
+
This license does not grant you any rights to use the "AbsoluteJS" or
|
|
61
|
+
"@absolutejs" name, logo, or any related trademarks. Forks and derivative works
|
|
62
|
+
must not be named or branded in a manner that suggests endorsement by or
|
|
63
|
+
affiliation with AbsoluteJS or the Licensor.
|
|
64
|
+
|
|
65
|
+
### Notices
|
|
66
|
+
|
|
67
|
+
You must not remove or obscure any licensing, copyright, or other notices
|
|
68
|
+
included in the Licensed Work.
|
|
69
|
+
|
|
70
|
+
### No Warranty
|
|
71
|
+
|
|
72
|
+
THE LICENSED WORK IS PROVIDED "AS IS". THE LICENSOR HEREBY DISCLAIMS ALL
|
|
73
|
+
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
|
|
74
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO
|
|
75
|
+
EVENT SHALL THE LICENSOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY,
|
|
76
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR
|
|
77
|
+
IN CONNECTION WITH THE LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE
|
|
78
|
+
LICENSED WORK.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Contact
|
|
83
|
+
|
|
84
|
+
For commercial licensing inquiries or additional permissions, contact:
|
|
85
|
+
|
|
86
|
+
- **Alex Kahn**
|
|
87
|
+
- alexkahndev@gmail.com
|
|
88
|
+
- alexkahndev.github.io
|
package/README.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @absolutejs/rendering
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Small Three.js material-lifecycle utilities for AbsoluteJS applications and 3D authoring tools. These complement R3F and drei; they do not provide a scene graph, camera controls, effects composer or renderer replacement.
|
|
4
|
+
|
|
5
|
+
## Coverage follows the destination
|
|
6
|
+
|
|
7
|
+
A multisampled canvas does not make a reflection texture multisampled. An alpha-to-coverage shader used unchanged in a single-sample target can draw partially covered foliage as solid pixels.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { bindAlphaToCoverageDestination } from '@absolutejs/rendering';
|
|
11
|
+
material.alphaToCoverage = true;
|
|
12
|
+
const unbind = bindAlphaToCoverageDestination(material);
|
|
13
|
+
// On every draw, the policy selects coverage from the active render destination.
|
|
14
|
+
// Call unbind() when removing the policy while retaining the material.
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The policy runs after the previous material callback and before Three selects the program. It updates the material only when the effective coverage mode changes. Three can cache the corresponding program variants. It does not inject shaders, allocate textures, traverse the scene, monkey-patch a renderer, or call synchronous GPU queries per draw.
|
|
18
|
+
|
|
19
|
+
Shader customizations must retain their non-ALPHA_TO_COVERAGE fallback. The policy uses declared render-target sample counts, bounded by renderer support; callers must create targets with supported attachment formats. Canvas antialias attributes are cached per renderer, but a lost context is not cached as a permanent capability result. Disabled preferences remain disabled on all destinations. Shadow passes that call renderBufferDirect require their own depth-material policy; this callback targets normal material draws.
|
|
20
|
+
|
|
21
|
+
Rebinding updates the existing preference without stacking callbacks. The returned disposer is idempotent and restores the original flag and callback only while it still owns the callback slot; it will not overwrite a later owner.
|
|
22
|
+
|
|
23
|
+
## Clone customized materials safely
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { cloneRenderMaterial } from '@absolutejs/rendering';
|
|
27
|
+
const owned = cloneRenderMaterial(source);
|
|
28
|
+
// Textures remain borrowed. Dispose the clone, not the source's textures.
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This retains onBeforeCompile, customProgramCacheKey and onBeforeRender. A registered destination policy is cloned independently, including when the source was last drawn into a non-MSAA target. Changing the clone's preference cannot affect the source. This does not clone arbitrary object callbacks, custom depth materials or scene state.
|
|
32
|
+
|
|
33
|
+
## Compatibility and scope
|
|
34
|
+
|
|
35
|
+
Qualified against Three r186. No React dependency and no animation loop. Runtime cost is a small callback and destination check per affected draw; switching sampling modes can select another cached shader program. Hardware frame-rate improvements are not claimed. Game-specific foliage algorithms, lighting, world generation and simulation remain outside this package.
|
|
36
|
+
|
|
37
|
+
Use `bun run check:package` to build declarations and run the ownership/policy tests. Browser evidence is retained under docs/.
|
|
38
|
+
|
|
39
|
+
## Scoped captures and readback
|
|
40
|
+
|
|
41
|
+
`renderTargetScope(renderer, run)` restores the render target, cube face, mip,
|
|
42
|
+
viewport, scissor and scissor-test state even if synchronous work throws. The
|
|
43
|
+
callback must finish renderer mutations synchronously; do not hold a scope over
|
|
44
|
+
an await.
|
|
45
|
+
|
|
46
|
+
`readTargetPixelsScoped(renderer, target, width, height, buffer, textureIndex?)`
|
|
47
|
+
submits a byte/float read from a neutral target and restores the caller through
|
|
48
|
+
Three's target setter. This avoids restoring a resolve framebuffer in place of
|
|
49
|
+
its multisample draw attachment, or treating a cube framebuffer array as a single
|
|
50
|
+
framebuffer.
|
|
51
|
+
|
|
52
|
+
`readFloatTargetScoped(...)` submits asynchronous float readback in the same
|
|
53
|
+
neutral state, restores the caller immediately, and returns the exact supplied
|
|
54
|
+
Float32Array on completion. It never restores stale renderer state after await.
|
|
55
|
+
Allocate supported targets and correctly sized buffers before calling these
|
|
56
|
+
helpers. The helpers do not choose formats, sample counts or capture resolution.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Material, WebGLRenderer } from "three";
|
|
2
|
+
/** Resolve the destination's declared sample count without a synchronous GPU
|
|
3
|
+
* query per draw. Render targets must have a supported attachment configuration. */
|
|
4
|
+
export declare function destinationIsMultisampled(renderer: WebGLRenderer): boolean;
|
|
5
|
+
/** Select coverage before program selection for each draw, including captures.
|
|
6
|
+
* Shader hooks must retain their non-ALPHA_TO_COVERAGE fallback. Rebinding is
|
|
7
|
+
* idempotent; disposal restores the previous callback/state while still owned.
|
|
8
|
+
* No global renderer monkey patch, traversal, texture allocation or shader text. */
|
|
9
|
+
export declare function bindAlphaToCoverageDestination(material: Material, enabled?: boolean): () => void;
|
|
10
|
+
/** Own the clone, borrow its textures, preserve authored shader/render hooks.
|
|
11
|
+
* Destination policy is cloned independently so changing one owner's preference
|
|
12
|
+
* cannot alter another. No source material is mutated. */
|
|
13
|
+
export declare function cloneRenderMaterial<T extends Material>(source: T): T;
|
|
14
|
+
export { renderTargetScope, readTargetPixelsScoped, readFloatTargetScoped, } from "./renderTargetScope.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const bindings = new WeakMap();
|
|
2
|
+
const canvasSampling = new WeakMap();
|
|
3
|
+
function setCoverage(material, enabled) {
|
|
4
|
+
if (material.alphaToCoverage === enabled)
|
|
5
|
+
return;
|
|
6
|
+
material.alphaToCoverage = enabled;
|
|
7
|
+
material.needsUpdate = true;
|
|
8
|
+
}
|
|
9
|
+
/** Resolve the destination's declared sample count without a synchronous GPU
|
|
10
|
+
* query per draw. Render targets must have a supported attachment configuration. */
|
|
11
|
+
export function destinationIsMultisampled(renderer) {
|
|
12
|
+
const target = renderer.getRenderTarget();
|
|
13
|
+
if (target)
|
|
14
|
+
return target.samples > 0 && renderer.capabilities.maxSamples > 0;
|
|
15
|
+
let sampled = canvasSampling.get(renderer);
|
|
16
|
+
if (sampled === undefined) {
|
|
17
|
+
const attributes = renderer.getContextAttributes();
|
|
18
|
+
if (!attributes)
|
|
19
|
+
return false; // Do not cache context loss as a permanent capability.
|
|
20
|
+
sampled = attributes.antialias === true;
|
|
21
|
+
canvasSampling.set(renderer, sampled);
|
|
22
|
+
}
|
|
23
|
+
return sampled;
|
|
24
|
+
}
|
|
25
|
+
/** Select coverage before program selection for each draw, including captures.
|
|
26
|
+
* Shader hooks must retain their non-ALPHA_TO_COVERAGE fallback. Rebinding is
|
|
27
|
+
* idempotent; disposal restores the previous callback/state while still owned.
|
|
28
|
+
* No global renderer monkey patch, traversal, texture allocation or shader text. */
|
|
29
|
+
export function bindAlphaToCoverageDestination(material, enabled = material.alphaToCoverage) {
|
|
30
|
+
if (typeof enabled !== "boolean")
|
|
31
|
+
throw Error("Invalid coverage preference");
|
|
32
|
+
const existing = bindings.get(material);
|
|
33
|
+
if (existing && material.onBeforeRender === existing.callback) {
|
|
34
|
+
existing.enabled = enabled;
|
|
35
|
+
return existing.dispose;
|
|
36
|
+
}
|
|
37
|
+
const previous = material.onBeforeRender, originalCoverage = material.alphaToCoverage;
|
|
38
|
+
let disposed = false;
|
|
39
|
+
const state = {
|
|
40
|
+
enabled,
|
|
41
|
+
previous,
|
|
42
|
+
callback: function (...args) {
|
|
43
|
+
previous.apply(this, args);
|
|
44
|
+
setCoverage(this, state.enabled && destinationIsMultisampled(args[0]));
|
|
45
|
+
},
|
|
46
|
+
dispose: () => {
|
|
47
|
+
if (disposed)
|
|
48
|
+
return;
|
|
49
|
+
disposed = true;
|
|
50
|
+
if (material.onBeforeRender === state.callback) {
|
|
51
|
+
material.onBeforeRender = previous;
|
|
52
|
+
setCoverage(material, originalCoverage);
|
|
53
|
+
}
|
|
54
|
+
if (bindings.get(material) === state)
|
|
55
|
+
bindings.delete(material);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
material.onBeforeRender = state.callback;
|
|
59
|
+
bindings.set(material, state);
|
|
60
|
+
return state.dispose;
|
|
61
|
+
}
|
|
62
|
+
/** Own the clone, borrow its textures, preserve authored shader/render hooks.
|
|
63
|
+
* Destination policy is cloned independently so changing one owner's preference
|
|
64
|
+
* cannot alter another. No source material is mutated. */
|
|
65
|
+
export function cloneRenderMaterial(source) {
|
|
66
|
+
const copy = source.clone();
|
|
67
|
+
copy.onBeforeCompile = source.onBeforeCompile;
|
|
68
|
+
copy.customProgramCacheKey = source.customProgramCacheKey;
|
|
69
|
+
const binding = bindings.get(source);
|
|
70
|
+
if (binding && source.onBeforeRender === binding.callback) {
|
|
71
|
+
copy.onBeforeRender = binding.previous;
|
|
72
|
+
bindAlphaToCoverageDestination(copy, binding.enabled);
|
|
73
|
+
}
|
|
74
|
+
else
|
|
75
|
+
copy.onBeforeRender = source.onBeforeRender;
|
|
76
|
+
return copy;
|
|
77
|
+
}
|
|
78
|
+
export { renderTargetScope, readTargetPixelsScoped, readFloatTargetScoped, } from "./renderTargetScope.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type WebGLRenderer } from "three";
|
|
2
|
+
/** Synchronous offscreen work; preserve target attachment, viewport and clipping.
|
|
3
|
+
* The callback must finish its renderer mutations before returning.
|
|
4
|
+
*/
|
|
5
|
+
export declare function renderTargetScope<T>(renderer: WebGLRenderer, run: () => T): T;
|
|
6
|
+
/** Start an async float diagnostic with a neutral current target, then immediately
|
|
7
|
+
* restore the caller's attachment. Three's async reader restores a raw framebuffer
|
|
8
|
+
* property before awaiting; cube/mipmap targets store arrays in that property.
|
|
9
|
+
* Keeping the neutral target only for submission avoids that invalid bind without
|
|
10
|
+
* holding renderer state across the asynchronous wait.
|
|
11
|
+
*/
|
|
12
|
+
export declare function readFloatTargetScoped(renderer: WebGLRenderer, target: import("three").WebGLRenderTarget, width: number, height: number, buffer: Float32Array, textureIndex?: number): Promise<Float32Array>;
|
|
13
|
+
/** Synchronous authoring/diagnostic read. Three restores the raw resolve
|
|
14
|
+
* framebuffer, not a current target's multisample attachment, after reading.
|
|
15
|
+
* Submit from a neutral target and let setRenderTarget restore the actual
|
|
16
|
+
* attachment; otherwise the next draw can resolve stale samples over new data.
|
|
17
|
+
* This also avoids restoring cube/mip framebuffer arrays as a framebuffer. */
|
|
18
|
+
export declare function readTargetPixelsScoped(renderer: WebGLRenderer, target: import("three").WebGLRenderTarget, width: number, height: number, buffer: Uint8Array | Float32Array, textureIndex?: number): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Vector4 } from "three";
|
|
2
|
+
/** Synchronous offscreen work; preserve target attachment, viewport and clipping.
|
|
3
|
+
* The callback must finish its renderer mutations before returning.
|
|
4
|
+
*/
|
|
5
|
+
export function renderTargetScope(renderer, run) {
|
|
6
|
+
const target = renderer.getRenderTarget(), face = renderer.getActiveCubeFace(), mip = renderer.getActiveMipmapLevel();
|
|
7
|
+
const viewport = renderer.getViewport(new Vector4()), scissor = renderer.getScissor(new Vector4()), test = renderer.getScissorTest();
|
|
8
|
+
try {
|
|
9
|
+
renderer.setScissorTest(false);
|
|
10
|
+
return run();
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
renderer.setRenderTarget(target, face, mip);
|
|
14
|
+
renderer.setViewport(viewport);
|
|
15
|
+
renderer.setScissor(scissor);
|
|
16
|
+
renderer.setScissorTest(test);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Start an async float diagnostic with a neutral current target, then immediately
|
|
20
|
+
* restore the caller's attachment. Three's async reader restores a raw framebuffer
|
|
21
|
+
* property before awaiting; cube/mipmap targets store arrays in that property.
|
|
22
|
+
* Keeping the neutral target only for submission avoids that invalid bind without
|
|
23
|
+
* holding renderer state across the asynchronous wait.
|
|
24
|
+
*/
|
|
25
|
+
export function readFloatTargetScoped(renderer, target, width, height, buffer, textureIndex = 0) {
|
|
26
|
+
let pending;
|
|
27
|
+
renderTargetScope(renderer, () => {
|
|
28
|
+
renderer.setRenderTarget(null);
|
|
29
|
+
pending = renderer.readRenderTargetPixelsAsync(target, 0, 0, width, height, buffer, undefined, textureIndex);
|
|
30
|
+
});
|
|
31
|
+
return pending.then((value) => {
|
|
32
|
+
if (value !== buffer)
|
|
33
|
+
throw new Error("Float target read did not return its buffer");
|
|
34
|
+
return buffer;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Synchronous authoring/diagnostic read. Three restores the raw resolve
|
|
38
|
+
* framebuffer, not a current target's multisample attachment, after reading.
|
|
39
|
+
* Submit from a neutral target and let setRenderTarget restore the actual
|
|
40
|
+
* attachment; otherwise the next draw can resolve stale samples over new data.
|
|
41
|
+
* This also avoids restoring cube/mip framebuffer arrays as a framebuffer. */
|
|
42
|
+
export function readTargetPixelsScoped(renderer, target, width, height, buffer, textureIndex = 0) {
|
|
43
|
+
renderTargetScope(renderer, () => {
|
|
44
|
+
renderer.setRenderTarget(null);
|
|
45
|
+
renderer.readRenderTargetPixels(target, 0, 0, width, height, buffer, undefined, textureIndex);
|
|
46
|
+
});
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absolutejs/rendering",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Render-destination coverage policies and callback-preserving material ownership for Three.js applications.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"license": "BSL-1.1",
|
|
22
|
+
"author": "Alex Kahn",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/absolutejs/rendering.git"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.json",
|
|
32
|
+
"test": "bun test ./tests",
|
|
33
|
+
"check:package": "bun run build && bun run test",
|
|
34
|
+
"test:browser": "bun scripts/verify-browser.cjs"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"three": ">=0.186.0 <0.187.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"three": "0.186.1",
|
|
41
|
+
"@types/three": "0.186.0",
|
|
42
|
+
"@types/bun": "1.4.2",
|
|
43
|
+
"typescript": "5.9.3",
|
|
44
|
+
"playwright": "1.61.1"
|
|
45
|
+
}
|
|
46
|
+
}
|