1 | import { ENV, BUFFER_BITS, MSAA_QUALITY } from '@pixi/constants';
|
2 | import { ExtensionType, extensions } from '@pixi/extensions';
|
3 | import { Rectangle } from '@pixi/math';
|
4 | import { settings } from '@pixi/settings';
|
5 | import { Framebuffer } from './Framebuffer.mjs';
|
6 | import { GLFramebuffer } from './GLFramebuffer.mjs';
|
7 |
|
8 | const tempRectangle = new Rectangle();
|
9 | class FramebufferSystem {
|
10 | constructor(renderer) {
|
11 | this.renderer = renderer;
|
12 | this.managedFramebuffers = [];
|
13 | this.unknownFramebuffer = new Framebuffer(10, 10);
|
14 | this.msaaSamples = null;
|
15 | }
|
16 | contextChange() {
|
17 | this.disposeAll(true);
|
18 | const gl = this.gl = this.renderer.gl;
|
19 | this.CONTEXT_UID = this.renderer.CONTEXT_UID;
|
20 | this.current = this.unknownFramebuffer;
|
21 | this.viewport = new Rectangle();
|
22 | this.hasMRT = true;
|
23 | this.writeDepthTexture = true;
|
24 | if (this.renderer.context.webGLVersion === 1) {
|
25 | let nativeDrawBuffersExtension = this.renderer.context.extensions.drawBuffers;
|
26 | let nativeDepthTextureExtension = this.renderer.context.extensions.depthTexture;
|
27 | if (settings.PREFER_ENV === ENV.WEBGL_LEGACY) {
|
28 | nativeDrawBuffersExtension = null;
|
29 | nativeDepthTextureExtension = null;
|
30 | }
|
31 | if (nativeDrawBuffersExtension) {
|
32 | gl.drawBuffers = (activeTextures) => nativeDrawBuffersExtension.drawBuffersWEBGL(activeTextures);
|
33 | } else {
|
34 | this.hasMRT = false;
|
35 | gl.drawBuffers = () => {
|
36 | };
|
37 | }
|
38 | if (!nativeDepthTextureExtension) {
|
39 | this.writeDepthTexture = false;
|
40 | }
|
41 | } else {
|
42 | this.msaaSamples = gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGBA8, gl.SAMPLES);
|
43 | }
|
44 | }
|
45 | bind(framebuffer, frame, mipLevel = 0) {
|
46 | const { gl } = this;
|
47 | if (framebuffer) {
|
48 | const fbo = framebuffer.glFramebuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer);
|
49 | if (this.current !== framebuffer) {
|
50 | this.current = framebuffer;
|
51 | gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer);
|
52 | }
|
53 | if (fbo.mipLevel !== mipLevel) {
|
54 | framebuffer.dirtyId++;
|
55 | framebuffer.dirtyFormat++;
|
56 | fbo.mipLevel = mipLevel;
|
57 | }
|
58 | if (fbo.dirtyId !== framebuffer.dirtyId) {
|
59 | fbo.dirtyId = framebuffer.dirtyId;
|
60 | if (fbo.dirtyFormat !== framebuffer.dirtyFormat) {
|
61 | fbo.dirtyFormat = framebuffer.dirtyFormat;
|
62 | fbo.dirtySize = framebuffer.dirtySize;
|
63 | this.updateFramebuffer(framebuffer, mipLevel);
|
64 | } else if (fbo.dirtySize !== framebuffer.dirtySize) {
|
65 | fbo.dirtySize = framebuffer.dirtySize;
|
66 | this.resizeFramebuffer(framebuffer);
|
67 | }
|
68 | }
|
69 | for (let i = 0; i < framebuffer.colorTextures.length; i++) {
|
70 | const tex = framebuffer.colorTextures[i];
|
71 | this.renderer.texture.unbind(tex.parentTextureArray || tex);
|
72 | }
|
73 | if (framebuffer.depthTexture) {
|
74 | this.renderer.texture.unbind(framebuffer.depthTexture);
|
75 | }
|
76 | if (frame) {
|
77 | const mipWidth = frame.width >> mipLevel;
|
78 | const mipHeight = frame.height >> mipLevel;
|
79 | const scale = mipWidth / frame.width;
|
80 | this.setViewport(frame.x * scale, frame.y * scale, mipWidth, mipHeight);
|
81 | } else {
|
82 | const mipWidth = framebuffer.width >> mipLevel;
|
83 | const mipHeight = framebuffer.height >> mipLevel;
|
84 | this.setViewport(0, 0, mipWidth, mipHeight);
|
85 | }
|
86 | } else {
|
87 | if (this.current) {
|
88 | this.current = null;
|
89 | gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
90 | }
|
91 | if (frame) {
|
92 | this.setViewport(frame.x, frame.y, frame.width, frame.height);
|
93 | } else {
|
94 | this.setViewport(0, 0, this.renderer.width, this.renderer.height);
|
95 | }
|
96 | }
|
97 | }
|
98 | setViewport(x, y, width, height) {
|
99 | const v = this.viewport;
|
100 | x = Math.round(x);
|
101 | y = Math.round(y);
|
102 | width = Math.round(width);
|
103 | height = Math.round(height);
|
104 | if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) {
|
105 | v.x = x;
|
106 | v.y = y;
|
107 | v.width = width;
|
108 | v.height = height;
|
109 | this.gl.viewport(x, y, width, height);
|
110 | }
|
111 | }
|
112 | get size() {
|
113 | if (this.current) {
|
114 | return { x: 0, y: 0, width: this.current.width, height: this.current.height };
|
115 | }
|
116 | return { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height };
|
117 | }
|
118 | clear(r, g, b, a, mask = BUFFER_BITS.COLOR | BUFFER_BITS.DEPTH) {
|
119 | const { gl } = this;
|
120 | gl.clearColor(r, g, b, a);
|
121 | gl.clear(mask);
|
122 | }
|
123 | initFramebuffer(framebuffer) {
|
124 | const { gl } = this;
|
125 | const fbo = new GLFramebuffer(gl.createFramebuffer());
|
126 | fbo.multisample = this.detectSamples(framebuffer.multisample);
|
127 | framebuffer.glFramebuffers[this.CONTEXT_UID] = fbo;
|
128 | this.managedFramebuffers.push(framebuffer);
|
129 | framebuffer.disposeRunner.add(this);
|
130 | return fbo;
|
131 | }
|
132 | resizeFramebuffer(framebuffer) {
|
133 | const { gl } = this;
|
134 | const fbo = framebuffer.glFramebuffers[this.CONTEXT_UID];
|
135 | if (fbo.stencil) {
|
136 | gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil);
|
137 | if (fbo.msaaBuffer) {
|
138 | gl.renderbufferStorageMultisample(gl.RENDERBUFFER, fbo.multisample, gl.DEPTH24_STENCIL8, framebuffer.width, framebuffer.height);
|
139 | } else {
|
140 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height);
|
141 | }
|
142 | }
|
143 | const colorTextures = framebuffer.colorTextures;
|
144 | let count = colorTextures.length;
|
145 | if (!gl.drawBuffers) {
|
146 | count = Math.min(count, 1);
|
147 | }
|
148 | for (let i = 0; i < count; i++) {
|
149 | const texture = colorTextures[i];
|
150 | const parentTexture = texture.parentTextureArray || texture;
|
151 | this.renderer.texture.bind(parentTexture, 0);
|
152 | if (i === 0 && fbo.msaaBuffer) {
|
153 | gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.msaaBuffer);
|
154 | gl.renderbufferStorageMultisample(gl.RENDERBUFFER, fbo.multisample, parentTexture._glTextures[this.CONTEXT_UID].internalFormat, framebuffer.width, framebuffer.height);
|
155 | }
|
156 | }
|
157 | if (framebuffer.depthTexture && this.writeDepthTexture) {
|
158 | this.renderer.texture.bind(framebuffer.depthTexture, 0);
|
159 | }
|
160 | }
|
161 | updateFramebuffer(framebuffer, mipLevel) {
|
162 | const { gl } = this;
|
163 | const fbo = framebuffer.glFramebuffers[this.CONTEXT_UID];
|
164 | const colorTextures = framebuffer.colorTextures;
|
165 | let count = colorTextures.length;
|
166 | if (!gl.drawBuffers) {
|
167 | count = Math.min(count, 1);
|
168 | }
|
169 | if (fbo.multisample > 1 && this.canMultisampleFramebuffer(framebuffer)) {
|
170 | fbo.msaaBuffer = fbo.msaaBuffer || gl.createRenderbuffer();
|
171 | } else if (fbo.msaaBuffer) {
|
172 | gl.deleteRenderbuffer(fbo.msaaBuffer);
|
173 | fbo.msaaBuffer = null;
|
174 | if (fbo.blitFramebuffer) {
|
175 | fbo.blitFramebuffer.dispose();
|
176 | fbo.blitFramebuffer = null;
|
177 | }
|
178 | }
|
179 | const activeTextures = [];
|
180 | for (let i = 0; i < count; i++) {
|
181 | const texture = colorTextures[i];
|
182 | const parentTexture = texture.parentTextureArray || texture;
|
183 | this.renderer.texture.bind(parentTexture, 0);
|
184 | if (i === 0 && fbo.msaaBuffer) {
|
185 | gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.msaaBuffer);
|
186 | gl.renderbufferStorageMultisample(gl.RENDERBUFFER, fbo.multisample, parentTexture._glTextures[this.CONTEXT_UID].internalFormat, framebuffer.width, framebuffer.height);
|
187 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, fbo.msaaBuffer);
|
188 | } else {
|
189 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, texture.target, parentTexture._glTextures[this.CONTEXT_UID].texture, mipLevel);
|
190 | activeTextures.push(gl.COLOR_ATTACHMENT0 + i);
|
191 | }
|
192 | }
|
193 | if (activeTextures.length > 1) {
|
194 | gl.drawBuffers(activeTextures);
|
195 | }
|
196 | if (framebuffer.depthTexture) {
|
197 | const writeDepthTexture = this.writeDepthTexture;
|
198 | if (writeDepthTexture) {
|
199 | const depthTexture = framebuffer.depthTexture;
|
200 | this.renderer.texture.bind(depthTexture, 0);
|
201 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture._glTextures[this.CONTEXT_UID].texture, mipLevel);
|
202 | }
|
203 | }
|
204 | if ((framebuffer.stencil || framebuffer.depth) && !(framebuffer.depthTexture && this.writeDepthTexture)) {
|
205 | fbo.stencil = fbo.stencil || gl.createRenderbuffer();
|
206 | gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil);
|
207 | if (fbo.msaaBuffer) {
|
208 | gl.renderbufferStorageMultisample(gl.RENDERBUFFER, fbo.multisample, gl.DEPTH24_STENCIL8, framebuffer.width, framebuffer.height);
|
209 | } else {
|
210 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height);
|
211 | }
|
212 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil);
|
213 | } else if (fbo.stencil) {
|
214 | gl.deleteRenderbuffer(fbo.stencil);
|
215 | fbo.stencil = null;
|
216 | }
|
217 | }
|
218 | canMultisampleFramebuffer(framebuffer) {
|
219 | return this.renderer.context.webGLVersion !== 1 && framebuffer.colorTextures.length <= 1 && !framebuffer.depthTexture;
|
220 | }
|
221 | detectSamples(samples) {
|
222 | const { msaaSamples } = this;
|
223 | let res = MSAA_QUALITY.NONE;
|
224 | if (samples <= 1 || msaaSamples === null) {
|
225 | return res;
|
226 | }
|
227 | for (let i = 0; i < msaaSamples.length; i++) {
|
228 | if (msaaSamples[i] <= samples) {
|
229 | res = msaaSamples[i];
|
230 | break;
|
231 | }
|
232 | }
|
233 | if (res === 1) {
|
234 | res = MSAA_QUALITY.NONE;
|
235 | }
|
236 | return res;
|
237 | }
|
238 | blit(framebuffer, sourcePixels, destPixels) {
|
239 | const { current, renderer, gl, CONTEXT_UID } = this;
|
240 | if (renderer.context.webGLVersion !== 2) {
|
241 | return;
|
242 | }
|
243 | if (!current) {
|
244 | return;
|
245 | }
|
246 | const fbo = current.glFramebuffers[CONTEXT_UID];
|
247 | if (!fbo) {
|
248 | return;
|
249 | }
|
250 | if (!framebuffer) {
|
251 | if (!fbo.msaaBuffer) {
|
252 | return;
|
253 | }
|
254 | const colorTexture = current.colorTextures[0];
|
255 | if (!colorTexture) {
|
256 | return;
|
257 | }
|
258 | if (!fbo.blitFramebuffer) {
|
259 | fbo.blitFramebuffer = new Framebuffer(current.width, current.height);
|
260 | fbo.blitFramebuffer.addColorTexture(0, colorTexture);
|
261 | }
|
262 | framebuffer = fbo.blitFramebuffer;
|
263 | if (framebuffer.colorTextures[0] !== colorTexture) {
|
264 | framebuffer.colorTextures[0] = colorTexture;
|
265 | framebuffer.dirtyId++;
|
266 | framebuffer.dirtyFormat++;
|
267 | }
|
268 | if (framebuffer.width !== current.width || framebuffer.height !== current.height) {
|
269 | framebuffer.width = current.width;
|
270 | framebuffer.height = current.height;
|
271 | framebuffer.dirtyId++;
|
272 | framebuffer.dirtySize++;
|
273 | }
|
274 | }
|
275 | if (!sourcePixels) {
|
276 | sourcePixels = tempRectangle;
|
277 | sourcePixels.width = current.width;
|
278 | sourcePixels.height = current.height;
|
279 | }
|
280 | if (!destPixels) {
|
281 | destPixels = sourcePixels;
|
282 | }
|
283 | const sameSize = sourcePixels.width === destPixels.width && sourcePixels.height === destPixels.height;
|
284 | this.bind(framebuffer);
|
285 | gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo.framebuffer);
|
286 | gl.blitFramebuffer(sourcePixels.left, sourcePixels.top, sourcePixels.right, sourcePixels.bottom, destPixels.left, destPixels.top, destPixels.right, destPixels.bottom, gl.COLOR_BUFFER_BIT, sameSize ? gl.NEAREST : gl.LINEAR);
|
287 | }
|
288 | disposeFramebuffer(framebuffer, contextLost) {
|
289 | const fbo = framebuffer.glFramebuffers[this.CONTEXT_UID];
|
290 | const gl = this.gl;
|
291 | if (!fbo) {
|
292 | return;
|
293 | }
|
294 | delete framebuffer.glFramebuffers[this.CONTEXT_UID];
|
295 | const index = this.managedFramebuffers.indexOf(framebuffer);
|
296 | if (index >= 0) {
|
297 | this.managedFramebuffers.splice(index, 1);
|
298 | }
|
299 | framebuffer.disposeRunner.remove(this);
|
300 | if (!contextLost) {
|
301 | gl.deleteFramebuffer(fbo.framebuffer);
|
302 | if (fbo.msaaBuffer) {
|
303 | gl.deleteRenderbuffer(fbo.msaaBuffer);
|
304 | }
|
305 | if (fbo.stencil) {
|
306 | gl.deleteRenderbuffer(fbo.stencil);
|
307 | }
|
308 | }
|
309 | if (fbo.blitFramebuffer) {
|
310 | this.disposeFramebuffer(fbo.blitFramebuffer, contextLost);
|
311 | }
|
312 | }
|
313 | disposeAll(contextLost) {
|
314 | const list = this.managedFramebuffers;
|
315 | this.managedFramebuffers = [];
|
316 | for (let i = 0; i < list.length; i++) {
|
317 | this.disposeFramebuffer(list[i], contextLost);
|
318 | }
|
319 | }
|
320 | forceStencil() {
|
321 | const framebuffer = this.current;
|
322 | if (!framebuffer) {
|
323 | return;
|
324 | }
|
325 | const fbo = framebuffer.glFramebuffers[this.CONTEXT_UID];
|
326 | if (!fbo || fbo.stencil) {
|
327 | return;
|
328 | }
|
329 | framebuffer.stencil = true;
|
330 | const w = framebuffer.width;
|
331 | const h = framebuffer.height;
|
332 | const gl = this.gl;
|
333 | const stencil = gl.createRenderbuffer();
|
334 | gl.bindRenderbuffer(gl.RENDERBUFFER, stencil);
|
335 | if (fbo.msaaBuffer) {
|
336 | gl.renderbufferStorageMultisample(gl.RENDERBUFFER, fbo.multisample, gl.DEPTH24_STENCIL8, w, h);
|
337 | } else {
|
338 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, w, h);
|
339 | }
|
340 | fbo.stencil = stencil;
|
341 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, stencil);
|
342 | }
|
343 | reset() {
|
344 | this.current = this.unknownFramebuffer;
|
345 | this.viewport = new Rectangle();
|
346 | }
|
347 | destroy() {
|
348 | this.renderer = null;
|
349 | }
|
350 | }
|
351 | FramebufferSystem.extension = {
|
352 | type: ExtensionType.RendererSystem,
|
353 | name: "framebuffer"
|
354 | };
|
355 | extensions.add(FramebufferSystem);
|
356 |
|
357 | export { FramebufferSystem };
|
358 |
|