UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

877 lines (839 loc) 35 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _constants = require("../../../constants.js"); let initialized = false, wrappingToGL, filterToGL, compareToGL; /** * A WebGL 2 backend utility module for managing textures. * * @private */ class WebGLTextureUtils { /** * Constructs a new utility object. * * @param {WebGLBackend} backend - The WebGL 2 backend. */ constructor(backend) { /** * A reference to the WebGL 2 backend. * * @type {WebGLBackend} */ this.backend = backend; /** * A reference to the rendering context. * * @type {WebGL2RenderingContext} */ this.gl = backend.gl; /** * A reference to a backend module holding extension-related * utility functions. * * @type {WebGLExtensions} */ this.extensions = backend.extensions; /** * A dictionary for managing default textures. The key * is the binding point (target), the value the WEbGL texture object. * * @type {Object<GLenum,WebGLTexture>} */ this.defaultTextures = {}; if (initialized === false) { this._init(); initialized = true; } } /** * Inits the state of the utility. * * @private */ _init() { const gl = this.gl; // Store only WebGL constants here. wrappingToGL = { [_constants.RepeatWrapping]: gl.REPEAT, [_constants.ClampToEdgeWrapping]: gl.CLAMP_TO_EDGE, [_constants.MirroredRepeatWrapping]: gl.MIRRORED_REPEAT }; filterToGL = { [_constants.NearestFilter]: gl.NEAREST, [_constants.NearestMipmapNearestFilter]: gl.NEAREST_MIPMAP_NEAREST, [_constants.NearestMipmapLinearFilter]: gl.NEAREST_MIPMAP_LINEAR, [_constants.LinearFilter]: gl.LINEAR, [_constants.LinearMipmapNearestFilter]: gl.LINEAR_MIPMAP_NEAREST, [_constants.LinearMipmapLinearFilter]: gl.LINEAR_MIPMAP_LINEAR }; compareToGL = { [_constants.NeverCompare]: gl.NEVER, [_constants.AlwaysCompare]: gl.ALWAYS, [_constants.LessCompare]: gl.LESS, [_constants.LessEqualCompare]: gl.LEQUAL, [_constants.EqualCompare]: gl.EQUAL, [_constants.GreaterEqualCompare]: gl.GEQUAL, [_constants.GreaterCompare]: gl.GREATER, [_constants.NotEqualCompare]: gl.NOTEQUAL }; } /** * Returns the native texture type for the given texture. * * @param {Texture} texture - The texture. * @return {GLenum} The native texture type. */ getGLTextureType(texture) { const { gl } = this; let glTextureType; if (texture.isCubeTexture === true) { glTextureType = gl.TEXTURE_CUBE_MAP; } else if (texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true) { glTextureType = gl.TEXTURE_2D_ARRAY; } else if (texture.isData3DTexture === true) { // TODO: isCompressed3DTexture, wait for #26642 glTextureType = gl.TEXTURE_3D; } else { glTextureType = gl.TEXTURE_2D; } return glTextureType; } /** * Returns the native texture type for the given texture. * * @param {?string} internalFormatName - The internal format name. When `null`, the internal format is derived from the subsequent parameters. * @param {GLenum} glFormat - The WebGL format. * @param {GLenum} glType - The WebGL type. * @param {string} colorSpace - The texture's color space. * @param {boolean} [forceLinearTransfer=false] - Whether to force a linear transfer or not. * @return {GLenum} The internal format. */ getInternalFormat(internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false) { const { gl, extensions } = this; if (internalFormatName !== null) { if (gl[internalFormatName] !== undefined) return gl[internalFormatName]; console.warn('THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\''); } let internalFormat = glFormat; if (glFormat === gl.RED) { if (glType === gl.FLOAT) internalFormat = gl.R32F; if (glType === gl.HALF_FLOAT) internalFormat = gl.R16F; if (glType === gl.UNSIGNED_BYTE) internalFormat = gl.R8; if (glType === gl.UNSIGNED_SHORT) internalFormat = gl.R16; if (glType === gl.UNSIGNED_INT) internalFormat = gl.R32UI; if (glType === gl.BYTE) internalFormat = gl.R8I; if (glType === gl.SHORT) internalFormat = gl.R16I; if (glType === gl.INT) internalFormat = gl.R32I; } if (glFormat === gl.RED_INTEGER) { if (glType === gl.UNSIGNED_BYTE) internalFormat = gl.R8UI; if (glType === gl.UNSIGNED_SHORT) internalFormat = gl.R16UI; if (glType === gl.UNSIGNED_INT) internalFormat = gl.R32UI; if (glType === gl.BYTE) internalFormat = gl.R8I; if (glType === gl.SHORT) internalFormat = gl.R16I; if (glType === gl.INT) internalFormat = gl.R32I; } if (glFormat === gl.RG) { if (glType === gl.FLOAT) internalFormat = gl.RG32F; if (glType === gl.HALF_FLOAT) internalFormat = gl.RG16F; if (glType === gl.UNSIGNED_BYTE) internalFormat = gl.RG8; if (glType === gl.UNSIGNED_SHORT) internalFormat = gl.RG16; if (glType === gl.UNSIGNED_INT) internalFormat = gl.RG32UI; if (glType === gl.BYTE) internalFormat = gl.RG8I; if (glType === gl.SHORT) internalFormat = gl.RG16I; if (glType === gl.INT) internalFormat = gl.RG32I; } if (glFormat === gl.RG_INTEGER) { if (glType === gl.UNSIGNED_BYTE) internalFormat = gl.RG8UI; if (glType === gl.UNSIGNED_SHORT) internalFormat = gl.RG16UI; if (glType === gl.UNSIGNED_INT) internalFormat = gl.RG32UI; if (glType === gl.BYTE) internalFormat = gl.RG8I; if (glType === gl.SHORT) internalFormat = gl.RG16I; if (glType === gl.INT) internalFormat = gl.RG32I; } if (glFormat === gl.RGB) { if (glType === gl.FLOAT) internalFormat = gl.RGB32F; if (glType === gl.HALF_FLOAT) internalFormat = gl.RGB16F; if (glType === gl.UNSIGNED_BYTE) internalFormat = gl.RGB8; if (glType === gl.UNSIGNED_SHORT) internalFormat = gl.RGB16; if (glType === gl.UNSIGNED_INT) internalFormat = gl.RGB32UI; if (glType === gl.BYTE) internalFormat = gl.RGB8I; if (glType === gl.SHORT) internalFormat = gl.RGB16I; if (glType === gl.INT) internalFormat = gl.RGB32I; if (glType === gl.UNSIGNED_BYTE) internalFormat = colorSpace === _constants.SRGBColorSpace && forceLinearTransfer === false ? gl.SRGB8 : gl.RGB8; if (glType === gl.UNSIGNED_SHORT_5_6_5) internalFormat = gl.RGB565; if (glType === gl.UNSIGNED_SHORT_5_5_5_1) internalFormat = gl.RGB5_A1; if (glType === gl.UNSIGNED_SHORT_4_4_4_4) internalFormat = gl.RGB4; if (glType === gl.UNSIGNED_INT_5_9_9_9_REV) internalFormat = gl.RGB9_E5; } if (glFormat === gl.RGB_INTEGER) { if (glType === gl.UNSIGNED_BYTE) internalFormat = gl.RGB8UI; if (glType === gl.UNSIGNED_SHORT) internalFormat = gl.RGB16UI; if (glType === gl.UNSIGNED_INT) internalFormat = gl.RGB32UI; if (glType === gl.BYTE) internalFormat = gl.RGB8I; if (glType === gl.SHORT) internalFormat = gl.RGB16I; if (glType === gl.INT) internalFormat = gl.RGB32I; } if (glFormat === gl.RGBA) { if (glType === gl.FLOAT) internalFormat = gl.RGBA32F; if (glType === gl.HALF_FLOAT) internalFormat = gl.RGBA16F; if (glType === gl.UNSIGNED_BYTE) internalFormat = gl.RGBA8; if (glType === gl.UNSIGNED_SHORT) internalFormat = gl.RGBA16; if (glType === gl.UNSIGNED_INT) internalFormat = gl.RGBA32UI; if (glType === gl.BYTE) internalFormat = gl.RGBA8I; if (glType === gl.SHORT) internalFormat = gl.RGBA16I; if (glType === gl.INT) internalFormat = gl.RGBA32I; if (glType === gl.UNSIGNED_BYTE) internalFormat = colorSpace === _constants.SRGBColorSpace && forceLinearTransfer === false ? gl.SRGB8_ALPHA8 : gl.RGBA8; if (glType === gl.UNSIGNED_SHORT_4_4_4_4) internalFormat = gl.RGBA4; if (glType === gl.UNSIGNED_SHORT_5_5_5_1) internalFormat = gl.RGB5_A1; } if (glFormat === gl.RGBA_INTEGER) { if (glType === gl.UNSIGNED_BYTE) internalFormat = gl.RGBA8UI; if (glType === gl.UNSIGNED_SHORT) internalFormat = gl.RGBA16UI; if (glType === gl.UNSIGNED_INT) internalFormat = gl.RGBA32UI; if (glType === gl.BYTE) internalFormat = gl.RGBA8I; if (glType === gl.SHORT) internalFormat = gl.RGBA16I; if (glType === gl.INT) internalFormat = gl.RGBA32I; } if (glFormat === gl.DEPTH_COMPONENT) { if (glType === gl.UNSIGNED_SHORT) internalFormat = gl.DEPTH_COMPONENT16; if (glType === gl.UNSIGNED_INT) internalFormat = gl.DEPTH_COMPONENT24; if (glType === gl.FLOAT) internalFormat = gl.DEPTH_COMPONENT32F; } if (glFormat === gl.DEPTH_STENCIL) { if (glType === gl.UNSIGNED_INT_24_8) internalFormat = gl.DEPTH24_STENCIL8; } if (internalFormat === gl.R16F || internalFormat === gl.R32F || internalFormat === gl.RG16F || internalFormat === gl.RG32F || internalFormat === gl.RGBA16F || internalFormat === gl.RGBA32F) { extensions.get('EXT_color_buffer_float'); } return internalFormat; } /** * Sets the texture parameters for the given texture. * * @param {GLenum} textureType - The texture type. * @param {Texture} texture - The texture. */ setTextureParameters(textureType, texture) { const { gl, extensions, backend } = this; gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, texture.flipY); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha); gl.pixelStorei(gl.UNPACK_ALIGNMENT, texture.unpackAlignment); gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE); gl.texParameteri(textureType, gl.TEXTURE_WRAP_S, wrappingToGL[texture.wrapS]); gl.texParameteri(textureType, gl.TEXTURE_WRAP_T, wrappingToGL[texture.wrapT]); if (textureType === gl.TEXTURE_3D || textureType === gl.TEXTURE_2D_ARRAY) { gl.texParameteri(textureType, gl.TEXTURE_WRAP_R, wrappingToGL[texture.wrapR]); } gl.texParameteri(textureType, gl.TEXTURE_MAG_FILTER, filterToGL[texture.magFilter]); const hasMipmaps = texture.mipmaps !== undefined && texture.mipmaps.length > 0; // follow WebGPU backend mapping for texture filtering const minFilter = texture.minFilter === _constants.LinearFilter && hasMipmaps ? _constants.LinearMipmapLinearFilter : texture.minFilter; gl.texParameteri(textureType, gl.TEXTURE_MIN_FILTER, filterToGL[minFilter]); if (texture.compareFunction) { gl.texParameteri(textureType, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE); gl.texParameteri(textureType, gl.TEXTURE_COMPARE_FUNC, compareToGL[texture.compareFunction]); } if (extensions.has('EXT_texture_filter_anisotropic') === true) { if (texture.magFilter === _constants.NearestFilter) return; if (texture.minFilter !== _constants.NearestMipmapLinearFilter && texture.minFilter !== _constants.LinearMipmapLinearFilter) return; if (texture.type === _constants.FloatType && extensions.has('OES_texture_float_linear') === false) return; // verify extension for WebGL 1 and WebGL 2 if (texture.anisotropy > 1) { const extension = extensions.get('EXT_texture_filter_anisotropic'); gl.texParameterf(textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min(texture.anisotropy, backend.getMaxAnisotropy())); } } } /** * Creates a default texture for the given texture that can be used * as a placeholder until the actual texture is ready for usage. * * @param {Texture} texture - The texture to create a default texture for. */ createDefaultTexture(texture) { const { gl, backend, defaultTextures } = this; const glTextureType = this.getGLTextureType(texture); let textureGPU = defaultTextures[glTextureType]; if (textureGPU === undefined) { textureGPU = gl.createTexture(); backend.state.bindTexture(glTextureType, textureGPU); gl.texParameteri(glTextureType, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(glTextureType, gl.TEXTURE_MAG_FILTER, gl.NEAREST); // gl.texImage2D( glTextureType, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); defaultTextures[glTextureType] = textureGPU; } backend.set(texture, { textureGPU, glTextureType, isDefault: true }); } /** * Defines a texture on the GPU for the given texture object. * * @param {Texture} texture - The texture. * @param {Object} [options={}] - Optional configuration parameter. * @return {undefined} */ createTexture(texture, options) { const { gl, backend } = this; const { levels, width, height, depth } = options; const glFormat = backend.utils.convert(texture.format, texture.colorSpace); const glType = backend.utils.convert(texture.type); const glInternalFormat = this.getInternalFormat(texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture); const textureGPU = gl.createTexture(); const glTextureType = this.getGLTextureType(texture); backend.state.bindTexture(glTextureType, textureGPU); this.setTextureParameters(glTextureType, texture); if (texture.isDataArrayTexture || texture.isCompressedArrayTexture) { gl.texStorage3D(gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, width, height, depth); } else if (texture.isData3DTexture) { gl.texStorage3D(gl.TEXTURE_3D, levels, glInternalFormat, width, height, depth); } else if (!texture.isVideoTexture) { gl.texStorage2D(glTextureType, levels, glInternalFormat, width, height); } backend.set(texture, { textureGPU, glTextureType, glFormat, glType, glInternalFormat }); } /** * Uploads texture buffer data to the GPU memory. * * @param {WebGLBuffer} buffer - The buffer data. * @param {Texture} texture - The texture, */ copyBufferToTexture(buffer, texture) { const { gl, backend } = this; const { textureGPU, glTextureType, glFormat, glType } = backend.get(texture); const { width, height } = texture.source.data; gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, buffer); backend.state.bindTexture(glTextureType, textureGPU); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); gl.texSubImage2D(glTextureType, 0, 0, 0, width, height, glFormat, glType, 0); gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null); backend.state.unbindTexture(); // debug // const framebuffer = gl.createFramebuffer(); // gl.bindFramebuffer( gl.FRAMEBUFFER, framebuffer ); // gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, glTextureType, textureGPU, 0 ); // const readout = new Float32Array( width * height * 4 ); // const altFormat = gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_FORMAT ); // const altType = gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_TYPE ); // gl.readPixels( 0, 0, width, height, altFormat, altType, readout ); // gl.bindFramebuffer( gl.FRAMEBUFFER, null ); // console.log( readout ); } /** * Uploads the updated texture data to the GPU. * * @param {Texture} texture - The texture. * @param {Object} [options={}] - Optional configuration parameter. */ updateTexture(texture, options) { const { gl } = this; const { width, height } = options; const { textureGPU, glTextureType, glFormat, glType, glInternalFormat } = this.backend.get(texture); if (texture.isRenderTargetTexture || textureGPU === undefined /* unsupported texture format */) return; const getImage = source => { if (source.isDataTexture) { return source.image.data; } else if (typeof HTMLImageElement !== 'undefined' && source instanceof HTMLImageElement || typeof HTMLCanvasElement !== 'undefined' && source instanceof HTMLCanvasElement || typeof ImageBitmap !== 'undefined' && source instanceof ImageBitmap || source instanceof OffscreenCanvas) { return source; } return source.data; }; this.backend.state.bindTexture(glTextureType, textureGPU); this.setTextureParameters(glTextureType, texture); if (texture.isCompressedTexture) { const mipmaps = texture.mipmaps; const image = options.image; for (let i = 0; i < mipmaps.length; i++) { const mipmap = mipmaps[i]; if (texture.isCompressedArrayTexture) { if (texture.format !== gl.RGBA) { if (glFormat !== null) { gl.compressedTexSubImage3D(gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data); } else { console.warn('THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()'); } } else { gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data); } } else { if (glFormat !== null) { gl.compressedTexSubImage2D(gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data); } else { console.warn('Unsupported compressed texture format'); } } } } else if (texture.isCubeTexture) { const images = options.images; for (let i = 0; i < 6; i++) { const image = getImage(images[i]); gl.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, width, height, glFormat, glType, image); } } else if (texture.isDataArrayTexture) { const image = options.image; gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data); } else if (texture.isData3DTexture) { const image = options.image; gl.texSubImage3D(gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data); } else if (texture.isVideoTexture) { texture.update(); gl.texImage2D(glTextureType, 0, glInternalFormat, glFormat, glType, options.image); } else { const image = getImage(options.image); gl.texSubImage2D(glTextureType, 0, 0, 0, width, height, glFormat, glType, image); } } /** * Generates mipmaps for the given texture. * * @param {Texture} texture - The texture. */ generateMipmaps(texture) { const { gl, backend } = this; const { textureGPU, glTextureType } = backend.get(texture); backend.state.bindTexture(glTextureType, textureGPU); gl.generateMipmap(glTextureType); } /** * Deallocates the render buffers of the given render target. * * @param {RenderTarget} renderTarget - The render target. */ deallocateRenderBuffers(renderTarget) { const { gl, backend } = this; // remove framebuffer reference if (renderTarget) { const renderContextData = backend.get(renderTarget); renderContextData.renderBufferStorageSetup = undefined; if (renderContextData.framebuffers) { for (const cacheKey in renderContextData.framebuffers) { gl.deleteFramebuffer(renderContextData.framebuffers[cacheKey]); } delete renderContextData.framebuffers; } if (renderContextData.depthRenderbuffer) { gl.deleteRenderbuffer(renderContextData.depthRenderbuffer); delete renderContextData.depthRenderbuffer; } if (renderContextData.stencilRenderbuffer) { gl.deleteRenderbuffer(renderContextData.stencilRenderbuffer); delete renderContextData.stencilRenderbuffer; } if (renderContextData.msaaFrameBuffer) { gl.deleteFramebuffer(renderContextData.msaaFrameBuffer); delete renderContextData.msaaFrameBuffer; } if (renderContextData.msaaRenderbuffers) { for (let i = 0; i < renderContextData.msaaRenderbuffers.length; i++) { gl.deleteRenderbuffer(renderContextData.msaaRenderbuffers[i]); } delete renderContextData.msaaRenderbuffers; } } } /** * Destroys the GPU data for the given texture object. * * @param {Texture} texture - The texture. */ destroyTexture(texture) { const { gl, backend } = this; const { textureGPU, renderTarget } = backend.get(texture); this.deallocateRenderBuffers(renderTarget); gl.deleteTexture(textureGPU); backend.delete(texture); } /** * Copies data of the given source texture to the given destination texture. * * @param {Texture} srcTexture - The source texture. * @param {Texture} dstTexture - The destination texture. * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy. * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy. * @param {number} [srcLevel=0] - The source mip level to copy from. * @param {number} [dstLevel=0] - The destination mip level to copy to. */ copyTextureToTexture(srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0) { const { gl, backend } = this; const { state } = this.backend; const { textureGPU: dstTextureGPU, glTextureType, glType, glFormat } = backend.get(dstTexture); state.bindTexture(glTextureType, dstTextureGPU); // gather the necessary dimensions to copy let width, height, depth, minX, minY, minZ; let dstX, dstY, dstZ; const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[dstLevel] : srcTexture.image; if (srcRegion !== null) { width = srcRegion.max.x - srcRegion.min.x; height = srcRegion.max.y - srcRegion.min.y; depth = srcRegion.isBox3 ? srcRegion.max.z - srcRegion.min.z : 1; minX = srcRegion.min.x; minY = srcRegion.min.y; minZ = srcRegion.isBox3 ? srcRegion.min.z : 0; } else { const levelScale = Math.pow(2, -srcLevel); width = Math.floor(image.width * levelScale); height = Math.floor(image.height * levelScale); if (srcTexture.isDataArrayTexture) { depth = image.depth; } else if (srcTexture.isData3DTexture) { depth = Math.floor(image.depth * levelScale); } else { depth = 1; } minX = 0; minY = 0; minZ = 0; } if (dstPosition !== null) { dstX = dstPosition.x; dstY = dstPosition.y; dstZ = dstPosition.z; } else { dstX = 0; dstY = 0; dstZ = 0; } gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha); gl.pixelStorei(gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment); // used for copying data from cpu const currentUnpackRowLen = gl.getParameter(gl.UNPACK_ROW_LENGTH); const currentUnpackImageHeight = gl.getParameter(gl.UNPACK_IMAGE_HEIGHT); const currentUnpackSkipPixels = gl.getParameter(gl.UNPACK_SKIP_PIXELS); const currentUnpackSkipRows = gl.getParameter(gl.UNPACK_SKIP_ROWS); const currentUnpackSkipImages = gl.getParameter(gl.UNPACK_SKIP_IMAGES); gl.pixelStorei(gl.UNPACK_ROW_LENGTH, image.width); gl.pixelStorei(gl.UNPACK_IMAGE_HEIGHT, image.height); gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, minX); gl.pixelStorei(gl.UNPACK_SKIP_ROWS, minY); gl.pixelStorei(gl.UNPACK_SKIP_IMAGES, minZ); // set up the src texture const isDst3D = dstTexture.isDataArrayTexture || dstTexture.isData3DTexture; if (srcTexture.isRenderTargetTexture || srcTexture.isDepthTexture) { const srcTextureData = backend.get(srcTexture); const dstTextureData = backend.get(dstTexture); const srcRenderContextData = backend.get(srcTextureData.renderTarget); const dstRenderContextData = backend.get(dstTextureData.renderTarget); const srcFramebuffer = srcRenderContextData.framebuffers[srcTextureData.cacheKey]; const dstFramebuffer = dstRenderContextData.framebuffers[dstTextureData.cacheKey]; state.bindFramebuffer(gl.READ_FRAMEBUFFER, srcFramebuffer); state.bindFramebuffer(gl.DRAW_FRAMEBUFFER, dstFramebuffer); let mask = gl.COLOR_BUFFER_BIT; if (srcTexture.isDepthTexture) mask = gl.DEPTH_BUFFER_BIT; gl.blitFramebuffer(minX, minY, width, height, dstX, dstY, width, height, mask, gl.NEAREST); state.bindFramebuffer(gl.READ_FRAMEBUFFER, null); state.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null); } else { if (isDst3D) { // copy data into the 3d texture if (srcTexture.isDataTexture || srcTexture.isData3DTexture) { gl.texSubImage3D(glTextureType, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image.data); } else if (dstTexture.isCompressedArrayTexture) { gl.compressedTexSubImage3D(glTextureType, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, image.data); } else { gl.texSubImage3D(glTextureType, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image); } } else { // copy data into the 2d texture if (srcTexture.isDataTexture) { gl.texSubImage2D(glTextureType, dstLevel, dstX, dstY, width, height, glFormat, glType, image.data); } else if (srcTexture.isCompressedTexture) { gl.compressedTexSubImage2D(glTextureType, dstLevel, dstX, dstY, image.width, image.height, glFormat, image.data); } else { gl.texSubImage2D(glTextureType, dstLevel, dstX, dstY, width, height, glFormat, glType, image); } } } // reset values gl.pixelStorei(gl.UNPACK_ROW_LENGTH, currentUnpackRowLen); gl.pixelStorei(gl.UNPACK_IMAGE_HEIGHT, currentUnpackImageHeight); gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels); gl.pixelStorei(gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows); gl.pixelStorei(gl.UNPACK_SKIP_IMAGES, currentUnpackSkipImages); // Generate mipmaps only when copying level 0 if (dstLevel === 0 && dstTexture.generateMipmaps) { gl.generateMipmap(glTextureType); } state.unbindTexture(); } /** * Copies the current bound framebuffer to the given texture. * * @param {Texture} texture - The destination texture. * @param {RenderContext} renderContext - The render context. * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy. */ copyFramebufferToTexture(texture, renderContext, rectangle) { const { gl } = this; const { state } = this.backend; const { textureGPU } = this.backend.get(texture); const { x, y, z: width, w: height } = rectangle; const requireDrawFrameBuffer = texture.isDepthTexture === true || renderContext.renderTarget && renderContext.renderTarget.samples > 0; const srcHeight = renderContext.renderTarget ? renderContext.renderTarget.height : this.backend.getDrawingBufferSize().y; if (requireDrawFrameBuffer) { const partial = x !== 0 || y !== 0; let mask; let attachment; if (texture.isDepthTexture === true) { mask = gl.DEPTH_BUFFER_BIT; attachment = gl.DEPTH_ATTACHMENT; if (renderContext.stencil) { mask |= gl.STENCIL_BUFFER_BIT; } } else { mask = gl.COLOR_BUFFER_BIT; attachment = gl.COLOR_ATTACHMENT0; } if (partial) { const renderTargetContextData = this.backend.get(renderContext.renderTarget); const fb = renderTargetContextData.framebuffers[renderContext.getCacheKey()]; const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer; state.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb); state.bindFramebuffer(gl.READ_FRAMEBUFFER, msaaFrameBuffer); const flippedY = srcHeight - y - height; gl.blitFramebuffer(x, flippedY, x + width, flippedY + height, x, flippedY, x + width, flippedY + height, mask, gl.NEAREST); state.bindFramebuffer(gl.READ_FRAMEBUFFER, fb); state.bindTexture(gl.TEXTURE_2D, textureGPU); gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, x, flippedY, width, height); state.unbindTexture(); } else { const fb = gl.createFramebuffer(); state.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb); gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureGPU, 0); gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, mask, gl.NEAREST); gl.deleteFramebuffer(fb); } } else { state.bindTexture(gl.TEXTURE_2D, textureGPU); gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, x, srcHeight - height - y, width, height); state.unbindTexture(); } if (texture.generateMipmaps) this.generateMipmaps(texture); this.backend._setFramebuffer(renderContext); } /** * SetupS storage for internal depth/stencil buffers and bind to correct framebuffer. * * @param {WebGLRenderbuffer} renderbuffer - The render buffer. * @param {RenderContext} renderContext - The render context. * @param {number} samples - The MSAA sample count. * @param {boolean} [useMultisampledRTT=false] - Whether to use WEBGL_multisampled_render_to_texture or not. */ setupRenderBufferStorage(renderbuffer, renderContext, samples, useMultisampledRTT = false) { const { gl } = this; const renderTarget = renderContext.renderTarget; const { depthTexture, depthBuffer, stencilBuffer, width, height } = renderTarget; gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer); if (depthBuffer && !stencilBuffer) { let glInternalFormat = gl.DEPTH_COMPONENT24; if (useMultisampledRTT === true) { const multisampledRTTExt = this.extensions.get('WEBGL_multisampled_render_to_texture'); multisampledRTTExt.renderbufferStorageMultisampleEXT(gl.RENDERBUFFER, renderTarget.samples, glInternalFormat, width, height); } else if (samples > 0) { if (depthTexture && depthTexture.isDepthTexture) { if (depthTexture.type === gl.FLOAT) { glInternalFormat = gl.DEPTH_COMPONENT32F; } } gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, glInternalFormat, width, height); } else { gl.renderbufferStorage(gl.RENDERBUFFER, glInternalFormat, width, height); } gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer); } else if (depthBuffer && stencilBuffer) { if (samples > 0) { gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, width, height); } else { gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height); } gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, renderbuffer); } } /** * Returns texture data as a typed array. * * @async * @param {Texture} texture - The texture to copy. * @param {number} x - The x coordinate of the copy origin. * @param {number} y - The y coordinate of the copy origin. * @param {number} width - The width of the copy. * @param {number} height - The height of the copy. * @param {number} faceIndex - The face index. * @return {Promise<TypedArray>} A Promise that resolves with a typed array when the copy operation has finished. */ async copyTextureToBuffer(texture, x, y, width, height, faceIndex) { const { backend, gl } = this; const { textureGPU, glFormat, glType } = this.backend.get(texture); const fb = gl.createFramebuffer(); gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb); const target = texture.isCubeTexture ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex : gl.TEXTURE_2D; gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, target, textureGPU, 0); const typedArrayType = this._getTypedArrayType(glType); const bytesPerTexel = this._getBytesPerTexel(glType, glFormat); const elementCount = width * height; const byteLength = elementCount * bytesPerTexel; const buffer = gl.createBuffer(); gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buffer); gl.bufferData(gl.PIXEL_PACK_BUFFER, byteLength, gl.STREAM_READ); gl.readPixels(x, y, width, height, glFormat, glType, 0); gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null); await backend.utils._clientWaitAsync(); const dstBuffer = new typedArrayType(byteLength / typedArrayType.BYTES_PER_ELEMENT); gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buffer); gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, dstBuffer); gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null); gl.deleteFramebuffer(fb); return dstBuffer; } /** * Returns the corresponding typed array type for the given WebGL data type. * * @private * @param {GLenum} glType - The WebGL data type. * @return {TypedArray.constructor} The typed array type. */ _getTypedArrayType(glType) { const { gl } = this; if (glType === gl.UNSIGNED_BYTE) return Uint8Array; if (glType === gl.UNSIGNED_SHORT_4_4_4_4) return Uint16Array; if (glType === gl.UNSIGNED_SHORT_5_5_5_1) return Uint16Array; if (glType === gl.UNSIGNED_SHORT_5_6_5) return Uint16Array; if (glType === gl.UNSIGNED_SHORT) return Uint16Array; if (glType === gl.UNSIGNED_INT) return Uint32Array; if (glType === gl.HALF_FLOAT) return Uint16Array; if (glType === gl.FLOAT) return Float32Array; throw new Error(`Unsupported WebGL type: ${glType}`); } /** * Returns the bytes-per-texel value for the given WebGL data type and texture format. * * @private * @param {GLenum} glType - The WebGL data type. * @param {GLenum} glFormat - The WebGL texture format. * @return {number} The bytes-per-texel. */ _getBytesPerTexel(glType, glFormat) { const { gl } = this; let bytesPerComponent = 0; if (glType === gl.UNSIGNED_BYTE) bytesPerComponent = 1; if (glType === gl.UNSIGNED_SHORT_4_4_4_4 || glType === gl.UNSIGNED_SHORT_5_5_5_1 || glType === gl.UNSIGNED_SHORT_5_6_5 || glType === gl.UNSIGNED_SHORT || glType === gl.HALF_FLOAT) bytesPerComponent = 2; if (glType === gl.UNSIGNED_INT || glType === gl.FLOAT) bytesPerComponent = 4; if (glFormat === gl.RGBA) return bytesPerComponent * 4; if (glFormat === gl.RGB) return bytesPerComponent * 3; if (glFormat === gl.ALPHA) return bytesPerComponent; } } var _default = exports.default = WebGLTextureUtils;