diff --git a/package-lock.json b/package-lock.json index 915d26ae..e403ee21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@next2d/player", - "version": "3.12.1", + "version": "3.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@next2d/player", - "version": "3.12.1", + "version": "3.13.0", "license": "MIT", "workspaces": [ "packages/*" @@ -22,11 +22,11 @@ "@typescript-eslint/eslint-plugin": "^8.71.0", "@typescript-eslint/parser": "^8.71.0", "@webgpu/types": "^0.1.74", - "eslint": "^10.11.0", + "eslint": "^10.12.0", "eslint-plugin-unused-imports": "^4.4.1", "globals": "^17.13.0", "jsdom": "^30.1.1", - "rollup": "^4.63.6", + "rollup": "^4.64.0", "tslib": "^2.8.1", "typescript": "^6.0.3", "vite": "^8.3.2", @@ -56,15 +56,15 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-7.1.2.tgz", - "integrity": "sha512-99DHAnXDB5z6EEK+9GMpVI7Mw4oxj97dY5bpOzMnjADQWxI8rN6TvTduuFLUhUMlS7/CfVZ06tcZsus6cltnNw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-7.1.3.tgz", + "integrity": "sha512-1t1U8Cm3RBl9vh3RAZhjkPaduzLSvQJRToRrxpGR/eDRW99mOREleuu+mWfiFa795bUzjwht7NgphCKTs2ianQ==", "dev": true, "license": "MIT", "dependencies": { - "@csstools/css-calc": "^3.4.1", - "@csstools/css-color-parser": "^4.2.4", - "@csstools/css-parser-algorithms": "^4.0.1", + "@csstools/css-calc": "^3.4.2", + "@csstools/css-color-parser": "^4.2.5", + "@csstools/css-parser-algorithms": "^4.0.2", "@csstools/css-tokenizer": "^4.0.2", "lru-cache": "^11.5.3" }, @@ -73,9 +73,9 @@ } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.3.tgz", - "integrity": "sha512-MDKmPBa6CfNN1zAJroTl7Uc+nBbcNtfE/F24iROfZAtcLffEeY3IbHR8XeHH5dJBspMYTpAsjEYmnp3tAmoVxw==", + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.4.tgz", + "integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==", "dev": true, "license": "MIT", "dependencies": { @@ -146,9 +146,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.4.2.tgz", - "integrity": "sha512-tL6LRF844sHjAKO7jt2w+uHo8Tocqpb9+KPUvIQETpR4RU87IaUzDuOV0QyzuQ37jmzFgwPHLLtLu/PDuH5qgg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.4.3.tgz", + "integrity": "sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==", "dev": true, "funding": [ { @@ -170,9 +170,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.5.tgz", - "integrity": "sha512-Qqbp5pjbMwoaErP2t5qne5QDVDr3G5243oNW8ev5zfyyoqbzrQKbYDpKP/cuo3xBkzwqZqpP/rChrJhBdyTX7g==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.6.tgz", + "integrity": "sha512-iiPQ3iRWwnJkeEn6RIu6SJPr7hYrLz6XZ9s/QZl+2/LI5KQVjpl2fdmDSZKuD4xP6GMmMPgHFFXg6k1Wkz0Trg==", "dev": true, "funding": [ { @@ -187,7 +187,7 @@ "license": "MIT", "dependencies": { "@csstools/color-helpers": "^6.1.2", - "@csstools/css-calc": "^3.4.2" + "@csstools/css-calc": "^3.4.3" }, "engines": { "node": ">=20.19.0" @@ -1126,9 +1126,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.6.tgz", - "integrity": "sha512-G6xF9OVRWsbHadMfoPNUDwW/Jt70QYG+ZDUvtYCFxjf3SPhqVAO0u9ShXZ7Cf5silVoRWyN4bDjCEKnJkXkw0w==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.0.tgz", + "integrity": "sha512-FLLpX941CD/7aBvX3GqLGBM9OvY5PGXTryPJkG5r5/OuBpZpV5k45Uo3RxAvqd35tsyc3hH1bhtz921IKzoIzg==", "cpu": [ "arm" ], @@ -1140,9 +1140,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.6.tgz", - "integrity": "sha512-Us/kTH5e2anr1CQvO8MEq4eeCVcIXI3Ag7OFxDI30n2OW7Q7xWuxo8ItbYq28PG2Sm1NWmZzG9+SqVEaj25SFA==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.0.tgz", + "integrity": "sha512-iI32XM/jnDYJsvx5ai/za/7gWO9XoSeFFtU06/ZCaRgyilfJOjaopD3nOpk2WrTCGOyeDt4hleUiGOAnd5tHNg==", "cpu": [ "arm64" ], @@ -1154,9 +1154,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.6.tgz", - "integrity": "sha512-fwaSNrSHp9PJuEh4bKtd0mYC+G3FeVDjSTmsSiSlIYu7TFCpxrfB6lk7Ao8cIbswFyklPB1ZDUV8LkLBM15VWw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.0.tgz", + "integrity": "sha512-dRBZlbt2sEtF8Mn4MODThze9fIPdDoOhVSlXMAKy5dQbOYK9pOOoPY4Fat3OTtr0+8zbJo5TTZwlsfxY/kDNJg==", "cpu": [ "arm64" ], @@ -1168,9 +1168,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.6.tgz", - "integrity": "sha512-QR2tx26gCeGh4eAGnUasfnJuRc+d7Vc0Jo1/fYtKGuiKqCuhbVpbNEvqgYGBwGDZGyS04jj+zdHi9W8uvb1Sdw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.0.tgz", + "integrity": "sha512-Y3whrJOMucfkhgNkvcM93M6j8C53iEAMmPMYD99C6G1kUy4ELigLWk9ar2/4aCBSRgsql0zdlOemLgW2m5GZgQ==", "cpu": [ "x64" ], @@ -1182,9 +1182,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.6.tgz", - "integrity": "sha512-7E1wRJEW8r7WlqmCy58ugndWHrzoEV/pRt2x0D9el7Inra62+Cw00lYW9XhW6iUCtFjhNw49McsSCIzJ/nG8uw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.0.tgz", + "integrity": "sha512-S6S1GEzPjFF6IONYJX3AKt/799+8RgBgCcwRnUo+Ep1IvBxUbw5M51iU0TDdK2NWWVnSvCQ8yi6Em0fGASMOng==", "cpu": [ "arm64" ], @@ -1196,9 +1196,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.6.tgz", - "integrity": "sha512-13KJeF+vDswMzEHmMIouCVcEQdov3CzAA3ZhM8QFmBJV/FZ/VMZI49UHDvPXTK+wokk86kYPmQ9Qb3xGGJbZoQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.0.tgz", + "integrity": "sha512-Ed1DPoFENkXj4IGzSB12SesV40iP6LxCCrbTIkSGIQBpOOxpBM+0kj62lmaeIHfMWTDoddqTOtqc7s8LYfn+Pw==", "cpu": [ "x64" ], @@ -1210,9 +1210,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.6.tgz", - "integrity": "sha512-hV+W1r8HM84PER4If94QuzrrD0DNk4CxobNhVUiz9ZiiWHDzJss4Jra2AYGH1a7GOTzjS1sIyZ35yYEhw/aq+g==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.0.tgz", + "integrity": "sha512-J/6xhutJPtFyIkmHdf4olOdHHKH5kOJNumojdWn+Elv5ICEYTVOa362Vw27TxsK0BLzb1le3EDlO3UbvFK1lnQ==", "cpu": [ "arm" ], @@ -1227,9 +1227,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.6.tgz", - "integrity": "sha512-5dWs/GENZufRph8PEfl0TrNH/R3DHJA+VxF/dqlfV2oUZfxRtN3qmFXg2DZj8DYM8d+Vd3dgZtuVmoBE/nb2YQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.0.tgz", + "integrity": "sha512-G4L/Nnx59d2LvDoV/P4S5lZ7oVLT6hodOHMacqjiq2LL2gts86KbkEBTYL53+fBt0odhf7XPIbIATGoYiJctxg==", "cpu": [ "arm" ], @@ -1244,9 +1244,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.6.tgz", - "integrity": "sha512-inQYLPVIUvYk+s7zpxQldqwrajUNs+t4TJjPGzsdPTvsT8gsQXVcW/sQFolHZ3YSWVk0nMMwU7TtRi0Ow+QMuw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.0.tgz", + "integrity": "sha512-zF16pvJDEUksGYRbfnXne7pdklAyj2XDmJeYKgP93oq0cUyX9Y8O7lUo1R+IY1GagP/YBvCEDCe7eEcAKpw//g==", "cpu": [ "arm64" ], @@ -1261,9 +1261,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.6.tgz", - "integrity": "sha512-sH+0MV1HmDC1q7+bQnlN9ywKBmHqkA2EdD/hHYrrz654ybc+vNC1FRNyzuYKicMQVtZZ4X2jz0osJYmsOtnj4g==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.0.tgz", + "integrity": "sha512-nIggWn8n+w12+R5lsV+PYYoUY8vuObxPAQqJI7WES+9v0qspYuAHoZMfYM0yLxlipasT5BY9ht5W8t5c/rSCkg==", "cpu": [ "arm64" ], @@ -1278,9 +1278,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.6.tgz", - "integrity": "sha512-mCECvRr6HGekdBc6dvHzUWubqEti4JyOqlNePrOzl4Wimk26iW5Zy9rld2mQbC9id1NYjfF8nHLFq5fr3CuOKQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.0.tgz", + "integrity": "sha512-WLOTiHPzszP0dxIMsHpFeDSOSB5I49Mj8K4+yKE3H1X/D0JRG5XZ5jrctJLMjnUc/LUzaKiwq6EHWNJ8pup8PA==", "cpu": [ "loong64" ], @@ -1295,9 +1295,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.6.tgz", - "integrity": "sha512-bb9rdoCPwM4DEsbOP3CJgFnvhSJ3XKCUYyUhxnlSs/XGT0DknpOYgLCPbK8VUzwl+cZGzFQm0KbBYRaSGZC2YQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.0.tgz", + "integrity": "sha512-35RK9aRhh7g7Pmeu5gQ01aCvrhXzj9LoBE6xtUcEH1ns+nyTaSzxSpADmBQINu4QiTDGvmQYwo1j5hlg1rRXFw==", "cpu": [ "loong64" ], @@ -1312,9 +1312,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.6.tgz", - "integrity": "sha512-aIJRoGCHev45JSu7JMzVrMzu1NX3nQrXx1Vb/6y4AblxW7JpCMpk4e8C6lHzvSElxi3UEcMdFJldsuWQjV1ddA==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.0.tgz", + "integrity": "sha512-RqTfnpoTjuWfPAmqK5p2edt2AMCDJWm6oTn7sKfmyx+6tRga0ip05SJ977o6udTcIiDeF/CJMUfkaN4gtWt6Aw==", "cpu": [ "ppc64" ], @@ -1329,9 +1329,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.6.tgz", - "integrity": "sha512-LvjnulezHjiaM5Ga9MPCDLTVddjvcrJhZc6vEz9fxeFRGPJsPAX9HuTuPTUQ3l8d+nYpZB/xWzUpuKJlLQm3VQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.0.tgz", + "integrity": "sha512-RQjZPigjxFvFZDijG6rjlSXxhRaTf9zLYVpGK+lVcga64oMqP1GbgVqCb46eHTTceSIgp89Ja0y5aPe3GFBYHg==", "cpu": [ "ppc64" ], @@ -1346,9 +1346,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.6.tgz", - "integrity": "sha512-YmhSBeYZwJ937s8tKpYAhms48N+tj7e+oFkbkVVxfMRdAiynK2AxqRapJXB4utyXxc/iYTkMfPRQ+7ZP0IO78w==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.0.tgz", + "integrity": "sha512-tjL58mgQcTRnDRpeky+Sth1MKQeYi2Ct6r3Luq9E5aTSjnM/i/JbozqZkKqp9thM5+oDSZxo+05f9kdQedfTGA==", "cpu": [ "riscv64" ], @@ -1363,9 +1363,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.6.tgz", - "integrity": "sha512-9+YukhzqTvJvzfDXJJzkURK9TDY01nofNH3pyHGuucGC4xPvBVtIfCAqJjkjnmBEGJYNXB8sBc2EDt7xDgGenw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.0.tgz", + "integrity": "sha512-ictoRyVaoZ8C67myHef+8hhCa6IplXJJ/xUCkVCxziV4zxMZh+jvhkhGsPBLqj1S72fnqD0QH5zL4lcaa4X7DQ==", "cpu": [ "riscv64" ], @@ -1380,9 +1380,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.6.tgz", - "integrity": "sha512-9G5AtEkpN/A89BILHHEL9aYRYTuFjwa8PUQqoEg8SDrwOZBKpwYqNlQCBNI5QPwGrf7qFlb6LNFilo8FCSA3Wg==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.0.tgz", + "integrity": "sha512-8aVvZ2hfr9RLFzBQkQNmYeHLDja8fLeQAmLt757zdKN/B+8L809KlLJKctQjS22wZotptV8G6o7fVPuAs8S1tQ==", "cpu": [ "s390x" ], @@ -1397,9 +1397,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.6.tgz", - "integrity": "sha512-Ezx2E5D6Siz805j41x91JiGbzuCm8i2U98U37SQ7ytY737+wsJq+P9nPL6UmqDww491zO7MTivYOTrtmTTeCSw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.0.tgz", + "integrity": "sha512-2dEF8GAcDKwshUfydD+GhosppNyBzhVUHkdFF3CXd3FpOkzj/RZzF5aoEtzklujklT5qFVRU2GK/cFeLaTNKVg==", "cpu": [ "x64" ], @@ -1414,9 +1414,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.6.tgz", - "integrity": "sha512-8EZ1Q3PB7yUjvR4MbFAnCBF1wF0XRe5QQqbtlJXP7ZX5C8iYlSQJugUhj9oS0gbUP4jp9qGhaIgFnV9Nvod86Q==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.0.tgz", + "integrity": "sha512-3XHTvoo2hKkh0MiOfS7SkhHjMaaWF7P4F3drocZZaho/Z8w7h/jpnuBHm91BOQLfvrKHs1yR+zir4E8RI3XzTQ==", "cpu": [ "x64" ], @@ -1431,9 +1431,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.6.tgz", - "integrity": "sha512-3JI27TALItfZ/qaxXKdbRXfV6WepUoUa3IN07FE+1xxoRdl3JIyrp5PiDF0vOHsPgEiOFkF8s3B3BRymQkdwGA==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.0.tgz", + "integrity": "sha512-HKtkzodL4a3KFWDXDoO61cRynAWTR3LnAE0oGtfG9lcNFpNuVYUFOByxDgNqEvic9rvaQbAj+SFwTEWDptRaIw==", "cpu": [ "x64" ], @@ -1445,9 +1445,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.6.tgz", - "integrity": "sha512-BNOGeNFNRZDT7Hfc7b9JbTZFXkpAXeXl/CZWtxdd5CNfBaor/0RkJjg+q+E2gwVIvGqqp/NclThKFGrhuScVhw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.0.tgz", + "integrity": "sha512-JBsxqiY16uyyCujtoC3s7zL+SrYyNElptD7K4V+KUoJ2V8/XG3hPWpheK+SuPwNh1cikJvIZmIdsrYXvD6PLJw==", "cpu": [ "arm64" ], @@ -1459,9 +1459,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.6.tgz", - "integrity": "sha512-D3eEyLSIim5WF5fHbwnx+ryk0delN7fJyBrDTO3wVCCNBZ/cA8aCGwLS3SiuK+vRI4E7dqikAlStGuR20dBMuw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.0.tgz", + "integrity": "sha512-S5Qrijh37qhnUYQ0ay8WN3LuWeKjow7wTUyCOPMZtnXiBXYXdsZETI6ZLfYnQAKovltckPLBFdNeD61P2rJc7g==", "cpu": [ "arm64" ], @@ -1473,9 +1473,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.6.tgz", - "integrity": "sha512-xwbs9g8zLbHzj1poG1uh27xtubsu27Gk2nuN6kvvGDybjiTfwMksQAnF+phlvI5oeXkJWWDc6kg1t3XpgUYvrg==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.0.tgz", + "integrity": "sha512-zbZ6iMmLFEsCtTdc2/y1F/cDFVNNCPF7Ae8PCT/wdPE//hGwYp6r+MyCLMFX+kOeIe09Pz6ASfnnfpSTN1FJQQ==", "cpu": [ "ia32" ], @@ -1487,9 +1487,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.6.tgz", - "integrity": "sha512-Tr6rhRNnFthvXr45zWVZQ5P8jZ5UwZnXNc7Qnu80cP/VVYtSioOPmvv7wP6UMgRfkBCJr2Pm60ua0YBDSJjgnw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.0.tgz", + "integrity": "sha512-L6Hw69oNpuYaOr1zCG30v0/DqKYCSRwodPGwIcVZ/6f8SOzq9rxtL/xNVI5MO4wjli+Ko0ErK9uHJL3mNPt8wQ==", "cpu": [ "x64" ], @@ -1501,9 +1501,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.6.tgz", - "integrity": "sha512-0RAxO1pS/6lY1mSoD2Apm+6aPyfUlm+OWKgncIdj/eVN9I+pDY3lKgD7xsUNFei1u7MZQETGOo6m8POXMDp53A==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.0.tgz", + "integrity": "sha512-nl+CTWIgOGszQmUz/kanfgrfFZ4qiwxKIfT/ryl3N3Ewk5tqizmYtNXiqb7Bu/fDtNgu1kyxdFiAUJNur3iysg==", "cpu": [ "x64" ], @@ -2221,9 +2221,9 @@ } }, "node_modules/eslint": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.11.0.tgz", - "integrity": "sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==", + "version": "10.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.12.0.tgz", + "integrity": "sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==", "dev": true, "license": "MIT", "workspaces": [ @@ -3565,9 +3565,9 @@ } }, "node_modules/rollup": { - "version": "4.63.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.6.tgz", - "integrity": "sha512-w4+GKyTBqshj84F1GF3s2zxAPFZPEmNkCbQtqilsYS7GsksmX6VoV7UggfZg8sUHCCOkBe7UtQp6fZwbH311FQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.0.tgz", + "integrity": "sha512-gjA/kFeDfffII8COoPsvLPvE16hWDTEc1+0QKQNKUkuV8iJjqsC4xXs8/FzuL0v9EV39kuxTbAgcFoTRyy2TGQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3582,31 +3582,31 @@ }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", - "@rollup/rollup-android-arm-eabi": "4.63.6", - "@rollup/rollup-android-arm64": "4.63.6", - "@rollup/rollup-darwin-arm64": "4.63.6", - "@rollup/rollup-darwin-x64": "4.63.6", - "@rollup/rollup-freebsd-arm64": "4.63.6", - "@rollup/rollup-freebsd-x64": "4.63.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.63.6", - "@rollup/rollup-linux-arm-musleabihf": "4.63.6", - "@rollup/rollup-linux-arm64-gnu": "4.63.6", - "@rollup/rollup-linux-arm64-musl": "4.63.6", - "@rollup/rollup-linux-loong64-gnu": "4.63.6", - "@rollup/rollup-linux-loong64-musl": "4.63.6", - "@rollup/rollup-linux-ppc64-gnu": "4.63.6", - "@rollup/rollup-linux-ppc64-musl": "4.63.6", - "@rollup/rollup-linux-riscv64-gnu": "4.63.6", - "@rollup/rollup-linux-riscv64-musl": "4.63.6", - "@rollup/rollup-linux-s390x-gnu": "4.63.6", - "@rollup/rollup-linux-x64-gnu": "4.63.6", - "@rollup/rollup-linux-x64-musl": "4.63.6", - "@rollup/rollup-openbsd-x64": "4.63.6", - "@rollup/rollup-openharmony-arm64": "4.63.6", - "@rollup/rollup-win32-arm64-msvc": "4.63.6", - "@rollup/rollup-win32-ia32-msvc": "4.63.6", - "@rollup/rollup-win32-x64-gnu": "4.63.6", - "@rollup/rollup-win32-x64-msvc": "4.63.6", + "@rollup/rollup-android-arm-eabi": "4.64.0", + "@rollup/rollup-android-arm64": "4.64.0", + "@rollup/rollup-darwin-arm64": "4.64.0", + "@rollup/rollup-darwin-x64": "4.64.0", + "@rollup/rollup-freebsd-arm64": "4.64.0", + "@rollup/rollup-freebsd-x64": "4.64.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.0", + "@rollup/rollup-linux-arm-musleabihf": "4.64.0", + "@rollup/rollup-linux-arm64-gnu": "4.64.0", + "@rollup/rollup-linux-arm64-musl": "4.64.0", + "@rollup/rollup-linux-loong64-gnu": "4.64.0", + "@rollup/rollup-linux-loong64-musl": "4.64.0", + "@rollup/rollup-linux-ppc64-gnu": "4.64.0", + "@rollup/rollup-linux-ppc64-musl": "4.64.0", + "@rollup/rollup-linux-riscv64-gnu": "4.64.0", + "@rollup/rollup-linux-riscv64-musl": "4.64.0", + "@rollup/rollup-linux-s390x-gnu": "4.64.0", + "@rollup/rollup-linux-x64-gnu": "4.64.0", + "@rollup/rollup-linux-x64-musl": "4.64.0", + "@rollup/rollup-openbsd-x64": "4.64.0", + "@rollup/rollup-openharmony-arm64": "4.64.0", + "@rollup/rollup-win32-arm64-msvc": "4.64.0", + "@rollup/rollup-win32-ia32-msvc": "4.64.0", + "@rollup/rollup-win32-x64-gnu": "4.64.0", + "@rollup/rollup-win32-x64-msvc": "4.64.0", "fsevents": "~2.3.2" } }, diff --git a/package.json b/package.json index f2deea0c..eb25bfcb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@next2d/player", - "version": "3.12.1", + "version": "3.13.0", "description": "Experience the fast and beautiful anti-aliased rendering of WebGL/WebGPU. You can create rich, interactive graphics, cross-platform applications and games without worrying about browser or device compatibility.", "author": "Toshiyuki Ienaga (https://github.com/ienaga/)", "license": "MIT", @@ -61,11 +61,11 @@ "@typescript-eslint/eslint-plugin": "^8.71.0", "@typescript-eslint/parser": "^8.71.0", "@webgpu/types": "^0.1.74", - "eslint": "^10.11.0", + "eslint": "^10.12.0", "eslint-plugin-unused-imports": "^4.4.1", "globals": "^17.13.0", "jsdom": "^30.1.1", - "rollup": "^4.63.6", + "rollup": "^4.64.0", "tslib": "^2.8.1", "typescript": "^6.0.3", "vite": "^8.3.2", diff --git a/packages/display/src/Shape/usecase/ShapeBoundsPrecision.test.ts b/packages/display/src/Shape/usecase/ShapeBoundsPrecision.test.ts index 7ab85e16..e69ec4e3 100644 --- a/packages/display/src/Shape/usecase/ShapeBoundsPrecision.test.ts +++ b/packages/display/src/Shape/usecase/ShapeBoundsPrecision.test.ts @@ -37,9 +37,13 @@ describe("Shape bounds precision before culling", () => const expected = calcBounds(...rect as [number, number, number, number], matrix, new Float32Array(4)); const width = Math.ceil(Math.abs(expected[2] - expected[0])); const height = Math.ceil(Math.abs(expected[3] - expected[1])); + // 片軸のスケールが0またはNaNの場合も面積0としてスキップされる + const xScale = Math.sqrt(matrix[0] * matrix[0] + matrix[1] * matrix[1]); + const yScale = Math.sqrt(matrix[2] * matrix[2] + matrix[3] * matrix[3]); const culled = width === 0 || height === 0 || width === Infinity || height === Infinity || expected[0] + width < 0 || expected[1] + height < 0 - || expected[0] > viewport || expected[1] > viewport; + || expected[0] > viewport || expected[1] > viewport + || !xScale || !yScale; renderQueue.offset = 0; execute(shape, matrix, color, viewport, viewport); expect(renderQueue.buffer[0]).toBe(culled ? 0 : 1); diff --git a/packages/display/src/Shape/usecase/ShapeGenerateRenderQueueUseCase.test.ts b/packages/display/src/Shape/usecase/ShapeGenerateRenderQueueUseCase.test.ts index c2843b30..01754a2f 100644 --- a/packages/display/src/Shape/usecase/ShapeGenerateRenderQueueUseCase.test.ts +++ b/packages/display/src/Shape/usecase/ShapeGenerateRenderQueueUseCase.test.ts @@ -1,5 +1,6 @@ import { execute } from "./ShapeGenerateRenderQueueUseCase"; import { Shape } from "../../Shape"; +import { renderQueue } from "@next2d/render-queue"; import { describe, expect, it } from "vitest"; describe("ShapeGenerateRenderQueueUseCase.js test", () => @@ -143,4 +144,24 @@ describe("ShapeGenerateRenderQueueUseCase.js test", () => expect(typeof 800).toBe("number"); expect(typeof 600).toBe("number"); }); + it("execute test case - skips rotated shape whose one axis scale is zero", () => + { + const enqueue = (matrix: Float32Array): number => + { + const shape = new Shape(); + shape.graphics.beginFill(0xff0000).drawRect(0, 0, 100, 100).endFill(); + + const offset = renderQueue.offset; + execute(shape, matrix, new Float32Array([1, 1, 1, 1, 0, 0, 0, 0]), 800, 600); + return renderQueue.offset - offset; + }; + + // 45度回転 + scaleY=0: スクリーンboundsは0にならないが面積は0 + const r = Math.SQRT1_2; + expect(enqueue(new Float32Array([r, r, 0, 0, 100, 100]))).toBe(1); + expect(renderQueue.buffer[renderQueue.offset - 1]).toBe(0); + + // スケールが0でなければ描画キューに積まれる + expect(enqueue(new Float32Array([r, r, -r, r, 100, 100]))).toBeGreaterThan(1); + }); }); diff --git a/packages/display/src/Shape/usecase/ShapeGenerateRenderQueueUseCase.ts b/packages/display/src/Shape/usecase/ShapeGenerateRenderQueueUseCase.ts index 5a18a97b..3f3dbf89 100644 --- a/packages/display/src/Shape/usecase/ShapeGenerateRenderQueueUseCase.ts +++ b/packages/display/src/Shape/usecase/ShapeGenerateRenderQueueUseCase.ts @@ -214,6 +214,19 @@ export const execute = ( ); } + // 回転・スキュー時はスクリーンboundsが0にならなくても、片軸のスケールが0なら + // 面積0で描画結果がなく、Worker側のノードサイズも0になるためスキップする + if (!renderXScale || !renderYScale) { + if (tColorTransform !== color_transform) { + ColorTransform.release(tColorTransform); + } + if (tMatrix !== matrix) { + Matrix.release(tMatrix); + } + renderQueue.push1(0); + return; + } + const xScaleRounded = Math.round(renderXScale * 100) / 100; const yScaleRounded = Math.round(renderYScale * 100) / 100; diff --git a/packages/display/src/TextField/usecase/TextFieldGenerateRenderQueueUseCase.test.ts b/packages/display/src/TextField/usecase/TextFieldGenerateRenderQueueUseCase.test.ts index 72a30fb4..4c8e4bac 100644 --- a/packages/display/src/TextField/usecase/TextFieldGenerateRenderQueueUseCase.test.ts +++ b/packages/display/src/TextField/usecase/TextFieldGenerateRenderQueueUseCase.test.ts @@ -1,5 +1,6 @@ import { execute } from "./TextFieldGenerateRenderQueueUseCase"; import { TextField } from "@next2d/text"; +import { renderQueue } from "@next2d/render-queue"; import { describe, expect, it } from "vitest"; describe("TextFieldGenerateRenderQueueUseCase.js test", () => @@ -214,4 +215,17 @@ describe("TextFieldGenerateRenderQueueUseCase.js test", () => execute(textField, combinedMatrix, colorTransform, 800, 600); }).not.toThrow(); }); + it("execute test case - skips rotated TextField whose one axis scale is zero", () => + { + const textField = new TextField(); + textField.text = "Hello World"; + + // 45度回転 + scaleX=0: スクリーンboundsは0にならないが面積は0 + const r = Math.SQRT1_2; + const offset = renderQueue.offset; + execute(textField, new Float32Array([0, 0, -r, r, 100, 100]), new Float32Array([1, 1, 1, 1, 0, 0, 0, 0]), 800, 600); + + expect(renderQueue.offset - offset).toBe(1); + expect(renderQueue.buffer[renderQueue.offset - 1]).toBe(0); + }); }); diff --git a/packages/display/src/TextField/usecase/TextFieldGenerateRenderQueueUseCase.ts b/packages/display/src/TextField/usecase/TextFieldGenerateRenderQueueUseCase.ts index f8815d68..53d44867 100644 --- a/packages/display/src/TextField/usecase/TextFieldGenerateRenderQueueUseCase.ts +++ b/packages/display/src/TextField/usecase/TextFieldGenerateRenderQueueUseCase.ts @@ -206,6 +206,19 @@ export const execute = ( ); } + // 回転・スキュー時はスクリーンboundsが0にならなくても、片軸のスケールが0なら + // 面積0で描画結果がなく、Worker側のノードサイズも0になるためスキップする + if (!renderXScale || !renderYScale) { + if (tColorTransform !== color_transform) { + ColorTransform.release(tColorTransform); + } + if (tMatrix !== matrix) { + Matrix.release(tMatrix); + } + renderQueue.push1(0); + return; + } + const xScaleRounded = Math.round(renderXScale * 100) / 100; const yScaleRounded = Math.round(renderYScale * 100) / 100; diff --git a/packages/renderer/src/Command/service/CommandInitializeContextService.ts b/packages/renderer/src/Command/service/CommandInitializeContextService.ts index 5db254a7..0e0a36a9 100644 --- a/packages/renderer/src/Command/service/CommandInitializeContextService.ts +++ b/packages/renderer/src/Command/service/CommandInitializeContextService.ts @@ -44,6 +44,16 @@ export const execute = async ( if (!device) { throw new Error("WebGPU device not available"); } + void device.lost.then((info) => { + if (info.reason !== "destroyed") { + globalThis.postMessage({ "message": "rendererFailure", "reason": "device-lost" }); + } + }); + device.addEventListener("uncapturederror", (event) => { + if (event.error instanceof GPUOutOfMemoryError) { + globalThis.postMessage({ "message": "rendererFailure", "reason": "gpu-memory" }); + } + }); const context = canvas.getContext("webgpu"); if (!context) { @@ -68,4 +78,4 @@ export const execute = async ( // Set CanvasToWebGLContext $setContext(new WebGLContext(gl, $samples, device_pixel_ratio)); } -}; \ No newline at end of file +}; diff --git a/packages/renderer/src/TextField/service/TextFieldGenerateFontStyleService.test.ts b/packages/renderer/src/TextField/service/TextFieldGenerateFontStyleService.test.ts index 43ef50a3..0ae22cf7 100644 --- a/packages/renderer/src/TextField/service/TextFieldGenerateFontStyleService.test.ts +++ b/packages/renderer/src/TextField/service/TextFieldGenerateFontStyleService.test.ts @@ -12,6 +12,6 @@ describe("TextFieldGenerateFontStyleService.js test", () => size: 12, font: "Arial", } as ITextFormat; - expect(execute(mockTextFormat)).toBe("italic bold 12px 'Arial','sans-serif'"); + expect(execute(mockTextFormat)).toBe("italic bold 12px 'Arial',sans-serif"); }); -}); \ No newline at end of file +}); diff --git a/packages/renderer/src/TextField/service/TextFieldGenerateFontStyleService.ts b/packages/renderer/src/TextField/service/TextFieldGenerateFontStyleService.ts index aef2c96e..b01bf059 100644 --- a/packages/renderer/src/TextField/service/TextFieldGenerateFontStyleService.ts +++ b/packages/renderer/src/TextField/service/TextFieldGenerateFontStyleService.ts @@ -21,5 +21,5 @@ export const execute = (text_format: ITextFormat): string => fontStyle += "bold "; } - return `${fontStyle}${text_format.size}px '${text_format.font}','sans-serif'`; -}; \ No newline at end of file + return `${fontStyle}${text_format.size}px '${text_format.font}',sans-serif`; +}; diff --git a/packages/webgpu/src/AtlasManager.ts b/packages/webgpu/src/AtlasManager.ts index 520e6ad9..6e0a6d37 100644 --- a/packages/webgpu/src/AtlasManager.ts +++ b/packages/webgpu/src/AtlasManager.ts @@ -1,5 +1,6 @@ import type { IAttachmentObject } from "./interface/IAttachmentObject"; import type { TexturePacker } from "@next2d/texture-packer"; +import { resetAtlasScratch } from "./AtlasScratch"; /** * @description テクスチャアトラス境界の初期最大値 @@ -212,6 +213,7 @@ export const $getCurrentAtlasIndex = (): number => */ export const $resetAtlas = (): void => { + resetAtlasScratch(); $rootNodes.length = 0; $setActiveAtlasIndex(0); diff --git a/packages/webgpu/src/AtlasScratch.test.ts b/packages/webgpu/src/AtlasScratch.test.ts new file mode 100644 index 00000000..b1be1e36 --- /dev/null +++ b/packages/webgpu/src/AtlasScratch.test.ts @@ -0,0 +1,36 @@ +import { beforeEach, expect, it, vi } from "vitest"; +import { atlasScratch, needsAtlasRestore, resetAtlasScratch, restoreAtlas } from "./AtlasScratch"; +import type { IAttachmentObject } from "./interface/IAttachmentObject"; + +beforeEach(() => { + resetAtlasScratch(); + vi.stubGlobal("GPUTextureUsage", { RENDER_ATTACHMENT: 16 }); +}); + +it("shares multisample storage while restoring independent resolved pages", () => { + const createTexture = vi.fn(() => ({ createView: () => ({}), destroy: vi.fn() })); + const device = { createTexture, createShaderModule: vi.fn(), createBindGroup: vi.fn(), + createRenderPipeline: vi.fn(() => ({ getBindGroupLayout: vi.fn() })) } as unknown as GPUDevice; + const scratch = atlasScratch(device, 4096, 4096); + expect(atlasScratch(device, 4096, 4096)).toBe(scratch); + expect(createTexture).toHaveBeenCalledTimes(2); + const attachment = (): IAttachmentObject => ({ width: 4096, height: 4096, + texture: { resource: {}, view: {} }, msaaTexture: { resource: scratch.color, view: {} } + } as unknown as IAttachmentObject); + const first = attachment(), second = attachment(); + const pass = { setPipeline: vi.fn(), setBindGroup: vi.fn(), draw: vi.fn(), end: vi.fn() }; + const beginRenderPass = vi.fn(() => pass); + const encoder = { beginRenderPass } as unknown as GPUCommandEncoder; + expect(needsAtlasRestore(device, first)).toBe(true); + restoreAtlas(device, encoder, first); + expect(needsAtlasRestore(device, first)).toBe(false); + expect(needsAtlasRestore(device, second)).toBe(true); + restoreAtlas(device, encoder, second); + expect(needsAtlasRestore(device, first)).toBe(true); + expect(beginRenderPass).toHaveBeenCalledTimes(2); + expect(beginRenderPass.mock.calls[0][0].colorAttachments[0].resolveTarget).toBeUndefined(); + expect(pass.draw).toHaveBeenCalledWith(3); + resetAtlasScratch(); + expect(atlasScratch(device, 4096, 4096)).not.toBe(scratch); + expect(createTexture).toHaveBeenCalledTimes(4); +}); diff --git a/packages/webgpu/src/AtlasScratch.ts b/packages/webgpu/src/AtlasScratch.ts new file mode 100644 index 00000000..e2174192 --- /dev/null +++ b/packages/webgpu/src/AtlasScratch.ts @@ -0,0 +1,64 @@ +import type { IAttachmentObject } from "./interface/IAttachmentObject"; +import { $samples } from "./WebGPUUtil"; + +interface IAtlasScratch { + color: GPUTexture; + stencil: GPUTexture; + owner: GPUTexture | null; + pipeline: GPURenderPipeline; +} +let pools = new WeakMap>(); + +/** Resolved cache pages stay resident; multisample working storage is shared. */ +export function atlasScratch(device: GPUDevice, width: number, height: number): IAtlasScratch { + let pool = pools.get(device); + if (!pool) { pool = new Map(); pools.set(device, pool) } + const key = `${width}:${height}`; + const cached = pool.get(key); + if (cached) { return cached } + const shader = device.createShaderModule({ "code": ` + @group(0) @binding(0) var image: texture_2d; + @vertex fn vertex(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f { + let points = array(vec2f(-1,-1),vec2f(3,-1),vec2f(-1,3)); + return vec4f(points[i],0,1); + } + @fragment fn fragment(@builtin(position) p: vec4f) -> @location(0) vec4f { + return textureLoad(image,vec2i(p.xy),0); + }` }); + const value: IAtlasScratch = { + "color": device.createTexture({ "size": { width, height }, "format": "rgba8unorm", + "sampleCount": $samples, "usage": GPUTextureUsage.RENDER_ATTACHMENT }), + "stencil": device.createTexture({ "size": { width, height }, "format": "stencil8", + "sampleCount": $samples, "usage": GPUTextureUsage.RENDER_ATTACHMENT }), + "owner": null, + "pipeline": device.createRenderPipeline({ "layout": "auto", + "vertex": { "module": shader, "entryPoint": "vertex" }, + "fragment": { "module": shader, "entryPoint": "fragment", "targets": [{ "format": "rgba8unorm" }] }, + "multisample": { "count": $samples } }) + }; + pool.set(key, value); + return value; +} + +export function needsAtlasRestore(device: GPUDevice, attachment: IAttachmentObject): boolean { + const scratch = pools.get(device)?.get(`${attachment.width}:${attachment.height}`); + return !!scratch && attachment.msaaTexture?.resource === scratch.color + && scratch.owner !== attachment.texture?.resource; +} + +export function restoreAtlas(device: GPUDevice, encoder: GPUCommandEncoder, attachment: IAttachmentObject): void { + const scratch = atlasScratch(device, attachment.width, attachment.height); + if (!attachment.texture || !attachment.msaaTexture) { return } + const pass = encoder.beginRenderPass({ "colorAttachments": [{ + "view": attachment.msaaTexture.view, "loadOp": "clear", "storeOp": "store", + "clearValue": { "r": 0, "g": 0, "b": 0, "a": 0 } + }] }); + pass.setPipeline(scratch.pipeline); + pass.setBindGroup(0, device.createBindGroup({ "layout": scratch.pipeline.getBindGroupLayout(0), + "entries": [{ "binding": 0, "resource": attachment.texture.view }] })); + pass.draw(3); pass.end(); + scratch.owner = attachment.texture.resource; +} + +/** AtlasManager destroys the shared resources along with the page attachments. */ +export function resetAtlasScratch(): void { pools = new WeakMap() } diff --git a/packages/webgpu/src/Context.test.ts b/packages/webgpu/src/Context.test.ts index d36e80a8..b6aa11dd 100644 --- a/packages/webgpu/src/Context.test.ts +++ b/packages/webgpu/src/Context.test.ts @@ -56,7 +56,7 @@ const mockDevice = { createTexture: vi.fn().mockReturnValue(mockTexture), createBuffer: vi.fn().mockReturnValue(mockBuffer), createCommandEncoder: vi.fn().mockReturnValue(mockCommandEncoder), - createRenderPipeline: vi.fn().mockReturnValue({}), + createRenderPipeline: vi.fn().mockReturnValue({ getBindGroupLayout: vi.fn().mockReturnValue({}) }), createBindGroup: vi.fn().mockReturnValue({}), createBindGroupLayout: vi.fn().mockReturnValue({}), createPipelineLayout: vi.fn().mockReturnValue({}), diff --git a/packages/webgpu/src/Context.ts b/packages/webgpu/src/Context.ts index 5b6410f7..fd3dc43c 100644 --- a/packages/webgpu/src/Context.ts +++ b/packages/webgpu/src/Context.ts @@ -1,4 +1,5 @@ import type { IAttachmentObject } from "./interface/IAttachmentObject"; +import { needsAtlasRestore, restoreAtlas } from "./AtlasScratch"; import type { IBlendMode } from "./interface/IBlendMode"; import type { IBounds } from "./interface/IBounds"; import type { Node } from "@next2d/texture-packer"; @@ -944,6 +945,11 @@ export class Context const textureView = this.getCurrentTextureView(); const attachment = $getAtlasAttachmentObject(); + // アトラスへ描画する場合は共有MSAAスクラッチを対象ページの内容に揃える + if (this.currentRenderTarget && attachment) { + this.prepareAtlasScratch(attachment); + } + // MSAAテクスチャを使用するかどうか const useMsaa = attachment?.msaa && attachment?.msaaTexture?.view; const colorView = useMsaa ? attachment!.msaaTexture!.view : textureView; @@ -1734,6 +1740,27 @@ export class Context $gridDataMap.set($fillBufferIndex, grid_data); } + /** + * @description 共有MSAAスクラッチが別ページの内容を保持している場合、対象アトラスの解決済みテクスチャから復元 + * Restore the shared MSAA scratch from the target atlas when it holds another page + * + * @param {IAttachmentObject} attachment - 描画対象のアトラス / Target atlas attachment + * @return {void} + */ + private prepareAtlasScratch (attachment: IAttachmentObject): void + { + if (!needsAtlasRestore(this.device, attachment)) { + return; + } + if (this.renderPassEncoder) { + this.renderPassEncoder.end(); + this.renderPassEncoder = null; + } + this.nodeRenderPassAtlasIndex = -1; + this.ensureCommandEncoder(); + restoreAtlas(this.device, this.commandEncoder!, attachment); + } + /** * @description 指定のノード範囲で描画を開始(アトラステクスチャへの描画) * Begin rendering for the specified node region (drawing to atlas texture) @@ -1755,6 +1782,7 @@ export class Context // アトラステクスチャの該当箇所をレンダーターゲットに設定 const attachment = $getAtlasAttachmentObjectByIndex(node.index) || $getAtlasAttachmentObject(); if (attachment && attachment.texture) { + this.prepareAtlasScratch(attachment); // 同一アトラスへの連続描画ならレンダーパスを再利用 if (this.renderPassEncoder && this.nodeRenderPassAtlasIndex === node.index) { @@ -2215,6 +2243,7 @@ export class Context height: number ): void { + this.prepareAtlasScratch(attachment); // 一時テクスチャをプールから取得 const tempTexture = $acquireRenderTexture(this.device, width, height); @@ -2377,6 +2406,7 @@ export class Context flip_y: boolean ): void { + this.prepareAtlasScratch(attachment); // 一時テクスチャをプールから取得 const tempTexture = $acquireRenderTexture(this.device, width, height); @@ -2805,6 +2835,7 @@ export class Context if (!atlas || !atlas.texture || !temp_attachment.texture) { return; } + this.prepareAtlasScratch(atlas); // アトラスはrgba8unormフォーマット(FrameBufferManagerCreateAttachmentUseCase参照) // atlas_*テクスチャはcopyExternalImageToTextureとの互換性のためrgba8unormで作成される @@ -3282,23 +3313,33 @@ export class Context */ createNode (width: number, height: number): Node { - // WebGPU node creation implementation using texture-packer - const index = $getActiveAtlasIndex(); - - if (!$rootNodes[index]) { - const maxSize = WebGPUUtil.getRenderMaxSize(); - $rootNodes[index] = new TexturePacker(index, maxSize, maxSize); - } - - const rootNode = $rootNodes[index]; - const node = rootNode.insert(width, height); - - if (!node) { - // アトラスがいっぱいの場合、新しいアトラスインデックスに切り替えて再試行 - $setActiveAtlasIndex(index + 1); - return this.createNode(width, height); - } - + const maxSize = WebGPUUtil.getRenderMaxSize(); + if (!Number.isFinite(width) || !Number.isFinite(height) + || width > maxSize || height > maxSize + ) { + throw new RangeError("Invalid texture atlas allocation size"); + } + + // サイズ0の要素でも描画処理を止めないよう最小1pxで確保 + width = Math.max(1, width); + height = Math.max(1, height); + + const count = $rootNodes.length; + const start = count ? $getActiveAtlasIndex() % count : 0; + // Search all existing pages before growing; freed earlier pages remain reusable. + for (let offset = 0; offset < count; offset++) { + const index = (start + offset) % count; + const root = $rootNodes[index]; + const reused = root?.insert(width, height); + if (!reused) { continue } + $setActiveAtlasIndex(index); + this._nodeRoots.set(reused, root); + return reused; + } + const rootNode = new TexturePacker(count, maxSize, maxSize); + $rootNodes[count] = rootNode; + $setActiveAtlasIndex(count); + const node = rootNode.insert(width, height)!; this._nodeRoots.set(node, rootNode); return node; } diff --git a/packages/webgpu/src/ContextNodeReuse.test.ts b/packages/webgpu/src/ContextNodeReuse.test.ts index d4fc0450..1b1ff644 100644 --- a/packages/webgpu/src/ContextNodeReuse.test.ts +++ b/packages/webgpu/src/ContextNodeReuse.test.ts @@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { Context } from "./Context"; import { Node, TexturePacker } from "@next2d/texture-packer"; import { $rootNodes, $setActiveAtlasIndex, $getActiveAtlasIndex } from "./AtlasManager"; +import { WebGPUUtil } from "./WebGPUUtil"; const makeContext = (): Context => { const context = Object.create(Context.prototype) as Context; Reflect.set(context, "_nodeRoots", new WeakMap()); @@ -13,8 +14,32 @@ beforeEach(() => { $rootNodes[1] = new TexturePacker(1, 256, 256); $setActiveAtlasIndex(0); vi.clearAllMocks(); + WebGPUUtil.setRenderMaxSize(256); }); describe("cached atlas node reuse", () => { + it("reuses freed earlier pages over repeated full-page allocations", () => { + const context = makeContext(); + let first = context.createNode(256, 256); + $setActiveAtlasIndex(1); + const second = context.createNode(256, 256); + for (let i = 0; i < 100; i++) { + context.removeNode(first); + $setActiveAtlasIndex(1); + first = context.createNode(256, 256); + expect(first.index).toBe(0); + expect($rootNodes.length).toBe(2); + expect(second.used).toBe(true); + } + }); + it("rejects oversized nodes without allocating endless pages", () => { + expect(() => makeContext().createNode(257, 256)).toThrow(RangeError); + expect($rootNodes.length).toBe(2); + }); + it("allocates zero-sized nodes as 1px instead of throwing", () => { + const node = makeContext().createNode(0, 0); + expect(node.w).toBeGreaterThanOrEqual(1); + expect(node.h).toBeGreaterThanOrEqual(1); + }); it("selects an owned node's page while keeping both allocations", () => { const context = makeContext(); const first = context.createNode(17, 19); diff --git a/packages/webgpu/src/FrameBufferManager.test.ts b/packages/webgpu/src/FrameBufferManager.test.ts index f5cccf76..96470f30 100644 --- a/packages/webgpu/src/FrameBufferManager.test.ts +++ b/packages/webgpu/src/FrameBufferManager.test.ts @@ -335,6 +335,22 @@ describe("FrameBufferManager", () => describe("destroyAttachment", () => { + it("releases main multisample textures on resize without destroying shared atlas scratch", () => + { + const manager = new FrameBufferManager(createMockDevice(), "bgra8unorm"); + for (const name of ["main", "atlas_0"]) { + const attachment = manager.createAttachment(name, 100, 100, true, true); + const colorDestroy = vi.fn(), stencilDestroy = vi.fn(); + attachment.msaaTexture = { ...attachment.texture!, + resource: { destroy: colorDestroy } as unknown as GPUTexture }; + attachment.msaaStencil = { ...attachment.stencil!, + resource: { destroy: stencilDestroy } as unknown as GPUTexture }; + manager.destroyAttachment(name); + expect(colorDestroy).toHaveBeenCalledTimes(name === "main" ? 1 : 0); + expect(stencilDestroy).toHaveBeenCalledTimes(name === "main" ? 1 : 0); + } + }); + it("should destroy attachment and remove from map", () => { const device = createMockDevice(); diff --git a/packages/webgpu/src/FrameBufferManager.ts b/packages/webgpu/src/FrameBufferManager.ts index 523414ad..8d75f558 100644 --- a/packages/webgpu/src/FrameBufferManager.ts +++ b/packages/webgpu/src/FrameBufferManager.ts @@ -176,6 +176,11 @@ export class FrameBufferManager if (attachment.stencil) { attachment.stencil.resource.destroy(); } + // Atlas scratch is shared and released by AtlasManager as a group. + if (name !== "atlas" && !name.startsWith("atlas_")) { + attachment.msaaTexture?.resource.destroy(); + attachment.msaaStencil?.resource.destroy(); + } this.attachments.delete(name); } } diff --git a/packages/webgpu/src/FrameBufferManager/usecase/FrameBufferManagerCreateAttachmentUseCase.test.ts b/packages/webgpu/src/FrameBufferManager/usecase/FrameBufferManagerCreateAttachmentUseCase.test.ts index d4afe4fc..1407325d 100644 --- a/packages/webgpu/src/FrameBufferManager/usecase/FrameBufferManagerCreateAttachmentUseCase.test.ts +++ b/packages/webgpu/src/FrameBufferManager/usecase/FrameBufferManagerCreateAttachmentUseCase.test.ts @@ -26,6 +26,8 @@ describe("FrameBufferManagerCreateAttachmentUseCase", () => }; return { + "createShaderModule": vi.fn(() => ({})), + "createRenderPipeline": vi.fn(() => ({ getBindGroupLayout: vi.fn() })), "createTexture": vi.fn(() => mockTexture), "_mockTexture": mockTexture } as unknown as GPUDevice; diff --git a/packages/webgpu/src/FrameBufferManager/usecase/FrameBufferManagerCreateAttachmentUseCase.ts b/packages/webgpu/src/FrameBufferManager/usecase/FrameBufferManagerCreateAttachmentUseCase.ts index 6e5f6341..4c2df7c6 100644 --- a/packages/webgpu/src/FrameBufferManager/usecase/FrameBufferManagerCreateAttachmentUseCase.ts +++ b/packages/webgpu/src/FrameBufferManager/usecase/FrameBufferManagerCreateAttachmentUseCase.ts @@ -2,6 +2,7 @@ import type { IAttachmentObject } from "../../interface/IAttachmentObject"; import type { ITextureObject } from "../../interface/ITextureObject"; import type { IStencilBufferObject } from "../../interface/IStencilBufferObject"; import { $samples } from "../../WebGPUUtil"; +import { atlasScratch } from "../../AtlasScratch"; /** * @description アタッチメントオブジェクトを作成 @@ -69,7 +70,7 @@ export const execute = ( // MSAAテクスチャを作成(sampleCount > 1の場合) let msaaTexture: ITextureObject | null = null; if (useMsaa) { - const msaaGpuTexture = device.createTexture({ + const msaaGpuTexture = isAtlas ? atlasScratch(device, width, height).color : device.createTexture({ "size": { width, height }, "format": textureFormat, "sampleCount": sampleCount, @@ -115,7 +116,7 @@ export const execute = ( // MSAAステンシルテクスチャを作成(sampleCount > 1の場合) if (useMsaa) { - const msaaStencilTexture = device.createTexture({ + const msaaStencilTexture = isAtlas ? atlasScratch(device, width, height).stencil : device.createTexture({ "size": { width, height }, "format": "stencil8", "sampleCount": sampleCount,