Source: prepare/BasePrepare.js

prepare/BasePrepare.js

  1. import * as core from '../core';
  2. import CountLimiter from './limiters/CountLimiter';
  3. const SharedTicker = core.ticker.shared;
  4. /**
  5. * Default number of uploads per frame using prepare plugin.
  6. *
  7. * @static
  8. * @memberof PIXI.settings
  9. * @name UPLOADS_PER_FRAME
  10. * @type {number}
  11. * @default 4
  12. */
  13. core.settings.UPLOADS_PER_FRAME = 4;
  14. /**
  15. * The prepare manager provides functionality to upload content to the GPU. BasePrepare handles
  16. * basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare}
  17. * to provide preparation capabilities specific to their respective renderers.
  18. *
  19. * @example
  20. * // Create a sprite
  21. * const sprite = new PIXI.Sprite.fromImage('something.png');
  22. *
  23. * // Load object into GPU
  24. * app.renderer.plugins.prepare.upload(sprite, () => {
  25. *
  26. * //Texture(s) has been uploaded to GPU
  27. * app.stage.addChild(sprite);
  28. *
  29. * })
  30. *
  31. * @abstract
  32. * @class
  33. * @memberof PIXI.prepare
  34. */
  35. export default class BasePrepare
  36. {
  37. /**
  38. * @param {PIXI.SystemRenderer} renderer - A reference to the current renderer
  39. */
  40. constructor(renderer)
  41. {
  42. /**
  43. * The limiter to be used to control how quickly items are prepared.
  44. * @type {PIXI.prepare.CountLimiter|PIXI.prepare.TimeLimiter}
  45. */
  46. this.limiter = new CountLimiter(core.settings.UPLOADS_PER_FRAME);
  47. /**
  48. * Reference to the renderer.
  49. * @type {PIXI.SystemRenderer}
  50. * @protected
  51. */
  52. this.renderer = renderer;
  53. /**
  54. * The only real difference between CanvasPrepare and WebGLPrepare is what they pass
  55. * to upload hooks. That different parameter is stored here.
  56. * @type {PIXI.prepare.CanvasPrepare|PIXI.WebGLRenderer}
  57. * @protected
  58. */
  59. this.uploadHookHelper = null;
  60. /**
  61. * Collection of items to uploads at once.
  62. * @type {Array<*>}
  63. * @private
  64. */
  65. this.queue = [];
  66. /**
  67. * Collection of additional hooks for finding assets.
  68. * @type {Array<Function>}
  69. * @private
  70. */
  71. this.addHooks = [];
  72. /**
  73. * Collection of additional hooks for processing assets.
  74. * @type {Array<Function>}
  75. * @private
  76. */
  77. this.uploadHooks = [];
  78. /**
  79. * Callback to call after completed.
  80. * @type {Array<Function>}
  81. * @private
  82. */
  83. this.completes = [];
  84. /**
  85. * If prepare is ticking (running).
  86. * @type {boolean}
  87. * @private
  88. */
  89. this.ticking = false;
  90. /**
  91. * 'bound' call for prepareItems().
  92. * @type {Function}
  93. * @private
  94. */
  95. this.delayedTick = () =>
  96. {
  97. // unlikely, but in case we were destroyed between tick() and delayedTick()
  98. if (!this.queue)
  99. {
  100. return;
  101. }
  102. this.prepareItems();
  103. };
  104. // hooks to find the correct texture
  105. this.registerFindHook(findText);
  106. this.registerFindHook(findTextStyle);
  107. this.registerFindHook(findMultipleBaseTextures);
  108. this.registerFindHook(findBaseTexture);
  109. this.registerFindHook(findTexture);
  110. // upload hooks
  111. this.registerUploadHook(drawText);
  112. this.registerUploadHook(calculateTextStyle);
  113. }
  114. /**
  115. * Upload all the textures and graphics to the GPU.
  116. *
  117. * @param {Function|PIXI.DisplayObject|PIXI.Container|PIXI.BaseTexture|PIXI.Texture|PIXI.Graphics|PIXI.Text} item -
  118. * Either the container or display object to search for items to upload, the items to upload themselves,
  119. * or the callback function, if items have been added using `prepare.add`.
  120. * @param {Function} [done] - Optional callback when all queued uploads have completed
  121. */
  122. upload(item, done)
  123. {
  124. if (typeof item === 'function')
  125. {
  126. done = item;
  127. item = null;
  128. }
  129. // If a display object, search for items
  130. // that we could upload
  131. if (item)
  132. {
  133. this.add(item);
  134. }
  135. // Get the items for upload from the display
  136. if (this.queue.length)
  137. {
  138. if (done)
  139. {
  140. this.completes.push(done);
  141. }
  142. if (!this.ticking)
  143. {
  144. this.ticking = true;
  145. SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY);
  146. }
  147. }
  148. else if (done)
  149. {
  150. done();
  151. }
  152. }
  153. /**
  154. * Handle tick update
  155. *
  156. * @private
  157. */
  158. tick()
  159. {
  160. setTimeout(this.delayedTick, 0);
  161. }
  162. /**
  163. * Actually prepare items. This is handled outside of the tick because it will take a while
  164. * and we do NOT want to block the current animation frame from rendering.
  165. *
  166. * @private
  167. */
  168. prepareItems()
  169. {
  170. this.limiter.beginFrame();
  171. // Upload the graphics
  172. while (this.queue.length && this.limiter.allowedToUpload())
  173. {
  174. const item = this.queue[0];
  175. let uploaded = false;
  176. if (item && !item._destroyed)
  177. {
  178. for (let i = 0, len = this.uploadHooks.length; i < len; i++)
  179. {
  180. if (this.uploadHooks[i](this.uploadHookHelper, item))
  181. {
  182. this.queue.shift();
  183. uploaded = true;
  184. break;
  185. }
  186. }
  187. }
  188. if (!uploaded)
  189. {
  190. this.queue.shift();
  191. }
  192. }
  193. // We're finished
  194. if (!this.queue.length)
  195. {
  196. this.ticking = false;
  197. const completes = this.completes.slice(0);
  198. this.completes.length = 0;
  199. for (let i = 0, len = completes.length; i < len; i++)
  200. {
  201. completes[i]();
  202. }
  203. }
  204. else
  205. {
  206. // if we are not finished, on the next rAF do this again
  207. SharedTicker.addOnce(this.tick, this, core.UPDATE_PRIORITY.UTILITY);
  208. }
  209. }
  210. /**
  211. * Adds hooks for finding items.
  212. *
  213. * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array`
  214. * function must return `true` if it was able to add item to the queue.
  215. * @return {PIXI.BasePrepare} Instance of plugin for chaining.
  216. */
  217. registerFindHook(addHook)
  218. {
  219. if (addHook)
  220. {
  221. this.addHooks.push(addHook);
  222. }
  223. return this;
  224. }
  225. /**
  226. * Adds hooks for uploading items.
  227. *
  228. * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and
  229. * function must return `true` if it was able to handle upload of item.
  230. * @return {PIXI.BasePrepare} Instance of plugin for chaining.
  231. */
  232. registerUploadHook(uploadHook)
  233. {
  234. if (uploadHook)
  235. {
  236. this.uploadHooks.push(uploadHook);
  237. }
  238. return this;
  239. }
  240. /**
  241. * Manually add an item to the uploading queue.
  242. *
  243. * @param {PIXI.DisplayObject|PIXI.Container|PIXI.BaseTexture|PIXI.Texture|PIXI.Graphics|PIXI.Text|*} item - Object to
  244. * add to the queue
  245. * @return {PIXI.CanvasPrepare} Instance of plugin for chaining.
  246. */
  247. add(item)
  248. {
  249. // Add additional hooks for finding elements on special
  250. // types of objects that
  251. for (let i = 0, len = this.addHooks.length; i < len; i++)
  252. {
  253. if (this.addHooks[i](item, this.queue))
  254. {
  255. break;
  256. }
  257. }
  258. // Get childen recursively
  259. if (item instanceof core.Container)
  260. {
  261. for (let i = item.children.length - 1; i >= 0; i--)
  262. {
  263. this.add(item.children[i]);
  264. }
  265. }
  266. return this;
  267. }
  268. /**
  269. * Destroys the plugin, don't use after this.
  270. *
  271. */
  272. destroy()
  273. {
  274. if (this.ticking)
  275. {
  276. SharedTicker.remove(this.tick, this);
  277. }
  278. this.ticking = false;
  279. this.addHooks = null;
  280. this.uploadHooks = null;
  281. this.renderer = null;
  282. this.completes = null;
  283. this.queue = null;
  284. this.limiter = null;
  285. this.uploadHookHelper = null;
  286. }
  287. }
  288. /**
  289. * Built-in hook to find multiple textures from objects like AnimatedSprites.
  290. *
  291. * @private
  292. * @param {PIXI.DisplayObject} item - Display object to check
  293. * @param {Array<*>} queue - Collection of items to upload
  294. * @return {boolean} if a PIXI.Texture object was found.
  295. */
  296. function findMultipleBaseTextures(item, queue)
  297. {
  298. let result = false;
  299. // Objects with mutliple textures
  300. if (item && item._textures && item._textures.length)
  301. {
  302. for (let i = 0; i < item._textures.length; i++)
  303. {
  304. if (item._textures[i] instanceof core.Texture)
  305. {
  306. const baseTexture = item._textures[i].baseTexture;
  307. if (queue.indexOf(baseTexture) === -1)
  308. {
  309. queue.push(baseTexture);
  310. result = true;
  311. }
  312. }
  313. }
  314. }
  315. return result;
  316. }
  317. /**
  318. * Built-in hook to find BaseTextures from Sprites.
  319. *
  320. * @private
  321. * @param {PIXI.DisplayObject} item - Display object to check
  322. * @param {Array<*>} queue - Collection of items to upload
  323. * @return {boolean} if a PIXI.Texture object was found.
  324. */
  325. function findBaseTexture(item, queue)
  326. {
  327. // Objects with textures, like Sprites/Text
  328. if (item instanceof core.BaseTexture)
  329. {
  330. if (queue.indexOf(item) === -1)
  331. {
  332. queue.push(item);
  333. }
  334. return true;
  335. }
  336. return false;
  337. }
  338. /**
  339. * Built-in hook to find textures from objects.
  340. *
  341. * @private
  342. * @param {PIXI.DisplayObject} item - Display object to check
  343. * @param {Array<*>} queue - Collection of items to upload
  344. * @return {boolean} if a PIXI.Texture object was found.
  345. */
  346. function findTexture(item, queue)
  347. {
  348. if (item._texture && item._texture instanceof core.Texture)
  349. {
  350. const texture = item._texture.baseTexture;
  351. if (queue.indexOf(texture) === -1)
  352. {
  353. queue.push(texture);
  354. }
  355. return true;
  356. }
  357. return false;
  358. }
  359. /**
  360. * Built-in hook to draw PIXI.Text to its texture.
  361. *
  362. * @private
  363. * @param {PIXI.WebGLRenderer|PIXI.CanvasPrepare} helper - Not used by this upload handler
  364. * @param {PIXI.DisplayObject} item - Item to check
  365. * @return {boolean} If item was uploaded.
  366. */
  367. function drawText(helper, item)
  368. {
  369. if (item instanceof core.Text)
  370. {
  371. // updating text will return early if it is not dirty
  372. item.updateText(true);
  373. return true;
  374. }
  375. return false;
  376. }
  377. /**
  378. * Built-in hook to calculate a text style for a PIXI.Text object.
  379. *
  380. * @private
  381. * @param {PIXI.WebGLRenderer|PIXI.CanvasPrepare} helper - Not used by this upload handler
  382. * @param {PIXI.DisplayObject} item - Item to check
  383. * @return {boolean} If item was uploaded.
  384. */
  385. function calculateTextStyle(helper, item)
  386. {
  387. if (item instanceof core.TextStyle)
  388. {
  389. const font = item.toFontString();
  390. core.TextMetrics.measureFont(font);
  391. return true;
  392. }
  393. return false;
  394. }
  395. /**
  396. * Built-in hook to find Text objects.
  397. *
  398. * @private
  399. * @param {PIXI.DisplayObject} item - Display object to check
  400. * @param {Array<*>} queue - Collection of items to upload
  401. * @return {boolean} if a PIXI.Text object was found.
  402. */
  403. function findText(item, queue)
  404. {
  405. if (item instanceof core.Text)
  406. {
  407. // push the text style to prepare it - this can be really expensive
  408. if (queue.indexOf(item.style) === -1)
  409. {
  410. queue.push(item.style);
  411. }
  412. // also push the text object so that we can render it (to canvas/texture) if needed
  413. if (queue.indexOf(item) === -1)
  414. {
  415. queue.push(item);
  416. }
  417. // also push the Text's texture for upload to GPU
  418. const texture = item._texture.baseTexture;
  419. if (queue.indexOf(texture) === -1)
  420. {
  421. queue.push(texture);
  422. }
  423. return true;
  424. }
  425. return false;
  426. }
  427. /**
  428. * Built-in hook to find TextStyle objects.
  429. *
  430. * @private
  431. * @param {PIXI.TextStyle} item - Display object to check
  432. * @param {Array<*>} queue - Collection of items to upload
  433. * @return {boolean} if a PIXI.TextStyle object was found.
  434. */
  435. function findTextStyle(item, queue)
  436. {
  437. if (item instanceof core.TextStyle)
  438. {
  439. if (queue.indexOf(item) === -1)
  440. {
  441. queue.push(item);
  442. }
  443. return true;
  444. }
  445. return false;
  446. }