UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

550 lines (549 loc) 14.5 kB
import { TRACEID_TEXTURE_ALLOC, TRACEID_VRAM_TEXTURE } from "../../core/constants.js"; import { math } from "../../core/math/math.js"; import { isCompressedPixelFormat, getPixelFormatArrayType, ADDRESS_REPEAT, ADDRESS_CLAMP_TO_EDGE, FILTER_LINEAR, FILTER_LINEAR_MIPMAP_LINEAR, FUNC_LESS, PIXELFORMAT_RGBA8, TEXHINT_SHADOWMAP, TEXHINT_ASSET, TEXHINT_LIGHTMAP, TEXTURELOCK_WRITE, TEXTUREPROJECTION_NONE, TEXTUREPROJECTION_CUBE, TEXTURETYPE_DEFAULT, TEXTURETYPE_RGBM, TEXTURETYPE_RGBE, TEXTURETYPE_RGBP, isIntegerPixelFormat, FILTER_NEAREST, TEXTURELOCK_NONE, TEXTURELOCK_READ, TEXPROPERTY_MIN_FILTER, TEXPROPERTY_MAG_FILTER, TEXPROPERTY_ADDRESS_U, TEXPROPERTY_ADDRESS_V, TEXPROPERTY_ADDRESS_W, TEXPROPERTY_COMPARE_ON_READ, TEXPROPERTY_COMPARE_FUNC, TEXPROPERTY_ANISOTROPY, TEXPROPERTY_ALL, requiresManualGamma, pixelFormatInfo, isSrgbPixelFormat, pixelFormatLinearToGamma, pixelFormatGammaToLinear } from "./constants.js"; import { TextureUtils } from "./texture-utils.js"; import { TextureView } from "./texture-view.js"; let id = 0; class Texture { static createDataTexture2D(graphicsDevice, name, width, height, format, levels) { return new Texture(graphicsDevice, { name, width, height, format, mipmaps: false, minFilter: FILTER_NEAREST, magFilter: FILTER_NEAREST, addressU: ADDRESS_CLAMP_TO_EDGE, addressV: ADDRESS_CLAMP_TO_EDGE, levels }); } name; _gpuSize = 0; releaseSourceAfterUpload = false; id = id++; _invalid = false; _lockedLevel = -1; _lockedMode = TEXTURELOCK_NONE; renderVersionDirty = 0; _storage = false; _numLevels = 0; _numLevelsRequested; constructor(graphicsDevice, options = {}) { this.device = graphicsDevice; this.name = options.name ?? ""; this._width = Math.floor(options.width ?? 4); this._height = Math.floor(options.height ?? 4); this._format = options.format ?? PIXELFORMAT_RGBA8; this._compressed = isCompressedPixelFormat(this._format); this._integerFormat = isIntegerPixelFormat(this._format); if (this._integerFormat) { options.minFilter = FILTER_NEAREST; options.magFilter = FILTER_NEAREST; } this._volume = options.volume ?? false; this._depth = Math.floor(options.depth ?? 1); this._arrayLength = Math.floor(options.arrayLength ?? 0); this._storage = options.storage ?? false; this._cubemap = options.cubemap ?? false; this._flipY = options.flipY ?? false; this._premultiplyAlpha = options.premultiplyAlpha ?? false; this._mipmaps = options.mipmaps ?? true; this._numLevelsRequested = options.numLevels; if (options.numLevels !== void 0) { this._numLevels = options.numLevels; } this._updateNumLevels(); this._minFilter = options.minFilter ?? FILTER_LINEAR_MIPMAP_LINEAR; this._magFilter = options.magFilter ?? FILTER_LINEAR; this._anisotropy = options.anisotropy ?? 1; this._addressU = options.addressU ?? ADDRESS_REPEAT; this._addressV = options.addressV ?? ADDRESS_REPEAT; this._addressW = options.addressW ?? ADDRESS_REPEAT; this._compareOnRead = options.compareOnRead ?? false; this._compareFunc = options.compareFunc ?? FUNC_LESS; this._type = options.type ?? TEXTURETYPE_DEFAULT; this.projection = TEXTUREPROJECTION_NONE; if (this._cubemap) { this.projection = TEXTUREPROJECTION_CUBE; } else if (options.projection && options.projection !== TEXTUREPROJECTION_CUBE) { this.projection = options.projection; } this._levels = options.levels; const upload = !!options.levels; if (!this._levels) { this._clearLevels(); } this.recreateImpl(upload); } destroy() { const device = this.device; if (device) { device.onTextureDestroyed(this); this.impl.destroy(device); this.adjustVramSizeTracking(device._vram, -this._gpuSize); if (this.releaseSourceAfterUpload) { this.releaseImageSources(); } this._levels = null; this.device = null; } } releaseImageSources() { this.releaseSourceAfterUpload = false; if (typeof ImageBitmap === "undefined" || !this._levels) { return; } for (let i = 0; i < this._levels.length; i++) { const level = this._levels[i]; if (level instanceof ImageBitmap) { level.close(); this._levels[i] = null; } else if (Array.isArray(level)) { for (let j = 0; j < level.length; j++) { if (level[j] instanceof ImageBitmap) { level[j].close(); level[j] = null; } } } } } setReleaseSourceAfterUpload() { this.releaseSourceAfterUpload = true; if (!this._needsUpload && !this._needsMipmapsUpload) { this.releaseImageSources(); } } recreateImpl(upload = true) { const { device } = this; this.impl?.destroy(device); this.impl = null; this.impl = device.createTextureImpl(this); this.dirtyAll(); if (upload) { this.upload(); } } _clearLevels() { this._levels = this._cubemap ? [[null, null, null, null, null, null]] : [null]; } resize(width, height, depth = 1) { if (this.width !== width || this.height !== height || this.depth !== depth) { const device = this.device; this.adjustVramSizeTracking(device._vram, -this._gpuSize); this._gpuSize = 0; this.impl.destroy(device); this._clearLevels(); this._width = Math.floor(width); this._height = Math.floor(height); this._depth = Math.floor(depth); this._updateNumLevels(); this.impl = device.createTextureImpl(this); this.dirtyAll(); } } loseContext() { this.impl.loseContext(); this.dirtyAll(); } adjustVramSizeTracking(vram, size) { vram.tex += size; } propertyChanged(flag) { this.impl.propertyChanged(flag); this.renderVersionDirty = this.device.renderVersion; } _updateNumLevels() { const maxLevels = this.mipmaps ? TextureUtils.calcMipLevelsCount(this.width, this.height) : 1; const requestedLevels = this._numLevelsRequested; if (requestedLevels !== void 0 && requestedLevels > maxLevels) { } this._numLevels = Math.min(requestedLevels ?? maxLevels, maxLevels); this._mipmaps = this._numLevels > 1; } get lockedMode() { return this._lockedMode; } set minFilter(v) { if (this._minFilter !== v) { if (isIntegerPixelFormat(this._format)) { } else { this._minFilter = v; this.propertyChanged(TEXPROPERTY_MIN_FILTER); } } } get minFilter() { return this._minFilter; } set magFilter(v) { if (this._magFilter !== v) { if (isIntegerPixelFormat(this._format)) { } else { this._magFilter = v; this.propertyChanged(TEXPROPERTY_MAG_FILTER); } } } get magFilter() { return this._magFilter; } set addressU(v) { if (this._addressU !== v) { this._addressU = v; this.propertyChanged(TEXPROPERTY_ADDRESS_U); } } get addressU() { return this._addressU; } set addressV(v) { if (this._addressV !== v) { this._addressV = v; this.propertyChanged(TEXPROPERTY_ADDRESS_V); } } get addressV() { return this._addressV; } set addressW(addressW) { if (!this._volume) { return; } if (addressW !== this._addressW) { this._addressW = addressW; this.propertyChanged(TEXPROPERTY_ADDRESS_W); } } get addressW() { return this._addressW; } set compareOnRead(v) { if (this._compareOnRead !== v) { this._compareOnRead = v; this.propertyChanged(TEXPROPERTY_COMPARE_ON_READ); } } get compareOnRead() { return this._compareOnRead; } set compareFunc(v) { if (this._compareFunc !== v) { this._compareFunc = v; this.propertyChanged(TEXPROPERTY_COMPARE_FUNC); } } get compareFunc() { return this._compareFunc; } set anisotropy(v) { if (this._anisotropy !== v) { this._anisotropy = v; this.propertyChanged(TEXPROPERTY_ANISOTROPY); } } get anisotropy() { return this._anisotropy; } set mipmaps(v) { if (this._mipmaps !== v) { if (this.device.isWebGPU) { } else if (isIntegerPixelFormat(this._format)) { } else { const oldMipmaps = this._mipmaps; const oldNumLevels = this._numLevels; this._mipmaps = v; this._updateNumLevels(); if (this.array && this._numLevels !== oldNumLevels) { this.recreateImpl(); } else if (this._mipmaps !== oldMipmaps) { this.propertyChanged(TEXPROPERTY_MIN_FILTER); if (this._mipmaps) { this._needsMipmapsUpload = true; this.device?.texturesToUpload?.add(this); } else { this._needsMipmapsUpload = false; } } } } } get mipmaps() { return this._mipmaps; } get numLevels() { return this._numLevels; } get storage() { return this._storage; } get width() { return this._width; } get height() { return this._height; } get depth() { return this._depth; } get format() { return this._format; } get cubemap() { return this._cubemap; } get gpuSize() { const mips = this.pot && this._mipmaps && !(this._compressed && this._levels.length === 1); return TextureUtils.calcGpuSize(this._width, this._height, this._depth, this._format, mips, this._cubemap); } get array() { return this._arrayLength > 0; } get arrayLength() { return this._arrayLength; } get volume() { return this._volume; } set type(value) { if (this._type !== value) { this._type = value; this.device._shadersDirty = true; } } get type() { return this._type; } set srgb(value) { const currentSrgb = isSrgbPixelFormat(this.format); if (value !== currentSrgb) { if (value) { const srgbFormat = pixelFormatLinearToGamma(this.format); if (this._format !== srgbFormat) { this._format = srgbFormat; this.recreateImpl(); this.device._shadersDirty = true; } } else { const linearFormat = pixelFormatGammaToLinear(this.format); if (this._format !== linearFormat) { this._format = linearFormat; this.recreateImpl(); this.device._shadersDirty = true; } } } } get srgb() { return isSrgbPixelFormat(this.format); } set flipY(flipY) { if (this._flipY !== flipY) { this._flipY = flipY; this.markForUpload(); } } get flipY() { return this._flipY; } set premultiplyAlpha(premultiplyAlpha) { if (this._premultiplyAlpha !== premultiplyAlpha) { this._premultiplyAlpha = premultiplyAlpha; this.markForUpload(); } } get premultiplyAlpha() { return this._premultiplyAlpha; } get pot() { return math.powerOfTwo(this._width) && math.powerOfTwo(this._height); } // get the texture's encoding type get encoding() { switch (this.type) { case TEXTURETYPE_RGBM: return "rgbm"; case TEXTURETYPE_RGBE: return "rgbe"; case TEXTURETYPE_RGBP: return "rgbp"; } return requiresManualGamma(this.format) ? "srgb" : "linear"; } // Force a full resubmission of the texture to the GPU (used on a context restore event) dirtyAll() { this._levelsUpdated = this._cubemap ? [[true, true, true, true, true, true]] : [true]; this.markForUpload(); this._needsMipmapsUpload = this._mipmaps; this._mipmapsUploaded = false; this.propertyChanged(TEXPROPERTY_ALL); } lock(options = {}) { options.level ?? (options.level = 0); options.face ?? (options.face = 0); options.mode ?? (options.mode = TEXTURELOCK_WRITE); this._lockedMode = options.mode; this._lockedLevel = options.level; const levels = this.cubemap ? this._levels[options.face] : this._levels; if (!levels[options.level]) { const width = Math.max(1, this._width >> options.level); const height = Math.max(1, this._height >> options.level); const depth = Math.max(1, this._depth >> options.level); const data = new ArrayBuffer(TextureUtils.calcLevelGpuSize(width, height, depth, this._format)); levels[options.level] = new (getPixelFormatArrayType(this._format))(data); } return levels[options.level]; } setSource(source, mipLevel = 0) { if (this.device._isHTMLElementInterface(source)) { if (!this.device.supportsHtmlTextures) { return; } if (this._cubemap || this._volume) { return; } } let invalid = false; let width, height; if (this._cubemap) { if (source[0]) { width = source[0].width || 0; height = source[0].height || 0; for (let i = 0; i < 6; i++) { const face = source[i]; if (!face || // face is missing face.width !== width || // face is different width face.height !== height || // face is different height !this.device._isBrowserInterface(face)) { invalid = true; break; } } } else { invalid = true; } if (!invalid) { for (let i = 0; i < 6; i++) { if (this._levels[mipLevel][i] !== source[i]) { this._levelsUpdated[mipLevel][i] = true; } } } } else { if (!this.device._isBrowserInterface(source)) { invalid = true; } if (!invalid) { if (source !== this._levels[mipLevel]) { this._levelsUpdated[mipLevel] = true; } if (source instanceof HTMLVideoElement) { width = source.videoWidth; height = source.videoHeight; } else if (this.device._isHTMLElementInterface(source)) { const rect = source.getBoundingClientRect(); width = Math.floor(rect.width) || 1; height = Math.floor(rect.height) || 1; } else { width = source.width; height = source.height; } } } if (invalid) { this._width = 4; this._height = 4; if (this._cubemap) { for (let i = 0; i < 6; i++) { this._levels[mipLevel][i] = null; this._levelsUpdated[mipLevel][i] = true; } } else { this._levels[mipLevel] = null; this._levelsUpdated[mipLevel] = true; } } else { if (mipLevel === 0) { this._width = width; this._height = height; } this._levels[mipLevel] = source; } if (this._invalid !== invalid || !invalid) { this._invalid = invalid; this.upload(); } } getSource(mipLevel = 0) { return this._levels[mipLevel]; } unlock() { if (this._lockedMode === TEXTURELOCK_NONE) { } if (this._lockedMode === TEXTURELOCK_WRITE) { this.upload(); } this._lockedLevel = -1; this._lockedMode = TEXTURELOCK_NONE; } markForUpload() { this._needsUpload = true; this.device?.texturesToUpload?.add(this); } upload() { this.markForUpload(); this._needsMipmapsUpload = this._mipmaps; this.impl.uploadImmediate?.(this.device, this); } read(x, y, width, height, options = {}) { return this.impl.read?.(x, y, width, height, options); } write(x, y, width, height, data) { return this.impl.write?.(x, y, width, height, data); } getView(baseMipLevel = 0, mipLevelCount = 1, baseArrayLayer = 0, arrayLayerCount = 1) { return new TextureView(this, baseMipLevel, mipLevelCount, baseArrayLayer, arrayLayerCount); } } export { Texture };