phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
502 lines (455 loc) • 17.1 kB
JavaScript
/**
* @author Richard Davey <rich@phaser.io>
* @copyright 2013-2026 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var CanvasPool = require('../display/canvas/CanvasPool');
var Class = require('../utils/Class');
var IsSizePowerOfTwo = require('../math/pow2/IsSizePowerOfTwo');
var ScaleModes = require('../renderer/ScaleModes');
var WebGLTextureWrapper = require('../renderer/webgl/wrappers/WebGLTextureWrapper');
/**
* @classdesc
* A Texture Source is the encapsulation of the actual source data for a Texture.
*
* It wraps the raw pixel data — typically an Image Element loaded from the file system or
* network, a Canvas Element, or a Video Element — and manages the corresponding WebGL
* texture object used by the renderer. In WebGL mode, the TextureSource creates and owns
* the `WebGLTextureWrapper` that is uploaded to the GPU.
*
* You will not usually create a TextureSource directly. Instead, it is created automatically
* when a Texture is added to the Texture Manager. You can access it via `Texture.source`,
* which is an array of TextureSource instances. Most textures have a single source, but
* multi-atlas textures can have several.
*
* A Texture can contain multiple Texture Sources, which only happens when a multi-atlas is loaded.
*
* @class TextureSource
* @memberof Phaser.Textures
* @constructor
* @since 3.0.0
*
* @param {Phaser.Textures.Texture} texture - The Texture this TextureSource belongs to.
* @param {Phaser.Types.Textures.TextureSource} source - The source image data.
* @param {number} [width] - Optional width of the source image. If not given it's derived from the source itself.
* @param {number} [height] - Optional height of the source image. If not given it's derived from the source itself.
* @param {boolean} [flipY=true] - Sets the `UNPACK_FLIP_Y_WEBGL` flag the WebGL Texture uses during upload.
*/
var TextureSource = new Class({
initialize:
function TextureSource (texture, source, width, height, flipY)
{
if (flipY === undefined) { flipY = true; }
var game = texture.manager.game;
/**
* A reference to the Canvas or WebGL Renderer.
*
* @name Phaser.Textures.TextureSource#renderer
* @type {(Phaser.Renderer.Canvas.CanvasRenderer|Phaser.Renderer.WebGL.WebGLRenderer)}
* @since 3.7.0
*/
this.renderer = game.renderer;
/**
* The Texture this TextureSource instance belongs to.
*
* @name Phaser.Textures.TextureSource#texture
* @type {Phaser.Textures.Texture}
* @since 3.0.0
*/
this.texture = texture;
/**
* The source of the image data.
*
* This is either an Image Element, a Canvas Element, a Video Element, a RenderTexture or a WebGLTextureWrapper.
*
* In Phaser 3.60 and above it can also be a Compressed Texture data object.
*
* @name Phaser.Textures.TextureSource#source
* @type {Phaser.Types.Textures.TextureSource}
* @since 3.12.0
*/
this.source = source;
/**
* The image data.
*
* This is either an Image element, Canvas element, Video Element,
* or null if the `source` is not an HTMLElement.
* Non-HTMLElement sources include compressed textures, data arrays,
* and references to other WebGL textures.
*
* @name Phaser.Textures.TextureSource#image
* @type {Phaser.Types.Textures.TextureSourceElement | null}
* @since 3.0.0
*/
this.image = (source.compressed) ? null : source;
/**
* Holds the compressed texture algorithm, or `null` if it's not a compressed texture.
*
* Prior to Phaser 3.60 this value always held `null`.
*
* @name Phaser.Textures.TextureSource#compressionAlgorithm
* @type {number}
* @default null
* @since 3.0.0
*/
this.compressionAlgorithm = (source.compressed) ? source.format : null;
/**
* The resolution of the source image.
*
* @name Phaser.Textures.TextureSource#resolution
* @type {number}
* @default 1
* @since 3.0.0
*/
this.resolution = 1;
/**
* The width of the source image. If not specified in the constructor it will check
* the `naturalWidth`, `videoWidth`, and then `width` properties of the source image.
*
* @name Phaser.Textures.TextureSource#width
* @type {number}
* @since 3.0.0
*/
this.width = width || source.naturalWidth || source.videoWidth || source.width || 0;
/**
* The height of the source image. If not specified in the constructor it will check
* the `naturalHeight`, `videoHeight`, and then `height` properties of the source image.
*
* @name Phaser.Textures.TextureSource#height
* @type {number}
* @since 3.0.0
*/
this.height = height || source.naturalHeight || source.videoHeight || source.height || 0;
/**
* The Scale Mode the image will use when rendering.
* Either Linear or Nearest.
*
* @name Phaser.Textures.TextureSource#scaleMode
* @type {number}
* @since 3.0.0
*/
this.scaleMode = ScaleModes.DEFAULT;
/**
* Is the source image a Canvas Element?
*
* @name Phaser.Textures.TextureSource#isCanvas
* @type {boolean}
* @since 3.0.0
*/
this.isCanvas = (source instanceof HTMLCanvasElement);
/**
* Is the source image a Video Element?
*
* @name Phaser.Textures.TextureSource#isVideo
* @type {boolean}
* @since 3.20.0
*/
this.isVideo = (window.hasOwnProperty('HTMLVideoElement') && source instanceof HTMLVideoElement);
/**
* Is the source image a Render Texture?
*
* @name Phaser.Textures.TextureSource#isRenderTexture
* @type {boolean}
* @since 3.12.0
*/
this.isRenderTexture = (source.type === 'RenderTexture' || source.type === 'DynamicTexture');
/**
* Is the source image a WebGLTextureWrapper?
*
* @name Phaser.Textures.TextureSource#isGLTexture
* @type {boolean}
* @since 3.19.0
*/
this.isGLTexture = source instanceof WebGLTextureWrapper;
/**
* Are the source image dimensions a power of two?
*
* @name Phaser.Textures.TextureSource#isPowerOf2
* @type {boolean}
* @since 3.0.0
*/
this.isPowerOf2 = IsSizePowerOfTwo(this.width, this.height);
/**
* The wrapped WebGL Texture of the source image.
* If this TextureSource is driven from a WebGLTexture already,
* then this wrapper contains a reference to that WebGLTexture.
*
* @name Phaser.Textures.TextureSource#glTexture
* @type {?Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper}
* @default null
* @since 3.0.0
*/
this.glTexture = null;
/**
* Whether the `UNPACK_FLIP_Y_WEBGL` flag is set on this WebGL Texture during upload.
* When `true`, the texture data is flipped vertically as it is uploaded to the GPU.
*
* @name Phaser.Textures.TextureSource#flipY
* @type {boolean}
* @since 3.20.0
*/
this.flipY = flipY;
this.init(game);
},
/**
* Creates a WebGL Texture, if required, and sets the Texture filter mode.
*
* @method Phaser.Textures.TextureSource#init
* @since 3.0.0
*
* @param {Phaser.Game} game - A reference to the Phaser Game instance.
*/
init: function (game)
{
var renderer = this.renderer;
if (renderer)
{
var source = this.source;
if (renderer.gl)
{
var image = this.image;
var flipY = this.flipY;
var width = this.width;
var height = this.height;
var scaleMode = this.scaleMode;
if (this.isCanvas)
{
this.glTexture = renderer.createCanvasTexture(image, false, flipY);
}
else if (this.isVideo)
{
this.glTexture = renderer.createVideoTexture(image, false, flipY);
}
else if (this.isRenderTexture)
{
this.glTexture = renderer.createTextureFromSource(null, width, height, scaleMode, undefined, flipY);
}
else if (this.isGLTexture)
{
this.glTexture = source;
}
else if (this.compressionAlgorithm)
{
this.glTexture = renderer.createTextureFromSource(source, undefined, undefined, scaleMode, undefined, flipY);
}
else if (source instanceof Uint8Array)
{
this.glTexture = renderer.createUint8ArrayTexture(source, width, height, undefined, flipY);
}
else
{
this.glTexture = renderer.createTextureFromSource(image, width, height, scaleMode, undefined, flipY);
}
if (typeof WEBGL_DEBUG)
{
this.glTexture.spectorMetadata = { textureKey: this.texture.key };
}
}
else if (this.isRenderTexture)
{
this.image = source.canvas;
}
}
if (!game.config.antialias)
{
this.setFilter(1);
}
},
/**
* Sets the Filter Mode for this Texture.
*
* The mode can be either Linear, the default, or Nearest.
*
* For pixel-art you should use Nearest.
*
* @method Phaser.Textures.TextureSource#setFilter
* @since 3.0.0
*
* @param {Phaser.Textures.FilterMode} filterMode - The Filter Mode.
*/
setFilter: function (filterMode)
{
if (this.renderer && this.renderer.gl)
{
this.renderer.setTextureFilter(this.glTexture, filterMode);
}
this.scaleMode = filterMode;
},
/**
* Sets the `UNPACK_FLIP_Y_WEBGL` flag for the WebGL Texture during texture upload.
*
* @method Phaser.Textures.TextureSource#setFlipY
* @since 3.20.0
*
* @param {boolean} [value=true] - Should the WebGL Texture be flipped on the Y axis on texture upload or not?
*/
setFlipY: function (value)
{
if (value === undefined) { value = true; }
if (value === this.flipY) { return this; }
this.flipY = value;
this.update();
return this;
},
/**
* Sets the wrap mode for this TextureSource.
* This is only available for WebGL.
*
* The wrap mode can be one of the following:
*
* - Phaser.Textures.WrapMode.CLAMP_TO_EDGE
* - Phaser.Textures.WrapMode.REPEAT
* - Phaser.Textures.WrapMode.MIRRORED_REPEAT
*
* Note that only CLAMP_TO_EDGE is supported for non-power of two textures.
* If another wrap mode is specified for such a texture, it will be ignored.
*
* @method Phaser.Textures.TextureSource#setWrap
* @since 4.0.0
* @webglonly
*
* @param {Phaser.Textures.WrapMode} wrapModeS - The wrap mode for the S (horizontal) axis.
* @param {Phaser.Textures.WrapMode} wrapModeT - The wrap mode for the T (vertical) axis.
*
* @return {this} This TextureSource instance.
*/
setWrap: function (wrapModeS, wrapModeT)
{
if (this.renderer && this.renderer.gl)
{
this.renderer.setTextureWrap(this.glTexture, wrapModeS, wrapModeT);
}
return this;
},
/**
* Update the underlying WebGLTexture with the current source data.
* This is called automatically by game systems which manage textures,
* such as Text or Video, or on internal data changes.
* You should not need to call this method manually unless you are
* manually updating the source data.
*
* @method Phaser.Textures.TextureSource#update
* @since 3.7.0
*/
update: function ()
{
var renderer = this.renderer;
var image = this.image;
var flipY = this.flipY;
var gl = renderer.gl;
if (gl)
{
var textureWrapper = this.glTexture;
if (this.isCanvas)
{
renderer.updateCanvasTexture(image, textureWrapper, flipY);
}
else if (this.isVideo)
{
renderer.updateVideoTexture(image, textureWrapper, flipY);
}
else
{
textureWrapper.update(
this.source,
this.width,
this.height,
flipY,
textureWrapper.wrapS,
textureWrapper.wrapT,
textureWrapper.magFilter,
textureWrapper.minFilter,
textureWrapper.format
);
}
}
},
/**
* Updates the dimensions of this Texture Source.
* This is called automatically by game systems which manage textures,
* such as Text, which renders to a dedicated canvas that changes size.
*
* @method Phaser.Textures.TextureSource#updateSize
* @since 4.0.0
* @param {number} width - The new width of the source image.
* @param {number} height - The new height of the source image.
*/
updateSize: function (width, height)
{
if (this.width === width && this.height === height)
{
return;
}
this.width = width;
this.height = height;
this.isPowerOf2 = IsSizePowerOfTwo(width, height);
},
/**
* Change the source data of this TextureSource.
* This will update the underlying WebGLTexture with the new source data.
*
* Note that this will not update any consumers of the texture.
* Frames and game objects with the `Size` component
* will automatically cache width and height on creation, so you will need
* to update them if you change the resolution of the texture this way.
*
* @example
* // `logo` is a sprite
* // `newImage` is an ImageElement with the new source data
* logo.frame.source.updateSource(newImage);
* logo.texture.get("__BASE").setSize(logo.frame.source.width, logo.frame.source.height);
* logo.setSize(logo.frame.source.width, logo.frame.source.height);
* logo.setOrigin(0.5, 0.5);
*
* @method Phaser.Textures.TextureSource#updateSource
* @since 4.0.0
*
* @param {Phaser.Types.Textures.TextureSource} source - The new source data.
*/
updateSource: function (source)
{
// Tidy up unused pool canvases.
if (this.isCanvas && source !== this.image && !(source instanceof HTMLCanvasElement))
{
CanvasPool.remove(this.image);
}
this.source = source;
this.image = (source.compressed) ? null : source;
this.compressionAlgorithm = (source.compressed) ? source.format : null;
this.width = source.naturalWidth || source.videoWidth || source.width || 0;
this.height = source.naturalHeight || source.videoHeight || source.height || 0;
this.isPowerOf2 = IsSizePowerOfTwo(this.width, this.height);
this.isCanvas = (source instanceof HTMLCanvasElement);
this.isVideo = (window.hasOwnProperty('HTMLVideoElement') && source instanceof HTMLVideoElement);
this.isRenderTexture = (source.type === 'RenderTexture' || source.type === 'DynamicTexture');
this.isGLTexture = source instanceof WebGLTextureWrapper;
this.update();
},
/**
* Destroys this Texture Source and frees associated resources.
*
* In WebGL mode, the underlying WebGL texture is deleted from the GPU via the renderer.
* If the source is a canvas, it is returned to the CanvasPool. All internal references
* are then set to `null`.
*
* @method Phaser.Textures.TextureSource#destroy
* @since 3.0.0
*/
destroy: function ()
{
if (this.glTexture)
{
this.renderer.deleteTexture(this.glTexture);
}
if (this.isCanvas)
{
CanvasPool.remove(this.image);
}
this.renderer = null;
this.texture = null;
this.source = null;
this.image = null;
this.glTexture = null;
}
});
module.exports = TextureSource;