prepare.mjs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. /*!
  2. * @pixi/prepare - v6.5.10
  3. * Compiled Thu, 06 Jul 2023 15:25:11 UTC
  4. *
  5. * @pixi/prepare is licensed under the MIT License.
  6. * http://www.opensource.org/licenses/mit-license
  7. */
  8. import { settings } from '@pixi/settings';
  9. import { Texture, BaseTexture, ExtensionType } from '@pixi/core';
  10. import { Graphics } from '@pixi/graphics';
  11. import { Ticker, UPDATE_PRIORITY } from '@pixi/ticker';
  12. import { Container } from '@pixi/display';
  13. import { Text, TextStyle, TextMetrics } from '@pixi/text';
  14. import { deprecation } from '@pixi/utils';
  15. /**
  16. * Default number of uploads per frame using prepare plugin.
  17. * @static
  18. * @memberof PIXI.settings
  19. * @name UPLOADS_PER_FRAME
  20. * @type {number}
  21. * @default 4
  22. */
  23. settings.UPLOADS_PER_FRAME = 4;
  24. /*! *****************************************************************************
  25. Copyright (c) Microsoft Corporation.
  26. Permission to use, copy, modify, and/or distribute this software for any
  27. purpose with or without fee is hereby granted.
  28. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
  29. REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  30. AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
  31. INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  32. LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
  33. OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  34. PERFORMANCE OF THIS SOFTWARE.
  35. ***************************************************************************** */
  36. /* global Reflect, Promise */
  37. var extendStatics = function(d, b) {
  38. extendStatics = Object.setPrototypeOf ||
  39. ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
  40. function (d, b) { for (var p in b) { if (b.hasOwnProperty(p)) { d[p] = b[p]; } } };
  41. return extendStatics(d, b);
  42. };
  43. function __extends(d, b) {
  44. extendStatics(d, b);
  45. function __() { this.constructor = d; }
  46. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  47. }
  48. /**
  49. * CountLimiter limits the number of items handled by a {@link PIXI.BasePrepare} to a specified
  50. * number of items per frame.
  51. * @memberof PIXI
  52. */
  53. var CountLimiter = /** @class */ (function () {
  54. /**
  55. * @param maxItemsPerFrame - The maximum number of items that can be prepared each frame.
  56. */
  57. function CountLimiter(maxItemsPerFrame) {
  58. this.maxItemsPerFrame = maxItemsPerFrame;
  59. this.itemsLeft = 0;
  60. }
  61. /** Resets any counting properties to start fresh on a new frame. */
  62. CountLimiter.prototype.beginFrame = function () {
  63. this.itemsLeft = this.maxItemsPerFrame;
  64. };
  65. /**
  66. * Checks to see if another item can be uploaded. This should only be called once per item.
  67. * @returns If the item is allowed to be uploaded.
  68. */
  69. CountLimiter.prototype.allowedToUpload = function () {
  70. return this.itemsLeft-- > 0;
  71. };
  72. return CountLimiter;
  73. }());
  74. /**
  75. * Built-in hook to find multiple textures from objects like AnimatedSprites.
  76. * @private
  77. * @param item - Display object to check
  78. * @param queue - Collection of items to upload
  79. * @returns If a PIXI.Texture object was found.
  80. */
  81. function findMultipleBaseTextures(item, queue) {
  82. var result = false;
  83. // Objects with multiple textures
  84. if (item && item._textures && item._textures.length) {
  85. for (var i = 0; i < item._textures.length; i++) {
  86. if (item._textures[i] instanceof Texture) {
  87. var baseTexture = item._textures[i].baseTexture;
  88. if (queue.indexOf(baseTexture) === -1) {
  89. queue.push(baseTexture);
  90. result = true;
  91. }
  92. }
  93. }
  94. }
  95. return result;
  96. }
  97. /**
  98. * Built-in hook to find BaseTextures from Texture.
  99. * @private
  100. * @param item - Display object to check
  101. * @param queue - Collection of items to upload
  102. * @returns If a PIXI.Texture object was found.
  103. */
  104. function findBaseTexture(item, queue) {
  105. if (item.baseTexture instanceof BaseTexture) {
  106. var texture = item.baseTexture;
  107. if (queue.indexOf(texture) === -1) {
  108. queue.push(texture);
  109. }
  110. return true;
  111. }
  112. return false;
  113. }
  114. /**
  115. * Built-in hook to find textures from objects.
  116. * @private
  117. * @param item - Display object to check
  118. * @param queue - Collection of items to upload
  119. * @returns If a PIXI.Texture object was found.
  120. */
  121. function findTexture(item, queue) {
  122. if (item._texture && item._texture instanceof Texture) {
  123. var texture = item._texture.baseTexture;
  124. if (queue.indexOf(texture) === -1) {
  125. queue.push(texture);
  126. }
  127. return true;
  128. }
  129. return false;
  130. }
  131. /**
  132. * Built-in hook to draw PIXI.Text to its texture.
  133. * @private
  134. * @param _helper - Not used by this upload handler
  135. * @param item - Item to check
  136. * @returns If item was uploaded.
  137. */
  138. function drawText(_helper, item) {
  139. if (item instanceof Text) {
  140. // updating text will return early if it is not dirty
  141. item.updateText(true);
  142. return true;
  143. }
  144. return false;
  145. }
  146. /**
  147. * Built-in hook to calculate a text style for a PIXI.Text object.
  148. * @private
  149. * @param _helper - Not used by this upload handler
  150. * @param item - Item to check
  151. * @returns If item was uploaded.
  152. */
  153. function calculateTextStyle(_helper, item) {
  154. if (item instanceof TextStyle) {
  155. var font = item.toFontString();
  156. TextMetrics.measureFont(font);
  157. return true;
  158. }
  159. return false;
  160. }
  161. /**
  162. * Built-in hook to find Text objects.
  163. * @private
  164. * @param item - Display object to check
  165. * @param queue - Collection of items to upload
  166. * @returns if a PIXI.Text object was found.
  167. */
  168. function findText(item, queue) {
  169. if (item instanceof Text) {
  170. // push the text style to prepare it - this can be really expensive
  171. if (queue.indexOf(item.style) === -1) {
  172. queue.push(item.style);
  173. }
  174. // also push the text object so that we can render it (to canvas/texture) if needed
  175. if (queue.indexOf(item) === -1) {
  176. queue.push(item);
  177. }
  178. // also push the Text's texture for upload to GPU
  179. var texture = item._texture.baseTexture;
  180. if (queue.indexOf(texture) === -1) {
  181. queue.push(texture);
  182. }
  183. return true;
  184. }
  185. return false;
  186. }
  187. /**
  188. * Built-in hook to find TextStyle objects.
  189. * @private
  190. * @param item - Display object to check
  191. * @param queue - Collection of items to upload
  192. * @returns If a PIXI.TextStyle object was found.
  193. */
  194. function findTextStyle(item, queue) {
  195. if (item instanceof TextStyle) {
  196. if (queue.indexOf(item) === -1) {
  197. queue.push(item);
  198. }
  199. return true;
  200. }
  201. return false;
  202. }
  203. /**
  204. * The prepare manager provides functionality to upload content to the GPU.
  205. *
  206. * BasePrepare handles basic queuing functionality and is extended by
  207. * {@link PIXI.Prepare} and {@link PIXI.CanvasPrepare}
  208. * to provide preparation capabilities specific to their respective renderers.
  209. * @example
  210. * // Create a sprite
  211. * const sprite = PIXI.Sprite.from('something.png');
  212. *
  213. * // Load object into GPU
  214. * app.renderer.plugins.prepare.upload(sprite, () => {
  215. *
  216. * //Texture(s) has been uploaded to GPU
  217. * app.stage.addChild(sprite);
  218. *
  219. * })
  220. * @abstract
  221. * @memberof PIXI
  222. */
  223. var BasePrepare = /** @class */ (function () {
  224. /**
  225. * @param {PIXI.AbstractRenderer} renderer - A reference to the current renderer
  226. */
  227. function BasePrepare(renderer) {
  228. var _this = this;
  229. this.limiter = new CountLimiter(settings.UPLOADS_PER_FRAME);
  230. this.renderer = renderer;
  231. this.uploadHookHelper = null;
  232. this.queue = [];
  233. this.addHooks = [];
  234. this.uploadHooks = [];
  235. this.completes = [];
  236. this.ticking = false;
  237. this.delayedTick = function () {
  238. // unlikely, but in case we were destroyed between tick() and delayedTick()
  239. if (!_this.queue) {
  240. return;
  241. }
  242. _this.prepareItems();
  243. };
  244. // hooks to find the correct texture
  245. this.registerFindHook(findText);
  246. this.registerFindHook(findTextStyle);
  247. this.registerFindHook(findMultipleBaseTextures);
  248. this.registerFindHook(findBaseTexture);
  249. this.registerFindHook(findTexture);
  250. // upload hooks
  251. this.registerUploadHook(drawText);
  252. this.registerUploadHook(calculateTextStyle);
  253. }
  254. /** @ignore */
  255. BasePrepare.prototype.upload = function (item, done) {
  256. var _this = this;
  257. if (typeof item === 'function') {
  258. done = item;
  259. item = null;
  260. }
  261. if (done) {
  262. deprecation('6.5.0', 'BasePrepare.upload callback is deprecated, use the return Promise instead.');
  263. }
  264. return new Promise(function (resolve) {
  265. // If a display object, search for items
  266. // that we could upload
  267. if (item) {
  268. _this.add(item);
  269. }
  270. // TODO: remove done callback and just use resolve
  271. var complete = function () {
  272. done === null || done === void 0 ? void 0 : done();
  273. resolve();
  274. };
  275. // Get the items for upload from the display
  276. if (_this.queue.length) {
  277. _this.completes.push(complete);
  278. if (!_this.ticking) {
  279. _this.ticking = true;
  280. Ticker.system.addOnce(_this.tick, _this, UPDATE_PRIORITY.UTILITY);
  281. }
  282. }
  283. else {
  284. complete();
  285. }
  286. });
  287. };
  288. /**
  289. * Handle tick update
  290. * @private
  291. */
  292. BasePrepare.prototype.tick = function () {
  293. setTimeout(this.delayedTick, 0);
  294. };
  295. /**
  296. * Actually prepare items. This is handled outside of the tick because it will take a while
  297. * and we do NOT want to block the current animation frame from rendering.
  298. * @private
  299. */
  300. BasePrepare.prototype.prepareItems = function () {
  301. this.limiter.beginFrame();
  302. // Upload the graphics
  303. while (this.queue.length && this.limiter.allowedToUpload()) {
  304. var item = this.queue[0];
  305. var uploaded = false;
  306. if (item && !item._destroyed) {
  307. for (var i = 0, len = this.uploadHooks.length; i < len; i++) {
  308. if (this.uploadHooks[i](this.uploadHookHelper, item)) {
  309. this.queue.shift();
  310. uploaded = true;
  311. break;
  312. }
  313. }
  314. }
  315. if (!uploaded) {
  316. this.queue.shift();
  317. }
  318. }
  319. // We're finished
  320. if (!this.queue.length) {
  321. this.ticking = false;
  322. var completes = this.completes.slice(0);
  323. this.completes.length = 0;
  324. for (var i = 0, len = completes.length; i < len; i++) {
  325. completes[i]();
  326. }
  327. }
  328. else {
  329. // if we are not finished, on the next rAF do this again
  330. Ticker.system.addOnce(this.tick, this, UPDATE_PRIORITY.UTILITY);
  331. }
  332. };
  333. /**
  334. * Adds hooks for finding items.
  335. * @param {Function} addHook - Function call that takes two parameters: `item:*, queue:Array`
  336. * function must return `true` if it was able to add item to the queue.
  337. * @returns Instance of plugin for chaining.
  338. */
  339. BasePrepare.prototype.registerFindHook = function (addHook) {
  340. if (addHook) {
  341. this.addHooks.push(addHook);
  342. }
  343. return this;
  344. };
  345. /**
  346. * Adds hooks for uploading items.
  347. * @param {Function} uploadHook - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and
  348. * function must return `true` if it was able to handle upload of item.
  349. * @returns Instance of plugin for chaining.
  350. */
  351. BasePrepare.prototype.registerUploadHook = function (uploadHook) {
  352. if (uploadHook) {
  353. this.uploadHooks.push(uploadHook);
  354. }
  355. return this;
  356. };
  357. /**
  358. * Manually add an item to the uploading queue.
  359. * @param {PIXI.DisplayObject|PIXI.Container|PIXI.BaseTexture|PIXI.Texture|PIXI.Graphics|PIXI.Text|*} item - Object to
  360. * add to the queue
  361. * @returns Instance of plugin for chaining.
  362. */
  363. BasePrepare.prototype.add = function (item) {
  364. // Add additional hooks for finding elements on special
  365. // types of objects that
  366. for (var i = 0, len = this.addHooks.length; i < len; i++) {
  367. if (this.addHooks[i](item, this.queue)) {
  368. break;
  369. }
  370. }
  371. // Get children recursively
  372. if (item instanceof Container) {
  373. for (var i = item.children.length - 1; i >= 0; i--) {
  374. this.add(item.children[i]);
  375. }
  376. }
  377. return this;
  378. };
  379. /** Destroys the plugin, don't use after this. */
  380. BasePrepare.prototype.destroy = function () {
  381. if (this.ticking) {
  382. Ticker.system.remove(this.tick, this);
  383. }
  384. this.ticking = false;
  385. this.addHooks = null;
  386. this.uploadHooks = null;
  387. this.renderer = null;
  388. this.completes = null;
  389. this.queue = null;
  390. this.limiter = null;
  391. this.uploadHookHelper = null;
  392. };
  393. return BasePrepare;
  394. }());
  395. /**
  396. * Built-in hook to upload PIXI.Texture objects to the GPU.
  397. * @private
  398. * @param renderer - instance of the webgl renderer
  399. * @param item - Item to check
  400. * @returns If item was uploaded.
  401. */
  402. function uploadBaseTextures(renderer, item) {
  403. if (item instanceof BaseTexture) {
  404. // if the texture already has a GL texture, then the texture has been prepared or rendered
  405. // before now. If the texture changed, then the changer should be calling texture.update() which
  406. // reuploads the texture without need for preparing it again
  407. if (!item._glTextures[renderer.CONTEXT_UID]) {
  408. renderer.texture.bind(item);
  409. }
  410. return true;
  411. }
  412. return false;
  413. }
  414. /**
  415. * Built-in hook to upload PIXI.Graphics to the GPU.
  416. * @private
  417. * @param renderer - instance of the webgl renderer
  418. * @param item - Item to check
  419. * @returns If item was uploaded.
  420. */
  421. function uploadGraphics(renderer, item) {
  422. if (!(item instanceof Graphics)) {
  423. return false;
  424. }
  425. var geometry = item.geometry;
  426. // update dirty graphics to get batches
  427. item.finishPoly();
  428. geometry.updateBatches();
  429. var batches = geometry.batches;
  430. // upload all textures found in styles
  431. for (var i = 0; i < batches.length; i++) {
  432. var texture = batches[i].style.texture;
  433. if (texture) {
  434. uploadBaseTextures(renderer, texture.baseTexture);
  435. }
  436. }
  437. // if its not batchable - update vao for particular shader
  438. if (!geometry.batchable) {
  439. renderer.geometry.bind(geometry, item._resolveDirectShader(renderer));
  440. }
  441. return true;
  442. }
  443. /**
  444. * Built-in hook to find graphics.
  445. * @private
  446. * @param item - Display object to check
  447. * @param queue - Collection of items to upload
  448. * @returns if a PIXI.Graphics object was found.
  449. */
  450. function findGraphics(item, queue) {
  451. if (item instanceof Graphics) {
  452. queue.push(item);
  453. return true;
  454. }
  455. return false;
  456. }
  457. /**
  458. * The prepare plugin provides renderer-specific plugins for pre-rendering DisplayObjects. These plugins are useful for
  459. * asynchronously preparing and uploading to the GPU assets, textures, graphics waiting to be displayed.
  460. *
  461. * Do not instantiate this plugin directly. It is available from the `renderer.plugins` property.
  462. * See {@link PIXI.CanvasRenderer#plugins} or {@link PIXI.Renderer#plugins}.
  463. * @example
  464. * // Create a new application
  465. * const app = new PIXI.Application();
  466. * document.body.appendChild(app.view);
  467. *
  468. * // Don't start rendering right away
  469. * app.stop();
  470. *
  471. * // create a display object
  472. * const rect = new PIXI.Graphics()
  473. * .beginFill(0x00ff00)
  474. * .drawRect(40, 40, 200, 200);
  475. *
  476. * // Add to the stage
  477. * app.stage.addChild(rect);
  478. *
  479. * // Don't start rendering until the graphic is uploaded to the GPU
  480. * app.renderer.plugins.prepare.upload(app.stage, () => {
  481. * app.start();
  482. * });
  483. * @memberof PIXI
  484. */
  485. var Prepare = /** @class */ (function (_super) {
  486. __extends(Prepare, _super);
  487. /**
  488. * @param {PIXI.Renderer} renderer - A reference to the current renderer
  489. */
  490. function Prepare(renderer) {
  491. var _this = _super.call(this, renderer) || this;
  492. _this.uploadHookHelper = _this.renderer;
  493. // Add textures and graphics to upload
  494. _this.registerFindHook(findGraphics);
  495. _this.registerUploadHook(uploadBaseTextures);
  496. _this.registerUploadHook(uploadGraphics);
  497. return _this;
  498. }
  499. /** @ignore */
  500. Prepare.extension = {
  501. name: 'prepare',
  502. type: ExtensionType.RendererPlugin,
  503. };
  504. return Prepare;
  505. }(BasePrepare));
  506. /**
  507. * TimeLimiter limits the number of items handled by a {@link PIXI.BasePrepare} to a specified
  508. * number of milliseconds per frame.
  509. * @memberof PIXI
  510. */
  511. var TimeLimiter = /** @class */ (function () {
  512. /** @param maxMilliseconds - The maximum milliseconds that can be spent preparing items each frame. */
  513. function TimeLimiter(maxMilliseconds) {
  514. this.maxMilliseconds = maxMilliseconds;
  515. this.frameStart = 0;
  516. }
  517. /** Resets any counting properties to start fresh on a new frame. */
  518. TimeLimiter.prototype.beginFrame = function () {
  519. this.frameStart = Date.now();
  520. };
  521. /**
  522. * Checks to see if another item can be uploaded. This should only be called once per item.
  523. * @returns - If the item is allowed to be uploaded.
  524. */
  525. TimeLimiter.prototype.allowedToUpload = function () {
  526. return Date.now() - this.frameStart < this.maxMilliseconds;
  527. };
  528. return TimeLimiter;
  529. }());
  530. export { BasePrepare, CountLimiter, Prepare, TimeLimiter };
  531. //# sourceMappingURL=prepare.mjs.map