UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

445 lines (442 loc) 12.8 kB
import { math } from '../../core/math/math.js'; import { TEXTURELOCK_NONE, PIXELFORMAT_RGBA8, isCompressedPixelFormat, isIntegerPixelFormat, FILTER_NEAREST, FILTER_LINEAR_MIPMAP_LINEAR, FILTER_LINEAR, ADDRESS_REPEAT, FUNC_LESS, TEXTURETYPE_DEFAULT, TEXTUREPROJECTION_NONE, TEXTUREPROJECTION_CUBE, TEXPROPERTY_MIN_FILTER, TEXPROPERTY_MAG_FILTER, TEXPROPERTY_ADDRESS_U, TEXPROPERTY_ADDRESS_V, TEXPROPERTY_ADDRESS_W, TEXPROPERTY_COMPARE_ON_READ, TEXPROPERTY_COMPARE_FUNC, TEXPROPERTY_ANISOTROPY, isSrgbPixelFormat, pixelFormatLinearToGamma, pixelFormatGammaToLinear, TEXTURETYPE_RGBP, TEXTURETYPE_RGBE, TEXTURETYPE_RGBM, requiresManualGamma, TEXPROPERTY_ALL, TEXTURELOCK_WRITE, getPixelFormatArrayType } from './constants.js'; import { TextureUtils } from './texture-utils.js'; let id = 0; class Texture { constructor(graphicsDevice, options = {}){ this._gpuSize = 0; this.id = id++; this._invalid = false; this._lockedLevel = -1; this._lockedMode = TEXTURELOCK_NONE; this.renderVersionDirty = 0; this._storage = false; this._numLevels = 0; 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 !== undefined) { this._numLevels = options.numLevels; } this._updateNumLevel(); 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); graphicsDevice.textures.push(this); } destroy() { const device = this.device; if (device) { const idx = device.textures.indexOf(this); if (idx !== -1) { device.textures.splice(idx, 1); } device.scope.removeValue(this); this.impl.destroy(device); this.adjustVramSizeTracking(device._vram, -this._gpuSize); this._levels = null; this.device = null; } } 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) { const device = this.device; this.adjustVramSizeTracking(device._vram, -this._gpuSize); this.impl.destroy(device); this._clearLevels(); this._width = Math.floor(width); this._height = Math.floor(height); this._depth = Math.floor(depth); this._updateNumLevel(); 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; } _updateNumLevel() { const maxLevels = this.mipmaps ? TextureUtils.calcMipLevelsCount(this.width, this.height) : 1; const requestedLevels = this._numLevelsRequested; 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 { this._mipmaps = v; } if (v) this._needsMipmapsUpload = true; } } 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._needsUpload = true; } } get flipY() { return this._flipY; } set premultiplyAlpha(premultiplyAlpha) { if (this._premultiplyAlpha !== premultiplyAlpha) { this._premultiplyAlpha = premultiplyAlpha; this._needsUpload = true; } } get premultiplyAlpha() { return this._premultiplyAlpha; } get pot() { return math.powerOfTwo(this._width) && math.powerOfTwo(this._height); } 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'; } dirtyAll() { this._levelsUpdated = this._cubemap ? [ [ true, true, true, true, true, true ] ] : [ true ]; this._needsUpload = true; this._needsMipmapsUpload = this._mipmaps; this._mipmapsUploaded = false; this.propertyChanged(TEXPROPERTY_ALL); } lock(options = {}) { options.level ??= 0; options.face ??= 0; 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] === null) { 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) { 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.width !== width || face.height !== 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 { 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; } upload() { this._needsUpload = true; 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); } } export { Texture };