/
blitpass.ts
372 lines (303 loc) · 13.3 KB
/
blitpass.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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
/* spellchecker: disable */
import { vec4 } from 'gl-matrix';
import { assert, logIf, LogLevel } from './auxiliaries';
import { v4 } from './gl-matrix-extensions';
import { Context } from './context';
import { Framebuffer } from './framebuffer';
import { Initializable } from './initializable';
import { NdcFillingTriangle } from './ndcfillingtriangle';
import { Program } from './program';
import { Shader } from './shader';
import { Texture2D } from './texture2d';
/* spellchecker: enable */
/**
* This rendering pass blits the color attachment of a given rgba-framebuffer into the target buffer provided on frame.
* For rendering, a direct blit is used. However, if this is not supported, a textured, screen-aligned triangle is used
* for blitting as fallback.
*
* The blit pass can be used as follows:
* ```
* this._blitPass = new BlitPass(this._context);
* this._blitPass.initialize();
*
* this._blitPass.readBuffer = gl2facade.COLOR_ATTACHMENT0;
* // this._blitPass.srcBounds = vec4.fromValues(0, 0, this._sourceSize[0], this._sourceSize[1]);
* this._blitPass.filter = gl.LINEAR;
* this._blitPass.target = this._defaultFBO;
* this._blitPass.drawBuffer = gl.BACK;
*
* this.blitPass.framebuffer = this.intermediateFBO;
* // this.blitPass.dstBounds = vec4.fromValues(dstX0, dstY0, dstX1, dstY1);
* this.blitPass.frame();
* ```
*/
export class BlitPass extends Initializable {
/**
* Read-only access to the objects context, used to get context information and WebGL API access.
*/
protected _context: Context;
/** @see {@link target} */
protected _target: Framebuffer;
/** @see {@link framebuffer} */
protected _framebuffer: Framebuffer;
/** @see {@link readBuffer} */
protected _readBuffer: GLenum;
/** @see {@link drawBuffer} */
protected _drawBuffer: GLenum;
/** @see {@link filter} */
protected _filter: GLenum;
/** @see {@link srcBounds} */
protected _srcBounds: vec4 | undefined;
/** @see {@link dstBounds} */
protected _dstBounds: vec4 | undefined;
/* Indirect blit and fallback implementation. */
/** @see {@link forceProgramBlit} */
protected _enforceProgramBlit = false;
/**
* Geometry used to draw on. This is not provided by default to allow for geometry sharing. If no triangle is given,
* the ndc triangle will be created and managed internally.
*/
protected _ndcTriangle: NdcFillingTriangle;
/**
* Tracks ownership of the ndc-filling triangle.
*/
protected _ndcTriangleShared = false;
protected _program: Program;
/**
* Uniform for passing the filter to blit: true for nearest, false otherwise (linear).
*/
protected _uNearest: WebGLUniformLocation;
/**
* Uniform for passing the source bounds to blit.
*/
protected _uSrcBounds: WebGLUniformLocation;
/**
* Uniform for passing the destination bounds to blit.
*/
protected _uDstBounds: WebGLUniformLocation;
constructor(context: Context) {
super();
this._context = context;
}
/**
* Uses direct blit via glBlitFramebuffer for blitting a single read buffer into the given target's draw buffer.
*/
private functionBlit(): void {
const gl = this._context.gl;
this._target.bind(gl.DRAW_FRAMEBUFFER);
this._framebuffer.bind(gl.READ_FRAMEBUFFER);
gl.readBuffer(this._readBuffer);
if (
this._drawBuffer >= gl.COLOR_ATTACHMENT1 &&
this._drawBuffer <= gl.COLOR_ATTACHMENT15
) {
const offset = this._drawBuffer - gl.COLOR_ATTACHMENT0;
const drawBuffers = new Array(offset + 1).fill(gl.NONE);
drawBuffers[offset] = this._drawBuffer;
gl.drawBuffers(drawBuffers);
} else {
gl.drawBuffers([this._drawBuffer]);
}
/**
* The glClear is somehow required to make the blit work. Reducing the clear area to zero is intended to reduce
* the actual load of clear.
*/
gl.enable(gl.SCISSOR_TEST);
gl.scissor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.disable(gl.SCISSOR_TEST);
const srcBounds = this._srcBounds ? this._srcBounds : [0, 0, this._framebuffer.width, this._framebuffer.height];
const dstBounds = this._dstBounds ? this._dstBounds : [0, 0, this._target.width, this._target.height];
gl.blitFramebuffer(
srcBounds[0], srcBounds[1], srcBounds[2], srcBounds[3],
dstBounds[0], dstBounds[1], dstBounds[2], dstBounds[3],
gl.COLOR_BUFFER_BIT, this._filter);
this._framebuffer.unbind(gl.READ_FRAMEBUFFER);
this._target.unbind(gl.DRAW_FRAMEBUFFER);
}
/**
* Uses indirect blit by drawing a textured, screen-aligned triangle into the given target framebuffer.
* @param program - The program the is used for minimal blit.
*/
private programBlit(): void {
if (this._program === undefined) {
this.createProgram();
}
assert(this._ndcTriangle && this._ndcTriangle.initialized, `expected an initialized ndc triangle`);
const gl = this._context.gl;
const srcBounds: vec4 = this._srcBounds ? this._srcBounds :
vec4.fromValues(0, 0, this._framebuffer.width, this._framebuffer.height);
const dstBounds: vec4 = this._dstBounds ? this._dstBounds :
vec4.fromValues(0, 0, this._target.width, this._target.height);
const srcBoundsNormalized: vec4 = vec4.div(v4(), srcBounds,
[this._framebuffer.width, this._framebuffer.height, this._framebuffer.width, this._framebuffer.height]);
const dstBoundsNormalized: vec4 = vec4.div(v4(), dstBounds,
[this._target.width, this._target.height, this._target.width, this._target.height]);
gl.viewport(dstBounds[0], dstBounds[1], dstBounds[2] - dstBounds[0], dstBounds[3] - dstBounds[1]);
gl.disable(gl.DEPTH_TEST);
gl.depthMask(false);
this._program.bind();
gl.uniform4fv(this._uSrcBounds, srcBoundsNormalized);
gl.uniform4fv(this._uDstBounds, dstBoundsNormalized);
gl.uniform1i(this._uNearest, this.filter === gl.nearest);
const texture = this._framebuffer.texture(this._readBuffer) as Texture2D;
texture.bind(gl.TEXTURE0);
const target = this._context.isWebGL2 ? gl.DRAW_FRAMEBUFFER : gl.FRAMEBUFFER;
this._target.bind(target);
this._ndcTriangle.bind();
this._ndcTriangle.draw();
this._ndcTriangle.unbind();
this._target.unbind(target);
texture.unbind();
gl.enable(gl.DEPTH_TEST);
gl.depthMask(true);
/* Every pass is expected to bind its own program when drawing, thus, unbinding is not necessary. */
// this.program.unbind();
}
/**
* Used to create (on-demand) the blit program for program based blitting. This function can be specialized, e.g.,
* for creating custom blit passes such as the `DebugPass` {@link DebugPass}. This method assumes the program to be
* undefined.
*/
protected createProgram(): boolean {
assert(this._program === undefined, `expected blit program to be undefined before its creation`);
const gl = this._context.gl;
const vert = new Shader(this._context, gl.VERTEX_SHADER, 'blit.vert (blit)');
// eslint-disable-next-line @typescript-eslint/no-var-requires
vert.initialize(require('./shaders/blit.vert'));
const frag = new Shader(this._context, gl.FRAGMENT_SHADER, 'blit.frag (blit)');
// eslint-disable-next-line @typescript-eslint/no-var-requires
frag.initialize(require('./shaders/blit.frag'));
this._program = new Program(this._context, 'BlitProgram');
this._program.initialize([vert, frag], false);
if (!this._ndcTriangle.initialized) {
this._ndcTriangle.initialize();
}
this._program.attribute('a_vertex', this._ndcTriangle.vertexLocation);
this._program.link();
this._uSrcBounds = this._program.uniform('u_srcBounds');
this._uDstBounds = this._program.uniform('u_dstBounds');
this._uNearest = this._program.uniform('u_nearest');
this._program.bind();
gl.uniform1i(this._program.uniform('u_source'), 0);
this._program.unbind();
return this._program.valid;
}
/**
* Specializes this pass's initialization. This pass either requires blitFramebuffer support or creates screen-
* aligned triangle geometry and a single program. All attribute and dynamic uniform locations are cached.
* @param ndcTriangle - If specified, assumed to be used as shared geometry. If none is specified, a ndc-filling
* triangle will be created internally.
*/
@Initializable.initialize()
initialize(ndcTriangle?: NdcFillingTriangle): boolean {
const gl = this._context.gl;
if (ndcTriangle === undefined) {
this._ndcTriangle = new NdcFillingTriangle(this._context, 'NdcFillingTriangle-Blit');
} else {
this._ndcTriangle = ndcTriangle;
this._ndcTriangleShared = true;
}
this._filter = gl.NEAREST;
return true;
}
/**
* Specializes this pass's uninitialization. Program and geometry resources are released (if allocated). Cached
* uniform and attribute locations are invalidated.
*/
@Initializable.uninitialize()
uninitialize(): void {
if (!this._ndcTriangleShared && this._ndcTriangle.initialized) {
this._ndcTriangle.uninitialize();
}
this._program.uninitialize();
}
/**
* A blit frame either blits or draws the color attachment of the given framebuffer to the target framebuffer. For
* program based/indirect blit a viewport filling area, i.e., a screen-aligned triangle is used.
*/
@Initializable.assert_initialized()
frame(): void {
logIf(!this._target || !this._target.valid, LogLevel.Warning, `valid target expected, given ${this._target}`);
logIf(!this._framebuffer || !this._framebuffer.valid, LogLevel.Warning,
`valid framebuffer for blitting from expected, given ${this._framebuffer}`);
const gl = this._context.gl;
switch (this._readBuffer) {
/* falls through */
case gl.DEPTH_ATTACHMENT:
case gl.STENCIL_ATTACHMENT:
case gl.DEPTH_STENCIL_ATTACHMENT:
return this.programBlit();
default:
break;
}
/* BlitFramebuffer is not an extension and, thus, it does not need to be enabled. */
if (this._context.supportsBlitFramebuffer && this._enforceProgramBlit === false) {
return this.functionBlit();
}
this.programBlit();
}
/**
* Sets the framebuffer that is to be blitted.
* @param framebuffer - Framebuffer that is to be blitted.
*/
set framebuffer(framebuffer: Framebuffer) {
this.assertInitialized();
this._framebuffer = framebuffer;
}
/**
* The read buffer to blit from (currently, this is expected to refer to a texture attachment of this._framebuffer).
* @param readBuffer - GLenum that is to be queried from the FBO.
*/
set readBuffer(readBuffer: GLenum) {
this.assertInitialized();
this._readBuffer = readBuffer;
}
/**
* The draw buffer to blit to (currently, this is expected to refer to a texture attachment of this._target or
* gl.BACK). Please note that this will be ignored if WebGL 1 is used without a WEBGL_draw_buffer extension.
* Furthermore, if the drawBuffer is gl.BACK, a DefaultBuffer is expected as target.
* @param drawBuffer - GLenum that specifies the draw buffer.
*/
set drawBuffer(drawBuffer: GLenum) {
this.assertInitialized();
this._drawBuffer = drawBuffer;
}
/**
* Framebuffer to blit the given framebuffer (@see framebuffer} into.
* @param target - Framebuffer to blit into.
*/
set target(target: Framebuffer) {
this.assertInitialized();
this._target = target;
}
/**
* Specifies the interpolation to be applied if the image is stretched. Must be GL_NEAREST or GL_LINEAR.
*/
set filter(filter: GLenum) {
this._filter = filter;
}
/**
* Specify the bounds of the source rectangle within the read buffer of the read framebuffer.
* @param bounds - [srcX0, srcY0, srcX1, srcY1] as used in glBlitFramebuffer. If bounds is
* undefined, the full size of the source buffer (framebuffer) will be used.
*/
set srcBounds(bounds: vec4 | undefined) {
this._srcBounds = bounds ? vec4.clone(bounds) : undefined;
}
/**
* Specify the bounds of the destination rectangle within the write buffer of the write framebuffer.
* @param bounds - [srcX0, srcY0, srcX1, srcY1] as used in glBlitFramebuffer. If bounds is
* undefined, the full size of the destination (target) buffer will be used.
*/
set dstBounds(bounds: vec4 | undefined) {
this._dstBounds = bounds ? vec4.clone(bounds) : undefined;
}
/**
* Specify whether or not experimental WebGL blit can be used if available.
* @param enforce - If true, program based blit instead of WebGL experimental blit function will be used.
*/
set enforceProgramBlit(enforce: boolean) {
this._enforceProgramBlit = enforce;
}
}