playcanvas
Version:
PlayCanvas WebGL game engine
1,044 lines (1,042 loc) • 97.9 kB
JavaScript
import { math } from '../../../core/math/math.js';
import { Debug } from '../../../core/debug.js';
import { platform } from '../../../core/platform.js';
import { Color } from '../../../core/math/color.js';
import { DEVICETYPE_WEBGL2, UNIFORMTYPE_BOOL, UNIFORMTYPE_INT, UNIFORMTYPE_FLOAT, UNIFORMTYPE_VEC2, UNIFORMTYPE_VEC3, UNIFORMTYPE_VEC4, UNIFORMTYPE_IVEC2, UNIFORMTYPE_IVEC3, UNIFORMTYPE_IVEC4, UNIFORMTYPE_BVEC2, UNIFORMTYPE_BVEC3, UNIFORMTYPE_BVEC4, UNIFORMTYPE_MAT2, UNIFORMTYPE_MAT3, UNIFORMTYPE_MAT4, UNIFORMTYPE_TEXTURE2D, UNIFORMTYPE_TEXTURECUBE, UNIFORMTYPE_UINT, UNIFORMTYPE_UVEC2, UNIFORMTYPE_UVEC3, UNIFORMTYPE_UVEC4, UNIFORMTYPE_TEXTURE2D_SHADOW, UNIFORMTYPE_TEXTURECUBE_SHADOW, UNIFORMTYPE_TEXTURE2D_ARRAY, UNIFORMTYPE_TEXTURE3D, UNIFORMTYPE_ITEXTURE2D, UNIFORMTYPE_UTEXTURE2D, UNIFORMTYPE_ITEXTURECUBE, UNIFORMTYPE_UTEXTURECUBE, UNIFORMTYPE_ITEXTURE3D, UNIFORMTYPE_UTEXTURE3D, UNIFORMTYPE_ITEXTURE2D_ARRAY, UNIFORMTYPE_UTEXTURE2D_ARRAY, UNIFORMTYPE_FLOATARRAY, UNIFORMTYPE_VEC2ARRAY, UNIFORMTYPE_VEC3ARRAY, UNIFORMTYPE_VEC4ARRAY, UNIFORMTYPE_INTARRAY, UNIFORMTYPE_UINTARRAY, UNIFORMTYPE_BOOLARRAY, UNIFORMTYPE_IVEC2ARRAY, UNIFORMTYPE_UVEC2ARRAY, UNIFORMTYPE_BVEC2ARRAY, UNIFORMTYPE_IVEC3ARRAY, UNIFORMTYPE_UVEC3ARRAY, UNIFORMTYPE_BVEC3ARRAY, UNIFORMTYPE_IVEC4ARRAY, UNIFORMTYPE_UVEC4ARRAY, UNIFORMTYPE_BVEC4ARRAY, UNIFORMTYPE_MAT4ARRAY, PIXELFORMAT_RGBA8, PIXELFORMAT_RGB8, FUNC_ALWAYS, STENCILOP_KEEP, TEXPROPERTY_MIN_FILTER, TEXPROPERTY_MAG_FILTER, TEXPROPERTY_ADDRESS_U, TEXPROPERTY_ADDRESS_V, TEXPROPERTY_ADDRESS_W, TEXPROPERTY_COMPARE_ON_READ, TEXPROPERTY_COMPARE_FUNC, TEXPROPERTY_ANISOTROPY, semanticToLocation, CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL, getPixelFormatArrayType, CULLFACE_NONE, FILTER_NEAREST_MIPMAP_NEAREST, FILTER_NEAREST_MIPMAP_LINEAR, FILTER_NEAREST, FILTER_LINEAR_MIPMAP_NEAREST, FILTER_LINEAR_MIPMAP_LINEAR, FILTER_LINEAR } from '../constants.js';
import { GraphicsDevice } from '../graphics-device.js';
import { RenderTarget } from '../render-target.js';
import { Texture } from '../texture.js';
import { DebugGraphics } from '../debug-graphics.js';
import { WebglVertexBuffer } from './webgl-vertex-buffer.js';
import { WebglIndexBuffer } from './webgl-index-buffer.js';
import { WebglShader } from './webgl-shader.js';
import { WebglDrawCommands } from './webgl-draw-commands.js';
import { WebglTexture } from './webgl-texture.js';
import { WebglRenderTarget } from './webgl-render-target.js';
import { BlendState } from '../blend-state.js';
import { DepthState } from '../depth-state.js';
import { StencilParameters } from '../stencil-parameters.js';
import { WebglGpuProfiler } from './webgl-gpu-profiler.js';
import { TextureUtils } from '../texture-utils.js';
import { getBuiltInTexture } from '../built-in-textures.js';
/**
* @import { RenderPass } from '../render-pass.js'
* @import { Shader } from '../shader.js'
* @import { VertexBuffer } from '../vertex-buffer.js'
*/ const invalidateAttachments = [];
/**
* WebglGraphicsDevice extends the base {@link GraphicsDevice} to provide rendering capabilities
* utilizing the WebGL 2.0 specification.
*
* @category Graphics
*/ class WebglGraphicsDevice extends GraphicsDevice {
/**
* Creates a new WebglGraphicsDevice instance.
*
* @param {HTMLCanvasElement} canvas - The canvas to which the graphics device will render.
* @param {object} [options] - Options passed when creating the WebGL context.
* @param {boolean} [options.alpha] - Boolean that indicates if the canvas contains an
* alpha buffer. Defaults to true.
* @param {boolean} [options.depth] - Boolean that indicates that the drawing buffer is
* requested to have a depth buffer of at least 16 bits. Defaults to true.
* @param {boolean} [options.stencil] - Boolean that indicates that the drawing buffer is
* requested to have a stencil buffer of at least 8 bits. Defaults to true.
* @param {boolean} [options.antialias] - Boolean that indicates whether or not to perform
* anti-aliasing if possible. Defaults to true.
* @param {boolean} [options.premultipliedAlpha] - Boolean that indicates that the page
* compositor will assume the drawing buffer contains colors with pre-multiplied alpha.
* Defaults to true.
* @param {boolean} [options.preserveDrawingBuffer] - If the value is true the buffers will not
* be cleared and will preserve their values until cleared or overwritten by the author.
* Defaults to false.
* @param {'default'|'high-performance'|'low-power'} [options.powerPreference] - A hint to the
* user agent indicating what configuration of GPU is suitable for the WebGL context. Possible
* values are:
*
* - 'default': Let the user agent decide which GPU configuration is most suitable. This is the
* default value.
* - 'high-performance': Prioritizes rendering performance over power consumption.
* - 'low-power': Prioritizes power saving over rendering performance.
*
* Defaults to 'default'.
* @param {boolean} [options.failIfMajorPerformanceCaveat] - Boolean that indicates if a
* context will be created if the system performance is low or if no hardware GPU is available.
* Defaults to false.
* @param {boolean} [options.desynchronized] - Boolean that hints the user agent to reduce the
* latency by desynchronizing the canvas paint cycle from the event loop. Defaults to false.
* @param {boolean} [options.xrCompatible] - Boolean that hints to the user agent to use a
* compatible graphics adapter for an immersive XR device.
* @param {WebGL2RenderingContext} [options.gl] - The rendering context
* to use. If not specified, a new context will be created.
*/ constructor(canvas, options = {}){
super(canvas, options), /**
* WebGLFramebuffer object that represents the backbuffer of the device for a rendering frame.
* When null, this is a framebuffer created when the device was created, otherwise it is a
* framebuffer supplied by the XR session.
*
* @ignore
*/ this._defaultFramebuffer = null, /**
* True if the default framebuffer has changed since the last frame.
*
* @ignore
*/ this._defaultFramebufferChanged = false;
options = this.initOptions;
this.updateClientRect();
// initialize this before registering lost context handlers to avoid undefined access when the device is created lost.
this.initTextureUnits();
// Add handlers for when the WebGL context is lost or restored
this.contextLost = false;
this._contextLostHandler = (event)=>{
event.preventDefault();
this.loseContext();
Debug.log('pc.GraphicsDevice: WebGL context lost.');
this.fire('devicelost');
};
this._contextRestoredHandler = ()=>{
Debug.log('pc.GraphicsDevice: WebGL context restored.');
this.restoreContext();
this.fire('devicerestored');
};
// #4136 - turn off antialiasing on AppleWebKit browsers 15.4
const ua = typeof navigator !== 'undefined' && navigator.userAgent;
this.forceDisableMultisampling = ua && ua.includes('AppleWebKit') && (ua.includes('15.4') || ua.includes('15_4'));
if (this.forceDisableMultisampling) {
options.antialias = false;
Debug.log('Antialiasing has been turned off due to rendering issues on AppleWebKit 15.4');
}
// #5856 - turn off antialiasing on Firefox running on Windows / Android
if (platform.browserName === 'firefox') {
const ua = typeof navigator !== 'undefined' ? navigator.userAgent : '';
const match = ua.match(/Firefox\/(\d+(\.\d+)*)/);
const firefoxVersion = match ? match[1] : null;
if (firefoxVersion) {
const version = parseFloat(firefoxVersion);
const disableAntialias = platform.name === 'windows' && (version >= 120 || version === 115) || platform.name === 'android' && version >= 132;
if (disableAntialias) {
options.antialias = false;
Debug.log(`Antialiasing has been turned off due to rendering issues on Firefox ${platform.name} platform version ${firefoxVersion}`);
}
}
}
// we always allocate the default framebuffer without antialiasing, so remove that option
this.backBufferAntialias = options.antialias ?? false;
options.antialias = false;
// Retrieve the WebGL context
/** @type {WebGL2RenderingContext} */ const gl = options.gl ?? canvas.getContext('webgl2', options);
if (!gl) {
throw new Error('WebGL not supported');
}
this.gl = gl;
this.isWebGL2 = true;
this._deviceType = DEVICETYPE_WEBGL2;
// pixel format of the framebuffer
this.updateBackbufferFormat(null);
const isChrome = platform.browserName === 'chrome';
const isSafari = platform.browserName === 'safari';
const isMac = platform.browser && navigator.appVersion.indexOf('Mac') !== -1;
// enable temporary texture unit workaround on desktop safari
this._tempEnableSafariTextureUnitWorkaround = isSafari;
// enable temporary workaround for glBlitFramebuffer failing on Mac Chrome (#2504)
this._tempMacChromeBlitFramebufferWorkaround = isMac && isChrome && !options.alpha;
canvas.addEventListener('webglcontextlost', this._contextLostHandler, false);
canvas.addEventListener('webglcontextrestored', this._contextRestoredHandler, false);
this.initializeExtensions();
this.initializeCapabilities();
this.initializeRenderState();
this.initializeContextCaches();
this.createBackbuffer(null);
// only enable ImageBitmap on chrome
this.supportsImageBitmap = !isSafari && typeof ImageBitmap !== 'undefined';
// supported sampler types
this._samplerTypes = new Set([
gl.SAMPLER_2D,
gl.SAMPLER_CUBE,
gl.UNSIGNED_INT_SAMPLER_2D,
gl.INT_SAMPLER_2D,
gl.SAMPLER_2D_SHADOW,
gl.SAMPLER_CUBE_SHADOW,
gl.SAMPLER_3D,
gl.INT_SAMPLER_3D,
gl.UNSIGNED_INT_SAMPLER_3D,
gl.SAMPLER_2D_ARRAY,
gl.INT_SAMPLER_2D_ARRAY,
gl.UNSIGNED_INT_SAMPLER_2D_ARRAY
]);
this.glAddress = [
gl.REPEAT,
gl.CLAMP_TO_EDGE,
gl.MIRRORED_REPEAT
];
this.glBlendEquation = [
gl.FUNC_ADD,
gl.FUNC_SUBTRACT,
gl.FUNC_REVERSE_SUBTRACT,
gl.MIN,
gl.MAX
];
this.glBlendFunctionColor = [
gl.ZERO,
gl.ONE,
gl.SRC_COLOR,
gl.ONE_MINUS_SRC_COLOR,
gl.DST_COLOR,
gl.ONE_MINUS_DST_COLOR,
gl.SRC_ALPHA,
gl.SRC_ALPHA_SATURATE,
gl.ONE_MINUS_SRC_ALPHA,
gl.DST_ALPHA,
gl.ONE_MINUS_DST_ALPHA,
gl.CONSTANT_COLOR,
gl.ONE_MINUS_CONSTANT_COLOR
];
this.glBlendFunctionAlpha = [
gl.ZERO,
gl.ONE,
gl.SRC_COLOR,
gl.ONE_MINUS_SRC_COLOR,
gl.DST_COLOR,
gl.ONE_MINUS_DST_COLOR,
gl.SRC_ALPHA,
gl.SRC_ALPHA_SATURATE,
gl.ONE_MINUS_SRC_ALPHA,
gl.DST_ALPHA,
gl.ONE_MINUS_DST_ALPHA,
gl.CONSTANT_ALPHA,
gl.ONE_MINUS_CONSTANT_ALPHA
];
this.glComparison = [
gl.NEVER,
gl.LESS,
gl.EQUAL,
gl.LEQUAL,
gl.GREATER,
gl.NOTEQUAL,
gl.GEQUAL,
gl.ALWAYS
];
this.glStencilOp = [
gl.KEEP,
gl.ZERO,
gl.REPLACE,
gl.INCR,
gl.INCR_WRAP,
gl.DECR,
gl.DECR_WRAP,
gl.INVERT
];
this.glClearFlag = [
0,
gl.COLOR_BUFFER_BIT,
gl.DEPTH_BUFFER_BIT,
gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT,
gl.STENCIL_BUFFER_BIT,
gl.STENCIL_BUFFER_BIT | gl.COLOR_BUFFER_BIT,
gl.STENCIL_BUFFER_BIT | gl.DEPTH_BUFFER_BIT,
gl.STENCIL_BUFFER_BIT | gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT
];
this.glCull = [
0,
gl.BACK,
gl.FRONT,
gl.FRONT_AND_BACK
];
this.glFilter = [
gl.NEAREST,
gl.LINEAR,
gl.NEAREST_MIPMAP_NEAREST,
gl.NEAREST_MIPMAP_LINEAR,
gl.LINEAR_MIPMAP_NEAREST,
gl.LINEAR_MIPMAP_LINEAR
];
this.glPrimitive = [
gl.POINTS,
gl.LINES,
gl.LINE_LOOP,
gl.LINE_STRIP,
gl.TRIANGLES,
gl.TRIANGLE_STRIP,
gl.TRIANGLE_FAN
];
this.glType = [
gl.BYTE,
gl.UNSIGNED_BYTE,
gl.SHORT,
gl.UNSIGNED_SHORT,
gl.INT,
gl.UNSIGNED_INT,
gl.FLOAT,
gl.HALF_FLOAT
];
this.pcUniformType = {};
this.pcUniformType[gl.BOOL] = UNIFORMTYPE_BOOL;
this.pcUniformType[gl.INT] = UNIFORMTYPE_INT;
this.pcUniformType[gl.FLOAT] = UNIFORMTYPE_FLOAT;
this.pcUniformType[gl.FLOAT_VEC2] = UNIFORMTYPE_VEC2;
this.pcUniformType[gl.FLOAT_VEC3] = UNIFORMTYPE_VEC3;
this.pcUniformType[gl.FLOAT_VEC4] = UNIFORMTYPE_VEC4;
this.pcUniformType[gl.INT_VEC2] = UNIFORMTYPE_IVEC2;
this.pcUniformType[gl.INT_VEC3] = UNIFORMTYPE_IVEC3;
this.pcUniformType[gl.INT_VEC4] = UNIFORMTYPE_IVEC4;
this.pcUniformType[gl.BOOL_VEC2] = UNIFORMTYPE_BVEC2;
this.pcUniformType[gl.BOOL_VEC3] = UNIFORMTYPE_BVEC3;
this.pcUniformType[gl.BOOL_VEC4] = UNIFORMTYPE_BVEC4;
this.pcUniformType[gl.FLOAT_MAT2] = UNIFORMTYPE_MAT2;
this.pcUniformType[gl.FLOAT_MAT3] = UNIFORMTYPE_MAT3;
this.pcUniformType[gl.FLOAT_MAT4] = UNIFORMTYPE_MAT4;
this.pcUniformType[gl.SAMPLER_2D] = UNIFORMTYPE_TEXTURE2D;
this.pcUniformType[gl.SAMPLER_CUBE] = UNIFORMTYPE_TEXTURECUBE;
this.pcUniformType[gl.UNSIGNED_INT] = UNIFORMTYPE_UINT;
this.pcUniformType[gl.UNSIGNED_INT_VEC2] = UNIFORMTYPE_UVEC2;
this.pcUniformType[gl.UNSIGNED_INT_VEC3] = UNIFORMTYPE_UVEC3;
this.pcUniformType[gl.UNSIGNED_INT_VEC4] = UNIFORMTYPE_UVEC4;
this.pcUniformType[gl.SAMPLER_2D_SHADOW] = UNIFORMTYPE_TEXTURE2D_SHADOW;
this.pcUniformType[gl.SAMPLER_CUBE_SHADOW] = UNIFORMTYPE_TEXTURECUBE_SHADOW;
this.pcUniformType[gl.SAMPLER_2D_ARRAY] = UNIFORMTYPE_TEXTURE2D_ARRAY;
this.pcUniformType[gl.SAMPLER_3D] = UNIFORMTYPE_TEXTURE3D;
this.pcUniformType[gl.INT_SAMPLER_2D] = UNIFORMTYPE_ITEXTURE2D;
this.pcUniformType[gl.UNSIGNED_INT_SAMPLER_2D] = UNIFORMTYPE_UTEXTURE2D;
this.pcUniformType[gl.INT_SAMPLER_CUBE] = UNIFORMTYPE_ITEXTURECUBE;
this.pcUniformType[gl.UNSIGNED_INT_SAMPLER_2D] = UNIFORMTYPE_UTEXTURECUBE;
this.pcUniformType[gl.INT_SAMPLER_3D] = UNIFORMTYPE_ITEXTURE3D;
this.pcUniformType[gl.UNSIGNED_INT_SAMPLER_3D] = UNIFORMTYPE_UTEXTURE3D;
this.pcUniformType[gl.INT_SAMPLER_2D_ARRAY] = UNIFORMTYPE_ITEXTURE2D_ARRAY;
this.pcUniformType[gl.UNSIGNED_INT_SAMPLER_2D_ARRAY] = UNIFORMTYPE_UTEXTURE2D_ARRAY;
this.targetToSlot = {};
this.targetToSlot[gl.TEXTURE_2D] = 0;
this.targetToSlot[gl.TEXTURE_CUBE_MAP] = 1;
this.targetToSlot[gl.TEXTURE_3D] = 2;
// Define the uniform commit functions
let scopeX, scopeY, scopeZ, scopeW;
let uniformValue;
this.commitFunction = [];
this.commitFunction[UNIFORMTYPE_BOOL] = function(uniform, value) {
if (uniform.value !== value) {
gl.uniform1i(uniform.locationId, value);
uniform.value = value;
}
};
this.commitFunction[UNIFORMTYPE_INT] = this.commitFunction[UNIFORMTYPE_BOOL];
this.commitFunction[UNIFORMTYPE_FLOAT] = function(uniform, value) {
if (uniform.value !== value) {
gl.uniform1f(uniform.locationId, value);
uniform.value = value;
}
};
this.commitFunction[UNIFORMTYPE_VEC2] = function(uniform, value) {
uniformValue = uniform.value;
scopeX = value[0];
scopeY = value[1];
if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) {
gl.uniform2fv(uniform.locationId, value);
uniformValue[0] = scopeX;
uniformValue[1] = scopeY;
}
};
this.commitFunction[UNIFORMTYPE_VEC3] = function(uniform, value) {
uniformValue = uniform.value;
scopeX = value[0];
scopeY = value[1];
scopeZ = value[2];
if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) {
gl.uniform3fv(uniform.locationId, value);
uniformValue[0] = scopeX;
uniformValue[1] = scopeY;
uniformValue[2] = scopeZ;
}
};
this.commitFunction[UNIFORMTYPE_VEC4] = function(uniform, value) {
uniformValue = uniform.value;
scopeX = value[0];
scopeY = value[1];
scopeZ = value[2];
scopeW = value[3];
if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) {
gl.uniform4fv(uniform.locationId, value);
uniformValue[0] = scopeX;
uniformValue[1] = scopeY;
uniformValue[2] = scopeZ;
uniformValue[3] = scopeW;
}
};
this.commitFunction[UNIFORMTYPE_IVEC2] = function(uniform, value) {
uniformValue = uniform.value;
scopeX = value[0];
scopeY = value[1];
if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) {
gl.uniform2iv(uniform.locationId, value);
uniformValue[0] = scopeX;
uniformValue[1] = scopeY;
}
};
this.commitFunction[UNIFORMTYPE_BVEC2] = this.commitFunction[UNIFORMTYPE_IVEC2];
this.commitFunction[UNIFORMTYPE_IVEC3] = function(uniform, value) {
uniformValue = uniform.value;
scopeX = value[0];
scopeY = value[1];
scopeZ = value[2];
if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) {
gl.uniform3iv(uniform.locationId, value);
uniformValue[0] = scopeX;
uniformValue[1] = scopeY;
uniformValue[2] = scopeZ;
}
};
this.commitFunction[UNIFORMTYPE_BVEC3] = this.commitFunction[UNIFORMTYPE_IVEC3];
this.commitFunction[UNIFORMTYPE_IVEC4] = function(uniform, value) {
uniformValue = uniform.value;
scopeX = value[0];
scopeY = value[1];
scopeZ = value[2];
scopeW = value[3];
if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) {
gl.uniform4iv(uniform.locationId, value);
uniformValue[0] = scopeX;
uniformValue[1] = scopeY;
uniformValue[2] = scopeZ;
uniformValue[3] = scopeW;
}
};
this.commitFunction[UNIFORMTYPE_BVEC4] = this.commitFunction[UNIFORMTYPE_IVEC4];
this.commitFunction[UNIFORMTYPE_MAT2] = function(uniform, value) {
gl.uniformMatrix2fv(uniform.locationId, false, value);
};
this.commitFunction[UNIFORMTYPE_MAT3] = function(uniform, value) {
gl.uniformMatrix3fv(uniform.locationId, false, value);
};
this.commitFunction[UNIFORMTYPE_MAT4] = function(uniform, value) {
gl.uniformMatrix4fv(uniform.locationId, false, value);
};
this.commitFunction[UNIFORMTYPE_FLOATARRAY] = function(uniform, value) {
gl.uniform1fv(uniform.locationId, value);
};
this.commitFunction[UNIFORMTYPE_VEC2ARRAY] = function(uniform, value) {
gl.uniform2fv(uniform.locationId, value);
};
this.commitFunction[UNIFORMTYPE_VEC3ARRAY] = function(uniform, value) {
gl.uniform3fv(uniform.locationId, value);
};
this.commitFunction[UNIFORMTYPE_VEC4ARRAY] = function(uniform, value) {
gl.uniform4fv(uniform.locationId, value);
};
this.commitFunction[UNIFORMTYPE_UINT] = function(uniform, value) {
if (uniform.value !== value) {
gl.uniform1ui(uniform.locationId, value);
uniform.value = value;
}
};
this.commitFunction[UNIFORMTYPE_UVEC2] = function(uniform, value) {
uniformValue = uniform.value;
scopeX = value[0];
scopeY = value[1];
if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY) {
gl.uniform2uiv(uniform.locationId, value);
uniformValue[0] = scopeX;
uniformValue[1] = scopeY;
}
};
this.commitFunction[UNIFORMTYPE_UVEC3] = function(uniform, value) {
uniformValue = uniform.value;
scopeX = value[0];
scopeY = value[1];
scopeZ = value[2];
if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ) {
gl.uniform3uiv(uniform.locationId, value);
uniformValue[0] = scopeX;
uniformValue[1] = scopeY;
uniformValue[2] = scopeZ;
}
};
this.commitFunction[UNIFORMTYPE_UVEC4] = function(uniform, value) {
uniformValue = uniform.value;
scopeX = value[0];
scopeY = value[1];
scopeZ = value[2];
scopeW = value[3];
if (uniformValue[0] !== scopeX || uniformValue[1] !== scopeY || uniformValue[2] !== scopeZ || uniformValue[3] !== scopeW) {
gl.uniform4uiv(uniform.locationId, value);
uniformValue[0] = scopeX;
uniformValue[1] = scopeY;
uniformValue[2] = scopeZ;
uniformValue[3] = scopeW;
}
};
this.commitFunction[UNIFORMTYPE_INTARRAY] = function(uniform, value) {
gl.uniform1iv(uniform.locationId, value);
};
this.commitFunction[UNIFORMTYPE_UINTARRAY] = function(uniform, value) {
gl.uniform1uiv(uniform.locationId, value);
};
this.commitFunction[UNIFORMTYPE_BOOLARRAY] = this.commitFunction[UNIFORMTYPE_INTARRAY];
this.commitFunction[UNIFORMTYPE_IVEC2ARRAY] = function(uniform, value) {
gl.uniform2iv(uniform.locationId, value);
};
this.commitFunction[UNIFORMTYPE_UVEC2ARRAY] = function(uniform, value) {
gl.uniform2uiv(uniform.locationId, value);
};
this.commitFunction[UNIFORMTYPE_BVEC2ARRAY] = this.commitFunction[UNIFORMTYPE_IVEC2ARRAY];
this.commitFunction[UNIFORMTYPE_IVEC3ARRAY] = function(uniform, value) {
gl.uniform3iv(uniform.locationId, value);
};
this.commitFunction[UNIFORMTYPE_UVEC3ARRAY] = function(uniform, value) {
gl.uniform3uiv(uniform.locationId, value);
};
this.commitFunction[UNIFORMTYPE_BVEC3ARRAY] = this.commitFunction[UNIFORMTYPE_IVEC3ARRAY];
this.commitFunction[UNIFORMTYPE_IVEC4ARRAY] = function(uniform, value) {
gl.uniform4iv(uniform.locationId, value);
};
this.commitFunction[UNIFORMTYPE_UVEC4ARRAY] = function(uniform, value) {
gl.uniform4uiv(uniform.locationId, value);
};
this.commitFunction[UNIFORMTYPE_BVEC4ARRAY] = this.commitFunction[UNIFORMTYPE_IVEC4ARRAY];
this.commitFunction[UNIFORMTYPE_MAT4ARRAY] = function(uniform, value) {
gl.uniformMatrix4fv(uniform.locationId, false, value);
};
this.constantTexSource = this.scope.resolve('source');
this.postInit();
}
postInit() {
super.postInit();
this.gpuProfiler = new WebglGpuProfiler(this);
}
/**
* Destroy the graphics device.
*/ destroy() {
super.destroy();
const gl = this.gl;
if (this.feedback) {
gl.deleteTransformFeedback(this.feedback);
}
this.clearVertexArrayObjectCache();
this.canvas.removeEventListener('webglcontextlost', this._contextLostHandler, false);
this.canvas.removeEventListener('webglcontextrestored', this._contextRestoredHandler, false);
this._contextLostHandler = null;
this._contextRestoredHandler = null;
this.gl = null;
super.postDestroy();
}
createBackbuffer(frameBuffer) {
this.supportsStencil = this.initOptions.stencil;
this.backBuffer = new RenderTarget({
name: 'WebglFramebuffer',
graphicsDevice: this,
depth: this.initOptions.depth,
stencil: this.supportsStencil,
samples: this.samples
});
// use the default WebGL framebuffer for rendering
this.backBuffer.impl.suppliedColorFramebuffer = frameBuffer;
}
// Update framebuffer format based on the current framebuffer, as this is use to create matching multi-sampled framebuffer
updateBackbufferFormat(framebuffer) {
const gl = this.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
const alphaBits = this.gl.getParameter(this.gl.ALPHA_BITS);
this.backBufferFormat = alphaBits ? PIXELFORMAT_RGBA8 : PIXELFORMAT_RGB8;
}
updateBackbuffer() {
const resolutionChanged = this.canvas.width !== this.backBufferSize.x || this.canvas.height !== this.backBufferSize.y;
if (this._defaultFramebufferChanged || resolutionChanged) {
// if the default framebuffer changes (entering or exiting XR for example)
if (this._defaultFramebufferChanged) {
this.updateBackbufferFormat(this._defaultFramebuffer);
}
this._defaultFramebufferChanged = false;
this.backBufferSize.set(this.canvas.width, this.canvas.height);
// recreate the backbuffer with newly supplied framebuffer
this.backBuffer.destroy();
this.createBackbuffer(this._defaultFramebuffer);
}
}
// provide webgl implementation for the vertex buffer
createVertexBufferImpl(vertexBuffer, format) {
return new WebglVertexBuffer();
}
// provide webgl implementation for the index buffer
createIndexBufferImpl(indexBuffer) {
return new WebglIndexBuffer(indexBuffer);
}
createShaderImpl(shader) {
return new WebglShader(shader);
}
createDrawCommandImpl(drawCommands) {
return new WebglDrawCommands(drawCommands.indexSizeBytes);
}
createTextureImpl(texture) {
return new WebglTexture(texture);
}
createRenderTargetImpl(renderTarget) {
return new WebglRenderTarget();
}
pushMarker(name) {
if (platform.browser && window.spector) {
const label = DebugGraphics.toString();
window.spector.setMarker(`${label} #`);
}
}
popMarker() {
if (platform.browser && window.spector) {
const label = DebugGraphics.toString();
if (label.length) {
window.spector.setMarker(`${label} #`);
} else {
window.spector.clearMarker();
}
}
}
/**
* Query the precision supported by ints and floats in vertex and fragment shaders. Note that
* getShaderPrecisionFormat is not guaranteed to be present (such as some instances of the
* default Android browser). In this case, assume highp is available.
*
* @returns {"highp"|"mediump"|"lowp"} The highest precision supported by the WebGL context.
* @ignore
*/ getPrecision() {
const gl = this.gl;
let precision = 'highp';
if (gl.getShaderPrecisionFormat) {
const vertexShaderPrecisionHighpFloat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT);
const vertexShaderPrecisionMediumpFloat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT);
const fragmentShaderPrecisionHighpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
const fragmentShaderPrecisionMediumpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT);
if (vertexShaderPrecisionHighpFloat && vertexShaderPrecisionMediumpFloat && fragmentShaderPrecisionHighpFloat && fragmentShaderPrecisionMediumpFloat) {
const highpAvailable = vertexShaderPrecisionHighpFloat.precision > 0 && fragmentShaderPrecisionHighpFloat.precision > 0;
const mediumpAvailable = vertexShaderPrecisionMediumpFloat.precision > 0 && fragmentShaderPrecisionMediumpFloat.precision > 0;
if (!highpAvailable) {
if (mediumpAvailable) {
precision = 'mediump';
Debug.warn('WARNING: highp not supported, using mediump');
} else {
precision = 'lowp';
Debug.warn('WARNING: highp and mediump not supported, using lowp');
}
}
}
}
return precision;
}
getExtension() {
for(let i = 0; i < arguments.length; i++){
if (this.supportedExtensions.indexOf(arguments[i]) !== -1) {
return this.gl.getExtension(arguments[i]);
}
}
return null;
}
get extDisjointTimerQuery() {
// lazy evaluation as this is not typically used
if (!this._extDisjointTimerQuery) {
// Note that Firefox exposes EXT_disjoint_timer_query under WebGL2 rather than EXT_disjoint_timer_query_webgl2
this._extDisjointTimerQuery = this.getExtension('EXT_disjoint_timer_query_webgl2', 'EXT_disjoint_timer_query');
}
return this._extDisjointTimerQuery;
}
/**
* Initialize the extensions provided by the WebGL context.
*
* @ignore
*/ initializeExtensions() {
const gl = this.gl;
this.supportedExtensions = gl.getSupportedExtensions() ?? [];
this._extDisjointTimerQuery = null;
this.textureRG11B10Renderable = true;
// In WebGL2 float texture renderability is dictated by the EXT_color_buffer_float extension
this.extColorBufferFloat = this.getExtension('EXT_color_buffer_float');
this.textureFloatRenderable = !!this.extColorBufferFloat;
// iOS exposes this for half precision render targets on WebGL2 from iOS v 14.5beta
this.extColorBufferHalfFloat = this.getExtension('EXT_color_buffer_half_float');
// render to half float buffers support - either of these two extensions
this.textureHalfFloatRenderable = !!this.extColorBufferHalfFloat || !!this.extColorBufferFloat;
this.extDebugRendererInfo = this.getExtension('WEBGL_debug_renderer_info');
this.extTextureFloatLinear = this.getExtension('OES_texture_float_linear');
this.textureFloatFilterable = !!this.extTextureFloatLinear;
this.extFloatBlend = this.getExtension('EXT_float_blend');
this.extTextureFilterAnisotropic = this.getExtension('EXT_texture_filter_anisotropic', 'WEBKIT_EXT_texture_filter_anisotropic');
this.extParallelShaderCompile = this.getExtension('KHR_parallel_shader_compile');
this.extMultiDraw = this.getExtension('WEBGL_multi_draw');
this.supportsMultiDraw = !!this.extMultiDraw;
// compressed textures
this.extCompressedTextureETC1 = this.getExtension('WEBGL_compressed_texture_etc1');
this.extCompressedTextureETC = this.getExtension('WEBGL_compressed_texture_etc');
this.extCompressedTexturePVRTC = this.getExtension('WEBGL_compressed_texture_pvrtc', 'WEBKIT_WEBGL_compressed_texture_pvrtc');
this.extCompressedTextureS3TC = this.getExtension('WEBGL_compressed_texture_s3tc', 'WEBKIT_WEBGL_compressed_texture_s3tc');
this.extCompressedTextureS3TC_SRGB = this.getExtension('WEBGL_compressed_texture_s3tc_srgb');
this.extCompressedTextureATC = this.getExtension('WEBGL_compressed_texture_atc');
this.extCompressedTextureASTC = this.getExtension('WEBGL_compressed_texture_astc');
this.extTextureCompressionBPTC = this.getExtension('EXT_texture_compression_bptc');
}
/**
* Query the capabilities of the WebGL context.
*
* @ignore
*/ initializeCapabilities() {
const gl = this.gl;
let ext;
const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';
this.maxPrecision = this.precision = this.getPrecision();
const contextAttribs = gl.getContextAttributes();
this.supportsMsaa = contextAttribs?.antialias ?? false;
this.supportsStencil = contextAttribs?.stencil ?? false;
// Query parameter values from the WebGL context
this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
this.maxCubeMapSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);
this.maxRenderBufferSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
this.maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
this.maxCombinedTextures = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
this.maxVertexTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
this.vertexUniformsCount = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS);
this.fragmentUniformsCount = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);
this.maxColorAttachments = gl.getParameter(gl.MAX_COLOR_ATTACHMENTS);
this.maxVolumeSize = gl.getParameter(gl.MAX_3D_TEXTURE_SIZE);
ext = this.extDebugRendererInfo;
this.unmaskedRenderer = ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : '';
this.unmaskedVendor = ext ? gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) : '';
// Mali-G52 has rendering issues with GPU particles including
// SM-A225M, M2003J15SC and KFRAWI (Amazon Fire HD 8 2022)
const maliRendererRegex = /\bMali-G52+/;
// Samsung devices with Exynos (ARM) either crash or render incorrectly when using GPU for particles. See:
// https://github.com/playcanvas/engine/issues/3967
// https://github.com/playcanvas/engine/issues/3415
// https://github.com/playcanvas/engine/issues/4514
// Example UA matches: Starting 'SM' and any combination of letters or numbers:
// Mozilla/5.0 (Linux, Android 12; SM-G970F Build/SP1A.210812.016; wv)
const samsungModelRegex = /SM-[a-zA-Z0-9]+/;
this.supportsGpuParticles = !(this.unmaskedVendor === 'ARM' && userAgent.match(samsungModelRegex)) && !this.unmaskedRenderer.match(maliRendererRegex);
ext = this.extTextureFilterAnisotropic;
this.maxAnisotropy = ext ? gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT) : 1;
const antialiasSupported = !this.forceDisableMultisampling;
this.maxSamples = antialiasSupported ? gl.getParameter(gl.MAX_SAMPLES) : 1;
// some devices incorrectly report max samples larger than 4
this.maxSamples = Math.min(this.maxSamples, 4);
// we handle anti-aliasing internally by allocating multi-sampled backbuffer
this.samples = antialiasSupported && this.backBufferAntialias ? this.maxSamples : 1;
// Don't allow area lights on old android devices, they often fail to compile the shader, run it incorrectly or are very slow.
this.supportsAreaLights = !platform.android;
// Also do not allow them when we only have small number of texture units
if (this.maxTextures <= 8) {
this.supportsAreaLights = false;
}
this.initCapsDefines();
}
/**
* Set the initial render state on the WebGL context.
*
* @ignore
*/ initializeRenderState() {
super.initializeRenderState();
const gl = this.gl;
// Initialize render state to a known start state
// default blend state
gl.disable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ZERO);
gl.blendEquation(gl.FUNC_ADD);
gl.colorMask(true, true, true, true);
gl.blendColor(0, 0, 0, 0);
gl.enable(gl.CULL_FACE);
this.cullFace = gl.BACK;
gl.cullFace(gl.BACK);
// default depth state
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.depthMask(true);
this.stencil = false;
gl.disable(gl.STENCIL_TEST);
this.stencilFuncFront = this.stencilFuncBack = FUNC_ALWAYS;
this.stencilRefFront = this.stencilRefBack = 0;
this.stencilMaskFront = this.stencilMaskBack = 0xFF;
gl.stencilFunc(gl.ALWAYS, 0, 0xFF);
this.stencilFailFront = this.stencilFailBack = STENCILOP_KEEP;
this.stencilZfailFront = this.stencilZfailBack = STENCILOP_KEEP;
this.stencilZpassFront = this.stencilZpassBack = STENCILOP_KEEP;
this.stencilWriteMaskFront = 0xFF;
this.stencilWriteMaskBack = 0xFF;
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
gl.stencilMask(0xFF);
this.alphaToCoverage = false;
this.raster = true;
gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
gl.disable(gl.RASTERIZER_DISCARD);
this.depthBiasEnabled = false;
gl.disable(gl.POLYGON_OFFSET_FILL);
this.clearDepth = 1;
gl.clearDepth(1);
this.clearColor = new Color(0, 0, 0, 0);
gl.clearColor(0, 0, 0, 0);
this.clearStencil = 0;
gl.clearStencil(0);
gl.hint(gl.FRAGMENT_SHADER_DERIVATIVE_HINT, gl.NICEST);
gl.enable(gl.SCISSOR_TEST);
gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
this.unpackFlipY = false;
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
this.unpackPremultiplyAlpha = false;
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
}
initTextureUnits(count = 16) {
this.textureUnits = [];
for(let i = 0; i < count; i++){
this.textureUnits.push([
null,
null,
null
]);
}
}
initializeContextCaches() {
super.initializeContextCaches();
// cache of VAOs
this._vaoMap = new Map();
this.boundVao = null;
this.activeFramebuffer = null;
this.feedback = null;
this.transformFeedbackBuffer = null;
this.textureUnit = 0;
this.initTextureUnits(this.maxCombinedTextures);
}
/**
* Called when the WebGL context was lost. It releases all context related resources.
*
* @ignore
*/ loseContext() {
super.loseContext();
// release shaders
for (const shader of this.shaders){
shader.loseContext();
}
}
/**
* Called when the WebGL context is restored. It reinitializes all context related resources.
*
* @ignore
*/ restoreContext() {
this.initializeExtensions();
this.initializeCapabilities();
super.restoreContext();
// Recompile all shaders
for (const shader of this.shaders){
shader.restoreContext();
}
}
/**
* Set the active rectangle for rendering on the specified device.
*
* @param {number} x - The pixel space x-coordinate of the bottom left corner of the viewport.
* @param {number} y - The pixel space y-coordinate of the bottom left corner of the viewport.
* @param {number} w - The width of the viewport in pixels.
* @param {number} h - The height of the viewport in pixels.
*/ setViewport(x, y, w, h) {
if (this.vx !== x || this.vy !== y || this.vw !== w || this.vh !== h) {
this.gl.viewport(x, y, w, h);
this.vx = x;
this.vy = y;
this.vw = w;
this.vh = h;
}
}
/**
* Set the active scissor rectangle on the specified device.
*
* @param {number} x - The pixel space x-coordinate of the bottom left corner of the scissor rectangle.
* @param {number} y - The pixel space y-coordinate of the bottom left corner of the scissor rectangle.
* @param {number} w - The width of the scissor rectangle in pixels.
* @param {number} h - The height of the scissor rectangle in pixels.
*/ setScissor(x, y, w, h) {
if (this.sx !== x || this.sy !== y || this.sw !== w || this.sh !== h) {
this.gl.scissor(x, y, w, h);
this.sx = x;
this.sy = y;
this.sw = w;
this.sh = h;
}
}
/**
* Binds the specified framebuffer object.
*
* @param {WebGLFramebuffer | null} fb - The framebuffer to bind.
* @ignore
*/ setFramebuffer(fb) {
if (this.activeFramebuffer !== fb) {
const gl = this.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
this.activeFramebuffer = fb;
}
}
/**
* Copies source render target into destination render target. Mostly used by post-effects.
*
* @param {RenderTarget} [source] - The source render target. Defaults to frame buffer.
* @param {RenderTarget} [dest] - The destination render target. Defaults to frame buffer.
* @param {boolean} [color] - If true, will copy the color buffer. Defaults to false.
* @param {boolean} [depth] - If true, will copy the depth buffer. Defaults to false.
* @returns {boolean} True if the copy was successful, false otherwise.
*/ copyRenderTarget(source, dest, color, depth) {
const gl = this.gl;
// if copying from the backbuffer
if (source === this.backBuffer) {
source = null;
}
if (color) {
if (!dest) {
// copying to backbuffer
if (!source._colorBuffer) {
Debug.error('Can\'t copy empty color buffer to backbuffer');
return false;
}
} else if (source) {
// copying to render target
if (!source._colorBuffer || !dest._colorBuffer) {
Debug.error('Can\'t copy color buffer, because one of the render targets doesn\'t have it');
return false;
}
if (source._colorBuffer._format !== dest._colorBuffer._format) {
Debug.error('Can\'t copy render targets of different color formats');
return false;
}
}
}
if (depth && source) {
if (!source._depth) {
if (!source._depthBuffer || !dest._depthBuffer) {
Debug.error('Can\'t copy depth buffer, because one of the render targets doesn\'t have it');
return false;
}
if (source._depthBuffer._format !== dest._depthBuffer._format) {
Debug.error('Can\'t copy render targets of different depth formats');
return false;
}
}
}
DebugGraphics.pushGpuMarker(this, 'COPY-RT');
const prevRt = this.renderTarget;
this.renderTarget = dest;
this.updateBegin();
// copy from single sampled framebuffer
const src = source ? source.impl._glFrameBuffer : this.backBuffer?.impl._glFrameBuffer;
const dst = dest ? dest.impl._glFrameBuffer : this.backBuffer?.impl._glFrameBuffer;
Debug.assert(src !== dst, 'Source and destination framebuffers must be different when blitting.');
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, src);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, dst);
const w = source ? source.width : dest ? dest.width : this.width;
const h = source ? source.height : dest ? dest.height : this.height;
gl.blitFramebuffer(0, 0, w, h, 0, 0, w, h, (color ? gl.COLOR_BUFFER_BIT : 0) | (depth ? gl.DEPTH_BUFFER_BIT : 0), gl.NEAREST);
// TODO: not sure we need to restore the prev target, as this only should run in-between render passes
this.renderTarget = prevRt;
gl.bindFramebuffer(gl.FRAMEBUFFER, prevRt ? prevRt.impl._glFrameBuffer : null);
DebugGraphics.popGpuMarker(this);
return true;
}
frameStart() {
super.frameStart();
this.updateBackbuffer();
this.gpuProfiler.frameStart();
}
frameEnd() {
super.frameEnd();
this.gpuProfiler.frameEnd();
this.gpuProfiler.request();
}
/**
* Start a render pass.
*
* @param {RenderPass} renderPass - The render pass to start.
* @ignore
*/ startRenderPass(renderPass) {
// set up render target
const rt = renderPass.renderTarget ?? this.backBuffer;
this.renderTarget = rt;
Debug.assert(rt);
DebugGraphics.pushGpuMarker(this, `Pass:${renderPass.name} RT:${rt.name}`);
DebugGraphics.pushGpuMarker(this, 'START-PASS');
this.updateBegin();
// the pass always start using full size of the target
const { width, height } = rt;
this.setViewport(0, 0, width, height);
this.setScissor(0, 0, width, height);
// clear the render target
const colorOps = renderPass.colorOps;
const depthStencilOps = renderPass.depthStencilOps;
if (colorOps?.clear || depthStencilOps.clearDepth || depthStencilOps.clearStencil) {
let clearFlags = 0;
const clearOptions = {};
if (colorOps?.clear) {
clearFlags |= CLEARFLAG_COLOR;
clearOptions.color = [
colorOps.clearValue.r,
colorOps.clearValue.g,
colorOps.clearValue.b,
colorOps.clearValue.a
];
}
if (depthStencilOps.clearDepth) {
clearFlags |= CLEARFLAG_DEPTH;
clearOptions.depth = depthStencilOps.clearDepthValue;
}
if (depthStencilOps.clearStencil) {
clearFlags |= CLEARFLAG_STENCIL;
clearOptions.stencil = depthStencilOps.clearStencilValue;
}
// clear it
clearOptions.flags = clearFlags;
this.clear(clearOptions);
}
Debug.call(()=>{
if (this.insideRenderPass) {
Debug.errorOnce('RenderPass cannot be started while inside another render pass.');
}
});
this.insideRenderPass = true;
DebugGraphics.popGpuMarker(this);
}
/**
* End a render pass.
*
* @param {RenderPass} renderPass - The render pass to end.
* @ignore
*/ endRenderPass(renderPass) {
DebugGraphics.pushGpuMarker(this, 'END-PASS');
this.unbindVertexArray();
const target = this.renderTarget;
const colorBufferCount = renderPass.colorArrayOps.length;
if (target) {
// invalidate buffers to stop them being written to on tiled architectures
invalidateAttachments.length = 0;
const gl = this.gl;
// color buffers
for(let i = 0; i < colorBufferCount; i++){
const colorOps = renderPass.colorArrayOps[i];
// invalidate color only if we don't need to resolve it
if (!(colorOps.store || colorOps.resolve)) {
invalidateAttachments.push(gl.COLOR_ATTACHMENT0 + i);
}
}
// we cannot invalidate depth/stencil buffers of the backbuffer
if (target !== this.backBuffer) {
if (!renderPass.depthStencilOps.storeDepth) {
invalidateAttachments.push(gl.DEPTH_ATTACHMENT);
}
if (!renderPass.depthStencilOps.storeStencil) {
invalidateAttachments.push(gl.STENCIL_ATTACHMENT);
}
}
if (invalidateAttachments.length > 0) {
// invalidate the whole buffer
// TODO: we could handle viewport invalidation as well
if (renderPass.fullSizeClearRect) {
gl.invalidateFramebuffer(gl.DRAW_FRAMEBUFFER, invalidateAttachments);
}
}
// resolve the color buffer (this resolves all MRT color buffers at once)
if (colorBufferCount && renderPass.colorOps?.resolve) {
if (renderPass.samples > 1 && target.autoResolve) {
target.resolve(true, false);
}
}
// resolve depth/stencil buffer
if (target.depthBuffer && renderPass.depthStencilOps.resolveDepth) {
if (renderPass.samples > 1 && target.autoResolve) {
target.resolve(false, true);
}
}
// generate mipmaps
for(let i = 0; i < colorBufferCount; i++){
const colorOps = renderPass.colorArrayOps[i];