-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathRenderTarget.ts
175 lines (150 loc) · 6.3 KB
/
RenderTarget.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import { isRenderer, Renderer } from '../renderers/utils'
import { RenderPass, RenderPassParams } from './RenderPass'
import { RenderTexture } from '../textures/RenderTexture'
import { generateUUID } from '../../utils/utils'
import { GPUCurtains } from '../../curtains/GPUCurtains'
/**
* Parameters used to create a {@link RenderTarget}
*/
export interface RenderTargetParams extends RenderPassParams {
/** Whether we should add this {@link RenderTarget} to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */
autoRender?: boolean
}
/**
* Used to draw to {@link RenderPass#viewTextures | RenderPass view textures} (and eventually {@link RenderPass#depthTexture | depth texture}) instead of directly to screen.
*
* The meshes assigned to a {@link RenderTarget} will be drawn before the other objects in the {@link core/scenes/Scene.Scene | Scene} rendering loop.
*
* Can also be assigned as ShaderPass {@link core/renderPasses/ShaderPass.ShaderPass#inputTarget | input} or {@link core/renderPasses/ShaderPass.ShaderPass#outputTarget | output} targets.
*
* If the {@link RenderPass} created handle color attachments, then a {@link RenderTarget#renderTexture | RenderTexture} will be created to update and/or resolve the content of the current view. This {@link RenderTarget#renderTexture | RenderTexture} could therefore usually be used to access the current content of this {@link RenderTarget}.
*
* @example
* ```javascript
* // set our main GPUCurtains instance
* const gpuCurtains = new GPUCurtains({
* container: '#canvas' // selector of our WebGPU canvas container
* })
*
* // set the GPU device
* // note this is asynchronous
* await gpuCurtains.setDevice()
*
* const outputTarget = new RenderTarget(gpuCurtains, {
* label: 'My render target',
* })
* ```
*/
export class RenderTarget {
/** {@link Renderer} used by this {@link RenderTarget} */
renderer: Renderer
/** The type of the {@link RenderTarget} */
type: string
/** The universal unique id of this {@link RenderTarget} */
readonly uuid: string
/** Options used to create this {@link RenderTarget} */
options: RenderTargetParams
/** {@link RenderPass} used by this {@link RenderTarget} */
renderPass: RenderPass
/** {@link RenderTexture} that will be resolved by the {@link renderPass} when {@link RenderPass#updateView | setting the current texture} */
renderTexture?: RenderTexture
/** Whether we should add this {@link RenderTarget} to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */
#autoRender = true
/**
* RenderTarget constructor
* @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link RenderTarget}
* @param parameters - {@link RenderTargetParams | parameters} use to create this {@link RenderTarget}
*/
constructor(renderer: Renderer | GPUCurtains, parameters = {} as RenderTargetParams) {
// we could pass our curtains object OR our curtains renderer object
renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)
isRenderer(renderer, 'RenderTarget')
this.type = 'RenderTarget'
this.renderer = renderer
this.uuid = generateUUID()
const { label, targetFormat, depthTexture, autoRender, ...renderPassParams } = parameters
this.options = {
label,
...renderPassParams,
...(depthTexture && { depthTexture }),
targetFormat: targetFormat ?? this.renderer.options.preferredFormat,
autoRender: autoRender === undefined ? true : autoRender,
} as RenderTargetParams
if (autoRender !== undefined) {
this.#autoRender = autoRender
}
this.renderPass = new RenderPass(this.renderer, {
label: this.options.label ? `${this.options.label} Render Pass` : 'Render Target Render Pass',
targetFormat: this.options.targetFormat,
depthTexture: this.options.depthTexture ?? this.renderer.renderPass.depthTexture, // reuse renderer depth texture for every pass
...renderPassParams,
})
if (renderPassParams.useColorAttachments !== false) {
// this is the texture that will be resolved when setting the current render pass texture
this.renderTexture = new RenderTexture(this.renderer, {
label: this.options.label ? `${this.options.label} Render Texture` : 'Render Target render texture',
name: 'renderTexture',
format: this.options.targetFormat,
...(this.options.qualityRatio !== undefined && { qualityRatio: this.options.qualityRatio }),
})
}
this.addToScene()
}
/**
* Add the {@link RenderTarget} to the renderer and the {@link core/scenes/Scene.Scene | Scene}
*/
addToScene() {
this.renderer.renderTargets.push(this)
if (this.#autoRender) {
this.renderer.scene.addRenderTarget(this)
}
}
/**
* Remove the {@link RenderTarget} from the renderer and the {@link core/scenes/Scene.Scene | Scene}
*/
removeFromScene() {
if (this.#autoRender) {
this.renderer.scene.removeRenderTarget(this)
}
this.renderer.renderTargets = this.renderer.renderTargets.filter((renderTarget) => renderTarget.uuid !== this.uuid)
}
/**
* Resize our {@link renderPass}
*/
resize() {
// reset the newly created depth texture
this.renderPass.options.depthTexture.texture = this.options.depthTexture
? this.options.depthTexture.texture
: this.renderer.renderPass.depthTexture.texture
this.renderPass?.resize()
}
/**
* Remove our {@link RenderTarget}. Alias of {@link RenderTarget#destroy}
*/
remove() {
this.destroy()
}
/**
* Destroy our {@link RenderTarget}
*/
destroy() {
// release mesh struct
this.renderer.meshes.forEach((mesh) => {
if (mesh.outputTarget && mesh.outputTarget.uuid === this.uuid) {
mesh.setOutputTarget(null)
}
})
// release shader passes struct
this.renderer.shaderPasses.forEach((shaderPass) => {
if (shaderPass.outputTarget && shaderPass.outputTarget.uuid === this.uuid) {
// force render target to null before removing / re-adding to scene
shaderPass.outputTarget = null
shaderPass.setOutputTarget(null)
}
})
// remove from scene and renderer array
this.removeFromScene()
this.renderPass?.destroy()
this.renderTexture?.destroy()
}
}