@effect-motion/three 0.5.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/dist/Interop.d.ts +28 -0
- package/dist/Interop.js +34 -0
- package/dist/Line2.d.ts +42 -0
- package/dist/Line2.js +39 -0
- package/dist/Object3D.d.ts +13 -0
- package/dist/Object3D.js +1 -0
- package/dist/PostProcessing.d.ts +112 -0
- package/dist/PostProcessing.js +102 -0
- package/dist/RenderTarget.d.ts +86 -0
- package/dist/RenderTarget.js +97 -0
- package/dist/Renderer.d.ts +204 -0
- package/dist/Renderer.js +241 -0
- package/dist/Scene.d.ts +105 -0
- package/dist/Scene.js +118 -0
- package/dist/ThreeException.d.ts +24 -0
- package/dist/ThreeException.js +16 -0
- package/dist/Tsl.d.ts +18 -0
- package/dist/Tsl.js +18 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +76 -0
- package/dist/node.d.ts +47 -0
- package/dist/node.js +157 -0
- package/dist/usageDemo.d.ts +16 -0
- package/dist/usageDemo.js +35 -0
- package/package.json +57 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ThreeException } from "./ThreeException.js";
|
|
3
|
+
/**
|
|
4
|
+
* The seam between three.js and Effect: turning thrown exceptions and
|
|
5
|
+
* rejected promises into typed failures.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Used to wrap the calls that can actually fail — construction, GPU work,
|
|
9
|
+
* async initialization. Per-frame object mutation deliberately does NOT go
|
|
10
|
+
* through here: it cannot fail, and wrapping it would allocate an Effect per
|
|
11
|
+
* property write in a hot loop.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Run a synchronous three call, converting a throw into a
|
|
15
|
+
* {@link ThreeException}.
|
|
16
|
+
*
|
|
17
|
+
* @param operation - Name of the three operation, used in the error.
|
|
18
|
+
* @param fn - The call to make.
|
|
19
|
+
*/
|
|
20
|
+
export declare const wrap: <A>(operation: string, fn: () => A) => Effect.Effect<A, ThreeException, never>;
|
|
21
|
+
/**
|
|
22
|
+
* Run an async three call, converting a rejection into a
|
|
23
|
+
* {@link ThreeException}.
|
|
24
|
+
*
|
|
25
|
+
* @param operation - Name of the three operation, used in the error.
|
|
26
|
+
* @param fn - The call to make.
|
|
27
|
+
*/
|
|
28
|
+
export declare const wrapPromise: <A>(operation: string, fn: () => Promise<A>) => Effect.Effect<A, ThreeException, never>;
|
package/dist/Interop.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ThreeException } from "./ThreeException.js";
|
|
3
|
+
/**
|
|
4
|
+
* The seam between three.js and Effect: turning thrown exceptions and
|
|
5
|
+
* rejected promises into typed failures.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Used to wrap the calls that can actually fail — construction, GPU work,
|
|
9
|
+
* async initialization. Per-frame object mutation deliberately does NOT go
|
|
10
|
+
* through here: it cannot fail, and wrapping it would allocate an Effect per
|
|
11
|
+
* property write in a hot loop.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Run a synchronous three call, converting a throw into a
|
|
15
|
+
* {@link ThreeException}.
|
|
16
|
+
*
|
|
17
|
+
* @param operation - Name of the three operation, used in the error.
|
|
18
|
+
* @param fn - The call to make.
|
|
19
|
+
*/
|
|
20
|
+
export const wrap = (operation, fn) => Effect.try({
|
|
21
|
+
try: fn,
|
|
22
|
+
catch: (cause) => new ThreeException({ operation, cause }),
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Run an async three call, converting a rejection into a
|
|
26
|
+
* {@link ThreeException}.
|
|
27
|
+
*
|
|
28
|
+
* @param operation - Name of the three operation, used in the error.
|
|
29
|
+
* @param fn - The call to make.
|
|
30
|
+
*/
|
|
31
|
+
export const wrapPromise = (operation, fn) => Effect.tryPromise({
|
|
32
|
+
try: fn,
|
|
33
|
+
catch: (cause) => new ThreeException({ operation, cause }),
|
|
34
|
+
});
|
package/dist/Line2.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Line2NodeMaterial } from "three/webgpu";
|
|
2
|
+
/**
|
|
3
|
+
* Thick lines — strokes with a real pixel width.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* A plain three `Line` is always one pixel wide regardless of its material,
|
|
7
|
+
* because that is all the GPU's line primitive offers. `Line2` draws
|
|
8
|
+
* strokes as geometry instead, so a width in pixels means something.
|
|
9
|
+
*
|
|
10
|
+
* This module is a stable import point over three's addon subpaths, which
|
|
11
|
+
* move between versions, plus one behavioral fix — see
|
|
12
|
+
* {@link BlendedLine2NodeMaterial}. Construction and mutation are ordinary
|
|
13
|
+
* synchronous three.
|
|
14
|
+
*/
|
|
15
|
+
type SetupDiffuseColorArgs = Parameters<Line2NodeMaterial["setupDiffuseColor"]>;
|
|
16
|
+
/**
|
|
17
|
+
* A `Line2NodeMaterial` whose transparency actually blends.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* Use this instead of three's `Line2NodeMaterial` for any stroke that is
|
|
21
|
+
* not fully opaque.
|
|
22
|
+
*
|
|
23
|
+
* Upstream, a transparent fat line does not truly blend. It fakes
|
|
24
|
+
* transparency by sampling a copy of the framebuffer's opaque content —
|
|
25
|
+
* three's own comment says "transparency is not supported, yet". That copy
|
|
26
|
+
* is a module-level singleton, recreated per render and shared by every
|
|
27
|
+
* renderer on the page, which produces two problems: validation errors
|
|
28
|
+
* about destroyed textures whenever more than one renderer is alive, and
|
|
29
|
+
* wrong results against other translucent content, which the copy does not
|
|
30
|
+
* contain.
|
|
31
|
+
*
|
|
32
|
+
* This subclass skips that branch during shader setup and uses ordinary
|
|
33
|
+
* alpha blending, so a translucent stroke composites like every other
|
|
34
|
+
* material. Drop it if upstream ever supports real fat-line transparency.
|
|
35
|
+
*/
|
|
36
|
+
export declare class BlendedLine2NodeMaterial extends Line2NodeMaterial {
|
|
37
|
+
constructor(...parameters: ConstructorParameters<typeof Line2NodeMaterial>);
|
|
38
|
+
setupDiffuseColor(...args: SetupDiffuseColorArgs): void;
|
|
39
|
+
}
|
|
40
|
+
export { LineGeometry } from "three/addons/lines/LineGeometry.js";
|
|
41
|
+
export { Line2 } from "three/addons/lines/webgpu/Line2.js";
|
|
42
|
+
export { Line2NodeMaterial } from "three/webgpu";
|
package/dist/Line2.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Line2NodeMaterial, NormalBlending } from "three/webgpu";
|
|
2
|
+
/**
|
|
3
|
+
* A `Line2NodeMaterial` whose transparency actually blends.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Use this instead of three's `Line2NodeMaterial` for any stroke that is
|
|
7
|
+
* not fully opaque.
|
|
8
|
+
*
|
|
9
|
+
* Upstream, a transparent fat line does not truly blend. It fakes
|
|
10
|
+
* transparency by sampling a copy of the framebuffer's opaque content —
|
|
11
|
+
* three's own comment says "transparency is not supported, yet". That copy
|
|
12
|
+
* is a module-level singleton, recreated per render and shared by every
|
|
13
|
+
* renderer on the page, which produces two problems: validation errors
|
|
14
|
+
* about destroyed textures whenever more than one renderer is alive, and
|
|
15
|
+
* wrong results against other translucent content, which the copy does not
|
|
16
|
+
* contain.
|
|
17
|
+
*
|
|
18
|
+
* This subclass skips that branch during shader setup and uses ordinary
|
|
19
|
+
* alpha blending, so a translucent stroke composites like every other
|
|
20
|
+
* material. Drop it if upstream ever supports real fat-line transparency.
|
|
21
|
+
*/
|
|
22
|
+
export class BlendedLine2NodeMaterial extends Line2NodeMaterial {
|
|
23
|
+
constructor(...parameters) {
|
|
24
|
+
super(...parameters);
|
|
25
|
+
this.blending = NormalBlending;
|
|
26
|
+
}
|
|
27
|
+
setupDiffuseColor(...args) {
|
|
28
|
+
// momentarily report opaque so the base setup skips its
|
|
29
|
+
// viewportOpaqueMipTexture fake-transparency branch; the flag is
|
|
30
|
+
// restored so the render pipeline still alpha-blends
|
|
31
|
+
const transparent = this.transparent;
|
|
32
|
+
this.transparent = false;
|
|
33
|
+
super.setupDiffuseColor(...args);
|
|
34
|
+
this.transparent = transparent;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export { LineGeometry } from "three/addons/lines/LineGeometry.js";
|
|
38
|
+
export { Line2 } from "three/addons/lines/webgpu/Line2.js";
|
|
39
|
+
export { Line2NodeMaterial } from "three/webgpu";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type * as THREE from "three/webgpu";
|
|
2
|
+
/**
|
|
3
|
+
* Leaf value type: an `Object3D` carries no lifecycle of its own — the
|
|
4
|
+
* disposables hang off it (geometries, materials), and those get their
|
|
5
|
+
* own modules — so it stays a plain alias to three's type rather than a
|
|
6
|
+
* branded handle. See "Wrapping a library that is already actor-shaped"
|
|
7
|
+
* in AGENTS.md.
|
|
8
|
+
*
|
|
9
|
+
* Imported from `three/webgpu` (not `three`) so it is the same nominal
|
|
10
|
+
* type the rest of the package uses; the two entries declare separate
|
|
11
|
+
* class identities to TypeScript.
|
|
12
|
+
*/
|
|
13
|
+
export type Object3D = THREE.Object3D;
|
package/dist/Object3D.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { type Effect } from "effect";
|
|
2
|
+
import * as Pipeable from "effect/Pipeable";
|
|
3
|
+
import { pass as threePass, uniform } from "three/tsl";
|
|
4
|
+
import * as THREE from "three/webgpu";
|
|
5
|
+
import type * as Renderer from "./Renderer.js";
|
|
6
|
+
import type * as Scene from "./Scene.js";
|
|
7
|
+
import type { ThreeException } from "./ThreeException.js";
|
|
8
|
+
/**
|
|
9
|
+
* Post-processing: rendering a scene through a shader graph rather than
|
|
10
|
+
* straight to the output.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* Two pieces. A {@link pass} renders a scene and exposes its result as
|
|
14
|
+
* something a shader can sample; a {@link RenderPipeline} draws a node
|
|
15
|
+
* graph built from those samples. Together they cover compositing two
|
|
16
|
+
* scenes, applying a full-screen effect, or — the plainest use, and the
|
|
17
|
+
* reason the export path has one at all — getting the sRGB output
|
|
18
|
+
* transform applied, which a direct render-target readback does not do.
|
|
19
|
+
*
|
|
20
|
+
* Construction and graph assembly are synchronous and infallible;
|
|
21
|
+
* {@link render} drives the GPU, so it is the one Effect here.
|
|
22
|
+
*
|
|
23
|
+
* TSL node types are deliberately `unknown` throughout this module. three's
|
|
24
|
+
* published node types expand into unions large enough to send `tsc` into a
|
|
25
|
+
* multi-minute type check, so they are quarantined and consumers re-declare
|
|
26
|
+
* the minimal shape they actually use.
|
|
27
|
+
*/
|
|
28
|
+
export declare const TypeId: "~three/RenderPipeline";
|
|
29
|
+
/** A handle to a post-processing pipeline. */
|
|
30
|
+
export interface RenderPipeline extends Pipeable.Pipeable {
|
|
31
|
+
readonly [TypeId]: typeof TypeId;
|
|
32
|
+
readonly "~three.renderPipeline": THREE.RenderPipeline;
|
|
33
|
+
}
|
|
34
|
+
/** Whether `u` is a {@link RenderPipeline} handle. */
|
|
35
|
+
export declare const isRenderPipeline: (u: unknown) => u is RenderPipeline;
|
|
36
|
+
/**
|
|
37
|
+
* A rendered scene, exposed as nodes a shader graph can sample.
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* Accessors return TSL nodes typed as `unknown` — see the module overview
|
|
41
|
+
* on why. Consumers re-declare the minimal node shape they use.
|
|
42
|
+
*/
|
|
43
|
+
export interface Pass {
|
|
44
|
+
readonly "~three.pass": ReturnType<typeof threePass>;
|
|
45
|
+
/** The pass's rendered color, as a node to sample. */
|
|
46
|
+
readonly getTextureNode: (name?: string) => unknown;
|
|
47
|
+
/**
|
|
48
|
+
* Per-pixel view-space depth, for effects that need to know how far away
|
|
49
|
+
* something is — fog, or a depth-driven blur.
|
|
50
|
+
*/
|
|
51
|
+
readonly getViewZNode: (name?: string) => unknown;
|
|
52
|
+
/**
|
|
53
|
+
* The camera's near and far planes as auto-updating uniforms.
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* Needed to linearize depth at an arbitrary sample point.
|
|
57
|
+
* {@link Pass.getViewZNode} only reports depth at the current pixel, so
|
|
58
|
+
* any effect gathering depth from NEIGHBOURING taps has to do the
|
|
59
|
+
* conversion itself. Reaches past three's public surface to get them.
|
|
60
|
+
*/
|
|
61
|
+
readonly cameraNear: unknown;
|
|
62
|
+
readonly cameraFar: unknown;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Render a scene through a camera into something a shader graph can sample.
|
|
66
|
+
*
|
|
67
|
+
* @remarks
|
|
68
|
+
* The pass is the input end of a post chain: build one, take
|
|
69
|
+
* {@link Pass.getTextureNode} as a node, combine it however you like, and
|
|
70
|
+
* hand the result to {@link makePipeline}.
|
|
71
|
+
*/
|
|
72
|
+
export declare const pass: (scene: Scene.Scene, camera: THREE.Camera) => Pass;
|
|
73
|
+
/**
|
|
74
|
+
* Release a pass's GPU resources.
|
|
75
|
+
*
|
|
76
|
+
* @remarks
|
|
77
|
+
* Passes are not scoped, so dispose one explicitly when its owner goes
|
|
78
|
+
* away.
|
|
79
|
+
*/
|
|
80
|
+
export declare const disposePass: (self: Pass) => void;
|
|
81
|
+
/**
|
|
82
|
+
* Build a pipeline that draws a shader graph to the renderer's output.
|
|
83
|
+
*
|
|
84
|
+
* @remarks
|
|
85
|
+
* `outputNode` is the assembled TSL graph — a pass's texture straight
|
|
86
|
+
* through for a plain sRGB transform, or something composited from several
|
|
87
|
+
* sources. Its type is `unknown` on purpose; see the module overview.
|
|
88
|
+
*
|
|
89
|
+
* @param renderer - The renderer to draw with.
|
|
90
|
+
* @param outputNode - The TSL node graph producing the final color.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* A scene drawn through a pipeline, so the sRGB output transform applies.
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const scenePass = PostProcessing.pass(scene, camera);
|
|
96
|
+
* const pipeline = PostProcessing.makePipeline(
|
|
97
|
+
* renderer,
|
|
98
|
+
* scenePass.getTextureNode(),
|
|
99
|
+
* );
|
|
100
|
+
* yield* PostProcessing.render(pipeline);
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export declare const makePipeline: (renderer: Renderer.Renderer, outputNode: unknown) => RenderPipeline;
|
|
104
|
+
/**
|
|
105
|
+
* Draw the pipeline to the renderer's current output.
|
|
106
|
+
*
|
|
107
|
+
* @remarks
|
|
108
|
+
* Use this INSTEAD of `Renderer.render` when a pipeline is involved — it
|
|
109
|
+
* renders the passes the graph depends on and applies the output transform.
|
|
110
|
+
*/
|
|
111
|
+
export declare const render: (self: RenderPipeline) => Effect.Effect<void, ThreeException>;
|
|
112
|
+
export { uniform };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Predicate } from "effect";
|
|
2
|
+
import * as Pipeable from "effect/Pipeable";
|
|
3
|
+
import { pass as threePass, uniform } from "three/tsl";
|
|
4
|
+
import * as THREE from "three/webgpu";
|
|
5
|
+
import { wrap } from "./Interop.js";
|
|
6
|
+
/**
|
|
7
|
+
* Post-processing: rendering a scene through a shader graph rather than
|
|
8
|
+
* straight to the output.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Two pieces. A {@link pass} renders a scene and exposes its result as
|
|
12
|
+
* something a shader can sample; a {@link RenderPipeline} draws a node
|
|
13
|
+
* graph built from those samples. Together they cover compositing two
|
|
14
|
+
* scenes, applying a full-screen effect, or — the plainest use, and the
|
|
15
|
+
* reason the export path has one at all — getting the sRGB output
|
|
16
|
+
* transform applied, which a direct render-target readback does not do.
|
|
17
|
+
*
|
|
18
|
+
* Construction and graph assembly are synchronous and infallible;
|
|
19
|
+
* {@link render} drives the GPU, so it is the one Effect here.
|
|
20
|
+
*
|
|
21
|
+
* TSL node types are deliberately `unknown` throughout this module. three's
|
|
22
|
+
* published node types expand into unions large enough to send `tsc` into a
|
|
23
|
+
* multi-minute type check, so they are quarantined and consumers re-declare
|
|
24
|
+
* the minimal shape they actually use.
|
|
25
|
+
*/
|
|
26
|
+
export const TypeId = "~three/RenderPipeline";
|
|
27
|
+
/** Whether `u` is a {@link RenderPipeline} handle. */
|
|
28
|
+
export const isRenderPipeline = (u) => Predicate.hasProperty(u, TypeId);
|
|
29
|
+
/**
|
|
30
|
+
* Render a scene through a camera into something a shader graph can sample.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* The pass is the input end of a post chain: build one, take
|
|
34
|
+
* {@link Pass.getTextureNode} as a node, combine it however you like, and
|
|
35
|
+
* hand the result to {@link makePipeline}.
|
|
36
|
+
*/
|
|
37
|
+
export const pass = (scene, camera) => {
|
|
38
|
+
const raw = threePass(scene["~three.scene"], camera);
|
|
39
|
+
const internals = raw;
|
|
40
|
+
return {
|
|
41
|
+
"~three.pass": raw,
|
|
42
|
+
cameraNear: internals._cameraNear,
|
|
43
|
+
cameraFar: internals._cameraFar,
|
|
44
|
+
getTextureNode: (name) => name === undefined ? raw.getTextureNode() : raw.getTextureNode(name),
|
|
45
|
+
getViewZNode: (name) => name === undefined ? raw.getViewZNode() : raw.getViewZNode(name),
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Release a pass's GPU resources.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* Passes are not scoped, so dispose one explicitly when its owner goes
|
|
53
|
+
* away.
|
|
54
|
+
*/
|
|
55
|
+
export const disposePass = (self) => {
|
|
56
|
+
self["~three.pass"].dispose?.();
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Build a pipeline that draws a shader graph to the renderer's output.
|
|
60
|
+
*
|
|
61
|
+
* @remarks
|
|
62
|
+
* `outputNode` is the assembled TSL graph — a pass's texture straight
|
|
63
|
+
* through for a plain sRGB transform, or something composited from several
|
|
64
|
+
* sources. Its type is `unknown` on purpose; see the module overview.
|
|
65
|
+
*
|
|
66
|
+
* @param renderer - The renderer to draw with.
|
|
67
|
+
* @param outputNode - The TSL node graph producing the final color.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* A scene drawn through a pipeline, so the sRGB output transform applies.
|
|
71
|
+
* ```typescript
|
|
72
|
+
* const scenePass = PostProcessing.pass(scene, camera);
|
|
73
|
+
* const pipeline = PostProcessing.makePipeline(
|
|
74
|
+
* renderer,
|
|
75
|
+
* scenePass.getTextureNode(),
|
|
76
|
+
* );
|
|
77
|
+
* yield* PostProcessing.render(pipeline);
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export const makePipeline = (renderer, outputNode) => {
|
|
81
|
+
const pipeline = new THREE.RenderPipeline(renderer["~three.renderer"]);
|
|
82
|
+
// the node graph's type is quarantined; the pipeline accepts it
|
|
83
|
+
pipeline.outputNode = outputNode;
|
|
84
|
+
const self = {
|
|
85
|
+
[TypeId]: TypeId,
|
|
86
|
+
"~three.renderPipeline": pipeline,
|
|
87
|
+
// see Scene.ts on the array-like cast
|
|
88
|
+
pipe(...fns) {
|
|
89
|
+
return Pipeable.pipeArguments(self, fns);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
return self;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Draw the pipeline to the renderer's current output.
|
|
96
|
+
*
|
|
97
|
+
* @remarks
|
|
98
|
+
* Use this INSTEAD of `Renderer.render` when a pipeline is involved — it
|
|
99
|
+
* renders the passes the graph depends on and applies the output transform.
|
|
100
|
+
*/
|
|
101
|
+
export const render = (self) => wrap("RenderPipeline.render", () => self["~three.renderPipeline"].render());
|
|
102
|
+
export { uniform };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Scope } from "effect";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import * as Pipeable from "effect/Pipeable";
|
|
4
|
+
import * as THREE from "three/webgpu";
|
|
5
|
+
/**
|
|
6
|
+
* An offscreen render destination — draw into it instead of the canvas.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Rendering to a target is how a rendered result becomes something you can
|
|
10
|
+
* use: read its pixels back for export, or sample its texture in another
|
|
11
|
+
* pass.
|
|
12
|
+
*
|
|
13
|
+
* Construction cannot fail — the GPU allocation is deferred until first
|
|
14
|
+
* use — so {@link make} is an Effect purely to register teardown. That is
|
|
15
|
+
* also the only reason this is a branded handle rather than a plain alias:
|
|
16
|
+
* a render target owns GPU memory that has to be released.
|
|
17
|
+
*/
|
|
18
|
+
export declare const TypeId: "~three/RenderTarget";
|
|
19
|
+
/**
|
|
20
|
+
* A handle to an offscreen render destination.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* The underlying target stays reachable through `~three.renderTarget` for
|
|
24
|
+
* anything this wrapper does not cover.
|
|
25
|
+
*/
|
|
26
|
+
export interface RenderTarget extends Pipeable.Pipeable {
|
|
27
|
+
readonly [TypeId]: typeof TypeId;
|
|
28
|
+
readonly "~three.renderTarget": THREE.RenderTarget;
|
|
29
|
+
}
|
|
30
|
+
/** Whether `u` is a {@link RenderTarget} handle. */
|
|
31
|
+
export declare const isRenderTarget: (u: unknown) => u is RenderTarget;
|
|
32
|
+
/**
|
|
33
|
+
* A render target of `width × height` device pixels, freed when the scope
|
|
34
|
+
* closes.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* Dimensions are DEVICE pixels, so multiply by the pixel ratio yourself
|
|
38
|
+
* when supersampling.
|
|
39
|
+
*/
|
|
40
|
+
export declare const make: (width: number, height: number) => Effect.Effect<RenderTarget, never, Scope.Scope>;
|
|
41
|
+
/**
|
|
42
|
+
* A render target WITHOUT scope-registered teardown.
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* For targets whose lifetime does not match a scope — one recreated on
|
|
46
|
+
* every resize, for instance, where a longer-lived owner tracks it and
|
|
47
|
+
* calls {@link dispose}. Prefer {@link make} whenever a scope will do.
|
|
48
|
+
*/
|
|
49
|
+
export declare const makeUnsafe: (width: number, height: number) => RenderTarget;
|
|
50
|
+
/**
|
|
51
|
+
* Wrap an existing three render target, without registering teardown.
|
|
52
|
+
*
|
|
53
|
+
* @remarks
|
|
54
|
+
* Same caveat as {@link makeUnsafe}: whoever owns it must dispose it.
|
|
55
|
+
*/
|
|
56
|
+
export declare const fromRaw: (target: THREE.RenderTarget) => RenderTarget;
|
|
57
|
+
/**
|
|
58
|
+
* Release the target's GPU memory.
|
|
59
|
+
*
|
|
60
|
+
* @remarks
|
|
61
|
+
* Only for targets from {@link makeUnsafe} or {@link fromRaw} — one from
|
|
62
|
+
* {@link make} disposes itself with its scope, and disposing it here would
|
|
63
|
+
* be a double free.
|
|
64
|
+
*/
|
|
65
|
+
export declare const dispose: (self: RenderTarget) => void;
|
|
66
|
+
/**
|
|
67
|
+
* The target's color texture — what you sample to use the rendered result
|
|
68
|
+
* in another pass.
|
|
69
|
+
*/
|
|
70
|
+
export declare const texture: (self: RenderTarget) => THREE.Texture;
|
|
71
|
+
/** The target's width in device pixels. */
|
|
72
|
+
export declare const width: (self: RenderTarget) => number;
|
|
73
|
+
/** The target's height in device pixels. */
|
|
74
|
+
export declare const height: (self: RenderTarget) => number;
|
|
75
|
+
/**
|
|
76
|
+
* Resize the target in place.
|
|
77
|
+
*
|
|
78
|
+
* @remarks
|
|
79
|
+
* three reallocates the underlying GPU textures, so the handle stays valid
|
|
80
|
+
* and nothing needs re-registering with the scope. Contents are not
|
|
81
|
+
* preserved — redraw after resizing.
|
|
82
|
+
*/
|
|
83
|
+
export declare const setSize: {
|
|
84
|
+
(nextWidth: number, nextHeight: number): (self: RenderTarget) => RenderTarget;
|
|
85
|
+
(self: RenderTarget, nextWidth: number, nextHeight: number): RenderTarget;
|
|
86
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Effect, Predicate } from "effect";
|
|
2
|
+
import { dual } from "effect/Function";
|
|
3
|
+
import * as Pipeable from "effect/Pipeable";
|
|
4
|
+
import * as THREE from "three/webgpu";
|
|
5
|
+
/**
|
|
6
|
+
* An offscreen render destination — draw into it instead of the canvas.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Rendering to a target is how a rendered result becomes something you can
|
|
10
|
+
* use: read its pixels back for export, or sample its texture in another
|
|
11
|
+
* pass.
|
|
12
|
+
*
|
|
13
|
+
* Construction cannot fail — the GPU allocation is deferred until first
|
|
14
|
+
* use — so {@link make} is an Effect purely to register teardown. That is
|
|
15
|
+
* also the only reason this is a branded handle rather than a plain alias:
|
|
16
|
+
* a render target owns GPU memory that has to be released.
|
|
17
|
+
*/
|
|
18
|
+
export const TypeId = "~three/RenderTarget";
|
|
19
|
+
/** Whether `u` is a {@link RenderTarget} handle. */
|
|
20
|
+
export const isRenderTarget = (u) => Predicate.hasProperty(u, TypeId);
|
|
21
|
+
/**
|
|
22
|
+
* `dual`'s predicate receives the whole `arguments` object, not the first
|
|
23
|
+
* argument — dispatch on `args[0]`. Guard-based, never arity (AGENTS.md).
|
|
24
|
+
*/
|
|
25
|
+
const firstArgIsRenderTarget = (args) => isRenderTarget(args[0]);
|
|
26
|
+
const brand = (target) => {
|
|
27
|
+
const self = {
|
|
28
|
+
[TypeId]: TypeId,
|
|
29
|
+
"~three.renderTarget": target,
|
|
30
|
+
// see Scene.ts on the array-like cast
|
|
31
|
+
pipe(...fns) {
|
|
32
|
+
return Pipeable.pipeArguments(self, fns);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
return self;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* A render target of `width × height` device pixels, freed when the scope
|
|
39
|
+
* closes.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* Dimensions are DEVICE pixels, so multiply by the pixel ratio yourself
|
|
43
|
+
* when supersampling.
|
|
44
|
+
*/
|
|
45
|
+
export const make = Effect.fnUntraced(function* (width, height) {
|
|
46
|
+
const target = new THREE.RenderTarget(width, height);
|
|
47
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => target.dispose()));
|
|
48
|
+
return brand(target);
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* A render target WITHOUT scope-registered teardown.
|
|
52
|
+
*
|
|
53
|
+
* @remarks
|
|
54
|
+
* For targets whose lifetime does not match a scope — one recreated on
|
|
55
|
+
* every resize, for instance, where a longer-lived owner tracks it and
|
|
56
|
+
* calls {@link dispose}. Prefer {@link make} whenever a scope will do.
|
|
57
|
+
*/
|
|
58
|
+
export const makeUnsafe = (width, height) => brand(new THREE.RenderTarget(width, height));
|
|
59
|
+
/**
|
|
60
|
+
* Wrap an existing three render target, without registering teardown.
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* Same caveat as {@link makeUnsafe}: whoever owns it must dispose it.
|
|
64
|
+
*/
|
|
65
|
+
export const fromRaw = (target) => brand(target);
|
|
66
|
+
/**
|
|
67
|
+
* Release the target's GPU memory.
|
|
68
|
+
*
|
|
69
|
+
* @remarks
|
|
70
|
+
* Only for targets from {@link makeUnsafe} or {@link fromRaw} — one from
|
|
71
|
+
* {@link make} disposes itself with its scope, and disposing it here would
|
|
72
|
+
* be a double free.
|
|
73
|
+
*/
|
|
74
|
+
export const dispose = (self) => {
|
|
75
|
+
self["~three.renderTarget"].dispose();
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* The target's color texture — what you sample to use the rendered result
|
|
79
|
+
* in another pass.
|
|
80
|
+
*/
|
|
81
|
+
export const texture = (self) => self["~three.renderTarget"].texture;
|
|
82
|
+
/** The target's width in device pixels. */
|
|
83
|
+
export const width = (self) => self["~three.renderTarget"].width;
|
|
84
|
+
/** The target's height in device pixels. */
|
|
85
|
+
export const height = (self) => self["~three.renderTarget"].height;
|
|
86
|
+
/**
|
|
87
|
+
* Resize the target in place.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* three reallocates the underlying GPU textures, so the handle stays valid
|
|
91
|
+
* and nothing needs re-registering with the scope. Contents are not
|
|
92
|
+
* preserved — redraw after resizing.
|
|
93
|
+
*/
|
|
94
|
+
export const setSize = dual(firstArgIsRenderTarget, (self, nextWidth, nextHeight) => {
|
|
95
|
+
self["~three.renderTarget"].setSize(nextWidth, nextHeight);
|
|
96
|
+
return self;
|
|
97
|
+
});
|