UNPKG

16.3 kBJavaScriptView Raw
1"use strict";
2var constants = require("@pixi/constants"), extensions = require("@pixi/extensions"), math = require("@pixi/math"), settings = require("@pixi/settings"), Framebuffer = require("./Framebuffer.js"), GLFramebuffer = require("./GLFramebuffer.js");
3const tempRectangle = new math.Rectangle();
4class FramebufferSystem {
5 /**
6 * @param renderer - The renderer this System works for.
7 */
8 constructor(renderer) {
9 this.renderer = renderer, this.managedFramebuffers = [], this.unknownFramebuffer = new Framebuffer.Framebuffer(10, 10), this.msaaSamples = null;
10 }
11 /** Sets up the renderer context and necessary buffers. */
12 contextChange() {
13 this.disposeAll(!0);
14 const gl = this.gl = this.renderer.gl;
15 if (this.CONTEXT_UID = this.renderer.CONTEXT_UID, this.current = this.unknownFramebuffer, this.viewport = new math.Rectangle(), this.hasMRT = !0, this.writeDepthTexture = !0, this.renderer.context.webGLVersion === 1) {
16 let nativeDrawBuffersExtension = this.renderer.context.extensions.drawBuffers, nativeDepthTextureExtension = this.renderer.context.extensions.depthTexture;
17 settings.settings.PREFER_ENV === constants.ENV.WEBGL_LEGACY && (nativeDrawBuffersExtension = null, nativeDepthTextureExtension = null), nativeDrawBuffersExtension ? gl.drawBuffers = (activeTextures) => nativeDrawBuffersExtension.drawBuffersWEBGL(activeTextures) : (this.hasMRT = !1, gl.drawBuffers = () => {
18 }), nativeDepthTextureExtension || (this.writeDepthTexture = !1);
19 } else
20 this.msaaSamples = gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGBA8, gl.SAMPLES);
21 }
22 /**
23 * Bind a framebuffer.
24 * @param framebuffer
25 * @param frame - frame, default is framebuffer size
26 * @param mipLevel - optional mip level to set on the framebuffer - defaults to 0
27 */
28 bind(framebuffer, frame, mipLevel = 0) {
29 const { gl } = this;
30 if (framebuffer) {
31 const fbo = framebuffer.glFramebuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer);
32 this.current !== framebuffer && (this.current = framebuffer, gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer)), fbo.mipLevel !== mipLevel && (framebuffer.dirtyId++, framebuffer.dirtyFormat++, fbo.mipLevel = mipLevel), fbo.dirtyId !== framebuffer.dirtyId && (fbo.dirtyId = framebuffer.dirtyId, fbo.dirtyFormat !== framebuffer.dirtyFormat ? (fbo.dirtyFormat = framebuffer.dirtyFormat, fbo.dirtySize = framebuffer.dirtySize, this.updateFramebuffer(framebuffer, mipLevel)) : fbo.dirtySize !== framebuffer.dirtySize && (fbo.dirtySize = framebuffer.dirtySize, this.resizeFramebuffer(framebuffer)));
33 for (let i = 0; i < framebuffer.colorTextures.length; i++) {
34 const tex = framebuffer.colorTextures[i];
35 this.renderer.texture.unbind(tex.parentTextureArray || tex);
36 }
37 if (framebuffer.depthTexture && this.renderer.texture.unbind(framebuffer.depthTexture), frame) {
38 const mipWidth = frame.width >> mipLevel, mipHeight = frame.height >> mipLevel, scale = mipWidth / frame.width;
39 this.setViewport(
40 frame.x * scale,
41 frame.y * scale,
42 mipWidth,
43 mipHeight
44 );
45 } else {
46 const mipWidth = framebuffer.width >> mipLevel, mipHeight = framebuffer.height >> mipLevel;
47 this.setViewport(0, 0, mipWidth, mipHeight);
48 }
49 } else
50 this.current && (this.current = null, gl.bindFramebuffer(gl.FRAMEBUFFER, null)), frame ? this.setViewport(frame.x, frame.y, frame.width, frame.height) : this.setViewport(0, 0, this.renderer.width, this.renderer.height);
51 }
52 /**
53 * Set the WebGLRenderingContext's viewport.
54 * @param x - X position of viewport
55 * @param y - Y position of viewport
56 * @param width - Width of viewport
57 * @param height - Height of viewport
58 */
59 setViewport(x, y, width, height) {
60 const v = this.viewport;
61 x = Math.round(x), y = Math.round(y), width = Math.round(width), height = Math.round(height), (v.width !== width || v.height !== height || v.x !== x || v.y !== y) && (v.x = x, v.y = y, v.width = width, v.height = height, this.gl.viewport(x, y, width, height));
62 }
63 /**
64 * Get the size of the current width and height. Returns object with `width` and `height` values.
65 * @readonly
66 */
67 get size() {
68 return this.current ? { x: 0, y: 0, width: this.current.width, height: this.current.height } : { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height };
69 }
70 /**
71 * Clear the color of the context
72 * @param r - Red value from 0 to 1
73 * @param g - Green value from 0 to 1
74 * @param b - Blue value from 0 to 1
75 * @param a - Alpha value from 0 to 1
76 * @param {PIXI.BUFFER_BITS} [mask=BUFFER_BITS.COLOR | BUFFER_BITS.DEPTH] - Bitwise OR of masks
77 * that indicate the buffers to be cleared, by default COLOR and DEPTH buffers.
78 */
79 clear(r, g, b, a, mask = constants.BUFFER_BITS.COLOR | constants.BUFFER_BITS.DEPTH) {
80 const { gl } = this;
81 gl.clearColor(r, g, b, a), gl.clear(mask);
82 }
83 /**
84 * Initialize framebuffer for this context
85 * @protected
86 * @param framebuffer
87 * @returns - created GLFramebuffer
88 */
89 initFramebuffer(framebuffer) {
90 const { gl } = this, fbo = new GLFramebuffer.GLFramebuffer(gl.createFramebuffer());
91 return fbo.multisample = this.detectSamples(framebuffer.multisample), framebuffer.glFramebuffers[this.CONTEXT_UID] = fbo, this.managedFramebuffers.push(framebuffer), framebuffer.disposeRunner.add(this), fbo;
92 }
93 /**
94 * Resize the framebuffer
95 * @param framebuffer
96 * @protected
97 */
98 resizeFramebuffer(framebuffer) {
99 const { gl } = this, fbo = framebuffer.glFramebuffers[this.CONTEXT_UID];
100 if (fbo.stencil) {
101 gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil);
102 let stencilFormat;
103 this.renderer.context.webGLVersion === 1 ? stencilFormat = gl.DEPTH_STENCIL : framebuffer.depth && framebuffer.stencil ? stencilFormat = gl.DEPTH24_STENCIL8 : framebuffer.depth ? stencilFormat = gl.DEPTH_COMPONENT24 : stencilFormat = gl.STENCIL_INDEX8, fbo.msaaBuffer ? gl.renderbufferStorageMultisample(
104 gl.RENDERBUFFER,
105 fbo.multisample,
106 stencilFormat,
107 framebuffer.width,
108 framebuffer.height
109 ) : gl.renderbufferStorage(gl.RENDERBUFFER, stencilFormat, framebuffer.width, framebuffer.height);
110 }
111 const colorTextures = framebuffer.colorTextures;
112 let count = colorTextures.length;
113 gl.drawBuffers || (count = Math.min(count, 1));
114 for (let i = 0; i < count; i++) {
115 const texture = colorTextures[i], parentTexture = texture.parentTextureArray || texture;
116 this.renderer.texture.bind(parentTexture, 0), i === 0 && fbo.msaaBuffer && (gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.msaaBuffer), gl.renderbufferStorageMultisample(
117 gl.RENDERBUFFER,
118 fbo.multisample,
119 parentTexture._glTextures[this.CONTEXT_UID].internalFormat,
120 framebuffer.width,
121 framebuffer.height
122 ));
123 }
124 framebuffer.depthTexture && this.writeDepthTexture && this.renderer.texture.bind(framebuffer.depthTexture, 0);
125 }
126 /**
127 * Update the framebuffer
128 * @param framebuffer
129 * @param mipLevel
130 * @protected
131 */
132 updateFramebuffer(framebuffer, mipLevel) {
133 const { gl } = this, fbo = framebuffer.glFramebuffers[this.CONTEXT_UID], colorTextures = framebuffer.colorTextures;
134 let count = colorTextures.length;
135 gl.drawBuffers || (count = Math.min(count, 1)), fbo.multisample > 1 && this.canMultisampleFramebuffer(framebuffer) ? fbo.msaaBuffer = fbo.msaaBuffer || gl.createRenderbuffer() : fbo.msaaBuffer && (gl.deleteRenderbuffer(fbo.msaaBuffer), fbo.msaaBuffer = null, fbo.blitFramebuffer && (fbo.blitFramebuffer.dispose(), fbo.blitFramebuffer = null));
136 const activeTextures = [];
137 for (let i = 0; i < count; i++) {
138 const texture = colorTextures[i], parentTexture = texture.parentTextureArray || texture;
139 this.renderer.texture.bind(parentTexture, 0), i === 0 && fbo.msaaBuffer ? (gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.msaaBuffer), gl.renderbufferStorageMultisample(
140 gl.RENDERBUFFER,
141 fbo.multisample,
142 parentTexture._glTextures[this.CONTEXT_UID].internalFormat,
143 framebuffer.width,
144 framebuffer.height
145 ), gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, fbo.msaaBuffer)) : (gl.framebufferTexture2D(
146 gl.FRAMEBUFFER,
147 gl.COLOR_ATTACHMENT0 + i,
148 texture.target,
149 parentTexture._glTextures[this.CONTEXT_UID].texture,
150 mipLevel
151 ), activeTextures.push(gl.COLOR_ATTACHMENT0 + i));
152 }
153 if (activeTextures.length > 1 && gl.drawBuffers(activeTextures), framebuffer.depthTexture && this.writeDepthTexture) {
154 const depthTexture = framebuffer.depthTexture;
155 this.renderer.texture.bind(depthTexture, 0), gl.framebufferTexture2D(
156 gl.FRAMEBUFFER,
157 gl.DEPTH_ATTACHMENT,
158 gl.TEXTURE_2D,
159 depthTexture._glTextures[this.CONTEXT_UID].texture,
160 mipLevel
161 );
162 }
163 if ((framebuffer.stencil || framebuffer.depth) && !(framebuffer.depthTexture && this.writeDepthTexture)) {
164 fbo.stencil = fbo.stencil || gl.createRenderbuffer();
165 let stencilAttachment, stencilFormat;
166 this.renderer.context.webGLVersion === 1 ? (stencilAttachment = gl.DEPTH_STENCIL_ATTACHMENT, stencilFormat = gl.DEPTH_STENCIL) : framebuffer.depth && framebuffer.stencil ? (stencilAttachment = gl.DEPTH_STENCIL_ATTACHMENT, stencilFormat = gl.DEPTH24_STENCIL8) : framebuffer.depth ? (stencilAttachment = gl.DEPTH_ATTACHMENT, stencilFormat = gl.DEPTH_COMPONENT24) : (stencilAttachment = gl.STENCIL_ATTACHMENT, stencilFormat = gl.STENCIL_INDEX8), gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil), fbo.msaaBuffer ? gl.renderbufferStorageMultisample(
167 gl.RENDERBUFFER,
168 fbo.multisample,
169 stencilFormat,
170 framebuffer.width,
171 framebuffer.height
172 ) : gl.renderbufferStorage(gl.RENDERBUFFER, stencilFormat, framebuffer.width, framebuffer.height), gl.framebufferRenderbuffer(gl.FRAMEBUFFER, stencilAttachment, gl.RENDERBUFFER, fbo.stencil);
173 } else
174 fbo.stencil && (gl.deleteRenderbuffer(fbo.stencil), fbo.stencil = null);
175 }
176 /**
177 * Returns true if the frame buffer can be multisampled.
178 * @param framebuffer
179 */
180 canMultisampleFramebuffer(framebuffer) {
181 return this.renderer.context.webGLVersion !== 1 && framebuffer.colorTextures.length <= 1 && !framebuffer.depthTexture;
182 }
183 /**
184 * Detects number of samples that is not more than a param but as close to it as possible
185 * @param samples - number of samples
186 * @returns - recommended number of samples
187 */
188 detectSamples(samples) {
189 const { msaaSamples } = this;
190 let res = constants.MSAA_QUALITY.NONE;
191 if (samples <= 1 || msaaSamples === null)
192 return res;
193 for (let i = 0; i < msaaSamples.length; i++)
194 if (msaaSamples[i] <= samples) {
195 res = msaaSamples[i];
196 break;
197 }
198 return res === 1 && (res = constants.MSAA_QUALITY.NONE), res;
199 }
200 /**
201 * Only works with WebGL2
202 *
203 * blits framebuffer to another of the same or bigger size
204 * after that target framebuffer is bound
205 *
206 * Fails with WebGL warning if blits multisample framebuffer to different size
207 * @param framebuffer - by default it blits "into itself", from renderBuffer to texture.
208 * @param sourcePixels - source rectangle in pixels
209 * @param destPixels - dest rectangle in pixels, assumed to be the same as sourcePixels
210 */
211 blit(framebuffer, sourcePixels, destPixels) {
212 const { current, renderer, gl, CONTEXT_UID } = this;
213 if (renderer.context.webGLVersion !== 2 || !current)
214 return;
215 const fbo = current.glFramebuffers[CONTEXT_UID];
216 if (!fbo)
217 return;
218 if (!framebuffer) {
219 if (!fbo.msaaBuffer)
220 return;
221 const colorTexture = current.colorTextures[0];
222 if (!colorTexture)
223 return;
224 fbo.blitFramebuffer || (fbo.blitFramebuffer = new Framebuffer.Framebuffer(current.width, current.height), fbo.blitFramebuffer.addColorTexture(0, colorTexture)), framebuffer = fbo.blitFramebuffer, framebuffer.colorTextures[0] !== colorTexture && (framebuffer.colorTextures[0] = colorTexture, framebuffer.dirtyId++, framebuffer.dirtyFormat++), (framebuffer.width !== current.width || framebuffer.height !== current.height) && (framebuffer.width = current.width, framebuffer.height = current.height, framebuffer.dirtyId++, framebuffer.dirtySize++);
225 }
226 sourcePixels || (sourcePixels = tempRectangle, sourcePixels.width = current.width, sourcePixels.height = current.height), destPixels || (destPixels = sourcePixels);
227 const sameSize = sourcePixels.width === destPixels.width && sourcePixels.height === destPixels.height;
228 this.bind(framebuffer), gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo.framebuffer), gl.blitFramebuffer(
229 sourcePixels.left,
230 sourcePixels.top,
231 sourcePixels.right,
232 sourcePixels.bottom,
233 destPixels.left,
234 destPixels.top,
235 destPixels.right,
236 destPixels.bottom,
237 gl.COLOR_BUFFER_BIT,
238 sameSize ? gl.NEAREST : gl.LINEAR
239 ), gl.bindFramebuffer(gl.READ_FRAMEBUFFER, framebuffer.glFramebuffers[this.CONTEXT_UID].framebuffer);
240 }
241 /**
242 * Disposes framebuffer.
243 * @param framebuffer - framebuffer that has to be disposed of
244 * @param contextLost - If context was lost, we suppress all delete function calls
245 */
246 disposeFramebuffer(framebuffer, contextLost) {
247 const fbo = framebuffer.glFramebuffers[this.CONTEXT_UID], gl = this.gl;
248 if (!fbo)
249 return;
250 delete framebuffer.glFramebuffers[this.CONTEXT_UID];
251 const index = this.managedFramebuffers.indexOf(framebuffer);
252 index >= 0 && this.managedFramebuffers.splice(index, 1), framebuffer.disposeRunner.remove(this), contextLost || (gl.deleteFramebuffer(fbo.framebuffer), fbo.msaaBuffer && gl.deleteRenderbuffer(fbo.msaaBuffer), fbo.stencil && gl.deleteRenderbuffer(fbo.stencil)), fbo.blitFramebuffer && this.disposeFramebuffer(fbo.blitFramebuffer, contextLost);
253 }
254 /**
255 * Disposes all framebuffers, but not textures bound to them.
256 * @param [contextLost=false] - If context was lost, we suppress all delete function calls
257 */
258 disposeAll(contextLost) {
259 const list = this.managedFramebuffers;
260 this.managedFramebuffers = [];
261 for (let i = 0; i < list.length; i++)
262 this.disposeFramebuffer(list[i], contextLost);
263 }
264 /**
265 * Forcing creation of stencil buffer for current framebuffer, if it wasn't done before.
266 * Used by MaskSystem, when its time to use stencil mask for Graphics element.
267 *
268 * Its an alternative for public lazy `framebuffer.enableStencil`, in case we need stencil without rebind.
269 * @private
270 */
271 forceStencil() {
272 const framebuffer = this.current;
273 if (!framebuffer)
274 return;
275 const fbo = framebuffer.glFramebuffers[this.CONTEXT_UID];
276 if (!fbo || fbo.stencil && framebuffer.stencil)
277 return;
278 framebuffer.stencil = !0;
279 const w = framebuffer.width, h = framebuffer.height, gl = this.gl, stencil = fbo.stencil = gl.createRenderbuffer();
280 gl.bindRenderbuffer(gl.RENDERBUFFER, stencil);
281 let stencilAttachment, stencilFormat;
282 this.renderer.context.webGLVersion === 1 ? (stencilAttachment = gl.DEPTH_STENCIL_ATTACHMENT, stencilFormat = gl.DEPTH_STENCIL) : framebuffer.depth ? (stencilAttachment = gl.DEPTH_STENCIL_ATTACHMENT, stencilFormat = gl.DEPTH24_STENCIL8) : (stencilAttachment = gl.STENCIL_ATTACHMENT, stencilFormat = gl.STENCIL_INDEX8), fbo.msaaBuffer ? gl.renderbufferStorageMultisample(gl.RENDERBUFFER, fbo.multisample, stencilFormat, w, h) : gl.renderbufferStorage(gl.RENDERBUFFER, stencilFormat, w, h), gl.framebufferRenderbuffer(gl.FRAMEBUFFER, stencilAttachment, gl.RENDERBUFFER, stencil);
283 }
284 /** Resets framebuffer stored state, binds screen framebuffer. Should be called before renderTexture reset(). */
285 reset() {
286 this.current = this.unknownFramebuffer, this.viewport = new math.Rectangle();
287 }
288 destroy() {
289 this.renderer = null;
290 }
291}
292FramebufferSystem.extension = {
293 type: extensions.ExtensionType.RendererSystem,
294 name: "framebuffer"
295};
296extensions.extensions.add(FramebufferSystem);
297exports.FramebufferSystem = FramebufferSystem;
298//# sourceMappingURL=FramebufferSystem.js.map