(function (global, factory) { if (typeof define === "function" && define.amd) { define(["exports", "./constants.js", "three", "./WebGPUTextureUtils.js"], factory); } else if (typeof exports !== "undefined") { factory(exports, require("./constants.js"), require("three"), require("./WebGPUTextureUtils.js")); } else { var mod = { exports: {} }; factory(mod.exports, global.constants, global.three, global.WebGPUTextureUtils); global.WebGPUTextures = mod.exports; } })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports, _constants, _three, _WebGPUTextureUtils) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; _WebGPUTextureUtils = _interopRequireDefault(_WebGPUTextureUtils); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } var WebGPUTextures = /*#__PURE__*/function () { function WebGPUTextures(device, properties, info) { _classCallCheck(this, WebGPUTextures); this.device = device; this.properties = properties; this.info = info; this.defaultTexture = null; this.defaultCubeTexture = null; this.defaultSampler = null; this.samplerCache = new Map(); this.utils = null; } _createClass(WebGPUTextures, [{ key: "getDefaultSampler", value: function getDefaultSampler() { if (this.defaultSampler === null) { this.defaultSampler = this.device.createSampler({}); } return this.defaultSampler; } }, { key: "getDefaultTexture", value: function getDefaultTexture() { if (this.defaultTexture === null) { var texture = new _three.Texture(); texture.minFilter = _three.NearestFilter; texture.magFilter = _three.NearestFilter; this._uploadTexture(texture); this.defaultTexture = this.getTextureGPU(texture); } return this.defaultTexture; } }, { key: "getDefaultCubeTexture", value: function getDefaultCubeTexture() { if (this.defaultCubeTexture === null) { var texture = new _three.CubeTexture(); texture.minFilter = _three.NearestFilter; texture.magFilter = _three.NearestFilter; this._uploadTexture(texture); this.defaultCubeTexture = this.getTextureGPU(texture); } return this.defaultCubeTexture; } }, { key: "getTextureGPU", value: function getTextureGPU(texture) { var textureProperties = this.properties.get(texture); return textureProperties.textureGPU; } }, { key: "getSampler", value: function getSampler(texture) { var textureProperties = this.properties.get(texture); return textureProperties.samplerGPU; } }, { key: "updateTexture", value: function updateTexture(texture) { var needsUpdate = false; var textureProperties = this.properties.get(texture); if (texture.version > 0 && textureProperties.version !== texture.version) { var image = texture.image; if (image === undefined) { console.warn('THREE.WebGPURenderer: Texture marked for update but image is undefined.'); } else if (image.complete === false) { console.warn('THREE.WebGPURenderer: Texture marked for update but image is incomplete.'); } else { // texture init if (textureProperties.initialized === undefined) { textureProperties.initialized = true; var disposeCallback = onTextureDispose.bind(this); textureProperties.disposeCallback = disposeCallback; texture.addEventListener('dispose', disposeCallback); this.info.memory.textures++; } // needsUpdate = this._uploadTexture(texture); } } // if the texture is used for RTT, it's necessary to init it once so the binding // group's resource definition points to the respective GPUTexture if (textureProperties.initializedRTT === false) { textureProperties.initializedRTT = true; needsUpdate = true; } return needsUpdate; } }, { key: "updateSampler", value: function updateSampler(texture) { var array = []; array.push(texture.wrapS); array.push(texture.wrapT); array.push(texture.wrapR); array.push(texture.magFilter); array.push(texture.minFilter); array.push(texture.anisotropy); var key = array.join(); var samplerGPU = this.samplerCache.get(key); if (samplerGPU === undefined) { samplerGPU = this.device.createSampler({ addressModeU: this._convertAddressMode(texture.wrapS), addressModeV: this._convertAddressMode(texture.wrapT), addressModeW: this._convertAddressMode(texture.wrapR), magFilter: this._convertFilterMode(texture.magFilter), minFilter: this._convertFilterMode(texture.minFilter), mipmapFilter: this._convertFilterMode(texture.minFilter), maxAnisotropy: texture.anisotropy }); this.samplerCache.set(key, samplerGPU); } var textureProperties = this.properties.get(texture); textureProperties.samplerGPU = samplerGPU; } }, { key: "initRenderTarget", value: function initRenderTarget(renderTarget) { var properties = this.properties; var renderTargetProperties = properties.get(renderTarget); if (renderTargetProperties.initialized === undefined) { var device = this.device; var width = renderTarget.width; var height = renderTarget.height; var colorTextureFormat = this._getFormat(renderTarget.texture); var colorTextureGPU = device.createTexture({ size: { width: width, height: height, depthOrArrayLayers: 1 }, format: colorTextureFormat, usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING }); this.info.memory.textures++; renderTargetProperties.colorTextureGPU = colorTextureGPU; renderTargetProperties.colorTextureFormat = colorTextureFormat; // When the ".texture" or ".depthTexture" property of a render target is used as a map, // the renderer has to find the respective GPUTexture objects to setup the bind groups. // Since it's not possible to see just from a texture object whether it belongs to a render // target or not, we need the initializedRTT flag. var textureProperties = properties.get(renderTarget.texture); textureProperties.textureGPU = colorTextureGPU; textureProperties.initializedRTT = false; if (renderTarget.depthBuffer === true) { var depthTextureFormat = _constants.GPUTextureFormat.Depth24PlusStencil8; // @TODO: Make configurable var depthTextureGPU = device.createTexture({ size: { width: width, height: height, depthOrArrayLayers: 1 }, format: depthTextureFormat, usage: GPUTextureUsage.RENDER_ATTACHMENT }); this.info.memory.textures++; renderTargetProperties.depthTextureGPU = depthTextureGPU; renderTargetProperties.depthTextureFormat = depthTextureFormat; if (renderTarget.depthTexture !== null) { var depthTextureProperties = properties.get(renderTarget.depthTexture); depthTextureProperties.textureGPU = depthTextureGPU; depthTextureProperties.initializedRTT = false; } } // var disposeCallback = onRenderTargetDispose.bind(this); renderTargetProperties.disposeCallback = disposeCallback; renderTarget.addEventListener('dispose', disposeCallback); // renderTargetProperties.initialized = true; } } }, { key: "dispose", value: function dispose() { this.samplerCache.clear(); } }, { key: "_convertAddressMode", value: function _convertAddressMode(value) { var addressMode = _constants.GPUAddressMode.ClampToEdge; if (value === _three.RepeatWrapping) { addressMode = _constants.GPUAddressMode.Repeat; } else if (value === _three.MirroredRepeatWrapping) { addressMode = _constants.GPUAddressMode.MirrorRepeat; } return addressMode; } }, { key: "_convertFilterMode", value: function _convertFilterMode(value) { var filterMode = _constants.GPUFilterMode.Linear; if (value === _three.NearestFilter || value === _three.NearestMipmapNearestFilter || value === _three.NearestMipmapLinearFilter) { filterMode = _constants.GPUFilterMode.Nearest; } return filterMode; } }, { key: "_uploadTexture", value: function _uploadTexture(texture) { var _this = this; var needsUpdate = false; var device = this.device; var image = texture.image; var textureProperties = this.properties.get(texture); var _this$_getSize = this._getSize(texture), width = _this$_getSize.width, height = _this$_getSize.height, depth = _this$_getSize.depth; var needsMipmaps = this._needsMipmaps(texture); var dimension = this._getDimension(texture); var mipLevelCount = this._getMipLevelCount(texture, width, height, needsMipmaps); var format = this._getFormat(texture); var usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST; if (needsMipmaps === true) { // current mipmap generation requires RENDER_ATTACHMENT usage |= GPUTextureUsage.RENDER_ATTACHMENT; } var textureGPUDescriptor = { size: { width: width, height: height, depthOrArrayLayers: depth }, mipLevelCount: mipLevelCount, sampleCount: 1, dimension: dimension, format: format, usage: usage }; // texture creation var textureGPU = textureProperties.textureGPU; if (textureGPU === undefined) { textureGPU = device.createTexture(textureGPUDescriptor); textureProperties.textureGPU = textureGPU; needsUpdate = true; } // transfer texture data if (texture.isDataTexture || texture.isDataTexture2DArray || texture.isDataTexture3D) { this._copyBufferToTexture(image, format, textureGPU); if (needsMipmaps === true) this._generateMipmaps(textureGPU, textureGPUDescriptor); } else if (texture.isCompressedTexture) { this._copyCompressedBufferToTexture(texture.mipmaps, format, textureGPU); } else if (texture.isCubeTexture) { this._copyCubeMapToTexture(image, texture, textureGPU); } else { if (image !== undefined) { // assume HTMLImageElement, HTMLCanvasElement or ImageBitmap this._getImageBitmap(image, texture).then(function (imageBitmap) { _this._copyExternalImageToTexture(imageBitmap, textureGPU); if (needsMipmaps === true) _this._generateMipmaps(textureGPU, textureGPUDescriptor); }); } } textureProperties.version = texture.version; return needsUpdate; } }, { key: "_copyBufferToTexture", value: function _copyBufferToTexture(image, format, textureGPU) { // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() // @TODO: Consider to support valid buffer layouts with other formats like RGB var data = image.data; var bytesPerTexel = this._getBytesPerTexel(format); var bytesPerRow = Math.ceil(image.width * bytesPerTexel / 256) * 256; this.device.queue.writeTexture({ texture: textureGPU, mipLevel: 0 }, data, { offset: 0, bytesPerRow: bytesPerRow }, { width: image.width, height: image.height, depthOrArrayLayers: image.depth !== undefined ? image.depth : 1 }); } }, { key: "_copyCubeMapToTexture", value: function _copyCubeMapToTexture(images, texture, textureGPU) { var _this2 = this; var _loop = function _loop(i) { var image = images[i]; _this2._getImageBitmap(image, texture).then(function (imageBitmap) { _this2._copyExternalImageToTexture(imageBitmap, textureGPU, { x: 0, y: 0, z: i }); }); }; for (var i = 0; i < images.length; i++) { _loop(i); } } }, { key: "_copyExternalImageToTexture", value: function _copyExternalImageToTexture(image, textureGPU) { var origin = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : { x: 0, y: 0, z: 0 }; this.device.queue.copyExternalImageToTexture({ source: image }, { texture: textureGPU, mipLevel: 0, origin: origin }, { width: image.width, height: image.height, depthOrArrayLayers: 1 }); } }, { key: "_copyCompressedBufferToTexture", value: function _copyCompressedBufferToTexture(mipmaps, format, textureGPU) { // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() var blockData = this._getBlockData(format); for (var i = 0; i < mipmaps.length; i++) { var mipmap = mipmaps[i]; var width = mipmap.width; var height = mipmap.height; var bytesPerRow = Math.ceil(width / blockData.width) * blockData.byteLength; this.device.queue.writeTexture({ texture: textureGPU, mipLevel: i }, mipmap.data, { offset: 0, bytesPerRow: bytesPerRow }, { width: Math.ceil(width / blockData.width) * blockData.width, height: Math.ceil(height / blockData.width) * blockData.width, depthOrArrayLayers: 1 }); } } }, { key: "_generateMipmaps", value: function _generateMipmaps(textureGPU, textureGPUDescriptor) { if (this.utils === null) { this.utils = new _WebGPUTextureUtils.default(this.device); // only create this helper if necessary } this.utils.generateMipmaps(textureGPU, textureGPUDescriptor); } }, { key: "_getBlockData", value: function _getBlockData(format) { // this method is only relevant for compressed texture formats if (format === _constants.GPUTextureFormat.BC1RGBAUnorm || format === _constants.GPUTextureFormat.BC1RGBAUnormSRGB) return { byteLength: 8, width: 4, height: 4 }; // DXT1 if (format === _constants.GPUTextureFormat.BC2RGBAUnorm || format === _constants.GPUTextureFormat.BC2RGBAUnormSRGB) return { byteLength: 16, width: 4, height: 4 }; // DXT3 if (format === _constants.GPUTextureFormat.BC3RGBAUnorm || format === _constants.GPUTextureFormat.BC3RGBAUnormSRGB) return { byteLength: 16, width: 4, height: 4 }; // DXT5 if (format === _constants.GPUTextureFormat.BC4RUnorm || format === _constants.GPUTextureFormat.BC4RSNorm) return { byteLength: 8, width: 4, height: 4 }; // RGTC1 if (format === _constants.GPUTextureFormat.BC5RGUnorm || format === _constants.GPUTextureFormat.BC5RGSnorm) return { byteLength: 16, width: 4, height: 4 }; // RGTC2 if (format === _constants.GPUTextureFormat.BC6HRGBUFloat || format === _constants.GPUTextureFormat.BC6HRGBFloat) return { byteLength: 16, width: 4, height: 4 }; // BPTC (float) if (format === _constants.GPUTextureFormat.BC7RGBAUnorm || format === _constants.GPUTextureFormat.BC7RGBAUnormSRGB) return { byteLength: 16, width: 4, height: 4 }; // BPTC (unorm) } }, { key: "_getBytesPerTexel", value: function _getBytesPerTexel(format) { if (format === _constants.GPUTextureFormat.R8Unorm) return 1; if (format === _constants.GPUTextureFormat.R16Float) return 2; if (format === _constants.GPUTextureFormat.RG8Unorm) return 2; if (format === _constants.GPUTextureFormat.RG16Float) return 4; if (format === _constants.GPUTextureFormat.R32Float) return 4; if (format === _constants.GPUTextureFormat.RGBA8Unorm || format === _constants.GPUTextureFormat.RGBA8UnormSRGB) return 4; if (format === _constants.GPUTextureFormat.RG32Float) return 8; if (format === _constants.GPUTextureFormat.RGBA16Float) return 8; if (format === _constants.GPUTextureFormat.RGBA32Float) return 16; } }, { key: "_getDimension", value: function _getDimension(texture) { var dimension; if (texture.isDataTexture3D) { dimension = _constants.GPUTextureDimension.ThreeD; } else { dimension = _constants.GPUTextureDimension.TwoD; } return dimension; } }, { key: "_getFormat", value: function _getFormat(texture) { var format = texture.format; var type = texture.type; var encoding = texture.encoding; var formatGPU; switch (format) { case _three.RGBA_S3TC_DXT1_Format: formatGPU = encoding === _three.sRGBEncoding ? _constants.GPUTextureFormat.BC1RGBAUnormSRGB : _constants.GPUTextureFormat.BC1RGBAUnorm; break; case _three.RGBA_S3TC_DXT3_Format: formatGPU = encoding === _three.sRGBEncoding ? _constants.GPUTextureFormat.BC2RGBAUnormSRGB : _constants.GPUTextureFormat.BC2RGBAUnorm; break; case _three.RGBA_S3TC_DXT5_Format: formatGPU = encoding === _three.sRGBEncoding ? _constants.GPUTextureFormat.BC3RGBAUnormSRGB : _constants.GPUTextureFormat.BC3RGBAUnorm; break; case _three.RGBFormat: case _three.RGBAFormat: switch (type) { case _three.UnsignedByteType: formatGPU = encoding === _three.sRGBEncoding ? _constants.GPUTextureFormat.RGBA8UnormSRGB : _constants.GPUTextureFormat.RGBA8Unorm; break; case _three.HalfFloatType: formatGPU = _constants.GPUTextureFormat.RGBA16Float; break; case _three.FloatType: formatGPU = _constants.GPUTextureFormat.RGBA32Float; break; default: console.error('WebGPURenderer: Unsupported texture type with RGBAFormat.', type); } break; case _three.RedFormat: switch (type) { case _three.UnsignedByteType: formatGPU = _constants.GPUTextureFormat.R8Unorm; break; case _three.HalfFloatType: formatGPU = _constants.GPUTextureFormat.R16Float; break; case _three.FloatType: formatGPU = _constants.GPUTextureFormat.R32Float; break; default: console.error('WebGPURenderer: Unsupported texture type with RedFormat.', type); } break; case _three.RGFormat: switch (type) { case _three.UnsignedByteType: formatGPU = _constants.GPUTextureFormat.RG8Unorm; break; case _three.HalfFloatType: formatGPU = _constants.GPUTextureFormat.RG16Float; break; case _three.FloatType: formatGPU = _constants.GPUTextureFormat.RG32Float; break; default: console.error('WebGPURenderer: Unsupported texture type with RGFormat.', type); } break; default: console.error('WebGPURenderer: Unsupported texture format.', format); } return formatGPU; } }, { key: "_getImageBitmap", value: function _getImageBitmap(image, texture) { var width = image.width; var height = image.height; if (typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement || typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement) { var options = {}; options.imageOrientation = texture.flipY === true ? 'flipY' : 'none'; options.premultiplyAlpha = texture.premultiplyAlpha === true ? 'premultiply' : 'default'; return createImageBitmap(image, 0, 0, width, height, options); } else { // assume ImageBitmap return Promise.resolve(image); } } }, { key: "_getMipLevelCount", value: function _getMipLevelCount(texture, width, height, needsMipmaps) { var mipLevelCount; if (texture.isCompressedTexture) { mipLevelCount = texture.mipmaps.length; } else if (needsMipmaps === true) { mipLevelCount = Math.floor(Math.log2(Math.max(width, height))) + 1; } else { mipLevelCount = 1; // a texture without mipmaps has a base mip (mipLevel 0) } return mipLevelCount; } }, { key: "_getSize", value: function _getSize(texture) { var image = texture.image; var width, height, depth; if (texture.isCubeTexture) { width = image.length > 0 ? image[0].width : 1; height = image.length > 0 ? image[0].height : 1; depth = 6; // one image for each side of the cube map } else if (image !== undefined) { width = image.width; height = image.height; depth = image.depth !== undefined ? image.depth : 1; } else { width = height = depth = 1; } return { width: width, height: height, depth: depth }; } }, { key: "_needsMipmaps", value: function _needsMipmaps(texture) { return texture.isCompressedTexture !== true && texture.generateMipmaps === true && texture.minFilter !== _three.NearestFilter && texture.minFilter !== _three.LinearFilter; } }]); return WebGPUTextures; }(); function onRenderTargetDispose(event) { var renderTarget = event.target; var properties = this.properties; var renderTargetProperties = properties.get(renderTarget); renderTarget.removeEventListener('dispose', renderTargetProperties.disposeCallback); renderTargetProperties.colorTextureGPU.destroy(); properties.remove(renderTarget.texture); this.info.memory.textures--; if (renderTarget.depthBuffer === true) { renderTargetProperties.depthTextureGPU.destroy(); this.info.memory.textures--; if (renderTarget.depthTexture !== null) { properties.remove(renderTarget.depthTexture); } } properties.remove(renderTarget); } function onTextureDispose(event) { var texture = event.target; var textureProperties = this.properties.get(texture); textureProperties.textureGPU.destroy(); texture.removeEventListener('dispose', textureProperties.disposeCallback); this.properties.remove(texture); this.info.memory.textures--; } var _default = WebGPUTextures; _exports.default = _default; });