diff --git a/src/deprecation.js b/src/deprecation.js index 9d63a5e..56e45f4 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -3,6 +3,7 @@ import * as particles from './particles'; import * as extras from './extras'; import * as filters from './filters'; +import * as prepare from './prepare'; // provide method to give a stack track for warnings // useful for tracking-down where deprecated methods/properties/classes @@ -642,3 +643,49 @@ return saidHello; }, }); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.canvas, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.webgl.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.webgl, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); diff --git a/src/deprecation.js b/src/deprecation.js index 9d63a5e..56e45f4 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -3,6 +3,7 @@ import * as particles from './particles'; import * as extras from './extras'; import * as filters from './filters'; +import * as prepare from './prepare'; // provide method to give a stack track for warnings // useful for tracking-down where deprecated methods/properties/classes @@ -642,3 +643,49 @@ return saidHello; }, }); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.canvas, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.webgl.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.webgl, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js new file mode 100644 index 0000000..cb56559 --- /dev/null +++ b/src/prepare/BasePrepare.js @@ -0,0 +1,245 @@ +import * as core from '../core'; +import CountLimiter from './limiters/CountLimiter'; +const SharedTicker = core.ticker.shared; + +const DEFAULT_UPLOADS_PER_FRAME = 4; + +/** + * The prepare manager provides functionality to upload content to the GPU. BasePrepare handles + * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} + * to provide preparation capabilities specific to their respective renderers. + * + * @abstract + * @class + * @memberof PIXI + */ +export default class BasePrepare +{ + /** + * @param {PIXI.SystemRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + /** + * The limiter to be used to control how quickly items are prepared. + * @type {PIXI.prepare.CountLimiter|PIXI.prepare.TimeLimiter} + */ + this.limiter = new CountLimiter(DEFAULT_UPLOADS_PER_FRAME); + + /** + * Reference to the renderer. + * @type {PIXI.SystemRenderer} + * @protected + */ + this.renderer = renderer; + + /** + * The only real difference between CanvasPrepare and WebGLPrepare is what they pass + * to upload hooks. That different parameter is stored here. + * @type {PIXI.prepare.CanvasPrepare|PIXI.WebGLRenderer} + * @protected + */ + this.uploadHookSource = null; + + /** + * Collection of items to uploads at once. + * @type {Array<*>} + * @private + */ + this.queue = []; + + /** + * Collection of additional hooks for finding assets. + * @type {Array} + * @private + */ + this.addHooks = []; + + /** + * Collection of additional hooks for processing assets. + * @type {Array} + * @private + */ + this.uploadHooks = []; + + /** + * Callback to call after completed. + * @type {Array} + * @private + */ + this.completes = []; + + /** + * If prepare is ticking (running). + * @type {boolean} + * @private + */ + this.ticking = false; + } + + /** + * Upload all the textures and graphics to the GPU. + * + * @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either + * the container or display object to search for items to upload or + * the callback function, if items have been added using `prepare.add`. + * @param {Function} [done] - Optional callback when all queued uploads have completed + */ + upload(item, done) + { + if (typeof item === 'function') + { + done = item; + item = null; + } + + // If a display object, search for items + // that we could upload + if (item) + { + this.add(item); + } + + // Get the items for upload from the display + if (this.queue.length) + { + if (done) + { + this.completes.push(done); + } + + if (!this.ticking) + { + this.ticking = true; + SharedTicker.add(this.tick, this); + } + } + else if (done) + { + done(); + } + } + + /** + * Handle tick update + * + * @private + */ + tick() + { + this.limiter.beginFrame(); + // Upload the graphics + while (this.queue.length && this.limiter.allowedToUpload()) + { + const item = this.queue[0]; + let uploaded = false; + + for (let i = 0, len = this.uploadHooks.length; i < len; i++) + { + if (this.uploadHooks[i](this.uploadHookSource, item)) + { + this.queue.shift(); + uploaded = true; + break; + } + } + + if (!uploaded) + { + this.queue.shift(); + } + } + + // We're finished + if (!this.queue.length) + { + this.ticking = false; + + SharedTicker.remove(this.tick, this); + + const completes = this.completes.slice(0); + + this.completes.length = 0; + + for (let i = 0, len = completes.length; i < len; i++) + { + completes[i](); + } + } + } + + /** + * Adds hooks for finding and uploading items. + * + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + register(addHook, uploadHook) + { + if (addHook) + { + this.addHooks.push(addHook); + } + + if (uploadHook) + { + this.uploadHooks.push(uploadHook); + } + + return this; + } + + /** + * Manually add an item to the uploading queue. + * + * @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + add(item) + { + // Add additional hooks for finding elements on special + // types of objects that + for (let i = 0, len = this.addHooks.length; i < len; i++) + { + if (this.addHooks[i](item, this.queue)) + { + break; + } + } + + // Get childen recursively + if (item instanceof core.Container) + { + for (let i = item.children.length - 1; i >= 0; i--) + { + this.add(item.children[i]); + } + } + + return this; + } + + /** + * Destroys the plugin, don't use after this. + * + */ + destroy() + { + if (this.ticking) + { + SharedTicker.remove(this.tick, this); + } + this.ticking = false; + this.addHooks = null; + this.uploadHooks = null; + this.renderer = null; + this.completes = null; + this.queue = null; + this.limiter = null; + this.uploadHookSource = null; + } + +} diff --git a/src/deprecation.js b/src/deprecation.js index 9d63a5e..56e45f4 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -3,6 +3,7 @@ import * as particles from './particles'; import * as extras from './extras'; import * as filters from './filters'; +import * as prepare from './prepare'; // provide method to give a stack track for warnings // useful for tracking-down where deprecated methods/properties/classes @@ -642,3 +643,49 @@ return saidHello; }, }); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.canvas, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.webgl.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.webgl, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js new file mode 100644 index 0000000..cb56559 --- /dev/null +++ b/src/prepare/BasePrepare.js @@ -0,0 +1,245 @@ +import * as core from '../core'; +import CountLimiter from './limiters/CountLimiter'; +const SharedTicker = core.ticker.shared; + +const DEFAULT_UPLOADS_PER_FRAME = 4; + +/** + * The prepare manager provides functionality to upload content to the GPU. BasePrepare handles + * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} + * to provide preparation capabilities specific to their respective renderers. + * + * @abstract + * @class + * @memberof PIXI + */ +export default class BasePrepare +{ + /** + * @param {PIXI.SystemRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + /** + * The limiter to be used to control how quickly items are prepared. + * @type {PIXI.prepare.CountLimiter|PIXI.prepare.TimeLimiter} + */ + this.limiter = new CountLimiter(DEFAULT_UPLOADS_PER_FRAME); + + /** + * Reference to the renderer. + * @type {PIXI.SystemRenderer} + * @protected + */ + this.renderer = renderer; + + /** + * The only real difference between CanvasPrepare and WebGLPrepare is what they pass + * to upload hooks. That different parameter is stored here. + * @type {PIXI.prepare.CanvasPrepare|PIXI.WebGLRenderer} + * @protected + */ + this.uploadHookSource = null; + + /** + * Collection of items to uploads at once. + * @type {Array<*>} + * @private + */ + this.queue = []; + + /** + * Collection of additional hooks for finding assets. + * @type {Array} + * @private + */ + this.addHooks = []; + + /** + * Collection of additional hooks for processing assets. + * @type {Array} + * @private + */ + this.uploadHooks = []; + + /** + * Callback to call after completed. + * @type {Array} + * @private + */ + this.completes = []; + + /** + * If prepare is ticking (running). + * @type {boolean} + * @private + */ + this.ticking = false; + } + + /** + * Upload all the textures and graphics to the GPU. + * + * @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either + * the container or display object to search for items to upload or + * the callback function, if items have been added using `prepare.add`. + * @param {Function} [done] - Optional callback when all queued uploads have completed + */ + upload(item, done) + { + if (typeof item === 'function') + { + done = item; + item = null; + } + + // If a display object, search for items + // that we could upload + if (item) + { + this.add(item); + } + + // Get the items for upload from the display + if (this.queue.length) + { + if (done) + { + this.completes.push(done); + } + + if (!this.ticking) + { + this.ticking = true; + SharedTicker.add(this.tick, this); + } + } + else if (done) + { + done(); + } + } + + /** + * Handle tick update + * + * @private + */ + tick() + { + this.limiter.beginFrame(); + // Upload the graphics + while (this.queue.length && this.limiter.allowedToUpload()) + { + const item = this.queue[0]; + let uploaded = false; + + for (let i = 0, len = this.uploadHooks.length; i < len; i++) + { + if (this.uploadHooks[i](this.uploadHookSource, item)) + { + this.queue.shift(); + uploaded = true; + break; + } + } + + if (!uploaded) + { + this.queue.shift(); + } + } + + // We're finished + if (!this.queue.length) + { + this.ticking = false; + + SharedTicker.remove(this.tick, this); + + const completes = this.completes.slice(0); + + this.completes.length = 0; + + for (let i = 0, len = completes.length; i < len; i++) + { + completes[i](); + } + } + } + + /** + * Adds hooks for finding and uploading items. + * + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + register(addHook, uploadHook) + { + if (addHook) + { + this.addHooks.push(addHook); + } + + if (uploadHook) + { + this.uploadHooks.push(uploadHook); + } + + return this; + } + + /** + * Manually add an item to the uploading queue. + * + * @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + add(item) + { + // Add additional hooks for finding elements on special + // types of objects that + for (let i = 0, len = this.addHooks.length; i < len; i++) + { + if (this.addHooks[i](item, this.queue)) + { + break; + } + } + + // Get childen recursively + if (item instanceof core.Container) + { + for (let i = item.children.length - 1; i >= 0; i--) + { + this.add(item.children[i]); + } + } + + return this; + } + + /** + * Destroys the plugin, don't use after this. + * + */ + destroy() + { + if (this.ticking) + { + SharedTicker.remove(this.tick, this); + } + this.ticking = false; + this.addHooks = null; + this.uploadHooks = null; + this.renderer = null; + this.completes = null; + this.queue = null; + this.limiter = null; + this.uploadHookSource = null; + } + +} diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 51dbfd0..d292d0d 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -1,8 +1,7 @@ import * as core from '../../core'; -const SharedTicker = core.ticker.shared; +import BasePrepare from '../BasePrepare'; const CANVAS_START_SIZE = 16; -const DEFAULT_UPLOADS_PER_FRAME = 4; /** * The prepare manager provides functionality to upload content to the GPU @@ -13,19 +12,16 @@ * @class * @memberof PIXI */ -export default class CanvasPrepare +export default class CanvasPrepare extends BasePrepare { /** * @param {PIXI.CanvasRenderer} renderer - A reference to the current renderer */ constructor(renderer) { - /** - * Reference to the renderer. - * @type {PIXI.CanvasRenderer} - * @private - */ - this.renderer = renderer; + super(renderer); + + this.uploadHookSource = this; /** * An offline canvas to render textures to @@ -43,212 +39,17 @@ */ this.ctx = this.canvas.getContext('2d'); - /** - * Collection of items to uploads at once. - * @type {Array<*>} - * @private - */ - this.queue = []; - - /** - * Collection of additional hooks for finding assets. - * @type {Array} - * @private - */ - this.addHooks = []; - - /** - * Collection of additional hooks for processing assets. - * @type {Array} - * @private - */ - this.uploadHooks = []; - - /** - * Callback to call after completed. - * @type {Array} - * @private - */ - this.completes = []; - - /** - * If prepare is ticking (running). - * @type {boolean} - * @private - */ - this.ticking = false; - // Add textures to upload this.register(findBaseTextures, uploadBaseTextures); } /** - * Upload all the textures and graphics to the GPU. - * - * @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either - * the container or display object to search for items to upload or - * the callback function, if items have been added using `prepare.add`. - * @param {Function} [done] - Optional callback when all queued uploads have completed - */ - upload(item, done) - { - if (typeof item === 'function') - { - done = item; - item = null; - } - - // If a display object, search for items - // that we could upload - if (item) - { - this.add(item); - } - - // Get the items for upload from the display - if (this.queue.length) - { - this.numLeft = CanvasPrepare.UPLOADS_PER_FRAME; - - if (done) - { - this.completes.push(done); - } - - if (!this.ticking) - { - this.ticking = true; - SharedTicker.add(this.tick, this); - } - } - else if (done) - { - done(); - } - } - - /** - * Handle tick update - * - * @private - */ - tick() - { - // Upload the graphics - while (this.queue.length && this.numLeft > 0) - { - const item = this.queue[0]; - let uploaded = false; - - for (let i = 0, len = this.uploadHooks.length; i < len; i++) - { - if (this.uploadHooks[i](this, item)) - { - this.numLeft--; - this.queue.shift(); - uploaded = true; - break; - } - } - - if (!uploaded) - { - this.queue.shift(); - } - } - - // We're finished - if (this.queue.length) - { - this.numLeft = CanvasPrepare.UPLOADS_PER_FRAME; - } - else - { - this.ticking = false; - - SharedTicker.remove(this.tick, this); - - const completes = this.completes.slice(0); - - this.completes.length = 0; - - for (let i = 0, len = completes.length; i < len; i++) - { - completes[i](); - } - } - } - - /** - * Adds hooks for finding and uploading items. - * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ - register(addHook, uploadHook) - { - if (addHook) - { - this.addHooks.push(addHook); - } - - if (uploadHook) - { - this.uploadHooks.push(uploadHook); - } - - return this; - } - - /** - * Manually add an item to the uploading queue. - * - * @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ - add(item) - { - // Add additional hooks for finding elements on special - // types of objects that - for (let i = 0, len = this.addHooks.length; i < len; i++) - { - if (this.addHooks[i](item, this.queue)) - { - break; - } - } - - // Get childen recursively - if (item instanceof core.Container) - { - for (let i = item.children.length - 1; i >= 0; i--) - { - this.add(item.children[i]); - } - } - - return this; - } - - /** * Destroys the plugin, don't use after this. * */ destroy() { - if (this.ticking) - { - SharedTicker.remove(this.tick, this); - } - this.ticking = false; - this.addHooks = null; - this.uploadHooks = null; - this.renderer = null; - this.completes = null; - this.queue = null; + super.destroy(); this.ctx = null; this.canvas = null; } @@ -256,15 +57,6 @@ } /** - * The number of graphics or textures to upload to the GPU. - * - * @static - * @type {number} - * @default 4 - */ -CanvasPrepare.UPLOADS_PER_FRAME = DEFAULT_UPLOADS_PER_FRAME; - -/** * Built-in hook to upload PIXI.Texture objects to the GPU. * * @private diff --git a/src/deprecation.js b/src/deprecation.js index 9d63a5e..56e45f4 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -3,6 +3,7 @@ import * as particles from './particles'; import * as extras from './extras'; import * as filters from './filters'; +import * as prepare from './prepare'; // provide method to give a stack track for warnings // useful for tracking-down where deprecated methods/properties/classes @@ -642,3 +643,49 @@ return saidHello; }, }); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.canvas, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.webgl.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.webgl, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js new file mode 100644 index 0000000..cb56559 --- /dev/null +++ b/src/prepare/BasePrepare.js @@ -0,0 +1,245 @@ +import * as core from '../core'; +import CountLimiter from './limiters/CountLimiter'; +const SharedTicker = core.ticker.shared; + +const DEFAULT_UPLOADS_PER_FRAME = 4; + +/** + * The prepare manager provides functionality to upload content to the GPU. BasePrepare handles + * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} + * to provide preparation capabilities specific to their respective renderers. + * + * @abstract + * @class + * @memberof PIXI + */ +export default class BasePrepare +{ + /** + * @param {PIXI.SystemRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + /** + * The limiter to be used to control how quickly items are prepared. + * @type {PIXI.prepare.CountLimiter|PIXI.prepare.TimeLimiter} + */ + this.limiter = new CountLimiter(DEFAULT_UPLOADS_PER_FRAME); + + /** + * Reference to the renderer. + * @type {PIXI.SystemRenderer} + * @protected + */ + this.renderer = renderer; + + /** + * The only real difference between CanvasPrepare and WebGLPrepare is what they pass + * to upload hooks. That different parameter is stored here. + * @type {PIXI.prepare.CanvasPrepare|PIXI.WebGLRenderer} + * @protected + */ + this.uploadHookSource = null; + + /** + * Collection of items to uploads at once. + * @type {Array<*>} + * @private + */ + this.queue = []; + + /** + * Collection of additional hooks for finding assets. + * @type {Array} + * @private + */ + this.addHooks = []; + + /** + * Collection of additional hooks for processing assets. + * @type {Array} + * @private + */ + this.uploadHooks = []; + + /** + * Callback to call after completed. + * @type {Array} + * @private + */ + this.completes = []; + + /** + * If prepare is ticking (running). + * @type {boolean} + * @private + */ + this.ticking = false; + } + + /** + * Upload all the textures and graphics to the GPU. + * + * @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either + * the container or display object to search for items to upload or + * the callback function, if items have been added using `prepare.add`. + * @param {Function} [done] - Optional callback when all queued uploads have completed + */ + upload(item, done) + { + if (typeof item === 'function') + { + done = item; + item = null; + } + + // If a display object, search for items + // that we could upload + if (item) + { + this.add(item); + } + + // Get the items for upload from the display + if (this.queue.length) + { + if (done) + { + this.completes.push(done); + } + + if (!this.ticking) + { + this.ticking = true; + SharedTicker.add(this.tick, this); + } + } + else if (done) + { + done(); + } + } + + /** + * Handle tick update + * + * @private + */ + tick() + { + this.limiter.beginFrame(); + // Upload the graphics + while (this.queue.length && this.limiter.allowedToUpload()) + { + const item = this.queue[0]; + let uploaded = false; + + for (let i = 0, len = this.uploadHooks.length; i < len; i++) + { + if (this.uploadHooks[i](this.uploadHookSource, item)) + { + this.queue.shift(); + uploaded = true; + break; + } + } + + if (!uploaded) + { + this.queue.shift(); + } + } + + // We're finished + if (!this.queue.length) + { + this.ticking = false; + + SharedTicker.remove(this.tick, this); + + const completes = this.completes.slice(0); + + this.completes.length = 0; + + for (let i = 0, len = completes.length; i < len; i++) + { + completes[i](); + } + } + } + + /** + * Adds hooks for finding and uploading items. + * + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + register(addHook, uploadHook) + { + if (addHook) + { + this.addHooks.push(addHook); + } + + if (uploadHook) + { + this.uploadHooks.push(uploadHook); + } + + return this; + } + + /** + * Manually add an item to the uploading queue. + * + * @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + add(item) + { + // Add additional hooks for finding elements on special + // types of objects that + for (let i = 0, len = this.addHooks.length; i < len; i++) + { + if (this.addHooks[i](item, this.queue)) + { + break; + } + } + + // Get childen recursively + if (item instanceof core.Container) + { + for (let i = item.children.length - 1; i >= 0; i--) + { + this.add(item.children[i]); + } + } + + return this; + } + + /** + * Destroys the plugin, don't use after this. + * + */ + destroy() + { + if (this.ticking) + { + SharedTicker.remove(this.tick, this); + } + this.ticking = false; + this.addHooks = null; + this.uploadHooks = null; + this.renderer = null; + this.completes = null; + this.queue = null; + this.limiter = null; + this.uploadHookSource = null; + } + +} diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 51dbfd0..d292d0d 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -1,8 +1,7 @@ import * as core from '../../core'; -const SharedTicker = core.ticker.shared; +import BasePrepare from '../BasePrepare'; const CANVAS_START_SIZE = 16; -const DEFAULT_UPLOADS_PER_FRAME = 4; /** * The prepare manager provides functionality to upload content to the GPU @@ -13,19 +12,16 @@ * @class * @memberof PIXI */ -export default class CanvasPrepare +export default class CanvasPrepare extends BasePrepare { /** * @param {PIXI.CanvasRenderer} renderer - A reference to the current renderer */ constructor(renderer) { - /** - * Reference to the renderer. - * @type {PIXI.CanvasRenderer} - * @private - */ - this.renderer = renderer; + super(renderer); + + this.uploadHookSource = this; /** * An offline canvas to render textures to @@ -43,212 +39,17 @@ */ this.ctx = this.canvas.getContext('2d'); - /** - * Collection of items to uploads at once. - * @type {Array<*>} - * @private - */ - this.queue = []; - - /** - * Collection of additional hooks for finding assets. - * @type {Array} - * @private - */ - this.addHooks = []; - - /** - * Collection of additional hooks for processing assets. - * @type {Array} - * @private - */ - this.uploadHooks = []; - - /** - * Callback to call after completed. - * @type {Array} - * @private - */ - this.completes = []; - - /** - * If prepare is ticking (running). - * @type {boolean} - * @private - */ - this.ticking = false; - // Add textures to upload this.register(findBaseTextures, uploadBaseTextures); } /** - * Upload all the textures and graphics to the GPU. - * - * @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either - * the container or display object to search for items to upload or - * the callback function, if items have been added using `prepare.add`. - * @param {Function} [done] - Optional callback when all queued uploads have completed - */ - upload(item, done) - { - if (typeof item === 'function') - { - done = item; - item = null; - } - - // If a display object, search for items - // that we could upload - if (item) - { - this.add(item); - } - - // Get the items for upload from the display - if (this.queue.length) - { - this.numLeft = CanvasPrepare.UPLOADS_PER_FRAME; - - if (done) - { - this.completes.push(done); - } - - if (!this.ticking) - { - this.ticking = true; - SharedTicker.add(this.tick, this); - } - } - else if (done) - { - done(); - } - } - - /** - * Handle tick update - * - * @private - */ - tick() - { - // Upload the graphics - while (this.queue.length && this.numLeft > 0) - { - const item = this.queue[0]; - let uploaded = false; - - for (let i = 0, len = this.uploadHooks.length; i < len; i++) - { - if (this.uploadHooks[i](this, item)) - { - this.numLeft--; - this.queue.shift(); - uploaded = true; - break; - } - } - - if (!uploaded) - { - this.queue.shift(); - } - } - - // We're finished - if (this.queue.length) - { - this.numLeft = CanvasPrepare.UPLOADS_PER_FRAME; - } - else - { - this.ticking = false; - - SharedTicker.remove(this.tick, this); - - const completes = this.completes.slice(0); - - this.completes.length = 0; - - for (let i = 0, len = completes.length; i < len; i++) - { - completes[i](); - } - } - } - - /** - * Adds hooks for finding and uploading items. - * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ - register(addHook, uploadHook) - { - if (addHook) - { - this.addHooks.push(addHook); - } - - if (uploadHook) - { - this.uploadHooks.push(uploadHook); - } - - return this; - } - - /** - * Manually add an item to the uploading queue. - * - * @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ - add(item) - { - // Add additional hooks for finding elements on special - // types of objects that - for (let i = 0, len = this.addHooks.length; i < len; i++) - { - if (this.addHooks[i](item, this.queue)) - { - break; - } - } - - // Get childen recursively - if (item instanceof core.Container) - { - for (let i = item.children.length - 1; i >= 0; i--) - { - this.add(item.children[i]); - } - } - - return this; - } - - /** * Destroys the plugin, don't use after this. * */ destroy() { - if (this.ticking) - { - SharedTicker.remove(this.tick, this); - } - this.ticking = false; - this.addHooks = null; - this.uploadHooks = null; - this.renderer = null; - this.completes = null; - this.queue = null; + super.destroy(); this.ctx = null; this.canvas = null; } @@ -256,15 +57,6 @@ } /** - * The number of graphics or textures to upload to the GPU. - * - * @static - * @type {number} - * @default 4 - */ -CanvasPrepare.UPLOADS_PER_FRAME = DEFAULT_UPLOADS_PER_FRAME; - -/** * Built-in hook to upload PIXI.Texture objects to the GPU. * * @private diff --git a/src/prepare/index.js b/src/prepare/index.js index 1aa203a..f559c45 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -3,3 +3,6 @@ */ export { default as webgl } from './webgl/WebGLPrepare'; export { default as canvas } from './canvas/CanvasPrepare'; +export { default as BasePrepare } from './BasePrepare'; +export { default as CountLimiter } from './limiters/CountLimiter'; +export { default as TimeLimiter } from './limiters/TimeLimiter'; diff --git a/src/deprecation.js b/src/deprecation.js index 9d63a5e..56e45f4 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -3,6 +3,7 @@ import * as particles from './particles'; import * as extras from './extras'; import * as filters from './filters'; +import * as prepare from './prepare'; // provide method to give a stack track for warnings // useful for tracking-down where deprecated methods/properties/classes @@ -642,3 +643,49 @@ return saidHello; }, }); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.canvas, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.webgl.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.webgl, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js new file mode 100644 index 0000000..cb56559 --- /dev/null +++ b/src/prepare/BasePrepare.js @@ -0,0 +1,245 @@ +import * as core from '../core'; +import CountLimiter from './limiters/CountLimiter'; +const SharedTicker = core.ticker.shared; + +const DEFAULT_UPLOADS_PER_FRAME = 4; + +/** + * The prepare manager provides functionality to upload content to the GPU. BasePrepare handles + * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} + * to provide preparation capabilities specific to their respective renderers. + * + * @abstract + * @class + * @memberof PIXI + */ +export default class BasePrepare +{ + /** + * @param {PIXI.SystemRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + /** + * The limiter to be used to control how quickly items are prepared. + * @type {PIXI.prepare.CountLimiter|PIXI.prepare.TimeLimiter} + */ + this.limiter = new CountLimiter(DEFAULT_UPLOADS_PER_FRAME); + + /** + * Reference to the renderer. + * @type {PIXI.SystemRenderer} + * @protected + */ + this.renderer = renderer; + + /** + * The only real difference between CanvasPrepare and WebGLPrepare is what they pass + * to upload hooks. That different parameter is stored here. + * @type {PIXI.prepare.CanvasPrepare|PIXI.WebGLRenderer} + * @protected + */ + this.uploadHookSource = null; + + /** + * Collection of items to uploads at once. + * @type {Array<*>} + * @private + */ + this.queue = []; + + /** + * Collection of additional hooks for finding assets. + * @type {Array} + * @private + */ + this.addHooks = []; + + /** + * Collection of additional hooks for processing assets. + * @type {Array} + * @private + */ + this.uploadHooks = []; + + /** + * Callback to call after completed. + * @type {Array} + * @private + */ + this.completes = []; + + /** + * If prepare is ticking (running). + * @type {boolean} + * @private + */ + this.ticking = false; + } + + /** + * Upload all the textures and graphics to the GPU. + * + * @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either + * the container or display object to search for items to upload or + * the callback function, if items have been added using `prepare.add`. + * @param {Function} [done] - Optional callback when all queued uploads have completed + */ + upload(item, done) + { + if (typeof item === 'function') + { + done = item; + item = null; + } + + // If a display object, search for items + // that we could upload + if (item) + { + this.add(item); + } + + // Get the items for upload from the display + if (this.queue.length) + { + if (done) + { + this.completes.push(done); + } + + if (!this.ticking) + { + this.ticking = true; + SharedTicker.add(this.tick, this); + } + } + else if (done) + { + done(); + } + } + + /** + * Handle tick update + * + * @private + */ + tick() + { + this.limiter.beginFrame(); + // Upload the graphics + while (this.queue.length && this.limiter.allowedToUpload()) + { + const item = this.queue[0]; + let uploaded = false; + + for (let i = 0, len = this.uploadHooks.length; i < len; i++) + { + if (this.uploadHooks[i](this.uploadHookSource, item)) + { + this.queue.shift(); + uploaded = true; + break; + } + } + + if (!uploaded) + { + this.queue.shift(); + } + } + + // We're finished + if (!this.queue.length) + { + this.ticking = false; + + SharedTicker.remove(this.tick, this); + + const completes = this.completes.slice(0); + + this.completes.length = 0; + + for (let i = 0, len = completes.length; i < len; i++) + { + completes[i](); + } + } + } + + /** + * Adds hooks for finding and uploading items. + * + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + register(addHook, uploadHook) + { + if (addHook) + { + this.addHooks.push(addHook); + } + + if (uploadHook) + { + this.uploadHooks.push(uploadHook); + } + + return this; + } + + /** + * Manually add an item to the uploading queue. + * + * @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + add(item) + { + // Add additional hooks for finding elements on special + // types of objects that + for (let i = 0, len = this.addHooks.length; i < len; i++) + { + if (this.addHooks[i](item, this.queue)) + { + break; + } + } + + // Get childen recursively + if (item instanceof core.Container) + { + for (let i = item.children.length - 1; i >= 0; i--) + { + this.add(item.children[i]); + } + } + + return this; + } + + /** + * Destroys the plugin, don't use after this. + * + */ + destroy() + { + if (this.ticking) + { + SharedTicker.remove(this.tick, this); + } + this.ticking = false; + this.addHooks = null; + this.uploadHooks = null; + this.renderer = null; + this.completes = null; + this.queue = null; + this.limiter = null; + this.uploadHookSource = null; + } + +} diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 51dbfd0..d292d0d 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -1,8 +1,7 @@ import * as core from '../../core'; -const SharedTicker = core.ticker.shared; +import BasePrepare from '../BasePrepare'; const CANVAS_START_SIZE = 16; -const DEFAULT_UPLOADS_PER_FRAME = 4; /** * The prepare manager provides functionality to upload content to the GPU @@ -13,19 +12,16 @@ * @class * @memberof PIXI */ -export default class CanvasPrepare +export default class CanvasPrepare extends BasePrepare { /** * @param {PIXI.CanvasRenderer} renderer - A reference to the current renderer */ constructor(renderer) { - /** - * Reference to the renderer. - * @type {PIXI.CanvasRenderer} - * @private - */ - this.renderer = renderer; + super(renderer); + + this.uploadHookSource = this; /** * An offline canvas to render textures to @@ -43,212 +39,17 @@ */ this.ctx = this.canvas.getContext('2d'); - /** - * Collection of items to uploads at once. - * @type {Array<*>} - * @private - */ - this.queue = []; - - /** - * Collection of additional hooks for finding assets. - * @type {Array} - * @private - */ - this.addHooks = []; - - /** - * Collection of additional hooks for processing assets. - * @type {Array} - * @private - */ - this.uploadHooks = []; - - /** - * Callback to call after completed. - * @type {Array} - * @private - */ - this.completes = []; - - /** - * If prepare is ticking (running). - * @type {boolean} - * @private - */ - this.ticking = false; - // Add textures to upload this.register(findBaseTextures, uploadBaseTextures); } /** - * Upload all the textures and graphics to the GPU. - * - * @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either - * the container or display object to search for items to upload or - * the callback function, if items have been added using `prepare.add`. - * @param {Function} [done] - Optional callback when all queued uploads have completed - */ - upload(item, done) - { - if (typeof item === 'function') - { - done = item; - item = null; - } - - // If a display object, search for items - // that we could upload - if (item) - { - this.add(item); - } - - // Get the items for upload from the display - if (this.queue.length) - { - this.numLeft = CanvasPrepare.UPLOADS_PER_FRAME; - - if (done) - { - this.completes.push(done); - } - - if (!this.ticking) - { - this.ticking = true; - SharedTicker.add(this.tick, this); - } - } - else if (done) - { - done(); - } - } - - /** - * Handle tick update - * - * @private - */ - tick() - { - // Upload the graphics - while (this.queue.length && this.numLeft > 0) - { - const item = this.queue[0]; - let uploaded = false; - - for (let i = 0, len = this.uploadHooks.length; i < len; i++) - { - if (this.uploadHooks[i](this, item)) - { - this.numLeft--; - this.queue.shift(); - uploaded = true; - break; - } - } - - if (!uploaded) - { - this.queue.shift(); - } - } - - // We're finished - if (this.queue.length) - { - this.numLeft = CanvasPrepare.UPLOADS_PER_FRAME; - } - else - { - this.ticking = false; - - SharedTicker.remove(this.tick, this); - - const completes = this.completes.slice(0); - - this.completes.length = 0; - - for (let i = 0, len = completes.length; i < len; i++) - { - completes[i](); - } - } - } - - /** - * Adds hooks for finding and uploading items. - * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ - register(addHook, uploadHook) - { - if (addHook) - { - this.addHooks.push(addHook); - } - - if (uploadHook) - { - this.uploadHooks.push(uploadHook); - } - - return this; - } - - /** - * Manually add an item to the uploading queue. - * - * @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ - add(item) - { - // Add additional hooks for finding elements on special - // types of objects that - for (let i = 0, len = this.addHooks.length; i < len; i++) - { - if (this.addHooks[i](item, this.queue)) - { - break; - } - } - - // Get childen recursively - if (item instanceof core.Container) - { - for (let i = item.children.length - 1; i >= 0; i--) - { - this.add(item.children[i]); - } - } - - return this; - } - - /** * Destroys the plugin, don't use after this. * */ destroy() { - if (this.ticking) - { - SharedTicker.remove(this.tick, this); - } - this.ticking = false; - this.addHooks = null; - this.uploadHooks = null; - this.renderer = null; - this.completes = null; - this.queue = null; + super.destroy(); this.ctx = null; this.canvas = null; } @@ -256,15 +57,6 @@ } /** - * The number of graphics or textures to upload to the GPU. - * - * @static - * @type {number} - * @default 4 - */ -CanvasPrepare.UPLOADS_PER_FRAME = DEFAULT_UPLOADS_PER_FRAME; - -/** * Built-in hook to upload PIXI.Texture objects to the GPU. * * @private diff --git a/src/prepare/index.js b/src/prepare/index.js index 1aa203a..f559c45 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -3,3 +3,6 @@ */ export { default as webgl } from './webgl/WebGLPrepare'; export { default as canvas } from './canvas/CanvasPrepare'; +export { default as BasePrepare } from './BasePrepare'; +export { default as CountLimiter } from './limiters/CountLimiter'; +export { default as TimeLimiter } from './limiters/TimeLimiter'; diff --git a/src/prepare/limiters/CountLimiter.js b/src/prepare/limiters/CountLimiter.js new file mode 100644 index 0000000..7fd0b70 --- /dev/null +++ b/src/prepare/limiters/CountLimiter.js @@ -0,0 +1,43 @@ +/** + * CountLimiter limits the number of items handled by a {@link PIXI.prepare.BasePrepare} to a specified + * number of items per frame. + * + * @class + * @memberof PIXI + */ +export default class CountLimiter { + /** + * @param {number} maxItemsPerFrame - The maximum number of items that can be prepared each frame. + */ + constructor(maxItemsPerFrame) + { + /** + * The maximum number of items that can be prepared each frame. + * @private + */ + this.maxItemsPerFrame = maxItemsPerFrame; + /** + * The number of items that can be prepared in the current frame. + * @type {number} + * @private + */ + this.itemsLeft = 0; + } + + /** + * Resets any counting properties to start fresh on a new frame. + */ + beginFrame() + { + this.itemsLeft = this.maxItemsPerFrame; + } + + /** + * Checks to see if another item can be uploaded. This should only be called once per item. + * @return {boolean} If the item is allowed to be uploaded. + */ + allowedToUpload() + { + return this.itemsLeft-- > 0; + } +} diff --git a/src/deprecation.js b/src/deprecation.js index 9d63a5e..56e45f4 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -3,6 +3,7 @@ import * as particles from './particles'; import * as extras from './extras'; import * as filters from './filters'; +import * as prepare from './prepare'; // provide method to give a stack track for warnings // useful for tracking-down where deprecated methods/properties/classes @@ -642,3 +643,49 @@ return saidHello; }, }); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.canvas, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.webgl.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.webgl, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js new file mode 100644 index 0000000..cb56559 --- /dev/null +++ b/src/prepare/BasePrepare.js @@ -0,0 +1,245 @@ +import * as core from '../core'; +import CountLimiter from './limiters/CountLimiter'; +const SharedTicker = core.ticker.shared; + +const DEFAULT_UPLOADS_PER_FRAME = 4; + +/** + * The prepare manager provides functionality to upload content to the GPU. BasePrepare handles + * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} + * to provide preparation capabilities specific to their respective renderers. + * + * @abstract + * @class + * @memberof PIXI + */ +export default class BasePrepare +{ + /** + * @param {PIXI.SystemRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + /** + * The limiter to be used to control how quickly items are prepared. + * @type {PIXI.prepare.CountLimiter|PIXI.prepare.TimeLimiter} + */ + this.limiter = new CountLimiter(DEFAULT_UPLOADS_PER_FRAME); + + /** + * Reference to the renderer. + * @type {PIXI.SystemRenderer} + * @protected + */ + this.renderer = renderer; + + /** + * The only real difference between CanvasPrepare and WebGLPrepare is what they pass + * to upload hooks. That different parameter is stored here. + * @type {PIXI.prepare.CanvasPrepare|PIXI.WebGLRenderer} + * @protected + */ + this.uploadHookSource = null; + + /** + * Collection of items to uploads at once. + * @type {Array<*>} + * @private + */ + this.queue = []; + + /** + * Collection of additional hooks for finding assets. + * @type {Array} + * @private + */ + this.addHooks = []; + + /** + * Collection of additional hooks for processing assets. + * @type {Array} + * @private + */ + this.uploadHooks = []; + + /** + * Callback to call after completed. + * @type {Array} + * @private + */ + this.completes = []; + + /** + * If prepare is ticking (running). + * @type {boolean} + * @private + */ + this.ticking = false; + } + + /** + * Upload all the textures and graphics to the GPU. + * + * @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either + * the container or display object to search for items to upload or + * the callback function, if items have been added using `prepare.add`. + * @param {Function} [done] - Optional callback when all queued uploads have completed + */ + upload(item, done) + { + if (typeof item === 'function') + { + done = item; + item = null; + } + + // If a display object, search for items + // that we could upload + if (item) + { + this.add(item); + } + + // Get the items for upload from the display + if (this.queue.length) + { + if (done) + { + this.completes.push(done); + } + + if (!this.ticking) + { + this.ticking = true; + SharedTicker.add(this.tick, this); + } + } + else if (done) + { + done(); + } + } + + /** + * Handle tick update + * + * @private + */ + tick() + { + this.limiter.beginFrame(); + // Upload the graphics + while (this.queue.length && this.limiter.allowedToUpload()) + { + const item = this.queue[0]; + let uploaded = false; + + for (let i = 0, len = this.uploadHooks.length; i < len; i++) + { + if (this.uploadHooks[i](this.uploadHookSource, item)) + { + this.queue.shift(); + uploaded = true; + break; + } + } + + if (!uploaded) + { + this.queue.shift(); + } + } + + // We're finished + if (!this.queue.length) + { + this.ticking = false; + + SharedTicker.remove(this.tick, this); + + const completes = this.completes.slice(0); + + this.completes.length = 0; + + for (let i = 0, len = completes.length; i < len; i++) + { + completes[i](); + } + } + } + + /** + * Adds hooks for finding and uploading items. + * + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + register(addHook, uploadHook) + { + if (addHook) + { + this.addHooks.push(addHook); + } + + if (uploadHook) + { + this.uploadHooks.push(uploadHook); + } + + return this; + } + + /** + * Manually add an item to the uploading queue. + * + * @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + add(item) + { + // Add additional hooks for finding elements on special + // types of objects that + for (let i = 0, len = this.addHooks.length; i < len; i++) + { + if (this.addHooks[i](item, this.queue)) + { + break; + } + } + + // Get childen recursively + if (item instanceof core.Container) + { + for (let i = item.children.length - 1; i >= 0; i--) + { + this.add(item.children[i]); + } + } + + return this; + } + + /** + * Destroys the plugin, don't use after this. + * + */ + destroy() + { + if (this.ticking) + { + SharedTicker.remove(this.tick, this); + } + this.ticking = false; + this.addHooks = null; + this.uploadHooks = null; + this.renderer = null; + this.completes = null; + this.queue = null; + this.limiter = null; + this.uploadHookSource = null; + } + +} diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 51dbfd0..d292d0d 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -1,8 +1,7 @@ import * as core from '../../core'; -const SharedTicker = core.ticker.shared; +import BasePrepare from '../BasePrepare'; const CANVAS_START_SIZE = 16; -const DEFAULT_UPLOADS_PER_FRAME = 4; /** * The prepare manager provides functionality to upload content to the GPU @@ -13,19 +12,16 @@ * @class * @memberof PIXI */ -export default class CanvasPrepare +export default class CanvasPrepare extends BasePrepare { /** * @param {PIXI.CanvasRenderer} renderer - A reference to the current renderer */ constructor(renderer) { - /** - * Reference to the renderer. - * @type {PIXI.CanvasRenderer} - * @private - */ - this.renderer = renderer; + super(renderer); + + this.uploadHookSource = this; /** * An offline canvas to render textures to @@ -43,212 +39,17 @@ */ this.ctx = this.canvas.getContext('2d'); - /** - * Collection of items to uploads at once. - * @type {Array<*>} - * @private - */ - this.queue = []; - - /** - * Collection of additional hooks for finding assets. - * @type {Array} - * @private - */ - this.addHooks = []; - - /** - * Collection of additional hooks for processing assets. - * @type {Array} - * @private - */ - this.uploadHooks = []; - - /** - * Callback to call after completed. - * @type {Array} - * @private - */ - this.completes = []; - - /** - * If prepare is ticking (running). - * @type {boolean} - * @private - */ - this.ticking = false; - // Add textures to upload this.register(findBaseTextures, uploadBaseTextures); } /** - * Upload all the textures and graphics to the GPU. - * - * @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either - * the container or display object to search for items to upload or - * the callback function, if items have been added using `prepare.add`. - * @param {Function} [done] - Optional callback when all queued uploads have completed - */ - upload(item, done) - { - if (typeof item === 'function') - { - done = item; - item = null; - } - - // If a display object, search for items - // that we could upload - if (item) - { - this.add(item); - } - - // Get the items for upload from the display - if (this.queue.length) - { - this.numLeft = CanvasPrepare.UPLOADS_PER_FRAME; - - if (done) - { - this.completes.push(done); - } - - if (!this.ticking) - { - this.ticking = true; - SharedTicker.add(this.tick, this); - } - } - else if (done) - { - done(); - } - } - - /** - * Handle tick update - * - * @private - */ - tick() - { - // Upload the graphics - while (this.queue.length && this.numLeft > 0) - { - const item = this.queue[0]; - let uploaded = false; - - for (let i = 0, len = this.uploadHooks.length; i < len; i++) - { - if (this.uploadHooks[i](this, item)) - { - this.numLeft--; - this.queue.shift(); - uploaded = true; - break; - } - } - - if (!uploaded) - { - this.queue.shift(); - } - } - - // We're finished - if (this.queue.length) - { - this.numLeft = CanvasPrepare.UPLOADS_PER_FRAME; - } - else - { - this.ticking = false; - - SharedTicker.remove(this.tick, this); - - const completes = this.completes.slice(0); - - this.completes.length = 0; - - for (let i = 0, len = completes.length; i < len; i++) - { - completes[i](); - } - } - } - - /** - * Adds hooks for finding and uploading items. - * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ - register(addHook, uploadHook) - { - if (addHook) - { - this.addHooks.push(addHook); - } - - if (uploadHook) - { - this.uploadHooks.push(uploadHook); - } - - return this; - } - - /** - * Manually add an item to the uploading queue. - * - * @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ - add(item) - { - // Add additional hooks for finding elements on special - // types of objects that - for (let i = 0, len = this.addHooks.length; i < len; i++) - { - if (this.addHooks[i](item, this.queue)) - { - break; - } - } - - // Get childen recursively - if (item instanceof core.Container) - { - for (let i = item.children.length - 1; i >= 0; i--) - { - this.add(item.children[i]); - } - } - - return this; - } - - /** * Destroys the plugin, don't use after this. * */ destroy() { - if (this.ticking) - { - SharedTicker.remove(this.tick, this); - } - this.ticking = false; - this.addHooks = null; - this.uploadHooks = null; - this.renderer = null; - this.completes = null; - this.queue = null; + super.destroy(); this.ctx = null; this.canvas = null; } @@ -256,15 +57,6 @@ } /** - * The number of graphics or textures to upload to the GPU. - * - * @static - * @type {number} - * @default 4 - */ -CanvasPrepare.UPLOADS_PER_FRAME = DEFAULT_UPLOADS_PER_FRAME; - -/** * Built-in hook to upload PIXI.Texture objects to the GPU. * * @private diff --git a/src/prepare/index.js b/src/prepare/index.js index 1aa203a..f559c45 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -3,3 +3,6 @@ */ export { default as webgl } from './webgl/WebGLPrepare'; export { default as canvas } from './canvas/CanvasPrepare'; +export { default as BasePrepare } from './BasePrepare'; +export { default as CountLimiter } from './limiters/CountLimiter'; +export { default as TimeLimiter } from './limiters/TimeLimiter'; diff --git a/src/prepare/limiters/CountLimiter.js b/src/prepare/limiters/CountLimiter.js new file mode 100644 index 0000000..7fd0b70 --- /dev/null +++ b/src/prepare/limiters/CountLimiter.js @@ -0,0 +1,43 @@ +/** + * CountLimiter limits the number of items handled by a {@link PIXI.prepare.BasePrepare} to a specified + * number of items per frame. + * + * @class + * @memberof PIXI + */ +export default class CountLimiter { + /** + * @param {number} maxItemsPerFrame - The maximum number of items that can be prepared each frame. + */ + constructor(maxItemsPerFrame) + { + /** + * The maximum number of items that can be prepared each frame. + * @private + */ + this.maxItemsPerFrame = maxItemsPerFrame; + /** + * The number of items that can be prepared in the current frame. + * @type {number} + * @private + */ + this.itemsLeft = 0; + } + + /** + * Resets any counting properties to start fresh on a new frame. + */ + beginFrame() + { + this.itemsLeft = this.maxItemsPerFrame; + } + + /** + * Checks to see if another item can be uploaded. This should only be called once per item. + * @return {boolean} If the item is allowed to be uploaded. + */ + allowedToUpload() + { + return this.itemsLeft-- > 0; + } +} diff --git a/src/prepare/limiters/TimeLimiter.js b/src/prepare/limiters/TimeLimiter.js new file mode 100644 index 0000000..8908aba --- /dev/null +++ b/src/prepare/limiters/TimeLimiter.js @@ -0,0 +1,43 @@ +/** + * TimeLimiter limits the number of items handled by a {@link PIXI.BasePrepare} to a specified + * number of milliseconds per frame. + * + * @class + * @memberof PIXI + */ +export default class TimeLimiter { + /** + * @param {number} maxMilliseconds - The maximum milliseconds that can be spent preparing items each frame. + */ + constructor(maxMilliseconds) + { + /** + * The maximum milliseconds that can be spent preparing items each frame. + * @private + */ + this.maxMilliseconds = maxMilliseconds; + /** + * The start time of the current frame. + * @type {number} + * @private + */ + this.frameStart = 0; + } + + /** + * Resets any counting properties to start fresh on a new frame. + */ + beginFrame() + { + this.frameStart = Date.now(); + } + + /** + * Checks to see if another item can be uploaded. This should only be called once per item. + * @return {boolean} If the item is allowed to be uploaded. + */ + allowedToUpload() + { + return Date.now() - this.frameStart < this.maxMilliseconds; + } +} diff --git a/src/deprecation.js b/src/deprecation.js index 9d63a5e..56e45f4 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -3,6 +3,7 @@ import * as particles from './particles'; import * as extras from './extras'; import * as filters from './filters'; +import * as prepare from './prepare'; // provide method to give a stack track for warnings // useful for tracking-down where deprecated methods/properties/classes @@ -642,3 +643,49 @@ return saidHello; }, }); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.canvas.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.canvas, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); + +/** + * The number of graphics or textures to upload to the GPU. + * + * @name PIXI.prepare.webgl.UPLOADS_PER_FRAME + * @static + * @type {number} + * @see PIXI.prepare.BasePrepare.limiter + * @deprecated since 4.2.0 + */ +Object.defineProperty(prepare.webgl, 'UPLOADS_PER_FRAME', { + set() + { + warn('PIXI.CanvasPrepare.UPLOADS_PER_FRAME has been removed. Please set ' + + 'renderer.plugins.prepare.limiter.maxItemsPerFrame on your renderer'); + // because we don't have a reference to the renderer, we can't actually set + // the uploads per frame, so we'll have to stick with the warning. + }, + get() + { + return 4; + }, +}); diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js new file mode 100644 index 0000000..cb56559 --- /dev/null +++ b/src/prepare/BasePrepare.js @@ -0,0 +1,245 @@ +import * as core from '../core'; +import CountLimiter from './limiters/CountLimiter'; +const SharedTicker = core.ticker.shared; + +const DEFAULT_UPLOADS_PER_FRAME = 4; + +/** + * The prepare manager provides functionality to upload content to the GPU. BasePrepare handles + * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare} + * to provide preparation capabilities specific to their respective renderers. + * + * @abstract + * @class + * @memberof PIXI + */ +export default class BasePrepare +{ + /** + * @param {PIXI.SystemRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + /** + * The limiter to be used to control how quickly items are prepared. + * @type {PIXI.prepare.CountLimiter|PIXI.prepare.TimeLimiter} + */ + this.limiter = new CountLimiter(DEFAULT_UPLOADS_PER_FRAME); + + /** + * Reference to the renderer. + * @type {PIXI.SystemRenderer} + * @protected + */ + this.renderer = renderer; + + /** + * The only real difference between CanvasPrepare and WebGLPrepare is what they pass + * to upload hooks. That different parameter is stored here. + * @type {PIXI.prepare.CanvasPrepare|PIXI.WebGLRenderer} + * @protected + */ + this.uploadHookSource = null; + + /** + * Collection of items to uploads at once. + * @type {Array<*>} + * @private + */ + this.queue = []; + + /** + * Collection of additional hooks for finding assets. + * @type {Array} + * @private + */ + this.addHooks = []; + + /** + * Collection of additional hooks for processing assets. + * @type {Array} + * @private + */ + this.uploadHooks = []; + + /** + * Callback to call after completed. + * @type {Array} + * @private + */ + this.completes = []; + + /** + * If prepare is ticking (running). + * @type {boolean} + * @private + */ + this.ticking = false; + } + + /** + * Upload all the textures and graphics to the GPU. + * + * @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either + * the container or display object to search for items to upload or + * the callback function, if items have been added using `prepare.add`. + * @param {Function} [done] - Optional callback when all queued uploads have completed + */ + upload(item, done) + { + if (typeof item === 'function') + { + done = item; + item = null; + } + + // If a display object, search for items + // that we could upload + if (item) + { + this.add(item); + } + + // Get the items for upload from the display + if (this.queue.length) + { + if (done) + { + this.completes.push(done); + } + + if (!this.ticking) + { + this.ticking = true; + SharedTicker.add(this.tick, this); + } + } + else if (done) + { + done(); + } + } + + /** + * Handle tick update + * + * @private + */ + tick() + { + this.limiter.beginFrame(); + // Upload the graphics + while (this.queue.length && this.limiter.allowedToUpload()) + { + const item = this.queue[0]; + let uploaded = false; + + for (let i = 0, len = this.uploadHooks.length; i < len; i++) + { + if (this.uploadHooks[i](this.uploadHookSource, item)) + { + this.queue.shift(); + uploaded = true; + break; + } + } + + if (!uploaded) + { + this.queue.shift(); + } + } + + // We're finished + if (!this.queue.length) + { + this.ticking = false; + + SharedTicker.remove(this.tick, this); + + const completes = this.completes.slice(0); + + this.completes.length = 0; + + for (let i = 0, len = completes.length; i < len; i++) + { + completes[i](); + } + } + } + + /** + * Adds hooks for finding and uploading items. + * + * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` + function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + register(addHook, uploadHook) + { + if (addHook) + { + this.addHooks.push(addHook); + } + + if (uploadHook) + { + this.uploadHooks.push(uploadHook); + } + + return this; + } + + /** + * Manually add an item to the uploading queue. + * + * @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + add(item) + { + // Add additional hooks for finding elements on special + // types of objects that + for (let i = 0, len = this.addHooks.length; i < len; i++) + { + if (this.addHooks[i](item, this.queue)) + { + break; + } + } + + // Get childen recursively + if (item instanceof core.Container) + { + for (let i = item.children.length - 1; i >= 0; i--) + { + this.add(item.children[i]); + } + } + + return this; + } + + /** + * Destroys the plugin, don't use after this. + * + */ + destroy() + { + if (this.ticking) + { + SharedTicker.remove(this.tick, this); + } + this.ticking = false; + this.addHooks = null; + this.uploadHooks = null; + this.renderer = null; + this.completes = null; + this.queue = null; + this.limiter = null; + this.uploadHookSource = null; + } + +} diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 51dbfd0..d292d0d 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -1,8 +1,7 @@ import * as core from '../../core'; -const SharedTicker = core.ticker.shared; +import BasePrepare from '../BasePrepare'; const CANVAS_START_SIZE = 16; -const DEFAULT_UPLOADS_PER_FRAME = 4; /** * The prepare manager provides functionality to upload content to the GPU @@ -13,19 +12,16 @@ * @class * @memberof PIXI */ -export default class CanvasPrepare +export default class CanvasPrepare extends BasePrepare { /** * @param {PIXI.CanvasRenderer} renderer - A reference to the current renderer */ constructor(renderer) { - /** - * Reference to the renderer. - * @type {PIXI.CanvasRenderer} - * @private - */ - this.renderer = renderer; + super(renderer); + + this.uploadHookSource = this; /** * An offline canvas to render textures to @@ -43,212 +39,17 @@ */ this.ctx = this.canvas.getContext('2d'); - /** - * Collection of items to uploads at once. - * @type {Array<*>} - * @private - */ - this.queue = []; - - /** - * Collection of additional hooks for finding assets. - * @type {Array} - * @private - */ - this.addHooks = []; - - /** - * Collection of additional hooks for processing assets. - * @type {Array} - * @private - */ - this.uploadHooks = []; - - /** - * Callback to call after completed. - * @type {Array} - * @private - */ - this.completes = []; - - /** - * If prepare is ticking (running). - * @type {boolean} - * @private - */ - this.ticking = false; - // Add textures to upload this.register(findBaseTextures, uploadBaseTextures); } /** - * Upload all the textures and graphics to the GPU. - * - * @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either - * the container or display object to search for items to upload or - * the callback function, if items have been added using `prepare.add`. - * @param {Function} [done] - Optional callback when all queued uploads have completed - */ - upload(item, done) - { - if (typeof item === 'function') - { - done = item; - item = null; - } - - // If a display object, search for items - // that we could upload - if (item) - { - this.add(item); - } - - // Get the items for upload from the display - if (this.queue.length) - { - this.numLeft = CanvasPrepare.UPLOADS_PER_FRAME; - - if (done) - { - this.completes.push(done); - } - - if (!this.ticking) - { - this.ticking = true; - SharedTicker.add(this.tick, this); - } - } - else if (done) - { - done(); - } - } - - /** - * Handle tick update - * - * @private - */ - tick() - { - // Upload the graphics - while (this.queue.length && this.numLeft > 0) - { - const item = this.queue[0]; - let uploaded = false; - - for (let i = 0, len = this.uploadHooks.length; i < len; i++) - { - if (this.uploadHooks[i](this, item)) - { - this.numLeft--; - this.queue.shift(); - uploaded = true; - break; - } - } - - if (!uploaded) - { - this.queue.shift(); - } - } - - // We're finished - if (this.queue.length) - { - this.numLeft = CanvasPrepare.UPLOADS_PER_FRAME; - } - else - { - this.ticking = false; - - SharedTicker.remove(this.tick, this); - - const completes = this.completes.slice(0); - - this.completes.length = 0; - - for (let i = 0, len = completes.length; i < len; i++) - { - completes[i](); - } - } - } - - /** - * Adds hooks for finding and uploading items. - * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ - register(addHook, uploadHook) - { - if (addHook) - { - this.addHooks.push(addHook); - } - - if (uploadHook) - { - this.uploadHooks.push(uploadHook); - } - - return this; - } - - /** - * Manually add an item to the uploading queue. - * - * @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ - add(item) - { - // Add additional hooks for finding elements on special - // types of objects that - for (let i = 0, len = this.addHooks.length; i < len; i++) - { - if (this.addHooks[i](item, this.queue)) - { - break; - } - } - - // Get childen recursively - if (item instanceof core.Container) - { - for (let i = item.children.length - 1; i >= 0; i--) - { - this.add(item.children[i]); - } - } - - return this; - } - - /** * Destroys the plugin, don't use after this. * */ destroy() { - if (this.ticking) - { - SharedTicker.remove(this.tick, this); - } - this.ticking = false; - this.addHooks = null; - this.uploadHooks = null; - this.renderer = null; - this.completes = null; - this.queue = null; + super.destroy(); this.ctx = null; this.canvas = null; } @@ -256,15 +57,6 @@ } /** - * The number of graphics or textures to upload to the GPU. - * - * @static - * @type {number} - * @default 4 - */ -CanvasPrepare.UPLOADS_PER_FRAME = DEFAULT_UPLOADS_PER_FRAME; - -/** * Built-in hook to upload PIXI.Texture objects to the GPU. * * @private diff --git a/src/prepare/index.js b/src/prepare/index.js index 1aa203a..f559c45 100644 --- a/src/prepare/index.js +++ b/src/prepare/index.js @@ -3,3 +3,6 @@ */ export { default as webgl } from './webgl/WebGLPrepare'; export { default as canvas } from './canvas/CanvasPrepare'; +export { default as BasePrepare } from './BasePrepare'; +export { default as CountLimiter } from './limiters/CountLimiter'; +export { default as TimeLimiter } from './limiters/TimeLimiter'; diff --git a/src/prepare/limiters/CountLimiter.js b/src/prepare/limiters/CountLimiter.js new file mode 100644 index 0000000..7fd0b70 --- /dev/null +++ b/src/prepare/limiters/CountLimiter.js @@ -0,0 +1,43 @@ +/** + * CountLimiter limits the number of items handled by a {@link PIXI.prepare.BasePrepare} to a specified + * number of items per frame. + * + * @class + * @memberof PIXI + */ +export default class CountLimiter { + /** + * @param {number} maxItemsPerFrame - The maximum number of items that can be prepared each frame. + */ + constructor(maxItemsPerFrame) + { + /** + * The maximum number of items that can be prepared each frame. + * @private + */ + this.maxItemsPerFrame = maxItemsPerFrame; + /** + * The number of items that can be prepared in the current frame. + * @type {number} + * @private + */ + this.itemsLeft = 0; + } + + /** + * Resets any counting properties to start fresh on a new frame. + */ + beginFrame() + { + this.itemsLeft = this.maxItemsPerFrame; + } + + /** + * Checks to see if another item can be uploaded. This should only be called once per item. + * @return {boolean} If the item is allowed to be uploaded. + */ + allowedToUpload() + { + return this.itemsLeft-- > 0; + } +} diff --git a/src/prepare/limiters/TimeLimiter.js b/src/prepare/limiters/TimeLimiter.js new file mode 100644 index 0000000..8908aba --- /dev/null +++ b/src/prepare/limiters/TimeLimiter.js @@ -0,0 +1,43 @@ +/** + * TimeLimiter limits the number of items handled by a {@link PIXI.BasePrepare} to a specified + * number of milliseconds per frame. + * + * @class + * @memberof PIXI + */ +export default class TimeLimiter { + /** + * @param {number} maxMilliseconds - The maximum milliseconds that can be spent preparing items each frame. + */ + constructor(maxMilliseconds) + { + /** + * The maximum milliseconds that can be spent preparing items each frame. + * @private + */ + this.maxMilliseconds = maxMilliseconds; + /** + * The start time of the current frame. + * @type {number} + * @private + */ + this.frameStart = 0; + } + + /** + * Resets any counting properties to start fresh on a new frame. + */ + beginFrame() + { + this.frameStart = Date.now(); + } + + /** + * Checks to see if another item can be uploaded. This should only be called once per item. + * @return {boolean} If the item is allowed to be uploaded. + */ + allowedToUpload() + { + return Date.now() - this.frameStart < this.maxMilliseconds; + } +} diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index 8d2b5a6..7023948 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -1,7 +1,5 @@ import * as core from '../../core'; - -const SharedTicker = core.ticker.shared; -const DEFAULT_UPLOADS_PER_FRAME = 4; +import BasePrepare from '../BasePrepare'; /** * The prepare manager provides functionality to upload content to the GPU. @@ -9,241 +7,25 @@ * @class * @memberof PIXI */ -export default class WebGLPrepare +export default class WebGLPrepare extends BasePrepare { /** * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer */ constructor(renderer) { - /** - * Reference to the renderer. - * @type {PIXI.WebGLRenderer} - * @private - */ - this.renderer = renderer; + super(renderer); - /** - * Collection of items to uploads at once. - * @type {Array<*>} - * @private - */ - this.queue = []; - - /** - * Collection of additional hooks for finding assets. - * @type {Array} - * @private - */ - this.addHooks = []; - - /** - * Collection of additional hooks for processing assets. - * @type {Array} - * @private - */ - this.uploadHooks = []; - - /** - * Callback to call after completed. - * @type {Array} - * @private - */ - this.completes = []; - - /** - * If prepare is ticking (running). - * @type {boolean} - * @private - */ - this.ticking = false; + this.uploadHookSource = this.renderer; // Add textures and graphics to upload this.register(findBaseTextures, uploadBaseTextures) .register(findGraphics, uploadGraphics); } - /** - * Upload all the textures and graphics to the GPU. - * - * @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either - * the container or display object to search for items to upload or - * the callback function, if items have been added using `prepare.add`. - * @param {Function} [done] - Optional callback when all queued uploads have completed - */ - upload(item, done) - { - if (typeof item === 'function') - { - done = item; - item = null; - } - - // If a display object, search for items - // that we could upload - if (item) - { - this.add(item); - } - - // Get the items for upload from the display - if (this.queue.length) - { - this.numLeft = WebGLPrepare.UPLOADS_PER_FRAME; - - if (done) - { - this.completes.push(done); - } - - if (!this.ticking) - { - this.ticking = true; - SharedTicker.add(this.tick, this); - } - } - else if (done) - { - done(); - } - } - - /** - * Handle tick update. - * - * @private - */ - tick() - { - // Upload the graphics - while (this.queue.length && this.numLeft > 0) - { - const item = this.queue[0]; - let uploaded = false; - - for (let i = 0, len = this.uploadHooks.length; i < len; i++) - { - if (this.uploadHooks[i](this.renderer, item)) - { - this.numLeft--; - this.queue.shift(); - uploaded = true; - break; - } - } - - if (!uploaded) - { - this.queue.shift(); - } - } - - // We're finished - if (this.queue.length) - { - this.numLeft = WebGLPrepare.UPLOADS_PER_FRAME; - } - else - { - this.ticking = false; - - SharedTicker.remove(this.tick, this); - - const completes = this.completes.slice(0); - - this.completes.length = 0; - - for (let i = 0, len = completes.length; i < len; i++) - { - completes[i](); - } - } - } - - /** - * Adds hooks for finding and uploading items. - * - * @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] - Function call that takes two parameters: `renderer:WebGLRenderer, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.WebGLPrepare} Instance of plugin for chaining. - */ - register(addHook, uploadHook) - { - if (addHook) - { - this.addHooks.push(addHook); - } - - if (uploadHook) - { - this.uploadHooks.push(uploadHook); - } - - return this; - } - - /** - * Manually add an item to the uploading queue. - * - * @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue - * @return {PIXI.WebGLPrepare} Instance of plugin for chaining. - */ - add(item) - { - // Add additional hooks for finding elements on special - // types of objects that - for (let i = 0, len = this.addHooks.length; i < len; i++) - { - if (this.addHooks[i](item, this.queue)) - { - break; - } - } - - // Get childen recursively - if (item instanceof core.Container) - { - for (let i = item.children.length - 1; i >= 0; i--) - { - this.add(item.children[i]); - } - } - - return this; - } - - /** - * Destroys the plugin, don't use after this. - * - */ - destroy() - { - if (this.ticking) - { - SharedTicker.remove(this.tick, this); - } - this.ticking = false; - this.addHooks = null; - this.uploadHooks = null; - this.renderer = null; - this.completes = null; - this.queue = null; - } - } /** - * The number of graphics or textures to upload to the GPU - * - * @static - * @type {number} - * @default 4 - */ -WebGLPrepare.UPLOADS_PER_FRAME = DEFAULT_UPLOADS_PER_FRAME; - -/** * Built-in hook to upload PIXI.Texture objects to the GPU. * * @private