/
githubmirror
/
pixijs
Обзор
Документация
Войти
/
githubmirror
/
pixijs
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
src/rendering/renderers/shared/texture/sources/TextureSource.ts
651 строка
21 KB
Zyie
feat: WebGPU-first RenderTarget attachment architecture & TextureView depth sampling (#12091)
08 июл 2026, 13:22
Не верифицирован
08 июл 2026, 13:22
f4b382b
Код
Авторство
О чём код?
import EventEmitter from 'eventemitter3'; import { isPow2 } from '../../../../../maths/misc/pow2'; import { definedProps } from '../../../../../scene/container/utils/definedProps'; import { uid } from '../../../../../utils/data/uid'; import { type GPUDataOwner } from '../../../../renderers/types'; import { type GlTexture } from '../../../gl/texture/GlTexture'; import { type GPUTextureGpuData } from '../../../gpu/texture/GpuTextureSystem'; import { type GCable, type GCData } from '../../GCSystem'; import { TextureStyle } from '../TextureStyle'; import type { BindResource } from '../../../gpu/shader/BindResource'; import type { ALPHA_MODES, SCALE_MODE, TEXTURE_DIMENSIONS, TEXTURE_FORMATS, TEXTURE_VIEW_DIMENSIONS, WRAP_MODE, } from '../const'; import type { TextureStyleOptions } from '../TextureStyle'; import type { TextureResourceOrOptions } from '../utils/textureFrom'; /** * options for creating a new TextureSource * @category rendering * @advanced */ export interface TextureSourceOptions<T extends Record<string, any> = any> extends TextureStyleOptions { /** * the resource that will be uploaded to the GPU. This is where we get our pixels from * eg an ImageBimt / Canvas / Video etc */ resource?: T; /** the pixel width of this texture source. This is the REAL pure number, not accounting resolution */ width?: number; /** the pixel height of this texture source. This is the REAL pure number, not accounting resolution */ height?: number; /** the resolution of the texture. */ resolution?: number; /** the format that the texture data has */ format?: TEXTURE_FORMATS; /** * Used by internal textures * @ignore */ sampleCount?: number; /** * Only really affects RenderTextures. * Should we use antialiasing for this texture. It will look better, but may impact performance as a * Blit operation will be required to resolve the texture. */ antialias?: boolean; /** how many dimensions does this texture have? currently v8 only supports 2d */ dimensions?: TEXTURE_DIMENSIONS; /** * How this texture is viewed/sampled by shaders. * * This aligns with WebGPU's `GPUTextureViewDescriptor.dimension`. For example, cube maps are typically stored as a * 2D texture with 6 array layers (`dimensions: '2d'`) but viewed as `viewDimension: 'cube'`. */ viewDimension?: TEXTURE_VIEW_DIMENSIONS; /** * The number of array layers for this texture source. * * This maps to WebGPU's `GPUTextureDescriptor.size.depthOrArrayLayers` and is used for array-backed textures * such as cube maps (6 layers). * @default 1 * @advanced */ arrayLayerCount?: number; /** The number of mip levels to generate for this texture. this is overridden if autoGenerateMipmaps is true */ mipLevelCount?: number; /** * Should we auto generate mipmaps for this texture? This will automatically generate mipmaps * for this texture when uploading to the GPU. Mipmapped textures take up more memory, but * can look better when scaled down. * * For performance reasons, it is recommended to NOT use this with RenderTextures, as they are often updated every frame. * If you do, make sure to call `updateMipmaps` after you update the texture. */ autoGenerateMipmaps?: boolean; /** the alpha mode of the texture */ alphaMode?: ALPHA_MODES; /** optional label, can be used for debugging */ label?: string; /** If true, the Garbage Collector will unload this texture if it is not used after a period of time */ autoGarbageCollect?: boolean; /** Used by RenderTexture.create to allow resizing. Not used by TextureSource itself. */ dynamic?: boolean; /** * Mark this texture as transient — its contents are scratch and do not need to persist * beyond a single render pass. When the WebGPU backend sees this: * * - It uses `storeOp: 'discard'` on the attachment at end-of-pass, skipping the writeback to DRAM. * - When the browser exposes `GPUTextureUsage.TRANSIENT_ATTACHMENT`, it adds that bit so the * driver can keep contents in tile memory on TBDR mobile GPUs and never allocate DRAM at all. * * Only safe when no later render pass needs to load the prior contents back * (`loadOp: 'load'` on a transient attachment is a spec violation, and discarding makes * loaded contents undefined even without the bit set). Pixi sets this internally for the * MSAA buffer attached to the canvas root, which is rendered as a single pass per frame. * Set it yourself only on textures you know follow the same single-pass-then-discard pattern. * @default false */ transient?: boolean; } /** * A TextureSource stores the information that represents an image. * All textures have require TextureSource, which contains information about the source. * Therefore you can have many textures all using a single TextureSource (eg a sprite sheet) * * This is an class is extended depending on the source of the texture. * Eg if you are using an an image as your resource, then an ImageSource is used. * @category rendering * @advanced */ export class TextureSource<T extends Record<string, any> = any> extends EventEmitter<{ change: BindResource; update: TextureSource; unload: TextureSource; destroy: TextureSource; resize: TextureSource; styleChange: TextureSource; updateMipmaps: TextureSource; error: Error; }> implements BindResource, GPUDataOwner, GCable { /** The default options used when creating a new TextureSource. override these to add your own defaults */ public static defaultOptions: TextureSourceOptions = { resolution: 1, format: 'bgra8unorm', alphaMode: 'premultiply-alpha-on-upload', dimensions: '2d', viewDimension: '2d', arrayLayerCount: 1, mipLevelCount: 1, autoGenerateMipmaps: false, sampleCount: 1, antialias: false, autoGarbageCollect: false, }; /** @internal */ public _gpuData: Record<number, GlTexture | GPUTextureGpuData> = Object.create(null); /** GC tracking data, undefined if not being tracked */ public _gcData?: GCData; /** @internal */ public _gcLastUsed = -1; /** unique id for this Texture source */ public readonly uid: number = uid('textureSource'); /** optional label, can be used for debugging */ public label: string; /** * The resource type used by this TextureSource. This is used by the bind groups to determine * how to handle this resource. * @internal */ public readonly _resourceType = 'textureSource'; /** * i unique resource id, used by the bind group systems. * This can change if the texture is resized or its resource changes * @internal */ public _resourceId = uid('resource'); /** * this is how the backends know how to upload this texture to the GPU * It changes depending on the resource type. Classes that extend TextureSource * should override this property. * @internal */ public uploadMethodId = 'unknown'; /** @internal */ public _resolution = 1; /** the pixel width of this texture source. This is the REAL pure number, not accounting resolution */ public pixelWidth = 1; /** the pixel height of this texture source. This is the REAL pure number, not accounting resolution */ public pixelHeight = 1; /** * the width of this texture source, accounting for resolution * eg pixelWidth 200, resolution 2, then width will be 100 */ public width = 1; /** * the height of this texture source, accounting for resolution * eg pixelHeight 200, resolution 2, then height will be 100 */ public height = 1; /** * the resource that will be uploaded to the GPU. This is where we get our pixels from * eg an ImageBimt / Canvas / Video etc */ public resource: T; /** * The number of samples of a multisample texture. This is always 1 for non-multisample textures. * To enable multisample for a texture, set antialias to true * @internal */ public sampleCount = 1; /** * The number of mip levels to generate for this texture. * this is overridden if autoGenerateMipmaps is true. it is read only! */ public mipLevelCount = 1; /** * Should we auto generate mipmaps for this texture? This will automatically generate mipmaps * for this texture when uploading to the GPU. Mipmapped textures take up more memory, but * can look better when scaled down. * * For performance reasons, it is recommended to NOT use this with RenderTextures, as they are often updated every frame. * If you do, make sure to call `updateMipmaps` after you update the texture. */ public autoGenerateMipmaps = false; /** the format that the texture data has */ public format: TEXTURE_FORMATS = 'rgba8unorm'; /** how many dimensions does this texture have? currently v8 only supports 2d */ public dimension: TEXTURE_DIMENSIONS = '2d'; /** how this texture is viewed/sampled by shaders (WebGPU view dimension) */ public viewDimension: TEXTURE_VIEW_DIMENSIONS = '2d'; /** how many array layers this texture has (WebGPU depthOrArrayLayers) */ public arrayLayerCount = 1; /** the alpha mode of the texture */ public alphaMode: ALPHA_MODES; private _style: TextureStyle; private _ownsStyle = false; /** * Only really affects RenderTextures. * Should we use antialiasing for this texture. It will look better, but may impact performance as a * Blit operation will be required to resolve the texture. */ public antialias = false; /** * Treat the underlying GPU texture as transient — see {@link TextureSourceOptions.transient}. * Internal flag, populated from options. * @internal */ public transient = false; /** * Has the source been destroyed? * @readonly */ public destroyed: boolean; /** * Used by automatic texture Garbage Collection, stores last GC tick when it was bound * @protected */ public _touched = 0; /** * Used by the batcher to build texture batches. faster to have the variable here! * @protected */ public _batchTick = -1; /** * A temporary batch location for the texture batching. Here for performance reasons only! * @protected */ public _textureBindLocation = -1; public isPowerOfTwo: boolean; /** If true, the Garbage Collector will unload this texture if it is not used after a period of time */ public autoGarbageCollect: boolean; /** * used internally to know where a texture came from. Usually assigned by the asset loader! * @ignore */ public _sourceOrigin: string; /** * @param options - options for creating a new TextureSource */ constructor(protected readonly options: TextureSourceOptions<T> = {}) { super(); options = { ...TextureSource.defaultOptions, ...options }; this.label = options.label ?? ''; this.resource = options.resource; this.autoGarbageCollect = options.autoGarbageCollect; this._resolution = options.resolution; if (options.width) { this.pixelWidth = options.width * this._resolution; } else { this.pixelWidth = this.resource ? (this.resourceWidth ?? 1) : 1; } if (options.height) { this.pixelHeight = options.height * this._resolution; } else { this.pixelHeight = this.resource ? (this.resourceHeight ?? 1) : 1; } this.width = this.pixelWidth / this._resolution; this.height = this.pixelHeight / this._resolution; this.format = options.format; this.dimension = options.dimensions; this.viewDimension = options.viewDimension ?? options.dimensions; this.arrayLayerCount = options.arrayLayerCount; this.mipLevelCount = options.mipLevelCount; this.autoGenerateMipmaps = options.autoGenerateMipmaps; this.sampleCount = options.sampleCount; this.antialias = options.antialias; this.transient = options.transient ?? false; this.alphaMode = options.alphaMode; this.style = new TextureStyle(definedProps(options)); // the source constructed this style itself, so it may destroy it; styles assigned // from outside (e.g. TexturePool's shared default) are shared and must survive us this._ownsStyle = true; this.destroyed = false; this._refreshPOT(); } /** returns itself */ get source(): TextureSource { return this; } /** the style of the texture */ get style(): TextureStyle { return this._style; } set style(value: TextureStyle) { if (this.style === value) return; // an assigned style instance is shared with its provider — we no longer own it this._ownsStyle = false; this._style?.off('change', this._onStyleChange, this); this._style = value; this._style?.on('change', this._onStyleChange, this); this._onStyleChange(); } /** Specifies the maximum anisotropy value clamp used by the sampler. */ set maxAnisotropy(value: number) { this._style.maxAnisotropy = value; } get maxAnisotropy(): number { return this._style.maxAnisotropy; } /** setting this will set wrapModeU, wrapModeV and wrapModeW all at once! */ get addressMode(): WRAP_MODE { return this._style.addressMode; } set addressMode(value: WRAP_MODE) { this._style.addressMode = value; } /** setting this will set wrapModeU, wrapModeV and wrapModeW all at once! */ get repeatMode(): WRAP_MODE { return this._style.addressMode; } set repeatMode(value: WRAP_MODE) { this._style.addressMode = value; } /** Specifies the sampling behavior when the sample footprint is smaller than or equal to one texel. */ get magFilter(): SCALE_MODE { return this._style.magFilter; } set magFilter(value: SCALE_MODE) { this._style.magFilter = value; } /** Specifies the sampling behavior when the sample footprint is larger than one texel. */ get minFilter(): SCALE_MODE { return this._style.minFilter; } set minFilter(value: SCALE_MODE) { this._style.minFilter = value; } /** Specifies behavior for sampling between mipmap levels. */ get mipmapFilter(): SCALE_MODE { return this._style.mipmapFilter; } set mipmapFilter(value: SCALE_MODE) { this._style.mipmapFilter = value; } /** Specifies the minimum and maximum levels of detail, respectively, used internally when sampling a texture. */ get lodMinClamp(): number { return this._style.lodMinClamp; } set lodMinClamp(value: number) { this._style.lodMinClamp = value; } /** Specifies the minimum and maximum levels of detail, respectively, used internally when sampling a texture. */ get lodMaxClamp(): number { return this._style.lodMaxClamp; } set lodMaxClamp(value: number) { this._style.lodMaxClamp = value; } private _onStyleChange() { this.emit('styleChange', this); } /** call this if you have modified the texture outside of the constructor */ public update() { // update resource... if (this.resource) { const resolution = this._resolution; const didResize = this.resize(this.resourceWidth / resolution, this.resourceHeight / resolution); // no need to dispatch the update we resized as that will // notify the texture systems anyway if (didResize) return; } this.emit('update', this); } /** Destroys this texture source */ public destroy() { this.destroyed = true; this.unload(); this.emit('destroy', this); if (this._style) { // only destroy a style we created — a shared style (e.g. TexturePool's // default) is still in use by other sources if (this._ownsStyle) this._style.destroy(); this._style = null; } this.uploadMethodId = null; this.resource = null; this.removeAllListeners(); } /** * This will unload the Texture source from the GPU. This will free up the GPU memory * As soon as it is required fore rendering, it will be re-uploaded. */ public unload() { this._resourceId = uid('resource'); this.emit('change', this); /** Unloads the GPU data from the view container. */ this.emit('unload', this); for (const key in this._gpuData) { this._gpuData[key]?.destroy?.(); } this._gpuData = Object.create(null); } /** the width of the resource. This is the REAL pure number, not accounting resolution */ public get resourceWidth(): number { const { resource } = this; return resource.naturalWidth || resource.videoWidth || resource.displayWidth || resource.width; } /** the height of the resource. This is the REAL pure number, not accounting resolution */ public get resourceHeight(): number { const { resource } = this; return resource.naturalHeight || resource.videoHeight || resource.displayHeight || resource.height; } /** * the resolution of the texture. Changing this number, will not change the number of pixels in the actual texture * but will the size of the texture when rendered. * * changing the resolution of this texture to 2 for example will make it appear twice as small when rendered (as pixel * density will have increased) */ get resolution(): number { return this._resolution; } set resolution(resolution: number) { if (this._resolution === resolution) return; this._resolution = resolution; this.width = this.pixelWidth / resolution; this.height = this.pixelHeight / resolution; } /** * Resize the texture, this is handy if you want to use the texture as a render texture * @param width - the new width of the texture * @param height - the new height of the texture * @param resolution - the new resolution of the texture * @returns - if the texture was resized */ public resize(width?: number, height?: number, resolution?: number): boolean { resolution ||= this._resolution; width ||= this.width; height ||= this.height; // make sure we work with rounded pixels const newPixelWidth = Math.round(width * resolution); const newPixelHeight = Math.round(height * resolution); this.width = newPixelWidth / resolution; this.height = newPixelHeight / resolution; this._resolution = resolution; if (this.pixelWidth === newPixelWidth && this.pixelHeight === newPixelHeight) { return false; } this._refreshPOT(); this.pixelWidth = newPixelWidth; this.pixelHeight = newPixelHeight; this.emit('resize', this); this._resourceId = uid('resource'); this.emit('change', this); return true; } /** * Lets the renderer know that this texture has been updated and its mipmaps should be re-generated. * This is only important for RenderTexture instances, as standard Texture instances will have their * mipmaps generated on upload. You should call this method after you make any change to the texture * * The reason for this is is can be quite expensive to update mipmaps for a texture. So by default, * We want you, the developer to specify when this action should happen. * * Generally you don't want to have mipmaps generated on Render targets that are changed every frame, */ public updateMipmaps() { if (this.autoGenerateMipmaps && this.mipLevelCount > 1) { this.emit('updateMipmaps', this); } } set wrapMode(value: WRAP_MODE) { this._style.wrapMode = value; } get wrapMode(): WRAP_MODE { return this._style.wrapMode; } set scaleMode(value: SCALE_MODE) { this._style.scaleMode = value; } /** setting this will set magFilter,minFilter and mipmapFilter all at once! */ get scaleMode(): SCALE_MODE { return this._style.scaleMode; } /** * Refresh check for isPowerOfTwo texture based on size * @private */ protected _refreshPOT(): void { this.isPowerOfTwo = isPow2(this.pixelWidth) && isPow2(this.pixelHeight); } public static test(_resource: any): any { // this should be overridden by other sources.. throw new Error('Unimplemented'); } /** * A helper function that creates a new TextureSource based on the resource you provide. * @param resource - The resource to create the texture source from. */ public static from: (resource: TextureResourceOrOptions) => TextureSource; }