├── .eslintignore
├── .gitattributes
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .npmignore
├── README.md
├── common
├── config
│ └── rush
│ │ ├── .npmrc
│ │ ├── .npmrc-publish
│ │ ├── command-line.json
│ │ ├── common-versions.json
│ │ ├── experiments.json
│ │ ├── pnpm-lock.yaml
│ │ └── version-policies.json
├── git-hooks
│ └── commit-msg.sample
└── scripts
│ ├── install-run-rush-pnpm.js
│ ├── install-run-rush.js
│ ├── install-run-rushx.js
│ └── install-run.js
├── package.json
├── packages
├── batch-renderer
│ ├── .jsdoc.conf.json
│ ├── .npmignore
│ ├── CHANGELOG.json
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── api-extractor.json
│ ├── package.json
│ ├── src
│ │ ├── AggregateUniformsBatch.ts
│ │ ├── AggregateUniformsBatchFactory.ts
│ │ ├── BatchDrawer.ts
│ │ ├── BatchGeometryFactory.ts
│ │ ├── BatchRenderer.ts
│ │ ├── BatchRendererPluginFactory.ts
│ │ ├── BatchShaderFactory.ts
│ │ ├── StdBatch.ts
│ │ ├── StdBatchFactory.ts
│ │ ├── index.ts
│ │ ├── redirects
│ │ │ ├── AttributeRedirect.ts
│ │ │ ├── Redirect.ts
│ │ │ ├── UniformRedirect.ts
│ │ │ └── index.ts
│ │ ├── resolve
│ │ │ ├── index.ts
│ │ │ ├── resolveConstantOrProperty.ts
│ │ │ └── resolveFunctionOrProperty.ts
│ │ └── utils
│ │ │ ├── BufferPool.ts
│ │ │ └── resolveProperty.ts
│ ├── test
│ │ ├── BufferPool.js
│ │ └── index.js
│ └── tsconfig.json
└── diffy
│ ├── .eslintrc
│ ├── .npmignore
│ ├── CHANGELOG.json
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── api-extractor.json
│ ├── package.json
│ ├── src
│ ├── DiffBuffer.ts
│ ├── DiffDrawer.ts
│ ├── DiffGeometry.ts
│ ├── DiffGeometryFactory.ts
│ ├── index.ts
│ └── utils
│ │ ├── BufferInvalidation.ts
│ │ ├── BufferInvalidationQueue.ts
│ │ └── uploadBuffer.ts
│ ├── test
│ ├── BufferInvalidationQueue.js
│ └── index.js
│ └── tsconfig.json
├── rush.json
└── tools
└── unit-tests
├── index.js
├── package.json
└── test.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | /lib
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Don't allow people to merge changes to these generated files, because the result
2 | # may be invalid. You need to run "rush update" again.
3 | pnpm-lock.yaml merge=binary
4 | shrinkwrap.yaml merge=binary
5 | npm-shrinkwrap.json merge=binary
6 | yarn.lock merge=binary
7 |
8 | # Rush's JSON config files use JavaScript-style code comments. The rule below prevents pedantic
9 | # syntax highlighters such as GitHub's from highlighting these comments as errors. Your text editor
10 | # may also require a special configuration to allow comments in JSON.
11 | #
12 | # For more information, see this issue: https://github.com/microsoft/rushstack/issues/1088
13 | #
14 | *.json linguist-language=JSON-with-Comments
15 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [14.x]
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 | - name: Use Node.js ${{ matrix.node-version }}
24 | uses: actions/setup-node@v1
25 | with:
26 | node-version: ${{ matrix.node-version }}
27 | - uses: pnpm/action-setup@v1.2.1
28 | with:
29 | version: 5.17.2
30 | - run: pnpm install -g @microsoft/rush
31 | - run: rush install
32 | - run: rush rebuild
33 | - uses: GabrielBB/xvfb-action@v1.0
34 | with:
35 | run: rush unit-test
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | example/node_modules
3 | docs
4 | .DS_Store
5 | compile
6 | lib
7 | dist
8 | index.d.ts
9 |
10 | # Logs
11 | *.log
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 |
16 | # Runtime data
17 | *.pid
18 | *.seed
19 | *.pid.lock
20 |
21 | # Directory for instrumented libs generated by jscoverage/JSCover
22 | lib-cov
23 |
24 | # Coverage directory used by tools like istanbul
25 | coverage
26 |
27 | # nyc test coverage
28 | .nyc_output
29 |
30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
31 | .grunt
32 |
33 | # Bower dependency directory (https://bower.io/)
34 | bower_components
35 |
36 | # node-waf configuration
37 | .lock-wscript
38 |
39 | # Compiled binary addons (https://nodejs.org/api/addons.html)
40 | build/Release
41 |
42 | # Dependency directories
43 | node_modules/
44 | jspm_packages/
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
55 | # Output of 'npm pack'
56 | *.tgz
57 |
58 | # Yarn Integrity file
59 | .yarn-integrity
60 |
61 | # dotenv environment variables file
62 | .env
63 |
64 | # next.js build output
65 | .next
66 |
67 | # OS X temporary files
68 | .DS_Store
69 |
70 | # Rush temporary files
71 | common/deploy/
72 | common/temp/
73 | common/autoinstallers/*/.npmrc
74 | **/.rush/temp/
75 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | example
3 | docs
4 | src
5 | compile
6 |
7 | # Build Tools
8 | .eslintrc.json
9 | .eslintignore
10 | .jsdoc.conf.json
11 | rollup.config.js
12 | tsconfig.json
13 | README.md
14 | api-extractor.json
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PixiJS Batch Rendering Kit
2 |
3 |
4 |
5 |
6 |
7 | [](https://github.com/pixijs/batch/actions/workflows/node.js.yml)
8 |
9 | This project has been converted into a monorepo. See the [pixi-batch-renderer package](https://github.com/pixijs/pixi-batch-renderer/tree/master/packages/pixi-batch-renderer) for the original documentation.
10 |
--------------------------------------------------------------------------------
/common/config/rush/.npmrc:
--------------------------------------------------------------------------------
1 | # Rush uses this file to configure the NPM package registry during installation. It is applicable
2 | # to PNPM, NPM, and Yarn package managers. It is used by operations such as "rush install",
3 | # "rush update", and the "install-run.js" scripts.
4 | #
5 | # NOTE: The "rush publish" command uses .npmrc-publish instead.
6 | #
7 | # Before invoking the package manager, Rush will copy this file to the folder where installation
8 | # is performed. The copied file will omit any config lines that reference environment variables
9 | # that are undefined in that session; this avoids problems that would otherwise result due to
10 | # a missing variable being replaced by an empty string.
11 | #
12 | # * * * SECURITY WARNING * * *
13 | #
14 | # It is NOT recommended to store authentication tokens in a text file on a lab machine, because
15 | # other unrelated processes may be able to read the file. Also, the file may persist indefinitely,
16 | # for example if the machine loses power. A safer practice is to pass the token via an
17 | # environment variable, which can be referenced from .npmrc using ${} expansion. For example:
18 | #
19 | # //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}
20 | #
21 | registry=https://registry.npmjs.org/
22 | always-auth=false
23 |
--------------------------------------------------------------------------------
/common/config/rush/.npmrc-publish:
--------------------------------------------------------------------------------
1 | # This config file is very similar to common/config/rush/.npmrc, except that .npmrc-publish
2 | # is used by the "rush publish" command, as publishing often involves different credentials
3 | # and registries than other operations.
4 | #
5 | # Before invoking the package manager, Rush will copy this file to "common/temp/publish-home/.npmrc"
6 | # and then temporarily map that folder as the "home directory" for the current user account.
7 | # This enables the same settings to apply for each project folder that gets published. The copied file
8 | # will omit any config lines that reference environment variables that are undefined in that session;
9 | # this avoids problems that would otherwise result due to a missing variable being replaced by
10 | # an empty string.
11 | #
12 | # * * * SECURITY WARNING * * *
13 | #
14 | # It is NOT recommended to store authentication tokens in a text file on a lab machine, because
15 | # other unrelated processes may be able to read the file. Also, the file may persist indefinitely,
16 | # for example if the machine loses power. A safer practice is to pass the token via an
17 | # environment variable, which can be referenced from .npmrc using ${} expansion. For example:
18 | #
19 | # //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}
20 | #
21 |
--------------------------------------------------------------------------------
/common/config/rush/command-line.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json",
3 | "commands": [
4 | {
5 | "commandKind": "global",
6 | "name": "unit-test",
7 | "summary": "Unit testing",
8 | "description": "Unit test each project",
9 | "shellCommand": "pnpm test"
10 | },
11 | {
12 | "commandKind": "bulk",
13 | "name": "build",
14 | "summary": "Build",
15 | "description": "Bundle all flavors & generate typings declaration files",
16 | "enableParallelism": true,
17 | "safeForSimultaneousRushProcesses": false
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/common/config/rush/common-versions.json:
--------------------------------------------------------------------------------
1 | /**
2 | * This configuration file specifies NPM dependency version selections that affect all projects
3 | * in a Rush repo. For full documentation, please see https://rushjs.io
4 | */
5 | {
6 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json",
7 |
8 | /**
9 | * A table that specifies a "preferred version" for a given NPM package. This feature is typically used
10 | * to hold back an indirect dependency to a specific older version, or to reduce duplication of indirect dependencies.
11 | *
12 | * The "preferredVersions" value can be any SemVer range specifier (e.g. "~1.2.3"). Rush injects these values into
13 | * the "dependencies" field of the top-level common/temp/package.json, which influences how the package manager
14 | * will calculate versions. The specific effect depends on your package manager. Generally it will have no
15 | * effect on an incompatible or already constrained SemVer range. If you are using PNPM, similar effects can be
16 | * achieved using the pnpmfile.js hook. See the Rush documentation for more details.
17 | *
18 | * After modifying this field, it's recommended to run "rush update --full" so that the package manager
19 | * will recalculate all version selections.
20 | */
21 | "preferredVersions": {
22 | /**
23 | * When someone asks for "^1.0.0" make sure they get "1.2.3" when working in this repo,
24 | * instead of the latest version.
25 | */
26 | // "some-library": "1.2.3"
27 | },
28 |
29 | /**
30 | * When set to true, for all projects in the repo, all dependencies will be automatically added as preferredVersions,
31 | * except in cases where different projects specify different version ranges for a given dependency. For older
32 | * package managers, this tended to reduce duplication of indirect dependencies. However, it can sometimes cause
33 | * trouble for indirect dependencies with incompatible peerDependencies ranges.
34 | *
35 | * The default value is true. If you're encountering installation errors related to peer dependencies,
36 | * it's recommended to set this to false.
37 | *
38 | * After modifying this field, it's recommended to run "rush update --full" so that the package manager
39 | * will recalculate all version selections.
40 | */
41 | // "implicitlyPreferredVersions": false,
42 |
43 | /**
44 | * The "rush check" command can be used to enforce that every project in the repo must specify
45 | * the same SemVer range for a given dependency. However, sometimes exceptions are needed.
46 | * The allowedAlternativeVersions table allows you to list other SemVer ranges that will be
47 | * accepted by "rush check" for a given dependency.
48 | *
49 | * IMPORTANT: THIS TABLE IS FOR *ADDITIONAL* VERSION RANGES THAT ARE ALTERNATIVES TO THE
50 | * USUAL VERSION (WHICH IS INFERRED BY LOOKING AT ALL PROJECTS IN THE REPO).
51 | * This design avoids unnecessary churn in this file.
52 | */
53 | "allowedAlternativeVersions": {
54 | /**
55 | * For example, allow some projects to use an older TypeScript compiler
56 | * (in addition to whatever "usual" version is being used by other projects in the repo):
57 | */
58 | // "typescript": [
59 | // "~2.4.0"
60 | // ]
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/common/config/rush/experiments.json:
--------------------------------------------------------------------------------
1 | /**
2 | * This configuration file allows repo maintainers to enable and disable experimental
3 | * Rush features. For full documentation, please see https://rushjs.io
4 | */
5 | {
6 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/experiments.schema.json",
7 |
8 | /**
9 | * Rush 5.14.0 improved incremental builds to ignore spurious changes in the pnpm-lock.json file.
10 | * This optimization is enabled by default. If you encounter a problem where "rush build" is neglecting
11 | * to build some projects, please open a GitHub issue. As a workaround you can uncomment this line
12 | * to temporarily restore the old behavior where everything must be rebuilt whenever pnpm-lock.json
13 | * is modified.
14 | */
15 | // "legacyIncrementalBuildDependencyDetection": true,
16 |
17 | /**
18 | * By default, rush passes --no-prefer-frozen-lockfile to 'pnpm install'.
19 | * Set this option to true to pass '--frozen-lockfile' instead.
20 | */
21 | // "usePnpmFrozenLockfileForRushInstall": true,
22 |
23 | /**
24 | * If true, the chmod field in temporary project tar headers will not be normalized.
25 | * This normalization can help ensure consistent tarball integrity across platforms.
26 | */
27 | // "noChmodFieldInTarHeaderNormalization": true
28 | }
29 |
--------------------------------------------------------------------------------
/common/config/rush/version-policies.json:
--------------------------------------------------------------------------------
1 | /**
2 | * This is configuration file is used for advanced publishing configurations with Rush.
3 | * For full documentation, please see https://rushjs.io
4 | */
5 |
6 | /**
7 | * A list of version policy definitions. A "version policy" is a custom package versioning
8 | * strategy that affects "rush change", "rush version", and "rush publish". The strategy applies
9 | * to a set of projects that are specified using the "versionPolicyName" field in rush.json.
10 | */
11 | [
12 | // {
13 | // /**
14 | // * (Required) Indicates the kind of version policy being defined ("lockStepVersion" or "individualVersion").
15 | // *
16 | // * The "lockStepVersion" mode specifies that the projects will use "lock-step versioning". This
17 | // * strategy is appropriate for a set of packages that act as selectable components of a
18 | // * unified product. The entire set of packages are always published together, and always share
19 | // * the same NPM version number. When the packages depend on other packages in the set, the
20 | // * SemVer range is usually restricted to a single version.
21 | // */
22 | // "definitionName": "lockStepVersion",
23 | //
24 | // /**
25 | // * (Required) The name that will be used for the "versionPolicyName" field in rush.json.
26 | // * This name is also used command-line parameters such as "--version-policy"
27 | // * and "--to-version-policy".
28 | // */
29 | // "policyName": "MyBigFramework",
30 | //
31 | // /**
32 | // * (Required) The current version. All packages belonging to the set should have this version
33 | // * in the current branch. When bumping versions, Rush uses this to determine the next version.
34 | // * (The "version" field in package.json is NOT considered.)
35 | // */
36 | // "version": "1.0.0",
37 | //
38 | // /**
39 | // * (Required) The type of bump that will be performed when publishing the next release.
40 | // * When creating a release branch in Git, this field should be updated according to the
41 | // * type of release.
42 | // *
43 | // * Valid values are: "prerelease", "release", "minor", "patch", "major"
44 | // */
45 | // "nextBump": "prerelease",
46 | //
47 | // /**
48 | // * (Optional) If specified, all packages in the set share a common CHANGELOG.md file.
49 | // * This file is stored with the specified "main" project, which must be a member of the set.
50 | // *
51 | // * If this field is omitted, then a separate CHANGELOG.md file will be maintained for each
52 | // * package in the set.
53 | // */
54 | // "mainProject": "my-app"
55 | // },
56 | //
57 | // {
58 | // /**
59 | // * (Required) Indicates the kind of version policy being defined ("lockStepVersion" or "individualVersion").
60 | // *
61 | // * The "individualVersion" mode specifies that the projects will use "individual versioning".
62 | // * This is the typical NPM model where each package has an independent version number
63 | // * and CHANGELOG.md file. Although a single CI definition is responsible for publishing the
64 | // * packages, they otherwise don't have any special relationship. The version bumping will
65 | // * depend on how developers answer the "rush change" questions for each package that
66 | // * is changed.
67 | // */
68 | // "definitionName": "individualVersion",
69 | //
70 | // "policyName": "MyRandomLibraries",
71 | //
72 | // /**
73 | // * (Optional) This can be used to enforce that all packages in the set must share a common
74 | // * major version number, e.g. because they are from the same major release branch.
75 | // * It can also be used to discourage people from accidentally making "MAJOR" SemVer changes
76 | // * inappropriately. The minor/patch version parts will be bumped independently according
77 | // * to the types of changes made to each project, according to the "rush change" command.
78 | // */
79 | // "lockedMajor": 3,
80 | //
81 | // /**
82 | // * (Optional) When publishing is managed by Rush, by default the "rush change" command will
83 | // * request changes for any projects that are modified by a pull request. These change entries
84 | // * will produce a CHANGELOG.md file. If you author your CHANGELOG.md manually or announce updates
85 | // * in some other way, set "exemptFromRushChange" to true to tell "rush change" to ignore the projects
86 | // * belonging to this version policy.
87 | // */
88 | // "exemptFromRushChange": false
89 | // }
90 | ]
91 |
--------------------------------------------------------------------------------
/common/git-hooks/commit-msg.sample:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # This is an example Git hook for use with Rush. To enable this hook, rename this file
4 | # to "commit-msg" and then run "rush install", which will copy it from common/git-hooks
5 | # to the .git/hooks folder.
6 | #
7 | # TO LEARN MORE ABOUT GIT HOOKS
8 | #
9 | # The Git documentation is here: https://git-scm.com/docs/githooks
10 | # Some helpful resources: https://githooks.com
11 | #
12 | # ABOUT THIS EXAMPLE
13 | #
14 | # The commit-msg hook is called by "git commit" with one argument, the name of the file
15 | # that has the commit message. The hook should exit with non-zero status after issuing
16 | # an appropriate message if it wants to stop the commit. The hook is allowed to edit
17 | # the commit message file.
18 |
19 | # This example enforces that commit message should contain a minimum amount of
20 | # description text.
21 | if [ `cat $1 | wc -w` -lt 3 ]; then
22 | echo ""
23 | echo "Invalid commit message: The message must contain at least 3 words."
24 | exit 1
25 | fi
26 |
--------------------------------------------------------------------------------
/common/scripts/install-run-rush-pnpm.js:
--------------------------------------------------------------------------------
1 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
2 | //
3 | // This script is intended for usage in an automated build environment where the Rush command may not have
4 | // been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush
5 | // specified in the rush.json configuration file (if not already installed), and then pass a command-line to the
6 | // rush-pnpm command.
7 | //
8 | // An example usage would be:
9 | //
10 | // node common/scripts/install-run-rush-pnpm.js pnpm-command
11 | //
12 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
13 |
14 | /******/ (() => { // webpackBootstrap
15 | /******/ "use strict";
16 | var __webpack_exports__ = {};
17 | /*!*****************************************************!*\
18 | !*** ./lib-esnext/scripts/install-run-rush-pnpm.js ***!
19 | \*****************************************************/
20 |
21 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22 | // See the @microsoft/rush package's LICENSE file for license information.
23 | require('./install-run-rush');
24 | //# sourceMappingURL=install-run-rush-pnpm.js.map
25 | module.exports = __webpack_exports__;
26 | /******/ })()
27 | ;
28 | //# sourceMappingURL=install-run-rush-pnpm.js.map
--------------------------------------------------------------------------------
/common/scripts/install-run-rush.js:
--------------------------------------------------------------------------------
1 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
2 | //
3 | // This script is intended for usage in an automated build environment where the Rush command may not have
4 | // been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush
5 | // specified in the rush.json configuration file (if not already installed), and then pass a command-line to it.
6 | // An example usage would be:
7 | //
8 | // node common/scripts/install-run-rush.js install
9 | //
10 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
11 |
12 | /******/ (() => { // webpackBootstrap
13 | /******/ "use strict";
14 | /******/ var __webpack_modules__ = ({
15 |
16 | /***/ 657147:
17 | /*!*********************!*\
18 | !*** external "fs" ***!
19 | \*********************/
20 | /***/ ((module) => {
21 |
22 | module.exports = require("fs");
23 |
24 | /***/ }),
25 |
26 | /***/ 371017:
27 | /*!***********************!*\
28 | !*** external "path" ***!
29 | \***********************/
30 | /***/ ((module) => {
31 |
32 | module.exports = require("path");
33 |
34 | /***/ })
35 |
36 | /******/ });
37 | /************************************************************************/
38 | /******/ // The module cache
39 | /******/ var __webpack_module_cache__ = {};
40 | /******/
41 | /******/ // The require function
42 | /******/ function __webpack_require__(moduleId) {
43 | /******/ // Check if module is in cache
44 | /******/ var cachedModule = __webpack_module_cache__[moduleId];
45 | /******/ if (cachedModule !== undefined) {
46 | /******/ return cachedModule.exports;
47 | /******/ }
48 | /******/ // Create a new module (and put it into the cache)
49 | /******/ var module = __webpack_module_cache__[moduleId] = {
50 | /******/ // no module.id needed
51 | /******/ // no module.loaded needed
52 | /******/ exports: {}
53 | /******/ };
54 | /******/
55 | /******/ // Execute the module function
56 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
57 | /******/
58 | /******/ // Return the exports of the module
59 | /******/ return module.exports;
60 | /******/ }
61 | /******/
62 | /************************************************************************/
63 | /******/ /* webpack/runtime/compat get default export */
64 | /******/ (() => {
65 | /******/ // getDefaultExport function for compatibility with non-harmony modules
66 | /******/ __webpack_require__.n = (module) => {
67 | /******/ var getter = module && module.__esModule ?
68 | /******/ () => (module['default']) :
69 | /******/ () => (module);
70 | /******/ __webpack_require__.d(getter, { a: getter });
71 | /******/ return getter;
72 | /******/ };
73 | /******/ })();
74 | /******/
75 | /******/ /* webpack/runtime/define property getters */
76 | /******/ (() => {
77 | /******/ // define getter functions for harmony exports
78 | /******/ __webpack_require__.d = (exports, definition) => {
79 | /******/ for(var key in definition) {
80 | /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
81 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
82 | /******/ }
83 | /******/ }
84 | /******/ };
85 | /******/ })();
86 | /******/
87 | /******/ /* webpack/runtime/hasOwnProperty shorthand */
88 | /******/ (() => {
89 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
90 | /******/ })();
91 | /******/
92 | /******/ /* webpack/runtime/make namespace object */
93 | /******/ (() => {
94 | /******/ // define __esModule on exports
95 | /******/ __webpack_require__.r = (exports) => {
96 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
97 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
98 | /******/ }
99 | /******/ Object.defineProperty(exports, '__esModule', { value: true });
100 | /******/ };
101 | /******/ })();
102 | /******/
103 | /************************************************************************/
104 | var __webpack_exports__ = {};
105 | // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
106 | (() => {
107 | /*!************************************************!*\
108 | !*** ./lib-esnext/scripts/install-run-rush.js ***!
109 | \************************************************/
110 | __webpack_require__.r(__webpack_exports__);
111 | /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! path */ 371017);
112 | /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__);
113 | /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! fs */ 657147);
114 | /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__);
115 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
116 | // See the @microsoft/rush package's LICENSE file for license information.
117 |
118 |
119 | const { installAndRun, findRushJsonFolder, RUSH_JSON_FILENAME, runWithErrorAndStatusCode } = require('./install-run');
120 | const PACKAGE_NAME = '@microsoft/rush';
121 | const RUSH_PREVIEW_VERSION = 'RUSH_PREVIEW_VERSION';
122 | const INSTALL_RUN_RUSH_LOCKFILE_PATH_VARIABLE = 'INSTALL_RUN_RUSH_LOCKFILE_PATH';
123 | function _getRushVersion(logger) {
124 | const rushPreviewVersion = process.env[RUSH_PREVIEW_VERSION];
125 | if (rushPreviewVersion !== undefined) {
126 | logger.info(`Using Rush version from environment variable ${RUSH_PREVIEW_VERSION}=${rushPreviewVersion}`);
127 | return rushPreviewVersion;
128 | }
129 | const rushJsonFolder = findRushJsonFolder();
130 | const rushJsonPath = path__WEBPACK_IMPORTED_MODULE_0__.join(rushJsonFolder, RUSH_JSON_FILENAME);
131 | try {
132 | const rushJsonContents = fs__WEBPACK_IMPORTED_MODULE_1__.readFileSync(rushJsonPath, 'utf-8');
133 | // Use a regular expression to parse out the rushVersion value because rush.json supports comments,
134 | // but JSON.parse does not and we don't want to pull in more dependencies than we need to in this script.
135 | const rushJsonMatches = rushJsonContents.match(/\"rushVersion\"\s*\:\s*\"([0-9a-zA-Z.+\-]+)\"/);
136 | return rushJsonMatches[1];
137 | }
138 | catch (e) {
139 | throw new Error(`Unable to determine the required version of Rush from rush.json (${rushJsonFolder}). ` +
140 | "The 'rushVersion' field is either not assigned in rush.json or was specified " +
141 | 'using an unexpected syntax.');
142 | }
143 | }
144 | function _getBin(scriptName) {
145 | switch (scriptName.toLowerCase()) {
146 | case 'install-run-rush-pnpm.js':
147 | return 'rush-pnpm';
148 | case 'install-run-rushx.js':
149 | return 'rushx';
150 | default:
151 | return 'rush';
152 | }
153 | }
154 | function _run() {
155 | const [nodePath /* Ex: /bin/node */, scriptPath /* /repo/common/scripts/install-run-rush.js */, ...packageBinArgs /* [build, --to, myproject] */] = process.argv;
156 | // Detect if this script was directly invoked, or if the install-run-rushx script was invokved to select the
157 | // appropriate binary inside the rush package to run
158 | const scriptName = path__WEBPACK_IMPORTED_MODULE_0__.basename(scriptPath);
159 | const bin = _getBin(scriptName);
160 | if (!nodePath || !scriptPath) {
161 | throw new Error('Unexpected exception: could not detect node path or script path');
162 | }
163 | let commandFound = false;
164 | let logger = { info: console.log, error: console.error };
165 | for (const arg of packageBinArgs) {
166 | if (arg === '-q' || arg === '--quiet') {
167 | // The -q/--quiet flag is supported by both `rush` and `rushx`, and will suppress
168 | // any normal informational/diagnostic information printed during startup.
169 | //
170 | // To maintain the same user experience, the install-run* scripts pass along this
171 | // flag but also use it to suppress any diagnostic information normally printed
172 | // to stdout.
173 | logger = {
174 | info: () => { },
175 | error: console.error
176 | };
177 | }
178 | else if (!arg.startsWith('-') || arg === '-h' || arg === '--help') {
179 | // We either found something that looks like a command (i.e. - doesn't start with a "-"),
180 | // or we found the -h/--help flag, which can be run without a command
181 | commandFound = true;
182 | }
183 | }
184 | if (!commandFound) {
185 | console.log(`Usage: ${scriptName} [args...]`);
186 | if (scriptName === 'install-run-rush-pnpm.js') {
187 | console.log(`Example: ${scriptName} pnpm-command`);
188 | }
189 | else if (scriptName === 'install-run-rush.js') {
190 | console.log(`Example: ${scriptName} build --to myproject`);
191 | }
192 | else {
193 | console.log(`Example: ${scriptName} custom-command`);
194 | }
195 | process.exit(1);
196 | }
197 | runWithErrorAndStatusCode(logger, () => {
198 | const version = _getRushVersion(logger);
199 | logger.info(`The rush.json configuration requests Rush version ${version}`);
200 | const lockFilePath = process.env[INSTALL_RUN_RUSH_LOCKFILE_PATH_VARIABLE];
201 | if (lockFilePath) {
202 | logger.info(`Found ${INSTALL_RUN_RUSH_LOCKFILE_PATH_VARIABLE}="${lockFilePath}", installing with lockfile.`);
203 | }
204 | return installAndRun(logger, PACKAGE_NAME, version, bin, packageBinArgs, lockFilePath);
205 | });
206 | }
207 | _run();
208 | //# sourceMappingURL=install-run-rush.js.map
209 | })();
210 |
211 | module.exports = __webpack_exports__;
212 | /******/ })()
213 | ;
214 | //# sourceMappingURL=install-run-rush.js.map
--------------------------------------------------------------------------------
/common/scripts/install-run-rushx.js:
--------------------------------------------------------------------------------
1 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
2 | //
3 | // This script is intended for usage in an automated build environment where the Rush command may not have
4 | // been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush
5 | // specified in the rush.json configuration file (if not already installed), and then pass a command-line to the
6 | // rushx command.
7 | //
8 | // An example usage would be:
9 | //
10 | // node common/scripts/install-run-rushx.js custom-command
11 | //
12 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
13 |
14 | /******/ (() => { // webpackBootstrap
15 | /******/ "use strict";
16 | var __webpack_exports__ = {};
17 | /*!*************************************************!*\
18 | !*** ./lib-esnext/scripts/install-run-rushx.js ***!
19 | \*************************************************/
20 |
21 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22 | // See the @microsoft/rush package's LICENSE file for license information.
23 | require('./install-run-rush');
24 | //# sourceMappingURL=install-run-rushx.js.map
25 | module.exports = __webpack_exports__;
26 | /******/ })()
27 | ;
28 | //# sourceMappingURL=install-run-rushx.js.map
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pixi-pbr-monorepo",
3 | "version": "1.0.0",
4 | "description": "PixiJS Batch Rendering Library",
5 | "private": true,
6 | "scripts": {
7 | "test": "node ./tools/unit-tests/index.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/SukantPal/pixi-batch-renderer.git"
12 | },
13 | "keywords": [
14 | "pixijs",
15 | "batch-renderer",
16 | "optimizations",
17 | "webgl"
18 | ],
19 | "author": "Shukant K. Pal ",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/SukantPal/pixi-batch-renderer/issues"
23 | },
24 | "homepage": "https://github.com/SukantPal/pixi-batch-renderer#readme"
25 | }
--------------------------------------------------------------------------------
/packages/batch-renderer/.jsdoc.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "tags": {
3 | "allowUnknownTags": false
4 | },
5 | "source": {
6 | "include": [
7 | "lib/pixi-batch-renderer.es.js"
8 | ],
9 | "includePattern": ".+\\.es.js(doc)?$"
10 | },
11 | "plugins": [
12 | "plugins/markdown"
13 | ],
14 | "templates": {
15 | "cleverLinks": false,
16 | "monospaceLinks": false,
17 | "default": {
18 | "outputSourceFiles": true
19 | },
20 | "applicationName": "pixi-batch-renderer",
21 | "footer": "Made with ♥ by Shukant Pal",
22 | "copyright": "Copyright © 2019-2020 Shukant Pal",
23 | "disqus": "",
24 | "openGraph": {
25 | "title": "",
26 | "type": "website",
27 | "image": "",
28 | "site_name": "",
29 | "url": ""
30 | },
31 | "meta": {
32 | "title": "PixiJS Batch Rendering Library API Documentation",
33 | "description": "Documentation for pixi-batch-renderer library",
34 | "keyword": "docs, documentation, pixi, pixijs, renderer, html5, javascript, jsdoc"
35 | },
36 | "linenums": true,
37 | "sort": false
38 | },
39 | "markdown": {
40 | "parser": "gfm",
41 | "hardwrap": false
42 | },
43 | "opts": {
44 | "encoding": "utf8",
45 | "recurse": true,
46 | "lenient": true,
47 | "destination": "./docs",
48 | "template": "./node_modules/@pixi/jsdoc-template",
49 | "sort": false
50 | }
51 | }
--------------------------------------------------------------------------------
/packages/batch-renderer/.npmignore:
--------------------------------------------------------------------------------
1 | compile
2 | api-extractor.json
3 | *.log
4 | tsconfig.json
5 | src
6 | *.json
7 | CHANGELOG.md
8 | docs
--------------------------------------------------------------------------------
/packages/batch-renderer/CHANGELOG.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pixi-batch-renderer",
3 | "entries": [
4 | {
5 | "version": "2.5.3",
6 | "tag": "pixi-batch-renderer_v2.5.3",
7 | "date": "Tue, 06 Apr 2021 13:59:04 GMT",
8 | "comments": {
9 | "patch": [
10 | {
11 | "comment": "Fix texture-lookup attribute not being filled correctly for multi-texture geometres (i.e. texturesPerObject > 1)"
12 | }
13 | ]
14 | }
15 | },
16 | {
17 | "version": "2.5.1",
18 | "tag": "pixi-batch-renderer_v2.5.1",
19 | "date": "Sun, 28 Mar 2021 19:51:30 GMT",
20 | "comments": {
21 | "patch": [
22 | {
23 | "comment": "Fix vertexSizeFor crashing pixi-batch-renderer due to dangling reference to PIXI."
24 | }
25 | ]
26 | }
27 | },
28 | {
29 | "version": "2.5.0",
30 | "tag": "pixi-batch-renderer_v2.5.0",
31 | "date": "Sun, 28 Mar 2021 19:20:49 GMT",
32 | "comments": {
33 | "minor": [
34 | {
35 | "comment": "Upgrade to PixiJS 6"
36 | }
37 | ]
38 | }
39 | },
40 | {
41 | "version": "2.4.2",
42 | "tag": "pixi-batch-renderer_v2.4.2",
43 | "date": "Mon, 19 Oct 2020 00:38:23 GMT",
44 | "comments": {
45 | "patch": [
46 | {
47 | "comment": "Fixes the incorrect resolution of vertex count in geometry merging"
48 | }
49 | ]
50 | }
51 | },
52 | {
53 | "version": "2.4.1",
54 | "tag": "pixi-batch-renderer_v2.4.1",
55 | "date": "Mon, 19 Oct 2020 00:28:47 GMT",
56 | "comments": {
57 | "patch": [
58 | {
59 | "comment": "Fixes the resolution of the vertex count if vertexCountProperty was not passed."
60 | }
61 | ]
62 | }
63 | },
64 | {
65 | "version": "2.4.0",
66 | "tag": "pixi-batch-renderer_v2.4.0",
67 | "date": "Mon, 19 Oct 2020 00:17:18 GMT",
68 | "comments": {
69 | "minor": [
70 | {
71 | "comment": "Support manual resolution of the number of indices to be batched."
72 | }
73 | ]
74 | }
75 | },
76 | {
77 | "version": "2.3.7",
78 | "tag": "pixi-batch-renderer_v2.3.7",
79 | "date": "Sun, 18 Oct 2020 23:46:38 GMT",
80 | "comments": {
81 | "patch": [
82 | {
83 | "comment": "Support passing vertexCountProperty as a functor."
84 | }
85 | ]
86 | }
87 | },
88 | {
89 | "version": "2.3.6",
90 | "tag": "pixi-batch-renderer_v2.3.6",
91 | "date": "Sun, 30 Aug 2020 15:11:17 GMT",
92 | "comments": {
93 | "patch": [
94 | {
95 | "comment": "Added unit-test for BufferPool, fixed crash when the an unallocated buffer was released."
96 | }
97 | ]
98 | }
99 | },
100 | {
101 | "version": "2.3.5",
102 | "tag": "pixi-batch-renderer_v2.3.5",
103 | "date": "Sat, 29 Aug 2020 19:06:52 GMT",
104 | "comments": {
105 | "patch": [
106 | {
107 | "comment": "Don't upload docs, compile to npm"
108 | }
109 | ]
110 | }
111 | },
112 | {
113 | "version": "2.3.4",
114 | "tag": "pixi-batch-renderer_v2.3.4",
115 | "date": "Sat, 29 Aug 2020 19:02:11 GMT",
116 | "comments": {
117 | "patch": [
118 | {
119 | "comment": ".cjs -> .js, .mjs -> .es.js for bundles"
120 | },
121 | {
122 | "comment": "BufferPool API!"
123 | }
124 | ]
125 | }
126 | }
127 | ]
128 | }
129 |
--------------------------------------------------------------------------------
/packages/batch-renderer/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log - pixi-batch-renderer
2 |
3 | This log was last generated on Tue, 06 Apr 2021 13:59:04 GMT and should not be manually modified.
4 |
5 | ## 2.5.3
6 | Tue, 06 Apr 2021 13:59:04 GMT
7 |
8 | ### Patches
9 |
10 | - Fix texture-lookup attribute not being filled correctly for multi-texture geometres (i.e. texturesPerObject > 1)
11 |
12 | ## 2.5.1
13 | Sun, 28 Mar 2021 19:51:30 GMT
14 |
15 | ### Patches
16 |
17 | - Fix vertexSizeFor crashing pixi-batch-renderer due to dangling reference to PIXI.
18 |
19 | ## 2.5.0
20 | Sun, 28 Mar 2021 19:20:49 GMT
21 |
22 | ### Minor changes
23 |
24 | - Upgrade to PixiJS 6
25 |
26 | ## 2.4.2
27 | Mon, 19 Oct 2020 00:38:23 GMT
28 |
29 | ### Patches
30 |
31 | - Fixes the incorrect resolution of vertex count in geometry merging
32 |
33 | ## 2.4.1
34 | Mon, 19 Oct 2020 00:28:47 GMT
35 |
36 | ### Patches
37 |
38 | - Fixes the resolution of the vertex count if vertexCountProperty was not passed.
39 |
40 | ## 2.4.0
41 | Mon, 19 Oct 2020 00:17:18 GMT
42 |
43 | ### Minor changes
44 |
45 | - Support manual resolution of the number of indices to be batched.
46 |
47 | ## 2.3.7
48 | Sun, 18 Oct 2020 23:46:38 GMT
49 |
50 | ### Patches
51 |
52 | - Support passing vertexCountProperty as a functor.
53 |
54 | ## 2.3.6
55 | Sun, 30 Aug 2020 15:11:17 GMT
56 |
57 | ### Patches
58 |
59 | - Added unit-test for BufferPool, fixed crash when the an unallocated buffer was released.
60 |
61 | ## 2.3.5
62 | Sat, 29 Aug 2020 19:06:52 GMT
63 |
64 | ### Patches
65 |
66 | - Don't upload docs, compile to npm
67 |
68 | ## 2.3.4
69 | Sat, 29 Aug 2020 19:02:11 GMT
70 |
71 | ### Patches
72 |
73 | - .cjs -> .js, .mjs -> .es.js for bundles
74 | - BufferPool API!
75 |
76 |
--------------------------------------------------------------------------------
/packages/batch-renderer/README.md:
--------------------------------------------------------------------------------
1 | # pixi-batch-renderer
2 |
3 | 
4 | [](https://packagephobia.now.sh/result?p=pixi-batch-renderer)
5 | 
6 | 
7 | [](https://www.jsdelivr.com/package/npm/pixi-batch-renderer-alpha)
8 | 
9 |
10 | `pixi-batch-renderer` is a PixiJS plugin that allows you to add batch rendering to your custom display objects. I have documented each class in the `PIXI.brend` namespace.
11 |
12 | ## Usage
13 |
14 | In browser:
15 | ```js
16 | PIXI.brend.BatchRenderer
17 | ```
18 |
19 | Using ES:
20 | ```js
21 | import { BatchRenderer } from 'pixi-batch-renderer';
22 | ```
23 |
24 | ## Concepts
25 |
26 | [Batch rendering](https://medium.com/swlh/inside-pixijs-batch-rendering-system-fad1b466c420) objects involves aggregating them into groups/batches and rendering them together with one WebGL draw call. PixiJS supports batch rendering its internal display objects - `PIXI.Sprite`, `PIXI.Graphics`, and `PIXI.Mesh`. However, it is difficult to extend that to custom-built display objects; it wasn't designed as an exposable API.
27 |
28 | This library builds upon the same concept and is designed for maximum flexibility. It still uses PixiJS's batch system - a stub that enables objects to be rendered asynchronously, without loosing the order of rendering. To understand how it works, understanding these things are helpful:
29 |
30 | * **Attribute Redirects**: An attribute redirect is a data-object that tells `pixi-batch-renderer` how it will transform your object into a set of shader attributes.
31 |
32 | * **Index Property**: If you use indices, this will be property on your display object that holds those indices. It could also be a constant array, rather than a property on each object.
33 |
34 | * **State Function**: This function/property tells the batch renderer what WebGL state is required to render a display-object. It is optional if you're display objects use the default state (`PIXI.State.for2d()`).
35 |
36 | * **Shader Function**: This function generates the shader to render whole batches. It takes one argument - the batch renderer
37 | that will use the shader. You can use the `BatchShaderFactory#derive()` API for create one.
38 |
39 | ### New features
40 |
41 | * **Shader Templates**: The `BatchShaderFactory` allows you to write shader "templates" containing `%macros%`. At runtime, you
42 | can replace these macros based with another expression. For example, the (built-in) `%texturesPerBatch%` macro is set to the
43 | no. of textures units in the GPU.
44 |
45 | * **Custom uniforms**: [Experimental] You can also use uniforms in the batch shader; however, this might reduce the batching
46 | efficiency if most batchable display-objects have different values for uniforms (because then they can't be batched together).
47 |
48 | * **Modular architecture**: With the modular architecture of this library, you change the behaviour of any component. The
49 | geometry composition, batch generation, and drawing stages are componentized and can be modified by providing a custom
50 | implementation to `BatchRendererPluginFactory.from`.
51 |
52 | ### Caveat with filtered/masked objects
53 |
54 | Before rendering itself, a `PIXI.Container` with filters or a mask will flush the batch renderer and will not batch itself. This
55 | is because the PixiJS batch renderer cannot batch filtered and masked objects. Although this does not break pixi-batch-renderer,
56 | it does reduce batching-efficiency. If you want to create a batch renderer that will batch filtered and masked objects too, your display-object must override `render` (**however, you will have to derive your own batch renderer class for that**):
57 |
58 | ```
59 | render(renderer: PIXI.Renderer): void
60 | {
61 | // If you registered the batch renderer as a plugin "pluginName", then replace with
62 | // renderer.plugins.pluginName
63 | renderer.setObjectRenderer();
64 | .render(this);
65 |
66 | for (let i = 0, j = this.children.length; i < j; i++)
67 | {
68 | this._children.render(renderer);
69 | }
70 | }
71 | ```
72 |
73 | # Usage
74 |
75 | ### Standard Pipeline
76 |
77 | For most use cases, `PIXI.brend.BatchRendererPluginFactory` is all you'll need from this library. You need to do these three things:
78 |
79 | 1. **Generate the plugin class using `PIXI.brend.BatchRendererPluginFactory.from`**
80 |
81 | 2. **Register the plugin with PixiJS's WebGL renderer**
82 |
83 | 3. **Make your custom display object defer its rendering to your plugin**
84 |
85 | An example implementation would look like:
86 |
87 | ```js
88 | import * as PIXI from 'pixi.js';
89 | import { AttributeRedirect, BatchRendererPluginFactory, BatchShaderFactory } from 'pixi-batch-renderer';
90 |
91 | // ExampleFigure has two attributes: aVertex and aTextureCoord. They come from the
92 | // vertices and uvs properties in this object. The indices are in the indices property.
93 | class ExampleFigure extends PIXI.Container
94 | {
95 | _render(renderer)
96 | {
97 | this.vertices = [x0,y0, x1,y1, x2,y2, ..., xn,yn];// variable number of vertices
98 | this.uvs = [u0,v0, u1,v1, u2, v2, ..., un,yn];// however, all other attributes must have equal length
99 | this.texture = PIXI.Texture.from("url:example");
100 |
101 | this.indices = [0, 1, 2, ..., n];// we could also tell our batch renderer to not use indices too :)
102 |
103 | renderer.setObjectRenderer(renderer.plugins["ExampleRenderer"]);
104 | renderer.plugins["ExampleRenderer"].render(this);
105 | }
106 | }
107 |
108 | // Define the geometry of ExampleFigure.
109 | const attribSet = [
110 | new AttributeRedirect({
111 | source: "vertices",
112 | attrib: "aVertex",
113 | type: 'float32',
114 | size: 2,
115 | glType: PIXI.TYPES.FLOAT,
116 | glSize: 2
117 | }),
118 | new AttributeRedirect({
119 | source: "uvs",
120 | attrib: "aTextureCoord",
121 | type: 'float32',
122 | size: 2,
123 | glType: PIXI.TYPES.FLOAT,
124 | size: 2
125 | }),
126 | ];
127 |
128 | // Create a shader function from a shader template!
129 | const shaderFunction = new BatchShaderFactory(
130 | // Vertex Shader
131 | `
132 | attribute vec2 aVertex;
133 | attribute vec2 aTextureCoord;
134 | attribute float aTextureId;
135 |
136 | varying float vTextureId;
137 | varying vec2 vTextureCoord;
138 |
139 | uniform mat3 projectionMatrix;
140 |
141 | void main()
142 | {
143 | gl_Position = vec4((projectionMatrix * vec3(aVertex.xy, 1)), 0, 1);
144 | vTextureId = aTextureId;
145 | vTextureCoord = aTextureCoord;
146 | }
147 | `,
148 |
149 | // Fragment Shader
150 | `
151 | uniform uSamplers[%texturesPerBatch%];/* %texturesPerBatch% is a macro and will become a number */\
152 | varying float vTextureId;
153 | varying vec2 vTextureCoord;
154 |
155 | void main(void){
156 | vec4 color;
157 |
158 | /* get color, which is the pixel in texture uSamplers[vTextureId] @ vTextureCoord */
159 | for (int k = 0; k < %texturesPerBatch%; ++k)
160 | {
161 | if (int(vTextureId) == k)
162 | color = texture2D(uSamplers[k], vTextureCoord);
163 |
164 | }
165 |
166 | gl_FragColor = color;
167 | }
168 | `,
169 | {}).derive();
170 |
171 | // Create batch renderer class
172 | const ExampleRenderer = BatchRendererPluginFactory.from({
173 | attribSet,
174 | indexProperty: "indices",
175 | textureProperty: "texture",
176 | texIDAttrib: "aTextureId", // this will be used to locate the texture in the fragment shader later
177 | shaderFunction
178 | });
179 |
180 | // Remember to do this before instantiating a PIXI.Application or PIXI.Renderer!
181 | PIXI.Renderer.registerPlugin("ExampleRenderer", ExampleRenderer);
182 | ```
183 |
184 | ### Uniforms Pipeline [Experimental]
185 |
186 | You can take advantage of shader uniforms in batching too! pixi-batch-renderer supports this out of the box
187 | with the `AggregateUniformsBatchFactory`. There are two modes for uniforms:
188 |
189 | * **Aggregation mode**: Uniforms of each display-object are aggregated into an array. Then an uniform-ID attribute
190 | is uploaded (say `aUniformId`) that tells the shader which uniform to pick out of an array. The attribute is passed
191 | to the plugin factory via the `uniformIDAttrib` option.
192 |
193 | * **No-aggregation mode**: Uniforms are uploaded in one-element arrays. Display-objects with different uniform values
194 | are not batched together. This is useful if your uniform values don't differ a lot and will avoid another attribute. To
195 | use this mode, simply don't pass an `uniformIDAttrib`.
196 |
197 | 1. Aggregation Mode (Example)
198 |
199 | ```js
200 | const { UniformRedirect, AggregateUniformsBatchFactory } = require('pixi-batch-renderer');
201 |
202 | const shaderFunction = new BatchShaderFactory(
203 | // Vertex Shader
204 | `
205 | attribute vec2 aVertex;
206 | attribute vec2 aTextureCoord;
207 | attribute float aTextureId;
208 | attribute float aUniformId;
209 |
210 | varying float vTextureId;
211 | varying vec2 vTextureCoord;
212 | varying float vUniformId;
213 |
214 | uniform mat3 projectionMatrix;
215 |
216 | void main()
217 | {
218 | gl_Position = vec4((projectionMatrix * vec3(aVertex.xy, 1)), 0, 1);
219 | vTextureId = aTextureId;
220 | vTextureCoord = aTextureCoord;
221 |
222 | vUniformId = aUniformId;
223 | }
224 | `,
225 |
226 | // Fragment Shader
227 | `
228 | // You can also use this in the vertex shader.
229 | uniform shaderType[%uniformsPerBatch%];
230 | varying float vUniformId;
231 |
232 | uniform uSamplers[%texturesPerBatch%];/* %texturesPerBatch% is a macro and will become a number */\
233 | varying float vTextureId;
234 | varying vec2 vTextureCoord;
235 |
236 | void main(void){
237 | vec4 color;
238 | float type;
239 |
240 | /* get color & shaderType */
241 | for (int k = 0; k < int(max(%texturesPerBatch%., %uniformsPerBatch%.)); ++k)
242 | {
243 | if (int(vTextureId) == k) {
244 | color = texture2D(uSamplers[k], vTextureCoord);
245 | }
246 | if (int(vUniformId) == k) {
247 | type = shaderType[vUniformId];
248 | }
249 | }
250 |
251 |
252 | if (type == 1)
253 | {
254 | gl_FragColor = color;
255 | }
256 | else
257 | {
258 | gl_FragColor = vec4(color.rgb * vTextureCoord.x, vTextureCoord.x);
259 | }
260 | }
261 | `,
262 | {}).derive();
263 |
264 | const uniformSet = [
265 | new UniformRedirect({ source: "type", uniform: "shadingType" });
266 | ];
267 |
268 | const ExampleRenderer = BatchRendererPluginFactory.from({
269 | uniformSet,
270 | uniformIDAttrib: "aUniformId",
271 |
272 | // Previous example's stuff
273 | attribSet,
274 | indexProperty: "indices",
275 | textureProperty: "texture",
276 | texIDAttrib: "aTextureId",
277 | shaderFunction,
278 |
279 | BatchFactoryClass: AggregateUniformsBatchFactory
280 | })
281 | ```
282 |
283 | 2. No Aggregation Mode (Example)
284 |
285 |
286 | ```js
287 | const { UniformRedirect, AggregateUniformsBatchFactory } = require('pixi-batch-renderer');
288 |
289 | const shaderFunction = new BatchShaderFactory(
290 | // Vertex Shader (no changes to standard pipeline example!)
291 | `
292 | attribute vec2 aVertex;
293 | attribute vec2 aTextureCoord;
294 | attribute float aTextureId;
295 | varying float vTextureId;
296 | varying vec2 vTextureCoord;
297 |
298 | uniform mat3 projectionMatrix;
299 |
300 | void main()
301 | {
302 | gl_Position = vec4((projectionMatrix * vec3(aVertex.xy, 1)), 0, 1);
303 | vTextureId = aTextureId;
304 | vTextureCoord = aTextureCoord;
305 | }
306 | `,
307 |
308 | // Fragment Shader
309 | `
310 | // Look only one-element instead of %uniformsPerBatch%
311 | uniform shaderType[1];
312 | varying float vUniformId;
313 |
314 | uniform uSamplers[%texturesPerBatch%];/* %texturesPerBatch% is a macro and will become a number */\
315 | varying float vTextureId;
316 | varying vec2 vTextureCoord;
317 |
318 | void main(void){
319 | vec4 color;
320 | float type = shaderType;
321 |
322 | /* get color & shaderType */
323 | for (int k = 0; k < %texturesPerBatch%; ++k)
324 | {
325 | if (int(vTextureId) == k) {
326 | color = texture2D(uSamplers[k], vTextureCoord);
327 | }
328 | }
329 |
330 | if (type == 1)
331 | {
332 | gl_FragColor = color;
333 | }
334 | else
335 | {
336 | gl_FragColor = vec4(color.rgb * vTextureCoord.x, vTextureCoord.x);
337 | }
338 | }
339 | `,
340 | {}).derive();
341 |
342 | const uniformSet = [
343 | new UniformRedirect({ source: "type", uniform: "shaderType" });
344 | ];
345 |
346 | const ExampleRenderer = BatchRendererPluginFactory.from({
347 | uniformSet,
348 |
349 | attribSet,
350 | indexProperty: "indices",
351 | textureProperty: "texture",
352 | texIDAttrib: "aTextureId",
353 | shaderFunction,
354 |
355 | BatchFactoryClass: AggregateUniformsBatchFactory
356 | });
357 |
358 | PIXI.Renderer.registerPlugin("erend", ExampleRenderer);
359 |
360 | // Try using with a Sprite!
361 | const sprite = PIXI.Sprite.from();
362 |
363 | sprite.pluginName = "erend";
364 | sprite.type = 0;// will fade out horizontally in shader
365 | ```
366 |
367 | ### Advanced/Customized Batch Generation
368 |
369 | The `BatchRendererPluginFactory.from` method also accepts these (optional) options that can be used to extend the
370 | behaviour of built-in components:
371 |
372 | * `BatchFactoryClass`: Child class of [StdBatchFactory]{@link https://pixijs.io/pixi-batch-renderer/PIXI.brend.StdBatchFactory.html}
373 |
374 | * `BatchGeometryClass`: Child class of [BatchGeometry]{@link https://pixijs.io/pixi-batch-renderer/PIXI.brend.BatchGeometryFactory.html}
375 |
376 | * `BatchDrawerClass`: Child class of [BatchDrawer]{@link https://pixijs.io/pixi-batch-renderer/PIXI.brend.BatchDrawer.html}
377 |
378 | * `BatchRendererClass`: If overriding a component does not meet your requirements, you can derive your own batch renderer by
379 | providing a child class of [BatchRenderer]{@link https://pixijs.io/pixi-batch-renderer/PIXI.brend.BatchRenderer.html}
380 |
--------------------------------------------------------------------------------
/packages/batch-renderer/api-extractor.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3 | "mainEntryPointFilePath": "/compile/index.d.ts",
4 | "bundledPackages": [
5 | "@pixi-essentials/pixi-object-pool"
6 | ],
7 | "compiler": {
8 | "tsconfigFilePath": "/tsconfig.json"
9 | },
10 | "apiReport": {
11 | "enabled": false
12 | },
13 | "docModel": {
14 | "enabled": false
15 | },
16 | "dtsRollup": {
17 | "enabled": true,
18 | "untrimmedFilePath": "/index.d.ts",
19 | },
20 | "tsdocMetadata": {
21 | "enabled": false
22 | },
23 | "messages": {
24 | "compilerMessageReporting": {
25 | "default": {
26 | "logLevel": "none"
27 | }
28 | },
29 | "extractorMessageReporting": {
30 | "default": {
31 | "logLevel": "none"
32 | }
33 | },
34 | "tsdocMessageReporting": {
35 | "default": {
36 | "logLevel": "none"
37 | }
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/packages/batch-renderer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pixi-batch-renderer",
3 | "version": "3.0.0",
4 | "description": "Batch rendering library for PixiJS applications",
5 | "main": "lib/pixi-batch-renderer.js",
6 | "module": "lib/pixi-batch-renderer.es.js",
7 | "bundle": "dist/pixi-batch-renderer.js",
8 | "namespace": "PIXI.brend",
9 | "types": "index.d.ts",
10 | "scripts": {
11 | "build": "npm run compile && pnpm compile:types",
12 | "compile": "rollup -c node_modules/@pixi-build-tools/rollup-configurator/index.js --silent",
13 | "compile:types": "rm -rf compile && tsc; api-extractor run; echo Done",
14 | "prepublishOnly": "npm run build"
15 | },
16 | "author": "Shukant Pal ",
17 | "license": "MIT",
18 | "files": [
19 | "lib",
20 | "dist",
21 | "index.d.ts"
22 | ],
23 | "keywords": [
24 | "pixi",
25 | "pixijs",
26 | "webgl",
27 | "rendering",
28 | "canvas",
29 | "batching"
30 | ],
31 | "repository": {
32 | "type": "git",
33 | "url": "https://github.com/SukantPal/pixi-batch-renderer"
34 | },
35 | "peerDependencies": {
36 | "@pixi/constants": "^7.0.0",
37 | "@pixi/core": "^7.0.0",
38 | "@pixi/display": "^7.0.0",
39 | "@pixi/math": "^7.0.0",
40 | "@pixi/settings": "^7.0.0",
41 | "@pixi/utils": "^7.0.0"
42 | },
43 | "devDependencies": {
44 | "@microsoft/api-extractor": "^7.7.13",
45 | "@pixi/eslint-config": "^1.0.1",
46 | "@pixi-build-tools/rollup-configurator": "~1.0.11",
47 | "eslint": "^6.8.0",
48 | "eslint-plugin-jsdoc": "^15.12.2",
49 | "floss": "^5.0.0",
50 | "electron": "^12.0.0",
51 | "nyc": "15.1.0",
52 | "gh-pages": "^2.2.0",
53 | "rollup": "^2.3.3",
54 | "tslib": "^2.0.0",
55 | "typescript": "~4.9.5",
56 | "@types/node": "^12.12.2"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/packages/batch-renderer/src/AggregateUniformsBatch.ts:
--------------------------------------------------------------------------------
1 | import { StdBatch } from './StdBatch';
2 |
3 | import type { BatchRenderer } from './BatchRenderer';
4 | import type { Renderer, UniformGroup } from '@pixi/core';
5 |
6 | /**
7 | * Allows usage of uniforms when rendering display-objects in batches. It expects you to
8 | * aggregate each display-object's uniforms in an array and that the shader will pick
9 | * the appropriate uniform at runtime (an index into the uniforms array will be passed).
10 | *
11 | * **Usage in shader:**
12 | * ```
13 | * // Your display-objects' affine transforms are aggregated into this array.
14 | * uniform mat3d affineTransform[];
15 | *
16 | * // For WebGL1+ machines, your uniforms may be fetched by the uniform-ID attrib (float).
17 | * varying float vUniformID;
18 | *
19 | * // For WebGL-2 only, to prevent interpolation overhead, you may use the flat in variables. You
20 | * // can configure this in AggregateUniformShaderFactory.
21 | * flat in int uniformID;
22 | * ```
23 | *
24 | * # No Aggregation Mode
25 | *
26 | * Aggregating uniforms into arrays requries a uniform-ID attribute to be uploaded as well. This
27 | * may cost a lot of memory if your uniforms don't really change a lot. For these cases, you can
28 | * disable uniform aggregation by not passing a `uniformIDAttrib`. This will make batches **only**
29 | * have one value for each uniform. The uniforms will still be uploaded as 1-element arrays, however.
30 | */
31 | export class AggregateUniformsBatch extends StdBatch
32 | {
33 | renderer: BatchRenderer;
34 |
35 | uniformBuffer: { [id: string]: Array };
36 | uniformMap: Array;
37 | uniformLength: number;
38 |
39 | constructor(renderer: BatchRenderer, geometryOffset?: number)
40 | {
41 | super(geometryOffset);
42 |
43 | /**
44 | * Renderer holding the uniform redirects
45 | */
46 | this.renderer = renderer;
47 |
48 | /**
49 | * The buffer of uniform arrays of the display-objects
50 | * @member {Object>}
51 | */
52 | this.uniformBuffer = null;
53 |
54 | /**
55 | * Array mapping the in-batch ID to the uniform ID.
56 | * @member {Array}
57 | */
58 | this.uniformMap = null;
59 |
60 | /**
61 | * No. of uniforms buffered (per uniform name)
62 | * @member {number}
63 | */
64 | this.uniformLength = 0;
65 | }
66 |
67 | upload(renderer: Renderer): void
68 | {
69 | super.upload(renderer);
70 |
71 | const { _uniformRedirects: uniformRedirects, _shader: shader } = this.renderer;
72 |
73 | for (let i = 0, j = uniformRedirects.length; i < j; i++)
74 | {
75 | const glslIdentifer = uniformRedirects[i].glslIdentifer;
76 |
77 | shader.uniforms[glslIdentifer] = this.uniformBuffer[glslIdentifer];
78 | }
79 |
80 | // shader.uniformGroup.update();
81 | }
82 |
83 | /**
84 | * @override
85 | */
86 | reset(): void
87 | {
88 | super.reset();
89 |
90 | for (const uniformName in this.uniformBuffer)
91 | {
92 | this.uniformBuffer[uniformName].length = 0;
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/packages/batch-renderer/src/AggregateUniformsBatchFactory.ts:
--------------------------------------------------------------------------------
1 | import { AggregateUniformsBatch } from './AggregateUniformsBatch';
2 | import { BaseTexture } from '@pixi/core';
3 | import { BatchRenderer } from './BatchRenderer';
4 | import { Matrix, Point } from '@pixi/math';
5 | import { StdBatchFactory } from './StdBatchFactory';
6 |
7 | import type { DisplayObject } from '@pixi/display';
8 | import type { UniformGroup } from '@pixi/core';
9 |
10 | /**
11 | * Factory for producing aggregate-uniforms batches. This is useful for shaders that
12 | * **must** use uniforms.
13 | */
14 | export class AggregateUniformsBatchFactory extends StdBatchFactory
15 | {
16 | MAX_UNIFORMS: number;
17 |
18 | protected uniformBuffer: { [id: string]: Array };
19 | protected uniformMap: Array;
20 | protected uniformLength: number;
21 |
22 | constructor(renderer: BatchRenderer)
23 | {
24 | super(renderer);
25 |
26 | /**
27 | * The max. uniforms until the batch is filled
28 | * @member {number}
29 | * @readonly
30 | */
31 | // Max. no. of uniforms that can be passed to the batch shader. We divide by four because
32 | // mat4d/vec4 count as four uniforms.
33 | this.MAX_UNIFORMS = Math.floor(
34 | Math.min(
35 | renderer.renderer.gl.getParameter(renderer.renderer.gl.MAX_VERTEX_UNIFORM_VECTORS),
36 | renderer.renderer.gl.getParameter(renderer.renderer.gl.MAX_FRAGMENT_UNIFORM_VECTORS))
37 | / (4 * renderer._uniformRedirects.length));
38 |
39 | this.uniformBuffer = this._createUniformBuffer();
40 | this.uniformMap = [];
41 | this.uniformLength = 0;
42 | }
43 |
44 | /**
45 | * @returns {AggregateUniformsBatch}
46 | */
47 | _newBatch(): AggregateUniformsBatch
48 | {
49 | const batch = new AggregateUniformsBatch(this._renderer);
50 |
51 | // All pooled batches will have a buffer
52 | batch.uniformBuffer = this._createUniformBuffer();
53 | batch.uniformMap = [];
54 |
55 | return batch;
56 | }
57 |
58 | /**
59 | * Stores uniforms in the current batch, if possible.
60 | *
61 | * If you want to override this, be sure to return beforehand if `super._put` returns
62 | * false:
63 | * ```
64 | * _put(displayObject: DisplayObject): boolean
65 | * {
66 | * if (!super._put(displayObject))
67 | * {
68 | * return false;
69 | * }
70 | *
71 | * // Your logic ...
72 | * }
73 | * ```
74 | *
75 | * @protected
76 | * @returns Whether uniforms can be buffered
77 | */
78 | protected _put(displayObject: DisplayObject): boolean
79 | {
80 | if (!this._renderer._uniformIDAttrib)
81 | {
82 | // No aggregation mode! If uniforms already buffered, they **must** match or batch will break.
83 | if (this.uniformLength >= 0)
84 | {
85 | const id = this._matchUniforms(displayObject);
86 |
87 | if (id >= 0)
88 | {
89 | return true;
90 | }
91 |
92 | return false;
93 | }
94 | }
95 |
96 | if (this.uniformLength + 1 >= this.MAX_UNIFORMS)
97 | {
98 | return false;
99 | }
100 |
101 | if (this._renderer._uniformIDAttrib)
102 | {
103 | const id = this._matchUniforms(displayObject);
104 |
105 | if (id >= 0)
106 | {
107 | this.uniformMap.push(id);
108 |
109 | return true;
110 | }
111 | }
112 |
113 | // Push each uniform into the buffer
114 | for (let i = 0, j = this._renderer._uniformRedirects.length; i < j; i++)
115 | {
116 | const uniformRedirect = this._renderer._uniformRedirects[i];
117 | const { source, glslIdentifer } = uniformRedirect;
118 |
119 | const value = typeof source === 'string'
120 | ? (displayObject as any)[source]
121 | : source(displayObject, this._renderer);
122 |
123 | if (Array.isArray(value))
124 | {
125 | this.uniformBuffer[glslIdentifer].push(...value);
126 | }
127 | else
128 | {
129 | this.uniformBuffer[glslIdentifer].push(value);
130 | }
131 | }
132 |
133 | this.uniformMap.push(this.uniformLength);
134 | ++this.uniformLength;
135 |
136 | return true;
137 | }
138 |
139 | /**
140 | * @protected
141 | * @param {AggregateUniformBatch} batch
142 | */
143 | _buildBatch(batch: any): void
144 | {
145 | super._buildBatch(batch);
146 |
147 | const buffer = batch.uniformBuffer;
148 | const map = batch.uniformMap;
149 |
150 | batch.uniformBuffer = this.uniformBuffer;
151 | batch.uniformMap = this.uniformMap;
152 | batch.uniformLength = this.uniformLength;
153 |
154 | // Swap & reset instead of new allocation
155 | this.uniformBuffer = buffer;
156 | this.uniformMap = map;
157 | this.uniformLength = 0;
158 | this._resetUniformBuffer(this.uniformBuffer);
159 | this.uniformMap.length = 0;
160 | }
161 |
162 | /**
163 | * Creates an array for each uniform-name in an object.
164 | *
165 | * @returns - the object created (the uniform buffer)
166 | */
167 | private _createUniformBuffer(): { [id: string]: Array }
168 | {
169 | const buffer: { [id: string]: Array } = {};
170 |
171 | for (let i = 0, j = this._renderer._uniformRedirects.length; i < j; i++)
172 | {
173 | const uniformRedirect = this._renderer._uniformRedirects[i];
174 |
175 | buffer[uniformRedirect.glslIdentifer] = [];
176 | }
177 |
178 | return buffer;
179 | }
180 |
181 | /**
182 | * Resets each array in the uniform buffer
183 | * @param {object} buffer
184 | */
185 | private _resetUniformBuffer(buffer: { [id: string]: Array }): void
186 | {
187 | for (let i = 0, j = this._renderer._uniformRedirects.length; i < j; i++)
188 | {
189 | const uniformRedirect = this._renderer._uniformRedirects[i];
190 |
191 | buffer[uniformRedirect.glslIdentifer].length = 0;
192 | }
193 | }
194 |
195 | /**
196 | * Finds a matching set of uniforms in the buffer.
197 | */
198 | private _matchUniforms(displayObject: any): number
199 | {
200 | const uniforms = this._renderer._uniformRedirects;
201 |
202 | for (let i = this.uniformLength - 1; i >= 0; i--)
203 | {
204 | let isMatch = true;
205 |
206 | for (let k = 0, n = uniforms.length; k < n; k++)
207 | {
208 | const { glslIdentifer, source } = uniforms[k];
209 |
210 | const value = typeof source === 'string'
211 | ? displayObject[source]
212 | : source(displayObject as DisplayObject, this._renderer);
213 |
214 | if (!this._compareUniforms(value, this.uniformBuffer[glslIdentifer][i]))
215 | {
216 | isMatch = false;
217 | break;
218 | }
219 | }
220 |
221 | if (isMatch)
222 | {
223 | return i;
224 | }
225 | }
226 |
227 | return -1;
228 | }
229 |
230 | // Compares two uniforms u1 & u2 for equality.
231 | private _compareUniforms(u1: any, u2: any): boolean
232 | {
233 | if (u1 === u2)
234 | {
235 | return true;
236 | }
237 |
238 | // UniformGroups must have referential equality
239 | if (u1.group || u2.group)
240 | {
241 | return false;
242 | }
243 |
244 | // Allow equals() method for custom stuff.
245 | if (u1.equals)
246 | {
247 | return u1.equals(u2);
248 | }
249 |
250 | // Test one-depth equality for arrays
251 | if (Array.isArray(u1) && Array.isArray(u2))
252 | {
253 | if (u1.length !== u2.length)
254 | {
255 | return false;
256 | }
257 |
258 | for (let i = 0, j = u1.length; i < j; i++)
259 | {
260 | // Referential equality for array elements
261 | if (u1[i] !== u2[i])
262 | {
263 | return false;
264 | }
265 | }
266 |
267 | return true;
268 | }
269 |
270 | if (u1 instanceof Point && u2 instanceof Point)
271 | {
272 | return u1.x === u2.x && u1.y === u2.y;
273 | }
274 | if (u1 instanceof Matrix && u2 instanceof Matrix)
275 | {
276 | return u1.a === u2.a && u1.b === u2.b
277 | && u1.c === u2.c && u1.d === u2.d
278 | && u1.tx === u2.tx && u1.ty === u2.ty;
279 | }
280 |
281 | // Unlikely for batch rendering
282 | if (u1 instanceof BaseTexture && u2 instanceof BaseTexture)
283 | {
284 | return u1.uid === u2.uid;
285 | }
286 |
287 | return false;
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/packages/batch-renderer/src/BatchDrawer.ts:
--------------------------------------------------------------------------------
1 | import { BatchRenderer } from './BatchRenderer';
2 |
3 | /**
4 | * Executes the final stage of batch rendering - drawing. The drawer can assume that
5 | * all display-objects have been into the batch-factory and the batch-geometry factory.
6 | */
7 | export class BatchDrawer
8 | {
9 | /** The batch renderer */
10 | renderer: BatchRenderer;
11 |
12 | constructor(renderer: BatchRenderer)
13 | {
14 | this.renderer = renderer;
15 | }
16 |
17 | /**
18 | * This method will be called after all display-object have been fed into the
19 | * batch and batch-geometry factories.
20 | *
21 | * **Hint**: You will call some form of `BatchGeometryFactory#build`; be sure to release
22 | * that geometry for reuse in next render pass via `BatchGeometryFactory#release(geom)`.
23 | */
24 | draw(): void
25 | {
26 | const {
27 | renderer,
28 | _batchFactory: batchFactory,
29 | _geometryFactory: geometryFactory,
30 | _indexProperty: indexProperty,
31 | } = this.renderer;
32 |
33 | const batchList = batchFactory.access();
34 | const batchCount = batchFactory.size();
35 | const geom = geometryFactory.build();
36 | const { gl } = renderer;
37 |
38 | // PixiJS bugs - the shader can't be bound before uploading because uniform sync caching
39 | // and geometry requires the shader to be bound.
40 | batchList[0].upload(renderer);
41 | renderer.shader.bind(this.renderer._shader, false);
42 | renderer.geometry.bind(geom);
43 |
44 | for (let i = 0; i < batchCount; i++)
45 | {
46 | const batch = batchList[i];
47 |
48 | batch.upload(renderer);
49 | renderer.shader.bind(this.renderer._shader, false);
50 |
51 | if (indexProperty)
52 | {
53 | // TODO: Get rid of the $indexCount black magic!
54 | gl.drawElements(gl.TRIANGLES, batch.$indexCount, gl.UNSIGNED_SHORT, batch.geometryOffset * 2);
55 | }
56 | else
57 | {
58 | // TODO: Get rid of the $vertexCount black magic!
59 | gl.drawArrays(gl.TRIANGLES, batch.geometryOffset, batch.$vertexCount);
60 | }
61 |
62 | batch.reset();
63 | }
64 |
65 | geometryFactory.release(geom);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/packages/batch-renderer/src/BatchGeometryFactory.ts:
--------------------------------------------------------------------------------
1 | import { AttributeRedirect } from './redirects/AttributeRedirect';
2 | import Redirect from './redirects/Redirect';
3 | import BatchRenderer from './BatchRenderer';
4 | import { Buffer, Geometry, ViewableBuffer } from '@pixi/core';
5 | import { StdBatch } from './StdBatch';
6 | import { TYPES } from '@pixi/constants';
7 | import { AggregateUniformsBatch } from './AggregateUniformsBatch';
8 | import { nextPow2, log2 } from '@pixi/utils';
9 |
10 | import type { DisplayObject } from '@pixi/display';
11 | import type { Resolvable } from './utils/resolveProperty';
12 |
13 | // BatchGeometryFactory uses this class internally to setup the attributes of
14 | // the batches.
15 | //
16 | // Supports Uniforms+Standard Pipeline's in-batch/uniform ID.
17 | export class BatchGeometry extends Geometry
18 | {
19 | // Interleaved attribute data buffer
20 | attribBuffer: Buffer;
21 |
22 | // Batched indicies
23 | indexBuffer: Buffer;
24 |
25 | constructor(attributeRedirects: AttributeRedirect[],
26 | hasIndex: boolean,
27 | texIDAttrib: string,
28 | texturesPerObject: number,
29 | inBatchIDAttrib: string,
30 | uniformIDAttrib: string,
31 | masterIDAttrib: string,
32 | attributeBuffer?: Buffer,
33 | indexBuffer?: Buffer
34 | )
35 | {
36 | super();
37 |
38 | attributeBuffer = attributeBuffer || new Buffer(null, false, false);
39 | indexBuffer = indexBuffer || (hasIndex ? new Buffer(null, false, true) : null);
40 |
41 | attributeRedirects.forEach((redirect) =>
42 | {
43 | const { glslIdentifer, glType, glSize, normalize } = redirect;
44 |
45 | this.addAttribute(glslIdentifer, attributeBuffer, glSize, normalize, glType);
46 | });
47 |
48 | if (!masterIDAttrib)
49 | {
50 | if (texIDAttrib && texturesPerObject > 0)
51 | {
52 | this.addAttribute(texIDAttrib, attributeBuffer, texturesPerObject, true, TYPES.FLOAT);
53 | }
54 | if (inBatchIDAttrib)
55 | {
56 | this.addAttribute(inBatchIDAttrib, attributeBuffer, 1, false, TYPES.FLOAT);
57 | }
58 | if (uniformIDAttrib)
59 | {
60 | this.addAttribute(uniformIDAttrib, attributeBuffer, 1, false, TYPES.FLOAT);
61 | }
62 | }
63 | else
64 | {
65 | this.addAttribute(masterIDAttrib, attributeBuffer, 1, false, TYPES.FLOAT);
66 | }
67 |
68 | if (hasIndex)
69 | {
70 | this.addIndex(indexBuffer);
71 | }
72 |
73 | this.attribBuffer = attributeBuffer;
74 | this.indexBuffer = indexBuffer;
75 | }
76 | }
77 |
78 | // To define the constructor shape, this is defined as an abstract class but documented
79 | // as an interface.
80 | export abstract class IBatchGeometryFactory
81 | {
82 | protected _renderer: BatchRenderer;
83 |
84 | // eslint-disable-next-line @typescript-eslint/no-useless-constructor, @typescript-eslint/no-unused-vars
85 | constructor(renderer: BatchRenderer)
86 | {
87 | // Implementation
88 | this._renderer = renderer;
89 | }
90 |
91 | abstract init(verticesBatched: number, indiciesBatched: number): void;
92 | abstract append(displayObject: DisplayObject, batch: any): void;
93 | abstract build(): Geometry;
94 | abstract release(geom: Geometry): void;
95 | }
96 |
97 | /**
98 | * This interface defines the methods you need to implement to creating your own batch
99 | * geometry factory.
100 | *
101 | * The constructor of an implementation should take only one argument - the batch renderer.
102 | *
103 | * @interface IBatchGeometryFactory
104 | */
105 |
106 | /**
107 | * Called before the batch renderer starts feeding the display-objects. This can be used
108 | * to pre-allocated space for the batch geometry.
109 | *
110 | * @memberof IBatchGeometryFactory#
111 | * @method init
112 | * @param {number} verticesBatched
113 | * @param {number}[indiciesBatched] - optional when display-object's don't use a index buffer
114 | */
115 |
116 | /**
117 | * Adds the display-object to the batch geometry.
118 | *
119 | * If the display-object's shader also uses textures (in `uSamplers` uniform), then it will
120 | * be given a texture-ID to get the texture from the `uSamplers` array. If it uses multiple
121 | * textures, then the texture-ID is an array of indices into `uSamplers`. The texture-attrib
122 | * passed to the batch renderer sets the name of the texture-ID attribute (defualt is `aTextureId`).
123 | *
124 | * @memberof IBatchGeometryFactory#
125 | * @method append
126 | * @param {PIXI.DisplayObject} displayObject
127 | * @param {object} batch - the batch
128 | */
129 |
130 | /**
131 | * This should wrap up the batch geometry in a `PIXI.Geometry` object.
132 | *
133 | * @memberof IBatchGeometryFactory#
134 | * @method build
135 | * @returns {PIXI.Geometry} batch geometry
136 | */
137 |
138 | /**
139 | * This is used to return a batch geometry so it can be pooled and reused in a future `build()`
140 | * call.
141 | *
142 | * @memberof IBatchGeometryFactory#
143 | * @method release
144 | * @param {PIXI.Geometry} geom
145 | */
146 |
147 | /**
148 | * Factory class that generates the geometry for a whole batch by feeding on
149 | * the individual display-object geometries. This factory is reusable, i.e. you
150 | * can build another geometry after a {@link build} call.
151 | *
152 | * **Optimizations:** To speed up geometry generation, this compiles an optimized
153 | * packing function that pushes attributes without looping through the attribute
154 | * redirects.
155 | *
156 | * **Default Format:** If you are not using a custom draw-call issuer, then
157 | * the batch geometry must have an interleaved attribute data buffer and one
158 | * index buffer.
159 | *
160 | * **Customization:** If you want to customize the batch geometry, then you must
161 | * also define your draw call issuer.
162 | *
163 | * **inBatchID Support**: If you specified an `inBatchID` attribute in the batch-renderer,
164 | * then this will support it automatically. The aggregate-uniforms pipeline doesn't need a custom
165 | * geometry factory.
166 | */
167 | export class BatchGeometryFactory extends IBatchGeometryFactory
168 | {
169 | _targetCompositeAttributeBuffer: ViewableBuffer;
170 | _targetCompositeIndexBuffer: Uint16Array;
171 | _aIndex: number;
172 | _iIndex: number;
173 |
174 | // These properties are not protected because GeometryMerger uses them!
175 |
176 | // Standard Pipeline
177 | _attribRedirects: AttributeRedirect[];
178 | _indexProperty: string;
179 | _vertexCountProperty: string | number | ((object: DisplayObject) => number);
180 | _vertexSize: number;
181 | _texturesPerObject: number;
182 | _textureProperty: string;
183 | _texIDAttrib: string;
184 | _inBatchIDAttrib: string;
185 | _inBatchID: number;
186 |
187 | // Uniform+Standard Pipeline
188 | _uniformIDAttrib: string;
189 | _uniformID: number;
190 |
191 | // Master-ID attribute feature
192 | _masterIDAttrib: string;
193 |
194 | /* Set to the indicies of the display-object's textures in uSamplers uniform before
195 | invoking geometryMerger(). */
196 | protected _texID: number | number[];
197 |
198 | protected _aBuffers: ViewableBuffer[];
199 | protected _iBuffers: Uint16Array[];
200 |
201 | protected _geometryPool: Array;
202 |
203 | _geometryMerger: (displayObject: DisplayObject, factory: BatchGeometryFactory) => void;
204 |
205 | /**
206 | * @param renderer
207 | */
208 | constructor(renderer: BatchRenderer)
209 | {
210 | super(renderer);
211 |
212 | this._targetCompositeAttributeBuffer = null;
213 | this._targetCompositeIndexBuffer = null;
214 | this._aIndex = 0;
215 | this._iIndex = 0;
216 |
217 | this._attribRedirects = renderer._attribRedirects;
218 | this._indexProperty = renderer._indexProperty;
219 | this._vertexCountProperty = renderer._vertexCountProperty;
220 | this._vertexSize = AttributeRedirect.vertexSizeFor(this._attribRedirects);
221 | this._texturesPerObject = renderer._texturesPerObject;
222 | this._textureProperty = renderer._textureProperty;
223 | this._texIDAttrib = renderer._texIDAttrib;
224 |
225 | this._inBatchIDAttrib = renderer._inBatchIDAttrib;
226 | this._uniformIDAttrib = renderer._uniformIDAttrib;
227 |
228 | this._masterIDAttrib = renderer._masterIDAttrib;
229 |
230 | if (!this._masterIDAttrib)
231 | {
232 | this._vertexSize += this._texturesPerObject * 4;// texture indices are also passed
233 |
234 | if (this._inBatchIDAttrib)
235 | {
236 | this._vertexSize += 4;
237 | }
238 | if (this._uniformIDAttrib)
239 | {
240 | this._vertexSize += 4;
241 | }
242 | }
243 | else
244 | {
245 | this._vertexSize += 4;
246 | }
247 |
248 | if (this._texturesPerObject === 1)
249 | {
250 | this._texID = 0;
251 | }
252 | else if (this._texturesPerObject > 1)
253 | {
254 | this._texID = new Array(this._texturesPerObject);
255 | }
256 |
257 | this._aBuffers = [];// @see _getAttributeBuffer
258 | this._iBuffers = [];// @see _getIndexBuffer
259 |
260 | /**
261 | * Batch geometries that can be reused.
262 | *
263 | * @member {PIXI.Geometry}
264 | * @protected
265 | * @see IBatchGeometryFactory#release
266 | */
267 | this._geometryPool = [];
268 | }
269 |
270 | /**
271 | * Ensures this factory has enough space to buffer the given number of vertices
272 | * and indices. This should be called before feeding display-objects from the
273 | * batch.
274 | *
275 | * @param {number} verticesBatched
276 | * @param {number} indiciesBatched
277 | */
278 | init(verticesBatched: number, indiciesBatched?: number): void
279 | {
280 | this._targetCompositeAttributeBuffer = this.getAttributeBuffer(verticesBatched);
281 |
282 | if (this._indexProperty)
283 | {
284 | this._targetCompositeIndexBuffer = this.getIndexBuffer(indiciesBatched);
285 | }
286 |
287 | this._aIndex = this._iIndex = 0;
288 | }
289 |
290 | /**
291 | * Append's the display-object geometry to this batch's geometry. You must override
292 | * this you need to "modify" the geometry of the display-object before merging into
293 | * the composite geometry (for example, adding an ID to a special uniform)
294 | */
295 | append(targetObject: DisplayObject, batch_: any): void
296 | {
297 | const batch: StdBatch = batch_ as StdBatch;
298 | const tex = (targetObject as any)[this._textureProperty];
299 |
300 | // GeometryMerger uses _texID for texIDAttrib
301 | if (this._texturesPerObject === 1)
302 | {
303 | const texUID = tex.baseTexture ? tex.baseTexture.uid : tex.uid;
304 |
305 | this._texID = batch.uidMap[texUID];
306 | }
307 | else if (this._texturesPerObject > 1)
308 | {
309 | let _tex;
310 |
311 | for (let k = 0; k < tex.length; k++)
312 | {
313 | _tex = tex[k];
314 |
315 | const texUID = _tex.baseTexture ? _tex.baseTexture.uid : _tex.uid;
316 |
317 | (this._texID as number[])[k] = batch.uidMap[texUID];
318 | }
319 | }
320 |
321 | // GeometryMerger uses this
322 | if (this._inBatchIDAttrib || this._uniformIDAttrib)
323 | {
324 | this._inBatchID = batch.batchBuffer.indexOf(targetObject);
325 | }
326 | if (this._uniformIDAttrib)
327 | {
328 | this._uniformID = (batch as AggregateUniformsBatch).uniformMap[this._inBatchID];
329 | }
330 |
331 | // If _masterIDAttrib, then it is expected you override this function.
332 |
333 | this.geometryMerger(targetObject, this);
334 | }
335 |
336 | /**
337 | * @override
338 | * @returns {PIXI.Geometry} the generated batch geometry
339 | * @example
340 | * build(): PIXI.Geometry
341 | * {
342 | * // Make sure you're not allocating new geometries if _geometryPool has some
343 | * // already. (Otherwise, a memory leak will result!)
344 | * const geom: ExampleGeometry = (this._geometryPool.pop() || new ExampleGeometry(
345 | * // ...your arguments... //)) as ExampleGeometry;
346 | *
347 | * // Put data into geometry's buffer
348 | *
349 | * return geom;
350 | * }
351 | */
352 | build(): Geometry
353 | {
354 | const geom: BatchGeometry = (this._geometryPool.pop() || new BatchGeometry(
355 | this._attribRedirects,
356 | true,
357 | this._texIDAttrib,
358 | this._texturesPerObject,
359 | this._inBatchIDAttrib,
360 | this._uniformIDAttrib,
361 | this._masterIDAttrib,
362 | )) as BatchGeometry;
363 |
364 | // We don't really have to remove the buffers because BatchRenderer won't reuse
365 | // the data in these buffers after the next build() call.
366 | geom.attribBuffer.update(this._targetCompositeAttributeBuffer.float32View);
367 | geom.indexBuffer.update(this._targetCompositeIndexBuffer);
368 |
369 | return geom;
370 | }
371 |
372 | /**
373 | * @param {PIXI.Geometry} geom - releases back the geometry to be reused. It is expected
374 | * that it is not used externally again.
375 | * @override
376 | */
377 | release(geom: Geometry): void
378 | {
379 | this._geometryPool.push(geom);
380 | }
381 |
382 | /**
383 | * This lazy getter returns the geometry-merger function. This function
384 | * takes one argument - the display-object to be appended to the batch -
385 | * and pushes its geometry to the batch geometry.
386 | *
387 | * You can overwrite this property with a custom geometry-merger function
388 | * if customizing `BatchGeometryFactory`.
389 | *
390 | * @member {IGeometryMerger}
391 | */
392 | protected get geometryMerger(): (displayObject: DisplayObject, factory: BatchGeometryFactory) => void
393 | {
394 | if (!this._geometryMerger)
395 | {
396 | // eslint-disable-next-line @typescript-eslint/no-use-before-define
397 | this._geometryMerger = new GeometryMergerFactory(this).compile();
398 | }
399 |
400 | return this._geometryMerger;
401 | }
402 | // eslint-disable-next-line require-jsdoc
403 | protected set geometryMerger(func: (displayObject: DisplayObject, factory: BatchGeometryFactory) => void)
404 | {
405 | this._geometryMerger = func;
406 | }
407 |
408 | /**
409 | * @protected
410 | */
411 | get _indexCountProperty(): Resolvable
412 | {
413 | return this._renderer._indexCountProperty;
414 | }
415 |
416 | /**
417 | * Allocates an attribute buffer with sufficient capacity to hold `size` elements.
418 | *
419 | * @param {number} size
420 | * @protected
421 | */
422 | protected getAttributeBuffer(size: number): ViewableBuffer
423 | {
424 | const roundedP2 = nextPow2(size);
425 | const roundedSizeIndex = log2(roundedP2);
426 | const roundedSize = roundedP2;
427 |
428 | if (this._aBuffers.length <= roundedSizeIndex)
429 | {
430 | this._aBuffers.length = roundedSizeIndex + 1;
431 | }
432 |
433 | let buffer = this._aBuffers[roundedSizeIndex];
434 |
435 | if (!buffer)
436 | {
437 | this._aBuffers[roundedSizeIndex] = buffer = new ViewableBuffer(roundedSize * this._vertexSize);
438 | }
439 |
440 | return buffer;
441 | }
442 |
443 | /**
444 | * Allocates an index buffer (`Uint16Array`) with sufficient capacity to hold `size` indices.
445 | *
446 | * @param size
447 | * @protected
448 | */
449 | protected getIndexBuffer(size: number): Uint16Array
450 | {
451 | // 12 indices is enough for 2 quads
452 | const roundedP2 = nextPow2(Math.ceil(size / 12));
453 | const roundedSizeIndex = log2(roundedP2);
454 | const roundedSize = roundedP2 * 12;
455 |
456 | if (this._iBuffers.length <= roundedSizeIndex)
457 | {
458 | this._iBuffers.length = roundedSizeIndex + 1;
459 | }
460 |
461 | let buffer = this._iBuffers[roundedSizeIndex];
462 |
463 | if (!buffer)
464 | {
465 | this._iBuffers[roundedSizeIndex] = buffer = new Uint16Array(roundedSize);
466 | }
467 |
468 | return buffer;
469 | }
470 | }
471 |
472 | // GeometryMergerFactory uses these variable names.
473 | const CompilerConstants = {
474 | INDICES_OFFSET: '__offset_indices_',
475 | FUNC_SOURCE_BUFFER: 'getSourceBuffer',
476 |
477 | // Argument names for the geometryMerger() function.
478 | packerArguments: [
479 | 'targetObject',
480 | 'factory',
481 | ],
482 | };
483 |
484 | // This was intended to be an inner class of BatchGeometryFactory; however, due to
485 | // a bug in JSDoc, it was placed outside.
486 | // https://github.com/jsdoc/jsdoc/issues/1673
487 |
488 | // Factory for generating a geometry-merger function (which appends the geometry of
489 | // a display-object to the batch geometry).
490 | const GeometryMergerFactory = class
491 | {
492 | packer: BatchGeometryFactory;
493 |
494 | // We need the BatchGeometryFactory for attribute redirect information.
495 | constructor(packer: BatchGeometryFactory)
496 | {
497 | this.packer = packer;
498 | }
499 |
500 | compile(): (displayObject: DisplayObject, factory: BatchGeometryFactory) => void
501 | {
502 | const packer = this.packer;
503 |
504 | // The function's body/code is placed here.
505 | let packerBody = `
506 | const compositeAttributes = factory._targetCompositeAttributeBuffer;
507 | const compositeIndices = factory._targetCompositeIndexBuffer;
508 | let aIndex = factory._aIndex;
509 | let iIndex = factory._iIndex;
510 | const textureId = factory._texID;
511 | const attributeRedirects = factory._attribRedirects;
512 | `;
513 |
514 | // Define __offset_${i}, the offset of each attribute in the display-object's
515 | // geometry, __buffer_${i} the source buffer of the attribute data.
516 | packer._attribRedirects.forEach((redirect, i) =>
517 | {
518 | packerBody += `
519 | let __offset_${i} = 0;
520 | const __buffer_${i} = (
521 | ${this.generateSourceBufferExpr(redirect, i)});
522 | `;
523 | });
524 |
525 | // This loops through each vertex in the display-object's geometry and appends
526 | // them (attributes are interleaved, so each attribute element is pushed per vertex)
527 | packerBody += `
528 | const {
529 | int8View,
530 | uint8View,
531 | int16View,
532 | uint16View,
533 | int32View,
534 | uint32View,
535 | float32View,
536 | } = compositeAttributes;
537 |
538 | const vertexCount = ${this.generateVertexCountExpr()};
539 |
540 | let adjustedAIndex = 0;
541 |
542 | for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++)
543 | {
544 | `;
545 |
546 | // Eliminate offset conversion when adjacent attributes
547 | // have similar source-types.
548 | let skipByteIndexConversion = false;
549 |
550 | // Appends a vertice's attributes (inside the for-loop above).
551 | for (let i = 0; i < packer._attribRedirects.length; i++)
552 | {
553 | const redirect = packer._attribRedirects[i];
554 |
555 | // Initialize adjustedAIndex in terms of source type.
556 | if (!skipByteIndexConversion)
557 | {
558 | packerBody += `
559 | adjustedAIndex = aIndex / ${this._sizeOf(i)};
560 | `;
561 | }
562 |
563 | if (typeof redirect.size === 'number')
564 | {
565 | for (let j = 0; j < redirect.size; j++)
566 | {
567 | packerBody += `
568 | ${redirect.type}View[adjustedAIndex++] = __buffer_${i}[__offset_${i}++];
569 | `;
570 | }
571 | }
572 | else
573 | {
574 | packerBody += `
575 | ${redirect.type}View[adjustedAIndex++] = __buffer_${i};
576 | `;
577 | }
578 |
579 | if (packer._attribRedirects[i + 1] && (this._sizeOf(i + 1) !== this._sizeOf(i)))
580 | {
581 | packerBody += `
582 | aIndex = adjustedAIndex * ${this._sizeOf(i)};
583 | `;
584 | }
585 | else
586 | {
587 | skipByteIndexConversion = true;
588 | }
589 | }
590 |
591 | if (skipByteIndexConversion)
592 | {
593 | if (this._sizeOf(packer._attribRedirects.length - 1) !== 4)
594 | {
595 | packerBody += `
596 | aIndex = adjustedAIndex * ${this._sizeOf(packer._attribRedirects.length - 1)}
597 | `;
598 | skipByteIndexConversion = false;
599 | }
600 | }
601 |
602 | if (!packer._masterIDAttrib)
603 | {
604 | if (packer._texturesPerObject > 0)
605 | {
606 | if (packer._texturesPerObject > 1)
607 | {
608 | if (!skipByteIndexConversion)
609 | {
610 | packerBody += `
611 | adjustedAIndex = aIndex / 4;
612 | `;
613 | }
614 |
615 | for (let k = 0; k < packer._texturesPerObject; k++)
616 | {
617 | packerBody += `
618 | float32View[adjustedAIndex++] = textureId[${k}];
619 | `;
620 | }
621 |
622 | packerBody += `
623 | aIndex = adjustedAIndex * 4;
624 | `;
625 | }
626 | else if (!skipByteIndexConversion)
627 | {
628 | packerBody += `
629 | float32View[aIndex / 4] = textureId;
630 | `;
631 | }
632 | else
633 | {
634 | packerBody += `
635 | float32View[adjustedAIndex++] = textureId;
636 | aIndex = adjustedAIndex * 4;
637 | `;
638 | }
639 | }
640 | if (packer._inBatchIDAttrib)
641 | {
642 | packerBody += `
643 | float32View[adjustedAIndex++] = factory._inBatchID;
644 | aIndex = adjustedAIndex * 4;
645 | `;
646 | }
647 | if (packer._uniformIDAttrib)
648 | {
649 | packerBody += `
650 | float32View[adjustedAIndex++] = factory._uniformID;
651 | aIndex = adjustedAIndex * 4;
652 | `;
653 | }
654 | }
655 | else
656 | {
657 | if (!skipByteIndexConversion)
658 | {
659 | packerBody += `
660 | adjustedAIndex = aIndex / 4;
661 | `;
662 | }
663 |
664 | packerBody += `
665 | float32View[adjustedAIndex++] = factory._masterID;
666 | aIndex = adjustedAIndex * 4;
667 | `;
668 | }
669 |
670 | /* Close the packing for-loop. */
671 | packerBody += `}
672 | ${this.packer._indexProperty
673 | ? `const oldAIndex = this._aIndex;`
674 | : ''}
675 | this._aIndex = aIndex;
676 | `;
677 |
678 | if (this.packer._indexProperty)
679 | {
680 | packerBody += `
681 | const verticesBefore = oldAIndex / ${this.packer._vertexSize};
682 | const indexCount = ${this.generateIndexCountExpr()};
683 |
684 | for (let j = 0; j < indexCount; j++)
685 | {
686 | compositeIndices[iIndex++] = verticesBefore + targetObject['${this.packer._indexProperty}'][j];
687 | }
688 |
689 | this._iIndex = iIndex;
690 | `;
691 | }
692 |
693 | // eslint-disable-next-line no-new-func
694 | return new Function(
695 | ...CompilerConstants.packerArguments,
696 | packerBody) as
697 | (displayObject: DisplayObject, factory: BatchGeometryFactory) => void;
698 | }
699 |
700 | // Returns an expression that fetches the attribute data source from
701 | // targetObject (DisplayObject).
702 | generateSourceBufferExpr(redirect: Redirect, i: number): string
703 | {
704 | return (typeof redirect.source === 'string')
705 | ? `targetObject['${redirect.source}']`
706 | : `attributeRedirects[${i}].source(targetObject, factory._renderer)`;
707 | }
708 |
709 | generateVertexCountExpr(): string
710 | {
711 | if (!this.packer._vertexCountProperty)
712 | {
713 | // auto-calculate based on primary attribute
714 | return `__buffer_0.length / ${this.packer._attribRedirects[0].size}`;
715 | }
716 |
717 | if (typeof this.packer._vertexCountProperty === 'function')
718 | {
719 | return `factory._vertexCountProperty(targetObject)`;
720 | }
721 |
722 | return (
723 | (typeof this.packer._vertexCountProperty === 'string')
724 | ? `targetObject.${this.packer._vertexCountProperty}`
725 | : `${this.packer._vertexCountProperty}`
726 | );
727 | }
728 |
729 | generateIndexCountExpr(): string
730 | {
731 | const idxCountProp = this.packer._indexCountProperty;
732 | const idxProp = this.packer._indexProperty;
733 |
734 | if (!idxCountProp)
735 | {
736 | return `targetObject['${idxProp}'].length`;
737 | }
738 |
739 | if (typeof idxCountProp === 'function')
740 | {
741 | return `factory._indexCountProperty(targetObject)`;
742 | }
743 | else if (typeof idxCountProp === 'string')
744 | {
745 | return `targetObject['${idxCountProp}']`;
746 | }
747 |
748 | return `${idxCountProp}`;
749 | }
750 |
751 | _sizeOf(i: number): number
752 | {
753 | return ViewableBuffer.sizeOf(
754 | this.packer._attribRedirects[i].type);
755 | }
756 | };
757 |
758 | export default BatchGeometryFactory;
759 |
--------------------------------------------------------------------------------
/packages/batch-renderer/src/BatchRenderer.ts:
--------------------------------------------------------------------------------
1 | import { StdBatchFactory } from './StdBatchFactory';
2 | import { BatchGeometryFactory } from './BatchGeometryFactory';
3 | import { AttributeRedirect } from './redirects/AttributeRedirect';
4 | import { BatchDrawer } from './BatchDrawer';
5 | import { ENV } from '@pixi/constants';
6 | import { ObjectRenderer, State } from '@pixi/core';
7 | import { UniformRedirect } from './redirects/UniformRedirect';
8 | import { resolve } from './utils/resolveProperty';
9 | import { resolveFunctionOrProperty } from './resolve';
10 | import { settings } from '@pixi/settings';
11 |
12 | import type {
13 | Renderer,
14 | Shader
15 | } from '@pixi/core';
16 | import type { DisplayObject } from '@pixi/display';
17 |
18 | export interface IBatchRendererOptions
19 | {
20 | // Standard pipeline
21 | attribSet: AttributeRedirect[];
22 | indexProperty: string;
23 | indexCountProperty?: string | number | ((object: DisplayObject) => number);
24 | vertexCountProperty?: string | number | ((object: DisplayObject) => number);
25 | textureProperty: string;
26 | texturesPerObject?: number;
27 | texIDAttrib: string;
28 | inBatchIDAttrib?: string;
29 | masterIDAttrib?: string;
30 | stateFunction?: (renderer: DisplayObject) => State;
31 | shaderFunction: (renderer: BatchRenderer) => Shader;
32 |
33 | // Components
34 | BatchFactoryClass?: typeof StdBatchFactory;
35 | BatchGeometryFactoryClass?: typeof BatchGeometryFactory;
36 | BatchDrawerClass?: typeof BatchDrawer;
37 |
38 | // Uniforms+Standard Pipeline
39 | uniformSet?: UniformRedirect[];
40 | uniformIDAttrib?: string;
41 | }
42 |
43 | /**
44 | * This object renderer renders multiple display-objects in batches. It can greatly
45 | * reduce the number of draw calls issued per frame.
46 | *
47 | * ## Batch Rendering Pipeline
48 | *
49 | * The batch rendering pipeline consists of the following stages:
50 | *
51 | * * **Display-Object Buffering**: Each display-object is kept in a buffer until it fills up or a
52 | * flush is required.
53 | *
54 | * * **Batch Generation**: In a sliding window, display-object batches are generated based off of certain
55 | * constraints like GPU texture units and the uniforms used in each display-object. This is done using an
56 | * instance of {@link BatchFactory}.
57 | *
58 | * * **Geometry Composition**: The geometries of all display-objects are merged together in a
59 | * composite geometry. This is done using an instance of {@link BatchGeometryFactory}.
60 | *
61 | * * **Drawing**: Each batch is rendered in-order using `gl.draw*`. The textures and
62 | * uniforms of each display-object are uploaded as arrays. This is done using an instance of
63 | * {@link BatchDrawer}.
64 | *
65 | * Each stage in this pipeline can be configured by overriding the appropriate component and passing that
66 | * class to `BatchRendererPluginFactory.from*`.
67 | *
68 | * ## Shaders
69 | *
70 | * ### Shader templates
71 | *
72 | * Since the max. display-object count per batch is not known until the WebGL context is created,
73 | * shaders are generated at runtime by processing shader templates. A shader templates has "%macros%"
74 | * that are replaced by constants at runtime.
75 | *
76 | * To use shader templates, simply use {@link BatchShaderFactory#derive}. This will generate a
77 | * function that derives a shader from your template at runtime.
78 | *
79 | * ### Textures
80 | *
81 | * The batch renderer uploads textures in the `uniform sampler2D uSamplers[%texturesPerBatch%];`. The
82 | * `varying float vTextureId` defines the index into this array that holds the current display-object's
83 | * texture.
84 | *
85 | * ### Uniforms
86 | *
87 | * This renderer currently does not support customized uniforms for display-objects. This is a
88 | * work-in-progress feature.
89 | *
90 | * ## Learn more
91 | * This batch renderer uses the PixiJS object-renderer API to hook itself:
92 | *
93 | * 1. [PIXI.ObjectRenderer]{@link http://pixijs.download/release/docs/PIXI.ObjectRenderer.html}
94 | *
95 | * 2. [PIXI.AbstractBatchRenderer]{@link http://pixijs.download/release/docs/PIXI.AbstractBatchRenderer.html}
96 | *
97 | * @example
98 | * import * as PIXI from 'pixi.js';
99 | * import { BatchRendererPluginFactory } from 'pixi-batch-renderer';
100 | *
101 | * // Define the geometry of your display-object and create a BatchRenderer using
102 | * // BatchRendererPluginFactory. Register it as a plugin with PIXI.Renderer.
103 | * PIXI.Renderer.registerPlugin('ExampleBatchRenderer', BatchRendererPluginFactory.from(...));
104 | *
105 | * class ExampleObject extends PIXI.Container
106 | * {
107 | * _render(renderer: PIXI.Renderer): void
108 | * {
109 | * // BatchRenderer will handle the whole rendering process for you!
110 | * renderer.batch.setObjectRenderer(renderer.plugins['ExampleBatchRenderer']);
111 | * renderer.plugins['ExampleBatchRenderer'].render(this);
112 | * }
113 | * }
114 | */
115 | export class BatchRenderer extends ObjectRenderer
116 | {
117 | /** @protected */
118 | public renderer: Renderer;
119 |
120 | // Standard pipeline
121 | /**
122 | * Attribute redirects
123 | *
124 | * @access protected
125 | */
126 | readonly _attribRedirects: AttributeRedirect[];
127 |
128 | /**
129 | * Indices property
130 | *
131 | * @access protected
132 | */
133 | readonly _indexProperty: string;
134 |
135 | /**
136 | * A manual resolution of the number of indicies in a display object's geometry. This is ignored if the
137 | * index buffer is not used (see _indexProperty). If not provided, the index buffer's entire length
138 | * is used.
139 | *
140 | * @access protected
141 | */
142 | readonly _indexCountProperty: string | number | ((object: DisplayObject) => number);
143 |
144 | /**
145 | * A manual resolution of the number of vertices in a display object's geometry. If not provided, this is
146 | * calculated as the number of element in the first attribute's buffer.
147 | *
148 | * @access protected
149 | */
150 | readonly _vertexCountProperty: string | number | ((object: DisplayObject) => number);
151 | readonly _textureProperty: string;
152 | readonly _texturesPerObject: number;
153 | readonly _texIDAttrib: string;
154 | readonly _inBatchIDAttrib: string;
155 | readonly _stateFunction: Function;
156 | readonly _shaderFunction: Function;
157 |
158 | // Uniforms+Standard Pipeline
159 | readonly _uniformRedirects: UniformRedirect[];
160 | readonly _uniformIDAttrib: string;
161 |
162 | // Master-ID optimization
163 | readonly _masterIDAttrib: string;
164 |
165 | // API Visiblity Note: These properties are used by component/factories and must be public;
166 | // however, they are prefixed with an underscore because they are not for exposure to the end-user.
167 |
168 | // Components
169 | _batchFactory: StdBatchFactory;
170 | _geometryFactory: BatchGeometryFactory;
171 | _drawer: BatchDrawer;
172 |
173 | // Display-object buffering
174 | _objectBuffer: DisplayObject[];
175 | _bufferedVertices: number;
176 | _bufferedIndices: number;
177 |
178 | // Drawer
179 | _shader: Shader;
180 |
181 | // WebGL Context config
182 | MAX_TEXTURES: number;
183 |
184 | // Component ctors
185 | protected readonly _BatchFactoryClass: typeof StdBatchFactory;
186 | protected readonly _BatchGeometryFactoryClass: typeof BatchGeometryFactory;
187 | protected readonly _BatchDrawerClass: typeof BatchDrawer;
188 |
189 | // Additional args
190 | protected readonly options: any;
191 |
192 | /**
193 | * Creates a batch renderer the renders display-objects with the described geometry.
194 | *
195 | * To register a batch-renderer plugin, you must use the API provided by `BatchRendererPluginFactory`.
196 | *
197 | * @param {PIXI.Renderer} renderer - renderer to attach to
198 | * @param {object} options
199 | * @param {AttributeRedirect[]} options.attribSet
200 | * @param {string | null} options.indexProperty
201 | * @param {string | number} [options.vertexCountProperty]
202 | * @param {string | null} options.textureProperty
203 | * @param {number} [options.texturesPerObject=1]
204 | * @param {string} options.texIDAttrib - name of texture-id attribute variable
205 | * @param {Function}[options.stateFunction=PIXI.State.for2d()] - returns a `PIXI.State` for an object
206 | * @param {Function} options.shaderFunction - generates a shader given this instance
207 | * @param {Class} [options.BatchGeometryFactory=BatchGeometry]
208 | * @param {Class} [options.BatchFactoryClass=StdBatchFactory]
209 | * @param {Class} [options.BatchDrawer=BatchDrawer]
210 | * @see BatchShaderFactory
211 | * @see StdBatchFactory
212 | * @see BatchGeometryFactory
213 | * @see BatchDrawer
214 | */
215 | constructor(renderer: Renderer, options: IBatchRendererOptions)
216 | {
217 | super(renderer);
218 |
219 | this._attribRedirects = options.attribSet;
220 | this._indexProperty = options.indexProperty;
221 | this._indexCountProperty = options.indexCountProperty;
222 | this._vertexCountProperty = options.vertexCountProperty;
223 |
224 | /**
225 | * Texture(s) property
226 | * @member {string}
227 | * @protected
228 | * @readonly
229 | */
230 | this._textureProperty = options.textureProperty;
231 |
232 | /**
233 | * Textures per display-object
234 | * @member {number}
235 | * @protected
236 | * @readonly
237 | * @default 1
238 | */
239 | this._texturesPerObject = typeof options.texturesPerObject !== 'undefined' ? options.texturesPerObject : 1;
240 |
241 | /**
242 | * Texture ID attribute
243 | * @member {string}
244 | * @protected
245 | * @readonly
246 | */
247 | this._texIDAttrib = options.texIDAttrib;
248 |
249 | /**
250 | * Indexes the display-object in the batch.
251 | * @member {string}
252 | * @protected
253 | * @readonly
254 | */
255 | this._inBatchIDAttrib = options.inBatchIDAttrib;
256 |
257 | /**
258 | * State generating function (takes a display-object)
259 | * @member {Function}
260 | * @default () => PIXI.State.for2d()
261 | * @protected
262 | * @readonly
263 | */
264 | this._stateFunction = options.stateFunction || ((): State => State.for2d());
265 |
266 | /**
267 | * Shader generating function (takes the batch renderer)
268 | * @member {Function}
269 | * @protected
270 | * @see BatchShaderFactory
271 | * @readonly
272 | */
273 | this._shaderFunction = options.shaderFunction;
274 |
275 | /**
276 | * Batch-factory class.
277 | * @member {Class}
278 | * @protected
279 | * @default StdBatchFactory
280 | * @readonly
281 | */
282 | this._BatchFactoryClass = options.BatchFactoryClass || StdBatchFactory;
283 |
284 | /**
285 | * Batch-geometry factory class. Its constructor takes one argument - this batch renderer.
286 | * @member {Class}
287 | * @protected
288 | * @default BatchGeometryFactory
289 | * @readonly
290 | */
291 | this._BatchGeometryFactoryClass = options.BatchGeometryFactoryClass || BatchGeometryFactory;
292 |
293 | /**
294 | * Batch drawer class. Its constructor takes one argument - this batch renderer.
295 | * @member {Class}
296 | * @protected
297 | * @default BatchDrawer
298 | * @readonly
299 | */
300 | this._BatchDrawerClass = options.BatchDrawerClass || BatchDrawer;
301 |
302 | /**
303 | * Uniform redirects. If you use uniforms in your shader, be sure to use one the compatible
304 | * batch factories (like {@link AggregateUniformsBatchFactory}).
305 | * @member {UniformRedirect[]}
306 | * @protected
307 | * @default null
308 | * @readonly
309 | */
310 | this._uniformRedirects = options.uniformSet || null;
311 |
312 | /**
313 | * Indexes the uniforms of the display-object in the uniform arrays. This is not equal to the
314 | * in-batch ID because equal uniforms are not uploaded twice.
315 | * @member {string}
316 | * @protected
317 | * @readonly
318 | */
319 | this._uniformIDAttrib = options.uniformIDAttrib;
320 |
321 | /**
322 | * This is an advanced feature that allows you to pack the {@code _texIDAttrib}, {@code _uniformIDAttrib},
323 | * {@code _inBatchIDAttrib}, and other information into one 32-bit float attribute. You can then unpack
324 | * them in the vertex shader and pass varyings to the fragment shader (because {@code int} varyings are not
325 | * supported).
326 | *
327 | * To use it, you must provide your own {@link BatchGeometryFactory} that overrides
328 | * {@link BatchGeometryFactory#append} and sets the {@code _masterIDAttrib}.
329 | */
330 | this._masterIDAttrib = options.masterIDAttrib;
331 |
332 | /**
333 | * The options used to create this batch renderer.
334 | * @readonly {object}
335 | * @protected
336 | * @readonly
337 | */
338 | this.options = options;
339 |
340 | if (options.masterIDAttrib)
341 | {
342 | this._texIDAttrib = this._masterIDAttrib;
343 | this._uniformIDAttrib = this._masterIDAttrib;
344 | this._inBatchIDAttrib = this._masterIDAttrib;
345 | }
346 |
347 | // Although the runners property is not a public API, it is required to
348 | // handle contextChange events.
349 | this.renderer.runners.contextChange.add(this);
350 |
351 | // If the WebGL context has already been created, initialization requires a
352 | // syntheic call to contextChange.
353 | if (this.renderer.gl)
354 | {
355 | this.contextChange();
356 | }
357 |
358 | this._objectBuffer = [];
359 | this._bufferedVertices = 0;
360 | this._bufferedIndices = 0;
361 | this._shader = null;
362 | }
363 |
364 | /**
365 | * Internal method that is called whenever the renderer's WebGL context changes.
366 | */
367 | contextChange(): void
368 | {
369 | const gl = this.renderer.gl;
370 |
371 | if (settings.PREFER_ENV === ENV.WEBGL_LEGACY)
372 | {
373 | this.MAX_TEXTURES = 1;
374 | }
375 | else
376 | {
377 | this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), settings.SPRITE_MAX_TEXTURES);
378 | }
379 |
380 | /**
381 | * @member {_BatchFactoryClass}
382 | * @readonly
383 | * @protected
384 | */
385 | this._batchFactory = new this._BatchFactoryClass(this);
386 |
387 | /**
388 | * @member {_BatchGeometryFactoryClass}
389 | * @readonly
390 | * @protected
391 | */
392 | this._geometryFactory = new this._BatchGeometryFactoryClass(this);
393 |
394 | /**
395 | * @member {_BatchDrawerClass}
396 | * @readonly
397 | * @protected
398 | */
399 | this._drawer = new this._BatchDrawerClass(this);
400 | }
401 |
402 | /**
403 | * This is an internal method. It ensures that the batch renderer is ready to start buffering display-objects.
404 | * This is automatically invoked by the renderer's batch system.
405 | *
406 | * @override
407 | */
408 | start(): void
409 | {
410 | this._objectBuffer.length = 0;
411 | this._bufferedVertices = 0;
412 | this._bufferedIndices = 0;
413 |
414 | this._shader = this._shaderFunction(this);
415 | }
416 |
417 | /**
418 | * Adds the display-object to be rendered in a batch. Your display-object's render/_render method should call
419 | * this as follows:
420 | *
421 | * ```js
422 | * renderer.setObjectRenderer();
423 | * .render(this);
424 | * ```
425 | *
426 | * @override
427 | */
428 | render(displayObject: DisplayObject): void
429 | {
430 | this._objectBuffer.push(displayObject);
431 |
432 | this._bufferedVertices += this.calculateVertexCount(displayObject);
433 |
434 | if (this._indexProperty)
435 | {
436 | this._bufferedIndices += this.calculateIndexCount(displayObject);
437 | }
438 | }
439 |
440 | /**
441 | * Forces buffered display-objects to be rendered immediately. This should not be called unless absolutely
442 | * necessary like the following scenarios:
443 | *
444 | * * before directly rendering your display-object, to preserve render-order.
445 | *
446 | * * to do a nested render pass (calling `Renderer#render` inside a `render` method)
447 | * because the PixiJS renderer is not re-entrant.
448 | *
449 | * @override
450 | */
451 | flush(): void
452 | {
453 | const { _batchFactory: batchFactory, _geometryFactory: geometryFactory, _stateFunction: stateFunction } = this;
454 | const buffer = this._objectBuffer;
455 | const bufferLength = buffer.length;
456 |
457 | // Reset components
458 | batchFactory.reset();
459 | geometryFactory.init(this._bufferedVertices, this._bufferedIndices);
460 |
461 | let batchStart = 0;
462 |
463 | // Loop through display-objects and create batches
464 | for (let objectIndex = 0; objectIndex < bufferLength;)
465 | {
466 | const target = buffer[objectIndex];
467 | const wasPut = batchFactory.put(target, resolveFunctionOrProperty(target, stateFunction));
468 |
469 | if (!wasPut)
470 | {
471 | batchFactory.build(batchStart);
472 | batchStart = objectIndex;
473 | }
474 | else
475 | {
476 | ++objectIndex;
477 | }
478 | }
479 |
480 | // Generate the last batch, if required.
481 | if (!batchFactory.ready())
482 | {
483 | batchFactory.build(batchStart);
484 | }
485 |
486 | const batchList = batchFactory.access();
487 | const batchCount = batchFactory.size();
488 | let indices = 0;
489 |
490 | // Loop through batches and their display-object list to compose geometry
491 | for (let i = 0; i < batchCount; i++)// loop-per(batch)
492 | {
493 | const batch = batchList[i];
494 | const batchBuffer = batch.batchBuffer;
495 | const batchLength = batchBuffer.length;
496 |
497 | let vertexCount = 0;
498 | let indexCount = 0;
499 |
500 | batch.geometryOffset = indices;
501 |
502 | for (let j = 0; j < batchLength; j++)// loop-per(targetObject)
503 | {
504 | const targetObject = batchBuffer[j];
505 |
506 | if (this._indexProperty)
507 | {
508 | indexCount += this.calculateIndexCount(targetObject);
509 | }
510 | else
511 | {
512 | vertexCount += this.calculateVertexCount(targetObject);
513 | }
514 |
515 | geometryFactory.append(targetObject, batch);
516 | }
517 |
518 | // externally-defined properties for draw calls
519 | batch.$vertexCount = vertexCount;
520 | batch.$indexCount = indexCount;
521 |
522 | indices += batch.$indexCount;
523 | }
524 |
525 | // BatchDrawer handles the rest!
526 | this._drawer.draw();
527 | }
528 |
529 | /**
530 | * Internal method that stops buffering of display-objects and flushes any existing buffers.
531 | *
532 | * @override
533 | */
534 | stop(): void
535 | {
536 | if (this._bufferedVertices)
537 | {
538 | this.flush();
539 | }
540 | }
541 |
542 | /**
543 | * Calculates the number of vertices in the display object's geometry.
544 | *
545 | * @param object
546 | */
547 | protected calculateVertexCount(object: DisplayObject): number
548 | {
549 | return resolve(
550 | object,
551 | this._vertexCountProperty,
552 | resolve>(object, this._attribRedirects[0].source).length / (this._attribRedirects[0].size as number)
553 | );
554 | }
555 |
556 | /**
557 | * Calculates the number of indices in the display object's geometry.
558 | *
559 | * @param object
560 | */
561 | protected calculateIndexCount(object: DisplayObject): number
562 | {
563 | if (!this._indexProperty)
564 | {
565 | return 0;
566 | }
567 |
568 | return resolve(
569 | object,
570 | this._indexCountProperty,
571 | resolve(object, this._indexProperty).length,
572 | );
573 | }
574 | }
575 |
576 | export default BatchRenderer;
577 |
--------------------------------------------------------------------------------
/packages/batch-renderer/src/BatchRendererPluginFactory.ts:
--------------------------------------------------------------------------------
1 | import { BatchRenderer } from './BatchRenderer';
2 | import { AttributeRedirect } from './redirects/AttributeRedirect';
3 | import BatchGeometryFactory from './BatchGeometryFactory';
4 | import StdBatchFactory from './StdBatchFactory';
5 |
6 | import type { BatchDrawer } from './BatchDrawer';
7 | import type { DisplayObject } from '@pixi/display';
8 | import type { Renderer } from '@pixi/core';
9 | import type { UniformRedirect } from './redirects';
10 |
11 | // (Uniforms?)+Geometry+Textures is the standard pipeline in Pixi's AbstractBatchRenderer.
12 | export interface IBatchRendererStdOptions
13 | {
14 | attribSet: AttributeRedirect[];
15 | vertexCountProperty?: string | number | ((object: DisplayObject) => number);
16 | indexCountProperty?: string | number | ((object: DisplayObject) => number);
17 | indexProperty: string;
18 | textureProperty: string;
19 | texturesPerObject?: number;
20 | texIDAttrib: string;
21 | inBatchIDAttrib?: string;
22 | styleIDAttrib?: string;
23 | stateFunction?: (brend: DisplayObject) => any;
24 | shaderFunction: (brend: BatchRenderer) => any;
25 | BatchFactoryClass?: typeof StdBatchFactory;
26 | BatchRendererClass?: typeof BatchRenderer;
27 | BatchGeometryFactoryClass?: typeof BatchGeometryFactory;
28 | BatchDrawerClass?: typeof BatchDrawer;
29 |
30 | uniformSet?: UniformRedirect[];
31 | uniformIDAttrib?: string;
32 | }
33 |
34 | /**
35 | * Factory class for creating a batch-renderer.
36 | *
37 | * @example
38 | * import * as PIXI from 'pixi.js';
39 | * import { AttributeRedirect, BatchShaderFactory, BatchRendererPluginFactory } from 'pixi-batch-renderer';
40 | *
41 | * // Define the geometry of Sprite.
42 | * const attribSet = [
43 | * // Sprite vertexData contains global coordinates of the corners
44 | * new AttributeRedirect({
45 | * source: 'vertexData',
46 | * attrib: 'aVertex',
47 | * type: 'float32',
48 | * size: 2,
49 | * glType: PIXI.TYPES.FLOAT,
50 | * glSize: 2,
51 | * }),
52 | * // Sprite uvs contains the normalized texture coordinates for each corner/vertex
53 | * new AttributeRedirect({
54 | * source: 'uvs',
55 | * attrib: 'aTextureCoord',
56 | * type: 'float32',
57 | * size: 2,
58 | * glType: PIXI.TYPES.FLOAT,
59 | * glSize: 2,
60 | * }),
61 | * ];
62 | *
63 | * const shaderFunction = new BatchShaderFactory(// 1. vertexShader
64 | * `
65 | * attribute vec2 aVertex;
66 | * attribute vec2 aTextureCoord;
67 | * attribute float aTextureId;
68 | *
69 | * varying float vTextureId;
70 | * varying vec2 vTextureCoord;
71 | *
72 | * uniform mat3 projectionMatrix;
73 | *
74 | * void main() {
75 | * gl_Position = vec4((projectionMatrix * vec3(aVertex, 1)).xy, 0, 1);
76 | * vTextureId = aTextureId;
77 | * vTextureCoord = aTextureCoord;
78 | * }
79 | * `,
80 | * `
81 | * uniform sampler2D uSamplers[%texturesPerBatch%];
82 | * varying float vTextureId;
83 | * varying vec2 vTextureCoord;
84 | *
85 | * void main(void){
86 | * vec4 color;
87 | *
88 | * // get color, which is the pixel in texture uSamplers[vTextureId] at vTextureCoord
89 | * for (int k = 0; k < %texturesPerBatch%; ++k) {
90 | * if (int(vTextureId) == k) {
91 | * color = texture2D(uSamplers[k], vTextureCoord);
92 | * break;
93 | * }
94 | * }
95 | *
96 | * gl_FragColor = color;
97 | * }
98 | * `,
99 | * {// we don't use any uniforms except uSamplers, which is handled by default!
100 | * },
101 | * // no custom template injectors
102 | * // disable vertex shader macros by default
103 | * ).derive();
104 | *
105 | * // Produce the SpriteBatchRenderer class!
106 | * const SpriteBatchRenderer = BatchRendererPluginFactory.from({
107 | * attribSet,
108 | * indexProperty: 'indices',
109 | * textureProperty: 'texture',
110 | * texturesPerObject: 1, // default
111 | * texIDAttrib: 'aTextureId',
112 | * stateFunction: () => PIXI.State.for2d(), // default
113 | * shaderFunction
114 | * });
115 | *
116 | * PIXI.Renderer.registerPlugin('customBatch', SpriteBatchRenderer);
117 | *
118 | * // Sprite will render using SpriteBatchRenderer instead of default PixiJS
119 | * // batch renderer. Instead of targetting PIXI.Sprite, you can write a batch
120 | * // renderer for a custom display-object too! (See main page for that example!)
121 | * const exampleSprite = PIXI.Sprite.from('./asset/example.png');
122 | * exampleSprite.pluginName = 'customBatch';
123 | * exampleSprite.width = 128;
124 | * exampleSprite.height = 128;
125 | */
126 | export class BatchRendererPluginFactory
127 | {
128 | /**
129 | * Generates a fully customized `BatchRenderer` that aggregates primitives and textures. This is useful
130 | * for non-uniform based display-objects.
131 | *
132 | * @param {object} options
133 | * @param {AttributeRedirect[]} options.attribSet - set of geometry attributes
134 | * @param {string | Array} options.indexProperty - no. of indices on display-object
135 | * @param {string | number | function(DisplayObject): number}[options.vertexCountProperty] - no. of vertices on display-object
136 | * @param {string | number | function(DisplayObject): number}[options.indexCountProperty] - no. of indicies on display object
137 | * @param {string} options.textureProperty - textures used in display-object
138 | * @param {number} options.texturePerObject - no. of textures used per display-object
139 | * @param {string} options.texIDAttrib - used to find texture for each display-object (index into array)
140 | * @param {string} options.uniformIDAttrib - used to find the uniform data for each display-object (index into array)
141 | * @param {string} options.inBatchIDAttrib - used get the index of the object in the batch
142 | * @param {string} options.masterIDAttrib - used to combine texture-ID, batch-ID, uniform-ID and other
143 | * information into one attribute. This is an advanced optimization. It is expected you override
144 | * {@code BatchGeometryFactory#append} and supply the `_masterID` property.
145 | * @param {string | Function}[options.stateFunction= ()=>PIXI.State.for2d()] - callback that finds the WebGL
146 | * state required for display-object shader
147 | * @param {Function} options.shaderFunction - shader generator function
148 | * @param {Class}[options.BatchGeometryFactoryClass] - custom batch geometry factory class
149 | * @param {Class} [options.BatchFactoryClass] - custom batch factory class
150 | * @param {Class} [options.BatchRendererClass] - custom batch renderer class
151 | * @param {Class} [options.BatchDrawerClass] - custom batch drawer class
152 | * @static
153 | */
154 | static from(options: IBatchRendererStdOptions): typeof BatchRenderer
155 | {
156 | // This class wraps around BatchRendererClass's constructor and passes the options from the outer scope.
157 | return class extends (options.BatchRendererClass || BatchRenderer)
158 | {
159 | constructor(renderer: Renderer)
160 | {
161 | super(renderer, options);
162 | }
163 | };
164 | }
165 | }
166 |
167 | export default BatchRendererPluginFactory;
168 |
--------------------------------------------------------------------------------
/packages/batch-renderer/src/BatchShaderFactory.ts:
--------------------------------------------------------------------------------
1 | import { AggregateUniformsBatchFactory } from './AggregateUniformsBatchFactory';
2 | import { Shader } from '@pixi/core';
3 |
4 | import type { BatchRenderer } from './BatchRenderer';
5 |
6 | // This file might need a cleanup :)
7 |
8 | // JavaScript is stupid enough not to have a replaceAll in String. This is a temporary
9 | // solution and we should depend on an actually polyfill.
10 | function _replaceAll(target: string, search: string, replacement: string): string
11 | {
12 | return target.replace(new RegExp(search, 'g'), replacement);
13 | }
14 |
15 | function injectTexturesPerBatch(batchRenderer: BatchRenderer): string
16 | {
17 | return `${batchRenderer.MAX_TEXTURES}`;
18 | }
19 |
20 | const INJECTORS = {
21 | uniformsPerBatch(renderer: BatchRenderer): string
22 | {
23 | return `${(renderer._batchFactory as AggregateUniformsBatchFactory).MAX_UNIFORMS}`;
24 | },
25 | };
26 |
27 | /**
28 | * Exposes an easy-to-use API for generating shader-functions to use in
29 | * the batch renderer!
30 | *
31 | * You are required to provide an injector map, which maps macros to functions
32 | * that return a string value for those macros given a renderer. By default, only one
33 | * injector is used - the textures per batch `%texturesPerBatch%` macro. This is replaced by
34 | * the number of textures passed to the `uSamplers` textures uniform.
35 | *
36 | * **Built-in Injectors**:
37 | *
38 | * * `%texturesPerBatch%`: replaced by the max. textures allowed by WebGL context
39 | *
40 | * * `%uniformsPerBatch%`: replaced by the (aggregate-uniforms) batch factory's `MAX_UNIFORMS` property.
41 | */
42 | export class BatchShaderFactory
43 | {
44 | protected _vertexShaderTemplate: string;
45 | protected _fragmentShaderTemplate: string;
46 | protected _uniforms: any;
47 | protected _templateInjectors: any;
48 |
49 | protected disableVertexShaderTemplate: boolean;
50 |
51 | protected _cache: any;
52 | protected _cState: any;
53 |
54 | /**
55 | * WARNING: Do not pass `uSamplers` in your uniforms. They
56 | * will be added to your shader instance directly.
57 | *
58 | * @param {string} vertexShaderTemplate
59 | * @param {string} fragmentShaderTemplate
60 | * @param {UniformGroup | Map} uniforms
61 | * @param {Object.} [templateInjectors]
62 | * @param {boolean} [disableVertexShaderTemplate=true] - turn off (true)
63 | * if you aren't using macros in the vertex shader
64 | */
65 | constructor(
66 | vertexShaderTemplate: string,
67 | fragmentShaderTemplate: string,
68 | uniforms = {},
69 | templateInjectors: any = {},
70 | disableVertexShaderTemplate = true,
71 | )
72 | {
73 | if (!templateInjectors['%texturesPerBatch%'])
74 | {
75 | templateInjectors['%texturesPerBatch%'] = injectTexturesPerBatch;
76 | }
77 | if (!templateInjectors['%uniformsPerBatch%'])
78 | {
79 | templateInjectors['%uniformsPerBatch%'] = INJECTORS.uniformsPerBatch;
80 | }
81 |
82 | this._vertexShaderTemplate = vertexShaderTemplate;
83 | this._fragmentShaderTemplate = fragmentShaderTemplate;
84 | this._uniforms = uniforms;
85 | this._templateInjectors = templateInjectors;
86 |
87 | /**
88 | * Disable vertex shader templates to speed up shader
89 | * generation.
90 | *
91 | * @member {Boolean}
92 | */
93 | this.disableVertexShaderTemplate = disableVertexShaderTemplate;
94 |
95 | /**
96 | * Maps the stringifed state of the batch renderer to the
97 | * generated shader.
98 | *
99 | * @private
100 | * @member {Object.}
101 | */
102 | this._cache = {};
103 |
104 | /**
105 | * Unstringifed current state of the batch renderer.
106 | *
107 | * @private
108 | * @member {Object.}
109 | * @see {ShaderGenerator#_generateInjectorBasedState}
110 | */
111 | this._cState = null;
112 | }
113 |
114 | /**
115 | * This essentially returns a function for generating the shader for a batch
116 | * renderer.
117 | *
118 | * @return shader function that can be given to the batch renderer
119 | */
120 | derive(): (brend: BatchRenderer) => Shader
121 | {
122 | return (batchRenderer: BatchRenderer): Shader =>
123 | {
124 | const stringState = this._generateInjectorBasedState(batchRenderer);
125 | const cachedShader = this._cache[stringState];
126 |
127 | if (cachedShader)
128 | {
129 | return cachedShader;
130 | }
131 |
132 | return this._generateShader(stringState, batchRenderer);
133 | };
134 | }
135 |
136 | protected _generateInjectorBasedState(batchRenderer: BatchRenderer): string
137 | {
138 | let state = '';
139 | const cState: any = this._cState = {};
140 |
141 | for (const injectorMacro in this._templateInjectors)
142 | {
143 | const val = this._templateInjectors[injectorMacro](batchRenderer);
144 |
145 | state += val;
146 | cState[injectorMacro] = val;
147 | }
148 |
149 | return state;
150 | }
151 |
152 | protected _generateShader(stringState: string, renderer: BatchRenderer): Shader
153 | {
154 | let vertexShaderTemplate = this._vertexShaderTemplate.slice(0);
155 |
156 | let fragmentShaderTemplate = this._fragmentShaderTemplate.slice(0);
157 |
158 | for (const injectorTemplate in this._cState)
159 | {
160 | if (!this.disableVertexShaderTemplate)
161 | {
162 | vertexShaderTemplate = _replaceAll(vertexShaderTemplate,
163 | injectorTemplate, this._cState[injectorTemplate]);
164 | }
165 |
166 | fragmentShaderTemplate = _replaceAll(fragmentShaderTemplate,
167 | injectorTemplate, this._cState[injectorTemplate]);
168 | }
169 |
170 | const shader = Shader.from(vertexShaderTemplate,
171 | fragmentShaderTemplate, this._uniforms);
172 |
173 | const textures = new Array(renderer.MAX_TEXTURES);
174 |
175 | for (let i = 0; i < textures.length; i++)
176 | {
177 | textures[i] = i;
178 | }
179 | shader.uniforms.uSamplers = textures;
180 |
181 | this._cache[stringState] = shader;
182 |
183 | return shader;
184 | }
185 | }
186 |
187 | export default BatchShaderFactory;
188 |
--------------------------------------------------------------------------------
/packages/batch-renderer/src/StdBatch.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | BaseTexture,
3 | Renderer,
4 | State,
5 | } from '@pixi/core';
6 | import type { DisplayObject } from '@pixi/display';
7 |
8 | /**
9 | * Resources that need to be uploaded to WebGL to render one batch.
10 | *
11 | * To customize batches, you must create your own batch factory by extending the
12 | * {@link StdBatchFactory} class.
13 | */
14 | export class StdBatch
15 | {
16 | geometryOffset: number;
17 | uidMap: any;
18 | state: State;
19 |
20 | batchBuffer: Array;
21 | textureBuffer: Array;
22 |
23 | constructor(geometryOffset?: number)
24 | {
25 | /**
26 | * Index of the first vertex of this batch's geometry in the uploaded geometry.
27 | *
28 | * @member {number}
29 | */
30 | this.geometryOffset = geometryOffset;
31 |
32 | /**
33 | * Textures that are used by the display-object's in this batch.
34 | *
35 | * @member {Array}
36 | */
37 | this.textureBuffer = null;
38 |
39 | /**
40 | * Map of base-texture UIDs to texture indices into `uSamplers`.
41 | *
42 | * @member {Map}
43 | */
44 | this.uidMap = null;
45 |
46 | /**
47 | * State required to render this batch.
48 | *
49 | * @member {PIXI.State}
50 | */
51 | this.state = null;
52 | }
53 |
54 | /**
55 | * Uploads the resources required before rendering this batch. If you override
56 | * this, you must call `super.upload`.
57 | *
58 | * @param {PIXI.Renderer} renderer
59 | */
60 | upload(renderer: Renderer): void
61 | {
62 | this.textureBuffer.forEach((tex, i) =>
63 | {
64 | renderer.texture.bind(tex, i);
65 | });
66 |
67 | renderer.state.set(this.state);
68 | }
69 |
70 | /**
71 | * Reset this batch to become "fresh"!
72 | */
73 | reset(): void
74 | {
75 | this.textureBuffer = this.uidMap = this.state = null;
76 |
77 | if (this.batchBuffer)
78 | {
79 | this.batchBuffer.length = 0;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/packages/batch-renderer/src/StdBatchFactory.ts:
--------------------------------------------------------------------------------
1 | import { StdBatch } from './StdBatch';
2 | import BatchRenderer from './BatchRenderer';
3 |
4 | import type { BaseTexture, State, Texture } from '@pixi/core';
5 | import type { DisplayObject } from '@pixi/display';
6 |
7 | /**
8 | * Factory for producing "standard" (based on state, geometry, & textures) batches of
9 | * display-objects.
10 | *
11 | * **NOTE:** Instead of "building" batches, this factory actually keeps the batches in
12 | * a buffer so they can be accessed together at the end.
13 | *
14 | * **Shared Textures**: If display-objects in the same batch use the same base-texture,
15 | * then that base-texture is not uploaded twice. This allows for more better batch density
16 | * when you use texture atlases (textures with same base-texture). This is one reason why
17 | * textures are treated as "special" uniforms.
18 | */
19 | export class StdBatchFactory
20 | {
21 | protected _renderer: BatchRenderer;
22 |
23 | protected _textureCount: number;
24 | protected _textureLimit: number;
25 | protected _textureProperty: string;
26 |
27 | /** @internal */
28 | public _batchBuffer: Array;
29 | protected _state: State;
30 |
31 | protected _textureBuffer: any;
32 | protected _textureBufferLength: number;
33 | protected _textureIndexedBuffer: Array;
34 | protected _textureIndexMap: any;
35 |
36 | protected _batchPool: any[];
37 | protected _batchCount: number;
38 |
39 | // _putTexture is optimized for the one texture/display-object case.
40 | protected _putTexture: any;
41 |
42 | /**
43 | * @param renderer
44 | */
45 | constructor(renderer: BatchRenderer)
46 | {
47 | /**
48 | * @protected
49 | */
50 | this._renderer = renderer;
51 | this._state = null;
52 |
53 | /**
54 | * Textures per display-object
55 | * @member {number}
56 | */
57 | this._textureCount = renderer._texturesPerObject;
58 |
59 | /**
60 | * Property in which textures are kept of display-objects
61 | * @member {string}
62 | */
63 | this._textureProperty = renderer._textureProperty;
64 |
65 | /**
66 | * Max. no of textures per batch (should be <= texture units of GPU)
67 | * @member {number}
68 | */
69 | this._textureLimit = renderer.MAX_TEXTURES;
70 |
71 | /**
72 | * @member {object}
73 | */
74 | this._textureBuffer = {}; // uid : texture map
75 | this._textureBufferLength = 0;
76 | this._textureIndexedBuffer = []; // array of textures
77 | this._textureIndexMap = {}; // uid : index in above
78 |
79 | /**
80 | * Display-objects in current batch
81 | * @protected
82 | */
83 | this._batchBuffer = [];
84 |
85 | /**
86 | * Pool to batch objects into which data is fed.
87 | * @member {any[]}
88 | * @protected
89 | */
90 | this._batchPool = [];
91 |
92 | /**
93 | * Number of batches created since last reset.
94 | * @member {number}
95 | * @protected
96 | */
97 | this._batchCount = 0;
98 |
99 | if (this._textureCount === 1)
100 | {
101 | this._putTexture = this._putSingleTexture;
102 | }
103 | else
104 | {
105 | this._putTexture = this._putAllTextures;
106 | }
107 | }
108 |
109 | /**
110 | * Puts the display-object into the current batch, if possible.
111 | *
112 | * @param targetObject - object to add
113 | * @param state - state required by that object
114 | * @return whether the object was added to the batch. If it wasn't, you should "build" it.
115 | */
116 | put(targetObject: DisplayObject, state: State): boolean
117 | {
118 | // State compat
119 | if (!this._state)
120 | {
121 | this._state = state;
122 | }
123 | else if (this._state.data !== state.data)
124 | {
125 | return false;
126 | }
127 |
128 | // Customized compat
129 | if (!this._put(targetObject))
130 | {
131 | return false;
132 | }
133 |
134 | // Texture compat
135 | if (this._textureCount > 0 && !this._putTexture((targetObject as any)[this._textureProperty]))
136 | {
137 | return false;
138 | }
139 |
140 | this._batchBuffer.push(targetObject);
141 |
142 | return true;
143 | }
144 |
145 | /**
146 | * Creates the batch object and pushes it into the pool This also resets any state
147 | * so that a new batch can be started again.
148 | *
149 | * @param batch
150 | */
151 | build(geometryOffset: number): void
152 | {
153 | const batch = this._nextBatch() as StdBatch;
154 |
155 | batch.geometryOffset = geometryOffset;
156 | this._buildBatch(batch);
157 |
158 | this._state = null;
159 | this._batchBuffer = [];
160 | this._textureBuffer = {};
161 | this._textureIndexMap = {};
162 | this._textureBufferLength = 0;
163 | this._textureIndexedBuffer = [];
164 | }
165 |
166 | /**
167 | * @returns {boolean} - whether this factory is ready to start a new batch from
168 | * "start". If not, then the current batch must be built before starting a new one.
169 | */
170 | ready(): boolean
171 | {
172 | return this._batchBuffer.length === 0;
173 | }
174 |
175 | /**
176 | * Clears the batch pool.
177 | */
178 | reset(): void
179 | {
180 | this._batchCount = 0;
181 | }
182 |
183 | /**
184 | * Returns the built batch pool. The array returned may be larger than the pool
185 | * itself.
186 | *
187 | * @returns {Array