@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.
@@ -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>;
@@ -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
+ });
@@ -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;
@@ -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
+ });