// luma.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { assert, Texture, Framebuffer } from '@luma.gl/core'; import { GL } from '@luma.gl/constants'; import { getGLTypeFromTypedArray, getTypedArrayFromGLType } from "./typed-array-utils.js"; import { glFormatToComponents, glTypeToBytes } from "./format-utils.js"; /** * Copies data from a type or a Texture object into ArrayBuffer object. * App can provide targetPixelArray or have it auto allocated by this method * newly allocated by this method unless provided by app. * @deprecated Use CommandEncoder.copyTextureToBuffer and Buffer.read * @note Slow requires roundtrip to GPU * * @param source * @param options * @returns pixel array, */ export function readPixelsToArray(source, options) { const { sourceX = 0, sourceY = 0, sourceFormat = 6408, sourceAttachment = 36064 // TODO - support gl.readBuffer } = options || {}; let { target = null, // following parameters are auto deduced if not provided sourceWidth, sourceHeight, sourceType } = options || {}; const { framebuffer, deleteFramebuffer } = getFramebuffer(source); assert(framebuffer); const { gl, handle } = framebuffer; sourceWidth = sourceWidth || framebuffer.width; sourceHeight = sourceHeight || framebuffer.height; // TODO - Set and unset gl.readBuffer // if (sourceAttachment === GL.COLOR_ATTACHMENT0 && handle === null) { // sourceAttachment = GL.FRONT; // } const attachment = sourceAttachment - 36064; // assert(attachments[sourceAttachment]); // Deduce the type from color attachment if not provided. sourceType = sourceType || framebuffer.colorAttachments[attachment]?.texture?.type || 5121; // Deduce type and allocated pixelArray if needed target = getPixelArray(target, sourceType, sourceFormat, sourceWidth, sourceHeight); // Pixel array available, if necessary, deduce type from it. sourceType = sourceType || getGLTypeFromTypedArray(target); const prevHandle = gl.bindFramebuffer(36160, handle); gl.readPixels(sourceX, sourceY, sourceWidth, sourceHeight, sourceFormat, sourceType, target); // @ts-expect-error gl.bindFramebuffer(36160, prevHandle || null); if (deleteFramebuffer) { framebuffer.destroy(); } return target; } /** * Copies data from a Framebuffer or a Texture object into a Buffer object. * NOTE: doesn't wait for copy to be complete, it programs GPU to perform a DMA transffer. * @deprecated Use CommandEncoder * @param source * @param options */ export function readPixelsToBuffer(source, options) { const { target, sourceX = 0, sourceY = 0, sourceFormat = 6408, targetByteOffset = 0 } = options || {}; // following parameters are auto deduced if not provided let { sourceWidth, sourceHeight, sourceType } = options || {}; const { framebuffer, deleteFramebuffer } = getFramebuffer(source); assert(framebuffer); sourceWidth = sourceWidth || framebuffer.width; sourceHeight = sourceHeight || framebuffer.height; // Asynchronous read (PIXEL_PACK_BUFFER) is WebGL2 only feature const webglFramebuffer = framebuffer; // deduce type if not available. sourceType = sourceType || 5121; let webglBufferTarget = target; if (!webglBufferTarget) { // Create new buffer with enough size const components = glFormatToComponents(sourceFormat); const byteCount = glTypeToBytes(sourceType); const byteLength = targetByteOffset + sourceWidth * sourceHeight * components * byteCount; webglBufferTarget = webglFramebuffer.device.createBuffer({ byteLength }); } // TODO(donmccurdy): Do we have tests to confirm this is working? const commandEncoder = source.device.createCommandEncoder(); commandEncoder.copyTextureToBuffer({ source: source, width: sourceWidth, height: sourceHeight, origin: [sourceX, sourceY], destination: webglBufferTarget, byteOffset: targetByteOffset }); commandEncoder.destroy(); if (deleteFramebuffer) { framebuffer.destroy(); } return webglBufferTarget; } /** * Copy a rectangle from a Framebuffer or Texture object into a texture (at an offset) * @deprecated Use CommandEncoder */ // eslint-disable-next-line complexity, max-statements export function copyToTexture(source, target, options) { const { sourceX = 0, sourceY = 0, // attachment = GL.COLOR_ATTACHMENT0, // TODO - support gl.readBuffer targetMipmaplevel = 0, targetInternalFormat = 6408 } = options || {}; let { targetX, targetY, targetZ, width, // defaults to target width height // defaults to target height } = options || {}; const { framebuffer, deleteFramebuffer } = getFramebuffer(source); assert(framebuffer); const webglFramebuffer = framebuffer; const { device, handle } = webglFramebuffer; const isSubCopy = typeof targetX !== 'undefined' || typeof targetY !== 'undefined' || typeof targetZ !== 'undefined'; targetX = targetX || 0; targetY = targetY || 0; targetZ = targetZ || 0; const prevHandle = device.gl.bindFramebuffer(36160, handle); // TODO - support gl.readBuffer (WebGL2 only) // const prevBuffer = gl.readBuffer(attachment); assert(target); let texture = null; let textureTarget; if (target instanceof Texture) { texture = target; width = Number.isFinite(width) ? width : texture.width; height = Number.isFinite(height) ? height : texture.height; texture.bind(0); textureTarget = texture.target; } else { textureTarget = target; } if (!isSubCopy) { device.gl.copyTexImage2D(textureTarget, targetMipmaplevel, targetInternalFormat, sourceX, sourceY, width, height, 0 /* border must be 0 */); } else { switch (textureTarget) { case 3553: case 34067: device.gl.copyTexSubImage2D(textureTarget, targetMipmaplevel, targetX, targetY, sourceX, sourceY, width, height); break; case 35866: case 32879: device.gl.copyTexSubImage3D(textureTarget, targetMipmaplevel, targetX, targetY, targetZ, sourceX, sourceY, width, height); break; default: } } if (texture) { texture.unbind(); } // @ts-expect-error device.gl.bindFramebuffer(36160, prevHandle || null); if (deleteFramebuffer) { framebuffer.destroy(); } return texture; } function getFramebuffer(source) { if (!(source instanceof Framebuffer)) { return { framebuffer: toFramebuffer(source), deleteFramebuffer: true }; } return { framebuffer: source, deleteFramebuffer: false }; } /** * Wraps a given texture into a framebuffer object, that can be further used * to read data from the texture object. */ export function toFramebuffer(texture, props) { const { device, width, height, id } = texture; const framebuffer = device.createFramebuffer({ ...props, id: `framebuffer-for-${id}`, width, height, colorAttachments: [texture] }); return framebuffer; } function getPixelArray(pixelArray, type, format, width, height) { if (pixelArray) { return pixelArray; } // Allocate pixel array if not already available, using supplied type type = type || 5121; const ArrayType = getTypedArrayFromGLType(type, { clamped: false }); const components = glFormatToComponents(format); // TODO - check for composite type (components = 1). return new ArrayType(width * height * components); }