From bafc3d5e2679e9c58d583f672536201eac1951c6 Mon Sep 17 00:00:00 2001 From: minggo Date: Thu, 21 Sep 2023 14:06:52 +0800 Subject: [PATCH] merge v3.8.2 --- .github/workflows/native-bindings.yml | 1 + .../workflows/native-compile-platforms.yml | 183 +- .github/workflows/native-compile-webgpu.yml | 66 + .github/workflows/native-linter-android.yml | 1 + cc.config.json | 6 + cocos/2d/assembler/label/letter-font.ts | 14 +- cocos/2d/assembler/label/ttfUtils.ts | 48 +- cocos/2d/components/deprecated.ts | 32 +- cocos/2d/components/label-outline.ts | 74 +- cocos/2d/components/label-shadow.ts | 85 +- cocos/2d/components/label.ts | 157 +- cocos/2d/components/mask.ts | 2 - cocos/2d/renderer/batcher-2d.ts | 4 +- cocos/3d/assets/mesh.ts | 160 +- cocos/3d/framework/mesh-renderer.ts | 1 + cocos/3d/misc/mesh-codec.ts | 17 +- cocos/core/geometry/geometry-native-ext.ts | 16 +- cocos/core/math/vec2.ts | 13 +- cocos/core/settings.ts | 24 +- cocos/gfx/base/framebuffer.ts | 10 + cocos/gfx/webgl/webgl-framebuffer.ts | 2 + cocos/gfx/webgl2/webgl2-framebuffer.ts | 2 + cocos/gfx/webgpu/webgpu-define.ts | 512 +- cocos/input/input.ts | 127 +- cocos/input/types/event-enum.ts | 2 +- cocos/input/types/touch.ts | 19 +- cocos/misc/camera-component.ts | 6 +- cocos/physics-2d/box2d-wasm/instantiated.ts | 3 +- .../physics-2d/box2d-wasm/joints/joint-2d.ts | 8 +- .../box2d-wasm/joints/mouse-joint.ts | 2 +- cocos/physics-2d/box2d-wasm/rigid-body.ts | 29 +- .../physics-2d/box2d-wasm/shapes/shape-2d.ts | 6 +- cocos/physics/utils/util.ts | 22 +- cocos/render-scene/core/pass.ts | 19 +- cocos/render-scene/scene/submodel.ts | 17 +- cocos/rendering/custom/builtin-pipelines.ts | 2 +- cocos/rendering/custom/compiler.ts | 72 +- cocos/rendering/custom/define.ts | 10 +- cocos/rendering/custom/executor.ts | 599 +- cocos/rendering/custom/pipeline-define.ts | 232 +- cocos/rendering/custom/render-graph.ts | 48 +- cocos/rendering/custom/web-pipeline.ts | 72 +- cocos/rendering/custom/web-program-library.ts | 401 +- cocos/rendering/pipeline-ubo.ts | 41 +- .../post-process/passes/forward-pass.ts | 5 +- .../post-process/post-process-builder.ts | 4 +- .../rendering/render-additive-light-queue.ts | 8 +- cocos/scene-graph/node-event.ts | 12 +- cocos/scene-graph/node.jsb.ts | 4 +- cocos/scene-graph/node.ts | 2 +- cocos/spine/lib/spine-define.ts | 20 +- cocos/ui/editbox/edit-box-impl-base.ts | 33 +- cocos/ui/editbox/edit-box-impl.ts | 25 +- cocos/ui/editbox/edit-box.ts | 6 + cocos/ui/layout.ts | 4 +- cocos/webgpu/instantiated.ts | 14 +- .../chunks/common/color/tone-mapping.chunk | 2 + .../chunks/common/math/coordinates.chunk | 2 +- .../legacy/shading-cluster-additive.chunk | 2 +- .../data-structures/fs-input.chunk | 2 +- .../main-functions/misc/sky-fs.chunk | 15 +- .../render-to-scene/pipeline/forward-fs.chunk | 23 +- .../default_prefab/2d/Camera.prefab.meta | 2 +- .../default_prefab/2d/ui/Canvas.prefab.meta | 2 +- .../default_prefab/3d/Capsule.prefab.meta | 2 +- .../assets/default_prefab/3d/Cone.prefab.meta | 2 +- .../assets/default_prefab/3d/Cube.prefab.meta | 2 +- .../default_prefab/3d/Cylinder.prefab.meta | 2 +- .../default_prefab/3d/Plane.prefab.meta | 2 +- .../assets/default_prefab/3d/Quad.prefab.meta | 2 +- .../default_prefab/3d/Sphere.prefab.meta | 2 +- .../default_prefab/3d/Torus.prefab.meta | 2 +- .../assets/default_prefab/Camera.prefab.meta | 2 +- .../assets/default_prefab/Terrain.prefab.meta | 2 +- .../effects/Particle System.prefab.meta | 2 +- .../light/Directional Light.prefab.meta | 2 +- .../light/Light Probe Group.prefab.meta | 2 +- .../light/Point Light.prefab.meta | 2 +- .../Ranged Directional Light.prefab.meta | 2 +- .../light/Reflection Probe.prefab.meta | 2 +- .../light/Sphere Light.prefab.meta | 2 +- .../light/Spot Light.prefab.meta | 2 +- .../default_prefab/ui/Button.prefab.meta | 2 +- .../default_prefab/ui/Canvas.prefab.meta | 2 +- .../default_prefab/ui/EditBox.prefab.meta | 2 +- .../default_prefab/ui/Graphics.prefab.meta | 2 +- .../default_prefab/ui/Label.prefab.meta | 2 +- .../default_prefab/ui/Layout.prefab.meta | 2 +- .../assets/default_prefab/ui/Mask.prefab.meta | 2 +- .../ui/ParticleSystem2D.prefab.meta | 2 +- .../default_prefab/ui/ProgressBar.prefab.meta | 2 +- .../default_prefab/ui/RichText.prefab.meta | 2 +- .../default_prefab/ui/ScrollView.prefab.meta | 2 +- .../default_prefab/ui/Slider.prefab.meta | 2 +- .../default_prefab/ui/Sprite.prefab.meta | 2 +- .../ui/SpriteRenderer.prefab.meta | 2 +- .../ui/SpriteSplash.prefab.meta | 2 +- .../default_prefab/ui/TiledMap.prefab.meta | 2 +- .../default_prefab/ui/Toggle.prefab.meta | 2 +- .../ui/ToggleContainer.prefab.meta | 2 +- .../default_prefab/ui/VideoPlayer.prefab.meta | 2 +- .../default_prefab/ui/WebView.prefab.meta | 2 +- .../default_prefab/ui/Widget.prefab.meta | 2 +- .../default_prefab/ui/pageView.prefab.meta | 2 +- .../effects/advanced/fabric.effect.meta | 2 +- editor/assets/effects/advanced/leaf.effect | 2 +- .../effects/pipeline/cluster-culling.effect | 14 +- .../pipeline/float-output-process.effect | 31 +- .../pipeline/post-process/post-final.effect | 2 +- .../dcc/imported-metallic-roughness.effect | 12 +- editor/assets/primitives.fbx | Bin 213184 -> 242784 bytes .../debug-view-runtime-control.prefab.meta | 2 +- editor/i18n/en/assets.js | 2 +- editor/i18n/en/localization.js | 11 + editor/i18n/zh/assets.js | 125 +- editor/i18n/zh/localization.js | 11 + editor/inspector/components.js | 2 - editor/inspector/components/widget.js | 8 +- editor/inspector/contributions/node.js | 6 +- jest.config.js | 10 +- native/CMakeLists.txt | 17 +- native/cocos/3d/assets/Mesh.cpp | 144 + native/cocos/3d/assets/Mesh.h | 9 + native/cocos/3d/misc/CreateMesh.cpp | 14 +- native/cocos/3d/misc/CreateMesh.h | 2 + .../bindings/jswrapper/napi/ScriptEngine.cpp | 16 +- .../cocos/bindings/jswrapper/napi/Utils.cpp | 31 +- .../bindings/jswrapper/v8/HelperMacros.h | 4 +- native/cocos/bindings/manual/jsb_global.cpp | 135 + native/cocos/core/assets/SimpleTexture.cpp | 10 +- .../spine-wasm/spine-type-export.cpp | 2019 ++--- native/cocos/engine/Engine.cpp | 41 +- native/cocos/platform/SDLHelper.cpp | 17 +- native/cocos/platform/SDLHelper.h | 2 + .../platform/interfaces/modules/Device.cpp | 23 +- .../platform/interfaces/modules/Device.h | 3 - native/cocos/platform/mac/AppDelegate.mm | 2 - native/cocos/platform/mac/View.h | 2 +- native/cocos/platform/mac/View.mm | 16 + native/cocos/platform/mac/ViewController.h | 32 - native/cocos/platform/mac/ViewController.mm | 41 - native/cocos/platform/mac/modules/Screen.mm | 6 +- .../platform/mac/modules/SystemWindow.mm | 18 +- .../openharmony/FileUtils-OpenHarmony.cpp | 46 +- .../openharmony/FileUtils-OpenHarmony.h | 19 +- .../openharmony/OpenHarmonyPlatform.cpp | 6 +- .../openharmony/OpenHarmonyPlatform.h | 11 +- .../openharmony/modules/Accelerometer.cpp | 35 +- .../platform/openharmony/modules/Battery.cpp | 8 +- .../platform/openharmony/modules/Screen.cpp | 27 +- .../platform/openharmony/modules/System.cpp | 17 +- .../platform/openharmony/napi/NapiHelper.cpp | 481 +- .../platform/openharmony/napi/NapiHelper.h | 91 +- .../platform/openharmony/napi/NapiInit.cpp | 29 +- .../platform/openharmony/napi/napi-inl.h | 6618 +++++++++++++++++ native/cocos/platform/openharmony/napi/napi.h | 3193 ++++++++ .../renderer/gfx-agent/DescriptorSetAgent.cpp | 9 +- .../renderer/gfx-agent/DescriptorSetAgent.h | 2 +- .../renderer/gfx-base/GFXDescriptorSet.cpp | 19 +- .../renderer/gfx-base/GFXDescriptorSet.h | 3 +- native/cocos/renderer/gfx-base/SPIRVUtils.cpp | 5 +- native/cocos/renderer/gfx-metal/MTLTexture.mm | 3 +- .../gfx-validator/DescriptorSetValidator.cpp | 6 +- .../gfx-validator/DescriptorSetValidator.h | 2 +- native/cocos/renderer/gfx-wgpu/CMakeLists.txt | 21 +- native/cocos/renderer/gfx-wgpu/WGPUBuffer.cpp | 8 +- .../renderer/gfx-wgpu/WGPUCommandBuffer.cpp | 143 +- .../renderer/gfx-wgpu/WGPUCommandBuffer.h | 6 +- .../renderer/gfx-wgpu/WGPUDescriptorSet.cpp | 269 +- .../renderer/gfx-wgpu/WGPUDescriptorSet.h | 40 +- .../gfx-wgpu/WGPUDescriptorSetLayout.cpp | 321 +- .../gfx-wgpu/WGPUDescriptorSetLayout.h | 16 +- native/cocos/renderer/gfx-wgpu/WGPUDevice.cpp | 8 +- native/cocos/renderer/gfx-wgpu/WGPUDevice.h | 3 +- .../cocos/renderer/gfx-wgpu/WGPUEMSImpl.cpp | 22 +- native/cocos/renderer/gfx-wgpu/WGPUExports.h | 22 +- native/cocos/renderer/gfx-wgpu/WGPUObject.h | 11 +- .../renderer/gfx-wgpu/WGPUPipelineLayout.h | 2 +- .../renderer/gfx-wgpu/WGPUPipelineState.cpp | 7 +- native/cocos/renderer/gfx-wgpu/WGPUQueue.cpp | 11 +- .../cocos/renderer/gfx-wgpu/WGPUSampler.cpp | 5 +- native/cocos/renderer/gfx-wgpu/WGPUSampler.h | 2 +- native/cocos/renderer/gfx-wgpu/WGPUShader.cpp | 39 +- native/cocos/renderer/gfx-wgpu/WGPUShader.h | 6 +- .../cocos/renderer/gfx-wgpu/WGPUTexture.cpp | 50 + native/cocos/renderer/gfx-wgpu/WGPUTexture.h | 2 + native/cocos/renderer/gfx-wgpu/WGPUUtils.cpp | 93 +- native/cocos/renderer/gfx-wgpu/WGPUUtils.h | 35 +- .../renderer/pipeline/ClusterLightCulling.h | 2 +- .../pipeline/custom/NativeBuiltinUtils.cpp | 54 +- native/cocos/ui/edit-box/EditBox-mac.mm | 5 + .../cocos/ui/edit-box/EditBox-openharmony.cpp | 48 +- .../cocos/ui/edit-box/EditBox-openharmony.h | 13 +- .../ui/webview/WebViewImpl-openharmony.cpp | 203 +- .../ui/webview/WebViewImpl-openharmony.h | 10 +- native/external-config.json | 2 +- package-lock.json | 3896 +++++----- package.json | 5 +- packages/build-engine/package.json | 4 +- .../native-pack-tool/src/platforms/android.ts | 2 +- .../src/platforms/huawei-agc.ts | 2 + pal/env/src/native/env.ts | 4 +- pal/input/minigame/touch-input.ts | 12 +- pal/input/native/touch-input.ts | 12 +- pal/input/touch-manager.ts | 52 +- pal/input/web/touch-input.ts | 13 +- pal/screen-adapter/native/screen-adapter.ts | 6 +- platforms/minigame/common/engine/Editbox.js | 5 +- platforms/native/builtin/index.js | 4 +- platforms/native/engine/index.js | 6 +- platforms/native/engine/jsb-gfx.js | 3 +- platforms/native/engine/jsb-loader.js | 2 +- scripts/clear-cache.js | 2 +- templates/cmake/openharmony.cmake | 7 + .../entry/src/main/ets/cocos/game.ts | 2 +- .../entry/src/main/ets/common/Constants.ts | 1 + .../src/main/ets/workers/cocos_worker.ts | 30 +- templates/openharmony/hvigorw | 0 tests/constants-for-test.ts | 4 +- tests/core/math/vec2.test.ts | 40 +- tests/init.ts | 73 +- tests/physics/character-controller.ts | 95 + tests/physics/characterController.ts | 97 - tests/physics/constraint.ts | 15 +- tests/physics/dynamic.ts | 260 +- tests/physics/event.ts | 91 +- tests/physics/filtering.ts | 151 +- tests/physics/physics.test.ts | 97 +- tests/physics/raycast.ts | 157 +- tests/physics/sleep.ts | 72 +- tests/physics/stability.ts | 24 +- tests/physics/sweep.ts | 80 +- tests/physics/volume.ts | 54 +- tests/physics2d/physics2d.test.ts | 5 +- tests/physics2d/rigid-body.ts | 52 + tests/ui/label.test.ts | 49 +- tests/utils/pal-wasm-testing.ts | 53 + 237 files changed, 17750 insertions(+), 6176 deletions(-) create mode 100644 .github/workflows/native-compile-webgpu.yml delete mode 100644 native/cocos/platform/mac/ViewController.h delete mode 100644 native/cocos/platform/mac/ViewController.mm create mode 100644 native/cocos/platform/openharmony/napi/napi-inl.h create mode 100644 native/cocos/platform/openharmony/napi/napi.h mode change 100644 => 100755 templates/openharmony/hvigorw create mode 100644 tests/physics/character-controller.ts delete mode 100644 tests/physics/characterController.ts create mode 100644 tests/utils/pal-wasm-testing.ts diff --git a/.github/workflows/native-bindings.yml b/.github/workflows/native-bindings.yml index 7684b3f3dbc..94a6a7155fe 100644 --- a/.github/workflows/native-bindings.yml +++ b/.github/workflows/native-bindings.yml @@ -26,6 +26,7 @@ jobs: with: ndk-version: r21e add-to-path: false + local-cache: true - name: Generate decorators run: | bash ./native/utils/generate_decorators.sh diff --git a/.github/workflows/native-compile-platforms.yml b/.github/workflows/native-compile-platforms.yml index c90b496ec70..e17a552204b 100644 --- a/.github/workflows/native-compile-platforms.yml +++ b/.github/workflows/native-compile-platforms.yml @@ -74,6 +74,7 @@ jobs: with: ndk-version: r21e add-to-path: false + local-cache: true - uses: actions/setup-java@v3 id: setup-jdk with: @@ -144,6 +145,7 @@ jobs: with: ndk-version: r21e add-to-path: false + local-cache: true - uses: actions/setup-java@v3 id: setup-jdk with: @@ -206,6 +208,157 @@ jobs: df -h echo "Compile Android Debug Done!" + compile_openharmony: + name: "Openharmony" + if: + (! contains(github.event.pull_request.body, '[X] does not change any runtime related code or build configuration')) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Download external libraries + shell: bash + run: | + EXT_VERSION=`node ./.github/workflows/get-native-external-version.js` + git clone --branch $EXT_VERSION --depth 1 https://github.com/cocos/cocos-engine-external native/external + - uses: actions/setup-java@v3 + id: setup-jdk + with: + distribution: 'zulu' + java-version: '17' + - name: Get oh sdk cache directory path + id: oh-sdk-cache-dir-path + run: | + echo "cache dir: " + echo "dir=$HOME/openharmony" >> $GITHUB_OUTPUT + + - name: Output cache dir + run: | + echo "Output cache dir: ${{ steps.oh-sdk-cache-dir-path.outputs.dir }}" + + - name: Cache OH SDK + id: cache-oh-sdk + uses: actions/cache@v3 + env: + cache-name: cache-oh-sdk-9 + with: + path: ${{ steps.oh-sdk-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-build-${{ env.cache-name }} + + - name: Add package.json + run: | + echo "{}" > package.json + echo "{\"name\": \"tests\",\"lockfileVersion\": 3,\"requires\": true,\"packages\": {}}" > package-lock.json + - uses: actions/setup-node@v3 + with: + node-version: 14 + cache: 'npm' + + - if: ${{ steps.cache-oh-sdk.outputs.cache-hit != 'true' }} + name: No Cache found, install oh sdk + continue-on-error: false + run: | + if [ ! -d "$HOME/openharmony" ]; then + mkdir -p $HOME/openharmony + echo "Download commandline-tools-linux.zip ..." + curl -o commandline-tools-linux.zip "https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_package_901_9/b1/v3/E6zhv5UFQ2-inIwNJhTN6Q/commandline-tools-linux-2.0.0.2.zip?HW-CC-KV=V1&HW-CC-Date=20230621T074401Z&HW-CC-Expire=315360000&HW-CC-Sign=621224257B02079B1E76C0A56FDF21483400B1E3556213F88DC79BC9BE7D595D" + echo "Unzip commandline-tools-linux.zip ..." + unzip commandline-tools-linux.zip -d $HOME/openharmony > /dev/null + cd $HOME/openharmony + ls -l + cd command-line-tools + echo "=============== PATCHING sdkmanager/bin/sdkmgr file ===============" + sed -i "s@-Dfile.encoding=UTF-8@-Dfile.encoding=UTF-8 -Duser.country=CN@g" ./sdkmanager/bin/sdkmgr + cd bin + ./sdkmgr list + echo "=============== INSTALL HOS toolchains:9 ===============" + ./sdkmgr install toolchains:9 --accept-license > /dev/null + echo "=============== INSTALL OH SDK ets:9 ===============" + ./sdkmgr install OpenHarmony/ets:9 --accept-license > /dev/null + echo "=============== INSTALL OH SDK js:9 ===============" + ./sdkmgr install OpenHarmony/js:9 --accept-license > /dev/null + echo "=============== INSTALL OH SDK native:9 ===============" + ./sdkmgr install OpenHarmony/native:9 --accept-license > /dev/null + echo "=============== INSTALL OH SDK toolchains:9 ===============" + ./sdkmgr install OpenHarmony/toolchains:9 --accept-license > /dev/null + echo "=============== INSTALL OH SDK DONE ===============" + ./sdkmgr list + fi + + - name: Compile for Openharmony + run: | + which node + which npm + which java + node -v + npm -v + java --version + echo "=============== list files in oh sdk ($HOME/openharmony) ===============" + pushd $HOME/openharmony + ls -l + popd + + NATIVE_ROOT=$GITHUB_WORKSPACE/native + echo "Compiling Openharmony ... " + + cd $GITHUB_WORKSPACE/templates/openharmony + + echo "message(STATUS \"hook before \${CC_TARGET_NAME}\")" >> Pre-Sample.cmake + echo "message(STATUS \"hook after \${CC_TARGET_NAME}\")" >> Post-Sample.cmake + + mkdir -p build-oh/proj + touch build-oh/proj/cfg.cmake + echo "set(CC_USE_GLES3 ON)" >> build-oh/proj/cfg.cmake + echo "set(CC_USE_VULKAN OFF)" >> build-oh/proj/cfg.cmake + echo "set(CC_USE_GLES2 ON)" >> build-oh/proj/cfg.cmake + echo "set(USE_WEBSOCKET_SERVER OFF)" >> build-oh/proj/cfg.cmake + echo "set(CMAKE_CXX_STANDARD_REQUIRED ON)" >> build-oh/proj/cfg.cmake + echo "set(COCOS_X_PATH $NATIVE_ROOT)" >> build-oh/proj/cfg.cmake + echo "set(CC_EXECUTABLE_NAME \"\")" >> build-oh/proj/cfg.cmake + echo "set(USE_SE_V8 OFF)" >> build-oh/proj/cfg.cmake + echo "set(USE_SE_NAPI ON)" >> build-oh/proj/cfg.cmake + + echo "=============== cat build-oh/proj/cfg.cmake ===============" + cat build-oh/proj/cfg.cmake + + mkdir -p build-oh/assets + + ASSET_DIR=$GITHUB_WORKSPACE/templates/openharmony/build-oh + + echo "=============== HACK ./entry/build-profile.json5 ===============" + sed -i "s@[^-]DRES_DIR[^=]@$ASSET_DIR@g" ./entry/build-profile.json5 + sed -i "s@[^-]DCOMMON_DIR[^=]@$GITHUB_WORKSPACE/templates/common@g" ./entry/build-profile.json5 + # To speedup CI, compile arm64-v8a only + sed -i "s@\"armeabi-v7a\",@@g" ./entry/build-profile.json5 + + echo "entry/build-profile.json5: " + cat ./entry/build-profile.json5 + + echo "=============== HACK ./hvigor/hvigor-wrapper.js ===============" + sed -i "s@HVIGOR_WRAPPER_PNPM_SCRIPT_PATH,\[\"install\"\]@HVIGOR_WRAPPER_PNPM_SCRIPT_PATH,\[\"install\", \"--lockfile=false\"\]@g" ./hvigor/hvigor-wrapper.js + + echo "=============== SET NPM OH REGISTRY ===============" + echo "@ohos:registry=https://repo.harmonyos.com/npm/" >> ~/.npmrc + npm config get @ohos:registry + npm config get registry + + echo "=============== UPDATE local.properties ===============" + echo "nodejs.dir=/usr/bin" >> local.properties + echo "hwsdk.dir=$HOME/openharmony/hwsdk" >> local.properties + + echo "=============== UPDATE ./hvigor/hvigor-config.json5 ===============" + echo "{\"hvigorVersion\":\"3.0.2\",\"dependencies\":{\"@ohos/hvigor-ohos-plugin\":\"3.0.2\"}}" > ./hvigor/hvigor-config.json5 + + echo "=============== Do not import game.ts for cocos_worker.ts ===============" + sed -i '/<% if(!useV8) { %>/,/<% } %>/d' ./entry/src/main/ets/workers/cocos_worker.ts + + echo "=============== EXECUTE hvigorw ===============" + ./hvigorw clean -i + ./hvigorw --stop-daemon -i + ./hvigorw assembleHap -p debuggable=true --no-daemon -d + #./hvigorw default@ProcessLibs -p debuggable=true --no-daemon -d + echo "=============== EXECUTE hvigorw DONE ===============" + + compile_mac_x86_64: name: "MacOS x86_64" if: @@ -359,33 +512,3 @@ jobs: cmake --build . --config Debug -- -jobs $NUM_OF_CORES CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO fi echo "Compile iOS Done!" - - compile_wgpu_mac: - name: "Emscripten" - runs-on: macos-latest - if: contains( github.event.pull_request.title, 'WGPU_CI_ON' ) - steps: - - uses: actions/checkout@v2 - - name: Download external libraries - shell: bash - run: | - EXT_VERSION=`node ./.github/workflows/get-native-external-version.js` - git clone --branch $EXT_VERSION --depth 1 https://github.com/cocos/cocos-engine-external native/external - - name: Setup Emscripten - run: | - NATIVE_ROOT=$GITHUB_WORKSPACE/native - git clone https://github.com/emscripten-core/emsdk.git NATIVE_ROOT/../../emsdk - cd NATIVE_ROOT/../../emsdk - ./emsdk install 3.1.17 - ./emsdk activate 3.1.17 - source ./emsdk_env.sh - emcc -v - - name: Compile - env: - COCOS_ENGINE_DEV: 1 - run: | - NATIVE_ROOT=$GITHUB_WORKSPACE/native - cd $NATIVE_ROOT/cocos/renderer/gfx-wgpu - $NATIVE_ROOT/../../emsdk/upstream/emscripten/emcmake cmake . - $NATIVE_ROOT/../../emsdk/upstream/emscripten/emmake make - echo "Compile WGPU by ems on MacOS Done!" diff --git a/.github/workflows/native-compile-webgpu.yml b/.github/workflows/native-compile-webgpu.yml new file mode 100644 index 00000000000..91b16e6359f --- /dev/null +++ b/.github/workflows/native-compile-webgpu.yml @@ -0,0 +1,66 @@ +name: Compile WebGPU + +on: + pull_request: + paths: + - 'native/external-config.json' + - 'native/cocos/base/**' + - 'native/cocos/renderer/gfx-base/**' + - 'native/cocos/renderer/gfx-wgpu/**' + - 'native/cocos/renderer/gfx-validator/**' + - 'native/cocos/renderer/gfx-empty/**' + - '.github/workflows/native-compile-webgpu.yml' + +# github.head_ref is only defined on pull_request events +concurrency: + group: ${{ github.workflow }}-${{ github.actor }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + compile_wgpu: + if: + (! contains(github.event.pull_request.body, '[X] does not change any runtime related code or build configuration')) + name: "Emscripten" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Download external libraries + shell: bash + run: | + EXT_VERSION=`node ./.github/workflows/get-native-external-version.js` + git clone --branch $EXT_VERSION --depth 1 https://github.com/cocos/cocos-engine-external native/external + + - name: Setup emsdk + uses: dumganhar/setup-emsdk@997d2cde2deabda085a11f98e86e842915b0e846 + with: + version: 3.1.45 + actions-cache-folder: 'emsdk-cache' + + - name: Verify + run: | + which emcc + emcc -v + + - name: Install ninja + run: | + if ! command -v ninja &> /dev/null; then + echo "Ninja not found, installing..." + # sudo apt update + sudo apt install ninja-build + else + echo "Ninja is already installed." + fi + which ninja + + - name: Compile + # env: + # COCOS_ENGINE_DEV: 1 + run: | + NATIVE_ROOT=$GITHUB_WORKSPACE/native + cd $NATIVE_ROOT/cocos/renderer/gfx-wgpu + mkdir build + cd build + cmake .. -GNinja -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Debug + ninja + echo "============== Compile WGPU by ems on Ubuntu Done! ==============" diff --git a/.github/workflows/native-linter-android.yml b/.github/workflows/native-linter-android.yml index da9c6cea328..edc7d90ae92 100644 --- a/.github/workflows/native-linter-android.yml +++ b/.github/workflows/native-linter-android.yml @@ -39,6 +39,7 @@ jobs: with: ndk-version: r21e add-to-path: false + local-cache: true - name: Get changed files uses: PatriceJiang/paths-filter@master id: listchanged diff --git a/cc.config.json b/cc.config.json index 6bf1cc4f72c..db1368c1396 100644 --- a/cc.config.json +++ b/cc.config.json @@ -680,6 +680,12 @@ "type": "boolean", "value": false, "internal": true + }, + "CULL_MESHOPT": { + "comment": "An internal constant to indicate whether we cull the meshopt wasm module and asm.js module.", + "type": "boolean", + "value": true, + "internal": true } }, diff --git a/cocos/2d/assembler/label/letter-font.ts b/cocos/2d/assembler/label/letter-font.ts index 3acef2a0bcf..c189b39fb2a 100644 --- a/cocos/2d/assembler/label/letter-font.ts +++ b/cocos/2d/assembler/label/letter-font.ts @@ -25,7 +25,7 @@ import { js } from '../../../core'; import { Label, LabelOutline } from '../../components'; import { bmfontUtils } from './bmfontUtils'; -import { shareLabelInfo, LetterAtlas, computeHash } from './font-utils'; +import { shareLabelInfo, LetterAtlas, computeHash, LetterRenderTexture } from './font-utils'; const _atlasWidth = 1024; const _atlasHeight = 1024; @@ -39,7 +39,7 @@ export const letterFont = js.mixin(bmfontUtils, { _shareAtlas = new LetterAtlas(_atlasWidth, _atlasHeight); } - return _shareAtlas.getTexture(); + return _shareAtlas.getTexture() as LetterRenderTexture | null; }, _updateFontFamily (comp) { @@ -47,12 +47,12 @@ export const letterFont = js.mixin(bmfontUtils, { shareLabelInfo.fontFamily = this._getFontFamily(comp); // outline - const outline = comp.getComponent(LabelOutline); - if (outline && outline.enabled) { + const isOutlined = comp.enableOutline && comp.outlineWidth > 0; + if (isOutlined) { shareLabelInfo.isOutlined = true; - shareLabelInfo.margin = outline.width; - shareLabelInfo.out = outline.color.clone(); - shareLabelInfo.out.a = outline.color.a * comp.color.a / 255.0; + shareLabelInfo.margin = comp.outlineWidth; + shareLabelInfo.out = comp.outlineColor.clone(); + shareLabelInfo.out.a = comp.outlineColor.color.a * comp.color.a / 255.0; } else { shareLabelInfo.isOutlined = false; shareLabelInfo.margin = 0; diff --git a/cocos/2d/assembler/label/ttfUtils.ts b/cocos/2d/assembler/label/ttfUtils.ts index 941cad48eac..c4ff09c4aa0 100644 --- a/cocos/2d/assembler/label/ttfUtils.ts +++ b/cocos/2d/assembler/label/ttfUtils.ts @@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { Label, LabelOutline, LabelShadow } from '../../components'; +import { Label } from '../../components'; import { ISharedLabelData } from './font-utils'; import { UITransform } from '../../framework/ui-transform'; import { dynamicAtlasManager } from '../../utils/dynamic-atlas/atlas-manager'; @@ -30,13 +30,20 @@ import { TextOutputLayoutData, TextOutputRenderData } from './text-output-data'; import { TextStyle } from './text-style'; import { TextLayout } from './text-layout'; import { view } from '../../../ui/view'; +import { approx } from '../../../core'; const Overflow = Label.Overflow; export const ttfUtils = { - updateProcessingData (style: TextStyle, layout: TextLayout, - outputLayoutData: TextOutputLayoutData, outputRenderData: TextOutputRenderData, comp: Label, trans: UITransform): void { + updateProcessingData ( + style: TextStyle, + layout: TextLayout, + outputLayoutData: TextOutputLayoutData, + outputRenderData: TextOutputRenderData, + comp: Label, + trans: UITransform, + ): void { // font info // both style.isSystemFontUsed = comp.useSystemFont; style.fontSize = comp.fontSize; @@ -62,25 +69,23 @@ export const ttfUtils = { style.underlineHeight = comp.underlineHeight; // outline// both - let outlineComp = LabelOutline && comp.getComponent(LabelOutline); - outlineComp = (outlineComp && outlineComp.enabled && outlineComp.width > 0) ? outlineComp : null; - if (outlineComp) { + const isOutlined = comp.enableOutline && comp.outlineWidth > 0; + if (isOutlined) { style.isOutlined = true; - style.outlineColor.set(outlineComp.color); - style.outlineWidth = outlineComp.width; + style.outlineColor.set(comp.outlineColor); + style.outlineWidth = comp.outlineWidth; } else { style.isOutlined = false; } // shadow// both - let shadowComp = LabelShadow && comp.getComponent(LabelShadow); - shadowComp = (shadowComp && shadowComp.enabled) ? shadowComp : null; - if (shadowComp) { + const isShadow = comp.enableShadow && (comp.shadowBlur > 0 || !approx(comp.shadowOffset.x, 0) || !approx(comp.shadowOffset.y, 0)); + if (isShadow) { style.hasShadow = true; - style.shadowColor.set(shadowComp.color); - style.shadowBlur = shadowComp.blur; - style.shadowOffsetX = shadowComp.offset.x; - style.shadowOffsetY = shadowComp.offset.y; + style.shadowColor.set(comp.shadowColor); + style.shadowBlur = comp.shadowBlur; + style.shadowOffsetX = comp.shadowOffset.x; + style.shadowOffsetY = comp.shadowOffset.y; } else { style.hasShadow = false; } @@ -126,8 +131,15 @@ export const ttfUtils = { // TextProcessing processing.processingString(false, style, layout, outputLayoutData, comp.string); - processing.generateRenderInfo(false, style, layout, outputLayoutData, outputRenderData, - comp.string, this.generateVertexData); + processing.generateRenderInfo( + false, + style, + layout, + outputLayoutData, + outputRenderData, + comp.string, + this.generateVertexData, + ); const renderData = comp.renderData; renderData.textureDirty = true; @@ -173,9 +185,11 @@ export const ttfUtils = { }, updateVertexData (comp: Label): void { + // no needs to update vertex data }, updateUVs (comp: Label): void { + // no needs to update uv data }, _updateFontFamily (comp: Label): string { diff --git a/cocos/2d/components/deprecated.ts b/cocos/2d/components/deprecated.ts index 777e6c7c623..f774440abd6 100644 --- a/cocos/2d/components/deprecated.ts +++ b/cocos/2d/components/deprecated.ts @@ -29,10 +29,10 @@ import { LabelOutline } from './label-outline'; import { RichText } from './rich-text'; import { Sprite } from './sprite'; import { UIMeshRenderer } from './ui-mesh-renderer'; -import { Graphics } from './graphics'; +import { Graphics } from './graphics' import { UIOpacity } from './ui-opacity'; -import { js, replaceProperty } from '../../core'; -import './deprecated-3.9.0'; +import { js, replaceProperty, markAsWarning } from '../../core'; +import { LabelShadow } from './label-shadow'; /** * Alias of [[Mask]] @@ -120,3 +120,29 @@ replaceProperty(MaskType, 'MaskType', [ targetName: 'MaskType', }, ]); + +markAsWarning(LabelOutline.prototype, 'LabelOutline.prototype', [ + { + name: 'width', + suggest: 'Please use Label.prototype.outlineWidth instead.', + }, + { + name: 'color', + suggest: 'Please use Label.prototype.outlineColor instead.', + }, +]); + +markAsWarning(LabelShadow.prototype, 'LabelShadow.prototype', [ + { + name: 'color', + suggest: 'Please use Label.prototype.shadowColor instead.', + }, + { + name: 'offset', + suggest: 'Please use Label.prototype.shadowOffset instead.', + }, + { + name: 'blur', + suggest: 'Please use Label.prototype.shadowBlur instead.', + }, +]); diff --git a/cocos/2d/components/label-outline.ts b/cocos/2d/components/label-outline.ts index 46172ea0251..6489668014d 100644 --- a/cocos/2d/components/label-outline.ts +++ b/cocos/2d/components/label-outline.ts @@ -25,7 +25,7 @@ import { ccclass, help, executionOrder, menu, tooltip, requireComponent, executeInEditMode, serializable } from 'cc.decorator'; import { Component } from '../../scene-graph/component'; -import { Color } from '../../core'; +import { Color, assertIsTrue } from '../../core'; import { cclegacy } from '@base/global'; import { Label } from './label'; @@ -36,15 +36,7 @@ import { Label } from './label'; * @zh * 描边效果组件,用于字体描边,只能用于系统字体。 * - * @example - * ```ts - * import { Node, Label, LabelOutline } from 'cc'; - * // Create a new node and add label components. - * const node = new Node("New Label"); - * const label = node.addComponent(Label); - * const outline = node.addComponent(LabelOutline); - * node.parent = this.node; - * ``` + * @deprecated since v3.8.2, please use [[Label.enableOutline]] instead. */ @ccclass('cc.LabelOutline') @help('i18n:cc.LabelOutline') @@ -53,11 +45,6 @@ import { Label } from './label'; @requireComponent(Label) @executeInEditMode export class LabelOutline extends Component { - @serializable - protected _color = new Color(0, 0, 0, 255); - @serializable - protected _width = 2; - /** * @en * Outline color. @@ -65,25 +52,19 @@ export class LabelOutline extends Component { * @zh * 改变描边的颜色。 * - * @example - * ```ts - * import { Color } from 'cc'; - * outline.color = new Color(0.5, 0.3, 0.7, 1.0); - * ``` + * @deprecated since v3.8.2, please use [[Label.outlineColor]] instead. */ @tooltip('i18n:labelOutline.color') - // @constget get color (): Readonly { - return this._color; + const label = this.node.getComponent(Label); + assertIsTrue(label); + return label.outlineColor; } set color (value) { - if (this._color === value) { - return; - } - - this._color.set(value); - this._updateRenderData(); + const label = this.node.getComponent(Label); + assertIsTrue(label); + label.outlineColor = value; } /** @@ -93,38 +74,37 @@ export class LabelOutline extends Component { * @zh * 改变描边的宽度。 * - * @example - * ```ts - * outline.width = 3; - * ``` + * @deprecated since v3.8.2, please use [[Label.outlineWidth]] instead. */ @tooltip('i18n:labelOutline.width') get width (): number { - return this._width; + const label = this.node.getComponent(Label); + assertIsTrue(label); + return label.outlineWidth; } set width (value) { - if (this._width === value) { - return; - } - - this._width = value; - this._updateRenderData(); + const label = this.node.getComponent(Label); + assertIsTrue(label); + label.outlineWidth = value; } + /** + * @deprecated since v3.8.2, please use [[Label.enableOutline]] instead. + */ public onEnable (): void { - this._updateRenderData(); + const label = this.node.getComponent(Label); + assertIsTrue(label); + label.enableOutline = true; } + /** + * @deprecated since v3.8.2, please use [[Label.enableOutline]] instead. + */ public onDisable (): void { - this._updateRenderData(); - } - - protected _updateRenderData (): void { const label = this.node.getComponent(Label); - if (label) { - label.updateRenderData(true); - } + assertIsTrue(label); + label.enableOutline = false; } } diff --git a/cocos/2d/components/label-shadow.ts b/cocos/2d/components/label-shadow.ts index bab05719cbd..36e7685d0ff 100644 --- a/cocos/2d/components/label-shadow.ts +++ b/cocos/2d/components/label-shadow.ts @@ -25,19 +25,14 @@ import { ccclass, help, executionOrder, menu, tooltip, requireComponent, executeInEditMode, serializable } from 'cc.decorator'; import { Component } from '../../scene-graph/component'; -import { Color, Vec2 } from '../../core'; +import { Color, Vec2, assertIsTrue } from '../../core'; import { Label } from './label'; /** * @en Shadow effect for Label component, only for system fonts or TTF fonts. * @zh 用于给 Label 组件添加阴影效果,只能用于系统字体或 ttf 字体。 - * @example - * import { Node, Label, LabelShadow } from 'cc'; - * // Create a new node and add label components. - * const node = new Node("New Label"); - * const label = node.addComponent(Label); - * const shadow = node.addComponent(LabelShadow); - * node.parent = this.node; + * + * @deprecated since v3.8.2, please use [[Label.enableShadow]] instead. */ @ccclass('cc.LabelShadow') @help('i18n:cc.LabelShadow') @@ -46,13 +41,6 @@ import { Label } from './label'; @requireComponent(Label) @executeInEditMode export class LabelShadow extends Component { - @serializable - protected _color = new Color(0, 0, 0, 255); - @serializable - protected _offset = new Vec2(2, 2); - @serializable - protected _blur = 2; - /** * @en * Shadow color. @@ -60,24 +48,19 @@ export class LabelShadow extends Component { * @zh * 阴影的颜色。 * - * @example - * ```ts - * import { Color } from 'cc'; - * labelShadow.color = new Color(0.5, 0.3, 0.7, 1.0); - * ``` + * @deprecated since v3.8.2, please use [[Label.shadowColor]] instead. */ @tooltip('i18n:labelShadow.color') get color (): Readonly { - return this._color; + const label = this.node.getComponent(Label); + assertIsTrue(label); + return label.shadowColor; } set color (value) { - if (this._color === value) { - return; - } - - this._color.set(value); - this._updateRenderData(); + const label = this.node.getComponent(Label); + assertIsTrue(label); + label.shadowColor = value; } /** @@ -87,20 +70,19 @@ export class LabelShadow extends Component { * @zh * 字体与阴影的偏移。 * - * @example - * ```ts - * import { Vec2 } from 'cc'; - * labelShadow.offset = new Vec2(2, 2); - * ``` + * @deprecated since v3.8.2, please use [[Label.shadowOffset]] instead. */ @tooltip('i18n:labelShadow.offset') get offset (): Vec2 { - return this._offset; + const label = this.node.getComponent(Label); + assertIsTrue(label); + return label.shadowOffset; } set offset (value) { - this._offset = value; - this._updateRenderData(); + const label = this.node.getComponent(Label); + assertIsTrue(label); + label.shadowOffset = value; } /** @@ -110,33 +92,36 @@ export class LabelShadow extends Component { * @zh * 阴影的模糊程度。 * - * @example - * ```ts - * labelShadow.blur = 2; - * ``` + * @deprecated since v3.8.2, please use [[Label.shadowBlur]] instead. */ @tooltip('i18n:labelShadow.blur') get blur (): number { - return this._blur; + const label = this.node.getComponent(Label); + assertIsTrue(label); + return label.shadowBlur; } set blur (value) { - this._blur = value; - this._updateRenderData(); + const label = this.node.getComponent(Label); + assertIsTrue(label); + label.shadowBlur = value; } + /** + * @deprecated since v3.8.2, please use [[Label.enableShadow]] instead. + */ public onEnable (): void { - this._updateRenderData(); + const label = this.node.getComponent(Label); + assertIsTrue(label); + label.enableShadow = true; } + /** + * @deprecated since v3.8.2, please use [[Label.enableShadow]] instead. + */ public onDisable (): void { - this._updateRenderData(); - } - - protected _updateRenderData (): void { const label = this.node.getComponent(Label); - if (label) { - label.updateRenderData(true); - } + assertIsTrue(label); + label.enableShadow = false; } } diff --git a/cocos/2d/components/label.ts b/cocos/2d/components/label.ts index 267f0a6df77..f0e1035f8c7 100644 --- a/cocos/2d/components/label.ts +++ b/cocos/2d/components/label.ts @@ -28,7 +28,7 @@ import { BYTEDANCE, EDITOR, JSB } from 'internal:constants'; import { minigame } from 'pal/minigame'; import { BitmapFont, Font, SpriteFrame } from '../assets'; import { ImageAsset, Texture2D } from '../../asset/assets'; -import { ccenum, Color } from '../../core'; +import { ccenum, Color, Vec2 } from '../../core'; import { cclegacy } from '@base/global'; import { IBatcher } from '../renderer/i-batcher'; import { FontAtlas } from '../assets/bitmap-font'; @@ -595,15 +595,152 @@ export class Label extends UIRenderer { @editable @displayOrder(18) @tooltip('i18n:label.underline_height') - public get underlineHeight (): number { + get underlineHeight (): number { return this._underlineHeight; } - public set underlineHeight (value) { + set underlineHeight (value) { if (this._underlineHeight === value) return; this._underlineHeight = value; this.markForUpdateRenderData(); } + /** + ** @en + ** Outline effect used to change the display, only for system fonts or TTF fonts. + ** + ** @zh + ** 描边效果组件,用于字体描边,只能用于系统字体或 ttf 字体。 + **/ + @editable + @visible(function (this: Label) { return !(this._font instanceof BitmapFont); }) + @displayOrder(19) + @tooltip('i18n:label.outline_enable') + get enableOutline (): boolean { + return this._enableOutline; + } + set enableOutline (value) { + if (this._enableOutline === value) return; + this._enableOutline = value; + this.markForUpdateRenderData(); + } + + /** + * @en + * Outline color. + * + * @zh + * 改变描边的颜色。 + */ + @editable + @visible(function (this: Label) { return this._enableOutline && !(this._font instanceof BitmapFont); }) + @displayOrder(20) + @tooltip('i18n:label.outline_color') + get outlineColor (): Color { + return this._outlineColor; + } + set outlineColor (value) { + if (this._outlineColor === value) return; + this._outlineColor.set(value); + this.markForUpdateRenderData(); + } + + /** + * @en + * Change the outline width. + * + * @zh + * 改变描边的宽度。 + */ + @editable + @visible(function (this: Label) { return this._enableOutline && !(this._font instanceof BitmapFont); }) + @displayOrder(21) + @tooltip('i18n:label.outline_width') + get outlineWidth (): number { + return this._outlineWidth; + } + set outlineWidth (value) { + if (this._outlineWidth === value) return; + this._outlineWidth = value; + this.markForUpdateRenderData(); + } + + /** + * @en Shadow effect for Label component, only for system fonts or TTF fonts. Disabled when cache mode is char. + * @zh 用于给 Label 组件添加阴影效果,只能用于系统字体或 ttf 字体。在缓存模式为 char 时不可用。 + */ + @editable + @visible(function (this: Label) { return !(this._font instanceof BitmapFont) && (this.cacheMode !== CacheMode.CHAR); }) + @displayOrder(22) + @tooltip('i18n:label.shadow_enable') + get enableShadow (): boolean { + return this._enableShadow; + } + set enableShadow (value) { + if (this._enableShadow === value) return; + this._enableShadow = value; + this.markForUpdateRenderData(); + } + + /** + * @en + * Shadow color. + * + * @zh + * 阴影的颜色。 + */ + @editable + @visible(function (this: Label) { return this._enableShadow && !(this._font instanceof BitmapFont) && (this.cacheMode !== CacheMode.CHAR); }) + @displayOrder(23) + @tooltip('i18n:label.shadow_color') + get shadowColor (): Color { + return this._shadowColor; + } + set shadowColor (value) { + if (this._shadowColor === value) return; + this._shadowColor.set(value); + this.markForUpdateRenderData(); + } + + /** + * @en + * Offset between font and shadow. + * + * @zh + * 字体与阴影的偏移。 + */ + @editable + @visible(function (this: Label) { return this._enableShadow && !(this._font instanceof BitmapFont) && (this.cacheMode !== CacheMode.CHAR); }) + @displayOrder(24) + @tooltip('i18n:label.shadow_offset') + get shadowOffset (): Vec2 { + return this._shadowOffset; + } + set shadowOffset (value) { + if (this._shadowOffset === value) return; + this._shadowOffset.set(value); + this.markForUpdateRenderData(); + } + + /** + * @en + * A non-negative float specifying the level of shadow blur. + * + * @zh + * 阴影的模糊程度。 + */ + @editable + @visible(function (this: Label) { return this._enableShadow && !(this._font instanceof BitmapFont) && (this.cacheMode !== CacheMode.CHAR); }) + @displayOrder(25) + @tooltip('i18n:label.shadow_blur') + get shadowBlur (): number { + return this._shadowBlur; + } + set shadowBlur (value) { + if (this._shadowBlur === value) return; + this._shadowBlur = value; + this.markForUpdateRenderData(); + } + /** * @deprecated since v3.7.0, this is an engine private interface that will be removed in the future. */ @@ -706,6 +843,20 @@ export class Label extends UIRenderer { protected _underlineHeight = 2; @serializable protected _cacheMode = CacheMode.NONE; + @serializable + protected _enableOutline = false; + @serializable + protected _outlineColor = new Color(0, 0, 0, 255); + @serializable + protected _outlineWidth = 2; + @serializable + protected _enableShadow = false; + @serializable + protected _shadowColor = new Color(0, 0, 0, 255); + @serializable + protected _shadowOffset = new Vec2(2, 2); + @serializable + protected _shadowBlur = 2; // don't need serialize // 这个保存了旧项目的 file 数据 diff --git a/cocos/2d/components/mask.ts b/cocos/2d/components/mask.ts index 922fe303f62..9d9250ebc68 100644 --- a/cocos/2d/components/mask.ts +++ b/cocos/2d/components/mask.ts @@ -457,11 +457,9 @@ export class Mask extends Component { protected _removeMaskNode (): void { if (this._sprite) { - this._sprite.destroy(); this._sprite = null; } if (this._graphics) { - this._graphics.destroy(); this._graphics = null; } } diff --git a/cocos/2d/renderer/batcher-2d.ts b/cocos/2d/renderer/batcher-2d.ts index 81e0546d5e4..e1119b16032 100644 --- a/cocos/2d/renderer/batcher-2d.ts +++ b/cocos/2d/renderer/batcher-2d.ts @@ -618,7 +618,7 @@ export class Batcher2D implements IBatcher { this._currBID = -1; // Request ia failed - if (!ia) { + if (!ia || !this._currTexture) { return; } @@ -635,7 +635,7 @@ export class Batcher2D implements IBatcher { const curDrawBatch = this._drawBatchPool.alloc(); curDrawBatch.visFlags = this._currLayer; - curDrawBatch.texture = this._currTexture!; + curDrawBatch.texture = this._currTexture; curDrawBatch.sampler = this._currSampler; curDrawBatch.inputAssembler = ia; curDrawBatch.useLocalData = this._currTransform; diff --git a/cocos/3d/assets/mesh.ts b/cocos/3d/assets/mesh.ts index f2aafacd52b..27fb494a370 100644 --- a/cocos/3d/assets/mesh.ts +++ b/cocos/3d/assets/mesh.ts @@ -28,11 +28,11 @@ import { Asset } from '../../asset/assets/asset'; import { IDynamicGeometry } from '../../primitive/define'; import { BufferBlob } from '../misc/buffer-blob'; import { Skeleton } from './skeleton'; -import { geometry, sys, Mat4, Quat, Vec3, assertIsTrue, murmurhash2_32_gc } from '../../core'; +import { geometry, sys, Mat4, Quat, Vec3, assertIsTrue, murmurhash2_32_gc, halfToFloat } from '../../core'; import { warnID, errorID } from '@base/debug'; import { cclegacy } from '@base/global'; import { RenderingSubMesh } from '../../asset/assets'; -import { Attribute, Device, Buffer, BufferInfo, AttributeName, BufferUsageBit, Feature, Format, FormatInfos, FormatType, MemoryUsageBit, PrimitiveMode, getTypedArrayConstructor, DrawInfo, FormatInfo, deviceManager } from '../../gfx'; +import { Attribute, Device, Buffer, BufferInfo, AttributeName, BufferUsageBit, Feature, Format, FormatInfos, FormatType, MemoryUsageBit, PrimitiveMode, getTypedArrayConstructor, DrawInfo, FormatInfo, deviceManager, FormatFeatureBit } from '../../gfx'; import { Morph } from './morph'; import { MorphRendering, createMorphRendering } from './morph-rendering'; import { MeshoptDecoder } from '../misc/mesh-codec'; @@ -414,6 +414,11 @@ export class Mesh extends Asset { if (this.struct.encoded) { // decode mesh data info = decodeMesh(info); } + if (this.struct.quantized + && !(deviceManager.gfxDevice.getFormatFeatures(Format.RGB16F) & FormatFeatureBit.VERTEX_ATTRIBUTE)) { + // dequantize mesh data + info = dequantizeMesh(info); + } this._struct = info.struct; this._data = info.data; @@ -1534,11 +1539,6 @@ export function decodeMesh (mesh: Mesh.ICreateInfo): Mesh.ICreateInfo { return mesh; } - // decode the mesh - if (!MeshoptDecoder.supported) { - return mesh; - } - const res_checker = (res: number): void => { if (res < 0) { errorID(14204, res); @@ -1608,4 +1608,150 @@ export function inflateMesh (mesh: Mesh.ICreateInfo): Mesh.ICreateInfo { return mesh; } +export function dequantizeMesh (mesh: Mesh.ICreateInfo): Mesh.ICreateInfo { + const struct = JSON.parse(JSON.stringify(mesh.struct)) as Mesh.IStruct; + + const bufferBlob = new BufferBlob(); + bufferBlob.setNextAlignment(0); + + function transformVertex ( + reader: ((offset: number) => number), + writer: ((offset: number, value: number) => void), + count: number, + components: number, + componentSize: number, + readerStride: number, + writerStride: number, + ): void { + for (let i = 0; i < count; i++) { + for (let j = 0; j < components; j++) { + const inputOffset = readerStride * i + componentSize * j; + const outputOffset = writerStride * i + componentSize * j; + writer(outputOffset, reader(inputOffset)); + } + } + } + + function dequantizeHalf ( + reader: ((offset: number) => number), + writer: ((offset: number, value: number) => void), + count: number, + components: number, + readerStride: number, + writerStride: number, + ): void { + for (let i = 0; i < count; i++) { + for (let j = 0; j < components; j++) { + const inputOffset = readerStride * i + 2 * j; + const outputOffset = writerStride * i + 4 * j; + const value = halfToFloat(reader(inputOffset)); + writer(outputOffset, value); + } + } + } + + for (let i = 0; i < struct.vertexBundles.length; ++i) { + const bundle = struct.vertexBundles[i]; + const view = bundle.view; + const attributes = bundle.attributes; + const oldAttributes = mesh.struct.vertexBundles[i].attributes; + const strides: number[] = []; + const dequantizes: boolean[] = []; + const readers: ((offset: number) => number)[] = []; + for (let j = 0; j < attributes.length; ++j) { + const attr = attributes[j]; + const inputView = new DataView(mesh.data.buffer, view.offset + getOffset(oldAttributes, j)); + const reader = getReader(inputView, attr.format); + let dequantize = true; + switch (attr.format) { + case Format.R16F: + attr.format = Format.R32F; + break; + case Format.RG16F: + attr.format = Format.RG32F; + break; + case Format.RGB16F: + attr.format = Format.RGB32F; + break; + case Format.RGBA16F: + attr.format = Format.RGBA32F; + break; + default: + dequantize = false; + break; + } + strides.push(FormatInfos[attr.format].size); + dequantizes.push(dequantize); + readers.push(reader!); + } + const netStride = strides.reduce((acc, cur) => acc + cur, 0); + const newBuffer = new Uint8Array(netStride * view.count); + for (let j = 0; j < attributes.length; ++j) { + const attribute = attributes[j]; + const reader = readers[j]; + const outputView = new DataView(newBuffer.buffer, getOffset(attributes, j)); + const writer = getWriter(outputView, attribute.format)!; + const dequantize = dequantizes[j]; + const formatInfo = FormatInfos[attribute.format]; + if (dequantize) { + dequantizeHalf( + reader, + writer, + view.count, + formatInfo.count, + view.stride, + netStride, + ); + } else { + transformVertex( + reader, + writer, + view.count, + formatInfo.count, + formatInfo.size / formatInfo.count, + view.stride, + netStride, + ); + } + } + + bufferBlob.setNextAlignment(netStride); + const newView: Mesh.IBufferView = { + offset: bufferBlob.getLength(), + length: newBuffer.byteLength, + count: view.count, + stride: netStride, + }; + bundle.view = newView; + bufferBlob.addBuffer(newBuffer); + } + + // dump index buffer + for (const primitive of struct.primitives) { + if (primitive.indexView === undefined) { + continue; + } + const view = primitive.indexView; + const buffer = new Uint8Array(mesh.data.buffer, view.offset, view.length); + bufferBlob.setNextAlignment(view.stride); + const newView: Mesh.IBufferView = { + offset: bufferBlob.getLength(), + length: buffer.byteLength, + count: view.count, + stride: view.stride, + }; + primitive.indexView = newView; + bufferBlob.addBuffer(buffer); + } + + const data = new Uint8Array(bufferBlob.getCombined()); + + struct.quantized = false; + + return { + struct, + data, + }; +} + // function get diff --git a/cocos/3d/framework/mesh-renderer.ts b/cocos/3d/framework/mesh-renderer.ts index a6778731518..a28fa4cebec 100644 --- a/cocos/3d/framework/mesh-renderer.ts +++ b/cocos/3d/framework/mesh-renderer.ts @@ -901,6 +901,7 @@ export class MeshRenderer extends ModelRenderer { if (this._mesh) { const meshStruct = this._mesh.struct; this._model.createBoundingShape(meshStruct.minPosition, meshStruct.maxPosition); + this._model.updateWorldBound(); } // Initialize lighting map before model initializing // because the lighting map will influence the model's shader diff --git a/cocos/3d/misc/mesh-codec.ts b/cocos/3d/misc/mesh-codec.ts index 07e59dfa374..829cc92e0a2 100644 --- a/cocos/3d/misc/mesh-codec.ts +++ b/cocos/3d/misc/mesh-codec.ts @@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { WASM_SUPPORT_MODE } from 'internal:constants'; +import { CULL_MESHOPT, WASM_SUPPORT_MODE } from 'internal:constants'; import { ensureWasmModuleReady, instantiateWasm } from 'pal/wasm'; import { sys } from '../../core'; @@ -91,9 +91,12 @@ export function InitDecoder (): Promise { })); } -const intervalId = setInterval(() => { - if (cclegacy.game) { - cclegacy.game.onPostInfrastructureInitDelegate.add(InitDecoder); - clearInterval(intervalId); - } -}, 10); +if (!CULL_MESHOPT) { + const intervalId = setInterval(() => { + if (cclegacy.game) { + cclegacy.game.onPostInfrastructureInitDelegate.add(InitDecoder); + clearInterval(intervalId); + } + }, 10); +} + diff --git a/cocos/core/geometry/geometry-native-ext.ts b/cocos/core/geometry/geometry-native-ext.ts index b927518d529..ff43509dac2 100644 --- a/cocos/core/geometry/geometry-native-ext.ts +++ b/cocos/core/geometry/geometry-native-ext.ts @@ -22,6 +22,7 @@ import { NATIVE } from 'internal:constants'; +import { assert, error } from '@base/debug'; import { Line } from './line'; import { Plane } from './plane'; import { Ray } from './ray'; @@ -30,7 +31,6 @@ import { Sphere } from './sphere'; import { AABB } from './aabb'; import { Capsule } from './capsule'; import { Frustum } from './frustum'; -import { assert, error } from '@base/debug'; /** * cache jsb attributes in js, reduce cross language invokations. @@ -88,10 +88,9 @@ const defineAttrFloat = (kls: Constructor, attr: string): void => { // __nativeFields__ is defined in jsb_geometry_manual.cpp const desc: FieldDesc = (kls as any).__nativeFields__[attr]; const cacheKey = `_$_${attr}`; - if (!window.oh) { - // openharmony does not support the assert interface at this time. - assert(desc.fieldSize === 4, `field ${attr} size ${desc.fieldSize}`); - } + + assert(desc.fieldSize === 4, `field ${attr} size ${desc.fieldSize}`); + Object.defineProperty(kls.prototype, desc.fieldName, { configurable: true, enumerable: true, @@ -121,10 +120,9 @@ const defineAttrInt = (kls: Constructor, attr: string): void => { error(`attr ${attr} not defined in class ${kls.toString()}`); } const cacheKey = `_$_${attr}`; - if (!window.oh) { - // openharmony does not support the assert interface at this time. - assert(desc.fieldSize === 4, `field ${attr} size ${desc.fieldSize}`); - } + + assert(desc.fieldSize === 4, `field ${attr} size ${desc.fieldSize}`); + Object.defineProperty(kls.prototype, desc.fieldName, { configurable: true, enumerable: true, diff --git a/cocos/core/math/vec2.ts b/cocos/core/math/vec2.ts index 59183571de8..fc4ec0396f8 100644 --- a/cocos/core/math/vec2.ts +++ b/cocos/core/math/vec2.ts @@ -780,11 +780,18 @@ export class Vec2 extends ValueType { * @zh 获取当前向量和指定向量之间的有符号弧度。
* 有符号弧度的取值范围为 (-PI, PI],当前向量可以通过逆时针旋转有符号角度与指定向量同向。
* @param other specified vector - * @return The signed angle between the current vector and the specified vector (in radians); if there is a zero vector in the current vector and the specified vector, 0 is returned. + * @return The signed angle between the current vector and the specified vector (in radians); + * if there is a zero vector in the current vector and the specified vector, 0 is returned. */ public signAngle (other: Vec2): number { - const angle = this.angle(other); - return this.cross(other) < 0 ? -angle : angle; + // θ = atan(tan(θ)) + // = atan(sin(θ) / cos(θ)) + // = atan2(sin(θ), cos(θ)) + // = atan2(|a|·|b|·sin(θ), |a|·|b|·cos(θ)) + // = atan2(cross(a, b), dot(a, b)) + const cross = this.cross(other); + const dot = this.dot(other); + return Math.atan2(cross, dot); } /** diff --git a/cocos/core/settings.ts b/cocos/core/settings.ts index 385a60a17ba..04c49276ba1 100644 --- a/cocos/core/settings.ts +++ b/cocos/core/settings.ts @@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { HTML5, TAOBAO, TAOBAO_MINIGAME } from 'internal:constants'; +import { HTML5, NATIVE, TAOBAO, TAOBAO_MINIGAME } from 'internal:constants'; import { cclegacy } from '@base/global'; declare const fsUtils: any; @@ -76,16 +76,18 @@ export class Settings { } if (!path) return Promise.resolve(); - if (window.oh) { - return new Promise((resolve, reject): void => { - // TODO: to support a virtual module of settings. - // For now, we use a system module context to dynamically import the relative path of module. - const settingsModule = '../settings.js'; - import(settingsModule).then((res): void => { - this._settings = res.default; - resolve(); - }).catch((e): void => reject(e)); - }); + if (NATIVE) { + if (window.oh && window.scriptEngineType === 'napi') { + return new Promise((resolve, reject): void => { + // TODO: to support a virtual module of settings. + // For now, we use a system module context to dynamically import the relative path of module. + const settingsModule = '../settings.js'; + import(settingsModule).then((res): void => { + this._settings = res.default; + resolve(); + }).catch((e): void => reject(e)); + }); + } } return new Promise((resolve, reject): void => { if (!HTML5 && !path.startsWith('http')) { diff --git a/cocos/gfx/base/framebuffer.ts b/cocos/gfx/base/framebuffer.ts index 71c04e1f54b..d609db6bd67 100644 --- a/cocos/gfx/base/framebuffer.ts +++ b/cocos/gfx/base/framebuffer.ts @@ -55,9 +55,19 @@ export abstract class Framebuffer extends GFXObject { return this._depthStencilTexture; } + public get width (): number { + return this._width; + } + + public get height (): number { + return this._height; + } + protected _renderPass: RenderPass | null = null; protected _colorTextures: (Texture | null)[] = []; protected _depthStencilTexture: Texture | null = null; + protected _width: number = 0; + protected _height: number = 0; constructor () { super(ObjectType.FRAMEBUFFER); diff --git a/cocos/gfx/webgl/webgl-framebuffer.ts b/cocos/gfx/webgl/webgl-framebuffer.ts index aeab6338e91..448239c19c6 100644 --- a/cocos/gfx/webgl/webgl-framebuffer.ts +++ b/cocos/gfx/webgl/webgl-framebuffer.ts @@ -96,6 +96,8 @@ export class WebGLFramebuffer extends Framebuffer { }; WebGLCmdFuncCreateFramebuffer(WebGLDeviceManager.instance, this._gpuFramebuffer); + this._width = this._gpuFramebuffer.width; + this._height = this._gpuFramebuffer.height; } public destroy (): void { diff --git a/cocos/gfx/webgl2/webgl2-framebuffer.ts b/cocos/gfx/webgl2/webgl2-framebuffer.ts index 255c7a9a6b3..539857308ce 100644 --- a/cocos/gfx/webgl2/webgl2-framebuffer.ts +++ b/cocos/gfx/webgl2/webgl2-framebuffer.ts @@ -90,6 +90,8 @@ export class WebGL2Framebuffer extends Framebuffer { }; WebGL2CmdFuncCreateFramebuffer(WebGL2DeviceManager.instance, this._gpuFramebuffer); + this._width = this._gpuFramebuffer.width; + this._height = this._gpuFramebuffer.height; } public destroy (): void { diff --git a/cocos/gfx/webgpu/webgpu-define.ts b/cocos/gfx/webgpu/webgpu-define.ts index 04feca0a7cb..76789eff37a 100644 --- a/cocos/gfx/webgpu/webgpu-define.ts +++ b/cocos/gfx/webgpu/webgpu-define.ts @@ -27,10 +27,9 @@ */ import { WEBGPU } from 'internal:constants'; -import { gfx, webgpuAdapter, glslalgWasmModule, promiseForWebGPUInstantiation } from '../../webgpu/instantiated'; -import { Texture, CommandBuffer, DescriptorSet, Device, InputAssembler, Buffer, Shader } from './override'; -import { DeviceInfo, BufferTextureCopy, ShaderInfo, ShaderStageFlagBit, TextureViewInfo, TextureInfo, DrawInfo, BufferViewInfo, BufferInfo, BufferUsageBit, IndirectBuffer } from '../base/define'; - +import { gfx, webgpuAdapter, glslangWasmModule, promiseForWebGPUInstantiation, twgslModule } from '../../webgpu/instantiated'; +import { Texture, CommandBuffer, DescriptorSet, Device, InputAssembler, Buffer } from './override'; +import { DeviceInfo, BufferTextureCopy, ShaderInfo, ShaderStageFlagBit, TextureViewInfo, TextureInfo, DrawInfo, BufferViewInfo, BufferInfo, BufferUsageBit, IndirectBuffer} from '../base/define'; import { ccwindow } from '@base/global'; import { ImageData } from 'pal/image'; @@ -202,339 +201,157 @@ WEBGPU && promiseForWebGPUInstantiation.then(() => { oldDeviceCopyBuffersToTexture.call(this, buffers, texture, regions); } - function seperateCombinedSamplerTexture (shaderSource: string) { - // sampler and texture - const samplerTexturArr = shaderSource.match(/(.*?)\(set = \d+, binding = \d+\) uniform(.*?)sampler\w* \w+;/g); - const count = samplerTexturArr?.length ? samplerTexturArr?.length : 0; - let code = shaderSource; + const SEPARATE_SAMPLER_BINDING_OFFSET = 16; + function seperateCombinedSamplerTexture(shaderSource: string) { + // gather + let samplerReg = /.*?(\(set = \d+, binding = )(\d+)\) uniform[^;]+sampler(\w*) (\w+);/g; + let iter = samplerReg.exec(shaderSource); + // samplerName, samplerType + const referredMap = new Map(); + while (iter) { + const samplerName = iter[4]; + const samplerType = iter[3]; + referredMap.set(samplerName, samplerType); + iter = samplerReg.exec(shaderSource); + } - const referredFuncMap = new Map(); - const samplerSet = new Set(); - samplerTexturArr?.every((str) => { - // `(?<=)` not compatible with str.match on safari. - // let textureName = str.match(/(?<=uniform(.*?)sampler\w* )(\w+)(?=;)/g)!.toString(); - const textureNameRegExpStr = '(?<=uniform(.*?)sampler\\w* )(\\w+)(?=;)'; - let textureName = (new RegExp(textureNameRegExpStr, 'g')).exec(str)![0]; - - let samplerStr = str.replace(textureName, `${textureName}Sampler`); - - // let samplerFunc = samplerStr.match(/(?<=uniform(.*?))sampler(\w*)/g)!.toString(); - const samplerRegExpStr = '(?<=uniform(.*?))sampler(\\w*)'; - let samplerFunc = (new RegExp(samplerRegExpStr, 'g')).exec(str)![0]; - samplerFunc = samplerFunc.replace('sampler', ''); - if (samplerFunc === '') { - textureName = textureName.replace('Sampler', ''); - } else { - const samplerReplaceReg = new RegExp('(?<=uniform(.*?))(sampler\\w*)', 'g'); - samplerStr = samplerStr.replace(samplerReplaceReg, 'sampler'); - - // layout (set = a, binding = b) uniform sampler2D cctex; - // to: - // layout (set = a, binding = b) uniform sampler cctexSampler; - // layout (set = a, binding = b + maxTextureNum) uniform texture2D cctex; - const samplerReg = new RegExp('(?<=binding = )(\\d+)(?=\\))', 'g'); - const samplerBindingStr = samplerReg.exec(str)![0]; - const samplerBinding = Number(samplerBindingStr) + 16; - samplerStr = samplerStr.replace(samplerReg, samplerBinding.toString()); - - const textureReg = new RegExp('(?<=uniform(.*?))(sampler)(?=\\w*)', 'g'); - const textureStr = str.replace(textureReg, 'texture'); - code = code.replace(str, `${textureStr}\n${samplerStr}`); - } + // replaceAll --> es 2021 required + let code = shaderSource; + // referredMap.forEach((value, key)=> { + // const samplerName = key; + // const samplerType = value; + // const exp = new RegExp(`\\b${samplerName}\\b([^;])`); + // let it = exp.exec(code); + // while (it) { + // code = code.replace(exp, `sampler${samplerType}(_${samplerName}, _${samplerName}_sampler)${it[1]}`); + // it = exp.exec(code); + // } + // }); + let sampReg = /.*?(\(set = \d+, binding = )(\d+)\) uniform[^;]+sampler(\w*) (\w+);/g; + let it = sampReg.exec(code); + while (it) { + code = code.replace(sampReg, `layout$1 $2) uniform texture$3 $4;\nlayout$1 $2 + ${SEPARATE_SAMPLER_BINDING_OFFSET}) uniform sampler $4_sampler;\n`); + it = sampReg.exec(code); + } - if (!samplerSet.has(`${textureName}Sampler`)) { - samplerSet.add(`${textureName}Sampler`); - // gathering referred func - let referredFuncStr = `([\\w]+)[\\s]*\\([0-9a-zA-Z_\\s,]*?sampler${samplerFunc}[^\\)]+\\)[\\s]*{`; - if (samplerFunc === '') { - referredFuncStr = `([\\w]+)[\\s]*\\([0-9a-zA-Z_\\s,]*?sampler([\\S]+)[^\\)]+\\)[\\s]*{`; + const builtinSample = ['texture', 'textureSize', 'texelFetch', 'textureLod']; + const replaceBultin = function (samplerName: string, samplerType: string, target: string) { + builtinSample.forEach((sampleFunc) => { + const builtinSampleReg = new RegExp(`${sampleFunc}\\s*\\(\\s*${samplerName}\\s*,`); + let builtinFuncIter = builtinSampleReg.exec(target); + while (builtinFuncIter) { + target = target.replace(builtinFuncIter[0], `${sampleFunc}(sampler${samplerType}(${samplerName}, ${samplerName}_sampler),`); + builtinFuncIter = builtinSampleReg.exec(target); } - const referredFuncRe = new RegExp(referredFuncStr, 'g'); - let reArr = referredFuncRe.exec(code); - while (reArr) { - // first to see if it's wrapped by #if 0 \n ... \n #ndif - let smpFunc = samplerFunc; - if (smpFunc === '') { - smpFunc = reArr[2]; - } - const searchTarget = code.slice(0, reArr.index); - const defValQueue: { str: string, b: boolean, condition: RegExpExecArray }[] = []; - let searchIndex = 1; - - while (searchIndex > 0) { - let ifIndex = searchTarget.indexOf('#if', searchIndex); - let elseIndex = searchTarget.indexOf('#else', searchIndex); - let endIndex = searchTarget.indexOf('#endif', searchIndex); - - if (ifIndex === -1 && elseIndex === -1 && endIndex === -1) { - break; - } - - if (ifIndex === -1) ifIndex = Number.MAX_SAFE_INTEGER; - if (elseIndex === -1) elseIndex = Number.MAX_SAFE_INTEGER; - if (endIndex === -1) endIndex = Number.MAX_SAFE_INTEGER; - - // next start with '#' is #if(def) - if (ifIndex < elseIndex && ifIndex < endIndex) { - const ifdef = (new RegExp(`#if(def)?[\\s]+(!)?([\\S]+)([\\s+]?(==)?(!=)[\\s+]?([\\S]+))?`, 'gm')).exec(searchTarget.slice(ifIndex))!; - defValQueue[defValQueue.length] = { str: ifdef[3], b: true, condition: ifdef }; - searchIndex = ifIndex + 1; - continue; - } - - if (elseIndex < endIndex && elseIndex < ifIndex) { - defValQueue[defValQueue.length - 1].b = false; - searchIndex = elseIndex + 1; - continue; - } + }); + return target; + } - if (endIndex < elseIndex && endIndex < ifIndex) { - defValQueue.pop(); - searchIndex = endIndex + 1; - continue; - } + let funcReg = /\s([\S]+)\s*\(([\w\s,]+)\)[\s|\\|n]*{/g; + let funcIter = funcReg.exec(code); + const funcSet = new Set(); + const paramTypeMap = new Map(); + while (funcIter) { + paramTypeMap.clear(); + + const params = funcIter[2]; + let paramsRes = params.slice(); + if (params.includes('sampler')) { + const paramIndexSet = new Set(); + const paramArr = params.split(','); + + for (let i = 0; i < paramArr.length; ++i) { + const paramDecl = paramArr[i].split(' '); + // assert(paramDecl.length >= 2) + const typeDecl = paramDecl[paramDecl.length - 2]; + if (typeDecl.includes('sampler') && typeDecl !== 'sampler') { + const samplerType = typeDecl.replace('sampler', ''); + const paramName = paramDecl[paramDecl.length - 1]; + paramsRes = paramsRes.replace(paramArr[i], ` texture${samplerType} ${paramName}, sampler ${paramName}_sampler`); + paramIndexSet.add(i); + paramTypeMap.set(paramName, samplerType); } - - let defCheck = true; - for (let i = 0; i < defValQueue.length; i++) { - const ifdef = defValQueue[i].condition; - let evalRes = false; - if (ifdef[1]) { - evalRes = !!(new RegExp(`#define[\\s]+${ifdef[3]}`, 'gm')).exec(searchTarget); - } else { - const defVal = (new RegExp(`#define[\\s]+${ifdef[3]}[\\s]+([\\S]+).*`, 'gm')).exec(searchTarget)![1]; - if (ifdef[4]) { - const conditionVal = ifdef[7]; - evalRes = ifdef[5] ? defVal === conditionVal : defVal !== conditionVal; - } else if (ifdef[3]) { - evalRes = (defVal !== '0' && !ifdef[2]); + } + // let singleParamReg = new RegExp(`(\\W?)(\\w+)\\s+\\b([^,)]+)\\b`); + + code = code.replace(params, paramsRes); + + const funcName = funcIter[1]; + // function may overload + if (!funcSet.has(funcName)) { + // const samplerTypePrefix = '1D|2D|3D|Cube|Buffer'; + const funcSamplerReg = new RegExp(`${funcName}\\s*?\\((\\s*[^;\\{]+)`, 'g'); + const matches = code.matchAll(funcSamplerReg); + for (let matched of matches) { + if (!matched[1].match(/\b\w+\b\s*\b\w+\b/g)) { + const stripStr = matched[1][matched[1].length - 1] === ')' ? matched[1].slice(0, -1) : matched[1]; + let params = stripStr.split(','); + let queued = 0; // '(' + let paramIndex = 0; + for (let i = 0; i < params.length; ++i) { + if (params[i].includes('(')) { + ++queued; + } + if (params[i].includes(')')) { + --queued; + } + + if (!queued || i === params.length - 1) { + if (paramIndexSet.has(paramIndex)) { + params[i] += `, ${params[i]}_sampler`; + } + ++paramIndex; + } } + const newParams = params.join(','); + const newInvokeStr = matched[0].replace(stripStr, newParams); + code = code.replace(matched[0], newInvokeStr); } - - if (defValQueue[i].b !== evalRes) { - defCheck = false; - break; - } + // else: function declare } - - const key = `${reArr[1]}_${smpFunc}_${textureName}Sampler`; - if (!referredFuncMap.has(key) && defCheck) { - referredFuncMap.set(key, [reArr[1], smpFunc, `${textureName}Sampler`]); - } - reArr = referredFuncRe.exec(code); } - } - // cctex in main() called directly - // .*?texture\( - const regStr = `texture\\(\\b(${textureName})\\b`; - const re = new RegExp(regStr); - let reArr = re.exec(code); - while (reArr) { - code = code.replace(re, `texture(sampler${samplerFunc}(${textureName},${textureName}Sampler)`); - reArr = re.exec(code); - } - return true; - }); - - const functionTemplates = new Map(); - const functionDeps = new Map(); - let forwardDecls = ''; - // function - referredFuncMap.forEach((pair) => { - //pre: if existed, replace - //getVec3DisplacementFromTexture\(cc_TangentDisplacements[^\\n]+ - const textureStr = pair[2].slice(0, -7); - const codePieceStr = `${pair[0]}\\((.*?)${textureStr}[^\\n]+`; - const codePieceRe = new RegExp(codePieceStr); - let res = codePieceRe.exec(code); - while (res) { - let replaceStr = res[0].replace(`${pair[0]}(`, `${pair[0]}_${pair[1]}_${pair[2]}_specialized(`); - replaceStr = replaceStr.replace(`${textureStr},`, ''); - code = code.replace(codePieceRe, replaceStr); - res = codePieceRe.exec(code); - } - - // 1. fn definition - const fnDeclReStr = `[\\n|\\W][\\w]+[\\W]+${pair[0]}[\\s]*\\((.*?)sampler${pair[1]}[^{]+`; - const fnDeclRe = new RegExp(fnDeclReStr); - const fnDecl = fnDeclRe.exec(code); - - let redefFunc = ''; - if (!functionTemplates.has(`${pair[0]}_${pair[1]}`)) { - const funcBodyStart = code.slice(fnDecl!.index); - - const funcRedefine = (funcStr: string) => { - const samplerType = `sampler${pair[1]}`; - const textureRe = (new RegExp(`.*?${samplerType}[\\s]+([\\S]+)[,\\)]`)).exec(funcStr)!; - const textureName = textureRe[1]; - const paramReStr = `${samplerType}[\\s]+${textureName}`; - let funcDef = funcStr.replace(new RegExp(paramReStr), `texture${pair[1]} ${textureName}`); - funcDef = funcDef.replace(pair[0], `${pair[0]}SAMPLER_SPEC`); - // 2. texture(...) inside, builtin funcs - const textureOpArr = ['texture', 'textureSize', 'texelFetch', 'textureLod']; - for (let i = 0; i < textureOpArr.length; i++) { - const texFuncReStr = `(${textureOpArr[i]})\\(${textureName},`; - const texFuncRe = new RegExp(texFuncReStr, 'g'); - funcDef = funcDef.replace(texFuncRe, `$1(${samplerType}(${textureName}TEXTURE_HOLDER, ${textureName}SAMPLER_HOLDER),`); - } - return funcDef; - }; - - const firstIfStatement = funcBodyStart.indexOf('#if'); - const firstElseStatement = funcBodyStart.indexOf('#e'); //#endif, #else, #elif maybe? - if ((firstElseStatement !== -1 && firstIfStatement > firstElseStatement) || (firstElseStatement === -1 && firstElseStatement !== -1)) { // ooops, now func body starts in a #if statement. - let startIndex = 0; - let count = 1; // already in #if - while (count > 0 && startIndex < funcBodyStart.length) { - const nextSymbolIdx = funcBodyStart.indexOf('#', startIndex); - const startSliceIdx = startIndex === 0 ? startIndex : startIndex - 1; - if (funcBodyStart[nextSymbolIdx + 1] === 'i') { // #if - count++; - redefFunc += funcBodyStart.slice(startSliceIdx, nextSymbolIdx); - } else if (funcBodyStart[nextSymbolIdx + 1] === 'e' && funcBodyStart[nextSymbolIdx + 2] === 'l') { //#elif, #else - if (count === 1) { - const tempFuncStr = funcBodyStart.slice(startSliceIdx, nextSymbolIdx - 1); - const funcDefStr = funcRedefine(tempFuncStr); - redefFunc += `\n${funcDefStr}`; - } else { - redefFunc += `\n${funcBodyStart.slice(startSliceIdx, nextSymbolIdx)}`; - } - } else if (funcBodyStart[nextSymbolIdx + 1] === 'e' && funcBodyStart[nextSymbolIdx + 2] === 'n') { //#endif - count--; - if (count === 0) { - const tempFuncStr = funcBodyStart.slice(startSliceIdx, nextSymbolIdx - 1); - const funcDefStr = funcRedefine(tempFuncStr); - redefFunc += `\n${funcDefStr}`; - } else { - redefFunc += `\n${funcBodyStart.slice(startSliceIdx, nextSymbolIdx)}`; - } - } else { // #define, dont care - redefFunc += funcBodyStart.slice(startSliceIdx, nextSymbolIdx); - } - startIndex = nextSymbolIdx + 1; + let count = 1; + let startIndex = code.indexOf(funcIter[1], funcIter.index); + startIndex = code.indexOf('{', startIndex) + 1; + let endIndex = 0; + while (count) { + if (code.at(startIndex) === '{') { + ++count; + } else if (code.at(startIndex) === '}') { + --count; } - //`(?:.(?!layout))+${pair[2]};` - const searchTarget = code.slice(0, fnDecl!.index); - const res = (new RegExp(`#if.+[\\s]*$`)).exec(searchTarget); - redefFunc = `${res![0]}${redefFunc}\n#endif`; - } else { - let count = 0; - let matchBegin = false; - let startIndex = 0; - let endIndex = 0; - for (let i = 0; i < funcBodyStart.length; ++i) { - if (funcBodyStart[i] === '{') { - ++count; - if (!matchBegin) { - matchBegin = true; - startIndex = i; - } - } else if (funcBodyStart[i] === '}') { - --count; - } - - if (matchBegin && count === 0) { - endIndex = i; - break; - } + if (count === 0) { + endIndex = startIndex; + break; } - const rawFunc = `${fnDecl![0]}${funcBodyStart.slice(startIndex, endIndex + 1)}`; - redefFunc = funcRedefine(rawFunc); - } - - functionTemplates.set(`${pair[0]}_${pair[1]}`, redefFunc); - } else { - redefFunc = functionTemplates.get(`${pair[0]}_${pair[1]}`)!; - } - const depsFuncs: string[] = []; - - const iterator = referredFuncMap.values(); - let val = iterator.next().value; - while (val) { - const funcDepReStr = `\\b(${val[0] as string})\\b`; - if (redefFunc.search(funcDepReStr) !== -1) { - depsFuncs[depsFuncs.length] = val[0]; + const nextLeft = code.indexOf('{', startIndex + 1); + const nextRight = code.indexOf('}', startIndex + 1); + startIndex = nextLeft === -1 ? nextRight : Math.min(nextLeft, nextRight); } - val = iterator.next().value; - } - // for (let i = 0; i < referredFuncMap.values.length; ++i) { - // const funcDepReStr = `\\b(${referredFuncMap.values[i].fnName as string})\\b`; - // if (redefFunc.search(funcDepReStr) !== -1) { - // depsFuncs[depsFuncs.length] = referredFuncMap.values[i].fnName; - // } - // } - functionDeps.set(`${pair[0]}_${pair[1]}`, depsFuncs); - - const specializedFuncs = new Map(); - const specialize = (funcs: string[]) => { - funcs.every((str) => { - if (!specializedFuncs.has(`${pair[0]}_${pair[2]}_specialized`)) { - const samplerReStr = `(\\w+)SAMPLER_HOLDER`; - const textureName = pair[2].slice(0, pair[2].length - 7); // xxxxSampler - const textureStr = `(\\w+)TEXTURE_HOLDER`; - let funcTemplate = functionTemplates.get(str); - funcTemplate = funcTemplate!.replace(new RegExp(samplerReStr, 'g'), pair[2]); - funcTemplate = funcTemplate.replace(new RegExp(textureStr, 'g'), textureName); - funcTemplate = funcTemplate.replace(new RegExp('SAMPLER_SPEC', 'g'), `_${pair[1]}_${pair[2]}_specialized`); - funcTemplate = funcTemplate.replace(new RegExp(`texture${pair[1]}\\s+\\w+,`, 'g'), ''); - // funcTemplate = funcTemplate.replace('SAMPLER_SPEC', `_${pair[2]}_specialized`); - - for (let i = 0; i < depsFuncs.length; ++i) { - const depFuncStr = `${depsFuncs[i]}([\\W]?)*\\([^,)]+(,)?`; - funcTemplate = funcTemplate.replace(new RegExp(depFuncStr, 'g'), `${depsFuncs[i]}_${pair[1]}_${pair[2]}_specialized(`); - } - - let declStr = fnDecl![0].replace(pair[0], `${str}_${pair[2]}_specialized`); - declStr = declStr.replace(new RegExp(`sampler${pair[1]}[^,)]+(,)?`, 'g'), ``); - declStr += `;`; - specializedFuncs.set(declStr, funcTemplate); - } else { - return specialize(functionDeps.get(str)!); - } - return true; + const funcBody = code.slice(funcIter.index, endIndex); + let newFunc = funcBody; + paramTypeMap.forEach((type, name) => { + newFunc = replaceBultin(name, type, newFunc); }); - return true; - }; - specialize([`${pair[0]}_${pair[1]}`]); - //(?:.(?!layout))+cc_PositionDisplacementsSampler; - const samplerDefReStr = `(?:.(?!layout))+${pair[2]};`; - const samplerDef = (new RegExp(samplerDefReStr)).exec(code); - - let funcImpls = ''; - for (const [key, value] of specializedFuncs) { - forwardDecls += `\n${key}\n`; - funcImpls += `\n${value}\n`; + + code = code.replace(funcBody, newFunc); + funcSet.add(funcIter[1]); } + funcIter = funcReg.exec(code); + } - code = code.replace(samplerDef![0], `${samplerDef![0]}\n${funcImpls}`); + referredMap.forEach((type, name) => { + code = replaceBultin(name, type, code); }); - // some function appears before it's defined so forward declaration is needed - forwardDecls += '\nvec3 SRGBToLinear (vec3 gamma);\nfloat getDisplacementWeight(int index);\n'; - - const highpIdx = code.indexOf('precision highp'); - const mediumpIdx = code.indexOf('precision mediump'); - const lowpIdx = code.indexOf('precision lowp'); - /////////////////////////////////////////////////////////// // isNan, isInf has been removed in dawn:tint let functionDefs = ''; const precisionKeyWord = 'highp'; - - // const getPrecision = (idx: number) => { - // if (highpIdx !== -1 && highpIdx < idx) { - // precisionKeyWord = 'highp'; - // } else if (mediumpIdx !== -1 && mediumpIdx < idx && highpIdx < mediumpIdx) { - // precisionKeyWord = 'mediump'; - // } else if (lowpIdx !== -1 && lowpIdx < idx && mediumpIdx < lowpIdx && highpIdx < lowpIdx) { - // precisionKeyWord = 'lowp'; - // } - // }; - const isNanIndex = code.indexOf('isnan'); if (isNanIndex !== -1) { // getPrecision(isNanIndex); @@ -562,31 +379,84 @@ WEBGPU && promiseForWebGPUInstantiation.then(() => { let firstPrecisionIdx = code.indexOf('precision'); firstPrecisionIdx = code.indexOf(';', firstPrecisionIdx); firstPrecisionIdx += 1; - code = `${code.slice(0, firstPrecisionIdx)}\n${forwardDecls}\n${functionDefs}\n${code.slice(firstPrecisionIdx)}`; + code = `${code.slice(0, firstPrecisionIdx)}\n${functionDefs}\n${code.slice(firstPrecisionIdx)}`; return code; } + function reflect(wgsl: string[]) { + const bindingList: number[][] = []; + for (let wgslStr of wgsl) { + // @group(1) @binding(0) var x_78 : Constants; + // @group(1) @binding(1) var albedoMap : texture_2d; + const reg = new RegExp(/@group\((\d)\)\s+@binding\((\d+)\)/g); + let iter = reg.exec(wgslStr); + while (iter) { + const set = +iter[1]; + const binding = +iter[2]; + while (bindingList.length <= set) { + bindingList.push([]); + } + bindingList[set][bindingList[set].length] = binding; + iter = reg.exec(wgslStr); + } + } + return bindingList; + } + + function overwriteBlock(info: ShaderInfo, code: string): string { + const regexp = new RegExp(/layout\(([^\)]+)\)\s+uniform\s+\b(\w+)\b/g); + let src = code; + let iter = regexp.exec(src); + if (iter) { + const blockName = iter[2]; + const block = info.blocks.find((ele) => { return ele.name === blockName; }); + const binding = block?.binding; + const overwriteStr = iter[0].replace(iter[1], `${iter[1]}, set = 0, binding = ${binding}`); + src = src.replace(iter[0], overwriteStr); + iter = regexp.exec(src); + } + return src; + } + const createShader = Device.prototype.createShader; Device.prototype.createShader = function (shaderInfo: ShaderInfo) { - const spvDatas: any = []; + const wgslStages: string[] = []; for (let i = 0; i < shaderInfo.stages.length; ++i) { - shaderInfo.stages[i].source = seperateCombinedSamplerTexture(shaderInfo.stages[i].source); + let glslSource = seperateCombinedSamplerTexture(shaderInfo.stages[i].source); const stageStr = shaderInfo.stages[i].stage === ShaderStageFlagBit.VERTEX ? 'vertex' : shaderInfo.stages[i].stage === ShaderStageFlagBit.FRAGMENT ? 'fragment' : 'compute'; - const sourceCode = `#version 450\n${shaderInfo.stages[i].source}`; - const spv = glslalgWasmModule.glslang.compileGLSL(sourceCode, stageStr, true, '1.3'); - spvDatas.push(spv); + // if (stageStr === 'compute') { + // glslSource = overwriteBlock(shaderInfo, glslSource); + // } + const sourceCode = `#version 450\n#define CC_USE_WGPU 1\n${glslSource}`; + const spv = glslangWasmModule.glslang.compileGLSL(sourceCode, stageStr, false, '1.3'); + + const twgsl = twgslModule.twgsl; + const wgsl = twgsl.convertSpirV2WGSL(spv); + if (wgsl === '') { + console.error("empty wgsl"); + } + shaderInfo.stages[i].source = wgsl; + wgslStages.push(wgsl); } - const shader = this.createShaderNative(shaderInfo, spvDatas); + const shader = this.createShaderNative(shaderInfo); + // optioanl : reflect bindings in shader + { + const bindingList = reflect(wgslStages); + for (let bindings of bindingList) { + const u8Array = new Uint8Array(bindings); + shader.reflectBinding(u8Array); + } + } return shader; }; // if property being frequently get in TS, try cache it // attention: invalid if got this object from a native object, // eg. inputAssembler.indexBuffer.objectID - function cacheReadOnlyWGPUProperties (type: T, props: string[]) { + function cacheReadOnlyWGPUProperties(type: T, props: string[]) { const descriptor = { writable: true }; props.map((prop) => { return Object.defineProperty(type['prototype'], `_${prop}`, descriptor); @@ -600,7 +470,7 @@ WEBGPU && promiseForWebGPUInstantiation.then(() => { for (let prop of props) { res[`_${prop}`] = res[`${prop}`]; Object.defineProperty(res, `${prop}`, { - get () { + get() { return this[`_${prop}`]; } }); diff --git a/cocos/input/input.ts b/cocos/input/input.ts index ba1d447eefa..8765ef0d188 100644 --- a/cocos/input/input.ts +++ b/cocos/input/input.ts @@ -25,9 +25,9 @@ */ import { EDITOR_NOT_IN_PREVIEW, NATIVE } from 'internal:constants'; -import { TouchInputSource, MouseInputSource, KeyboardInputSource, AccelerometerInputSource, GamepadInputDevice, HandleInputDevice, HMDInputDevice, HandheldInputDevice } from 'pal/input'; +import { AccelerometerInputSource, GamepadInputDevice, HMDInputDevice, HandheldInputDevice, HandleInputDevice, KeyboardInputSource, MouseInputSource, TouchInputSource } from 'pal/input'; import { touchManager } from '../../pal/input/touch-manager'; -import { sys, EventTarget } from '../core'; +import { EventTarget, sys } from '../core'; import { error } from '@base/debug'; import { Event, EventAcceleration, EventGamepad, EventHandle, EventHandheld, EventHMD, EventKeyboard, EventMouse, EventTouch, Touch } from './types'; import { InputEventType } from './types/event-enum'; @@ -63,7 +63,7 @@ class InputEventDispatcher implements IEventDispatcher { } } -const pointerEventTypeMap: Record = { +const pointerEventTypeMap: Record = { [InputEventType.MOUSE_DOWN]: InputEventType.TOUCH_START, [InputEventType.MOUSE_MOVE]: InputEventType.TOUCH_MOVE, [InputEventType.MOUSE_UP]: InputEventType.TOUCH_END, @@ -161,28 +161,40 @@ export class Input { /** * This should be a private method, but it's exposed for Editor Only. */ - private _dispatchMouseDownEvent (nativeMouseEvent: any): void { this._mouseInput.dispatchMouseDownEvent?.(nativeMouseEvent); } + private _dispatchMouseDownEvent (nativeMouseEvent: any): void { + this._mouseInput.dispatchMouseDownEvent?.(nativeMouseEvent); + } /** * This should be a private method, but it's exposed for Editor Only. */ - private _dispatchMouseMoveEvent (nativeMouseEvent: any): void { this._mouseInput.dispatchMouseMoveEvent?.(nativeMouseEvent); } + private _dispatchMouseMoveEvent (nativeMouseEvent: any): void { + this._mouseInput.dispatchMouseMoveEvent?.(nativeMouseEvent); + } /** * This should be a private method, but it's exposed for Editor Only. */ - private _dispatchMouseUpEvent (nativeMouseEvent: any): void { this._mouseInput.dispatchMouseUpEvent?.(nativeMouseEvent); } + private _dispatchMouseUpEvent (nativeMouseEvent: any): void { + this._mouseInput.dispatchMouseUpEvent?.(nativeMouseEvent); + } /** * This should be a private method, but it's exposed for Editor Only. */ - private _dispatchMouseScrollEvent (nativeMouseEvent: any): void { this._mouseInput.dispatchScrollEvent?.(nativeMouseEvent); } + private _dispatchMouseScrollEvent (nativeMouseEvent: any): void { + this._mouseInput.dispatchScrollEvent?.(nativeMouseEvent); + } /** * This should be a private method, but it's exposed for Editor Only. */ - private _dispatchKeyboardDownEvent (nativeKeyboardEvent: any): void { this._keyboardInput.dispatchKeyboardDownEvent?.(nativeKeyboardEvent); } + private _dispatchKeyboardDownEvent (nativeKeyboardEvent: any): void { + this._keyboardInput.dispatchKeyboardDownEvent?.(nativeKeyboardEvent); + } /** * This should be a private method, but it's exposed for Editor Only. */ - private _dispatchKeyboardUpEvent (nativeKeyboardEvent: any): void { this._keyboardInput.dispatchKeyboardUpEvent?.(nativeKeyboardEvent); } + private _dispatchKeyboardUpEvent (nativeKeyboardEvent: any): void { + this._keyboardInput.dispatchKeyboardUpEvent?.(nativeKeyboardEvent); + } /** * @en @@ -230,6 +242,37 @@ export class Input { } this._eventTarget.off(eventType, callback, target); } + + /** + * @en + * Get touch object by touch ID. + * @zh + * 通过 touch ID 获取 touch对象。 + */ + public getTouch (touchID: number): Readonly | undefined { + return touchManager.getTouch(touchID); + } + + /** + * @en + * Get all the current touches objects as array. + * @zh + * 获取当前 所有touch对象 的数组。 + */ + public getAllTouches (): Touch[] { + return touchManager.getAllTouches(); + } + + /** + * @en + * Get the number of touches. + * @zh + * 获取当前 touch 对象的数量。 + */ + public getTouchCount (): number { + return touchManager.getTouchCount(); + } + /** * @en * Sets whether to enable the accelerometer event listener or not. @@ -265,7 +308,7 @@ export class Input { private _simulateEventTouch (eventMouse: EventMouse): void { const eventType = pointerEventTypeMap[eventMouse.type]; const touchID = 0; - const touch = touchManager.getTouch(touchID, eventMouse.getLocationX(), eventMouse.getLocationY()); + const touch = touchManager.getOrCreateTouch(touchID, eventMouse.getLocationX(), eventMouse.getLocationY()); if (!touch) { return; } @@ -305,10 +348,18 @@ export class Input { private _registerEvent (): void { if (sys.hasFeature(sys.Feature.INPUT_TOUCH)) { const eventTouchList = this._eventTouchList; - this._touchInput.on(InputEventType.TOUCH_START, (event): void => { this._dispatchOrPushEventTouch(event, eventTouchList); }); - this._touchInput.on(InputEventType.TOUCH_MOVE, (event): void => { this._dispatchOrPushEventTouch(event, eventTouchList); }); - this._touchInput.on(InputEventType.TOUCH_END, (event): void => { this._dispatchOrPushEventTouch(event, eventTouchList); }); - this._touchInput.on(InputEventType.TOUCH_CANCEL, (event): void => { this._dispatchOrPushEventTouch(event, eventTouchList); }); + this._touchInput.on(InputEventType.TOUCH_START, (event): void => { + this._dispatchOrPushEventTouch(event, eventTouchList); + }); + this._touchInput.on(InputEventType.TOUCH_MOVE, (event): void => { + this._dispatchOrPushEventTouch(event, eventTouchList); + }); + this._touchInput.on(InputEventType.TOUCH_END, (event): void => { + this._dispatchOrPushEventTouch(event, eventTouchList); + }); + this._touchInput.on(InputEventType.TOUCH_CANCEL, (event): void => { + this._dispatchOrPushEventTouch(event, eventTouchList); + }); } if (sys.hasFeature(sys.Feature.EVENT_MOUSE)) { @@ -329,42 +380,66 @@ export class Input { this._simulateEventTouch(event); this._dispatchOrPushEvent(event, eventMouseList); }); - this._mouseInput.on(InputEventType.MOUSE_WHEEL, (event): void => { this._dispatchOrPushEvent(event, eventMouseList); }); + this._mouseInput.on(InputEventType.MOUSE_WHEEL, (event): void => { + this._dispatchOrPushEvent(event, eventMouseList); + }); } if (sys.hasFeature(sys.Feature.EVENT_KEYBOARD)) { const eventKeyboardList = this._eventKeyboardList; - this._keyboardInput.on(InputEventType.KEY_DOWN, (event): void => { this._dispatchOrPushEvent(event, eventKeyboardList); }); - this._keyboardInput.on(InputEventType.KEY_PRESSING, (event): void => { this._dispatchOrPushEvent(event, eventKeyboardList); }); - this._keyboardInput.on(InputEventType.KEY_UP, (event): void => { this._dispatchOrPushEvent(event, eventKeyboardList); }); + this._keyboardInput.on(InputEventType.KEY_DOWN, (event): void => { + this._dispatchOrPushEvent(event, eventKeyboardList); + }); + this._keyboardInput.on(InputEventType.KEY_PRESSING, (event): void => { + this._dispatchOrPushEvent(event, eventKeyboardList); + }); + this._keyboardInput.on(InputEventType.KEY_UP, (event): void => { + this._dispatchOrPushEvent(event, eventKeyboardList); + }); } if (sys.hasFeature(sys.Feature.EVENT_ACCELEROMETER)) { const eventAccelerationList = this._eventAccelerationList; - this._accelerometerInput.on(InputEventType.DEVICEMOTION, (event): void => { this._dispatchOrPushEvent(event, eventAccelerationList); }); + this._accelerometerInput.on(InputEventType.DEVICEMOTION, (event): void => { + this._dispatchOrPushEvent(event, eventAccelerationList); + }); } if (sys.hasFeature(sys.Feature.EVENT_GAMEPAD)) { const eventGamepadList = this._eventGamepadList; - GamepadInputDevice._on(InputEventType.GAMEPAD_CHANGE, (event): void => { this._dispatchOrPushEvent(event, eventGamepadList); }); - GamepadInputDevice._on(InputEventType.GAMEPAD_INPUT, (event): void => { this._dispatchOrPushEvent(event, eventGamepadList); }); - GamepadInputDevice._on(InputEventType.HANDLE_POSE_INPUT, (event): void => { this._dispatchOrPushEvent(event, eventGamepadList); }); + GamepadInputDevice._on(InputEventType.GAMEPAD_CHANGE, (event): void => { + this._dispatchOrPushEvent(event, eventGamepadList); + }); + GamepadInputDevice._on(InputEventType.GAMEPAD_INPUT, (event): void => { + this._dispatchOrPushEvent(event, eventGamepadList); + }); + GamepadInputDevice._on(InputEventType.HANDLE_POSE_INPUT, (event): void => { + this._dispatchOrPushEvent(event, eventGamepadList); + }); } if (sys.hasFeature(sys.Feature.EVENT_HANDLE)) { const eventHandleList = this._eventHandleList; - this._handleInput._on(InputEventType.HANDLE_INPUT, (event): void => { this._dispatchOrPushEvent(event, eventHandleList); }); - this._handleInput._on(InputEventType.HANDLE_POSE_INPUT, (event): void => { this._dispatchOrPushEvent(event, eventHandleList); }); + this._handleInput._on(InputEventType.HANDLE_INPUT, (event): void => { + this._dispatchOrPushEvent(event, eventHandleList); + }); + this._handleInput._on(InputEventType.HANDLE_POSE_INPUT, (event): void => { + this._dispatchOrPushEvent(event, eventHandleList); + }); } if (sys.hasFeature(sys.Feature.EVENT_HMD)) { const eventHMDList = this._eventHMDList; - this._hmdInput._on(InputEventType.HMD_POSE_INPUT, (event): void => { this._dispatchOrPushEvent(event, eventHMDList); }); + this._hmdInput._on(InputEventType.HMD_POSE_INPUT, (event): void => { + this._dispatchOrPushEvent(event, eventHMDList); + }); } if (sys.hasFeature(sys.Feature.EVENT_HANDHELD)) { const eventHandheldList = this._eventHandheldList; - this._handheldInput._on(InputEventType.HANDHELD_POSE_INPUT, (event): void => { this._dispatchOrPushEvent(event, eventHandheldList); }); + this._handheldInput._on(InputEventType.HANDHELD_POSE_INPUT, (event): void => { + this._dispatchOrPushEvent(event, eventHandheldList); + }); } } diff --git a/cocos/input/types/event-enum.ts b/cocos/input/types/event-enum.ts index f341570a8a2..aa0c8424e5d 100644 --- a/cocos/input/types/event-enum.ts +++ b/cocos/input/types/event-enum.ts @@ -265,7 +265,7 @@ export enum SystemEventType { * @en The event type for node's sibling order changed. * @zh 当节点在兄弟节点中的顺序发生变化时触发的事件。 * - * @deprecated since v3.3, please use Node.EventType.SIBLING_ORDER_CHANGED instead + * @deprecated since v3.3, please use Node.EventType.CHILDREN_ORDER_CHANGED instead */ SIBLING_ORDER_CHANGED = 'sibling-order-changed', } diff --git a/cocos/input/types/touch.ts b/cocos/input/types/touch.ts index c105170a8e4..e8f15baf247 100644 --- a/cocos/input/types/touch.ts +++ b/cocos/input/types/touch.ts @@ -200,7 +200,7 @@ export class Touch { _vec2.set(this._point); _vec2.subtract(this._prevPoint); - out.set(cclegacy.view.getScaleX(), cclegacy.view.getScaleY()); + out.set(cclegacy.view.getScaleX() as number, cclegacy.view.getScaleY() as number); Vec2.divide(out, _vec2, out); return out; } @@ -262,7 +262,7 @@ export class Touch { * @param x - x position of the touch point * @param y - y position of the touch point */ - public setTouchInfo (id = 0, x?: number, y?: number): void { + public setTouchInfo (id: number = 0, x: number = 0, y: number = 0): void { this._prevPoint = this._point; this._point = new Vec2(x || 0, y || 0); this._id = id; @@ -322,6 +322,21 @@ export class Touch { } this._lastModified = cclegacy.game.frameStartTime; } + + /** + * @zh Touch 对象的原始数据不应该被修改。如果你需要这么做,最好克隆一个新的对象。 + * @en The original Touch object shouldn't be modified. If you need to, it's better to clone a new one. + */ + public clone (): Touch { + const touchID = this.getID(); + this.getStartLocation(_vec2); + const clonedTouch = new Touch(_vec2.x, _vec2.y, touchID); + this.getLocation(_vec2); + clonedTouch.setPoint(_vec2.x, _vec2.y); + this.getPreviousLocation(_vec2); + clonedTouch.setPrevPoint(_vec2); + return clonedTouch; + } } cclegacy.Touch = Touch; diff --git a/cocos/misc/camera-component.ts b/cocos/misc/camera-component.ts index 6b85003f166..5c44200dd72 100644 --- a/cocos/misc/camera-component.ts +++ b/cocos/misc/camera-component.ts @@ -313,7 +313,7 @@ export class Camera extends Component { */ @type(FOVAxis) @displayOrder(7) - @visible(function (this: Camera): boolean { + @visible(function visible (this: Camera): boolean { return this._projection === ProjectionType.PERSPECTIVE; }) @tooltip('i18n:camera.fov_axis') @@ -354,10 +354,10 @@ export class Camera extends Component { * @zh 正交模式下的相机视角高度。 */ @displayOrder(9) - @visible(function (this: Camera): boolean { + @visible(function visible (this: Camera): boolean { return this._projection === ProjectionType.ORTHO; }) - @rangeMin(1) + @rangeMin(1e-6) @tooltip('i18n:camera.ortho_height') get orthoHeight (): number { return this._orthoHeight; diff --git a/cocos/physics-2d/box2d-wasm/instantiated.ts b/cocos/physics-2d/box2d-wasm/instantiated.ts index a529a3d21f7..214f2b59f2a 100644 --- a/cocos/physics-2d/box2d-wasm/instantiated.ts +++ b/cocos/physics-2d/box2d-wasm/instantiated.ts @@ -36,7 +36,8 @@ export const B2 = {} as any; export function getImplPtr (wasmObject: any): number { // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return (wasmObject).$$.ptr; + if (!wasmObject) return 0; + return (wasmObject).$$.ptr as number; } /** diff --git a/cocos/physics-2d/box2d-wasm/joints/joint-2d.ts b/cocos/physics-2d/box2d-wasm/joints/joint-2d.ts index c8cedd68a62..7fdaf9541a1 100644 --- a/cocos/physics-2d/box2d-wasm/joints/joint-2d.ts +++ b/cocos/physics-2d/box2d-wasm/joints/joint-2d.ts @@ -22,7 +22,7 @@ THE SOFTWARE. */ -import { B2, addImplPtrReference, addImplPtrReferenceWASM, getImplPtr } from '../instantiated'; +import { B2, addImplPtrReference, addImplPtrReferenceWASM, getImplPtr, removeImplPtrReference, removeImplPtrReferenceWASM } from '../instantiated'; import { IJoint2D } from '../../spec/i-physics-joint'; import { Joint2D, PhysicsSystem2D, RigidBody2D } from '../../framework'; import { B2PhysicsWorld } from '../physics-world'; @@ -55,7 +55,7 @@ export class B2Joint implements IJoint2D { } onDisable (): void { - PhysicsSystem2D.instance._callAfterStep(this, this._destroy); + PhysicsSystem2D.instance._callAfterStep(this, this.destroy); } // need init after body and connected body init @@ -107,9 +107,11 @@ export class B2Joint implements IJoint2D { this._inited = true; } - _destroy (): void { + destroy (): void { if (!this._inited) return; + removeImplPtrReference(getImplPtr(this._b2joint)); + removeImplPtrReferenceWASM(getImplPtr(this._b2joint)); (PhysicsSystem2D.instance.physicsWorld as B2PhysicsWorld).impl.DestroyJoint(this._b2joint!); this._b2joint = null; diff --git a/cocos/physics-2d/box2d-wasm/joints/mouse-joint.ts b/cocos/physics-2d/box2d-wasm/joints/mouse-joint.ts index f5d359056da..51f73a9cfd2 100644 --- a/cocos/physics-2d/box2d-wasm/joints/mouse-joint.ts +++ b/cocos/physics-2d/box2d-wasm/joints/mouse-joint.ts @@ -118,7 +118,7 @@ export class B2MouseJoint extends B2Joint implements IMouseJoint { } onTouchEnd (event: Touch): void { - this._destroy(); + this.destroy(); this._isTouched = false; } diff --git a/cocos/physics-2d/box2d-wasm/rigid-body.ts b/cocos/physics-2d/box2d-wasm/rigid-body.ts index feb13037e4c..a92e0533863 100644 --- a/cocos/physics-2d/box2d-wasm/rigid-body.ts +++ b/cocos/physics-2d/box2d-wasm/rigid-body.ts @@ -22,7 +22,7 @@ THE SOFTWARE. */ -import { B2 } from './instantiated'; +import { B2, getTSObjectFromWASMObject, getTSObjectFromWASMObjectPtr } from './instantiated'; import { IRigidBody2D } from '../spec/i-rigid-body'; import { RigidBody2D } from '../framework/components/rigid-body-2d'; import { PhysicsSystem2D } from '../framework/physics-system'; @@ -31,8 +31,10 @@ import { Vec2, toRadian, Vec3, Quat, IVec2Like, toDegree, TWO_PI, HALF_PI } from import { PHYSICS_2D_PTM_RATIO, ERigidBody2DType } from '../framework/physics-types'; import { Node } from '../../scene-graph/node'; -import { Collider2D } from '../framework'; +import { Collider2D, Joint2D } from '../framework'; import { NodeEventType } from '../../scene-graph/node-event'; +import { B2Shape2D } from './shapes/shape-2d'; +import { B2Joint } from './joints/joint-2d'; const tempVec3 = new Vec3(); const tempVec2_1 = { x: 0, y: 0 };//new B2.Vec2(0, 0); @@ -114,6 +116,29 @@ export class B2RigidBody2D implements IRigidBody2D { _destroy (): void { if (!this._inited) return; + //collect all fixtures attached to this rigid body and process them + const fixtureList = this.impl?.GetFixtureList(); + if (fixtureList) { + let shapeTSObj = getTSObjectFromWASMObject(fixtureList); + while (shapeTSObj && shapeTSObj.impl) { + shapeTSObj.destroy(); + const nextFixture = fixtureList.GetNext(); + shapeTSObj = getTSObjectFromWASMObject(nextFixture); + } + } + + //collect all joints attached to this rigid body and process them + const jointListPtr = this.impl?.GetJointList(); + if (jointListPtr) { + let jointWASMPtr = B2.JointEdgeGetJoint(jointListPtr) as number; + let jointTSObj = getTSObjectFromWASMObjectPtr(jointWASMPtr); + while (jointTSObj) { + jointTSObj.destroy(); + jointWASMPtr = B2.JointEdgeGetNext(jointListPtr) as number; + jointTSObj = getTSObjectFromWASMObjectPtr(jointWASMPtr); + } + } + (PhysicsSystem2D.instance.physicsWorld as B2PhysicsWorld).removeBody(this); this._inited = false; diff --git a/cocos/physics-2d/box2d-wasm/shapes/shape-2d.ts b/cocos/physics-2d/box2d-wasm/shapes/shape-2d.ts index 5f102e03580..84ab4a5881b 100644 --- a/cocos/physics-2d/box2d-wasm/shapes/shape-2d.ts +++ b/cocos/physics-2d/box2d-wasm/shapes/shape-2d.ts @@ -77,7 +77,7 @@ export class B2Shape2D implements IBaseShape { } onDisable (): void { - PhysicsSystem2D.instance._callAfterStep(this, this._destroy); + PhysicsSystem2D.instance._callAfterStep(this, this.destroy); } start (): void { @@ -92,7 +92,7 @@ export class B2Shape2D implements IBaseShape { } apply (): void { - this._destroy(); + this.destroy(); if (this.collider.enabledInHierarchy) { this._init(); } @@ -200,7 +200,7 @@ export class B2Shape2D implements IBaseShape { this._inited = true; } - _destroy (): void { + destroy (): void { if (!this._inited) return; const fixtures = this._fixtures; diff --git a/cocos/physics/utils/util.ts b/cocos/physics/utils/util.ts index ead93adcf0e..cd2c5c6c661 100644 --- a/cocos/physics/utils/util.ts +++ b/cocos/physics/utils/util.ts @@ -22,7 +22,7 @@ THE SOFTWARE. */ -import { equals, Vec3, IVec3Like } from '../../core'; +import { equals, Vec3, IVec3Like, murmurhash2_32_gc } from '../../core'; import { CharacterController, CharacterTriggerEventType, Collider, CollisionEventType, IContactEquation, TriggerEventType } from '../framework'; export { cylinder } from '../../primitive'; @@ -69,23 +69,21 @@ export const CollisionEventObject = { export function shrinkPositions (buffer: Float32Array | number[]): number[] { const pos: number[] = []; + const posHashMap = {}; if (buffer.length >= 3) { - // eslint-disable-next-line no-unused-expressions - pos[0] = buffer[0], pos[1] = buffer[1], pos[2] = buffer[2]; + pos[0] = buffer[0]; + pos[1] = buffer[1]; + pos[2] = buffer[2]; const len = buffer.length; for (let i = 3; i < len; i += 3) { const p0 = buffer[i]; const p1 = buffer[i + 1]; const p2 = buffer[i + 2]; - const len2 = pos.length; - let isNew = true; - for (let j = 0; j < len2; j += 3) { - if (equals(p0, pos[j]) && equals(p1, pos[j + 1]) && equals(p2, pos[j + 2])) { - isNew = false; - break; - } - } - if (isNew) { + const str = String(p0) + String(p1) + String(p2); + //todo: directly use buffer as input + const hash = murmurhash2_32_gc(str, 666); + if (posHashMap[hash] !== str) { + posHashMap[hash] = str; pos.push(p0); pos.push(p1); pos.push(p2); } } diff --git a/cocos/render-scene/core/pass.ts b/cocos/render-scene/core/pass.ts index 1775dba140b..42ebba62518 100644 --- a/cocos/render-scene/core/pass.ts +++ b/cocos/render-scene/core/pass.ts @@ -34,7 +34,7 @@ import { BufferUsageBit, DynamicStateFlagBit, DynamicStateFlags, Feature, GetTyp import { EffectAsset } from '../../asset/assets/effect-asset'; import { IProgramInfo, programLib } from './program-lib'; import { MacroRecord, MaterialProperty, customizeType, getBindingFromHandle, getDefaultFromType, getStringFromType, getOffsetFromHandle, getTypeFromHandle, type2reader, type2writer, getCountFromHandle, type2validator } from './pass-utils'; -import { RenderPassStage, RenderPriority } from '../../rendering/define'; +import { RenderPassStage, RenderPriority, SetIndex } from '../../rendering/define'; import { InstancedBuffer } from '../../rendering/instanced-buffer'; import { ProgramLibrary } from '../../rendering/custom/private'; @@ -464,10 +464,19 @@ export class Pass { * @zh 重置所有 texture 和 sampler 为初始默认值。 */ public resetTextures (): void { - for (let i = 0; i < this._shaderInfo.samplerTextures.length; i++) { - const u = this._shaderInfo.samplerTextures[i]; - for (let j = 0; j < u.count; j++) { - this.resetTexture(u.name, j); + if (cclegacy.rendering) { + const set = this._shaderInfo.descriptors[SetIndex.MATERIAL]; + for (const combined of set.samplerTextures) { + for (let j = 0; j < combined.count; ++j) { + this.resetTexture(combined.name, j); + } + } + } else { + for (let i = 0; i < this._shaderInfo.samplerTextures.length; i++) { + const u = this._shaderInfo.samplerTextures[i]; + for (let j = 0; j < u.count; j++) { + this.resetTexture(u.name, j); + } } } } diff --git a/cocos/render-scene/scene/submodel.ts b/cocos/render-scene/scene/submodel.ts index 9ad736b19f9..41afabb1996 100644 --- a/cocos/render-scene/scene/submodel.ts +++ b/cocos/render-scene/scene/submodel.ts @@ -31,7 +31,6 @@ import { Mat4 } from '../../core'; import { cclegacy } from '@base/global'; import { getPhaseID } from '../../rendering/pass-phase'; import { Root } from '../../root'; -import { MacroRecord } from '../core/pass-utils'; const _dsInfo = new DescriptorSetInfo(null!); const MAX_PASS_COUNT = 8; @@ -54,7 +53,6 @@ export class SubModel { protected _shaders: Shader[] | null = null; protected _subMesh: RenderingSubMesh | null = null; protected _patches: IMacroPatch[] | null = null; - protected _globalPatches: MacroRecord | null = null; protected _priority: RenderPriority = RenderPriority.DEFAULT; protected _inputAssembler: InputAssembler | null = null; protected _descriptorSet: DescriptorSet | null = null; @@ -295,7 +293,6 @@ export class SubModel { this.priority = RenderPriority.DEFAULT; this._patches = null; - this._globalPatches = null; this._subMesh = null; this._passes = null; @@ -326,19 +323,6 @@ export class SubModel { * @zh 管线更新回调 */ public onPipelineStateChanged (): void { - const root = cclegacy.director.root as Root; - const pipeline = root.pipeline; - const pipelinePatches = Object.entries(pipeline.macros); - if (!this._globalPatches && pipelinePatches.length === 0) { - return; - } else if (pipelinePatches.length) { - if (this._globalPatches && pipelinePatches.length === this._globalPatches.length) { - const globalPatches = Object.entries(this._globalPatches); - const patchesStateUnchanged = JSON.stringify(pipelinePatches.sort()) === JSON.stringify(globalPatches.sort()); - if (patchesStateUnchanged) return; - } - } - this._globalPatches = pipeline.macros; const passes = this._passes; if (!passes) { return; } @@ -362,6 +346,7 @@ export class SubModel { return; } else if (patches) { patches = patches.sort(); + // Sorting on shorter patches outperforms hashing, with negative optimization on longer global patches. if (this._patches && patches.length === this._patches.length) { const patchesStateUnchanged = JSON.stringify(patches) === JSON.stringify(this._patches); if (patchesStateUnchanged) return; diff --git a/cocos/rendering/custom/builtin-pipelines.ts b/cocos/rendering/custom/builtin-pipelines.ts index 1403ee8d602..b1e31a85d11 100644 --- a/cocos/rendering/custom/builtin-pipelines.ts +++ b/cocos/rendering/custom/builtin-pipelines.ts @@ -106,7 +106,7 @@ export class DeferredPipelineBuilder implements PipelineBuilder { // Lighting Pass const lightInfo = setupLightingPass(ppl, info, useCluster); // Deferred ForwardPass, for non-surface-shader material and transparent material - setupDeferredForward(ppl, info, lightInfo.rtName); + setupDeferredForward(ppl, info, lightInfo.rtName, useCluster); // Postprocess setupPostprocessPass(ppl, info, lightInfo.rtName); diff --git a/cocos/rendering/custom/compiler.ts b/cocos/rendering/custom/compiler.ts index 581a4a6bfb1..db4bef3904d 100644 --- a/cocos/rendering/custom/compiler.ts +++ b/cocos/rendering/custom/compiler.ts @@ -28,7 +28,11 @@ import { VectorGraphColorMap } from './effect'; import { DefaultVisitor, depthFirstSearch, ReferenceGraphView } from './graph'; import { LayoutGraphData } from './layout-graph'; import { BasicPipeline } from './pipeline'; -import { Blit, ClearView, ComputePass, ComputeSubpass, CopyPass, Dispatch, FormatView, ManagedBuffer, ManagedResource, ManagedTexture, MovePass, RasterPass, RasterSubpass, RaytracePass, RenderGraph, RenderGraphVisitor, RasterView, ComputeView, RenderQueue, RenderSwapchain, ResolvePass, ResourceGraph, ResourceGraphVisitor, SceneData, SubresourceView } from './render-graph'; +import { + Blit, ClearView, ComputePass, ComputeSubpass, CopyPass, Dispatch, FormatView, ManagedBuffer, ManagedResource, ManagedTexture, MovePass, + RasterPass, RasterSubpass, RaytracePass, RenderGraph, RenderGraphVisitor, RasterView, ComputeView, + RenderQueue, RenderSwapchain, ResolvePass, ResourceGraph, ResourceGraphVisitor, SceneData, SubresourceView, PersistentBuffer, PersistentTexture, +} from './render-graph'; import { AccessType, ResourceResidency, SceneFlags } from './types'; import { hashCombineNum, hashCombineStr } from './define'; @@ -81,10 +85,11 @@ class PassVisitor implements RenderGraphVisitor { public queueID = 0xFFFFFFFF; public sceneID = 0xFFFFFFFF; public passID = 0xFFFFFFFF; + public dispatchID = 0xFFFFFFFF; // output resourcetexture id public resID = 0xFFFFFFFF; public context: CompilerContext; - private _currPass: RasterPass | CopyPass | null = null; + private _currPass: RasterPass | CopyPass | ComputePass | null = null; private _resVisitor: ResourceVisitor; constructor (context: CompilerContext) { this.context = context; @@ -96,6 +101,12 @@ class PassVisitor implements RenderGraphVisitor { protected _isCopyPass (u: number): boolean { return !!this.context.renderGraph.tryGetCopy(u); } + protected _isCompute (u: number): boolean { + return !!this.context.renderGraph.tryGetCompute(u); + } + protected _isDispatch (u: number): boolean { + return !!this.context.renderGraph.tryGetDispatch(u); + } protected _isQueue (u: number): boolean { return !!this.context.renderGraph.tryGetQueue(u); } @@ -240,12 +251,14 @@ class PassVisitor implements RenderGraphVisitor { } applyID (id: number, resId: number): void { this.resID = resId; - if (this._isRasterPass(id) || this._isCopyPass(id)) { + if (this._isRasterPass(id) || this._isCopyPass(id) || this._isCompute(id)) { this.passID = id; } else if (this._isQueue(id)) { this.queueID = id; } else if (this._isScene(id) || this._isBlit(id)) { this.sceneID = id; + } else if (this._isDispatch(id)) { + this.dispatchID = id; } } rasterPass (pass: RasterPass): void { @@ -256,10 +269,20 @@ class PassVisitor implements RenderGraphVisitor { // } this._currPass = pass; } - rasterSubpass (value: RasterSubpass): void {} - computeSubpass (value: ComputeSubpass): void {} - compute (value: ComputePass): void {} - resolve (value: ResolvePass): void {} + rasterSubpass (value: RasterSubpass): void { + // noop + } + computeSubpass (value: ComputeSubpass): void { + // noop + } + compute (value: ComputePass): void { + this._currPass = value; + const rg = context.renderGraph; + rg.setValid(this.passID, true); + } + resolve (value: ResolvePass): void { + // noop + } copy (value: CopyPass): void { const rg = context.renderGraph; if (rg.getValid(this.passID)) { @@ -281,18 +304,32 @@ class PassVisitor implements RenderGraphVisitor { } } } - move (value: MovePass): void {} - raytrace (value: RaytracePass): void {} - queue (value: RenderQueue): void {} + move (value: MovePass): void { + // noop + } + raytrace (value: RaytracePass): void { + // noop + } + queue (value: RenderQueue): void { + // noop + } scene (value: SceneData): void { this._fetchValidPass(); } blit (value: Blit): void { this._fetchValidPass(); } - dispatch (value: Dispatch): void {} - clear (value: ClearView[]): void {} - viewport (value: Viewport): void {} + dispatch (value: Dispatch): void { + const rg = this.context.renderGraph; + rg.setValid(this.queueID, true); + rg.setValid(this.dispatchID, true); + } + clear (value: ClearView[]): void { + // noop + } + viewport (value: Viewport): void { + // noop + } } class PassManagerVisitor extends DefaultVisitor { @@ -340,19 +377,20 @@ class ResourceVisitor implements ResourceGraphVisitor { managed (value: ManagedResource): void { this.dependency(); } - persistentBuffer (value: Buffer): void { + persistentBuffer (value: Buffer | PersistentBuffer): void { + // noop } dependency (): void { if (!this._passManagerVis) { - this._passManagerVis = new PassManagerVisitor(this._context, this.resID); + this._passManagerVis = new PassManagerVisitor(this._context, this.resID); } else { this._passManagerVis.resId = this.resID; } depthFirstSearch(this._passManagerVis.graphView, this._passManagerVis, this._passManagerVis.colorMap); } - persistentTexture (value: Texture): void { + persistentTexture (value: Texture | PersistentTexture): void { this.dependency(); } framebuffer (value: Framebuffer): void { @@ -362,8 +400,10 @@ class ResourceVisitor implements ResourceGraphVisitor { this.dependency(); } formatView (value: FormatView): void { + // noop } subresourceView (value: SubresourceView): void { + // noop } } diff --git a/cocos/rendering/custom/define.ts b/cocos/rendering/custom/define.ts index 2607da5a70c..5a398937042 100644 --- a/cocos/rendering/custom/define.ts +++ b/cocos/rendering/custom/define.ts @@ -43,7 +43,7 @@ import { ImageAsset, Material, Texture2D } from '../../asset/assets'; import { getProfilerCamera, SRGBToLinear } from '../pipeline-funcs'; import { RenderWindow } from '../../render-scene/core/render-window'; import { RenderData, RenderGraph } from './render-graph'; -import { WebPipeline } from './web-pipeline'; +import { WebComputePassBuilder, WebPipeline } from './web-pipeline'; import { DescriptorSetData, LayoutGraph, LayoutGraphData } from './layout-graph'; import { AABB } from '../../core/geometry'; import { DebugViewCompositeType, DebugViewSingleType } from '../debug-view'; @@ -2176,7 +2176,7 @@ export function buildHBAOPasses ( return { rtName: haboCombined.rtName, dsName: inputDS }; } -export const MAX_LIGHTS_PER_CLUSTER = 100; +export const MAX_LIGHTS_PER_CLUSTER = 200; export const CLUSTERS_X = 16; export const CLUSTERS_Y = 8; export const CLUSTERS_Z = 24; @@ -2238,6 +2238,9 @@ export function buildLightClusterBuildPass ( const width = camera.width * ppl.pipelineSceneData.shadingScale; const height = camera.height * ppl.pipelineSceneData.shadingScale; + if ('setCurrConstant' in clusterPass) { // web-pipeline + (clusterPass as WebComputePassBuilder).addConstant('CCConst', 'cluster-build-cs'); + } clusterPass.setVec4('cc_nearFar', new Vec4(camera.nearClip, camera.farClip, camera.getClipSpaceMinz(), 0)); clusterPass.setVec4('cc_viewPort', new Vec4(0, 0, width, height)); clusterPass.setVec4('cc_workGroup', new Vec4(CLUSTERS_X, CLUSTERS_Y, CLUSTERS_Z, 0)); @@ -2278,6 +2281,9 @@ export function buildLightClusterCullingPass ( const width = camera.width * ppl.pipelineSceneData.shadingScale; const height = camera.height * ppl.pipelineSceneData.shadingScale; + if ('setCurrConstant' in clusterPass) { // web-pipeline + (clusterPass as WebComputePassBuilder).addConstant('CCConst', 'cluster-build-cs'); + } clusterPass.setVec4('cc_nearFar', new Vec4(camera.nearClip, camera.farClip, camera.getClipSpaceMinz(), 0)); clusterPass.setVec4('cc_viewPort', new Vec4(width, height, width, height)); clusterPass.setVec4('cc_workGroup', new Vec4(CLUSTERS_X, CLUSTERS_Y, CLUSTERS_Z, 0)); diff --git a/cocos/rendering/custom/executor.ts b/cocos/rendering/custom/executor.ts index 743836a5e02..bee3c14d048 100644 --- a/cocos/rendering/custom/executor.ts +++ b/cocos/rendering/custom/executor.ts @@ -34,21 +34,99 @@ import { RecyclePool } from '../../core'; import { cclegacy } from '@base/global'; import intersect from '../../core/geometry/intersect'; import { Sphere } from '../../core/geometry/sphere'; -import { AccessFlagBit, Attribute, Buffer, BufferInfo, BufferUsageBit, BufferViewInfo, Color, ColorAttachment, CommandBuffer, DepthStencilAttachment, DescriptorSet, DescriptorSetInfo, Device, deviceManager, Format, Framebuffer, FramebufferInfo, GeneralBarrierInfo, InputAssembler, InputAssemblerInfo, LoadOp, MemoryUsageBit, PipelineState, Rect, RenderPass, RenderPassInfo, Shader, StoreOp, SurfaceTransform, Swapchain, Texture, TextureInfo, TextureType, TextureUsageBit, Viewport } from '../../gfx'; +import { + AccessFlagBit, + Attribute, + Buffer, + BufferInfo, + BufferUsageBit, + BufferViewInfo, + Color, + ColorAttachment, + CommandBuffer, + DepthStencilAttachment, + DescriptorSet, + DescriptorSetInfo, + Device, + deviceManager, + DispatchInfo, + Format, + Framebuffer, + FramebufferInfo, + GeneralBarrierInfo, + InputAssembler, + InputAssemblerInfo, + LoadOp, + MemoryUsageBit, + PipelineBindPoint, + PipelineState, + PipelineStateInfo, + Rect, + RenderPass, + RenderPassInfo, + StoreOp, + SurfaceTransform, + Swapchain, + Texture, + TextureInfo, + TextureType, + TextureUsageBit, + Viewport, +} from '../../gfx'; import { Vec3 } from '../../core/math/vec3'; import { Vec4 } from '../../core/math/vec4'; import { Pass } from '../../render-scene'; import { Camera } from '../../render-scene/scene/camera'; import { ShadowType } from '../../render-scene/scene/shadows'; import { Root } from '../../root'; -import { IRenderPass, isEnableEffect, SetIndex, UBODeferredLight, UBOForwardLight, UBOLocal } from '../define'; +import { IRenderPass, SetIndex, UBODeferredLight, UBOForwardLight, UBOLocal } from '../define'; import { PipelineSceneData } from '../pipeline-scene-data'; import { PipelineInputAssemblerData } from '../render-pipeline'; import { DescriptorSetData, LayoutGraphData, PipelineLayoutData, RenderPhaseData, RenderStageData } from './layout-graph'; import { BasicPipeline } from './pipeline'; import { SceneVisitor } from './scene'; -import { Blit, ClearView, ComputePass, ComputeSubpass, ComputeView, CopyPass, Dispatch, FormatView, ManagedBuffer, ManagedResource, ManagedTexture, MovePass, RasterPass, RasterSubpass, RasterView, RaytracePass, RenderData, RenderGraph, RenderGraphVisitor, RenderQueue, RenderSwapchain, ResolvePass, ResourceDesc, ResourceGraph, ResourceGraphVisitor, ResourceTraits, SceneData, SubresourceView } from './render-graph'; -import { AttachmentType, QueueHint, ResourceDimension, ResourceFlags, ResourceResidency, SceneFlags, UpdateFrequency } from './types'; +import { + Blit, + ClearView, + ComputePass, + ComputeSubpass, + ComputeView, + CopyPass, + Dispatch, + FormatView, + ManagedBuffer, + ManagedResource, + ManagedTexture, + MovePass, + PersistentBuffer, + PersistentTexture, + RasterPass, + RasterSubpass, + RasterView, + RaytracePass, + RenderData, + RenderGraph, + RenderGraphVisitor, + RenderQueue, + RenderSwapchain, + ResolvePass, + ResourceDesc, + ResourceGraph, + ResourceGraphVisitor, + ResourceTraits, + SceneData, + SubresourceView, +} from './render-graph'; +import { + AccessType, + AttachmentType, + QueueHint, + ResourceDimension, + ResourceFlags, + ResourceResidency, + SceneFlags, + UpdateFrequency, +} from './types'; import { PipelineUBO } from '../pipeline-ubo'; import { WebSceneTask, WebSceneTransversal } from './web-scene'; import { WebSceneVisitor } from './web-scene-visitor'; @@ -63,27 +141,68 @@ class ResourceVisitor implements ResourceGraphVisitor { name: string; constructor (resName = '') { this.name = resName; + if (context) { + const ppl = context.pipeline as any; + ppl.resourceUses.push(resName); + } } set resName (value: string) { this.name = value; } - createDeviceTex (value: Texture | Framebuffer | ManagedResource | RenderSwapchain): void { - const deviceTex = new DeviceTexture(this.name, value); - context.deviceTextures.set(this.name, deviceTex); + checkTexture (name: string): boolean { + const dTex = context.deviceTextures.get(name)!; + const resID = context.resourceGraph.vertex(this.name); + const desc = context.resourceGraph.getDesc(resID); + let res = false; + if (dTex.texture) { + res = dTex.texture.width === desc.width && dTex.texture.height === desc.height; + } else if (dTex.swapchain) { + res = dTex.swapchain.width === desc.width && dTex.swapchain.height === desc.height; + } + return res; + } + createDeviceTex (value: PersistentTexture | Framebuffer | ManagedResource | RenderSwapchain): void { + if (!context.deviceTextures.get(this.name)) { + const deviceTex = new DeviceTexture(this.name, value); + context.deviceTextures.set(this.name, deviceTex); + } else if (!this.checkTexture(this.name)) { + const dTex = context.deviceTextures.get(this.name)!; + dTex.texture?.destroy(); + const deviceTex = new DeviceTexture(this.name, value); + context.deviceTextures.set(this.name, deviceTex); + } + } + checkBuffer (name: string): boolean { + const dBuf = context.deviceBuffers.get(name)!; + const resID = context.resourceGraph.vertex(this.name); + const desc = context.resourceGraph.getDesc(resID); + return dBuf.buffer!.size >= desc.width; + } + createDeviceBuf (value: ManagedBuffer | PersistentBuffer): void { + const mount: boolean = !!context.deviceBuffers.get(this.name); + if (!mount) { + const deviceBuf = new DeviceBuffer(this.name, value); + context.deviceBuffers.set(this.name, deviceBuf); + } else if (!this.checkBuffer(this.name)) { + const dBuf = context.deviceBuffers.get(this.name)!; + dBuf.buffer?.destroy(); + const deviceBuf = new DeviceBuffer(this.name, value); + context.deviceBuffers.set(this.name, deviceBuf); + } } managed (value: ManagedResource): void { this.createDeviceTex(value); } managedBuffer (value: ManagedBuffer): void { - // noop + this.createDeviceBuf(value); } managedTexture (value: ManagedTexture): void { // noop } - persistentBuffer (value: Buffer): void { - // noop + persistentBuffer (value: PersistentBuffer): void { + this.createDeviceBuf(value); } - persistentTexture (value: Texture): void { + persistentTexture (value: PersistentTexture): void { this.createDeviceTex(value); } framebuffer (value: Framebuffer): void { @@ -135,7 +254,7 @@ class DeviceTexture extends DeviceResource { this._desc.textureFlags = desc.textureFlags; this._desc.width = desc.width; } - constructor (name: string, tex: Texture | Framebuffer | RenderSwapchain | ManagedResource) { + constructor (name: string, tex: PersistentTexture | Framebuffer | RenderSwapchain | ManagedResource) { super(name); const resGraph = context.resourceGraph; const verID = resGraph.vertex(name); @@ -208,8 +327,35 @@ function isShadowMap (graphScene: GraphScene): boolean | null { } class DeviceBuffer extends DeviceResource { - constructor (name: string) { + private _buffer: Buffer | null; + + get buffer (): Buffer | null { + return this._buffer; + } + + constructor (name: string, buffer: ManagedBuffer | PersistentBuffer) { super(name); + const resGraph = context.resourceGraph; + const verID = resGraph.vertex(name); + const desc = resGraph.getDesc(verID); + const bufferInfo = new BufferInfo(); + bufferInfo.size = desc.width; + bufferInfo.memUsage = MemoryUsageBit.DEVICE; + + if (desc.flags & ResourceFlags.INDIRECT) bufferInfo.usage |= BufferUsageBit.INDIRECT; + if (desc.flags & ResourceFlags.UNIFORM) bufferInfo.usage |= BufferUsageBit.UNIFORM; + if (desc.flags & ResourceFlags.STORAGE) bufferInfo.usage |= BufferUsageBit.STORAGE; + if (desc.flags & ResourceFlags.TRANSFER_SRC) bufferInfo.usage |= BufferUsageBit.TRANSFER_SRC; + if (desc.flags & ResourceFlags.TRANSFER_DST) bufferInfo.usage |= BufferUsageBit.TRANSFER_DST; + + this._buffer = context.device.createBuffer(bufferInfo); + } + + release (): void { + if (this._buffer) { + this._buffer.destroy(); + this._buffer = null; + } } } @@ -375,12 +521,67 @@ class BlitDesc { } } +class DeviceComputeQueue { + private _devicePass: DeviceComputePass | undefined; + private _hint: QueueHint = QueueHint.NONE; + private _phaseID: number = getPhaseID('default'); + private _renderPhase: RenderPhaseData | null = null; + private _descSetData: DescriptorSetData | null = null; + private _layoutID = -1; + private _isUpdateUBO = false; + private _isUploadInstance = false; + private _isUploadBatched = false; + private _queueId = -1; + init (devicePass: DeviceComputePass, renderQueue: RenderQueue, id: number): void { + this.reset(); + this.queueHint = renderQueue.hint; + this.queueId = id; + this._devicePass = devicePass; + this._phaseID = cclegacy.rendering.getPhaseID(devicePass.passID, context.renderGraph.getLayout(id)); + } + get phaseID (): number { return this._phaseID; } + set layoutID (value: number) { + this._layoutID = value; + const layoutGraph = context.layoutGraph; + this._renderPhase = layoutGraph.tryGetRenderPhase(value); + const layout = layoutGraph.getLayout(value); + this._descSetData = layout.descriptorSets.get(UpdateFrequency.PER_PHASE)!; + } + get layoutID (): number { return this._layoutID; } + get descSetData (): DescriptorSetData | null { return this._descSetData; } + get renderPhase (): RenderPhaseData | null { return this._renderPhase; } + set queueId (val) { this._queueId = val; } + get queueId (): number { return this._queueId; } + set isUpdateUBO (update: boolean) { this._isUpdateUBO = update; } + get isUpdateUBO (): boolean { return this._isUpdateUBO; } + set isUploadInstance (value: boolean) { this._isUploadInstance = value; } + get isUploadInstance (): boolean { return this._isUploadInstance; } + set isUploadBatched (value: boolean) { this._isUploadBatched = value; } + get isUploadBatched (): boolean { return this._isUploadBatched; } + + reset (): void { + this._isUpdateUBO = false; + this._isUploadInstance = false; + this._isUploadBatched = false; + } + set queueHint (value: QueueHint) { this._hint = value; } + get queueHint (): QueueHint { return this._hint; } + get devicePass (): DeviceComputePass { return this._devicePass!; } + + record (): void { + if (this._descSetData && this._descSetData.descriptorSet) { + context.commandBuffer + .bindDescriptorSet(SetIndex.COUNT, this._descSetData.descriptorSet); + } + } +} + class DeviceRenderQueue { private _preSceneTasks: DevicePreSceneTask[] = []; private _sceneTasks: DeviceSceneTask[] = []; private _postSceneTasks: DevicePostSceneTask[] = []; private _devicePass: DeviceRenderPass | undefined; - private _hint: QueueHint = QueueHint.NONE; + private _hint: QueueHint = QueueHint.NONE; private _graphQueue!: RenderQueue; private _phaseID: number = getPhaseID('default'); private _renderPhase: RenderPhaseData | null = null; @@ -443,7 +644,7 @@ class DeviceRenderQueue { } addSceneTask (scene: GraphScene): void { if (!this._transversal) { - this._transversal = new DeviceSceneTransversal(this, context.pipelineSceneData, scene); + this._transversal = new DeviceSceneTransversal(this, context.pipelineSceneData, scene); } this._transversal.graphScene = scene; this._preSceneTasks.push(this._transversal.preRenderPass(this._sceneVisitor)); @@ -515,14 +716,17 @@ class RenderPassLayoutInfo { const lg = context.layoutGraph; this._stage = lg.getRenderStage(layoutId); this._layout = lg.getLayout(layoutId); - const layoutData = this._layout.descriptorSets.get(UpdateFrequency.PER_PASS); - const globalDesc = context.pipeline.descriptorSet; + const layoutData = this._layout.descriptorSets.get(UpdateFrequency.PER_PASS); + // const globalDesc = context.descriptorSet; if (layoutData) { + const layoutDesc = layoutData.descriptorSet!; // find resource const deviceTex = context.deviceTextures.get(this._inputName); const gfxTex = deviceTex?.texture; - const layoutDesc = layoutData.descriptorSet!; - if (!gfxTex) { + + const deviceBuf = context.deviceBuffers.get(this._inputName); + const gfxBuf = deviceBuf?.buffer; + if (!gfxTex && !gfxBuf) { throw Error(`Could not find texture with resource name ${this._inputName}`); } const resId = context.resourceGraph.vertex(this._inputName); @@ -537,8 +741,19 @@ class RenderPassLayoutInfo { // const buffer = layoutDesc.getBuffer(block.offset + i); // const texture = layoutDesc.getTexture(block.offset + i); if (descriptorID === block.descriptors[i].descriptorID) { - layoutDesc.bindTexture(block.offset + i, gfxTex); - layoutDesc.bindSampler(block.offset + i, context.device.getSampler(samplerInfo)); + if (gfxTex) { + layoutDesc.bindTexture(block.offset + i, gfxTex); + layoutDesc.bindSampler(block.offset + i, context.device.getSampler(samplerInfo)); + } else { + const desc = context.resourceGraph.getDesc(resId); + if (desc.flags & ResourceFlags.STORAGE) { + const access = input[1][0].accessType !== AccessType.READ ? AccessFlagBit.COMPUTE_SHADER_WRITE + : AccessFlagBit.COMPUTE_SHADER_READ_OTHER; + (layoutDesc as any).bindBuffer(block.offset + i, gfxBuf!, 0, access); + } else { + layoutDesc.bindBuffer(block.offset + i, gfxBuf!); + } + } if (!this._descriptorSet) this._descriptorSet = layoutDesc; continue; } @@ -727,6 +942,10 @@ class DeviceRenderPass { // colorTexs[0].height, // )); // } + const depth = swapchain ? swapchain.depthStencilTexture : depthTex; + if (!depth) { + depthStencilAttachment.format = Format.UNKNOWN; + } this._renderPass = device.createRenderPass(new RenderPassInfo(colors, depthStencilAttachment)); this._framebuffer = framebuffer || device.createFramebuffer(new FramebufferInfo( this._renderPass, @@ -753,6 +972,7 @@ class DeviceRenderPass { } addQueue (queue: DeviceRenderQueue): void { this._deviceQueues.push(queue); } prePass (): void { + context.descriptorSet = getDescriptorSetDataFromLayout(this.layoutName)!.descriptorSet; for (const queue of this._deviceQueues) { queue.preRecord(); } @@ -805,7 +1025,7 @@ class DeviceRenderPass { ia, ); const descData = getDescriptorSetDataFromLayoutId(pass.passID)!; - mergeSrcToTargetDesc(descData.descriptorSet, context.pipeline.descriptorSet, true); + mergeSrcToTargetDesc(descData.descriptorSet, context.descriptorSet, true); profilerViewport.width = rect.width; profilerViewport.height = rect.height; cmdBuff.setViewport(profilerViewport); @@ -842,7 +1062,7 @@ class DeviceRenderPass { ); cmdBuff.bindDescriptorSet( SetIndex.GLOBAL, - context.pipeline.descriptorSet, + context.descriptorSet!, ); for (const queue of this._deviceQueues) { queue.record(); @@ -874,6 +1094,25 @@ class DeviceRenderPass { if (this.renderLayout && this.renderLayout.descriptorSet) { this.renderLayout.descriptorSet.update(); } + + const resGraph = context.resourceGraph; + const currentWidth = this._framebuffer ? this._framebuffer.width : 0; + const currentHeight = this._framebuffer ? this._framebuffer.height : 0; + + let width = 0; + let height = 0; + for (const [resName, rasterV] of this._rasterInfo.pass.rasterViews) { + if (rasterV.attachmentType === AttachmentType.SHADING_RATE) { + continue; + } + const resId = resGraph.vertex(resName); + const resDesc = resGraph.getDesc(resId); + width = resDesc.width; + height = resDesc.height; + break; + } + const needRebuild = (width !== currentWidth) || (height !== currentHeight); + for (const [resName, rasterV] of this._rasterInfo.pass.rasterViews) { let deviceTex = context.deviceTextures.get(resName)!; const currTex = deviceTex; @@ -887,8 +1126,7 @@ class DeviceRenderPass { const resDesc = resGraph.getDesc(resId); if (deviceTex.framebuffer && resFbo instanceof Framebuffer && deviceTex.framebuffer !== resFbo) { framebuffer = this._framebuffer = deviceTex.framebuffer = resFbo; - } else if (!currTex || (deviceTex.texture - && (deviceTex.texture.width !== resDesc.width || deviceTex.texture.height !== resDesc.height))) { + } else if (!currTex || (deviceTex.texture && needRebuild)) { const gfxTex = deviceTex.texture!; if (currTex) gfxTex.resize(resDesc.width, resDesc.height); switch (rasterV.attachmentType) { @@ -916,6 +1154,135 @@ class DeviceRenderPass { } } +class ComputePassInfo { + protected _id!: number; + protected _pass!: ComputePass; + get id (): number { return this._id; } + get pass (): ComputePass { return this._pass; } + private _copyPass (pass: ComputePass): void { + const computePass = this._pass || new ComputePass(); + for (const val of pass.computeViews) { + const currComputeViews = val[1]; + const currComputeKey = val[0]; + const computeViews: ComputeView[] = computePass.computeViews.get(currComputeKey) || []; + if (computeViews.length) computeViews.length = currComputeViews.length; + let idx = 0; + for (const currComputeView of currComputeViews) { + const computeView = computeViews[idx] || new ComputeView(); + computeView.name = currComputeView.name; + computeView.accessType = currComputeView.accessType; + computeView.clearFlags = currComputeView.clearFlags; + computeView.clearValue.x = currComputeView.clearValue.x; + computeView.clearValue.y = currComputeView.clearValue.y; + computeView.clearValue.z = currComputeView.clearValue.z; + computeView.clearValue.w = currComputeView.clearValue.w; + computeView.clearValueType = currComputeView.clearValueType; + computeViews[idx] = computeView; + idx++; + } + computePass.computeViews.set(currComputeKey, computeViews); + } + this._pass = computePass; + } + applyInfo (id: number, pass: ComputePass): void { + this._id = id; + this._copyPass(pass); + } +} + +class DeviceComputePass { + protected _deviceQueues: DeviceComputeQueue[] = []; + protected _passID: number; + protected _layoutName: string; + protected _viewport: Viewport | null = null; + private _computeInfo: ComputePassInfo; + private _layout: RenderPassLayoutInfo | null = null; + constructor (passInfo: ComputePassInfo) { + this._computeInfo = passInfo; + this._layoutName = context.renderGraph.getLayout(passInfo.id); + this._passID = cclegacy.rendering.getPassID(this._layoutName); + + for (const cv of passInfo.pass.computeViews) { + let resTex = context.deviceTextures.get(cv[0]); + if (!resTex) { + this.visitResource(cv[0]); + resTex = context.deviceTextures.get(cv[0])!; + } + this._applyRenderLayout(cv); + } + // update the layout descriptorSet + if (this.renderLayout && this.renderLayout.descriptorSet) { + this.renderLayout.descriptorSet.update(); + } + } + get layoutName (): string { return this._layoutName; } + get passID (): number { return this._passID; } + get renderLayout (): RenderPassLayoutInfo | null { return this._layout; } + + get deviceQueues (): DeviceComputeQueue[] { return this._deviceQueues; } + get computePassInfo (): ComputePassInfo { return this._computeInfo; } + visitResource (resName: string): void { + const resourceGraph = context.resourceGraph; + const vertId = resourceGraph.vertex(resName); + resourceVisitor.resName = resName; + resourceGraph.visitVertex(resourceVisitor, vertId); + } + addQueue (queue: DeviceComputeQueue): void { this._deviceQueues.push(queue); } + prePass (): void { + // noop + } + protected _applyRenderLayout (input: [string, ComputeView[]]): void { + const stageName = context.renderGraph.getLayout(this._computeInfo.id); + if (stageName) { + const layoutGraph = context.layoutGraph; + const stageId = layoutGraph.locateChild(layoutGraph.nullVertex(), stageName); + if (stageId !== 0xFFFFFFFF) { + this._layout = new RenderPassLayoutInfo(stageId, input); + } + } + } + getGlobalDescData (): DescriptorSetData { + const stageId = context.layoutGraph.locateChild(context.layoutGraph.nullVertex(), 'default'); + assert(stageId !== 0xFFFFFFFF); + const layout = context.layoutGraph.getLayout(stageId); + const layoutData = layout.descriptorSets.get(UpdateFrequency.PER_PASS)!; + return layoutData; + } + + // record common buffer + record (): void { + const cmdBuff = context.commandBuffer; + + cmdBuff.bindDescriptorSet( + SetIndex.GLOBAL, + context.descriptorSet!, + ); + for (const queue of this._deviceQueues) { + queue.record(); + } + const renderData = context.renderGraph.getData(this._computeInfo.id); + updateGlobalDescBinding(renderData, context.renderGraph.getLayout(this._computeInfo.id)); + } + + postPass (): void { + // noop + } + resetResource (id: number, pass: ComputePass): void { + this._computeInfo.applyInfo(id, pass); + this._layoutName = context.renderGraph.getLayout(id); + this._passID = cclegacy.rendering.getPassID(this._layoutName); + this._deviceQueues.length = 0; + const colTextures: Texture[] = []; + for (const cv of this._computeInfo.pass.computeViews) { + this._applyRenderLayout(cv); + } + // update the layout descriptorSet + if (this.renderLayout && this.renderLayout.descriptorSet) { + this.renderLayout.descriptorSet.update(); + } + } +} + class DeviceSceneTransversal extends WebSceneTransversal { protected _currentQueue: DeviceRenderQueue; protected _graphScene: GraphScene; @@ -1083,6 +1450,23 @@ class DeviceSceneTask extends WebSceneTask { } protected _recordUI (): void { + const devicePass = this._currentQueue.devicePass; + const rasterId = devicePass.rasterPassInfo.id; + const passRenderData = context.renderGraph.getData(rasterId); + // CCGlobal + this._updateGlobal(passRenderData); + // CCCamera, CCShadow, CCCSM + const queueId = this._currentQueue.queueId; + const queueRenderData = context.renderGraph.getData(queueId)!; + this._updateGlobal(queueRenderData); + + const layoutName = context.renderGraph.getLayout(rasterId); + const descSetData = getDescriptorSetDataFromLayout(layoutName); + if (context.descriptorSet) { + mergeSrcToTargetDesc(descSetData!.descriptorSet, context.descriptorSet, true); + } + this._currentQueue.isUpdateUBO = true; + const batches = this.camera!.scene!.batches; for (let i = 0; i < batches.length; i++) { const batch = batches[i]; @@ -1146,7 +1530,7 @@ class DeviceSceneTask extends WebSceneTask { const shader = pass.getShaderVariant(); const devicePass = this._currentQueue.devicePass; const screenIa: InputAssembler = this._currentQueue.blitDesc!.screenQuad!.quadIA!; - const globalDesc = context.pipeline.descriptorSet; + const globalDesc = context.descriptorSet; let pso: PipelineState | null = null; if (pass !== null && shader !== null && screenIa !== null) { pso = PipelineStateManager.getOrCreatePipelineState( @@ -1191,7 +1575,7 @@ class DeviceSceneTask extends WebSceneTask { const layoutName = context.renderGraph.getLayout(rasterId); const descSetData = getDescriptorSetDataFromLayout(layoutName); - mergeSrcToTargetDesc(descSetData!.descriptorSet, context.pipeline.descriptorSet, true); + mergeSrcToTargetDesc(descSetData!.descriptorSet, context.descriptorSet, true); this._currentQueue.isUpdateUBO = true; } @@ -1235,7 +1619,7 @@ class DeviceSceneTask extends WebSceneTask { return; } const renderQueueDesc = sceneCulling.sceneQueryIndex.get(this.graphScene.sceneID)!; - const renderQueue = sceneCulling.renderQueues[renderQueueDesc.renderQueueTarget]; + const renderQueue = sceneCulling.renderQueues[renderQueueDesc.renderQueueTarget]; const graphSceneData = this.graphScene.scene!; renderQueue.opaqueQueue.recordCommandBuffer(deviceManager.gfxDevice, this._renderPass, context.commandBuffer); renderQueue.opaqueInstancingQueue.recordCommandBuffer(this._renderPass, context.commandBuffer); @@ -1243,7 +1627,7 @@ class DeviceSceneTask extends WebSceneTask { this._recordAdditiveLights(); this.visitor.bindDescriptorSet( SetIndex.GLOBAL, - context.pipeline.descriptorSet, + context.descriptorSet!, ); } @@ -1265,13 +1649,15 @@ class DeviceSceneTask extends WebSceneTask { } } -class DevicePostSceneTask extends WebSceneTask {} +class DevicePostSceneTask extends WebSceneTask { } class ExecutorPools { constructor (context: ExecutorContext) { this.deviceQueuePool = new RecyclePool((): DeviceRenderQueue => new DeviceRenderQueue(), 16); + this.computeQueuePool = new RecyclePool((): DeviceComputeQueue => new DeviceComputeQueue(), 16); this.graphScenePool = new RecyclePool((): GraphScene => new GraphScene(), 16); this.rasterPassInfoPool = new RecyclePool((): RasterPassInfo => new RasterPassInfo(), 16); + this.computePassInfoPool = new RecyclePool((): ComputePassInfo => new ComputePassInfo(), 16); this.reflectionProbe = new RecyclePool((): RenderReflectionProbeQueue => new RenderReflectionProbeQueue(context.pipeline), 8); this.passPool = new RecyclePool((): { priority: number; hash: number; depth: number; shaderId: number; subModel: any; passIdx: number; } => ({ priority: 0, @@ -1291,6 +1677,9 @@ class ExecutorPools { addDeviceQueue (): DeviceRenderQueue { return this.deviceQueuePool.add(); } + addComputeQueue (): DeviceComputeQueue { + return this.computeQueuePool.add(); + } addGraphScene (): GraphScene { return this.graphScenePool.add(); } @@ -1300,17 +1689,24 @@ class ExecutorPools { addRasterPassInfo (): RasterPassInfo { return this.rasterPassInfoPool.add(); } + addComputePassInfo (): ComputePassInfo { + return this.computePassInfoPool.add(); + } reset (): void { this.deviceQueuePool.reset(); + this.computeQueuePool.reset(); this.graphScenePool.reset(); this.reflectionProbe.reset(); this.resetPassInfo(); + this.computePassInfoPool.reset(); } readonly deviceQueuePool: RecyclePool; + readonly computeQueuePool: RecyclePool; readonly graphScenePool: RecyclePool; readonly reflectionProbe: RecyclePool; readonly passPool: RecyclePool; readonly rasterPassInfoPool: RecyclePool; + readonly computePassInfoPool: RecyclePool; } const vbData = new Float32Array(4 * 4); @@ -1395,8 +1791,8 @@ class BlitInfo { let maxY = (renderArea.y + renderArea.height) / this._context.height; if (this._context.root.device.capabilities.screenSpaceSignY > 0) { const temp = maxY; - maxY = minY; - minY = temp; + maxY = minY; + minY = temp; } let n = 0; switch (surfaceTransform) { @@ -1453,7 +1849,7 @@ class BlitInfo { } // create index buffer - const ibStride = Uint8Array.BYTES_PER_ELEMENT; + const ibStride = Uint16Array.BYTES_PER_ELEMENT; const ibSize = ibStride * 6; const quadIB: Buffer = device.createBuffer(new BufferInfo( @@ -1467,11 +1863,11 @@ class BlitInfo { return inputAssemblerData; } - const indices = new Uint8Array(6); + const indices = new Uint16Array(6); indices[0] = 0; indices[1] = 1; indices[2] = 2; indices[3] = 1; indices[4] = 3; indices[5] = 2; - quadIB.update(indices); + quadIB.update(indices.buffer); // create input assembler @@ -1502,6 +1898,7 @@ class ExecutorContext { layoutGraph: LayoutGraphData, width: number, height: number, + descriptorSet = null, ) { this.pipeline = pipeline; this.device = device; @@ -1518,6 +1915,7 @@ class ExecutorContext { this.pools = new ExecutorPools(this); this.blit = new BlitInfo(this); this.culling = new SceneCulling(); + this.descriptorSet = descriptorSet; } reset (): void { this.culling.clear(); @@ -1539,6 +1937,7 @@ class ExecutorContext { readonly resourceGraph: ResourceGraph; readonly devicePasses: Map = new Map(); readonly deviceTextures: Map = new Map(); + readonly deviceBuffers: Map = new Map(); readonly layoutGraph: LayoutGraphData; readonly root: Root; readonly ubo: PipelineUBO; @@ -1551,6 +1950,7 @@ class ExecutorContext { width: number; height: number; cullCamera; + descriptorSet: DescriptorSet | null; } export class Executor { @@ -1600,6 +2000,26 @@ export class Executor { deviceTexs.get(name)!.release(); deviceTexs.delete(name); } + + const deletesBuff: string[] = []; + const deviceBuffs = context.deviceBuffers; + for (const [name, dBuff] of deviceBuffs) { + const resId = context.resourceGraph.vertex(name); + const trait = context.resourceGraph.getTraits(resId); + if (!resourceUses.includes(name)) { + switch (trait.residency) { + case ResourceResidency.MANAGED: + deletesBuff.push(name); + break; + default: + } + } + } + for (const name of deletesBuff) { + deviceBuffs.get(name)!.release(); + deviceBuffs.delete(name); + } + resourceUses.length = 0; } execute (rg: RenderGraph): void { @@ -1622,6 +2042,11 @@ export class Executor { v.release(); } context.deviceTextures.clear(); + + for (const [k, v] of context.deviceBuffers) { + v.release(); + } + context.deviceBuffers.clear(); } readonly _context: ExecutorContext; private _visitor: RenderVisitor | undefined; @@ -1631,8 +2056,9 @@ class BaseRenderVisitor { public queueID = 0xFFFFFFFF; public sceneID = 0xFFFFFFFF; public passID = 0xFFFFFFFF; - public currPass: DeviceRenderPass | undefined; - public currQueue: DeviceRenderQueue | undefined; + public dispatchID = 0xFFFFFFFF; + public currPass: DeviceRenderPass | DeviceComputePass | undefined; + public currQueue: DeviceRenderQueue |DeviceComputeQueue | undefined; public rg: RenderGraph; constructor () { this.rg = context.renderGraph; @@ -1640,6 +2066,12 @@ class BaseRenderVisitor { protected _isRasterPass (u: number): boolean { return !!context.renderGraph.tryGetRasterPass(u); } + protected isComputePass (u: number): boolean { + return !!context.renderGraph.tryGetCompute(u); + } + protected isDispatch (u: number): boolean { + return !!context.renderGraph.tryGetDispatch(u); + } protected _isQueue (u: number): boolean { return !!context.renderGraph.tryGetQueue(u); } @@ -1650,7 +2082,17 @@ class BaseRenderVisitor { return !!context.renderGraph.tryGetBlit(u); } applyID (id: number): void { - if (this._isRasterPass(id)) { this.passID = id; } else if (this._isQueue(id)) { this.queueID = id; } else if (this._isScene(id) || this._isBlit(id)) { this.sceneID = id; } + if (this._isRasterPass(id)) { + this.passID = id; + } else if (this._isQueue(id)) { + this.queueID = id; + } else if (this._isScene(id) || this._isBlit(id)) { + this.sceneID = id; + } else if (this.isComputePass(id)) { + this.passID = id; + } else if (this.isDispatch(id)) { + this.dispatchID = id; + } } } @@ -1684,27 +2126,54 @@ class PreRenderVisitor extends BaseRenderVisitor implements RenderGraphVisitor { computeSubpass (value: ComputeSubpass): void { // noop } - compute (value: ComputePass): void { - // noop - } resolve (value: ResolvePass): void { // noop } - copy (value: CopyPass): void { - // noop - } move (value: MovePass): void { // noop } raytrace (value: RaytracePass): void { // noop } + compute (pass: ComputePass): void { + if (!this.rg.getValid(this.passID)) return; + const devicePasses = context.devicePasses; + const computeInfo = new ComputePassInfo(); + computeInfo.applyInfo(this.passID, pass); + this.currPass = new DeviceComputePass(computeInfo); + + this.currPass.prePass(); + this.currPass.record(); + this.currPass.postPass(); + } + copy (value: CopyPass): void { + if (value.uploadPairs.length) { + for (const upload of value.uploadPairs) { + const resBuffers = context.deviceBuffers; + const resourceGraph = context.resourceGraph; + const vertId = resourceGraph.vertex(upload.target); + resourceVisitor.resName = upload.target; + resourceGraph.visitVertex(resourceVisitor, vertId); + + const gfxBuffer = resBuffers.get(upload.target); + context.device.commandBuffer.updateBuffer(gfxBuffer!.buffer!, upload.source, upload.source.byteLength); + } + } + } queue (value: RenderQueue): void { if (!this.rg.getValid(this.queueID)) return; - const deviceQueue = context.pools.addDeviceQueue(); - deviceQueue.init(this.currPass!, value, this.queueID); - this.currQueue = deviceQueue; - this.currPass!.addQueue(deviceQueue); + let deviceQueue: DeviceComputeQueue | DeviceRenderQueue; + if ('rasterPassInfo' in this.currPass!) { + deviceQueue = context.pools.addDeviceQueue(); + deviceQueue.init(this.currPass, value, this.queueID); + this.currQueue = deviceQueue; + this.currPass.addQueue(deviceQueue); + } else { + deviceQueue = context.pools.addComputeQueue(); + deviceQueue.init(this.currPass!, value, this.queueID); + this.currQueue = deviceQueue; + this.currPass!.addQueue(deviceQueue); + } const layoutName = this.rg.getLayout(this.queueID); if (layoutName) { const layoutGraph = context.layoutGraph; @@ -1716,18 +2185,46 @@ class PreRenderVisitor extends BaseRenderVisitor implements RenderGraphVisitor { } scene (value: SceneData): void { if (!this.rg.getValid(this.sceneID)) return; + const renderQueue = this.currQueue as DeviceRenderQueue; const graphScene = context.pools.addGraphScene(); graphScene.init(value, null, this.sceneID); - this.currQueue!.addSceneTask(graphScene); + renderQueue.addSceneTask(graphScene); } blit (value: Blit): void { if (!this.rg.getValid(this.sceneID)) return; + const renderQueue = this.currQueue as DeviceRenderQueue; const graphScene = context.pools.addGraphScene(); graphScene.init(null, value, -1); - this.currQueue!.addSceneTask(graphScene); + renderQueue.addSceneTask(graphScene); } dispatch (value: Dispatch): void { - // noop + let pso: PipelineState | null = null; + const devicePass = this.currPass as DeviceComputePass; + const pass = value.material?.passes[value.passID]; + pass?.update(); + const shader = pass?.getShaderVariant(); + + if (pass !== null && shader !== null) { + const psoInfo = new PipelineStateInfo( + shader, + pass?.pipelineLayout, + ); + psoInfo.bindPoint = PipelineBindPoint.COMPUTE; + pso = deviceManager.gfxDevice.createPipelineState(psoInfo); + } + const cmdBuff = context.commandBuffer; + if (pso) { + cmdBuff.bindPipelineState(pso); + const layoutStage = devicePass.renderLayout; + const layoutDesc = layoutStage!.descriptorSet!; + const extResId: number[] = []; + cmdBuff.bindDescriptorSet(SetIndex.GLOBAL, layoutDesc); + } + + const gx = value.threadGroupCountX; + const gy = value.threadGroupCountY; + const gz = value.threadGroupCountZ; + (cmdBuff as any).dispatch(new DispatchInfo(gx, gy, gz)); } } diff --git a/cocos/rendering/custom/pipeline-define.ts b/cocos/rendering/custom/pipeline-define.ts index f51c173ff98..9a48fea77cf 100644 --- a/cocos/rendering/custom/pipeline-define.ts +++ b/cocos/rendering/custom/pipeline-define.ts @@ -17,7 +17,7 @@ export class CameraInfo { this.width = width; this.height = height; } - public camera; + public camera: Camera; public id = 0xFFFFFFFF; public windowID = 0xFFFFFFFF; public width = 0; @@ -35,9 +35,12 @@ function prepareRenderWindow (camera: Camera): number { return windowID; } -export function prepareResource (ppl: BasicPipeline, camera: Camera, +export function prepareResource ( + ppl: BasicPipeline, + camera: Camera, initResourceFunc: (ppl: BasicPipeline, info: CameraInfo) => void, - updateResourceFunc: (ppl: BasicPipeline, info: CameraInfo) => void): CameraInfo { + updateResourceFunc: (ppl: BasicPipeline, info: CameraInfo) => void, +): CameraInfo { let info = cameraInfos.get(camera); if (info !== undefined) { let width = camera.window.width; @@ -49,6 +52,9 @@ export function prepareResource (ppl: BasicPipeline, camera: Camera, height = 1; } const windowID = prepareRenderWindow(camera); + info.width = width; + info.height = height; + info.windowID = windowID; updateResourceFunc(ppl, info); return info; } @@ -66,7 +72,7 @@ export function prepareResource (ppl: BasicPipeline, camera: Camera, return info; } -function buildShadowRes (ppl: BasicPipeline, name: string, width, height): void { +function buildShadowRes (ppl: BasicPipeline, name: string, width: number, height: number): void { const fboW = width; const fboH = height; const shadowMapName = name; @@ -93,7 +99,7 @@ export function setupShadowRes (ppl: BasicPipeline, cameraInfo: CameraInfo): Sha const _validLights: Light[] = shadowInfo.validLights; let n = 0; let m = 0; - for (;n < shadow.maxReceived && m < validPunctualLights.length;) { + for (; n < shadow.maxReceived && m < validPunctualLights.length;) { const light = validPunctualLights[m]; if (light.type === LightType.SPOT) { const spotLight = light as any; @@ -131,12 +137,17 @@ export function setupShadowRes (ppl: BasicPipeline, cameraInfo: CameraInfo): Sha return shadowInfo; } -export const updateShadowRes = setupShadowRes; +export const updateShadowRes = setupShadowRes; let shadowPass; -function buildShadowPass (passName: Readonly, +function buildShadowPass ( + passName: Readonly, ppl: BasicPipeline, - camera: Camera, light: Light, level: number, - width: Readonly, height: Readonly): void { + camera: Camera, + light: Light, + level: number, + width: Readonly, + height: Readonly, +): void { const fboW = width; const fboH = height; const area = getRenderArea(camera, width, height, light, level); @@ -148,12 +159,21 @@ function buildShadowPass (passName: Readonly, shadowPass.name = passName; shadowPass.setViewport(new Viewport(0, 0, fboW, fboH)); shadowPass.addRenderTarget(shadowMapName, LoadOp.CLEAR, StoreOp.STORE, new Color(1, 1, 1, camera.clearColor.w)); - shadowPass.addDepthStencil(`${shadowMapName}Depth`, LoadOp.CLEAR, StoreOp.DISCARD, - camera.clearDepth, camera.clearStencil, ClearFlagBit.DEPTH_STENCIL); + shadowPass.addDepthStencil( + `${shadowMapName}Depth`, + LoadOp.CLEAR, + StoreOp.DISCARD, + camera.clearDepth, + camera.clearStencil, + ClearFlagBit.DEPTH_STENCIL, + ); } const queue = shadowPass.addQueue(QueueHint.RENDER_OPAQUE, 'shadow-caster'); - queue.addSceneOfCamera(camera, new LightInfo(light, level), - SceneFlags.SHADOW_CASTER); + queue.addSceneOfCamera( + camera, + new LightInfo(light, level), + SceneFlags.SHADOW_CASTER, + ); queue.setViewport(new Viewport(area.x, area.y, area.width, area.height)); } export function setupShadowPass (ppl: BasicPipeline, cameraInfo: CameraInfo): void { @@ -167,14 +187,28 @@ export function setupShadowPass (ppl: BasicPipeline, cameraInfo: CameraInfo): vo if (mainLight && mainLight.shadowEnabled) { shadowInfo.mainLightShadowNames[0] = `MainLightShadow${cameraInfo.id}`; if (mainLight.shadowFixedArea) { - buildShadowPass(shadowInfo.mainLightShadowNames[0], ppl, - camera, mainLight, 0, mapWidth, mapHeight); + buildShadowPass( + shadowInfo.mainLightShadowNames[0], + ppl, + camera, + mainLight, + 0, + mapWidth, + mapHeight, + ); } else { const csmLevel = ppl.pipelineSceneData.csmSupported ? mainLight.csmLevel : 1; shadowInfo.mainLightShadowNames[0] = `MainLightShadow${cameraInfo.id}`; for (let i = 0; i < csmLevel; i++) { - buildShadowPass(shadowInfo.mainLightShadowNames[0], ppl, - camera, mainLight, i, mapWidth, mapHeight); + buildShadowPass( + shadowInfo.mainLightShadowNames[0], + ppl, + camera, + mainLight, + i, + mapWidth, + mapHeight, + ); } } } @@ -183,8 +217,15 @@ export function setupShadowPass (ppl: BasicPipeline, cameraInfo: CameraInfo): vo const light = shadowInfo.validLights[l]; const passName = `SpotLightShadow${l.toString()}${cameraInfo.id}`; shadowInfo.spotLightShadowNames[l] = passName; - buildShadowPass(passName, ppl, - camera, light, 0, mapWidth, mapHeight); + buildShadowPass( + passName, + ppl, + camera, + light, + 0, + mapWidth, + mapHeight, + ); } } @@ -197,8 +238,13 @@ export function setupForwardRes (ppl: BasicPipeline, cameraInfo: CameraInfo, isO if (!isOffScreen) { ppl.addRenderWindow(`ForwardColor${cameraInfo.id}`, Format.BGRA8, width, height, cameraInfo.camera.window); } else { - ppl.addRenderTarget(`ForwardColor${cameraInfo.id}`, getRTFormatBeforeToneMapping(ppl), - width, height, ResourceResidency.PERSISTENT); + ppl.addRenderTarget( + `ForwardColor${cameraInfo.id}`, + getRTFormatBeforeToneMapping(ppl), + width, + height, + ResourceResidency.PERSISTENT, + ); } ppl.addDepthStencil(`ForwardDepthStencil${cameraInfo.id}`, Format.DEPTH_STENCIL, width, height); } @@ -217,7 +263,7 @@ export function updateForwardRes (ppl: BasicPipeline, cameraInfo: CameraInfo, is ppl.updateDepthStencil(`ForwardDepthStencil${cameraInfo.id}`, width, height); } -export function setupDeferredForward (ppl: BasicPipeline, cameraInfo: CameraInfo, inputColor: string): void { +export function setupDeferredForward (ppl: BasicPipeline, cameraInfo: CameraInfo, inputColor: string, clusterLighting?: boolean): void { const area = getRenderArea(cameraInfo.camera, cameraInfo.camera.window.width, cameraInfo.camera.window.height); const width = area.width; const height = area.height; @@ -237,10 +283,12 @@ export function setupDeferredForward (ppl: BasicPipeline, cameraInfo: CameraInfo } } + let sceneFlags = SceneFlags.OPAQUE_OBJECT | SceneFlags.PLANAR_SHADOW | SceneFlags.CUTOUT_OBJECT + | SceneFlags.DRAW_INSTANCING; + sceneFlags |= clusterLighting ? SceneFlags.CLUSTERED_LIGHTING : SceneFlags.DEFAULT_LIGHTING; + forwardPass.addQueue(QueueHint.RENDER_OPAQUE, 'deferred-forward') - .addSceneOfCamera(camera, new LightInfo(), - SceneFlags.OPAQUE_OBJECT | SceneFlags.PLANAR_SHADOW | SceneFlags.CUTOUT_OBJECT - | SceneFlags.DEFAULT_LIGHTING | SceneFlags.DRAW_INSTANCING); + .addSceneOfCamera(camera, new LightInfo(), sceneFlags); forwardPass.addQueue(QueueHint.RENDER_TRANSPARENT, 'deferred-forward') .addSceneOfCamera(camera, new LightInfo(), SceneFlags.TRANSPARENT_OBJECT | SceneFlags.GEOMETRY); } @@ -268,22 +316,29 @@ export function setupForwardPass (ppl: BasicPipeline, cameraInfo: CameraInfo, is } } const camera = cameraInfo.camera; - forwardPass.addRenderTarget(`ForwardColor${cameraInfo.id}`, + forwardPass.addRenderTarget( + `ForwardColor${cameraInfo.id}`, isOffScreen ? LoadOp.CLEAR : getLoadOpOfClearFlag(camera.clearFlag, AttachmentType.RENDER_TARGET), StoreOp.STORE, - new Color(camera.clearColor.x, camera.clearColor.y, camera.clearColor.z, camera.clearColor.w)); - forwardPass.addDepthStencil(`ForwardDepthStencil${cameraInfo.id}`, + new Color(camera.clearColor.x, camera.clearColor.y, camera.clearColor.z, camera.clearColor.w), + ); + forwardPass.addDepthStencil( + `ForwardDepthStencil${cameraInfo.id}`, isOffScreen ? LoadOp.CLEAR : getLoadOpOfClearFlag(camera.clearFlag, AttachmentType.DEPTH_STENCIL), // If the depth texture is used by subsequent passes, it must be set to store. isOffScreen ? StoreOp.DISCARD : StoreOp.STORE, camera.clearDepth, camera.clearStencil, - camera.clearFlag); + camera.clearFlag, + ); forwardPass .addQueue(QueueHint.RENDER_OPAQUE, 'default') - .addSceneOfCamera(camera, new LightInfo(), + .addSceneOfCamera( + camera, + new LightInfo(), SceneFlags.OPAQUE_OBJECT | SceneFlags.PLANAR_SHADOW | SceneFlags.CUTOUT_OBJECT - | SceneFlags.DEFAULT_LIGHTING | SceneFlags.DRAW_INSTANCING); + | SceneFlags.DEFAULT_LIGHTING | SceneFlags.DRAW_INSTANCING, + ); let sceneFlags = SceneFlags.TRANSPARENT_OBJECT | SceneFlags.GEOMETRY; if (!isOffScreen) { @@ -319,10 +374,11 @@ export function setupReflectionProbeRes (ppl: BasicPipeline, info: CameraInfo): const probes = cclegacy.internal.reflectionProbeManager.getProbes(); if (probes.length === 0) return; for (let i = 0; i < probes.length; i++) { - const probe = probes[i]; + const probe = probes[i] as ReflectionProbe; if (probe.needRender) { if (probes[i].probeType === ProbeType.PLANAR) { - buildReflectionProbeRes(ppl, probe, probe.realtimePlanarTexture.window!, 0); + const window: RenderWindow = probe.realtimePlanarTexture!.window!; + buildReflectionProbeRes(ppl, probe, window, 0); } else if (EDITOR) { for (let faceIdx = 0; faceIdx < probe.bakedCubeTextures.length; faceIdx++) { probe.updateCameraDir(faceIdx); @@ -348,10 +404,20 @@ function buildReflectProbePass (ppl: BasicPipeline, info: CameraInfo, probe: Ref const probePass = ppl.addRenderPass(width, height, 'default'); probePass.name = `ReflectionProbePass${faceIdx}`; probePass.setViewport(new Viewport(0, 0, width, height)); - probePass.addRenderTarget(probePassRTName, getLoadOpOfClearFlag(probeCamera.clearFlag, AttachmentType.RENDER_TARGET), - StoreOp.STORE, new Color(probeCamera.clearColor.x, probeCamera.clearColor.y, probeCamera.clearColor.z, probeCamera.clearColor.w)); - probePass.addDepthStencil(probePassDSName, getLoadOpOfClearFlag(probeCamera.clearFlag, AttachmentType.DEPTH_STENCIL), - StoreOp.STORE, probeCamera.clearDepth, probeCamera.clearStencil, probeCamera.clearFlag); + probePass.addRenderTarget( + probePassRTName, + getLoadOpOfClearFlag(probeCamera.clearFlag, AttachmentType.RENDER_TARGET), + StoreOp.STORE, + new Color(probeCamera.clearColor.x, probeCamera.clearColor.y, probeCamera.clearColor.z, probeCamera.clearColor.w), + ); + probePass.addDepthStencil( + probePassDSName, + getLoadOpOfClearFlag(probeCamera.clearFlag, AttachmentType.DEPTH_STENCIL), + StoreOp.STORE, + probeCamera.clearDepth, + probeCamera.clearStencil, + probeCamera.clearFlag, + ); const passBuilder = probePass.addQueue(QueueHint.RENDER_OPAQUE); passBuilder.addSceneOfCamera(info.camera, new LightInfo(), SceneFlags.REFLECTION_PROBE); updateCameraUBO(passBuilder as unknown as any, probeCamera, ppl); @@ -362,10 +428,11 @@ export function setupReflectionProbePass (ppl: BasicPipeline, info: CameraInfo): const probes = cclegacy.internal.reflectionProbeManager.getProbes(); if (probes.length === 0) return; for (let i = 0; i < probes.length; i++) { - const probe = probes[i]; + const probe = probes[i] as ReflectionProbe; if (probe.needRender) { if (probes[i].probeType === ProbeType.PLANAR) { - buildReflectProbePass(ppl, info, probe, probe.realtimePlanarTexture.window!, 0); + const window = probe.realtimePlanarTexture!.window!; + buildReflectProbePass(ppl, info, probe, window, 0); } else if (EDITOR) { for (let faceIdx = 0; faceIdx < probe.bakedCubeTextures.length; faceIdx++) { probe.updateCameraDir(faceIdx); @@ -447,8 +514,17 @@ export function setupScenePassTiled (pipeline: BasicPipeline, info: CameraInfo, gBufferPass.addRenderTarget(gBufferPassRTName, AccessType.WRITE, '_', LoadOp.CLEAR, StoreOp.DISCARD, emptyColor); gBufferPass.addRenderTarget(gBufferPassNormal, AccessType.WRITE, '_', LoadOp.CLEAR, StoreOp.DISCARD, emptyColor); gBufferPass.addRenderTarget(gBufferPassEmissive, AccessType.WRITE, '_', LoadOp.CLEAR, StoreOp.DISCARD, emptyColor); - gBufferPass.addDepthStencil(gBufferPassDSName, AccessType.WRITE, '_', '_', - LoadOp.CLEAR, StoreOp.DISCARD, camera.clearDepth, camera.clearStencil, camera.clearFlag); + gBufferPass.addDepthStencil( + gBufferPassDSName, + AccessType.WRITE, + '_', + '_', + LoadOp.CLEAR, + StoreOp.DISCARD, + camera.clearDepth, + camera.clearStencil, + camera.clearFlag, + ); gBufferPass .addQueue(QueueHint.RENDER_OPAQUE, 'gbuffer-tiled') .addSceneOfCamera(camera, new LightInfo(), SceneFlags.OPAQUE_OBJECT | SceneFlags.CUTOUT_OBJECT); @@ -476,7 +552,9 @@ export function setupScenePassTiled (pipeline: BasicPipeline, info: CameraInfo, const deferredLightingPassRTName = `deferredLightingPassRTName${info.id}`; lightingPass.addRenderTarget(deferredLightingPassRTName, AccessType.WRITE, '_', LoadOp.CLEAR, StoreOp.STORE, rtColor); lightingPass.addQueue(QueueHint.RENDER_TRANSPARENT, 'deferred-lighting-tiled').addCameraQuad( - camera, lightingInfo.deferredLightingMaterial, 1, + camera, + lightingInfo.deferredLightingMaterial, + 1, SceneFlags.VOLUMETRIC_LIGHTING, ); return { rtName: deferredLightingPassRTName }; @@ -590,7 +668,9 @@ export function setupLightingPass (pipeline: BasicPipeline, info: CameraInfo, us lightingClearColor.w = 0; lightingPass.addRenderTarget(deferredLightingPassRTName, LoadOp.CLEAR, StoreOp.STORE, lightingClearColor); lightingPass.addQueue(QueueHint.RENDER_TRANSPARENT).addCameraQuad( - camera, lightingInfo.deferredLightingMaterial, 0, + camera, + lightingInfo.deferredLightingMaterial, + 0, SceneFlags.VOLUMETRIC_LIGHTING, ); // lightingPass.addQueue(QueueHint.RENDER_TRANSPARENT).addSceneOfCamera(camera, new LightInfo(), @@ -622,9 +702,11 @@ export function updatePostprocessRes (ppl: BasicPipeline, info: CameraInfo): voi ppl.updateDepthStencil(postprocessPassDS, width, height); } let postInfo: PostInfo; -export function setupPostprocessPass (ppl: BasicPipeline, +export function setupPostprocessPass ( + ppl: BasicPipeline, info: CameraInfo, - inputTex: string): { rtName: string; dsName: string; } { + inputTex: string, +): { rtName: string; dsName: string; } { if (!postInfo) { postInfo = new PostInfo(); } @@ -647,13 +729,29 @@ export function setupPostprocessPass (ppl: BasicPipeline, postClearColor.y = camera.clearColor.y; postClearColor.z = camera.clearColor.z; } - postprocessPass.addRenderTarget(postprocessPassRTName, - getLoadOpOfClearFlag(camera.clearFlag, AttachmentType.RENDER_TARGET), StoreOp.STORE, postClearColor); - postprocessPass.addDepthStencil(postprocessPassDS, + postprocessPass.addRenderTarget( + postprocessPassRTName, + getLoadOpOfClearFlag(camera.clearFlag, AttachmentType.RENDER_TARGET), + + StoreOp.STORE, + + postClearColor, + ); + postprocessPass.addDepthStencil( + postprocessPassDS, getLoadOpOfClearFlag(camera.clearFlag, AttachmentType.DEPTH_STENCIL), - StoreOp.STORE, camera.clearDepth, camera.clearStencil, camera.clearFlag); + StoreOp.STORE, + + camera.clearDepth, + + camera.clearStencil, + + camera.clearFlag, + ); postprocessPass.addQueue(QueueHint.NONE).addCameraQuad( - camera, postInfo.postMaterial, 0, + camera, + postInfo.postMaterial, + 0, SceneFlags.NONE, ); if (getProfilerCamera() === camera) { @@ -662,8 +760,10 @@ export function setupPostprocessPass (ppl: BasicPipeline, return { rtName: postprocessPassRTName, dsName: postprocessPassDS }; } -export function setupUIRes (ppl: BasicPipeline, - info: CameraInfo): void { +export function setupUIRes ( + ppl: BasicPipeline, + info: CameraInfo, +): void { const camera = info.camera; const area = getRenderArea(camera, camera.window.width, camera.window.height); const width = area.width; @@ -675,8 +775,10 @@ export function setupUIRes (ppl: BasicPipeline, ppl.addDepthStencil(dsUIAndProfilerPassDSName, Format.DEPTH_STENCIL, width, height, ResourceResidency.MANAGED); } -export function updateUIRes (ppl: BasicPipeline, - info: CameraInfo): void { +export function updateUIRes ( + ppl: BasicPipeline, + info: CameraInfo, +): void { const camera = info.camera; const area = getRenderArea(camera, camera.window.width, camera.window.height); const width = area.width; @@ -688,8 +790,10 @@ export function updateUIRes (ppl: BasicPipeline, ppl.updateDepthStencil(dsUIAndProfilerPassDSName, width, height); } -export function setupUIPass (ppl: BasicPipeline, - info: CameraInfo): void { +export function setupUIPass ( + ppl: BasicPipeline, + info: CameraInfo, +): void { const camera = info.camera; const area = getRenderArea(camera, camera.window.width, camera.window.height); const width = area.width; @@ -699,14 +803,22 @@ export function setupUIPass (ppl: BasicPipeline, const uiAndProfilerPass = ppl.addRenderPass(width, height, 'default'); uiAndProfilerPass.name = `CameraUIAndProfilerPass${info.id}`; uiAndProfilerPass.setViewport(new Viewport(area.x, area.y, width, height)); - uiAndProfilerPass.addRenderTarget(dsUIAndProfilerPassRTName, + uiAndProfilerPass.addRenderTarget( + dsUIAndProfilerPassRTName, getLoadOpOfClearFlag(camera.clearFlag, AttachmentType.RENDER_TARGET), StoreOp.STORE, - new Color(camera.clearColor.x, camera.clearColor.y, camera.clearColor.z, camera.clearColor.w)); - uiAndProfilerPass.addDepthStencil(dsUIAndProfilerPassDSName, + new Color(camera.clearColor.x, camera.clearColor.y, camera.clearColor.z, camera.clearColor.w), + ); + uiAndProfilerPass.addDepthStencil( + dsUIAndProfilerPassDSName, getLoadOpOfClearFlag(camera.clearFlag, AttachmentType.DEPTH_STENCIL), StoreOp.STORE, - camera.clearDepth, camera.clearStencil, camera.clearFlag); + camera.clearDepth, + + camera.clearStencil, + + camera.clearFlag, + ); const sceneFlags = SceneFlags.UI; uiAndProfilerPass .addQueue(QueueHint.RENDER_TRANSPARENT) diff --git a/cocos/rendering/custom/render-graph.ts b/cocos/rendering/custom/render-graph.ts index ac2e3b2909a..6fe321be438 100644 --- a/cocos/rendering/custom/render-graph.ts +++ b/cocos/rendering/custom/render-graph.ts @@ -150,6 +150,14 @@ export class ManagedBuffer { fenceValue = 0; } +export class PersistentBuffer { + constructor (buffer: Buffer | null = null) { + this.buffer = buffer; + } + /*refcount*/ buffer: Buffer | null; + fenceValue = 0; +} + export class ManagedTexture { constructor (texture: Texture | null = null) { this.texture = texture; @@ -158,6 +166,14 @@ export class ManagedTexture { fenceValue = 0; } +export class PersistentTexture { + constructor (texture: Texture | null = null) { + this.texture = texture; + } + /*refcount*/ texture: Texture | null; + fenceValue = 0; +} + export class ManagedResource { unused = 0; } @@ -573,8 +589,8 @@ export interface ResourceGraphValueType { [ResourceGraphValue.Managed]: ManagedResource [ResourceGraphValue.ManagedBuffer]: ManagedBuffer [ResourceGraphValue.ManagedTexture]: ManagedTexture - [ResourceGraphValue.PersistentBuffer]: Buffer - [ResourceGraphValue.PersistentTexture]: Texture + [ResourceGraphValue.PersistentBuffer]: PersistentBuffer + [ResourceGraphValue.PersistentTexture]: PersistentTexture [ResourceGraphValue.Framebuffer]: Framebuffer [ResourceGraphValue.Swapchain]: RenderSwapchain [ResourceGraphValue.FormatView]: FormatView @@ -585,8 +601,8 @@ export interface ResourceGraphVisitor { managed(value: ManagedResource): unknown; managedBuffer(value: ManagedBuffer): unknown; managedTexture(value: ManagedTexture): unknown; - persistentBuffer(value: Buffer): unknown; - persistentTexture(value: Texture): unknown; + persistentBuffer(value: PersistentBuffer): unknown; + persistentTexture(value: PersistentTexture): unknown; framebuffer(value: Framebuffer): unknown; swapchain(value: RenderSwapchain): unknown; formatView(value: FormatView): unknown; @@ -601,7 +617,9 @@ export type ResourceGraphObject = ManagedResource | Framebuffer | RenderSwapchain | FormatView -| SubresourceView; +| SubresourceView +| PersistentBuffer +| PersistentTexture; //----------------------------------------------------------------- // Graph Concept @@ -1046,9 +1064,9 @@ export class ResourceGraph implements BidirectionalGraph case ResourceGraphValue.ManagedTexture: return visitor.managedTexture(vert._object as ManagedTexture); case ResourceGraphValue.PersistentBuffer: - return visitor.persistentBuffer(vert._object as Buffer); + return visitor.persistentBuffer(vert._object as PersistentBuffer); case ResourceGraphValue.PersistentTexture: - return visitor.persistentTexture(vert._object as Texture); + return visitor.persistentTexture(vert._object as PersistentTexture); case ResourceGraphValue.Framebuffer: return visitor.framebuffer(vert._object as Framebuffer); case ResourceGraphValue.Swapchain: @@ -1082,16 +1100,16 @@ export class ResourceGraph implements BidirectionalGraph throw Error('value id not match'); } } - getPersistentBuffer (v: number): Buffer { + getPersistentBuffer (v: number): PersistentBuffer { if (this._vertices[v]._id === ResourceGraphValue.PersistentBuffer) { - return this._vertices[v]._object as Buffer; + return this._vertices[v]._object as PersistentBuffer; } else { throw Error('value id not match'); } } - getPersistentTexture (v: number): Texture { + getPersistentTexture (v: number): PersistentTexture { if (this._vertices[v]._id === ResourceGraphValue.PersistentTexture) { - return this._vertices[v]._object as Texture; + return this._vertices[v]._object as PersistentTexture; } else { throw Error('value id not match'); } @@ -1145,16 +1163,16 @@ export class ResourceGraph implements BidirectionalGraph return null; } } - tryGetPersistentBuffer (v: number): Buffer | null { + tryGetPersistentBuffer (v: number): PersistentBuffer | null { if (this._vertices[v]._id === ResourceGraphValue.PersistentBuffer) { - return this._vertices[v]._object as Buffer; + return this._vertices[v]._object as PersistentBuffer; } else { return null; } } - tryGetPersistentTexture (v: number): Texture | null { + tryGetPersistentTexture (v: number): PersistentTexture | null { if (this._vertices[v]._id === ResourceGraphValue.PersistentTexture) { - return this._vertices[v]._object as Texture; + return this._vertices[v]._object as PersistentTexture; } else { return null; } diff --git a/cocos/rendering/custom/web-pipeline.ts b/cocos/rendering/custom/web-pipeline.ts index f8d6134c9ed..86c95ea7b35 100644 --- a/cocos/rendering/custom/web-pipeline.ts +++ b/cocos/rendering/custom/web-pipeline.ts @@ -29,8 +29,8 @@ import { Buffer, DescriptorSetLayout, Device, Feature, Format, FormatFeatureBit, import { Mat4, Quat, toRadian, Vec2, Vec3, Vec4, macro, IVec4Like, IMat4Like, IVec2Like, Color as CoreColor } from '../../core'; import { assert } from '@base/debug'; import { cclegacy } from '@base/global'; -import { AccessType, AttachmentType, CopyPair, LightInfo, LightingMode, MovePair, QueueHint, ResolvePair, ResourceDimension, ResourceFlags, ResourceResidency, SceneFlags, UpdateFrequency } from './types'; -import { ComputeView, RasterView, Blit, ClearView, ComputePass, CopyPass, Dispatch, ManagedBuffer, ManagedResource, MovePass, RasterPass, RasterSubpass, RenderData, RenderGraph, RenderGraphComponent, RenderGraphValue, RenderQueue, RenderSwapchain, ResourceDesc, ResourceGraph, ResourceGraphValue, ResourceStates, ResourceTraits, SceneData, Subpass } from './render-graph'; +import { AccessType, AttachmentType, CopyPair, LightInfo, LightingMode, MovePair, QueueHint, ResolvePair, ResourceDimension, ResourceFlags, ResourceResidency, SceneFlags, UpdateFrequency, UploadPair } from './types'; +import { ComputeView, RasterView, Blit, ClearView, ComputePass, CopyPass, Dispatch, ManagedBuffer, ManagedResource, MovePass, RasterPass, RasterSubpass, RenderData, RenderGraph, RenderGraphComponent, RenderGraphValue, RenderQueue, RenderSwapchain, ResourceDesc, ResourceGraph, ResourceGraphValue, ResourceStates, ResourceTraits, SceneData, Subpass, PersistentBuffer } from './render-graph'; import { ComputePassBuilder, ComputeQueueBuilder, ComputeSubpassBuilder, BasicPipeline, PipelineBuilder, RenderPassBuilder, RenderQueueBuilder, RenderSubpassBuilder, PipelineType, BasicRenderPassBuilder, PipelineCapabilities, BasicMultisampleRenderPassBuilder } from './pipeline'; import { PipelineSceneData } from '../pipeline-scene-data'; import { Model, Camera, ShadowType, CSMLevel, DirectionalLight, SpotLight, PCFType, Shadows, SphereLight, PointLight, RangedDirectionalLight } from '../../render-scene/scene'; @@ -200,7 +200,7 @@ export class WebSetter { value.fill(0); this._data.constants.set(num, value); } - this.setCurrConstant(block); + this.setCurrConstant(block, stage); return true; } public setMat4 (name: string, mat: Mat4, idx = 0): void { @@ -637,6 +637,13 @@ function setShadowUBOView (setter: WebSetter, camera: Camera | null, layout = 'd } } +function setComputeConstants (setter: WebSetter, layoutName: string): void { + const director = cclegacy.director; + const root = director.root; + const pipeline = root.pipeline as WebPipeline; + setter.addConstant('CCConst', layoutName); +} + function setCameraUBOValues ( setter: WebSetter, camera: Readonly | null, @@ -1339,7 +1346,7 @@ export class WebComputePassBuilder extends WebSetter implements ComputePassBuild throw new Error('Method not implemented.'); } addStorageBuffer (name: string, accessType: AccessType, slotName: string): void { - throw new Error('Method not implemented.'); + this._addComputeResource(name, accessType, slotName); } addStorageImage (name: string, accessType: AccessType, slotName: string): void { throw new Error('Method not implemented.'); @@ -1358,6 +1365,24 @@ export class WebComputePassBuilder extends WebSetter implements ComputePassBuild const queueID = this._renderGraph.addVertex(RenderGraphValue.Queue, queue, '', layoutName, data, false, this._vertID); return new WebComputeQueueBuilder(data, this._renderGraph, this._layoutGraph, queueID, queue, this._pipeline); } + + private _addComputeResource (name: string, accessType: AccessType, slotName: string): void { + const view = new ComputeView(slotName); + view.accessType = accessType; + if (DEBUG) { + assert(Boolean(view.name)); + assert(Boolean(name && this._resourceGraph.contains(name))); + const descriptorName = view.name; + const descriptorID = this._layoutGraph.attributeIndex.get(descriptorName); + assert(descriptorID !== undefined); + } + if (this._pass.computeViews.has(name)) { + this._pass.computeViews.get(name)?.push(view); + } else { + this._pass.computeViews.set(name, [view]); + } + } + private readonly _renderGraph: RenderGraph; private readonly _layoutGraph: LayoutGraphData; private readonly _resourceGraph: ResourceGraph; @@ -1598,6 +1623,30 @@ export class WebPipeline implements BasicPipeline { // TODO: implement resolve pass throw new Error('Method not implemented.'); } + + public addComputePass (passName: string): ComputePassBuilder { + const name = 'Compute'; + const pass = new ComputePass(); + + const data = new RenderData(); + const vertID = this._renderGraph!.addVertex(RenderGraphValue.Compute, pass, name, passName, data, false); + const result = new WebComputePassBuilder(data, this._renderGraph!, this._layoutGraph, this._resourceGraph, vertID, pass, this._pipelineSceneData); + setComputeConstants(result, passName); + initGlobalDescBinding(data, passName); + return result; + } + + public addUploadPass (uploadPairs: UploadPair[]): void { + const name = 'UploadPass'; + const pass = new CopyPass(); + for (const up of uploadPairs) { + pass.uploadPairs.push(up); + } + + const vertID = this._renderGraph!.addVertex(RenderGraphValue.Copy, pass, name, '', new RenderData(), false); + // const result = new WebCopyPassBuilder(this._renderGraph!, vertID, pass); + } + public addCopyPass (copyPairs: CopyPair[]): void { // const renderData = new RenderData(); // const vertID = this._renderGraph!.addVertex( @@ -1626,7 +1675,7 @@ export class WebPipeline implements BasicPipeline { let str = ''; str += `#define CC_DEVICE_SUPPORT_FLOAT_TEXTURE ${this._device.getFormatFeatures(Format.RGBA32F) & (FormatFeatureBit.RENDER_TARGET | FormatFeatureBit.SAMPLED_TEXTURE) ? 1 : 0}\n`; - str += `#define CC_ENABLE_CLUSTERED_LIGHT_CULLING ${clusterEnabled ? 1 : 0}\n`; + // str += `#define CC_ENABLE_CLUSTERED_LIGHT_CULLING ${clusterEnabled ? 1 : 0}\n`; // defined in material str += `#define CC_DEVICE_MAX_VERTEX_UNIFORM_VECTORS ${this._device.capabilities.maxVertexUniformVectors}\n`; str += `#define CC_DEVICE_MAX_FRAGMENT_UNIFORM_VECTORS ${this._device.capabilities.maxFragmentUniformVectors}\n`; str += `#define CC_DEVICE_CAN_BENEFIT_FROM_INPUT_ATTACHMENT ${this._device.hasFeature(Feature.INPUT_ATTACHMENT_BENEFIT) ? 1 : 0}\n`; @@ -1856,11 +1905,22 @@ export class WebPipeline implements BasicPipeline { desc.format = format; desc.flags = ResourceFlags.STORAGE; + if (residency === ResourceResidency.PERSISTENT) { + return this._resourceGraph.addVertex( + ResourceGraphValue.PersistentBuffer, + new PersistentBuffer(), + name, + desc, + new ResourceTraits(ResourceResidency.PERSISTENT), + new ResourceStates(), + new SamplerInfo(), + ); + } + return this._resourceGraph.addVertex( ResourceGraphValue.ManagedBuffer, new ManagedBuffer(), name, - desc, new ResourceTraits(residency), new ResourceStates(), diff --git a/cocos/rendering/custom/web-program-library.ts b/cocos/rendering/custom/web-program-library.ts index 6ed8d77b040..e01023f51c7 100644 --- a/cocos/rendering/custom/web-program-library.ts +++ b/cocos/rendering/custom/web-program-library.ts @@ -24,7 +24,7 @@ /* eslint-disable max-len */ import { EffectAsset } from '../../asset/assets'; -import { Attribute, DescriptorSetLayout, DescriptorType, DESCRIPTOR_BUFFER_TYPE, DESCRIPTOR_SAMPLER_TYPE, Device, MemoryAccessBit, PipelineLayout, PipelineLayoutInfo, Shader, ShaderInfo, ShaderStage, ShaderStageFlagBit, Type, Uniform, UniformBlock, UniformInputAttachment, UniformSampler, UniformSamplerTexture, UniformStorageBuffer, UniformStorageImage, UniformTexture } from '../../gfx'; +import { Attribute, DescriptorSetLayout, DescriptorType, DESCRIPTOR_BUFFER_TYPE, DESCRIPTOR_SAMPLER_TYPE, Device, MemoryAccessBit, PipelineLayout, PipelineLayoutInfo, Shader, ShaderInfo, ShaderStage, ShaderStageFlagBit, Type, Uniform, UniformBlock, UniformInputAttachment, UniformSampler, UniformSamplerTexture, UniformStorageBuffer, UniformStorageImage, UniformTexture, deviceManager } from '../../gfx'; import { genHandles, getActiveAttributes, getCombinationDefines, getShaderInstanceName, getSize, getVariantKey, populateMacros, prepareDefines } from '../../render-scene/core/program-utils'; import { getDeviceShaderVersion, MacroRecord } from '../../render-scene'; import { IProgramInfo } from '../../render-scene/core/program-lib'; @@ -49,8 +49,89 @@ function makeProgramInfo (effectName: string, shader: EffectAsset.IShaderInfo): return programInfo; } +function findBinding (shaderInfo: ShaderInfo, name: string): { set: number, binding: number } { + for (const v of shaderInfo.blocks) { + if (v.name === name) { + return { set: v.set, binding: v.binding }; + } + } + for (const v of shaderInfo.buffers) { + if (v.name === name) { + return { set: v.set, binding: v.binding }; + } + } + for (const v of shaderInfo.samplerTextures) { + if (v.name === name) { + return { set: v.set, binding: v.binding }; + } + } + for (const v of shaderInfo.samplers) { + if (v.name === name) { + return { set: v.set, binding: v.binding }; + } + } + for (const v of shaderInfo.textures) { + if (v.name === name) { + return { set: v.set, binding: v.binding }; + } + } + for (const v of shaderInfo.images) { + if (v.name === name) { + return { set: v.set, binding: v.binding }; + } + } + for (const v of shaderInfo.subpassInputs) { + if (v.name === name) { + return { set: v.set, binding: v.binding }; + } + } + // eslint-disable-next-line no-console + throw console.error('binding not found in shaderInfo!'); +} + +function overwriteShaderSourceBinding (shaderInfo: ShaderInfo, source: string): string { + let code = source; + const samplerExp = /layout\s*\(([^\)])+\)\s+uniform\s+(\b\w+\b\s+)?sampler(\w+)\s+(\b\w+\b)/g; + let samplerIter = samplerExp.exec(code); + while (samplerIter) { + const name = samplerIter[4]; + const { set, binding } = findBinding(shaderInfo, name); + const precStr = samplerIter[2] ? samplerIter[2] : ''; + const replaceStr = `layout(set = ${set}, binding = ${binding}) uniform ${precStr} sampler${samplerIter[3]} ${samplerIter[4]}`; + code = code.replace(samplerIter[0], replaceStr); + samplerIter = samplerExp.exec(code); + } + const blockExp = /layout\s*\(([^\)])+\)\s*(readonly)?\s*\b(uniform|buffer)\b\s+(\b\w+\b)\s*[{;]/g; + let blockIter = blockExp.exec(code); + while (blockIter) { + const name = blockIter[4]; + const { set, binding } = findBinding(shaderInfo, name); + const accessStr = blockIter[2] ? blockIter[2] : ''; + const replaceStr = `layout(set = ${set}, binding = ${binding}) ${accessStr} ${blockIter[3]} ${blockIter[4]} {`; + code = code.replace(blockIter[0], replaceStr); + blockIter = blockExp.exec(code); + } + return code; +} + +function overwriteShaderProgramBinding (shaderInfo: ShaderInfo, programInfo: IProgramInfo): void { + const version = getDeviceShaderVersion(deviceManager.gfxDevice); + if (version !== 'glsl4') { + return; + } + if (programInfo.glsl4.vert) { + programInfo.glsl4.vert = overwriteShaderSourceBinding(shaderInfo, programInfo.glsl4.vert); + } + if (programInfo.glsl4.frag) { + programInfo.glsl4.frag = overwriteShaderSourceBinding(shaderInfo, programInfo.glsl4.frag); + } if (programInfo.glsl4.compute) { + programInfo.glsl4.compute = overwriteShaderSourceBinding(shaderInfo, programInfo.glsl4.compute); + } +} + // overwrite IProgramInfo using gfx.ShaderInfo function overwriteProgramBlockInfo (shaderInfo: ShaderInfo, programInfo: IProgramInfo): void { + overwriteShaderProgramBinding(shaderInfo, programInfo); const set = _setIndex[UpdateFrequency.PER_BATCH]; for (const block of programInfo.blocks) { let found = false; @@ -74,7 +155,9 @@ function overwriteProgramBlockInfo (shaderInfo: ShaderInfo, programInfo: IProgra function populateGroupedShaderInfo ( layout: DescriptorSetLayoutData, descriptorInfo: EffectAsset.IDescriptorInfo, - set: number, shaderInfo: ShaderInfo, blockSizes: number[], + set: number, + shaderInfo: ShaderInfo, + blockSizes: number[], ): void { for (const descriptorBlock of layout.descriptorBlocks) { const visibility = descriptorBlock.visibility; @@ -88,9 +171,13 @@ function populateGroupedShaderInfo ( } blockSizes.push(getSize(block.members)); shaderInfo.blocks.push( - new UniformBlock(set, binding, block.name, + new UniformBlock( + set, + binding, + block.name, block.members.map((m): Uniform => new Uniform(m.name, m.type, m.count)), - 1), // count is always 1 for UniformBlock + 1, + ), // count is always 1 for UniformBlock ); ++binding; } @@ -103,9 +190,7 @@ function populateGroupedShaderInfo ( if (tex.stageFlags !== visibility) { continue; } - shaderInfo.samplerTextures.push(new UniformSamplerTexture( - set, binding, tex.name, tex.type, tex.count, - )); + shaderInfo.samplerTextures.push(new UniformSamplerTexture(set, binding, tex.name, tex.type, tex.count)); ++binding; } break; @@ -114,9 +199,7 @@ function populateGroupedShaderInfo ( if (sampler.stageFlags !== visibility) { continue; } - shaderInfo.samplers.push(new UniformSampler( - set, binding, sampler.name, sampler.count, - )); + shaderInfo.samplers.push(new UniformSampler(set, binding, sampler.name, sampler.count)); ++binding; } break; @@ -125,9 +208,7 @@ function populateGroupedShaderInfo ( if (texture.stageFlags !== visibility) { continue; } - shaderInfo.textures.push(new UniformTexture( - set, binding, texture.name, texture.type, texture.count, - )); + shaderInfo.textures.push(new UniformTexture(set, binding, texture.name, texture.type, texture.count)); ++binding; } break; @@ -136,9 +217,7 @@ function populateGroupedShaderInfo ( if (buffer.stageFlags !== visibility) { continue; } - shaderInfo.buffers.push(new UniformStorageBuffer( - set, binding, buffer.name, 1, buffer.memoryAccess, - )); // effect compiler guarantees buffer count = 1 + shaderInfo.buffers.push(new UniformStorageBuffer(set, binding, buffer.name, 1, buffer.memoryAccess)); // effect compiler guarantees buffer count = 1 ++binding; } break; @@ -150,9 +229,7 @@ function populateGroupedShaderInfo ( if (image.stageFlags !== visibility) { continue; } - shaderInfo.images.push(new UniformStorageImage( - set, binding, image.name, image.type, image.count, image.memoryAccess, - )); + shaderInfo.images.push(new UniformStorageImage(set, binding, image.name, image.type, image.count, image.memoryAccess)); ++binding; } break; @@ -161,9 +238,7 @@ function populateGroupedShaderInfo ( if (subpassInput.stageFlags !== visibility) { continue; } - shaderInfo.subpassInputs.push(new UniformInputAttachment( - set, subpassInput.binding, subpassInput.name, subpassInput.count, - )); + shaderInfo.subpassInputs.push(new UniformInputAttachment(set, subpassInput.binding, subpassInput.name, subpassInput.count)); ++binding; } break; @@ -173,9 +248,13 @@ function populateGroupedShaderInfo ( } // add merged descriptor to gfx.ShaderInfo -function populateMergedShaderInfo (valueNames: string[], +function populateMergedShaderInfo ( + valueNames: string[], layout: DescriptorSetLayoutData, - set: number, shaderInfo: ShaderInfo, blockSizes: number[]): void { + set: number, + shaderInfo: ShaderInfo, + blockSizes: number[], +): void { for (const descriptorBlock of layout.descriptorBlocks) { let binding = descriptorBlock.offset; switch (descriptorBlock.type) { @@ -188,9 +267,13 @@ function populateMergedShaderInfo (valueNames: string[], } blockSizes.push(getSize(uniformBlock.members)); shaderInfo.blocks.push( - new UniformBlock(set, binding, valueNames[block.descriptorID], + new UniformBlock( + set, + binding, + valueNames[block.descriptorID], uniformBlock.members.map((m): Uniform => new Uniform(m.name, m.type, m.count)), - 1), // count is always 1 for UniformBlock + 1, + ), // count is always 1 for UniformBlock ); ++binding; } @@ -203,32 +286,29 @@ function populateMergedShaderInfo (valueNames: string[], break; case DescriptorTypeOrder.SAMPLER_TEXTURE: for (const tex of descriptorBlock.descriptors) { - shaderInfo.samplerTextures.push(new UniformSamplerTexture( - set, binding, valueNames[tex.descriptorID], tex.type, tex.count, - )); + shaderInfo.samplerTextures.push(new UniformSamplerTexture(set, binding, valueNames[tex.descriptorID], tex.type, tex.count)); ++binding; } break; case DescriptorTypeOrder.SAMPLER: for (const sampler of descriptorBlock.descriptors) { - shaderInfo.samplers.push(new UniformSampler( - set, binding, valueNames[sampler.descriptorID], sampler.count, - )); + shaderInfo.samplers.push(new UniformSampler(set, binding, valueNames[sampler.descriptorID], sampler.count)); ++binding; } break; case DescriptorTypeOrder.TEXTURE: for (const texture of descriptorBlock.descriptors) { - shaderInfo.textures.push(new UniformTexture( - set, binding, valueNames[texture.descriptorID], texture.type, texture.count, - )); + shaderInfo.textures.push(new UniformTexture(set, binding, valueNames[texture.descriptorID], texture.type, texture.count)); ++binding; } break; case DescriptorTypeOrder.STORAGE_BUFFER: for (const buffer of descriptorBlock.descriptors) { shaderInfo.buffers.push(new UniformStorageBuffer( - set, binding, valueNames[buffer.descriptorID], 1, + set, + binding, + valueNames[buffer.descriptorID], + 1, MemoryAccessBit.READ_WRITE/*buffer.memoryAccess*/, )); // effect compiler guarantees buffer count = 1 ++binding; @@ -240,7 +320,11 @@ function populateMergedShaderInfo (valueNames: string[], case DescriptorTypeOrder.STORAGE_IMAGE: for (const image of descriptorBlock.descriptors) { shaderInfo.images.push(new UniformStorageImage( - set, binding, valueNames[image.descriptorID], image.type, image.count, + set, + binding, + valueNames[image.descriptorID], + image.type, + image.count, MemoryAccessBit.READ_WRITE/*image.memoryAccess*/, )); ++binding; @@ -248,9 +332,7 @@ function populateMergedShaderInfo (valueNames: string[], break; case DescriptorTypeOrder.INPUT_ATTACHMENT: for (const subpassInput of descriptorBlock.descriptors) { - shaderInfo.subpassInputs.push(new UniformInputAttachment( - set, binding, valueNames[subpassInput.descriptorID], subpassInput.count, - )); + shaderInfo.subpassInputs.push(new UniformInputAttachment(set, binding, valueNames[subpassInput.descriptorID], subpassInput.count)); ++binding; } break; @@ -262,56 +344,53 @@ function populateMergedShaderInfo (valueNames: string[], // add descriptor from effect to gfx.ShaderInfo function populateShaderInfo ( descriptorInfo: EffectAsset.IDescriptorInfo, - set: number, shaderInfo: ShaderInfo, blockSizes: number[], + set: number, + shaderInfo: ShaderInfo, + blockSizes: number[], ): void { for (let i = 0; i < descriptorInfo.blocks.length; i++) { const block = descriptorInfo.blocks[i]; blockSizes.push(getSize(block.members)); - shaderInfo.blocks.push(new UniformBlock(set, block.binding, block.name, - block.members.map((m): Uniform => new Uniform(m.name, m.type, m.count)), 1)); // effect compiler guarantees block count = 1 + shaderInfo.blocks.push(new UniformBlock( + set, + block.binding, + block.name, + block.members.map((m): Uniform => new Uniform(m.name, m.type, m.count)), + 1, + )); // effect compiler guarantees block count = 1 } for (let i = 0; i < descriptorInfo.samplerTextures.length; i++) { const samplerTexture = descriptorInfo.samplerTextures[i]; - shaderInfo.samplerTextures.push(new UniformSamplerTexture( - set, samplerTexture.binding, samplerTexture.name, samplerTexture.type, samplerTexture.count, - )); + shaderInfo.samplerTextures.push(new UniformSamplerTexture(set, samplerTexture.binding, samplerTexture.name, samplerTexture.type, samplerTexture.count)); } for (let i = 0; i < descriptorInfo.samplers.length; i++) { const sampler = descriptorInfo.samplers[i]; - shaderInfo.samplers.push(new UniformSampler( - set, sampler.binding, sampler.name, sampler.count, - )); + shaderInfo.samplers.push(new UniformSampler(set, sampler.binding, sampler.name, sampler.count)); } for (let i = 0; i < descriptorInfo.textures.length; i++) { const texture = descriptorInfo.textures[i]; - shaderInfo.textures.push(new UniformTexture( - set, texture.binding, texture.name, texture.type, texture.count, - )); + shaderInfo.textures.push(new UniformTexture(set, texture.binding, texture.name, texture.type, texture.count)); } for (let i = 0; i < descriptorInfo.buffers.length; i++) { const buffer = descriptorInfo.buffers[i]; - shaderInfo.buffers.push(new UniformStorageBuffer( - set, buffer.binding, buffer.name, 1, buffer.memoryAccess, - )); // effect compiler guarantees buffer count = 1 + shaderInfo.buffers.push(new UniformStorageBuffer(set, buffer.binding, buffer.name, 1, buffer.memoryAccess)); // effect compiler guarantees buffer count = 1 } for (let i = 0; i < descriptorInfo.images.length; i++) { const image = descriptorInfo.images[i]; - shaderInfo.images.push(new UniformStorageImage( - set, image.binding, image.name, image.type, image.count, image.memoryAccess, - )); + shaderInfo.images.push(new UniformStorageImage(set, image.binding, image.name, image.type, image.count, image.memoryAccess)); } for (let i = 0; i < descriptorInfo.subpassInputs.length; i++) { const subpassInput = descriptorInfo.subpassInputs[i]; - shaderInfo.subpassInputs.push(new UniformInputAttachment( - set, subpassInput.binding, subpassInput.name, subpassInput.count, - )); + shaderInfo.subpassInputs.push(new UniformInputAttachment(set, subpassInput.binding, subpassInput.name, subpassInput.count)); } } // add fixed local descriptors to gfx.ShaderInfo function populateLocalShaderInfo ( target: EffectAsset.IDescriptorInfo, - source: IDescriptorSetLayoutInfo, shaderInfo: ShaderInfo, blockSizes: number[], + source: IDescriptorSetLayoutInfo, + shaderInfo: ShaderInfo, + blockSizes: number[], ): void { const set = _setIndex[UpdateFrequency.PER_INSTANCE]; for (let i = 0; i < target.blocks.length; i++) { @@ -323,8 +402,13 @@ function populateLocalShaderInfo ( continue; } blockSizes.push(getSize(block.members)); - shaderInfo.blocks.push(new UniformBlock(set, binding.binding, block.name, - block.members.map((m): Uniform => new Uniform(m.name, m.type, m.count)), 1)); // effect compiler guarantees block count = 1 + shaderInfo.blocks.push(new UniformBlock( + set, + binding.binding, + block.name, + block.members.map((m): Uniform => new Uniform(m.name, m.type, m.count)), + 1, + )); // effect compiler guarantees block count = 1 } for (let i = 0; i < target.samplerTextures.length; i++) { const samplerTexture = target.samplerTextures[i]; @@ -334,9 +418,7 @@ function populateLocalShaderInfo ( warn(`builtin samplerTexture '${samplerTexture.name}' not available!`); continue; } - shaderInfo.samplerTextures.push(new UniformSamplerTexture( - set, binding.binding, samplerTexture.name, samplerTexture.type, samplerTexture.count, - )); + shaderInfo.samplerTextures.push(new UniformSamplerTexture(set, binding.binding, samplerTexture.name, samplerTexture.type, samplerTexture.count)); } } @@ -362,21 +444,25 @@ function getIDescriptorSetLayoutInfoSamplerTextureCapacity (info: IDescriptorSet return capacity; } -function setFlattenedUniformBlockBinding (setOffsets: number[], - descriptors: UniformBlock[]): void { +function setFlattenedUniformBlockBinding ( + setOffsets: number[], + descriptors: UniformBlock[], +): void { for (const d of descriptors) { d.flattened = setOffsets[d.set] + d.binding; } } -function setFlattenedSamplerTextureBinding (setOffsets: number[], +function setFlattenedSamplerTextureBinding ( + setOffsets: number[], uniformBlockCapacities: number[], descriptors: UniformSamplerTexture[] | UniformSampler[] | UniformTexture[] | UniformStorageBuffer[] | UniformStorageImage[] - | UniformInputAttachment[]): void { + | UniformInputAttachment[], +): void { for (const d of descriptors) { d.flattened = setOffsets[d.set] + d.binding - uniformBlockCapacities[d.set]; } @@ -463,16 +549,26 @@ function makeShaderInfo ( const passLayout = passLayouts.descriptorSets.get(UpdateFrequency.PER_PASS); if (passLayout) { descriptorSets[UpdateFrequency.PER_PASS] = passLayout.descriptorSetLayoutData; - populateMergedShaderInfo(lg.valueNames, passLayout.descriptorSetLayoutData, - _setIndex[UpdateFrequency.PER_PASS], shaderInfo, blockSizes); + populateMergedShaderInfo( + lg.valueNames, + passLayout.descriptorSetLayoutData, + _setIndex[UpdateFrequency.PER_PASS], + shaderInfo, + blockSizes, + ); } } { // phase const phaseLayout = phaseLayouts.descriptorSets.get(UpdateFrequency.PER_PHASE); if (phaseLayout) { descriptorSets[UpdateFrequency.PER_PHASE] = phaseLayout.descriptorSetLayoutData; - populateMergedShaderInfo(lg.valueNames, phaseLayout.descriptorSetLayoutData, - _setIndex[UpdateFrequency.PER_PHASE], shaderInfo, blockSizes); + populateMergedShaderInfo( + lg.valueNames, + phaseLayout.descriptorSetLayoutData, + _setIndex[UpdateFrequency.PER_PHASE], + shaderInfo, + blockSizes, + ); } } { // batch @@ -481,16 +577,27 @@ function makeShaderInfo ( const perBatch = programData.layout.descriptorSets.get(UpdateFrequency.PER_BATCH); if (perBatch) { descriptorSets[UpdateFrequency.PER_BATCH] = perBatch.descriptorSetLayoutData; - populateMergedShaderInfo(lg.valueNames, perBatch.descriptorSetLayoutData, - _setIndex[UpdateFrequency.PER_BATCH], shaderInfo, blockSizes); + populateMergedShaderInfo( + lg.valueNames, + perBatch.descriptorSetLayoutData, + _setIndex[UpdateFrequency.PER_BATCH], + shaderInfo, + blockSizes, + ); } } else { const batchLayout = phaseLayouts.descriptorSets.get(UpdateFrequency.PER_BATCH); if (batchLayout) { descriptorSets[UpdateFrequency.PER_BATCH] = batchLayout.descriptorSetLayoutData; - populateGroupedShaderInfo(batchLayout.descriptorSetLayoutData, - batchInfo, _setIndex[UpdateFrequency.PER_BATCH], - shaderInfo, blockSizes); + populateGroupedShaderInfo( + batchLayout.descriptorSetLayoutData, + batchInfo, + + _setIndex[UpdateFrequency.PER_BATCH], + shaderInfo, + + blockSizes, + ); } } } @@ -504,17 +611,28 @@ function makeShaderInfo ( const perInstance = programData.layout.descriptorSets.get(UpdateFrequency.PER_INSTANCE); if (perInstance) { descriptorSets[UpdateFrequency.PER_INSTANCE] = perInstance.descriptorSetLayoutData; - populateMergedShaderInfo(lg.valueNames, perInstance.descriptorSetLayoutData, - _setIndex[UpdateFrequency.PER_INSTANCE], shaderInfo, blockSizes); + populateMergedShaderInfo( + lg.valueNames, + perInstance.descriptorSetLayoutData, + _setIndex[UpdateFrequency.PER_INSTANCE], + shaderInfo, + blockSizes, + ); } } } else { const instanceLayout = phaseLayouts.descriptorSets.get(UpdateFrequency.PER_INSTANCE); if (instanceLayout) { descriptorSets[UpdateFrequency.PER_INSTANCE] = instanceLayout.descriptorSetLayoutData; - populateGroupedShaderInfo(instanceLayout.descriptorSetLayoutData, - instanceInfo, _setIndex[UpdateFrequency.PER_INSTANCE], - shaderInfo, blockSizes); + populateGroupedShaderInfo( + instanceLayout.descriptorSetLayoutData, + instanceInfo, + + _setIndex[UpdateFrequency.PER_INSTANCE], + shaderInfo, + + blockSizes, + ); } } } @@ -554,8 +672,10 @@ function getDescriptorNameAndType (source: IDescriptorSetLayoutInfo, binding: nu } // make DescriptorSetLayoutData from local descriptor set info -function makeLocalDescriptorSetLayoutData (lg: LayoutGraphData, - source: IDescriptorSetLayoutInfo): DescriptorSetLayoutData { +function makeLocalDescriptorSetLayoutData ( + lg: LayoutGraphData, + source: IDescriptorSetLayoutInfo, +): DescriptorSetLayoutData { const data = new DescriptorSetLayoutData(); for (const b of source.bindings) { const [name, type] = getDescriptorNameAndType(source, b.binding); @@ -582,24 +702,32 @@ function makeLocalDescriptorSetLayoutData (lg: LayoutGraphData, function buildProgramData ( programName: string, srcShaderInfo: EffectAsset.IShaderInfo, - lg: LayoutGraphData, phase: RenderPhaseData, programData: ShaderProgramData, + lg: LayoutGraphData, + phase: RenderPhaseData, + programData: ShaderProgramData, fixedLocal: boolean, ): void { { - const perBatch = makeDescriptorSetLayoutData(lg, + const perBatch = makeDescriptorSetLayoutData( + lg, UpdateFrequency.PER_BATCH, _setIndex[UpdateFrequency.PER_BATCH], - srcShaderInfo.descriptors[UpdateFrequency.PER_BATCH]); + srcShaderInfo.descriptors[UpdateFrequency.PER_BATCH], + ); const setData = new DescriptorSetData(perBatch); - initializeDescriptorSetLayoutInfo(setData.descriptorSetLayoutData, - setData.descriptorSetLayoutInfo); + initializeDescriptorSetLayoutInfo( + setData.descriptorSetLayoutData, + setData.descriptorSetLayoutInfo, + ); programData.layout.descriptorSets.set(UpdateFrequency.PER_BATCH, setData); } if (fixedLocal) { const perInstance = makeLocalDescriptorSetLayoutData(lg, localDescriptorSetLayout); const setData = new DescriptorSetData(perInstance); - initializeDescriptorSetLayoutInfo(setData.descriptorSetLayoutData, - setData.descriptorSetLayoutInfo); + initializeDescriptorSetLayoutInfo( + setData.descriptorSetLayoutData, + setData.descriptorSetLayoutInfo, + ); if (localDescriptorSetLayout.bindings.length !== setData.descriptorSetLayoutInfo.bindings.length) { error('local descriptor set layout inconsistent'); } else { @@ -616,13 +744,17 @@ function buildProgramData ( } programData.layout.descriptorSets.set(UpdateFrequency.PER_INSTANCE, setData); } else { - const perInstance = makeDescriptorSetLayoutData(lg, + const perInstance = makeDescriptorSetLayoutData( + lg, UpdateFrequency.PER_INSTANCE, _setIndex[UpdateFrequency.PER_INSTANCE], - srcShaderInfo.descriptors[UpdateFrequency.PER_INSTANCE]); + srcShaderInfo.descriptors[UpdateFrequency.PER_INSTANCE], + ); const setData = new DescriptorSetData(perInstance); - initializeDescriptorSetLayoutInfo(setData.descriptorSetLayoutData, - setData.descriptorSetLayoutInfo); + initializeDescriptorSetLayoutInfo( + setData.descriptorSetLayoutData, + setData.descriptorSetLayoutInfo, + ); programData.layout.descriptorSets.set(UpdateFrequency.PER_INSTANCE, setData); } const shaderID = phase.shaderPrograms.length; @@ -631,9 +763,13 @@ function buildProgramData ( } // get or create PerProgram gfx.DescriptorSetLayout -function getOrCreateProgramDescriptorSetLayout (device: Device, - lg: LayoutGraphData, phaseID: number, - programName: string, rate: UpdateFrequency): DescriptorSetLayout { +function getOrCreateProgramDescriptorSetLayout ( + device: Device, + lg: LayoutGraphData, + phaseID: number, + programName: string, + rate: UpdateFrequency, +): DescriptorSetLayout { assert(rate < UpdateFrequency.PER_PHASE); const phase = lg.getRenderPhase(phaseID); const programID = phase.shaderIndex.get(programName); @@ -654,9 +790,13 @@ function getOrCreateProgramDescriptorSetLayout (device: Device, } // get PerProgram gfx.DescriptorSetLayout -function getProgramDescriptorSetLayout (device: Device, - lg: LayoutGraphData, phaseID: number, - programName: string, rate: UpdateFrequency): DescriptorSetLayout | null { +function getProgramDescriptorSetLayout ( + device: Device, + lg: LayoutGraphData, + phaseID: number, + programName: string, + rate: UpdateFrequency, +): DescriptorSetLayout | null { assert(rate < UpdateFrequency.PER_PHASE); const phase = lg.getRenderPhase(phaseID); const programID = phase.shaderIndex.get(programName); @@ -677,8 +817,11 @@ function getProgramDescriptorSetLayout (device: Device, } // find shader program in LayoutGraphData -function getEffectShader (lg: LayoutGraphData, effect: EffectAsset, - pass: EffectAsset.IPassInfo): [number, number, number, EffectAsset.IShaderInfo | null, number] { +function getEffectShader ( + lg: LayoutGraphData, + effect: EffectAsset, + pass: EffectAsset.IPassInfo, +): [number, number, number, EffectAsset.IShaderInfo | null, number] { const programName = pass.program; const passID = getCustomPassID(lg, pass.pass); if (passID === INVALID_ID) { @@ -766,8 +909,14 @@ export class WebProgramLibrary implements ProgramLibrary { } // shaderInfo and blockSizes - const [shaderInfo, blockSizes] = makeShaderInfo(lg, passLayout, phaseLayout, - srcShaderInfo, programData, this.fixedLocal); + const [shaderInfo, blockSizes] = makeShaderInfo( + lg, + passLayout, + phaseLayout, + srcShaderInfo, + programData, + this.fixedLocal, + ); // overwrite programInfo overwriteProgramBlockInfo(shaderInfo, programInfo); @@ -777,9 +926,7 @@ export class WebProgramLibrary implements ProgramLibrary { // attributes const attributes = new Array(); for (const attr of programInfo.attributes) { - attributes.push(new Attribute( - attr.name, attr.format, attr.isNormalized, 0, attr.isInstanced, attr.location, - )); + attributes.push(new Attribute(attr.name, attr.format, attr.isNormalized, 0, attr.isInstanced, attr.location)); } // create programInfo const info = new ProgramInfo(programInfo, shaderInfo, attributes, blockSizes, handleMap); @@ -805,9 +952,7 @@ export class WebProgramLibrary implements ProgramLibrary { } const defines = getCombinationDefines(combination); defines.forEach( - (defines) => this.getProgramVariant( - device, phaseID, programName, defines, - ), + (defines) => this.getProgramVariant(device, phaseID, programName, defines), ); } } @@ -887,8 +1032,14 @@ export class WebProgramLibrary implements ProgramLibrary { // prepare shader info const shaderInfo = info.shaderInfo; - shaderInfo.stages[0].source = prefix + src.vert; - shaderInfo.stages[1].source = prefix + src.frag; + if (src.compute) { + shaderInfo.stages[0].source = prefix + src.compute; + shaderInfo.stages[0].stage = ShaderStageFlagBit.COMPUTE; + shaderInfo.stages.length = 1; + } else { + shaderInfo.stages[0].source = prefix + src.vert; + shaderInfo.stages[1].source = prefix + src.frag; + } shaderInfo.attributes = getActiveAttributes(programInfo, info.attributes, defines); shaderInfo.name = getShaderInstanceName(name, macroArray); @@ -909,8 +1060,13 @@ export class WebProgramLibrary implements ProgramLibrary { const subpassOrPassID = this.layoutGraph.getParent(phaseID); return getOrCreateDescriptorSetLayout(this.layoutGraph, subpassOrPassID, phaseID, UpdateFrequency.PER_BATCH); } - return getOrCreateProgramDescriptorSetLayout(device, this.layoutGraph, - phaseID, programName, UpdateFrequency.PER_BATCH); + return getOrCreateProgramDescriptorSetLayout( + device, + this.layoutGraph, + phaseID, + programName, + UpdateFrequency.PER_BATCH, + ); } // get local descriptor set layout getLocalDescriptorSetLayout (device: Device, phaseID: number, programName: string): DescriptorSetLayout { @@ -919,8 +1075,13 @@ export class WebProgramLibrary implements ProgramLibrary { const subpassOrPassID = this.layoutGraph.getParent(phaseID); return getOrCreateDescriptorSetLayout(this.layoutGraph, subpassOrPassID, phaseID, UpdateFrequency.PER_INSTANCE); } - return getOrCreateProgramDescriptorSetLayout(device, this.layoutGraph, - phaseID, programName, UpdateFrequency.PER_INSTANCE); + return getOrCreateProgramDescriptorSetLayout( + device, + this.layoutGraph, + phaseID, + programName, + UpdateFrequency.PER_INSTANCE, + ); } // get related uniform block sizes getBlockSizes (phaseID: number, programName: string): number[] { diff --git a/cocos/rendering/pipeline-ubo.ts b/cocos/rendering/pipeline-ubo.ts index 06fe34fd746..35ae5b88f52 100644 --- a/cocos/rendering/pipeline-ubo.ts +++ b/cocos/rendering/pipeline-ubo.ts @@ -36,6 +36,7 @@ import { builtinResMgr } from '../asset/asset-manager/builtin-res-mgr'; import { Texture2D } from '../asset/assets'; import { DebugViewCompositeType } from './debug-view'; import { getDescBindingFromName } from './custom/define'; +import { Root } from '../root'; const _matShadowView = new Mat4(); const _matShadowProj = new Mat4(); @@ -47,7 +48,7 @@ const _tempVec3 = new Vec3(); export class PipelineUBO { public static updateGlobalUBOView (window: RenderWindow, bufferView: Float32Array): void { const director = cclegacy.director; - const root = director.root; + const root = director.root as Root; const fv = bufferView; const shadingWidth = Math.floor(window.width); @@ -75,19 +76,21 @@ export class PipelineUBO { } const debugView = root.debugView; - fv[UBOGlobal.DEBUG_VIEW_MODE_OFFSET] = debugView.singleMode as number; - - for (let i = 1; i <= 3; i++) { + for (let i = 0; i <= 3; i++) { fv[UBOGlobal.DEBUG_VIEW_MODE_OFFSET + i] = 0.0; } - for (let i = DebugViewCompositeType.DIRECT_DIFFUSE as number; i < DebugViewCompositeType.MAX_BIT_COUNT; i++) { - const offset = i >> 3; - const bit = i % 8; - fv[UBOGlobal.DEBUG_VIEW_MODE_OFFSET + 1 + offset] += (debugView.isCompositeModeEnabled(i) ? 1.0 : 0.0) * (10.0 ** bit); - } + if (debugView.isEnabled()) { + fv[UBOGlobal.DEBUG_VIEW_MODE_OFFSET] = debugView.singleMode as number; + + for (let i = DebugViewCompositeType.DIRECT_DIFFUSE as number; i < (DebugViewCompositeType.MAX_BIT_COUNT as unknown as number); i++) { + const offset = i >> 3; + const bit = i % 8; + fv[UBOGlobal.DEBUG_VIEW_MODE_OFFSET + 1 + offset] += (debugView.isCompositeModeEnabled(i) ? 1.0 : 0.0) * (10.0 ** bit); + } - fv[UBOGlobal.DEBUG_VIEW_MODE_OFFSET + 3] += (debugView.lightingWithAlbedo ? 1.0 : 0.0) * (10.0 ** 6.0); - fv[UBOGlobal.DEBUG_VIEW_MODE_OFFSET + 3] += (debugView.csmLayerColoration ? 1.0 : 0.0) * (10.0 ** 7.0); + fv[UBOGlobal.DEBUG_VIEW_MODE_OFFSET + 3] += (debugView.lightingWithAlbedo ? 1.0 : 0.0) * (10.0 ** 6.0); + fv[UBOGlobal.DEBUG_VIEW_MODE_OFFSET + 3] += (debugView.csmLayerColoration ? 1.0 : 0.0) * (10.0 ** 7.0); + } } public static updateCameraUBOView ( @@ -96,7 +99,7 @@ export class PipelineUBO { camera: Camera, ): void { const scene = camera.scene ? camera.scene : cclegacy.director.getScene().renderScene; - const mainLight = scene.mainLight; + const mainLight = scene.mainLight as DirectionalLight; const sceneData = pipeline.pipelineSceneData; const ambient = sceneData.ambient; const skybox = sceneData.skybox; @@ -339,9 +342,9 @@ export class PipelineUBO { if (shadowInfo.type === ShadowType.ShadowMap) { let near = 0.1; let far = 0; - let matShadowView; - let matShadowProj; - let matShadowViewProj; + let matShadowView: Mat4; + let matShadowProj: Mat4; + let matShadowViewProj: Mat4; let levelCount = 0; if (mainLight.shadowFixedArea || mainLight.csmLevel === CSMLevel.LEVEL_1 || !csmSupported) { matShadowView = csmLayers.specialLayer.matShadowView; @@ -403,10 +406,10 @@ export class PipelineUBO { Mat4.perspective( _matShadowProj, - (light as any).angle, + spotLight.angle, 1.0, 0.001, - (light as any).range, + spotLight.range, true, cap.clipSpaceMinZ, cap.clipSpaceSignY, @@ -564,6 +567,6 @@ export class PipelineUBO { } } - public destroy (): void { - } + // eslint-disable-next-line @typescript-eslint/no-empty-function + public destroy (): void {} } diff --git a/cocos/rendering/post-process/passes/forward-pass.ts b/cocos/rendering/post-process/passes/forward-pass.ts index fb73550de00..4fa9f366640 100644 --- a/cocos/rendering/post-process/passes/forward-pass.ts +++ b/cocos/rendering/post-process/passes/forward-pass.ts @@ -17,10 +17,11 @@ export class ForwardPass extends BasePass { depthBufferShadingScale = 1; calcDepthSlot (camera: Camera): void { - let canUsePrevDepth = !!passContext.depthSlotName; - canUsePrevDepth = !(camera.clearFlag & ClearFlagBit.DEPTH_STENCIL); + const depthSlotName = !!passContext.depthSlotName; + let canUsePrevDepth = !(camera.clearFlag & ClearFlagBit.DEPTH_STENCIL); canUsePrevDepth = canUsePrevDepth && passContext.shadingScale === this.depthBufferShadingScale; if (canUsePrevDepth) { + if (!depthSlotName) passContext.depthSlotName = super.slotName(camera, 1); return; } this.depthBufferShadingScale = passContext.shadingScale; diff --git a/cocos/rendering/post-process/post-process-builder.ts b/cocos/rendering/post-process/post-process-builder.ts index fa656a459b1..eb56eb6baa1 100644 --- a/cocos/rendering/post-process/post-process-builder.ts +++ b/cocos/rendering/post-process/post-process-builder.ts @@ -237,7 +237,9 @@ export class PostProcessBuilder implements PipelineBuilder { } if (pass.name === 'BloomPass') { - (pass as BloomPass).hdrInputName = floatOutputPass.getHDRInputName(); + // for override post-process builder + (pass as BloomPass).hdrInputName = (floatOutputPass === undefined || floatOutputPass === null) + ? '' : floatOutputPass.getHDRInputName(); } pass.lastPass = lastPass; diff --git a/cocos/rendering/render-additive-light-queue.ts b/cocos/rendering/render-additive-light-queue.ts index 7a16ed0d3d4..3c905b689f5 100644 --- a/cocos/rendering/render-additive-light-queue.ts +++ b/cocos/rendering/render-additive-light-queue.ts @@ -27,7 +27,7 @@ import { Model } from '../render-scene/scene/model'; import { PipelineStateManager } from './pipeline-state-manager'; import { Vec3, nextPow2, Mat4, Color, Pool, geometry } from '../core'; import { cclegacy } from '@base/global'; -import { Device, RenderPass, Buffer, BufferUsageBit, MemoryUsageBit, BufferInfo, BufferViewInfo, CommandBuffer } from '../gfx'; +import { Device, RenderPass, Buffer, BufferUsageBit, MemoryUsageBit, BufferInfo, BufferViewInfo, CommandBuffer, deviceManager } from '../gfx'; import { RenderInstancedQueue } from './render-instanced-queue'; import { SphereLight } from '../render-scene/scene/sphere-light'; import { SpotLight } from '../render-scene/scene/spot-light'; @@ -169,7 +169,7 @@ export class RenderAdditiveLightQueue { const keys = descriptorSetMap.keys; for (let i = 0; i < keys.length; i++) { - const key = keys[i]; + const key = keys[i] as Light; const descriptorSet = descriptorSetMap.get(key)!; if (descriptorSet) { const binding = isEnableEffect() ? getDescBindingFromName('CCShadow') : UBOShadow.BINDING; @@ -182,7 +182,7 @@ export class RenderAdditiveLightQueue { } } - private _bindForwardAddLight (validPunctualLights, passLayout = 'default'): void { + private _bindForwardAddLight(validPunctualLights: Light[], passLayout = 'default'): void { const renderObjects = this._pipeline.pipelineSceneData.renderObjects; for (let i = 0; i < renderObjects.length; i++) { const ro = renderObjects[i]; @@ -489,7 +489,7 @@ export class RenderAdditiveLightQueue { this._lightBuffer.resize(this._lightBufferStride * this._lightBufferCount); this._lightBufferData = new Float32Array(this._lightBufferElementCount * this._lightBufferCount); - this._firstLightBufferView.initialize(new BufferViewInfo(this._lightBuffer, 0, UBOForwardLight.SIZE)); + this._firstLightBufferView = deviceManager.gfxDevice.createBuffer(new BufferViewInfo(this._lightBuffer, 0, UBOForwardLight.SIZE)); } for (let l = 0, offset = 0; l < validPunctualLights.length; l++, offset += this._lightBufferElementCount) { diff --git a/cocos/scene-graph/node-event.ts b/cocos/scene-graph/node-event.ts index 9d18185c689..901862c301c 100644 --- a/cocos/scene-graph/node-event.ts +++ b/cocos/scene-graph/node-event.ts @@ -245,11 +245,19 @@ export enum NodeEventType { LAYER_CHANGED = 'layer-changed', /** - * @en The event type for node's sibling order changed. - * @zh 当节点在兄弟节点中的顺序发生变化时触发的事件。 + * @en This event indicates that the order of child nodes has been changed. + * @zh 该事件表示子节点的排序发生了改变。 + * @deprecated since v3.8.2 @en Please use `CHILDREN_ORDER_CHANGED`. @zh 请使用 `CHILDREN_ORDER_CHANGED`。 */ SIBLING_ORDER_CHANGED = 'sibling-order-changed', + /** + * @en This event indicates that the order of child nodes has been changed. + * @zh 该事件表示子节点的排序发生了改变。 + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + CHILDREN_ORDER_CHANGED = 'sibling-order-changed', + /** * @en * Note: This event is only emitted from the top most node whose active value did changed, diff --git a/cocos/scene-graph/node.jsb.ts b/cocos/scene-graph/node.jsb.ts index 2ea6fc86738..3b56a542fc4 100644 --- a/cocos/scene-graph/node.jsb.ts +++ b/cocos/scene-graph/node.jsb.ts @@ -280,7 +280,7 @@ nodeProto.on = function (type, callback, target, useCapture: any = false) { this._registeredNodeEventTypeMask |= REGISTERED_EVENT_MASK_LAYER_CHANGED; } break; - case NodeEventType.SIBLING_ORDER_CHANGED: + case NodeEventType.CHILDREN_ORDER_CHANGED: if (!(this._registeredNodeEventTypeMask & REGISTERED_EVENT_MASK_SIBLING_ORDER_CHANGED)) { this._registerOnSiblingOrderChanged(); this._registeredNodeEventTypeMask |= REGISTERED_EVENT_MASK_SIBLING_ORDER_CHANGED; @@ -492,7 +492,7 @@ nodeProto.destroyAllChildren = function destroyAllChildren() { }; nodeProto._onSiblingOrderChanged = function () { - this.emit(NodeEventType.SIBLING_ORDER_CHANGED); + this.emit(NodeEventType.CHILDREN_ORDER_CHANGED); }; nodeProto._onActivateNode = function (shouldActiveNow) { diff --git a/cocos/scene-graph/node.ts b/cocos/scene-graph/node.ts index 13f681ddeb0..3d3d587897b 100644 --- a/cocos/scene-graph/node.ts +++ b/cocos/scene-graph/node.ts @@ -1315,7 +1315,7 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { this._children[i]._siblingIndex = i; } - this.emit(NodeEventType.SIBLING_ORDER_CHANGED); + this.emit(NodeEventType.CHILDREN_ORDER_CHANGED); } protected _instantiate (cloned, isSyncedNode): any { diff --git a/cocos/spine/lib/spine-define.ts b/cocos/spine/lib/spine-define.ts index 28d53f939f2..f267bf1c1fe 100644 --- a/cocos/spine/lib/spine-define.ts +++ b/cocos/spine/lib/spine-define.ts @@ -524,7 +524,6 @@ function overrideProperty_VertexAttachment (): void { proto: prototype, property: 'id', getter: prototype.getId, - setter: prototype.setId, }, { proto: prototype, @@ -727,15 +726,12 @@ function overrideProperty_RegionAttachment (): void { property: 'rendererObject', getter: prototype.getRendererObject, }, - { - proto: prototype, - property: 'offset', - getter: prototype.getOffset, - }, ]; propertyPolyfills.forEach((prop): void => { js.getset(prop.proto, prop.property, prop.getter, prop.setter); }); + + overrideDefineArrayProp(prototype, prototype.getOffset, 'offset'); overrideDefineArrayPropGetSet(prototype, prototype.getUVs, prototype.setUVs, spine.wasmUtil.wasm.VectorFloat, 'uvs'); } @@ -766,6 +762,11 @@ function overrideProperty_SlotData (): void { property: 'index', getter: prototype.getIndex, }, + { + proto: prototype, + property: 'boneData', + getter: prototype.getBoneData, + }, { proto: prototype, property: 'name', @@ -791,8 +792,6 @@ function overrideProperty_SlotData (): void { propertyPolyfills.forEach((prop): void => { js.getset(prop.proto, prop.property, prop.getter, prop.setter); }); - overrideDefineArrayProp(prototype, prototype.getBoneData, 'boneData'); - overrideDefineArrayProp(prototype, prototype.getDeform, 'deform'); } function overrideProperty_IkConstraint (): void { @@ -1226,13 +1225,14 @@ function overrideProperty_Slot (): void { }, { proto: prototype, - property: 'deform', - getter: prototype.getDeform, + property: 'skeleton', + getter: prototype.getSkeleton, }, ]; propertyPolyfills.forEach((prop): void => { js.getset(prop.proto, prop.property, prop.getter); }); + overrideDefineArrayProp(prototype, prototype.getDeform, 'deform'); } function overrideProperty_Skin (): void { diff --git a/cocos/ui/editbox/edit-box-impl-base.ts b/cocos/ui/editbox/edit-box-impl-base.ts index 7b742034370..9ce78026dc9 100644 --- a/cocos/ui/editbox/edit-box-impl-base.ts +++ b/cocos/ui/editbox/edit-box-impl-base.ts @@ -37,11 +37,22 @@ export class EditBoxImplBase { */ public _delegate: EditBox | null = null; - public init (delegate: EditBox): void {} + /** + * @engineInternal dirty flag to update the matrix + */ + public _dirtyFlag: boolean | null = false; - public onEnable (): void {} + public init (delegate: EditBox): void { + // To be overrode + } + + public onEnable (): void { + // To be overrode + } - public update (): void { } + public update (): void { + // To be overrode + } public onDisable (): void { if (this._editing) { @@ -53,9 +64,13 @@ export class EditBoxImplBase { this._delegate = null; } - public setTabIndex (index: number): void {} + public setTabIndex (index: number): void { + // To be overrode + } - public setSize (width: number, height: number): void {} + public setSize (width: number, height: number): void { + // To be overrode + } public setFocus (value): void { if (value) { @@ -69,7 +84,11 @@ export class EditBoxImplBase { return this._editing; } - public beginEditing (): void {} + public beginEditing (): void { + // To be overrode + } - public endEditing (): void {} + public endEditing (): void { + // To be overrode + } } diff --git a/cocos/ui/editbox/edit-box-impl.ts b/cocos/ui/editbox/edit-box-impl.ts index 1fc85bcc6aa..6e9151b1728 100644 --- a/cocos/ui/editbox/edit-box-impl.ts +++ b/cocos/ui/editbox/edit-box-impl.ts @@ -137,6 +137,7 @@ export class EditBoxImpl extends EditBoxImplBase { } public update (): void { + if (!this._dirtyFlag) return; this._updateMatrix(); } @@ -625,12 +626,12 @@ export class EditBoxImpl extends EditBoxImplBase { this._delegate!._editBoxEditingDidEnded(); }; - elem.addEventListener('compositionstart', cbs.compositionStart); - elem.addEventListener('compositionend', cbs.compositionEnd); - elem.addEventListener('input', cbs.onInput); - elem.addEventListener('keydown', cbs.onKeydown); - elem.addEventListener('blur', cbs.onBlur); - elem.addEventListener('touchstart', cbs.onClick); + elem.addEventListener('compositionstart', cbs.compositionStart as EventListenerOrEventListenerObject); + elem.addEventListener('compositionend', cbs.compositionEnd as EventListenerOrEventListenerObject); + elem.addEventListener('input', cbs.onInput as EventListenerOrEventListenerObject); + elem.addEventListener('keydown', cbs.onKeydown as EventListenerOrEventListenerObject); + elem.addEventListener('blur', cbs.onBlur as EventListenerOrEventListenerObject); + elem.addEventListener('touchstart', cbs.onClick as EventListenerOrEventListenerObject); } private _removeEventListeners (): void { if (!this._edTxt) { @@ -640,12 +641,12 @@ export class EditBoxImpl extends EditBoxImplBase { const elem = this._edTxt; const cbs = this.__eventListeners; - elem.removeEventListener('compositionstart', cbs.compositionStart); - elem.removeEventListener('compositionend', cbs.compositionEnd); - elem.removeEventListener('input', cbs.onInput); - elem.removeEventListener('keydown', cbs.onKeydown); - elem.removeEventListener('blur', cbs.onBlur); - elem.removeEventListener('touchstart', cbs.onClick); + elem.removeEventListener('compositionstart', cbs.compositionStart as EventListenerOrEventListenerObject); + elem.removeEventListener('compositionend', cbs.compositionEnd as EventListenerOrEventListenerObject); + elem.removeEventListener('input', cbs.onInput as EventListenerOrEventListenerObject); + elem.removeEventListener('keydown', cbs.onKeydown as EventListenerOrEventListenerObject); + elem.removeEventListener('blur', cbs.onBlur as EventListenerOrEventListenerObject); + elem.removeEventListener('touchstart', cbs.onClick as EventListenerOrEventListenerObject); cbs.compositionStart = null; cbs.compositionEnd = null; diff --git a/cocos/ui/editbox/edit-box.ts b/cocos/ui/editbox/edit-box.ts index 9b6abff4fbc..25b1b3933c7 100644 --- a/cocos/ui/editbox/edit-box.ts +++ b/cocos/ui/editbox/edit-box.ts @@ -492,6 +492,9 @@ export class EditBox extends Component { public _editBoxEditingDidBegan (): void { ComponentEventHandler.emitEvents(this.editingDidBegan, this); this.node.emit(EventType.EDITING_DID_BEGAN, this); + if (this._impl) { + this._impl._dirtyFlag = true; + } } /** @@ -503,6 +506,9 @@ export class EditBox extends Component { public _editBoxEditingDidEnded (text?: string): void { ComponentEventHandler.emitEvents(this.editingDidEnded, this); this.node.emit(EventType.EDITING_DID_ENDED, this, text); + if (this._impl) { + this._impl._dirtyFlag = false; + } } /** diff --git a/cocos/ui/layout.ts b/cocos/ui/layout.ts index 88789073db8..39fc56b95f0 100644 --- a/cocos/ui/layout.ts +++ b/cocos/ui/layout.ts @@ -736,7 +736,7 @@ export class Layout extends Component { this.node.on(NodeEventType.ANCHOR_CHANGED, this._doLayoutDirty, this); this.node.on(NodeEventType.CHILD_ADDED, this._childAdded, this); this.node.on(NodeEventType.CHILD_REMOVED, this._childRemoved, this); - this.node.on(NodeEventType.SIBLING_ORDER_CHANGED, this._childrenChanged, this); + this.node.on(NodeEventType.CHILDREN_ORDER_CHANGED, this._childrenChanged, this); this.node.on('childrenSiblingOrderChanged', this.updateLayout, this); this._addChildrenEventListeners(); } @@ -747,7 +747,7 @@ export class Layout extends Component { this.node.off(NodeEventType.ANCHOR_CHANGED, this._doLayoutDirty, this); this.node.off(NodeEventType.CHILD_ADDED, this._childAdded, this); this.node.off(NodeEventType.CHILD_REMOVED, this._childRemoved, this); - this.node.off(NodeEventType.SIBLING_ORDER_CHANGED, this._childrenChanged, this); + this.node.off(NodeEventType.CHILDREN_ORDER_CHANGED, this._childrenChanged, this); this.node.off('childrenSiblingOrderChanged', this.updateLayout, this); this._removeChildrenEventListeners(); } diff --git a/cocos/webgpu/instantiated.ts b/cocos/webgpu/instantiated.ts index fa23ac1e456..80ee5d5cf39 100644 --- a/cocos/webgpu/instantiated.ts +++ b/cocos/webgpu/instantiated.ts @@ -30,16 +30,23 @@ import { WASM_SUPPORT_MODE, WEBGPU } from 'internal:constants'; import webgpuUrl from 'external:emscripten/webgpu/webgpu_wasm.wasm'; import glslangUrl from 'external:emscripten/webgpu/glslang.wasm'; +import twgslUrl from 'external:emscripten/webgpu/twgsl.wasm' + import wasmDevice from 'external:emscripten/webgpu/webgpu_wasm.js'; import glslangLoader from 'external:emscripten/webgpu/glslang.js'; +import twgslLoader from 'external:emscripten/webgpu/twgsl.js' import { cclegacy } from '@base/global'; import { WebAssemblySupportMode } from '../misc/webassembly-support'; import { log } from 'console'; -export const glslalgWasmModule: any = { +export const glslangWasmModule: any = { glslang: null, }; +export const twgslModule: any = { + twgsl: null, +}; + export const gfx: any = cclegacy.gfx = { wasmBinary: null, nativeDevice: null, @@ -55,7 +62,10 @@ export const promiseForWebGPUInstantiation = (() => { // TODO: we need to support AsmJS fallback option return Promise.all([ glslangLoader(new URL(glslangUrl, import.meta.url).href).then((res) => { - glslalgWasmModule.glslang = res; + glslangWasmModule.glslang = res; + }), + twgslLoader(new URL(twgslUrl, import.meta.url).href).then((data) => { + twgslModule.twgsl = data; }), new Promise((resolve) => { fetch(new URL(webgpuUrl, import.meta.url).href).then((response) => { diff --git a/editor/assets/chunks/common/color/tone-mapping.chunk b/editor/assets/chunks/common/color/tone-mapping.chunk index 5f35af77b09..6808b806772 100644 --- a/editor/assets/chunks/common/color/tone-mapping.chunk +++ b/editor/assets/chunks/common/color/tone-mapping.chunk @@ -8,10 +8,12 @@ vec3 HDRToLDR(vec3 color) #if CC_USE_DEBUG_VIEW == CC_SURFACES_DEBUG_VIEW_COMPOSITE_AND_MISC && CC_SURFACES_ENABLE_DEBUG_VIEW if (IS_DEBUG_VIEW_COMPOSITE_ENABLE_TONE_MAPPING) #endif + { // linear exposure has already applied to light intensity #if CC_TONE_MAPPING_TYPE == HDR_TONE_MAPPING_ACES color.rgb = ACESToneMap(color.rgb); #endif + } #endif return color; diff --git a/editor/assets/chunks/common/math/coordinates.chunk b/editor/assets/chunks/common/math/coordinates.chunk index c994cfc699d..1628ab0bb9e 100644 --- a/editor/assets/chunks/common/math/coordinates.chunk +++ b/editor/assets/chunks/common/math/coordinates.chunk @@ -2,7 +2,7 @@ // cc_cameraPos.w is flipNDCSign #pragma define CC_HANDLE_NDC_SAMPLE_FLIP(uv, flipNDCSign) uv = flipNDCSign == 1.0 ? vec2(uv.x, 1.0 - uv.y) : uv -#ifdef CC_USE_METAL +#if defined(CC_USE_METAL) || defined(CC_USE_WGPU) #define CC_HANDLE_SAMPLE_NDC_FLIP_STATIC(y) y = -y #else #define CC_HANDLE_SAMPLE_NDC_FLIP_STATIC(y) diff --git a/editor/assets/chunks/legacy/shading-cluster-additive.chunk b/editor/assets/chunks/legacy/shading-cluster-additive.chunk index 0bf35248e78..ba4c16be3a7 100644 --- a/editor/assets/chunks/legacy/shading-cluster-additive.chunk +++ b/editor/assets/chunks/legacy/shading-cluster-additive.chunk @@ -2,7 +2,7 @@ #pragma define CLUSTERS_X 16u #pragma define CLUSTERS_Y 8u #pragma define CLUSTERS_Z 24u -#pragma define MAX_LIGHTS_PER_CLUSTER 100u +#pragma define MAX_LIGHTS_PER_CLUSTER 200u #pragma rate b_ccLightsBuffer pass #pragma glBinding(0) diff --git a/editor/assets/chunks/shading-entries/data-structures/fs-input.chunk b/editor/assets/chunks/shading-entries/data-structures/fs-input.chunk index bf3553519f8..c038209b091 100644 --- a/editor/assets/chunks/shading-entries/data-structures/fs-input.chunk +++ b/editor/assets/chunks/shading-entries/data-structures/fs-input.chunk @@ -31,7 +31,7 @@ void CCSurfacesGetFragmentInput(out SurfacesStandardFragmentInput fsInput) #define FSInput_worldTangent v_tangent.xyz // unnormalized #define FSInput_mirrorNormal v_tangent.w #else - #define FSInput_worldTangent vec3(0.0, 0.0, 0.0) + #define FSInput_worldTangent vec3(1.0, 1.0, 1.0) // normalize zero vector may crash on webgpu, use 1 instead #define FSInput_mirrorNormal 1.0 #endif diff --git a/editor/assets/chunks/shading-entries/main-functions/misc/sky-fs.chunk b/editor/assets/chunks/shading-entries/main-functions/misc/sky-fs.chunk index cd1e3391a55..4b19e669c80 100644 --- a/editor/assets/chunks/shading-entries/main-functions/misc/sky-fs.chunk +++ b/editor/assets/chunks/shading-entries/main-functions/misc/sky-fs.chunk @@ -15,6 +15,14 @@ void main() { vec4 color = SurfacesFragmentModifyBaseColorAndTransparency(); color.a = 1.0; + // HDR Fog + // todo: apply fogColorBrightness to linear fogColor for supporting scatter lighting with HDR + #if CC_USE_FOG != CC_FOG_NONE + float fogFactor = 1.0; + CC_TRANSFER_FOG_BASE(vec4(FSInput_worldPos, 1.0), fogFactor); + CC_APPLY_FOG_BASE(color, fogFactor); + #endif + #if CC_USE_RGBE_OUTPUT color = packRGBE(color.rgb); #else//todo: change to #elif !CC_USE_FLOAT_OUTPUT when sky render queue has been fixed with custom pipeline @@ -22,12 +30,5 @@ void main() { color.rgb = LinearToSRGB(color.rgb); #endif - //todo: LDR fogging in gamma space, HDR fogging should move before tone mapping - #if CC_USE_FOG != CC_FOG_NONE - float fogFactor = 1.0; - CC_TRANSFER_FOG_BASE(vec4(FSInput_worldPos, 1.0), fogFactor); - CC_APPLY_FOG_BASE(color, fogFactor); - #endif - fragColorX = color; } diff --git a/editor/assets/chunks/shading-entries/main-functions/render-to-scene/pipeline/forward-fs.chunk b/editor/assets/chunks/shading-entries/main-functions/render-to-scene/pipeline/forward-fs.chunk index 17565ac6f50..386daaf0908 100644 --- a/editor/assets/chunks/shading-entries/main-functions/render-to-scene/pipeline/forward-fs.chunk +++ b/editor/assets/chunks/shading-entries/main-functions/render-to-scene/pipeline/forward-fs.chunk @@ -128,6 +128,18 @@ void main() { #endif #endif + // Fog, rgbe and gamma output can't apply fog with forward-add pass + // todo: apply fogColorBrightness to linear fogColor for supporting scatter lighting with HDR + #if CC_USE_FOG != CC_FOG_NONE && (!CC_USE_FLOAT_OUTPUT || CC_IS_TRANSPARENCY_PASS) + #if !CC_FORWARD_ADD + #ifdef CC_SURFACES_LIGHTING_MODIFY_FOG + color.rgb = CCSurfacesLightingModifyFog(fogFactor, color.rgb, surfaceData, lightingResult); + #else + CC_APPLY_FOG_BASE(color, fogFactor); + #endif + #endif + #endif + // Color output #if CC_USE_RGBE_OUTPUT color = packRGBE(color.rgb); // for reflection-map @@ -139,16 +151,5 @@ void main() { #endif #endif - // Fog, rgbe and gamma output can't apply fog with forward-add pass - // todo: apply fogColorBrightness to fogColor for supporting scatter lighting with HDR - #if CC_USE_FOG != CC_FOG_NONE && (!CC_USE_FLOAT_OUTPUT || CC_IS_TRANSPARENCY_PASS) - #if !CC_FORWARD_ADD - #ifdef CC_SURFACES_LIGHTING_MODIFY_FOG - color.rgb = CCSurfacesLightingModifyFog(fogFactor, color.rgb, surfaceData, lightingResult); - #else - CC_APPLY_FOG_BASE(color, fogFactor); - #endif - #endif - #endif fragColorX = color; } diff --git a/editor/assets/default_prefab/2d/Camera.prefab.meta b/editor/assets/default_prefab/2d/Camera.prefab.meta index 2a9efe9d3ad..ad7cdf57623 100644 --- a/editor/assets/default_prefab/2d/Camera.prefab.meta +++ b/editor/assets/default_prefab/2d/Camera.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "3487d118-0158-4983-93fe-c3822790e7c5", diff --git a/editor/assets/default_prefab/2d/ui/Canvas.prefab.meta b/editor/assets/default_prefab/2d/ui/Canvas.prefab.meta index 7a02f8602a3..f33a54cb75e 100644 --- a/editor/assets/default_prefab/2d/ui/Canvas.prefab.meta +++ b/editor/assets/default_prefab/2d/ui/Canvas.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "4c33600e-9ca9-483b-b734-946008261697", diff --git a/editor/assets/default_prefab/3d/Capsule.prefab.meta b/editor/assets/default_prefab/3d/Capsule.prefab.meta index 2cdc8360ae1..76db4af8e75 100644 --- a/editor/assets/default_prefab/3d/Capsule.prefab.meta +++ b/editor/assets/default_prefab/3d/Capsule.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "73ce1f7f-d1f4-4942-ad93-66ca3b3041ab", diff --git a/editor/assets/default_prefab/3d/Cone.prefab.meta b/editor/assets/default_prefab/3d/Cone.prefab.meta index caef62b443a..b34605a1e5a 100644 --- a/editor/assets/default_prefab/3d/Cone.prefab.meta +++ b/editor/assets/default_prefab/3d/Cone.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "6350d660-e888-4acf-a552-f3b719ae9110", diff --git a/editor/assets/default_prefab/3d/Cube.prefab.meta b/editor/assets/default_prefab/3d/Cube.prefab.meta index 7a3b4277d85..0155ae18b60 100644 --- a/editor/assets/default_prefab/3d/Cube.prefab.meta +++ b/editor/assets/default_prefab/3d/Cube.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "30da77a1-f02d-4ede-aa56-403452ee7fde", diff --git a/editor/assets/default_prefab/3d/Cylinder.prefab.meta b/editor/assets/default_prefab/3d/Cylinder.prefab.meta index de6fd2091f3..23303d97fc7 100644 --- a/editor/assets/default_prefab/3d/Cylinder.prefab.meta +++ b/editor/assets/default_prefab/3d/Cylinder.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "ab3e16f9-671e-48a7-90b7-d0884d9cbb85", diff --git a/editor/assets/default_prefab/3d/Plane.prefab.meta b/editor/assets/default_prefab/3d/Plane.prefab.meta index 762d5e1c3cb..28f5dda5f49 100644 --- a/editor/assets/default_prefab/3d/Plane.prefab.meta +++ b/editor/assets/default_prefab/3d/Plane.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "40563723-f8fc-4216-99ea-a81636435c10", diff --git a/editor/assets/default_prefab/3d/Quad.prefab.meta b/editor/assets/default_prefab/3d/Quad.prefab.meta index 05f6a30a78f..8e16ec5e9e8 100644 --- a/editor/assets/default_prefab/3d/Quad.prefab.meta +++ b/editor/assets/default_prefab/3d/Quad.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "34a07346-9f62-4a84-90ae-cb83f7a426c1", diff --git a/editor/assets/default_prefab/3d/Sphere.prefab.meta b/editor/assets/default_prefab/3d/Sphere.prefab.meta index 379f0318d21..2226e2e1585 100644 --- a/editor/assets/default_prefab/3d/Sphere.prefab.meta +++ b/editor/assets/default_prefab/3d/Sphere.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "655c9519-1a37-472b-bae6-29fefac0b550", diff --git a/editor/assets/default_prefab/3d/Torus.prefab.meta b/editor/assets/default_prefab/3d/Torus.prefab.meta index 1c916b0f66e..03be60e79be 100644 --- a/editor/assets/default_prefab/3d/Torus.prefab.meta +++ b/editor/assets/default_prefab/3d/Torus.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "d47f5d5e-c931-4ff4-987b-cc818a728b82", diff --git a/editor/assets/default_prefab/Camera.prefab.meta b/editor/assets/default_prefab/Camera.prefab.meta index 647ff9aef3a..92ea57a3a5a 100644 --- a/editor/assets/default_prefab/Camera.prefab.meta +++ b/editor/assets/default_prefab/Camera.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "bb0a6472-cd67-4afb-a031-94fca8f4cc92", diff --git a/editor/assets/default_prefab/Terrain.prefab.meta b/editor/assets/default_prefab/Terrain.prefab.meta index 66b2240a22e..7b34955b2f4 100644 --- a/editor/assets/default_prefab/Terrain.prefab.meta +++ b/editor/assets/default_prefab/Terrain.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "90e8b0d4-12dc-412d-9156-ea1fdb18c15b", diff --git a/editor/assets/default_prefab/effects/Particle System.prefab.meta b/editor/assets/default_prefab/effects/Particle System.prefab.meta index fc311329e1c..ac7961c5284 100644 --- a/editor/assets/default_prefab/effects/Particle System.prefab.meta +++ b/editor/assets/default_prefab/effects/Particle System.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "f09a0597-10e6-49e5-8759-a148b5e85395", diff --git a/editor/assets/default_prefab/light/Directional Light.prefab.meta b/editor/assets/default_prefab/light/Directional Light.prefab.meta index 89d28c73bdc..b92f75d0535 100644 --- a/editor/assets/default_prefab/light/Directional Light.prefab.meta +++ b/editor/assets/default_prefab/light/Directional Light.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "a0e9756d-9128-4f49-8097-e041c8b733b8", diff --git a/editor/assets/default_prefab/light/Light Probe Group.prefab.meta b/editor/assets/default_prefab/light/Light Probe Group.prefab.meta index 197281caacb..359cec2c3c4 100644 --- a/editor/assets/default_prefab/light/Light Probe Group.prefab.meta +++ b/editor/assets/default_prefab/light/Light Probe Group.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "50dfda40-7c45-4868-a876-2fe2a4c782f4", diff --git a/editor/assets/default_prefab/light/Point Light.prefab.meta b/editor/assets/default_prefab/light/Point Light.prefab.meta index 598d473c0e3..7d1909c9a08 100644 --- a/editor/assets/default_prefab/light/Point Light.prefab.meta +++ b/editor/assets/default_prefab/light/Point Light.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "03029371-ee64-4f14-820a-d495ad7cdc29", diff --git a/editor/assets/default_prefab/light/Ranged Directional Light.prefab.meta b/editor/assets/default_prefab/light/Ranged Directional Light.prefab.meta index 69df4adf90d..925508bc6e4 100644 --- a/editor/assets/default_prefab/light/Ranged Directional Light.prefab.meta +++ b/editor/assets/default_prefab/light/Ranged Directional Light.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "df72d0f6-49d3-452a-b082-8b23d38b33af", diff --git a/editor/assets/default_prefab/light/Reflection Probe.prefab.meta b/editor/assets/default_prefab/light/Reflection Probe.prefab.meta index 0e0ab920114..c38fbdde580 100644 --- a/editor/assets/default_prefab/light/Reflection Probe.prefab.meta +++ b/editor/assets/default_prefab/light/Reflection Probe.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "d8b49b64-cfba-4cfa-be53-1e469547b28b", diff --git a/editor/assets/default_prefab/light/Sphere Light.prefab.meta b/editor/assets/default_prefab/light/Sphere Light.prefab.meta index 1bebad0a135..77bee59cc15 100644 --- a/editor/assets/default_prefab/light/Sphere Light.prefab.meta +++ b/editor/assets/default_prefab/light/Sphere Light.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "4182ee46-ffa0-4de2-b66b-c93cc6c7e9b8", diff --git a/editor/assets/default_prefab/light/Spot Light.prefab.meta b/editor/assets/default_prefab/light/Spot Light.prefab.meta index d60bc004f17..5a000536c5a 100644 --- a/editor/assets/default_prefab/light/Spot Light.prefab.meta +++ b/editor/assets/default_prefab/light/Spot Light.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "7a49aa24-bd7a-40a8-b31a-b2a9da85abcd", diff --git a/editor/assets/default_prefab/ui/Button.prefab.meta b/editor/assets/default_prefab/ui/Button.prefab.meta index 22327fc1710..ad185655c69 100644 --- a/editor/assets/default_prefab/ui/Button.prefab.meta +++ b/editor/assets/default_prefab/ui/Button.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "90bdd2a9-2838-4888-b66c-e94c8b7a5169", diff --git a/editor/assets/default_prefab/ui/Canvas.prefab.meta b/editor/assets/default_prefab/ui/Canvas.prefab.meta index a398ec48963..7b7c6384ab0 100644 --- a/editor/assets/default_prefab/ui/Canvas.prefab.meta +++ b/editor/assets/default_prefab/ui/Canvas.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "f773db21-62b8-4540-956a-29bacf5ddbf5", diff --git a/editor/assets/default_prefab/ui/EditBox.prefab.meta b/editor/assets/default_prefab/ui/EditBox.prefab.meta index d72c1f6fb58..7661b7f8c90 100644 --- a/editor/assets/default_prefab/ui/EditBox.prefab.meta +++ b/editor/assets/default_prefab/ui/EditBox.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "05e79121-8675-4551-9ad7-1b901a4025db", diff --git a/editor/assets/default_prefab/ui/Graphics.prefab.meta b/editor/assets/default_prefab/ui/Graphics.prefab.meta index 7c692252572..7b4463acc28 100644 --- a/editor/assets/default_prefab/ui/Graphics.prefab.meta +++ b/editor/assets/default_prefab/ui/Graphics.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "c96e159e-43ea-4a16-8279-05bc39119d1a", diff --git a/editor/assets/default_prefab/ui/Label.prefab.meta b/editor/assets/default_prefab/ui/Label.prefab.meta index 6a5eacbd453..881efa9de41 100644 --- a/editor/assets/default_prefab/ui/Label.prefab.meta +++ b/editor/assets/default_prefab/ui/Label.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "36008810-7ad3-47c0-8112-e30aee089e45", diff --git a/editor/assets/default_prefab/ui/Layout.prefab.meta b/editor/assets/default_prefab/ui/Layout.prefab.meta index 01428016c51..5d691e90730 100644 --- a/editor/assets/default_prefab/ui/Layout.prefab.meta +++ b/editor/assets/default_prefab/ui/Layout.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "a9ef7dfc-ea8b-4cf8-918e-36da948c4de0", diff --git a/editor/assets/default_prefab/ui/Mask.prefab.meta b/editor/assets/default_prefab/ui/Mask.prefab.meta index 377ec5e6f5a..3792c4b84bc 100644 --- a/editor/assets/default_prefab/ui/Mask.prefab.meta +++ b/editor/assets/default_prefab/ui/Mask.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "7fa63aed-f3e2-46a5-8a7c-c1a1adf6cea6", diff --git a/editor/assets/default_prefab/ui/ParticleSystem2D.prefab.meta b/editor/assets/default_prefab/ui/ParticleSystem2D.prefab.meta index 7072bfdef2b..7fc1b6ea004 100644 --- a/editor/assets/default_prefab/ui/ParticleSystem2D.prefab.meta +++ b/editor/assets/default_prefab/ui/ParticleSystem2D.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "f396261e-3e06-41ec-bdd6-9a8b6d99026f", diff --git a/editor/assets/default_prefab/ui/ProgressBar.prefab.meta b/editor/assets/default_prefab/ui/ProgressBar.prefab.meta index e73a3746162..8f13645d809 100644 --- a/editor/assets/default_prefab/ui/ProgressBar.prefab.meta +++ b/editor/assets/default_prefab/ui/ProgressBar.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "0d9353c4-6fb9-49bb-bc62-77f1750078c2", diff --git a/editor/assets/default_prefab/ui/RichText.prefab.meta b/editor/assets/default_prefab/ui/RichText.prefab.meta index 262b90f4b93..73797af04e4 100644 --- a/editor/assets/default_prefab/ui/RichText.prefab.meta +++ b/editor/assets/default_prefab/ui/RichText.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "fc6bfcfa-8086-4326-809b-0ba1226bac7d", diff --git a/editor/assets/default_prefab/ui/ScrollView.prefab.meta b/editor/assets/default_prefab/ui/ScrollView.prefab.meta index 98626d08d6f..92ae53d13fe 100644 --- a/editor/assets/default_prefab/ui/ScrollView.prefab.meta +++ b/editor/assets/default_prefab/ui/ScrollView.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "c1baa707-78d6-4b89-8d5d-0b7fdf0c39bc", diff --git a/editor/assets/default_prefab/ui/Slider.prefab.meta b/editor/assets/default_prefab/ui/Slider.prefab.meta index 70669c12858..14586825920 100644 --- a/editor/assets/default_prefab/ui/Slider.prefab.meta +++ b/editor/assets/default_prefab/ui/Slider.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "2bd7e5b6-cd8c-41a1-8136-ddb8efbf6326", diff --git a/editor/assets/default_prefab/ui/Sprite.prefab.meta b/editor/assets/default_prefab/ui/Sprite.prefab.meta index 26cd9988001..c0f94735dbb 100644 --- a/editor/assets/default_prefab/ui/Sprite.prefab.meta +++ b/editor/assets/default_prefab/ui/Sprite.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "9db8cd0b-cbe4-42e7-96a9-a239620c0a9d", diff --git a/editor/assets/default_prefab/ui/SpriteRenderer.prefab.meta b/editor/assets/default_prefab/ui/SpriteRenderer.prefab.meta index a257d54f0a8..77bbcb0ab98 100644 --- a/editor/assets/default_prefab/ui/SpriteRenderer.prefab.meta +++ b/editor/assets/default_prefab/ui/SpriteRenderer.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "279ed042-5a65-4efe-9afb-2fc23c61e15a", diff --git a/editor/assets/default_prefab/ui/SpriteSplash.prefab.meta b/editor/assets/default_prefab/ui/SpriteSplash.prefab.meta index cb3d5a47078..2727ee85ff7 100644 --- a/editor/assets/default_prefab/ui/SpriteSplash.prefab.meta +++ b/editor/assets/default_prefab/ui/SpriteSplash.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "e5f21aad-3a69-4011-ac62-b74352ac025e", diff --git a/editor/assets/default_prefab/ui/TiledMap.prefab.meta b/editor/assets/default_prefab/ui/TiledMap.prefab.meta index 53301ade82b..3072ca81b17 100644 --- a/editor/assets/default_prefab/ui/TiledMap.prefab.meta +++ b/editor/assets/default_prefab/ui/TiledMap.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "3139fa4f-8c42-4ce6-98be-15e848d9734c", diff --git a/editor/assets/default_prefab/ui/Toggle.prefab.meta b/editor/assets/default_prefab/ui/Toggle.prefab.meta index c05f65aa9f0..4aece91b233 100644 --- a/editor/assets/default_prefab/ui/Toggle.prefab.meta +++ b/editor/assets/default_prefab/ui/Toggle.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "0e89afe7-56de-4f99-96a1-cba8a75bedd2", diff --git a/editor/assets/default_prefab/ui/ToggleContainer.prefab.meta b/editor/assets/default_prefab/ui/ToggleContainer.prefab.meta index e3affc6f159..9c6c100db85 100644 --- a/editor/assets/default_prefab/ui/ToggleContainer.prefab.meta +++ b/editor/assets/default_prefab/ui/ToggleContainer.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "2af73429-41d1-4346-9062-7798e42945dd", diff --git a/editor/assets/default_prefab/ui/VideoPlayer.prefab.meta b/editor/assets/default_prefab/ui/VideoPlayer.prefab.meta index 24a28385ecc..fa11ee31a45 100644 --- a/editor/assets/default_prefab/ui/VideoPlayer.prefab.meta +++ b/editor/assets/default_prefab/ui/VideoPlayer.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "7e089eaf-fa97-40d7-8a20-741a152585df", diff --git a/editor/assets/default_prefab/ui/WebView.prefab.meta b/editor/assets/default_prefab/ui/WebView.prefab.meta index 7ad242dfe02..2085c013dbb 100644 --- a/editor/assets/default_prefab/ui/WebView.prefab.meta +++ b/editor/assets/default_prefab/ui/WebView.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "9c541fa2-1dc8-4d8b-813a-aec89133f5b1", diff --git a/editor/assets/default_prefab/ui/Widget.prefab.meta b/editor/assets/default_prefab/ui/Widget.prefab.meta index e1ed816df8a..68560828750 100644 --- a/editor/assets/default_prefab/ui/Widget.prefab.meta +++ b/editor/assets/default_prefab/ui/Widget.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "36ed4422-3542-4cc4-bf02-dc4bfc590836", diff --git a/editor/assets/default_prefab/ui/pageView.prefab.meta b/editor/assets/default_prefab/ui/pageView.prefab.meta index 3c4e7e8d451..000fc2d496c 100644 --- a/editor/assets/default_prefab/ui/pageView.prefab.meta +++ b/editor/assets/default_prefab/ui/pageView.prefab.meta @@ -1,5 +1,5 @@ { - "ver": "1.1.48", + "ver": "1.1.49", "importer": "prefab", "imported": true, "uuid": "20a5d8cb-ccad-4543-a937-fccd98c9f3de", diff --git a/editor/assets/effects/advanced/fabric.effect.meta b/editor/assets/effects/advanced/fabric.effect.meta index 8a62097c4df..8940a2567de 100644 --- a/editor/assets/effects/advanced/fabric.effect.meta +++ b/editor/assets/effects/advanced/fabric.effect.meta @@ -1,5 +1,5 @@ { - "ver": "1.7.0", + "ver": "1.7.1", "importer": "effect", "imported": true, "uuid": "b25c7601-1d07-4a56-86c5-62e83ea7c61e", diff --git a/editor/assets/effects/advanced/leaf.effect b/editor/assets/effects/advanced/leaf.effect index aa690844804..f43c3248f56 100644 --- a/editor/assets/effects/advanced/leaf.effect +++ b/editor/assets/effects/advanced/leaf.effect @@ -113,7 +113,7 @@ CCProgram macro-remapping %{ // ui displayed macros #pragma define-meta HAS_SECOND_UV #pragma define-meta USE_TWOSIDE - //use FSInput_faceSideSign for materials #pragma define-meta USE_TWOSIDE_MATERIAL + //use FSInput_faceSideSign for different textures #pragma define-meta USE_TWOSIDE_MATERIAL #pragma define-meta USE_VERTEX_COLOR #pragma define-meta USE_DITHERED_ALPHA_TEST diff --git a/editor/assets/effects/pipeline/cluster-culling.effect b/editor/assets/effects/pipeline/cluster-culling.effect index 9c418b01383..510d4ad9bfe 100644 --- a/editor/assets/effects/pipeline/cluster-culling.effect +++ b/editor/assets/effects/pipeline/cluster-culling.effect @@ -88,10 +88,18 @@ CCProgram cluster-main %{ vec3 center = (cluster.minBounds + cluster.maxBounds) * 0.5; float sphereRadius = sqrt(dot(halfExtents, halfExtents)); light.cc_lightDir = ((cc_matView) * (vec4(light.cc_lightDir.xyz, 1.0))); - light.cc_lightDir.xyz = normalize((light.cc_lightDir - ((cc_matView) * (vec4(0,0,0, 1.0)))).xyz).xyz; + light.cc_lightDir.xyz = (light.cc_lightDir - ((cc_matView) * (vec4(0,0,0, 1.0)))).xyz; + if (length(light.cc_lightDir.xyz) > 0.1) { + light.cc_lightDir.xyz = normalize(light.cc_lightDir.xyz); + } vec3 v = center - light.cc_lightPos.xyz; float lenSq = dot(v, v); float v1Len = dot(v, light.cc_lightDir.xyz); + if(light.cc_lightDir.w == 1.0) { + v1Len = sqrt(lenSq); + return (v1Len <= sphereRadius + light.cc_lightSizeRangeAngle.y); + } + float cosAngle = light.cc_lightSizeRangeAngle.z; float sinAngle = sqrt(1.0 - cosAngle * cosAngle); float distanceClosestPoint = cosAngle * sqrt(lenSq - v1Len * v1Len) - v1Len * sinAngle; @@ -108,7 +116,7 @@ CCProgram cluster-main %{ layout(local_size_x = LOCAL_SIZE_X, local_size_y = LOCAL_SIZE_Y, local_size_z = LOCAL_SIZE_Z) in; void main() { - uint visibleLights[100]; + uint visibleLights[200]; uint visibleCount = 0u; uint clusterIndex = gl_GlobalInvocationID.z * gl_WorkGroupSize.x * gl_WorkGroupSize.y + gl_GlobalInvocationID.y * gl_WorkGroupSize.x + @@ -127,7 +135,7 @@ CCProgram cluster-main %{ } barrier(); for (uint i = 0u; i < batchSize; i++) { - if (visibleCount < 100u && ccLightIntersectsCluster(lights[i], cluster)) { + if (visibleCount < 200u && ccLightIntersectsCluster(lights[i], cluster)) { visibleLights[visibleCount] = lightOffset + i; visibleCount++; } diff --git a/editor/assets/effects/pipeline/float-output-process.effect b/editor/assets/effects/pipeline/float-output-process.effect index de8f2d61d44..153ce534f7d 100644 --- a/editor/assets/effects/pipeline/float-output-process.effect +++ b/editor/assets/effects/pipeline/float-output-process.effect @@ -42,14 +42,15 @@ CCProgram copy-fs %{ CCProgram tonemap-fs %{ precision highp float; + #define CC_SURFACES_ENABLE_DEBUG_VIEW 1 #include #include - #include - #include + #include #include + #include + #include #include #include - #include in vec2 v_uv; @@ -60,16 +61,7 @@ CCProgram tonemap-fs %{ layout(location = 0) out vec4 fragColor; - vec3 HDR2LDR_PostProcess(vec3 color) { - #if CC_USE_HDR && CC_TONE_MAPPING_TYPE == TONE_MAPPING_ACES - return ACESToneMap(color.rgb); - #else - return color.rgb; - #endif - } - vec4 CCFragOutput_PostProcess(vec4 color) { - // fog related vec4 worldPos = vec4(0.0); #if CC_USE_FOG != CC_FOG_NONE @@ -79,22 +71,23 @@ CCProgram tonemap-fs %{ worldPos = GetWorldPosFromNDCPosRH(posHS, cc_matProj, cc_matViewProjInv); #endif - // todo: apply fogColorBrightness to fogColor for supporting scatter lighting with HDR + // HDR fog + // todo: apply fogColorBrightness to linear fogColor for supporting scatter lighting with HDR + #if CC_USE_FOG != CC_FOG_NONE + float fogFactor = 1.0; + CC_TRANSFER_FOG_BASE(worldPos, fogFactor); + CC_APPLY_FOG_BASE(color, fogFactor); + #endif // tone mapping if (!DebugViewNeedDisplayOriginalData()) { #if CC_USE_FLOAT_OUTPUT - color.rgb = HDR2LDR_PostProcess(color.rgb); + color.rgb = HDRToLDR(color.rgb); color.rgb = LinearToSRGB(color.rgb); #endif } // LDR fog - #if CC_USE_FOG != CC_FOG_NONE - float fogFactor = 1.0; - CC_TRANSFER_FOG_BASE(worldPos, fogFactor); - CC_APPLY_FOG_BASE(color, fogFactor); - #endif return color; } diff --git a/editor/assets/effects/pipeline/post-process/post-final.effect b/editor/assets/effects/pipeline/post-process/post-final.effect index a1c6e08c4d2..3d8aa313f37 100644 --- a/editor/assets/effects/pipeline/post-process/post-final.effect +++ b/editor/assets/effects/pipeline/post-process/post-final.effect @@ -33,7 +33,7 @@ CCProgram fs %{ layout(location = 0) out vec4 fragColor; void main () { - fragColor = vec4(texture(inputTexture, v_uv).rgb, 1.0); + fragColor = texture(inputTexture, v_uv); } }% diff --git a/editor/assets/effects/util/dcc/imported-metallic-roughness.effect b/editor/assets/effects/util/dcc/imported-metallic-roughness.effect index a385d707dde..2ab10f99fb8 100644 --- a/editor/assets/effects/util/dcc/imported-metallic-roughness.effect +++ b/editor/assets/effects/util/dcc/imported-metallic-roughness.effect @@ -25,7 +25,7 @@ CCEffect %{ emissiveMap: { value: grey, editor: { displayName: EmissiveColorMap } } alphaSource: { value: 1.0, editor: { slide: true, range: [0, 1.0], step: 0.001 } } alphaSourceMap: { value: grey, editor: { parent: USE_OPACITY_MAP } } - alphaThreshold: { value: 0.5, target: albedoScaleAndCutoff.w, editor: { parent: USE_ALPHA_TEST, slide: true, range: [0, 1.0], step: 0.001 } } + alphaThreshold: { value: 0.5, editor: { parent: USE_ALPHA_TEST, slide: true, range: [0, 1.0], step: 0.001 } } normalStrength: { value: 1.0, editor: { parent: USE_NORMAL_MAP, slide: true, range: [0, 1.0], step: 0.001 } } normalMap: { value: normal } - &forward-add @@ -55,8 +55,8 @@ CCEffect %{ properties: tilingOffset: { value: [1.0, 1.0, 0.0, 0.0] } mainColor: { value: [1.0, 1.0, 1.0, 1.0], target: albedo, editor: { displayName: Albedo, type: color } } - albedoScale: { value: [1.0, 1.0, 1.0], target: albedoScaleAndCutoff.xyz } - alphaThreshold: { value: 0.5, target: albedoScaleAndCutoff.w, editor: { parent: USE_ALPHA_TEST } } + albedoScale: { value: 1.0, editor: { displayName: BaseWeight } } + alphaThreshold: { value: 0.5, editor: { parent: USE_ALPHA_TEST } } mainTexture: { value: grey, target: albedoMap, editor: { displayName: AlbedoMap } } alphaSource: { value: 1.0, editor: { slide: true, range: [0, 1.0], step: 0.001 } } alphaSourceMap: { value: grey, editor: { parent: USE_OPACITY_MAP } } @@ -96,7 +96,6 @@ CCProgram shared-ubos %{ uniform Constants { vec4 tilingOffset; vec4 albedo; - vec4 albedoScaleAndCutoff; vec4 emissive; float emissiveScale; float occlusion; @@ -105,6 +104,7 @@ CCProgram shared-ubos %{ float normalStrength; float alphaSource; float albedoScale; + float alphaThreshold; float specularIntensity; }; }% @@ -237,7 +237,7 @@ CCProgram surface-fragment %{ #endif #endif #if USE_ALPHA_TEST - if (baseColor.a < albedoScaleAndCutoff.w) discard; + if (baseColor.a < alphaThreshold) discard; #endif return baseColor; } @@ -265,7 +265,7 @@ CCProgram surface-fragment %{ #endif #endif - if (alpha < albedoScaleAndCutoff.w) discard; + if (alpha < alphaThreshold) discard; #endif } diff --git a/editor/assets/primitives.fbx b/editor/assets/primitives.fbx index f93a097616a8ad8a93a4c72736061aaa7b8fd4b9..1ddad73bd082bda2cf04aee3b1cf8516e996080e 100755 GIT binary patch delta 129243 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 delta 99634 zcmZ^~2~<1PDoh7(xgq>Dy zVE@NY(|>cEJj-p;q`E1{{F!quc>3E6UWjqG9(q0rxn?ux!lR!i%j_mi3b8@neBwBD z2Q@nW@YateO`60+ez$R)YP;z>+E#n;lVgKCfE|%bHp^^0kUwk|PWA!^XW+0$AQqeH zxT!Ya1P&V)ss(^#0GJuMx%vo;J&o`yb3ghCjAjcdEQ2;%Ya78~ckZGpWG z{(mkBMJ|0DV1xhrk`FLbU7WHLIEpl7;ioH$mrDdopVU3B7Oc4IF+K)OMShv$G23(H zYVVb6*3Db9eqyPm+3oj9D9Vj+zA<+ z?q!SGPZ*DA57`DgOv;>}Z|jm-bk}w|a>ssyO$qYW{u>)DveAKv^PX{Wo-10c;eG7`)GjaOSMTNmu@J{~wOPUQWnN$2GXcpCN8eu9?)UwjUvpjzpWY zi2Y}7$aBZdHbY<(j@#vgymWO%e*0{b&1IzJvp^dY;_CDR?)$mmwq?jYCtuv{xfh+6 zA)m~3#tmCXk!M_2zO=6aD*hgS&MR?s#z0(}sy~-FE|65mU$peOBdVJSVxPNEym4Rj%-^-!hO9Q$zJxkO*YcCEWk#D%wO({ z1D9eMeqy;7E*Tr{vMIah;b)`0=;4M8db{C5SAazxvK6eb`fGD$NGgaqp~t5<{7U4L zbxxV{*tQ;-ud{3?BkUEkaFHvq7|r~VZA-9y2h}0(R@}kqRwK;SOQr-Kq(&W#kEI=6 zg@yQJjApx6gES#b-f=MQaLwv};@p++cb?cJUnDFS?{0Dl;(3AnG;Skngp!lWETxSELj#p|EP8rE15XvdnhIKQ>XPir8M z8c)^)<9gRx)5>e@8XE|4@N~m4Z2>~^oQ!bS&a(LxIqK<#NY<{f=>sRf!L42gAaW}% zb)9vNLeI}_l*o*A6rA^Z@bqmqnFw!P7*4hx^Jiv3fvx8UlP2xl0Aj-lIlX>6E^h-! z6<0*PegkfX7qV9Kc0xEoS!-XI&jd~vh9F$A$K^O=|Ko*gjyR;!2agl_{%snYl8e53aC+a101G31SJ-|{u*swxw!u&SefrTHM_lIT zUbb`LfZf2T;GfOJpGox?CW3z-?LQbBefBm&Y4gmXA0S>9|xkCxgN zneRkRq^IA zN1|GCOPra;;l~J3+IH7Js#Jfs_9ghCZC?_@`4^LjNv5<@|sw0&?d=HCZQBkRvejhVz2e;dp zZ#qiX-Xif~a#Mj9^O@$ZK8#A2rKZGHI#F8;gt)M4iQPVZnfev2BH~%)&< zC4`8odsJ&Jc%tPFEUZY9rnfK z8Hf5wEGbn$D@bBP`m-g&)ufGn23^%yl^h?(&Aaw z|59*}|C)_9tG21nLHZW;bV_~7Ey^p-yK`P#i4rpI)Kc{|40}AQ(tNWNMz zG;kQhryP2`x2a%B~n2KSI@pyWnXB zaGB=G84CJmC`Ud1U-S2qo&{Ewe#{;EI+JW+T3+k43*A<6jNY(O5@#y?D$Uul+#Kf1 zlDCwe=t8MP6QATpkI=>+3{CNhNg5>4H#zVec^ z>KOA{VyC*eTRkdXZYd+4fL=Hb$4xQ?{I}^t7{K!RLYOfQqR3S9v=70{U^azO1V8)f zpIK*8I@NC-6OH3F2mwTzCl)w2UpaO%B#!wMS8;VuCTm=-!Q;)2nw2r*u5?{WD(d1d z7-bWUX(DD(5{mvbNWeplVPcb~B|9cs$QUsMSH^KTk)cfSt4N-!&cb6!%DRIEWttpi z7yOoKcJFq9(2|sI2rRfJ0!;WUqCWN^>RP2lYieYUUY2p!Dhg$=R_jQ87shG!r|c)X|Ow4X;JCI%M2guvQFwW0%2eonr>95h-CS)l1EWp@U!CCQr%q;fnf}t zWwb$Mv>!ItYro1ShN%booLQB*5KAu8N4S)!xf0czm9TML168PGeZRhY0L}11|9o%q zzW*}$f%=r=tQw~wmbq3dOMIu%1tw~I)Tpc8LLc@hUUf8?MJ3cKr4-A}l2xbqD!f2A zq}(Q$Yf<}Dh}k_vQY(6`i_&w1+VYNOhu6(C>1@k10&#v%IC1s>-0@lM{vXBk4sU-! z**FNlcd3ybY$X8;>G46T$JZS_ad$1P@1C58XTyZ9I+C|QTT(;Xm`woyg@PDijAfOe zL7Kj=9sOBDM``0Nw@AiOZlSyyq$Y2Zu`yDU`u=Gxc)n#gA{xYXKB;2zj=y0lcTc&yJF~_Npe>x+YO(fFgJA{{mt9Ih$V549tD4kM%AwVJJ|(Z zJnfH7(%E;TMq83t8+RGDvI{(T-roL1MZ5B;JGh`=Tmddne8nBg2n&YjF){RP0-X;Y zT_$h&Fuyi1l%T=qbGWc$@KCl?)e=_YAVY}6h*iWQ`_VfA;8jG0Z@pz8M*rw(%RXau zjt};3C(^_m?rZ~phpWD#>=vb*;NPsBnO_rEb!)t5RKl*))iNHc1(8oE>(uoxOPUp3$U?!Lqa~FeG5|R=LPfQ<}iYZ&uUI+$*9oX}x=N z_h*D$b@Xgf&O0yzTax9t^cg}hx`=z+r4CT(NpfWl6`h@uO95FO!y2!7JOmQNA(O;1 zj>`0}YZDUj39T^qNueZ#Yy3syL2d6bO62$kLN22xUtDbNY2C=Y_LA8Q7lQ1V8L=M! zCpw#wMG1jfG3Y%YSv3fxYJi(9Y7SeS-FYRzDGPa*~ z&NeaRysei*LySvI18x74_7m^u$p;)u=`8NQEXARO@S9XUf&S=CK^QkiN~sZhJjVh_ za>KO&CghsI%+hpn-MxrWS%tR}-@x_m?^BF#5nOfXG5(rYPbh)}WsDJNz92mGvPGwq z$Tv!*tk6+G!Kj5g^k0+uvQ!|v!Q1!L`*>}O_i@d)a|2W38j5$j1xvMK4_BpCjT$c% z$pNCsQYaxEtPogz9PNc`g|vS98)kPX!QRR|ABLhI%SyY`^eLLue1P15!bq$@(^FwW zOf@0CJZv;ZYxtu@y(7DY@e$in)p|k-)`wZG0bCFyY+NreG=-bR+|*3AMcg-1UYWhV zXjMe_$pWcd^H#x1Q@%|@V$O~0bU`b*wBu>8^o(bH@!W4pPKvT#R+P19wh@SeGTJjj zQ4~}pNGfCGi=VRTBimq@=dDdXYlS=DDro39E2fqW=>0lm%GFMTL^V&QhPKue64gzr zx$t`Yb!leUdv}6~1q;7=BdWn1D!G|Y63@Y>Dr-8P0*@Nt*JZxBM2Di`XF>C?5{C-s zKKp57(X*ej?0_pI*_wOlwXG6ShjnJb6}KEYlx#dJCdy!?k1S(JmEu4}f2Nrg>O#Z- zg4^H$^%z?_f-J{@{BV#tlKAglP*cNmbCi-Zlt!6c^G(Nj5OBIr3$5zoaS7~fF-r8B z)R(n{qTH5fR=q`8k(8t#J)eAbj#9FtN3pgqD#X>DbH2c^uIQPoyY%+!Kx3t{=1Kj@ zBQ86j`r;{XN}egZ5<)u%(3PQ5i6%8G!YFAUlj>7bUV5QXw3g!d)|wuTnRs+S_bGQG z8-l69*^dBvxk4;h-J8l9Ngf~6ZEmVKTy;Z~5R*qTRu>e;sL7h|2%iW>+rj*>XBDPi zO1w&{=b~`wDmOR&yY49^UpsCVR&1v^lWSi@NEOj8(_7h(O6lwEJ7d<{1J7`ty*aT% zbFNuwo!#O&|6{yLeNO+Cgg0=-iXzAJR`&fhgL9mvjOu9YR=Nu8{ILLZr;PWO4y_g5 zO1SnCdkDEUkx+A#_=_f(+HosKJ)G0G8DEW0n%{hJ;R|!kD2noFZ0e*3BDGCDlfMbfV zM*F?T1&pZL#N1MV`#}_z`cWQmE9T$csT^Y({WM8=UHv6yHmf=)?cU%IO*jBJ6&P(H z%VO9iPfK_svm}I8?bT6(fn%@7BwQKUP?UxU;b$#f-pk{qN8H&lZ-HcVDWiQG_O{8- zqq$^`pxa;2;s8t{9W_dTXEjg%$%+vvBItLdgb=R67@ibp-V@W-|01T5jS44XVd}G~ zp`scr=q6CEu++(-n33wmw@G~tW^Jmuv17_MHG0yUo!|4cZj8r;|)kFD#J$g zqneK8=3I4}vgR6@!s^?xk^WC!G*NXl9STd{F6P}!4K^vpmkl zSE)tgcwxu1h8{ij!79D(n>n!HOcJUSZ_wRiDx)NF|ATc z-u5pRi2ijgm?+?MAK|^!VNGj zhGuKoz;aK?n)!ZaEO!CWnb4ZPjiQ<;|7{{;v_u?AD2a#^e;?`+653E@N3Ly>J!vty zh@OB{eJ;i$B9IE5eya)Zj^yrB9Sh8+#~pCA%a>(_y=N^Ty(efZ#2>9usq*N|;UY&n zGB<<6`&r|``kS?elQ=9PeG6QadeX51n9Cn5tN{IOd_rGSm$gST;CXC}lH$aHi6P0)!oPH+~* zF`q?HFK3NgIV;~4aOnS1R?;;LAGv8%$-VvnZ-MbJkUalR$=66+3lT)Afm%xnwGR;2 ze_N^x_EtQ9JZ_Etw1Swo{ZD+-7B=_@mw(-1A4Vl8CHsY{_5X$!NU21g8Rgt59S_8~%ghvc@w$b5QBDLlD0 zj9yDM=N@a)sm|Y>$!u?f_^Q8{w_J-@+Of~bEl{U!9;)N*O1SAokg38;%qBxeKY^*p zFx{yQ<4V}7$ERI`Va*;Y&+U=HvDSFwX^UYlg(r`&1g{+(=i@gjc(;Z3$ebWm5q?N+ zC@!Q>g9RDo=i_>__$kgjRoloms-Mx`!ZhEu4D+04)!*~b}ZNggo7!CQFwdN0#;b5CA(C>7@2DIIUkqLW4YIp^GC`c)gLM|7%< z1&P=4a!tBRdEE-Jz;3AOD~^cAdL?Fe-_5B=;kJ^KB(e-xoRwyI=4g)aw-jgs;=zAO z3XZ6~Jt?qKAxIfh`{M6-nVx$w3CRUHBX_)b>USP|G;imq;-=eZUKp=?l+X2*z~0cP zDp7;T+>f;!VY$`ho{;6*t%0cX^>>9`n$PE|gaKI`*^FUyZmxJ9Uv^+ocbIRdhR`qD zwy43I(rh?3+UhPisnfn7tTG!J=L#k2rvzS!smv^y&QEgtkHi6fY;L^8Q9^N6oVL`F zq)nR7KNYIqlDkLQtbH)&U6065E=xga^Md8C+*vhV0&=YvAm91fl$b{VsY%R6>Tysp zA0c|>Xz<+}yYE9~s84WB7Kf6~DwEl%PbPQoLJ!NPS1B~xCA4mbVcM29sFq~8rSlUG z*Q#?lfwQ{Gih7w*l1h7ZYLg~4RY)Kjw+q?r%Z2Y?$RAn`q+s<05-S^Gsmsl0Q!zHq zHBJRe=2vObyVQ*=qvS=Vw^345tkL9^G=f%0nD#6usSF^{4A*}ILM@>IFDR1*w&KHA?M3ZZ7@l|F=^6vVgB?) z^H){8c4Jw0GSP!bbWh*lJRJ9-0F;R1XCQxWLP63~wrt;ADz$^q03LL9A;%aXNFqj! z#+jOOwaJfDC@eZ}KkGVPdy8NJR^*s>K>nN)9$ntuU8>hQAycC{l84tEp%bIvGT|ox z6GSXwrmGt5(MbRpm{KTo01qjA1E`m~HAk5IJ3S z!{!W1r;OJNmm7Q3j)D!Hs-lXS)N*|6Wr+tE6-q zFI!A$g?ccJFl~V}Yg~F>)xpl#UVCkD{5(WxlCx)jbz=rQwf0p75YrI4!XBK;{b z{ozKfU*naH6u4I-dKF5Ev-rg6^4g#{j`&k*KTCYU=!qAIS8$n;W)m@8+@5%@03uw( zYE95Vg1F8y@kIT!L{RSl8ykgMnDhlEDE4GxBDN;RZ@i{SAU6miesZUziY=zOoyMgC z&I`tqdUsX~#!1E&##@N{*wwYRR0s}X#00ugY>KhRo&PRbV1d70ed(N}_(f2zv(4?| z!%;P#;96ko{vq4jb~diQ-a4k1;y%MI|Is?0;cN48=6#~=2e|D&g3k)(*|LAMw)#J} zeTfZxY<)fndo|Hp|GU>7hkWMGADOrB4?CU#a4{(c9 zX5BtV*!yv0>5ADGKRL1WNB;{hSJ;n@C4qWW0O39*il$|28! zp4*v5_K-ssHjFAH`G)3)_n<<|CSZm8@RGITfj0&(=p$F|M!W z62H&x)P{qV_cByLVnPec;pV0L+=fp*?e@v0e#>BRDZB#!VAP^Gp4DHlXr) z;fMf|ousk;aF6|+8o*F+&WYoU$s@&o>8~^~FLIWq=;v1lniBR~j=>2>1TRSGd49h$ zLu8j;0gG3{!#%}{*6(!%m8M@M?$58WK2mRmx_1FLu=Ej^(6iPVHwwjM5UY3!6lAXr zI78B_ukNNASwp-18=36b+uSc1jY7%_?XBdi{uKVMv^*C@$_&mZufBG`pd`O!Z1(xf z0ehF|ReATc{)oh`=_tCHc=n`e##;ntF~&vxrXlTd`Yj%Q^7|_x@GzaBg*j9Zn>J-) z3LfOcPzx&iy7n#NjTuE@qV4O$TPJwkn5We0Z!t2snR>qxbK-tZ2Kj}a6$(XNh=aY z?0w+e0nKPDS*|l(;hH>!P$cmVC(rLU?$hB)T~tLX6}|tgRu*bAEMknA3zfk?c`6cuVfMC9<%t*V_#qHr`vc6_rcj>cf`53mR!{XB*nJV%rx9F>{ zaJu$HzX0c~mS{r0;;9Xf|N1-JGas==^p8>bZrfmGpWiN(+brp2E_8^}DCVHrQFyUBU?B*fxZtSs9XV`S5^SE)T$ptaLdpyZI-gQnI8&5JKrh|HQ;ty*X{fn< zX~ca4@iVbu0}PyD3B+5z9y`LS@M`0Dog_(?I-HTRsz9_is={Infz@ri+pmQ0CMR2L zIMov-El%E>sUuT0#fmlnONt zXUnFmIxq|_#0w~O(8$cbr76u5HU)l`3a7qD!=GUQDN6y^3E3m;zf&l~<0w=IwK-rj zVS6W-Ke0n^l+v3Hm=3JS-{R9u4l-X5Ucw?x?x;h?g84-l)LKKaJB8uz0iq;5fH_-| zN9>!KBNl1C)quAPe@KH!d;A+8#J-%1MfL$oA7>>Wtt~L$0_=*mQt*YHhN8$vx+Q&=@~UB{uy5g=#$OsE zz6!w4#%sB(AI!vSXmBAxEA{HCIt5%u)6&NH^^+LQaDM`4MnnYbw0kC0`)h2v-P+pL zYr3e(04(T+JdNhNa-~1bf0*?&cHeK-#AC7ep_%o<%V`OMKV1fZKlJX!+BQI(VPgYO zCTDf6ViO#pi`=SQ763OU zByv`14~=S%2?>BG`)t%Enu6&J?pK~SJV*dE2>oEkXk_oF(hH!W~EJz}Jy z00_`lPb)Gu3>y3~*Y`Qa7+Ewie0uzLDP5|hSYb|)0x%}xC5CFF3Fa59pC|AtjM^y} zD>we)^BTBQOG<|o=w9;$4hF){?ZCCham|;gyJ0@(k0*cA`8I&J08T+*K2IUCV^4Lb z01lU>FmNgQRW(6-c8lcNtb0g~4k=p`~T$j^l!=y?FhnmbsE zPIe(i8yo6Y(sI8c4y15OR0ebdtP+NdiT8r2G=Xdw4Eq-Hk~NLR{aDcg)>S8K6BE~c2X39pWf<&B|16+0!# zp=#gzB0<4mWf!;Iw4*l5dp|ix0H6UsTDkEn{9iISVyh1PH!z*!1pBvKFX_iy1`HybtfM?|Q?->dDKZUO_$WQ6e( zNs`1iF$a6Gz*Bi3csliy1f;fJWxU1Of~y}d@xHi*fnyU=lb zll^Ga84@%1h2Aq&8*vGaU24QKAxjk)nMLjFwmjB#&h!VrS*HTG6d~{#e|pNftvq#BueDaX!G+^a6&F>1KR)m;AqTFNz0}j*6ms5}eg!e|Ga#~Q+jI{@sC;A1 zU_NY!gR(>p=pyTKO~|k+j24;8R_+wO$4Wj)8OZ8C1e<^gnk{KA-&_l7^#(=#=&Off z1EtjT;on%BFKFbe{v0Mp)B=t7WB*g7@G$7;H#z{uPG+6ILA49YRTTg_QdIQjVdvW@5mAXYK9uDsKn)nivjABHOe8SDuzSAkk0 zZDtCFMKkgnpkQwnp6O2*V=Ta`+=qb2mto9$lIi>w10Ta3K-j+lu_QdxGzL5h=s4!t za_oxYVtTQF(u(Pa43A#$Bx~Y+fL`Ku2}Uk96aYDzC*nL3u6G0Z3}e_wyui6u2Sos5 zX#;+9V7m29;Wjorms1GF350u4+eP6q2Eji5a9 zU-Sh)-~kH9H1h@SQ%?Re%MbA1EbeYeyh9x|zj7Abrf+glJlzE?x;EURO%O4St70mL zFW?)ww_%=T;|~N=W6jo5qdYT~x6|S**#+IOCCc3mv{RvEV|-#dOjtCb!^LgFBXi-3 z4vq1g_#i}Ze2@Gl<79*3WEbZUWD2em?l}W^{?SfFXKZ>J0H$U2Dc%8z2giz?hg35` zIxZ!j*&~~Sf^u|795+yr^^(3*cLXT&dHQzmHFJ71xXV@^7Un3VI()x&u5pE3cEwd8lGfiM@}4*KxJE6H`^y=ACL$@MuDID)gY{3QBE(10orhRet95}CgRBBR}KOo z@g&w-2rIun?(+hgu+a38YYurKL0}?SSLl8D4K9W!FcQ-Qv76O#7eO`O|2e2h|AMv( zcjO2fAgr$vbDNrHEPm$Be0b5Y{AGE5_Z{CyexsKh=;NDI-V6L=__s5y7U2cYEvjqtPc*?p&2;;BcBhiN;(qK3xrUx1q+v2L^4 z)4$v=AH>(Sp3z_*%NykuwP(IGZ&Xl?{|`wDwA;1{`@&$$pt-!y52#Jzj0mt5llW_1 z=r^RR_}-KJ$Y+(Ks=+9U@e-7o${`MU#helU#5(+}vM@WpYAB=K(&8=%`~;q73TLSe zaVBq@D5-Ib-#zPMC@lJ%@mhFROU)3H^Q`Xw@)~u^+~!$uHnUw?EWfH z!_)EEr&5PgNdF#DGI)g{j}s)QPo zN^2)svEMzLYr2D8(_F+X^)-$4!eQhK@p|dqz$|YM;l`LVTw~k~aVa}j+UNmunx%D6 zmiIE@qw>BL{1M?@VjmJh?YzxnUm*3~@I$yyeOSC|J07Sm+JwQs<@xQ3J26Jw!e}g& z8nm{=U>+wiBH_>chzF(?MGjkkI?wl}@?7ka0_T^mff)^JSx|?1Y5tJ&;BNB^Wrw1TX1v9IAWG&vAnY=?RX^}u!ylq6 z3(g5tie^~ZNlh+x$w`h$nbJbpP>^f?&cin8Q-!Abp z^adpu!fFN(_Xc?r|GDV#L)N;T^ttYw8#>?bUf$1IS^EpD-Qn6%r>09;k06a7C|bAe zfl6a5`q7(c1|G;>bCH($aw>(3L&n2bwRpP2)^=Z2U@151-i#eJ(z<$p8FP?We$@x%ifau7TPxDs1Fa z`;u~wH~b*l%4jrH`9}QpT&0Aru%y-4Z}jp z$ky!Y(4hj*dY8rU`#FBw%9qp~(SqpdlaLG_@Rh+0M6VJv{>MC*-9k@oR5nTf*l=A5 z7{&?fk~TIaU2bZ|55aMvj%r>}dhk{Kydr2uM6}|rV%r$#aTDw$@gCX`))}@wos`CC z94+YUS{iXUS9N#ldmH5Wg{vS|0_vYKew&2$&d4r*kx&O+#rIuWZ?%9^ll~2=!&j=R z-Tlv}_#B}$v-EZY9p`T3$CQOhw(kc!jC;Wj9O16cceEw8s@pr_p>h#X&J4@(Jl2bu zP(!e zJzs|&7_tn;g(h@g7YytA63@f&&yt2TUwTJsXiMNe;vr^J;BNo+Kb?NJz{P+ZMXf^z zh-anRCH0Z!!<8s7k-OLv{SH{KMXD{64wT}o2^tHJ;W-m7( zIF7+N4g1t5pC6;;;@=8?=Uk0}0G+APcZT(s9b{-39?5-XQm#tRlac*}#r}yPq}l@J8X~(?GEQ)MU z8Xu!RJCDz$3JsrUKh_>!0w3fZWEQjFN5azMlLsu1bX{1!Sbzv>c-?&7$MtV3+-vp+ z^ez`ExH_^=wN2bBB>G$~zuThQo~8W(*iTqE@Yo!gru*Vg0H8#u zT_*Vyo@{D9XNXCcY98pqMRGs9F1)d+4DLD=%ixug(coBJC`AZr%Kjo z?wDxluO4aS#xdeB0ML7d9fNit`(j1_A>DC~^S#8m&5EK>d+xP(V5mEn9Xq*T=8G@U zaf9J2Qzoe~(M~dWRc0{zgS$BP)#@X6SoJFMbM7x=g3XpXk)`P&NB8p%kRRLXAew0s z9=s#T^cN+g5MK|o8E7r@BEPhvj}CKFV?lgq=vYq}L{FgM!I-qRS|`@g-bt_ZygK31 zcN^@(OwJ-mKw#v#v}oFe-z$F{zoFAgG#jWu&Hiwc`Wq8Fmz{2>K-H}0{8jBAdRQ(; zhr;t3uIV?d3`Bd&LSEjr>{l`KPxzl3%Z3kY8v>cd_NB3{t%XIY4coBQwT|T(0o&}^ z1IJgLD&Ehn1_1yd&1rw`#u1Pl6U)5?IuaNq#l?99=0_7z(-ic)x9c2cox4}InYSa` zr=FDS5WbAu3wF$!@n<~0;k8k|xvRp!LySL@mk=xj1s_qsuTbV&p82FaBr**OqW3FmFZy6Rq)l?D8R%V{g zODH>4T+)p#GvyE`Z7lcGzU@1@UO&Lrx6Z8(xE)itWQB3gPMN`*_8gxKT>)*n!o2W^ z0nvGvzF+rb?+uI(1!Y|0IS=<^J=1uYHefoQkCxw(Zajx8QnWh zZxdqV3EceDrC{c8AnH!ZZ}G%)m|Z9NL(m{c^~FwK@rSyALDoG5*;`yl|Ak0+S5@a( zdvJHVrf4KoaH1QO?=x%ud=dBaILucCgAP(X#avjE8xhpd&U%B*F;aPAYQ#~+>9m>& z*B>ZI2A*%`Hlr)E8+0*zr5v=54}o4k{&$LV0!wIT;K&{zfV&KH#QJkxH9qZZBvu>iOkK+Cuh1bYkH zPI9}uRpa>=1I*}d^7@EQK#dPM_+Zimqm*zu*&`(W&Vp1T(NJNBk%DoYBl?ZR4Q>!~ zN);*T`XK!Vuvrc`!ZWC$rmdL{MR1hMS#Kz%?Uop(l2SeZ-wrW$%T66n_fXKn27NX$ zn!Q}QC_tLGwg&eDIlwFyJR+4IzjZ5m;AlcUU;yX$5lk=Y+RvvKy z&3d3ZJ;*;mf4DO@7RjpTq_uV|gT{0oU@sS><0Eb)RO~v_8jHXntSuM-fSx0_pwLAi zY`7BT59~8EUUnf*7U~a~tCkQIQ51c`GT{%=O_mtW^R4CA#}K~7UAM0)H>cL!gi6^$ z?HNWcmog-S2jjX>{VPgkk@SZOSF-s{lKzn>c}XDcRxYO|cP1!Ip1$$*d+8q2I<^h7 z$B-UBNKW!S@&Uc4iw*+^H6R&Ti^<%f91v$sKek#jrth)KaS* zszQ^la&K8QlQ3cuegg%{e;g0vG&JGQG!=C`$!&@c!{$(20zMwWf-=x2iq|N531XWh z4caO86YLG_1sx5%eRYtV_gKvx${yDXn6-$2FM*=)KM$w)$Sc<2%|YfjYt)Xfl~wC& zWj=Lp=-xGI>`Mcs+3jZlm2pM`40=HUF^}-?Lt)QTwG_yU^IXoGA~B6PTzdn5mDqUy z0?^sQjqScxp-uC!s>kRTI$`T$_C~SpJX@trk=*dx#pQs`^jOrd{2n+SidKd97QZpj ztlJ(dG5LtbV|ahlAL>kPyDJ7NY~O{-#zB zCht93j(P}PF~yK#8*?iIzyQF|NTCiiynlKOwtB3*e-$5^0;j|A+l?Q>qrTMoAXo99 z*peUUfak=JQfD7ZzPjzt7RI`a&Y*_t50up{SH68Fl zkqex#cgdP(KD)$+j(BhT1}hgb0*+d~acC?xY&Lu;o$zV+9Eu8&*YbD$0ewG&HVyv)$6O0n!e{jK;k22jv$aiKuy_$9wTSC_Ne8CE6J1%{tuQz+NUZhAO3J+4`H2K z|GiF~^n>~4cH=g|y6l?VRDkvsuz%`%)Xma+2D|;LaHu(=%wQbGbnl%znqZ&E32y@W zKak9SAK34AS~(j6Ktx0KxMqOw?tw|_4)W5gWa!_3;F z=l;A$v47M6H?(??n>AGyqNYxEDw|CEy?=$AkH5JY6i=nfqoo%BjbjN1upr`?LU@dJ zX8Z=FLsU_;I{%JPPmehRUEn+Ebe&5hhRP8PP~dgl`b5qR3RoLyFT8Z;BWi$toq2pW zyRjyn#3BW7UJHYYUNZ(EcN1qH$ck*n^=?IVxA2*n1kbZ>RPwtgGzylQtnCa7lS6{W zD4?bV*%@cmYOIgOE}Q{{vru>pQ){BQo_U}fHVL$GqQvmG7KTG?Bh$A^T3il3hxJnOw7nxz}d;r5zb4>UQE1!wXnHf z6DoA5ww;ZiDTc+eCbmg*!VfE^v!C>X&(82aGZ%sWY_rC*!wN_`NxjQ}Wd`2h=V3a} z$SHvs<}TPh=3=zPw7So9XA~Y~aAk!J4s#)tHDBqNQ+ws4#SA*;R?fZ?M=ms*2%=JR zMk*%kxVkUBhnQ_`nrUdgv6A0+&e^Qg~@<20;QH{?6Gl^HhfR*p_luD8XoQD z^u3RZuW7^2Cz79akU!U_*eYXzWPQy4V$6;RV~r+EvVCLc*hzjs3u!6MexaDEulhsNT3*0sS0|d< zY3!HRp+3t`V7=mh)_Xe)Yju}&k0h*@C0GOVA`262z@H%CgT0%+;?4d)3*Kqy9MvrV z>phyQ3I<<4^!dY!c6^2Q>Sp$y3`1ozvJWmM$XS3sCVr>f*-Ai+ok520BnRe-~D=4>68% z@QXhhDI_l5Y97>jFrTjk_3I_@IgCrDJUY>}8DC+(YTo6-#SaH+|07$7 z@gHE}0xlVR0s?eB4qQ5xFceV|-g}aD^qI-KE-2@oY1)aL2903=a4#tOv=Uj)5DGMj zvV0ds^e_Vf9W%9TqgtCC+LX*wD-}b+#6PodIY>e4C|^@JMrv1&GS|bTdRm<#P+8n! zpoxlfWeoKljW4F=h!Gpin8>gjBB5XKLI1Lw$3FmllM?J5X;>Tws*kx;5!I_l6uXO@S4m zSru#Y!>t12v}aWBE02VIfU7W~+)^;JVlygLsoo-^CXg(eoe7J?1c{fhjhFlEB&gDM zCo+DnGwgHt|HyjxfTrvHfBXQaI30`C@hAz%GV`F4QXv8gTV_g1GVi2ZR+&vjL2kle z+dCsALzo7N2y7`S-e4)}xNac|2*MPEsayt(F)qT`#@K!PK40(A<@5XXhXL>R>-Ky- z@2}_M^#)hmDo!#rF7P|g#j3fFpvnWzHgHed{Q&s|cNOsI9hOt=uC~e&|6Z$YT;;0% z>kaG^DD4{rt{|x^Hpd$!%6|{z*kD~b^wdF+I_#Wjkm1j&Ontazf${%kKqRU(&m$dEFj1YUmfRs;~$i zmEHZhp`)RwfPRUDyf#VJn1PQXZ)0%q*jg052E17u!)eA)&;{kIjspc91pgP5-3Fq1 zgJJgpD_0T^C%8y8fO9~aR!)s^s8+#P(i=gVi=7crLEisV{M-Pt0e=2m2mRHL z$@FhmgABow>X34#qB8fwzN_h^zic>@9-~e z$$sl|N_-9Be%|@Y3wt0>XQ=3ZJq$dcvjqJD%j<@>>gx*5$g@muV*fP%1A;RnLrlsg zVt*LJk*DJxLhhTwImOZ&67w?;>=Om0gXUWNF!2TVJaItrF^}FZ)(2JWn-!$JG^9f* zOd&(}alvh`17y$yad}~zTgKwJE%x-OxZ2Q{le2=5Y3O^Xb5<( zCfT!L><6O~4wEtDx@UC|v_%#+6p>McWiQD48yfP-+8fKi^>+{71|4xk$GgjEUQ&Di?x$~YE&Ux)X%n{@9B4*_=@VTGVv;{SwQ=|C0 zd3Pbq{ll23a z4&hmCUJe#lp{CHld4AhBfBZ@{b( z;R9aaI70BwgA|o90QWirQX)HQQaFz4H9(X%iY1WFfkI_GpHssL0x!bVKhmR{twnX5 zE`<24P`_F@g)Og+**1C<5_<`XnlGI98wEAJ3DxA7&P1B_t5&i0f`yV!w(9NtJhkS< zdCJORu?%wEB7*~@ESh&Qo`&_rS+~8QhQWRm6frsAI6#;fJ$Q0cPrIg>0QygoMF2G` zS~z21wv_r!=U<|hc)=t=j8XMdHYt-_^z^j(LE&@jg4TkeAZ-J=1_gRv*7QF7z*rHg zDl@I~&**rKOPq;bWUudpwSYSV+#l1Bn_2K=^ata}Apsny9ESsrVfuDn-SbZDsMuRO z0v73wV(Y#UkM$7WnyPV|)1`dCQP`q725*|u>WWEY0?G1)*NLo}33Wh?CgTb*j?;@e zKSRmNIMI&Wh3=#hV(gE?yR2K5j|*6PAy^NpF?ZkkXwg-t-O=w@@8{(kfCeb=LHQ!@ zl^Sf90Qn3t4e6P<*EEyG0OM(dvme|j=B$k?oIJKqu?|&*JzU@ypG`>32Td4h@!?sX z+s@S`KxMLpLzJPz$0Ddlhl3nstZ#&rqpC4b6J5%^3u(7k^|MkSdsQUpf8Evpnq0FA zD>yViIWV5v>S1m>oP*uZgo7X*9ZDt4aE#U!EBk3v!8O7j&7M0kQqy3ngztZ;I2KVL zsvtuhQ>y)HRdXV%S9wT`{Zfz=K5i|ClX!t*Tx$*gpt!;d(lrlXSBg6+oy4C&bOO9a z@|Dly*5-KoYG|J~=u@D82fNxooIDF+#j@#QKRDtk?SFqHNJ(P)>HI1X`RD}an-Ng0MGeOCA z3wi&0w7*kyP_$q`0q5w>pihnJ7xB;J+l&K#^ZV!cvIrA;g?3P*x-D!a9VgO18(H{@ zzj_vv+O4@1K$Ywk#DElWC6kS9e2dDtjB82D9a2E`XT5Ju!O!2WGR~81#S8DT5&mGc zcYs^@wrz;Ekpw04^Cl+ctzqD{wW%y2 zV5q&ApOyMHhdvV3iFyz^X-Z|0zjLMTv~nD^Je{CZHZ=sn}jmaphfcwy!^7*a`gd4dsGF56tJe0vVtnI zNHn3VQHTiqK97YSD2&oP{;4)nTm`%L8Hc0W~1_IC@(y1-j1p-{68)D19zNq#KN;yWcn}X6YXo~_68Ic)L2;iC=8mi(c z+5;-K;u9#V<8CUj{VAJ$l-GUh8b$x9bY>7!HXy%{Yya<=rr1;>6b?6_f{hI`G(bygShQM?K@pVa{j2dANcpM&2yxD@ zr_ife8}&KFH+Y$nQA@qmEZ(aw`&ExuKi$QoNs=9<3x}U!i0s!O;H#A-Xi8 z!d4lLkOz|*@8^K>YH~Qw zzKg>)+SI{rR5FW;kH($KR(OPwd!$XQ3>+->RQM~vEA{g(^TP8mHH6wQh5tdlmi|47 zcB$1Ziu+GU%?#YeGrbj`0QcEoCj;Av>-Fy$ZyN6e8-roZP(cpZMVwU^gdo2817igk zRDdX;GB~`{`-BNL7|{r7MNsLlN~$@$0jxXv%bOUgExYEM_{aOG5N(x^EQIX5+YoYX zei5rT%C$zo6F`-86B*EiHn2>ADx0GPR8|hoaR_6C3UU!sL^NN2l<58yY<{UtQsSWWLRmH1nTx zi;wWF8%mQN%k!vvB@&hHA{R4V&S!&qJPKzDXNU>J(SjoN1nH2m%N(HhCq7L^IGq8{ zbdb;1hjON|D7dG5EKo0^Urvfl)IzZb4uiRZS+H^9X;C3l`1`pm5!Cd*gtROU{)*NE zwzlTw6MuONz++G;wSS4Te%>SY1m6ZOfFjLyvK*lkR+6{7|DhDk#XbJd$ugE zNBns%17nz

ow>f55Cv^Wfwg4C9hZy_hZ~@*rM=TWtD*`@Qa0NyN_4mH`PdVI3fH zoe&(vpuDyYB+&) zKgC_eN9V0X>LE=%*IBgOxXf1dsnFDqQGA#2Hj`e9he99>0;$EJgLI7{8!!q(&xKS` zeUC@nLGx4kdba^j>u_~RFE=#JcvHR$Cn@5=Pf5j43=5fmDCs0>&L$yb!92!)0L_Ji zxrQ3J&hdbsq1Zqmpu<=U^RNwj^d^8eg5VF|hKZmQg4O7O{Q)Js(wi_81YC&|jasoz zp7%cY(RA;X@C(WZ+Nu_)WX=-OjH!G?Ibf~;;t=ho1}gGx$iqgpt@0KJT0*rj7bbYl zR?r&6g!C(*AEE5A$=k4sN*0iE!a7lvabv+!EB05>=1(s~=WIpjC}*pl_0lNdY&I-o zYD?M4mR;%%%(rDn8S^83Z^V>Uy%31TTV%#V=@ZYn|K(1ZgEvxdW3f*ZEXxa5_Rpjy zrTip0$>e{Oaow|orI)o)BeKSg$FPBUNszTI3csMYX*{MD+uo%ZqM0}u6Ws_dr`%%A zjF?au%;(!~;&7x9wEvr&d#=(s)a?`Esh?D=w`Kckew2>Ysuw{B8g4$h8Y+hJ$X{5h zKY)F26z??#SI8D46hV)$db*wwaaVXdi$9|a+odeo@dK>0VFx8R&}6fEgJ-7o zTB4~Jiw*)`PGQAdWF)?SdXH zeaYCjrnjOq9{Y!e#+- z_-o@G**5Dpw^FJDCwc` zq7B(40=o=R4`PcKHYC(ZzeC_kUEx)6gXIK z^0*2|ym`O}*h}U~#6@p5{GDoBNQgHJI{G(p7>~j^nhxB#Zan<>o$4 zV%PA7_y$?cv^tCv$|61RbE9Iv8t2GR((-BgJnJtE>x&64HixXP;vUZb1N-J-wam@u zTE6mSeT?NyYkzY;kwBiJyXvIygB*qf_;ylgR8erdu{x z=QoJO>UPOaR^-PsTrlRY{JJJ4aV1&$pfmkd?)_TUzc3}zqc@+r3aQ(!1-+mS17;0D zzMUZiC=S+q29r>%O|BU)p{&6%2VkS8xY0GG^WYNQR8K zUjWb@n5$-9QEeqngOGd3h)}cnn7<|ZVfAr<%d`SSPCKQ3q!Rh8b(;Dr$5EwuPOph6 zGj7)Ve_3WW`ZBdV@9jLk5dg^$=uJ^c#27Uy(Lq*<)t~I^B?i27@K89e` zjpBZd2NdwmCThev0pw@qQ47In{0+dFkNJ5g2nHF;sNS2;o!~Xa<`i{b)ITFTmLR`S z%l1^?RGT$BjcWVJ6K<&r_m>!J8K^jcD$T?z6!9BQsM1F3z z)tF+mJd$PDz+KOG6;4)O02IsecvF{Fvb1$d!8PuhufVdXT{7whi7;R+H*~IfveUB^ z;no|$p`uuW^{KZENnL|`DxQ>j+U|xBGSn@;#O8*mpSb${!Emd9O`@4v-)ZfM$S~9A z)TI(C=taa{TuyZk>zaOq$c(oL`e#^aB5*IJy~`R(e=c%e=yq}p}dKuOdx)5lHcPzQ29yQgpP^_onmKPg?Pkalw15KIH zIIWI@V-=yHL-uvs{4A0T&7SH`gX4Y(D4K`pXff--Ye$YLz?DYX9U3X4W{zo;Fs zh9BZl8C?4?8(k=XU@$12L^pjPC41!$Kar>*K&~M<;^CeMEp|k;)@@Y2nYt+Ad74V| zLMVQbC{+Jebf0Ec`PUFGi)z{ud#Dng0coH2#!o^yNp3tww))CU7DWroG}f&7Cywx) zrc|a9!$mYn#B{#O8D9R?ku;JdkDh2^E>)MM>Z8==Oxvv^NjxNquD&&*CAPL6^oq{2Q^Y=S~yNyd$amlsKbBH1&h< z5bG1SQ7k>72M*_&E5KtI2UvHyWLwrA6j*SlB9e@>DDslmx{t)3=ON@UTsZOhHE-^! zakcE-c0iR-`~>wB{7ILiQ_ z>@A4T7xVG=VzB}RMF|P(KY^kTM~tZt*2-`IIRfIDwuy>WlwFj**?vvlC{-d}$LPe8 zEc)3av&PL|S61nEX&)A71etNc{dX8`h@6_9pqNJS9O+`wKGLWO5_yG4?Wz**a<*Cg zn^<4lB5{uUARB{S0;=Du~i>{fHMoqePRzetQAxBsubd+Q&UioIdKt`)u^G&h&I z-Hzb!W9uK6iop-?d7_Uo&5QqgsTkpP+wb-}c7F3Jd~gU|Hf9fhOSUWN2+W$j=FL~W zlpN%}2s-z;|DR57es3@MVg0`@y!!gvA7rq<`%b%v^5W#C-5u=_8q14RKa)O{WvFuN zrI~-$M0N7-khTh2ud%jiBJYq|h1V5BM$#B&Out6nX!#2NoHJhJ+(R9a zsg;p&l+tfUhc*$76FyFcx`&j2zBmfp=H}xVDW5!?m8II?p;%Wse9uu49MX3-G+x(8 zV76Rv4V?Uq;EWANY37G8`c?#uukIG)Q)nsundu5$`xA!1UoNQreU{LFa+mT>WsFlH z+-MX1kV`VO&!%YE{%pzl7uZsA!oYQAK+r@#!NYRcpH?cE)_?C6KGZ~*U*pKozsv3= zaEE{4R*%RzbDXp_tHF~YkQh@JR+HVI1XF!#o*urY+0jm^`IUFRb@Yxv+P|9IKU&b2 zQGAAQpt9XHTCLowUNg?wP1Svyd~lcSRbqL|69R9McJK_Zm8!16L{Gv5P&Ss_ctQJ` zkSQOm70e+rJBCJzh0Mxfh4mlR+P2^~(*2W1X;~GFSZ=B5bZW{AVjjVn*>XCRbuNt% z9@iLCsob`F@@|Y%jqV_);P(t7v+uEl$(X98k^{^kwNer%`5cxJIi+oVbhGW15Uuiu zx~d->qbpaq$7ADlQfBQQ0c)rZcDg%0gj_yV*_;z)Xy>&%cH z9C9~JD?cC*DwL7c4szM=bame?g+M=Xy(ZK={73b8<&t`5bK>6csu{k}DRwg^S5QQe z%3CTiQ$3X~N-7^|NRq#7p2N`f$2didEY3}yJS{Mt4Z6-`J$!?~_>N0F{P;D(_4s6e z54K@oov&x)RFU?4K->NeE!675G*?P|p%{8i_%5x~(6@(3UDb4gbog8l)!eTl>FUJT z#M&V9;QgP3mdRm?Z?ZYMpFrfaDrYZebrly`7FvF9rl$P<3a?_a(Z4(4iHOA1t#M}6 z9&31gd1a~xV}*o7n(l8)kDgXOA-0AMsWxP?^+ThyJCq=`{&PoxUy>+%)#H&IrdB`B zAr0NzK(*AikeU6*x4l7_|L8)xflobKk*d_-a*6w-8geHC~J6}!z!LE zE(+*tCdL=GJBO%sb&j6o^+Qi?#wvr!mAaZ*0@ZKiS1;Do{cQ5#-xpC2tjgmNBz;HI zoK#A{%aU^u4u zIoePsC6Qawh|BMg817HPN~bQ(=JqEN*ufL+c20V!C$ay#3@=?97iiqzmeQ8t)LOU3 zt9k@m8yc_S$B;O2BN2}?LUa|ygdRpxubC9%6gg2FgspmB>}~D)b$0JU)(G4ms&sU+ zl+(DSL(jAp_q;tmqNISyGD?iOMZOx7wFa(gpH1~IVyhEsjdQCL5?cezx@$zTGM2z9 z@87&S$XTtCXIQ2hC>X2#VPHVt?t3Dv^77?0OYTdlC&6&u!Aml-fMB@7l+TXTolnp0 z5Ts7k1!FpmEZ;KOPGFg*`qM#p2yDxQF(k>Xi>CGt4_A>AR8G2YI1(ShW4()H@`r|s z))T3wc=ys&L;qK$E`nYD7hT<^qy+Qigumfl3DIOySB;Y*l&7PryR}%4TjfX;C+S^} z$!jeQR7_h~>`W|d5QXbHrt0Avb%!__3LlHLFNC+%UCrTE{WtWVT>T~gvlk+2)N_T&Sd+)SH=Gr4W zzPz>l3}>kCHD5umb9jxS=E<=vLvyAj?i#^js0$YFGAR#sC!{-K6`i5viu4T9Wc!B5 zg3z&|%h@hOtWhyokeXy2z_Zah|Sg3Xw3yzWN9K1|ZKa=H8 z1$EdOU-eVQHua7cN1vXKgspWAzSXkh!oy7ty@tox%Uegv9)ubXkH##In3Cl)8If%x zJB71bD;$d}bXw!nbb(IQq0OVmy)39uwfY83J`lMXr}~AYA^mdULchr(ZpGvknVyrH z@HmvCmsj~8eq2j8Yc7?=Pe~muajzAtF>l9_rFAfMeYvYKOWg%R zr0Li6IRsPRZ^HC+rhNEIr^vX=Lq+sfbzRl7j~Z4dQ3cgxuqz*?FE`Xi0cwlII4(+6 zhCJdELZ$Z`w4F!Ey0+Owbzj8yvq@_Gk8FR#^2gy8DRHW!}rjXhmF#UfQ3H%TDj|=8tl_j;A z+MNLf?YUVAQUZ}u;T3wgFUo2D@R&PQdD<;1I=lNeWum>7sjug2 zWxt2(?=}^OPdwEA+(DA`sY**G+a)!5q~2rNuUpS5lCRbf{JKM%kG-fV)qk8jRC1iS zyfQqEKp{0&sCBC-UdG8MX(GepOv+BJ>}^H-;k+QCZg@isbFkKU(Ydg@E~>?iu{+4{ z@D;bo_AMBZsf-oT6|ay}{3%{3s#k#1Z^e)b`(GA3viNpJ3NygVb1PMPh)Ag70E0U*}PzXoR zl_WQ1u;4;Xi!RHzXJo*wddOd~nkwr|x{k4uf4B4Cv_5>`f(<{!;`N!rGnAc1Z}U`c zRJ_vHktpdHx~2)OE4iFzaC1Ab>PqURd?tOn^lI;Qp(IE$a&;*Ee8_={9jVj<{#)C( zj0av_?ZucZ6durTR#V2A*Jazs=UYHO;2BWb|>-95p((k{D+}`CMH6{*1UOe(Xvl({+9ONgIj+8$D%o( z+HKiV`z2SHDyq<8M2R``!_IK=Za6ea_Q;QG`<3@OX@8qzv5s0AFk&^&&)#(Y*VxQj zN>h{z(cN^LalAfMoi~nMt4|tA^d-?qYo+7#Ql`c9zs#^>p5b7gX#vOwL6-B!)BS0e980|uF>cUQG4+QL=6v>z_ z@@uZvgT2>d)wS$a;RY%1Rf&(H!|%|4pN5VG$3j$|4qUg|kioRJVq6)WFncffl{`ex^py9qcKJl^ zj@GUee5^EoRh1dlO_Dr`7Q4_IYkEq3THn6gb*Db>-gK9`x?Uk){bGE#^ic)BtGwR% zReo${+0#t;QC@e%RqPk_3|dLfD`NOjneee7u?to}dqKfU>-VZwcb8{I6~r8N73OmMKs1v=mLq#7y zQlj+6N0-ON2$z+{)>g(=b?U|#LB^(v{if|bx=Z?lnYp=Ll@ZtiMS~S)fw@p0#zVW< zItB(kRxi^Ysp>2r?Sr0lwY1hK7scDAV+TxMPee1$9$>sLgc;qb&s8Z7!SrUm5iNBg z2bRM`yil{`r+q6+mGjf!4bf09TH$tIc*D5~6T?%#FcOdXwR(Sbpr}IWd_C40Agv$Y zjY&1={k^00hr*>^b<+#gyIf#PmeMy#sj4tUPLsYH$fwKu4an`j8Fk2Z>#p|+;iMX6 zdQxU+PFGC?@f?{!i$s*2B}Nkz4(X@6;@WkaIZ;3#p$ojvs*E;emDkT6g+Zlj5~Z-f z2tC|Vojc%C%yXqKR&?lc10-mRuDC)Q>ZAB^SWF)xU~&SYe87A78h7--dJFi`v%^HR z{YI8Q`lBux-{bhMT6^io(e=L>wz^I#37XD}*s5Wtw6ttxa8`oc_1@g$sKR>RNX zD@C_~5e|)^C0>KZ^*)iVci}H{@I~j~ge>1@=|L-t$4Q{Z5~W@ZJ`?yxvFCRKN8>++ z&POC}QXBwM@IV%>7r990AXcZ!^I$j1*Kzg(v5TEpL6Pl#L1^6t#nI4&v`Tjdwl2Tv zP)})Yv!XT^NkOXI3?s%qN3<-jM~g47jD;;-50t`OVrF&0*8i7&8P;m~3dumesdV~~ z)Ft#`(rp+~ul^j?%%CE&QeNwVr3v6&*wFuJ(M(b%&}!24HxQFZDwTWGt5>x8p^ZqI zKzyB#i$DkB9sv{fL&vX$F4(Hr@v!?o(OWP?T$(FPo%0bi>aIYm=$&`BtGeDd7dVTT zNACU*Co4h59B&@-$qXex3t}^pehULSEdSUN#)o-UsPkXILQIIRvFY7tq8}pG<`ZDz z!kkptZIc$sl?WvCPJNcEh(oovUcRzzk^RBtVh(aFX z@7180?$mdvF(%@)@9_O7MntmwiYO5BAB6~#)8JEa@wEt!hYOk(3$ zG{btJzzc?PxE6bdaom;sn+tXt%XTGSaiKP1!iSl}@JvO^ShVTX0aHR`yfl5M^ei+c z?##>`^zp6YkNIRY;QGLT_J;)=TIy2@N&)uT9e%W|6d|6Vs{_$ab|UWHkN9o_&OO$B zNXr~SOvOfz2K6yWki<>6Y&{>tkgkYD@As&}jcY;if%=OItYt)3fsMe*&uMeuS&%9m zx(WYKf)u3lJQ$UgJlUbDa7k)H#zX0ek0lNe!(;15NG*Z|kj1|iz zog{kib>%{&M5_-VP0@j?=YhT|nNMc=5Fd4ufos2=Xhnxq0%Xi~3W`Bp2xzFm9n`>Y zouCN*V(}4P2ZFY4!Hv|Tm;=CeRdOT`u{FStY8Rnja>B(A$x-*ldrL*w;?e@#c1s?m zB2_L?^`JMK7;9iQ-9<=f;5KX!)US|E7)MHf3}g`K{)9n+R~YYd5p@WyG_+e1CnS%p zAk84QxcLr5Tt7>e`hoy*a|?AR$Pq=s-@2Y2x~D#Q7gL1dHj;|Vdj(ow#H;XlY%mq6 zov-yAv{pK@P9{3qV&O`d4dX_1_mEGdPcp0bQ|WdYY=Vb8=6gCQFG?$sbXtq%5}^)9 z1TBqbu*>VcWEDuO9>ys&u@ZW#eh=p_jET#C3}}z2Oxkg@fBlG#=&FG38(RR>A>#0q&ecj8E`YHqCTwXs9U%^t`8qMw!8DESWc>7JI@+P1vS+yI{ zsvPlnAE#e90^Hss0haz(%MijlgN-~_6nmzpcf z>*LjE^L1muV^SA6V(QG8tLR@f?vup;(eKpHvFU6j(w-6^WcKV#M|?M=APlXsnY5>w z_o~g z1?@`sec>foRrH7OGt^Bf_8V8yL!{HVnQlFwTGbL&aJjqlkm-S`pR7(n&60W%$Amt@ zbw2hAzPbabUu7?@uQ!7e2>N!pq#a&MYV<~QItH98b|Ifa{h2l+3#hEeUw+OAs)`sp z!GtZ)Y}+5c0^iiNxUek24N^Sb|1u=W%3G*Oc@M~H>~r8=aPU6BKv?R1T0zCbZ@w_3 z)TQ_aqNk@E6T!0mXlRjB*!ao`>tWOg)^oSJbKEdnUw21kMiZ$)WX?pKk}Ho#Cgo|O zR6Ar`5t1!O5_JThhES$QjJzIqFsQi2k|2wPW#)r$#5~$0^#)dl*=@()@RjFHuammO z;8WolIB_846V;tCJFdIwZ1v?>F>YEg4sLBl_^t=zt6qo?r$N)vf>Q%Fi;4R&NyvOX z?9&gQsNWlxq{e`#InW`db}9DcC%Ot(VacDt(7^6CU7-a>4m7~K;3Oa=Iv=gH2dCmB zQn++|7R)wzCo2>&HU*z_*`OLNxJ{40jO!%bK|zsu2x0|%ZtH=EJHQg+Tki_C^+v=6z&h2{ zl>2a#l#lltikLrAI)$`Cvy6aokd>Y_REDM&hEHQV&YYZFWY)3jD@Z7Nk<%Hz9Os@Y z{H339-LJ)&tPp90^3OR@h-$9*gNMH%ReK$^&hhMg@p)%bKY| z?oh!ZAs@(88iDpoETZwj{hSBEKq)y#9NV4%tEt8oAWz9}EmG~g>qzjf+qNo7kNZ_- z+{YK&Ji{W~5)02DVPyavfME{dCN&uMiCfT~^ycAiT@ngTbw)&`XnQjGcL4s`17CeeFrrM?|@ql1V4cP{0p)^y@etv>`d;Z zSV?s!Yi+cPK z5s8Ob5)PV{34%Q!+WR=Q4;?{zj+_rm#dd?g%k79(^M(kDCH%xHju)mYNdN+abJzp( z@%q<{N(1@7jhN*ioeED)>u^su8c!6&;5@zwXNCdXje)2M!rEQFmQTU6*#kBaxIPup zu3)vb3T;CX?!@>l!Elu;{}I_7$#JA)e!=(bOV~4DGmfMKW~v1v08a#Ho#nVfE?5Dp zsrJTw4`&g?b6~s&#}N}};x2Uz3J_k`A7p%lzhXUy@4W&MO?ebg37GccIty#Z=f22h zF<*tzIxQ<*wjo0IR=?^9Kkd zaLENkr*!=9)mITEhO9<1G-$>hh5h%YjjG7ZcsvV&ZNdlZerKQ18}dY@TSF6!0s{MabrB&}z~W zrM?e*NV)PCMYA)B53M5PXOy5Y7?M_~`$9C7WDvJFu`JYuDYWN*kOc2Gaeiwt|otpAQfzaG|pnC-*P>QZ88zEF-m)19{_0gtt2fE4J{(5#v zeVMw_m0ao~dZdVxHiSxzs^`(B9}bu{lDaavUu1?3dUxwKbm-XC;!JLMCPiM>qg&FU z3-2APrVO6=3uR}Nmk;=;h;qOAs zCEqSB(2qx(x(_gdAm)eOa+S zEN||3z1P)xFFC(l8GK#oO7F>}?#$%NYobl61Ey$FS4-!3$_rEf2qIO5+JY1c_9fDM zaxiK@!!BIH+i|6v4Cx0@r)1nDHQ-*KUnu^rQx`WYsv1S_)sSSv!_G*YmxF&8&w%6} z1s9_}wEsNZxup%^$|OL)2@Nug$MBJOVxo5kBnrTLZ^4JB;F2AH8^jRgBL^(QeK0ES z97}8owOh4dleA3`I|#X=->I@B!?XnOV(U#>WOFI9Ucr7ykz#ddp<%i=i{25u5a1YQIpc-c*Cw~)~xZgHBCt2--dmo`CXh2(Zt)N!v znO5AdnKf93oQ4M5obfoumR$3~&6NVT+)21bBRs`WI7GyKI3Wl36H-U(MJJ-FS0n3_ zjM>7}(yq`Q)rW(DDinsX>o;|{4;uqjA1 z$|*=6#h^5F?^a(DJzQFX=j$*SxVoP517h{;uZ(ZOc?22HvUVYR0p+0RUne9QlzYhi zX-EB9KHzpCaasXRf+-gF>NcS6F_*jKEtv#aA`%d6E*jhu_w6ntdy{D5lN>>s^V_NI29t9|}re#(FBhUVI>&i!hM-ADGXFUgIi z*%57@*Id5D*7@TlcK^2D2T#6&ktRqE@7n*0|FX*N-P|op?atb>JagCVvHQU8XVi&v zE*jkKw(V!s)~c?k;;_R0yJfbfUd#XXbON3p^>J8cKX*C)H2VXfD%tDLDb2D!wcIwH z|9SnbnQZw?vp2j@vv+gf*k?y^IJdzrm%7i6`06i94iq}u=YH(#;9{TpA^)=v>?C&f z>=pbaAKCrK!Me!m1rDK`*-|pClVDEANPqQUe~XkGvm5@XA=w)Hk!_8; zKjsgS?JnBc{g;1YjooH@`_xZulfYOGu6D;zPdGc+zSnA3Z2JSgn*Oza>c`J) zv-p#xUR{rRSm^M}`oH$jp1R(~Qt9h$d=U`zw}-~{HkMi#WPAAhZx0>L1_*=6@z&to7Zrbn{OWAvE$c@}*_s*-o zy!sbMeeyN`mwk5MzHML69>7$(1-YI-P(Fy z_1U%H_{R^9Eb{!{`1=RGE}wHK{T~}voOsLe_KNl7b$RdlEjat4o8Uv_Hlr=R|J&K%8{`K7iem&umr-s!C)Cyle^rqRHvY1Wv( z$iFyb=GQ5pl@!0-2}equyCw<#Dq^3=`hgP3*C4Ntd!D3cAvF#d^}P(Zlk2|4EN8%w~-? z;^u3#YHiT+Tk_aX^SJx{)T*uUjYi@74gYP8t5TfR=V86o*7XGm^;0{p(}bQY$jxJi zK8IUfM3sB`Pl{?@j4ESuvfcNpPq#g%ZVP-U&(K}L1aQH^iq+KSphJR-1tV{Vc|}xi z@uxN)yUT%3!jSYS8z}sg%SDX9SNxmz-;FuT^!Or>y24$#1tWA()Y|~nE%9!q&x()p zcx>1;jrHi;Zm~FtLQ#%-J>xM!G0B>IqS!UR-7%}@hwKoZd1!g)n6f8Vh}9mkWH266 zlKA?0>&evIfcS;2edf!Q)Q#2+68ezvsylo@LJgmNdN8_${Ne}iRN;pG@5`f_jKP2c z6kr+Sy#9~J$mVqo2k(#cZZ?Xx4$)GcPwl`u;C50%!)|LHVLC;XJUb+bHYQ?LPUi0> ztGCc1mjf=FYTT4mtq^USmi*`~*W5b8=C&Q%%c`sGqLLdFJn64E;evC`Kib$;bR!u)hOY9v*>?r`aD%YTcV}-soZ?<3?)VX@4!+G zIsfn=?FMJeMCrHZPO0XgCM)|JMPCF0H1Xamq|q|oVs?}XwVsDwKjy$_6SLa!{xNTS z0Jv+ojQm8p+gkQkgK^UswvqfUX;?-{FkMih-mxb2XVBNO*b{^lefCz06!mZYvE4w7 zg^%p&v+wP<^&Y$1ijeGH*t;S46s%|pws&U3v$EHMSnCTqt>{LQW-c`6Zo>Rj-UGKS zI8@W}>tPrCo%+#=R@+PPF-J7bCG&s#ucVy_-=Psxsc^3$b0cvvn=CO!w9w9#kwmrO zi^h{~8lEQ{(?6Fft#l`E>&s=Ni~6@GV&jy;c3)Q9vHB&Ar(LsNb3|Li3@nVlPr5`zPr`o-q zsYe-sN&GVNTw80en7gbGgw37t8!;~|%djP9ZN{wQ_|@Pazcz|;qISjO1c5!n(;kHwN}{#}ZdGT?mH{+~)E&2bNf$0Cp}1zc}GM0ooIRT#fJz zD&T0q8|nXzb1O~~lJVcR=R+SyFSM4OB^cl=Gh5T9da_9gEPpaN#-Q(*X2s_$B)=0f7PbN|R4cB_GTm?&rv|ob-p682 z#4SchS^2g|)4GnbdGn)SYmSyphtj%w#eE`ZM64`cvvh*dFyw)AvHI zm66Z;z=N7JRlQqs@k1r;v6jdeH=Adda3?me1LLZwCn+{oO4l`J1 z8=j95ug!+x-sGyXm9)04oOH2w_v88*rn=oQRHRk#Hv!~uo&Py>*TzWC-N>PKW*`}^ z{pqk#%%J~Q7o|pA7H}Uv`)cDHoi^oFD%o5vKUqWPq6PC7V`Gnv2U^fI8iN`So=4!o zCix+G%0;00DT4o_Ve_}8ngHWb?$*GsfS-RacKdcxL+&thjHgF0tU6)qRr0%`<)yJk zWG*h?y@RteiH^C~a29B3NC&!q&uF9@Uc{IfiSlR0V_Tet;C>6UZu6LLM1+Wr3HP(E zgbd)c(e58pJdGLE1nT84i`<$3aGaw5=_4YEtRnAyJ@G$C8z6Pmz*8?o?6i=0vEhy- z2;J`^xCm4m#8wX2_ZcYKS_EN+ma%|bdWCs!t05p=b&4*TCGcX&wMn}s_WI8rX>hnJ>R`Oy5a!e& z?3$4L$Hrw|yj42sH5#dPbe<%Jt6;|sI^N%9surV8xx7Mhd#AVcw0t8-J_b*&8b1+S zIt;hCDUVK9|IFpd>{ec0bz&j;7b#z`4t>{EzGq5(N2fj!_W}9IfcXEWncwJ10c4BU4dMJr1?wgUVH z?15nu_M4_|;w?FIJuZ)L!Ut}2uPLr4smIp2U&nQ)y4|MyEJ0Pqc&=8z-mMW>Nxy+S zE?{Z&hD$hi1H(V$DKshAhqAHMtB&|CHKv>1mA6w-cBsQ1@hAVtDD zL2!~^7>{4sa1P@*-4yS+U+ea2T>kQ9lDR^y(J~eKT>EH{I8{9+DD@YqCLCJZgvCYU zsr#f~YS;WLzMAqh5&HsIVVje;xxJTgRxQ=M?{!Bc3-Sj&{xoF}x7J3vq(ybbsAFy; z?MJMPTk9(6OZEGOB8v4J*C78rfvvn zjrnRAEksh^jq&ghn6|#B(;DD zD5*@5F-m{{0YV5N<4NAy=LGe=_ro^fBs*tVdkx?Bt-X87ZnB>S%@7Z1oA`W%py`(s zVc~+jNia7#$`_KEk~|wXAeD2wZhSi=a&h6Yaq?VL0HAa!BO0FVTRLy`Ny$Gt4f0Mz)q zty*ng0-1ou%(oZ(jM7DvFhg~kt}*&&o+ zr7M=9%c~UMF$^^jl?gc4T8ssOG9!wSLs(kSB0Fmu@}{VNyV7xryMM&(Op|4^$bjxl z24zt)OD{IW^{%);+UHiyNk>C9@2j+u2SKoUA*wPAJg_CtS3$g>vrLk)s%kqu>;5}~ z;g+>^mEqVO10=K1^X@djx)eCyk^D!l+jM0bFs%=Yw9?GT}6!?m{<#$fNn{ zjdL8nW^Hafrk1(B8Mk^YRA_?YffH?NM|QQo*El4ud*9F!NWW|sIc8bDD6&kRQU*!I zN6#|^`Id{3m|=*;lN>()zdvqZ6@4nloXtHnMUV-9F~5-}%x82&RJ?Dx%Dgg|*c7O~ z?$~5541X(TX86*-Lim;{*d`AE=tT1DvfPgDSw(O=R*ybb zI)^_@-!irRdruc$EsJ?8T$7hB>BTA*KDw}RF&G)e&=B5R&2nI255GbgDa!Jb?&wo+ z;R$p%M#jO*o#U+Z{Lqn;ECkoHuRF)#t@^+jzb~RH1bVnnp}6WzKS|%FS=un-z%ppo zlzEgm8OssYbKvFaYS^-B%8Gc|e^oFj=E%7Y{dia9_DhdL(!_9vD!7WZ?GfZv9JLo* zPXAzZ?8N72tVqKTFw&TqnRzE2>&v|O;%x4Q>rA_4H~UW=cWejL)0g%hU?1^I{ZXv$ zo|9L2ey9cij4~x^E#0x1vll>;TjBic*}-zOA$G@imF_#JEttPwUeLLof0ORWC@uP! zIgKx<&ZXQ5BAg*YI`+;0*>5PW8u|-rk@)m^}{i`iuSTwjYn9>$=c|E(w zd`_zp3-`mVLJwQ3$*htOeuOy)14AV#-CkmOQe`W|*yh4eR8K-q*!;}G(BiCnM>?Jn zPBjFtAuyCP#Z`3X{U|bEE2pvXAFtu&KEFJp44evbo*kbTNEMhNb;Xc;#@8?{P_3%1 z-lJ?|^caR#I)XkV!#H~872GWKEUo@Gw_qrm98dKXjt1-2^rlCS8!8A=m+DAYW&SnN zkU;pj@GeuJ#>@IL=9-V*O>cmWzj=qV6V3EMW;y}DcwJ6^h!LI;p+}YQrQkj}=^DvD z+#w4c!U_9et9x|j?%R&1G$&%w!C1qfG6(5R91;_&wOXLU z87-5PyOnN7$rLo{K(AGsBT?&43$RUBM~d}rTbRQ8j-4H_kp*CG(si*@V6ju)S%W%& z-d!)i7F-)oR+KFcoj|4Glw<0pXsJN-yfd*sHHj_S*nSTH3A>3HTQj<9jjX|+>DZ~n z$^w2qaR03Vs4Gywgo|7KD`^v0YTgz!`Dve1h9h)Xv>pYBcj-KRw4f>0DqZDK_P-af`r`2Z22szt)`8E z?KX-$@7j#>zZ46eO6m7fyNnrvf)*dC8#%r;Y!_yXlE<8B5z;D7QvR`FcgW&Z+NhO8 zY0Oj^g$l-WS}_9Psle_z|Mq1<85@!BxJp zI?Izby|40dzj}Y%I56_|1GKb08TLZ&tL95jt6>=(RAa#Ur*-zAc78VZ@6^+R)kG0} z2hZ#Q@0#m)nRIRZs)iOAtyc2iq$AAlrBltPFh;sgSakPG(^AR_{? z`p{8BQ$qE^Tl~}ysGjldBn|+XYbAvC&!O^tt`5E-=KwcYqq%AK31Z*REb=T4n4WYY z=n{iv`@?iy(WG6hG0rydmdb+up(V{@ToK#S#>3b9HzcHXa&Gj;;#>JAwBnR#+3GTQZX>&!ay zMN}dDQy&J_0O0bmo1&o&5%S8KLOF_6l&u=bb-{aC1lRO63Tqjfd4wr3x8Z;2^>vs~ zw(pQ-@$WFb2E6IrJi(iEW{qpFK6j+NQEu0;u3BdqGiLXVM z%AmdR7eI=**BHODy{E!zh|F&H*!~gZUql@)Tp|{8?hX8pxUt~n6q>L5=wVVp7Tm|d zcV!qYd(b;+y-jdGM-IC@Ap8u%`rveWceQB6X-AfN$7B5B<5$lUki05+`-scb6rM6) z#WS-hsNf(;jccrH8ZV>Ui436X{?-Ye0Z zh+&mLXZ>QRwSO>@t3lxd%wrk_(tY{Mng^hmDg_vPcZgIQu2}^09x%}>devRz2t&v^ zA2PM^Pvk3&A}0GHoJ36e6i0@-4pMIEZVw4$i@+aEpN9N6P*#Z;z(y}atn_Qc@!a%Y z!rh_5iz#u-bvzIT(KXSy|!j^_CK1NL&pBZs7;cx!@(T* zfk?~E7)<(N_nBCq#Hzx=$Mp7~?iKb#@66u?b`^e%_E<+-W!y&9VkHWjDBo7N2Isju zq{Wdp(_c)042ZDJoRRSmN^QWPv<5s%I z2uO}Kvh(u=gL=TgRum=Qk$#P~r^vr=IA5jC#FSa0UEr`FFQjO<;#YQLYaucd(??q~ z-78DEw^b}EYqEat#FYW;*u3#90`7B_wRRQEyIjHE=0?i)6*EzcjwEwCM4O~2d@Y*s z$ON$vSW@z7+?`qgmO}(r_Vn0lo8Ls$E8@?fe)*P03Dk#KA@^mfGpdA^gU%U(Od9e4 z9UWi{1TLnD&msHI-+eTv){u%bUZ9K?O>_~N-$}~g&P_tBT3Q(->(u@=YA-7=yO{ z{4?`{nPAJwq8c7pEI*uz&>HysvP9`00^gPvJV)(YasiwAX6)&`=VLI-;y zt(<;9=D&Lpi;^xor9Ebyd@+hw>x=8C5>3|CPk{GsC z49zkODR1+BcA_|Z$6JYMfplGajG!#wP{7GBb;fXA0P}R--^#PHiQqp^R^7$)^SRT2 z9vY1MN{v`qDeqAL9^(<=Q>37a|EKYO{4*G9KJ8li<>5_oj^!A(dbnoqjWo$#SEt#A z;FR_B_tV=Amzt;jeALLF+-UCV$RG~adI;DPL4OO(vgKR?G$SzE$F|~d#ocMT` z>88VbGerD2+IB2kKgyP7j(B=q+il8q9FH1Zhw`b?V1aVk#1YU zWFFgfn5qBy@^Gx2gIF7l;X|v~af7TMYgvG`=h;KhUEQEwBbKNa49+GH?wOW{j-A)j zKR`1EQfz{y23-Sz^?-lBl+WD08;L9o!1L4vk5EVUMe-e2H4RLkgZ{1T4fG84VxAd$ zZ(96XaK)Gece-X!f4s)k5J?Hjmtkr+?bpg*kN;>3$BI(4H8;3O{{V=}#dxW8IYHHEowf@2reF((QPmB#|oHp_&xEtdS5b;m-p_+)dz^OvA$f+IjQJf06qUyCDkA84q|l05tWVb7Rf@E{_GK{JzX#m8Rwztd zsiO;|XQ;!0?-MBs0>Xy9&IzGZ(NAn&xqF84b~37SyCVpwq@6W%4-U;G>ZTq-CgVi< zNJQg>%_R*?k( zJ!+@1IK4GTD_xtutMc=;z`x74stZO^ikuQ1^C(i6{)WZK`y|cKnjWq*ateA{?5PQihroxK#9WE!DY+_wHf{mac6pOh$q4aEzDL6lX+2p2`UT#(n-81L3TD-+E)}x<=g_=!|6>mqT7?d3uj;?_X&X<48JNIQH10TU|LzP z-hhLIfKHq+qCD{Y212ldX4^$|0aQwZlMGWjeCq&c`W5^kOc2!u^=% z46och@;nNB-8y@r=VtY+%M8y8u-!myK!o!4Y5zwO{?_?w^A@X3ZaPglWA3JJ;_y%9 zOipAHpFqs606=*z~(fkx`#_it{Gm5S_7WnM&dBHp_gIRfn?LStV@# z!Je;ZU7307Ws~$)W?P*ciW|ZyG&pQMts6t)kMtQ|d?e2R0Ot`bp}WBm_7JHX7DM9! z_@Q>gA4olcZHAb9qXxi7WdyL&nU+3~2ZCaZfuivgqz0rdBDG;&e12p-5(63x8#GwS zS5kGwb&wv$*dP@D{nYS{^HW8(rNj@s=(1$LlxKVm2j!on9{=t<>9hzv4L-9XOOE9k z=uKx2@WgU)HKKw*GjV>;zItebLO};%S2w}$h8e*!OJ2s}hF@k-kiwDt%Ui^Ewqv_- zV(Tap6<9J==9w#iy;mypy6#=KSfooxLF8e`2uRK3Y8FyaV&9`gL6f+)$Il{Imz5!{ zT2y73dT8)ne*ZTT^$Q?h)Csv2p1*!I^c!&6d(J;ls1oNyo=a8-F(rTg7 z;j9ZNgX;b@WvN=(D!auPK(b1fc1X4KMn)*rK=WAX6=REIDokhQDgP3=KXqUZZ$2)~ zk~8=;;<3U_hlvB#eXr7WnK|}PE)P#%g7daeg}F|Zj7Fc|!D-jVi(~?NG9XAGBarUs zmxuK%b!;`mW-;KsRw;Sx6^0?dJ6g>WpwhWKO~lhN5miA zF=R$HPZ$N?rrOKAP8)n9|0i}O(Zp_14sM(}h<~|8u_u{Np*ge3RyNKlH|DISsxo^M zCnL`fdA%5xQm%|sNM=77|C0aK{3z0DGZCGJ5aT&`Sq4ZC9F!+vMGl1M0ep{1RNHCr zr#d9*A;byZNw4B7`h-OD1EDy4-$^uppCaTt#i`9~!@YX>)^B;{Gr1P7>=rUa&~Sm& z)~U(ph;{~TD5YhRQzWzZc&*L8;5v!7=Q}X@-KzLu`p1qjBkDyVB4ZX!ZK13dB1Ic$ zD8SMPV{~d7+cA_3pcVfAAC+T;9t3JmV4V;>f;@vD{}J#9c$4968oit;1Kt^xYS$IZ z&6Fn?fouZDI>{V4&8tcQn!>}NEeEhIAxzm~?;@bb}*T6;)DCJ|= zt${g;99uSCwUY4itHhXAe}x0>_THz=I&0x#a3J4;@%@gmGgH+zYoMhGHZ*2}5C(dfS6X6@xJ*# z6{U&lQIMoz7t6YhPw(n6rWst?sq3KY11d-QwFN%#l65|M*Y5usiGyO#h@9tO4v1(e zhWpwhkh1~U1cV__b|}lkpP7*M1^_jpMo(r+v;t<=OvK@(NQ-=i_N9B7DIIh)+k-3g z*oJaap@tM3`DE^l$QL3Lbn1`9SJS0BI-%@*iHZ{$VF>LKS^h{Z5As3(w#ME06p(;O zlBOxJ*3M)ZxRD4NMD_=XBSOxQBA7Fj7zHkmj}6D92Z=Wk+Ol`?{_i+c5yq4b;Kq)n zbiCnwRm$$ivSZ2T7~>h8NnGNAt1(&^a)NAXVM44Iyj@qht{&Z*E+9w zi&QrDHWVu;@2}32xk)(X%#{m+@xx@Hi=@i=g3i&>?!uc$IYxl;j8^tg;P2-seR@Lu z^hX&8*iid{KCUT|8r;eRffm3)ng|p)GI!i`JeFdz0LL`7695EebFF5Qo_E@evPF~z z(y31@%OmMUjN`ce*ko8S{Vv(X(rkoGTuxvAi+4(O89vX_9y=rZQ>pPoga zqobf40>@lstKGN zAQxx{$e0T>D1IedTyBjenM?#5&aE|<+cSeeZAsvk(u=i7H1X@o zF7SlrH=Z!m`mosZo^TTEKKw6rW@sKi+p_BS6#S>2EKLXjPcp6j*QALBhV%X2t&-hR zem^vNyahVBK!afn>@8MUNf)*0Yh**ug-D>} zfw7S%5HD?`!3rAreCh$OgHq`$L?H`q5gyIKdrxr40%O#A1C*qt}&z`BVxD zB-+?k_6SVLP>G_VY>Y_ytFjvWK$*D5QjTOhnZxI?{PZ~Lct|}(5<)=p%K_jRK|gU2 z=pKOnPFeQ9v?}a|y3#7N$@3B65VlY%1EvPl^uXDuDj@P^X|@aic>{@|L?W(El)-jm zpdti_XtlNBQ;NZET?8P}2zf+QI30{jVhrhaD>Ro0W>;aS)(iUaNWxXI0)+^}pXdI> zx(-R<2ff-rMM{V)u@Wdo77{ZIREsaw-iRPa7$$P9tdcB+8??S4&O7CA9h?|C6^Ma| zQoN8&!JOqfM*JaP*@1n5W+nnCMd{xOL_5r}iA6b6+r+N>1H6IO`G}rd)Wd2?=@sO6 z9~-9n*2=6u<$OV@jJ~2BYqWN}NlGJ%1BV4G4MSxS)S8?=!CT%=;F7roXAhgtj=ilN zPsAmPL8zDK)anf$3$U@VALMnzW=Fd4T_CJ~oSE1<4(O)+JngI^<0SLco9Bn7M);&j z#N2deHrerc6C;l5&b~9!%)+4D6|6KuA68zW=P_WNzfj&}H&h@+F0cEYO5^t;-$IAY z=vvkI3t0g3fq1vi4m8-RTivf==_rM43m)q`b*pujnVZghA#p!I666imCu^*mTGi($ zAfbZd#T*^0B3-9myIt#HvUEF)#g%IGnBIVZMN~WP+B=%?zND7eKI;IP7~-~1G=901 z6=qAedLkLoGKVfMH8ax0L-KtXrQBICTi<#T(w8)d0PRyqW)I<5F{@hx^zlg5Oh_b< zrVF*TfZv5G8o2TMl+)(@0ViGWpRfrN=%wIx7YBlwkfcg`u;4@JMo{2U@-Q;tBw%yP ztzIX~h!H|If>=aavIiSSjX>r==pf{f$ldI$a4MndA@3Eb{SNE&dgT24N@|$u_2^3w z=`VdGD8)N1mM^UT7p>jsPSDyIktZlKbsNm$cH@@*9{x{%RNdHk9sxHe{E9Z*fw_Z~4xt7ztV_%mZfV8#E&n@GMe-r`?U};d%=1 zf((q9Ha=b^t=42dH`ai^>AWif3ty(?wP?Gg9Yi_`>U_b*vtsj~DN%Ws?a-Go8uE@{ zL3LVgBK~bzlfhND8p&PU{W80ay+8jz?rm{Nvt4gBpOB#O>F69G=h^s z+S->K_+qUl?g}XjDxbEhS>WFR>Y<_p zn!n7?bjBv%b51>5)TEhvjTGwat*a@KJ0h{m>iafBOY0x@C6+ru>R5}zEW60i>#?h( z-hCBTH{T=IklEj(Jwsj+$P|@I{a~bFNVcwDIRR+8Q~c+Oj&n^&YG`h{9w!*Y_DQSz zb~?p(?%^0aoLc;jE&8i7=-<-NOpx9-e`_sB6~xqu`#j0D#r$T1_i;8$U@^m zPfd|*xBdmbhkLBfqa7$tUaqvXO2MV|WNP?Z!l^BubZjm(QDqxc_m{iCK2Sg4SE78* zs5fpVbd?W+egMHEKR|!EK&`eb&P1bTJs1_#iEBX;u^^l8v9Aiux z4a7h4cfcDTjRZ|A(kK!_SVE(?3-NUG#>RXe{>ICp(9Ovn(Ibv+m`@iq8fHB@DPHB+ zfit?3{$k-um`fUet9UgN009t5W~(&_nZ6YYLJo8v<>)?6F`tRUM*b|1N|PbKVHU@s z^5`~v63Mntx^GhhF;Z}&5*`H$Tb;~Qz_qMn9*1Fhl>slcmjtlsJSD6`7vHI#J#2#U zphiWbT?baX*L*_)ab$Wyy%2~TXjQo3_|1Tw4EOh-z3OY z)?jyoJQq^i;RZdnR(W$uhhpR+d)46XYKG(vr@!iRe*PoN2C@oKI!xgen!CFD>*Tz~ z*`c1(NClCP;+fSyMNuPr(keU>GY%BaUiiQVm12>|Yzul;wh}|n1XTk`&+xh()b6}K z>lrdb=Q~1&3qH?H?{>lH6P?%t-fqpR_q87wY37p#DwJ%ap(Znou?}{rC!UdPCw^sE zY~M1RFG39c#>;5mcCB9^?q!@|KI1HZp@6M5)U=fLW9o68UqW6|vFS&xU%A9XCH^iO zHWmrLqj=B`il?uX&?C1zO>Dj}oET1=R|p<*Q(0>WvR!nf`yi73Xrvy@aWp^y(A1ja zOBa2&8#c~Ef-+;44}PUSrBy2q`NfHTL-ZStC&gl<-Ui!iOS7j~Pa>cil_j>N1lU3( zmhKI?=~SQ8!CPYOOtF=QEjOy&wB{X0}eg+i=5~yDph8|Vm zL!hG8vgoBrQ%su$AhDQ}Y>>)ABf?M|{+LigrrJi8^dRV?X^GjEhJ;fsj0{VPavN20 zl@xZ9akN%tbiJ?K(Pw=OpdKj@FAnUpHpdApO-;sC#bi&(QsZW;y?dity#YE^2vW5& z%_jI4Y_*h!Hao{)K%hXPEY?QL(xe713c%};68leljY4jUQi3?5{nL_6+oi;g@(yd~ z0f~5vQjlU<4CUxspy0PvpIAoC5KFo*q~`%&jM(Z^;^UbO`1l{0^5Q6K)DGwLXn8QK z_|B?dzfXLQv_XOGg5i8S2AcAdE!bydd#CtYc590`DX8%xmwR>SZF)ZGScO4)y1e9p#-Omf0Fm2K?-1z0#yZZUGw!(Q#aw_8{T7=I5IfI)E zZBI~4s*8r}2#rW(qRr^d!-r)5gWXUuA~#~>8I(;rZvIfMifDX7g6yo}DRd^kRo$Zi zROicv%>MBNk7ravCp%woi0n`&Fp{WtymR+m*_kA zEyXT@z3FQ@%%3_t2EY}!LH|>f&mkV}{2Sb0oc9OxuN1vMC^<*jH zk*d(?0t7&`l+-4z z#6Isji`FDid|rVOT_f}={b}DD>37Po%!QCm{lmGcSt-4I914LKhq8JzOEqWN->c?S zIK2yv0zKi$xj^fakNQl06qu9zwqe4^9~6y(Wl2qzZU$K`ApbAm9WWet0QgB+a;bxW zEw(Chc$L^)%iEyLqLy^B0-mVVCxtG26 z$jqQA7LvSwbcsrBRzCsCL1M+Lc5h{$X~f`*DyO^qwh#KMc7RIP=|^XPx55~N6q~%W zkHknx^UC=VGJ~K10w1(+%&^7+fwutq0rd)M*a2(f-V4fiN0ymUi&X#~~KQ<{%@76xrYB>>PMy7*ikYBPOpu%j`a5V5%zQzxP5 zPP=@bx5Cci-L$6r_8H!fo{6I6J014&;sJZ-2x(&ms3*B{A4?b=4 zbP`li%gPk=@>q*7jeEAoJ@z&*#FmG7TffDiL(}5%Ompvn+N-GRgUT{sE8lMQI@9PE zgffqsF?VI64MJf?YNJ<=w3WjLEZ9h+#Ni)_kyQ}!wA+%|z`cK2qAu>!|4K^n@aNWT z*VLNtn%E=NWu{{r{`~-cy87qLM5F-UF+d@<$UM(fjIzV`FcG3Sq%{-Drz<5vn%Y@o6+d>*6Vy<4fdu@PvG`(Ym6Z{06Mk}wVW=;*PHuE{4sqH~na4RFiY)VsPXbO5B zK$jDmqvGm3v6mwZExbVa>?J0lW^Ex_Q7mg%(JY0xx(CH9`>`j%1dI zIsvXyoY++Z@DZ>`NC*1y6THd|NFH;Ejj9EwgFqE25vViucHlA4>M1?(pgjC9qD{3! z&_>#IFIPJo`N1Jf3(<;jm8Exk1_7qC=Md`(=#b22B{fX}ydd|_70*cmYUs!;z>y0~ zni?;YX6{vAae1!)+a1&O(m=syUYBWz`VSG9>%@hUB-MCe{cv< z6P|fQ0Bi~EO`NLIJ~QsM24f&4gchWm5!BrS`t+boq$+Ko$?B)|DyP`@%V>}|A3&>a zh=(_%pG)siSC)<3Ikmir_qK7stWyJ}f|7+z)tf=->eXm_TRT8XvMB#p&h}V9fDN5X ze3jQ0`iXd>`TkjL-b^wZxn0l?9OL;$EWcTJH&>Yj z*N$XGpk0-wTMOlDQ49AISR=|&&?nheS4YwMp6L<*@X!xu!ytGPUMiAKs zP;rAx0mB|bi1WYUdQqcRMk@|P?lLOem^}D9Xw?xVhLJ0dID<8*aY1Dq)I4h#3g|>3 zFu*D8!Br{Mf)3;RnX{TDoz{P4dA6+_JA=M5Jwz2zvGB6{Rq+$bZELf;nP#-aS2O6b zgQF~tVm9Bnzt5J34UG!lP3VZP(jWI1%oE4&%3|5}7YX38VeV(a9ru6ekUnQWvwg-M z-uQvDpPaHqDVjugP|s7K@s4nGEfG6u4_$W&-b9LZ@W>f<%Nb@eL#N~{tf&Wglk*K! zfj)GWbzA;l_4++hB}S|G88pDB#BXc=6nrkIt!FIrp{_BHJnz+9xq@lcv*q#DPf&YlS{@`q}gjrMsZNt{xlwPH_VK zCraSwMQy@4wpox~KxI5)A#{$coAIZKyvD-K1EBiObkFK0>JM`6DT~eOU%h+d*mypg^*$r z^+(nb_7K%3aI^=+Rgf7%+W>~RU*LrE{zjA>VK9qE^2?*Ep*sq-n}Be7dvc3eGZ!hg z7lF_eoSzd-##R%{e1QCeTPTfSoz znc-eFTCcaW)^&y`ft`2@F%ySjxurkN-*jIJolh?=!UZbmb^7#?-2Rn3alU#IuV`o+ ze3GzrHI=jXIMvb@L)%1m;a07xX-h6+~)Sa&osy|q46 zGzpN>4zoY&X^`-e<2Xfpp??K|p@Q1((%wb>%&`8Da}>U0bEGqaXDD+u;o|&i%+f3* ztOq-Qdg|z@+wm)*vdB)B;C_)E^`H{2LbYlmo%0Y1)}k+<-550% z63|WiEVTL?A@(C32_aA++INIp85T+|ya70zlb|@*Ezvs(3e3q+5DwJYhEme04nR%w zkc{5ZnTz+0SN+~MG9J8i;yd$ni+F^Dw8T-=>2%U~GF-*qCf)~qX! zeCO|C4i+W;&*hZM+Qp*(Zg%KlXBY&@@R9R`iu7|m=E_(Gmriq?cxIvNLf2$?!=S@m zUAfTJ%cWo$`Fp~Jt`l84z9ruftA)NB`r31u7fK!>Zfjhq^AX=dFZ^BoU3Y)$B1-dj z&3AnaFE#|Y?sdspfxhsm%Yx;v9*%3|a+jA2(7OJy+&PL0ULZqp4PD_Pk_WnOas3ru z!1;bE+7#qE*Clo(8j7?z%Zk^A^I|w{ZacgnZ0;t!AcmT_k{D{HXo%wiRr zJl8QIoaZ{4c>C^8YO9tGs|*wW-vA$nAB=F3ts<|Ju*}Mpuib$@;w;GZQeC}P5f|>X z%+2IGf7dJU0&eYk7nd;7G~ZkO>U3Qeu72$v#;$(NH1Do)oIO|vqLG_Rl5ygKMgU*j}Q)*ABYw6(7tZHA-7YRLayOCHU6{bj}oR?yzb>Wxw%YON1o5Mb*~M&ZJqOLC)d4ZnjE&%G(%pQ=JE9|dCfarceuE& z+2A5t#&#`rHNuN=dtCo;`6zYj^0vge{_uv&r(4iqpSmzNy?QLJhc>-tl;xY8K$mL8LmI?eD#1`zT8O~+}52= zliKy_plkC^r@{Su7kM=Q^`o3!PJ_GPJi2oCYu|O>?X(=f-NbhZgZrNS@|@gUZtW(| zCxrdl*WP41zcz>cn!z2ea~j+mFAWZR=YR`ZQ~X~eD;m!oPrUutf7We@*}HA?me?QK z{zD%92#zGYu`4yAYj@fDhvC=14qg7|_KOd$&U$d^S;?nM>(*(i+S^xnuPsUFm_F;z z_*MUT@0W+?xW8{0b7+nCuKgb$T`{@u!Ee=T&K)}XL+|$7-XtshHs9=6-McT#bcD1P8k)pIc&?(adRS1JfZeno*5{-x3?&3g(P@Lwd^@v?09OV*C-`hnT>GMf)0{o&7TJLudtb+BU&#+f|& zR3tl+dR*Lg?|Rc$OzPObS^81#Zhc|2y`}D#Ogj>F#p>%7vik`~Dh8WabiHJudB2ZbY#GBGXu;3XP0|ID;xN;?)Xny%v!ufr%p(-R5@`ctUxT4*qq`LI z?;reUQz2*SFLGbi=bznS*z0y+pO-S5P8b~tCt?i7SboogDPcR~1!}-%@s9I$>x423 zuAXy$#YV8QqP=b2U}P_jMr=N>_2#)z+6>7}^p=O2sxn*MLg_!1c|+Iv4~__Z77Av* z=mLXbmu618*ZGF-cdrQR9y4q?S|fcvnD(`p*PIsUQ^(owL&qvMA6?z|SNx!0Ve1pH ztD!plTsp69lX-txP=h6~$%B_C$ni<|aq2S<9;a{H)RlkxY2Gq)g_K3M^7j?49o)<; z8B^Jp(=tq9udrz96fIF}TaBi?tY%P8QdX?`+sJFsY~OiB?-+PE;o~68p)KAu_1e_? zd)MY0qlWWO&LWNL?rkp(btm<>7e-%AwnqPE#;8I5V4WFuvipkce9%^cXN6_Nt3G;Xlj#+c1gC|4U z-O`Kmh9)t0=V2QprbeM4*2e8pZl@Q74t3quJn)TX(*s-gS^pz`;=x_NRqP(QP}C;0 z)E>yIpJ|LwtMW1i`e2ohuB*$3{)%Kz=2$8M*=wk$SqWVwXWhBB!?UvW*C%|;8$)UH zSK{|$UFDbnU*2X4=mA?|cO!#(7`bn^qxL{0z#69gnxJnBpM^tfsM~{Z%IJ;Kuz?QN z3Tc4R;b(r%iuKIlUl6T`{yqGKcN3wD%Bg)}5IEqQ*;LEEidhyM0USEjJU*JE) zz9nAPtvir?&og>nbj#R6E|^uPo9BCtnF2gw_X?fDo?gbzUX`sh_iNX!rw{(G1}m;y zE9M?B*=sOM#?s3M>y#*c@QD!4p`>ixRlk_hT|teLIB+2yPh@&??5c6}X}kv=1>L$W zkwT#luPtfZ?nue#_>GMhS~?0=m>d4Ne@eSxEV!tL(J{^S;p#0#HJo`&spC}Hgv-g2 zvN4fI`wZKS2Mpb}!ssvD@O}YyQ_HcfGVfPk&0H8L-D;e&LiMGtNuYnC^RxCu_h#Pv zC1X~$;=U?-_vrKw#oHxqZuAF@>bs%r<+_k$UBp@oH|G8eVPo{(Do*uU%2xesp25iM zc%S8#*4Ay?-hOmet0bhQU!b|X%O2%v&yrP}dUbeFi3e|t@@Pum^;irh`c(R7w-b2UolvF zKv?daeDR9*uDixR}vToLpQe)p+c_dLcq)vVYN2L-lAfU#K`4>Vjs$_f@vMB{=q!{N(|P}8Wr%y-qPi8~kw%O)ILhZmgdo|hB+N)WP3CFw zYjXdXyLS|`y&+npsY=f4xLEcx|G!?~on|}gY!Bn23Oplu;yFe3UAvn9-Yng!K6`HT z>e^poqb?7=?H+l$N)g>PF+A0}ePA4IvZG73<^&_5=*3><1?z2w`HUyC3;fYNUMQo1 zV|owE^n}O<)A*in8*^Z)v}xg=KBgY)Pv?bGRz$5EY*%e4pCNsmqEGKTwh*F(k7=+g zi>cz8GQj>-+0XCJ|W8tY?5sw+Dx&e;K}Bf)4uz@-lQ}A8FSXv3%UkrD=ignMyfHx6QEhEaI25u`H*~t= z+)1rRRCE*%>-Cjy<&DD}gWb}d#*TfWf!TcHmO08c(}!aPT+RT4Iy885;T>1y-|?zu zpM6u!^$Wl2b1ULyK5vi)2zf_6)MKJj%UU=G!`1v;pM(qW0MeT!=2FZZ`&Js@uH0_? z-SLF5p*o#NEg$v~3>PPSnWs7?h;HuPcUTPZPJK4|{r4JKMQxHP5`pJ$xGh+}{;g3w zJ-?__?Y<|Qy*8QKy}ed}7oYTH^b4dD`kX4|KhOn_DWe;n58gE0ah1P_mGMiPswIv;d~gBnMPhqS=>rjF<>yA}aw+_M zwhq6lVfJltRKVE$9&U*@SWsG1h$+Z-Z1LCDt)Ac5BT74?Z|bEdJ7 z6V6oDgPVkiu$9$5xXOdSeTukGZem#*6_dfSz^Rrv{R=qz_!I9m{_LYm{cb8VElE4< z*h){X!P0wG&HloDqXuvLGfJ}BPg-4CPnH+R9sk7Aj`$>S?lbArnY``ozNHW6IwC2$ zT(P@OF_7}psy0aItpAj9v}bi0`ek)W=5XEi8-jk%DeOACDnx0Vuu`@DWBsYll#@R8 z)?|I=u&?9lRKw%2S;{G8;my~$#l~<$#SeCD0xSJ*58Bzxfl*r6`aL9iAoz$UicIk* zxOZI9G2Q~~T1$h?dqvbkb660gQ+`GKSlo8rI)-UF`$SwV?_}uUX!`h#bj z6J%WpPHC8usuczqZ zA~~nEiZdzt@1!A>IkRUj_Tt#W!ImL#UQ*juIymUGh!E;M-gR2vU;gr958gie*^5%% z^{I0Dl=!H1>LUxEI{Ff&(`;UlwM139>lUXz!6)ls$-XrOrFz9&$JyxbnI+Q&w|q=L z(kC(dL-xY2G^V9tM=?2^mX&ocec2nnb9TPs?h1;hH@G_3N;(uX$1nt)7D-~ByS(yYHGI>>BITj>Q%QMalfA_!sb6o~@iBCjUXIh=)2u8+Y`bD);x4 z%FtqbRv(kE+@<47vW@(J7Ix71BrCVsIA~64t3T}5iiJ@-eOf|-Jeo?h>Vu}3shu-; z7vPrFPEY3TI6n?n0PzSiLoE^2dqBU`)N5xQ?OfNdUEY?5x0jdY{#JItfwdR1tUZ1C z;(<}D)?@m=1dg0szx$2`Q@cGgA=-A_$J*SR^xM9tYZV7P9zNLP2-D3nPwIz<`c!{% z!jycrfsu`IpHbnr&7dIT!55HE^H5x#!Xwzo7%;a3onfXtz-H|)k3XGKclpv zEAxfNPF{Gaxfa zQa`N*PuT@dCIlH?cxlG8Y^Il7nX)%Ut!=Bwtk`U8D?IhWT(EZvXKz%2xLhCK9d4{K z*&>7PGdH*P$d#pjOg2R?6wrslV{H3w@8< zPgxpKiSyrNR@y&*3YHd1#!s%7ANHu9@eUkIQRF;#6W^HCBkh%yQR;oXlj|evZr$;8 z6nXM)3zkNI=XIySL-31_NER?8I5B6a)AR{qKEl^ahT? z@_2GU!XRh5^!a&f+?09@`_$fM*8c9;S=1J)P%T7B?53$J1KhigYX0jr-uQWI5ByT2 zI>Yc{>6BZ^`KO~Ov}~~lD~&t3KJ*w%qFZc+$Z_l%2;WZdxF4&pWW)`eMk3^ zmY74=Zex~^Rf7!}wYR20cAyfE6sFeibcn#Jt`S*s}P7Rz8*rxs`r>OV2{#~D5it342!xlm;dX_VY+Q(1kmwkbx# zA)~OwO@6cYmt$b>{v*L_$#Y4q?YHSf1^()Hn2 z)tbS!dvF5VGk>G>yJ1t)1H1Xfok@lnDMr6svGJoaqhBe95yQiBbmi9bZG~R`Qu@qv zJiXlnE3*7;@vv4~e`*_yTx+3LR(WaJ%jAjYPj%^pKBiuLdFcyk`9$WhZz2|EX@D>1 zz*sCT`>4ECIf0oVtJa?0=YXHN%#bp8j~(!ZZY;b_AN7gqhe7*~H5pUjW8xwiI3Ij@ zG;J{yI`OcL_qn6hk@|HG5nOPZf3-G`3OqDir|JALYPDy#NH zS?rO?R^8&M=H1QzSK!TSs~KCkkX|Z%o?}#o)r#1sk65o%#l}Qk(_@QLPwVFb5JBP) zu-agqp4btaxC3mC$Fb%qS31~x-evfZL8VvPot@}O7B88gMy$93- zT@&;9hm^73ykSZki?<%UYp}Y1YEDazGZplj*Sult z^tNq-@Sjl3l)Vb3wr#~|X+R!@_u%O&iN0K$bZM(l+5PO0(*3_;T=0!5g?w??)N2eb z@f)Ps;BQcbyue_fYPo=ZsdUrSw`G(MAqp!;O&8o1@Anao-`nxIpt{`{JmLS)_9kFW zB;EeMipn@DsHli+Dk`oZqOubm7j#rIiVDaQ0f9jTL}U+X9T!BlQB*)6(Qy=!O++>W ziLymmjIu@90tAQ$#Ynb9<+Er3VP9-Z)d3S z4CDwA$op9okaG$PKsFr$`u{HAg2}Pf^0kl^!2%AQu(pfo$##^r@Rk zATMJ*1^G!~4#-B{KqIFjIG~?oy?)SgKC3W4z^fp{-pa2fbFnp}HNdm-B)EIp*R|HG zAi(|@$V%}bb1GeT;x;rz9)>-~S$CkutA&NV`7PUYp2JgVZ_Nien)M6FafVcocYH_( zInod0T2?a1k%nZDw|@W{$-ku!dL}Ch^d!R)(05q^o*!ui@*wLj$l-=XAnRE^FRDEi zsRU6pq0Ixgs9lp#7;l&bg*#(1;jy!kF6X?-ZLF^##~MBdSwAMDXmH||7RV*6VUS6N z8NI3%6yKkrLpZhLel5tLrabgWzI+eVcm)EYa7zy8t1hw!EknP4&hE`O*)aiiD@2BY zT;{?B`R3_G9|r9GT2fY+ zG`0q4SHwUg?{oHo{Ns5X$SYeA&{He&LGQ~s3i^Tb4xp>pf$Uy!ALK7N`$4um@5qy5 zwSLZkhIMy=KObU`Lc{Z&SV*z#DcejDU5FzZic7Y$}bHv+?Ph6|2|gf>q=UVSR7XwW__~ z6(IN8JOX*$eW1Ogdq8e+{sQuZ%~O!I?gKp(^#o+H^LvnoY<>e-dwI6ty06ccQ+_RN z%8^a20espT=$2g0g<3EEnJvf(zZSEl^Fddn%>rFZ52^JEaILW>=Z9|T1=lrVgP}%k z=OL)^eF6kNbW<;Mlo(?Kvb*yH$X^mNLDq2tniy*fva)k8$Q21`Aa8U7+A&52WNT+3 z$c+gwVM4b)1==yz0|DK@xgYe}1eh_Qo1X&S9!o(H%~I07>! zY>^V9)h)JiU!Es9*0~h+WFif2Mw$!~ z#6mk6EMnCxDP$N`iu6gZTz)zNz8}ayU|An{j(|4@vJhBN2hynE{DDj=Eb9a5RH)%V zHWe~bevcHn=`}#250W z6CUQw(WROoE79aZUOPen*`wGRWJvI>0dX`Fke7}Gfb3d)669q*AY0K?cWSPF8o_{W zd9d;z9lQ=g!2Kl1?nQ%cH{(H9VLbu;X%HmHBqK-g-YtR+vMs9!@?*wOE)4n?Rw5uV zL9YSPCqid?Z)pQk#EJl!7*q_hfd=%oH_`%R0qZu%SArlv+HKPSdigDRkgv0@fPA|U zfdmP6g95%hG8FU*77_G_LWr&1_94LU-!cLD2`dKV#6rlAcDuZRz8`4^au4f1$kz)Y zKicVeLw~v_gdx-vK>tY+8evr4_I8v*}mhOkguHd?}r z35bC^7RX4&2<)WeYY#hls@eIq3@)d^{3X*qfvn+s0%UhJTacGofE-He1lioz24o*K znC*kg;UJ4>Z$X~v`xB(kRZ!{GuFJ4uIzB_dP1a682o<8kJiQTenKOJ1=g{zHh`7m` z35a2Sq#4A}qCx|9|C9qyF9|#{06|e#B}0E`6*@aLU!;IfDT_?tz@c4f3D}_=7r-tK z`~>on7LZdb13>o3@d7#IJS>-##daXOSDpgdHV2NUfdS`Xrl%lENuaYU-9Qh{fg}@1 zI1fq2VUa4x%9RH|-k;+HvhR7Q++hg^a*su22u#Cj=Qi+r)CQ6ac})|nysW4^=w`Ze z?E_6sYuZZKb7u~OJr%p7pb z!dVL5eoBBt(m(P#@_&`sq0RpxueS`mO+d4wBXR^LOo?V3Oc>4ba_CmL-Z4n2$Bx)Q zrdLw}nN&Ura;lyq$hJ{1@L?J(ka6XoL5|lu1@g%#n2cfSE+8|@zk*z#cN%1eD5yJ3 z^D)Sn@^>J|>KzB!DhleBsA+*rEk}ky&(?#P&gZVkhcu2|0JBA6a3iazwmtgMIYt1r z;#WYpdiwyeSO9rZqH+~-F;ejmWTxIhkg*Hk9An{*gHWR!Lndxy%8CZ(rpaJV%*~WR z4w;)SgMpZvErWb^;~4_k_(lc-E8xa+83Kd7&_M<#m8wsrSjd;>C2)%4T_|M0>!dLL zf#T;ZDWn(G94RbP)ifz2M%5xI9FtU^NGb4|C(Y{JCBotDAbXc&Bj9}WF$B(GqHPhF zFwrLw2u-xZf1A}2=*0g)TK`vB{rsz;NLV-MDSM0e{UC5YX(tg4gIlCKd1dOt=Hz?3WE@aSC~TxJ97ddN9`McnW(N3 z3PJgQvO4mAlh*%-tnLtziq;nZ0#4hABoI8@&Ho=I#X#=n!+|Lv@f{4dYf|48ct?Fr8zf1f%6>7_*7 z3NjzJycd601T8FIuB}vbIkglg$)*E(iRjvHsIddCp-dpkm#ZQg+ zcUirA{da1t!ZZj&Fx)-~cAvU(o_T#_zW z;ZRG1qEptq94M-9#J6Vkrp%OS)k^FoIt${+1jRy5;AL_C_6-7>gO?g?)2L@rXUB+T=scn^H-TOZKN!a zP0mq|Hdz^6dEoJG!W=3BavZGW+LVW8=7 z`TQ$0h4a>5+qUNBnj3~o4z9VrWBa=`;ilhj_$}dhRod^jR`&n=@V47&kJLEtkUsaz zs}HPjNj~&6(UrLWzO8HY!u-!pv2rnnop=3oih6IDAR0kk*Wc`x%vSng#o~R7_o~fX zd;5o#Drax+)tIyN*pZDrCywu5=)dFRLH*2E^@j{f#`NDT(u&giu(rNtJAF-c#>>jN zb^|YHGcA%YR2jwF`<1CHnvVR?Vg2vAi2NHqqAu=^uVws)ee^^-jr=Ej&G>hF{a2@v z|8``i28Pst-M0GL;yp|C%5U#pvhnxZdzKq@AKSgS;_h+FbtfBsvXG13QU9Y!%BA|l z7`X*{r7KuRw%2aqZFyM(@9Mv(PzY7KP(Cj;+3)>MgVvEZO1iUGuKee8ME=Ws{Qof> z_x$gB{a2@v|1uw^dVS_UJsq(e?Sw_8H&@+S(RFZD#L~KVt0L7#H$1%Q{Nv({sdqM+ z=497oSZ6g`$A}tLPTVH+Jqii-da%ImAl^!jnBYI_O z^S9)bYBzm3o8&NI)SaN|TRF!X-+4b&BY6F)-IzVatM6V|6S4k&(2_&z?_JvdetkUt zd%>=CcaOa_d7k!!$lvkl2UYb;pZ02Mt~i@;%zXRXrTR5FZ; zcelS#q3m76zU@A1poTGf5o;gVbRFtW`u&dO`Y+vgu&aGGK8pIj{n*j1xSvnksc@1sz?) zT=oC@8GLx2>j`8y=G?h7w%O5FA1nD^e+xG^-}Y|XWBl1)Ho7IaTv>N&vfMc7d~UUQ zxEe1}U#GB()_ReyMW6jSF?(Lu!bjJX4(-S(k2|l| z2V?)oaB9A!ucqO9k*N7ulcihvrlUvRz&ib_?`soJ)V=R6c)yPHL;LX`RF)M#{_pC< zpaI1@ryR}y;!4^ZBdAePwja4}Hx%5RIKzaub9#&f>zpIBZ=XGWNIUNQ#rLVd{xR&x z{gC$hee=C8ecZ1YCSG`Tfqs?6{}@obtIOQs2dml_{b+c5pVDE|h_!J)8YiBOJ8XWv z_{p{Hn}RHM<~8sf9{l_$TH*EAN3n_vw%2UXOgwW-rPt<6#4_`o59?b7(7uarqVYuL z7qs%;>CX)4_53Ah?}pBOOO9?CS$pTmroOXxj_L@Oj(VX14Lo6W*o^D(+x1<>|7wh8 za?Zc3)e)&(ta1NP*l|mDdW>fGY2P%sw#0Jnr}8D(HO#et%+IND|6|yZS04|^JB)p# z$Y(T&|(|2gNGkN=#Xx92@gaEh68-=~J;T8f2K1?DrO~UT(cmEgPnqj?~CxV%aU3Tz>tc z4aeb3BVKOStflc&SNdODrl)qgW|32lfE8}O)CB}a&m}MgO19AGof8%ix$`V z{C@BGveM(Bcl}??ec?Z|bjiN1cbginzub3m-}bfFPsH8Yc`JM?(`n<)%lFr;I_|sj z^x5=|PUP-akIWQNVkk*B7AHLu#BVF|W9#$6sQmUCwh_BDzks=o-AoN@!Wen(Kb^DT zaR$jSKg!p-2{*6R%aCo{FG@boKrm;lTZfhNm)b@1r^FlrWO zz3`4^pcDU?V@udPUHM{n0ToX zQrs1+&YsCm8h$fbz8pKbWJnQz(O~jJxcBS&;YQt8f<)?Ymj6u`-}rX(%C?Q0vu6?m zzu-3y?}q38elVD+sLaLJFQ4T4P#Pz-GmGaIYE#YGbuCv!k?ncjQQ>6rPQ83g)prG?6qdOYj%Rt^QZCc+_K#vFJq;|L-!(B6!EXfXP4PtD!wMziQprxc5_O7Xz5+a-L6W2_J>L}LU{9I(1k>M4rUiwG zW~p+62C9cvvPR@0MTHtKKX=`IW~HiVJfc2716SJiiP65$R6QSGW|L|zdl`cW^v~V-YFg)F$@06wbB0I-`3D2XPVn~=^%gc zsX*M-4+WHBlP;Ba%8n}$g~@#wgRqW7!ksb2LElSTi9cHh{cKwAglrc09@5lCg7{AY zI_1Q|QHJjI>^-T1T`83o8wuw|%5zvj^K=^mw^{~BwZe$Vwb(!mkuo=I^hZ_s;p^6Y zFV#C^M$){J-_i%H%?1R>d}c6pr{WC7R74VCyP2cRFJ9?N>1Y*QvFIW+R&i(O(u;c? zJG#O{8BEtnGtO4{OXP)MCx2FKW_tpTj^Pu^bX$4nIwHg`gKFj8RPQ<*??U(|`8jfa-HVz#&-#lJ|N7C9( zw#7cqKG@3rC~TE+F!RUdQ(*R2@|7?} z0zG^e#z6K)OSC!VNbK#w!i~ zP^{s5#~~K3FwPJ9PBFZ=Jj>+Cy$<)$!>m2J`5|^4N0kdr#CoARN=(GNaqpXYfkE;W z!A1$bM5fr$^J3_5p%#v^sU^ArJG#HMKK94Hv!hEm3k^l-0oOzaHIcD<&op(A$++Q6 z!r898kxxjUY%bT=H+#tU`6zNB*5!$AV1Q=)Xq5(kH?Au*#_HV2zT|7}F`lQ3TeCRV zg*Uau*Op`NCK^II@hvV_YzS1t3HGSH#g07*D0|dKwK5aZ3~fWldLrC~!(;1ArK6oY z6G%M;?ZYn`L%SQo5%6^`Kq{pxK7H*G2O+A-QmmHtnbs+({yp=ZHli+ z2XWVfR%gE;hQ6Y`wX&u(ct2GXtQ^J=>W>W%emz`B&fx1KIm}Wm){?&TckECVT7;~; z&^=mC;K<=TPRy=91i#N^yy|U|%?byJB7u zJGx)ZSI?YgZJfwEGyEjr{a)l4cI2&Lr_JC3$&=VMxO0L0Hp(offR<|NW{e4W^`Y4} zXxKkMHu%A;h;G-_bBo6<3odADk@?lcDw=tWAKy$f4C;8SF?=v3gBW>SX`lP>e*5CL$|F_M<@s8q zk?^rSxa;Cq^a#>1(@zxSA+0`#R2Nt-$6B&fJC7XUd)B_u`%6B}>{I-(Q}St(p9_x5 zQ`pVE!Jlv~JaV&tt-*zIr6+g=UdO1pBT{$8hgBbcX_Mq#ym%GyX|;g$m6rKLCv08k zR$_;|USNSqb#dnjw{$VLC0j=K6Wr_v$nIwCaPZJMcht8BAIE|ohZs+LxZ?Rn{%H4n0jzuxBl zHb$e?65L1nW8&-iuh_oS^W)fPY^$tYvP;o7|!SG5>&iM8-pVxJ~Lc^j{bHBJ5m z`ADs$)~C;WQ`oW*hL;yVIFYi{pdb_`@q2f(Oxm20eWKFpQVrT$<(8whGBtdDNQcN% zRQSeV_@#WUI<6{=%Z1GmhWoH`E;TGxlvD^_hH>NF5v8zoh`{I-jrN|A-)+E5kRR>2 zX)>X2Ikqf*ALh1je6LxItu!Lu)oiFgTU(*c=+$A|o*>>rQ=}`5{l-b#v&U zGv~DjXwo{1N4gcODZE1D1+y`}{bx$Qr?O6Yj$QDNBoagNke?K^$)H6XW#H9Dk|A%r zWIqy`vq(cPQ@seUG5yAmM%-4lUdR=c%DQmpUcZqrQ=CWi7Ridbqr(W4fKKno=G9^w z0SBk5VBTMm8)Gt~a59lnmn1!dbkt>!&hZv$V}^Yw#slS%WBWfJD#)LV5+uT;j?m-^ zCl0B$T#+5cY$5i{FCn@9hI2DRbgmN`n4j_Vohcu?jJz58Lqy3|HRZ3rkm#&J549x4 z4r!|^`z_-u)9Qx4RB+>DWRI$0rHZSd^QinRdR=OEjqeqodZ&$;VK3v1)BrQ~%&x1v zk5>p@6yKYLU3sg z@|Vf)$Lnt^I(+Rgo=xM9BosR5*j?H~MP9)oT9 zE(k&`XQzE6CS9iU#R_<#IEHOipJ+;D711a0j!k|^7tCleB<5QhkCk!Hi5nVICeEnq z^n0mkM!iZZXY)eWAVCTYzars&%=IT)S!}%EV!twp*%&h!LSTeme-^-fVPC?bObBavAwf2eaNhhOZc?hB<51$hD#ay`2wVPw6)y|liUH&AUpBTjaF2(I*yCTbM&7`flK_JH zQrbsLJBuHa>c2#Y&5fZa%jJcoEK@GV*L+ms-X+2p%jAh{TgQhNm&5IV5+m0i}{3>M6!h8k@I1`J%@WbutJ#Dcch6qf8;lR?<}&y z;U)J>BymJ&GM1w=zt!PIdZy2FhfF_DiCLP%NKa(pm|R$zLx$2`O-|*hTl8`MDqMLg zhkHAS=q^#0N*K$NJ$P}HO@cj~)&?3pz3ns-Xpd;(FG@=4kW*xPZr0=Rz_0tV{FJk( zkvPLFSB@=?d6s;dJu$l9^iKA8U`F*`DiIkT7!T|?jaYA#4M+1<$fsjvyi6(!-)+7? za2zi_MRtq{is9=fwx5+PnMj`z*sNVH+4Nb_#gPAH?U3DDsZvLx=-N;8pt??TjRCps z8iHo+6Zh={?>k^3TyWJHeRGEB?@P5s@eMi?yvvH1Q+IIieCu+>ZYPq$WWBU19%;^u zlN>%q2neDM%4u+M4?HgtB@5t>m(6_MWMZB#^H=d3gXVDaA4k;r7Co>-vW4lfD(jBP10SxIOAbdAPUx;1!YyHLG#`C3 zBFwE#?+h)L)i<-G*a7Qu-*X~F?wC+cu1rbXC&l)8YAhW*>`KlN94IktmPNSK%3*Pz=^ljMFS2>2z8dA9q}Z|| znU#p0MY!h0^j3DXq`xDy>$a#=pYIZhD~c_?APevi6&zC( z6}V?cwZhFHY;$D%O&I5jkSI27#ww`ySr0|V$B35>K2BAbkUVbt0m6-=Oq98hiuycB z#$gd81%#EBAn)`F(Gb^7ZnzzLIiTFa{FlzCxC=DH`w=oHs_=K}O6@^Xyh4|pq;McH zwv2HZJ3*gsfq9!9H!@oD_@gvP;XN$EkRhaF`0*jG^dP|2L zqpSpWoQ1%mH$2!}hmTjo%7)E6$Llx9-c448J4p&>qdA3i~TY#oJRdd93Xk+1~7W+6@sU{2`+Ncy>e@Q`mM>|%W68O)a~?T7a@c_vlN9VyGPWnXZD_iEZbKHq1nA&kP7 zn7CE#H@V9DW@b4_~QC7+1 zxk$F5Og+f9*7RJjF8MxpK`W`qRZ7-2?~aWfWxfs69SrWRN_qFHylM=u<~9}MIo?Bkh4)iSCk`ik#`WgT4~!EB zc1qbMVG0b%_9`Tte$kE2&8k5ZZejYTn#l3 zEp*#ljc7Kz5`z=VG5ZPfR|MQ+BPTRW!dxrj1d~-rJVAV%4%rg9O|s-ljy@I#PF^B~ z21v=BNYSTJ0~%9N4?mbo)tpuANR?Kp2y40Fg%b|nb@c5QOXpHGmt5n8s_~j|0xRw&kl_u{|XHWuHLryXS@&Hruiep3I?n%i)JbQM~>!*mpZ0RGd z;$e}t<#;zjVjfmOFtd#lnbz+gbHir@a%D`PGlbqx{JobsqSZv4iRRs3H)-z~E- zb8WhsF*9z_4+p zNtAX+f_)(5YCzQ<~ZrLP3%p+c7eakdhK{a7bve#8oI2@NlW|>~JbJTahc% zD=9IR`U#~eY~-kJK|F%ZjxCTK@gZSkfyn8P&DlQws=F}tpX%{utWWwHX8gfeV!rG% zU0Y-N_i3@mf*v#$g}$^FU_ypkG^aSxgatiJ^q5VNeDY&TzZUr*|0vEOKDPQ$-x*e7 zu3Kx6$+}I<(B{1VfXy1jP$tfr*hav&I87WkDs; z@hoxF_^Ys^m~qbD`y7v=sivWcVhy%?W&JmL9O-7<&&E71i_aDrBohZ@GegzPvEw_a z8pdWt`Bwr~^%RXwH#>gTEZA2vAhQTL?Z`^iX7G*z10os;YX+8~&cMp( zBFV4gQ(Be>L5=a?Df@$Q~Nf}5$?)@+Y&9i94-O&j-P z%2*Pw0r4x!R3}1ft6kM-V`|d=@sO&Ji)NPJaH2dZHRM6|^l)ZqGbX|H(K?c~&9q~| zgT!M0KYgtCMbagQ>7C$8Q4a_o;!*&<<_K6Z-|bsM#bK zDs(9SjNg;c(dMc6Pb>Al&!&3VeV7k3V!EKa+58(JLf*3b>da~xaZ}xcg&-nmPOXHg z88F`cgv-xCekF#TDwx_A>K?7jE-0Q5 zOgA#s)4F@U`)W>R|q+sh1Qcqw?aEM6$V(}(<6bl1PYDt`}8bC zV&lM3#ty3Nk5T#~kY~-l_;&nl{9$lfe5pQE`e%OlmT%S4$$u?2bijS<(Ai^dCUB6M zC;e4XQKf7wgnCU~`R@T5Z8G~Jc#ll?vM;zv`efQ8PU^QVwWMlOInz-b%|ocAXvAQR zao)!${V~PUIjPLZKr{zEVJc3IQ(ti7-zvgMN+Lw-rR~3GrjAdoS;3I`yFNd1PsnH^ z7ZVz0wiWqv`=6o>tgO@%d44P9XpE%<7dX!N?%MPWmsMTGqE zsinxo@;?!Ul3#oN7_aG(kJ0+WNzjX5GuhnJtz45BpF1lHCUn`~y2=(K5cU{4K{CJ( zh7{7m7Nfa?(EqL5{nC$pVX1%A9AonLD*dsRHSpn&KfeCY#Wof3c6*G-{~#I@Z4#y= z`zzHT2VI{qd`TR443T*M+jKSg04vA?na(EQqeh^gWPo$Y$ZRWwBp3vIpk#nnUq9Mx z&Ym6-I5PutFd7dxT9dEqtgNgI>>EI5ihTM%%`D zW1)d)AivGPsUO`T2!$zBd-i7+LtZMNUQIdWL2jlNw=19_DQcom&3}7V4?**f1p{>n znz=q`MV{j1siQ%WFD4n41y;g~FiFM8|%tQ7UIr zcII^qBlc-QOEvZ>6V4gVzm8$jQ%T;s)Z{GocsiUaP!qF85EBPFpI_go#SDCbAmft) ziX55|cGmJ$3n#RhJ{>DD@Dd^w5abvT0j$iE#Pqxo&D2rb&jXHX3UJ z??jdDb&CUGQ_>dJ@W(IRD&gVppT09RJ-^Gfva|DL+I9s+($N&(pljzJM3rf|)dRXQ zNryH3?#q};xW#)nK+o-RsqFmpGI6^?EU`A(Hz?}-{ire*w_HFsCv9a7fBrI{5{}T` z_X6v^%e}I59bN@(;^5 zab6unw`vVD>YY`>{ZE^+URWMJtJdW49>wng{shI30CXRPE~1b*K$}sh0-(&h03r8K z>PMhXq0~8)GGo06@I0IB)|`1S)3(WD&^V*s*xx4ApYy_!)7W|aozp86~#{ip8o;xT`05*pcWJoAwUhHol^&DtN{HD zbt^?*!L#6SrN`4Y?=M-dg8|uP&4b6n^F*QQO&)ac#}WJ~0e?gdozXAD&<1eOzyuu{ zmwAQNIV>GK8kWq04Fm2PlzAMyYt9Sl~h6 z52y!+zC^eVT4t3sryLK@Gsy$_w0hIfm~lp%3V5LpUbr_prF_ULGfuGzSA6#Yjbkuc zDF!N4Mk_5H_~oUgGW55v3#xax1Jxf!ov^KRLQ-^4rC+;%EFk6Z#yS*SPleSpw7fKy~}ir>&V z|79rJ3>umtv=upMC1hJDgk~?gg@y)+MIHPE4Vs~`p#eXJ#)b&7X|qyEO$?x)#m47_ z<|}ye?ZE{BxG*1FPzD!t8=VFnAuc5lmr#fcAC1d60IJ}E0l44;acPIRlvsspzH3M0 z5{kBq#zhltwrt=k^3qcF4hk%wK!`d-LJR7n%9HvjUD4>GK1rY<$A!I-$C!pFQ=+j|{4g;d&<3D+IfDhf3pi>j0 zk0@|%+ZLXuYFy(qcw?TQ4cVzo zt;xF(mCQjp@Eu5tQR#P7GD0Pkf`_OzdA~v>Gmt7#3a-~}^3FgdlNzU#N}y7G%MfU} z7}YSKtw$qq4I=Si14Kd&W}tyD%)t8JzpQmlS)X2JeAE%jeghX>VI4e&?leH8J^Ei* z);Bq&JVgN<0FMB00pKYBH2~NGkPCnt0JUfhj{$H8Aj=r$e=88dK=6R*MG0JZp4~&3 zcgw4mZ}BX+px)$t8^Zk?M7E;;ML;COY4HBDvgWO}fIWv*dI^9;0A2#n13)zZrvS*b z0U!;4ivZjM;5h(|0Hg!ZgaT*{NIDP!K%@Zi+W&z^0U_Vfmb?%+)h4IGUC+y!z3jvD zT7C!21F&4cdZD7N0N4SLj{-3IEl&aP0)Py_1OVy)m;fM!_0fX5;Rl8BP?!aUZBR%C zHzuATFh@$DvlCSl(Ahv>NWf5lkpLqCMgd$^hrXCRgGC0>82S}}M*x%o5Dq{L3P4{> zAR2)XjiCepasXffKm))MfE2U_a3c_+F?1JzH0Wz!NC0x$maGq)JGlA*`jP^DF-Zl^ z8@OWN1W?g@0F(jH0l)#GL564qLNtb60?-4%W&o@K$U*_Afeg_IglG)C24EZjRR9bC z@P1x@glzXRsk*uCG+>zuh-V<=)S(#QQh*}^7YHs7r9oz~10owLLIAh|z*PXk0Z;@$ z0{}CqA~XY*WDo!m0K@^13IG-WA__nap;-XnQ{0+M5=wq*8hQd)HDK0&WrIVAV>%=d zN8la<=LQvJ0?`6QAP`(2+ED=Jr05|4P5@{^VJsACLM!IbN*K5yg6TYjg917fkf4A9 zF%Pp%O$@-f2&ARc@)9_Z|#LVl3Jev$$ZkuXrhL5+o-Nw6~pb|#>uXsww*py7!H zfCRwItJ0nq0av?god)MFgeCp96qfWbx_sB5>0tp#k5K6=Dy^@9gU5wVC;g1JVJBG1 zKR{ZEUeWs^KO_(q~k%L?u;_Sg16K<|y>= zyBvc9LW12AM8LA2BhYCNX<{P z%Z!oQp8(8(U9BJ?^a8O3%@{!B0^tTk?J)py0k8&O2mn(wVF17bpcjCxP{ZjLa0vbh zfKC9`K&19_AbpOU&v_Ye7*g?lNX1)E!g7BN3-JgnvM&(Oi-7eYFc_PGI0Z!J2_TAq z_z{RkAkG2N2!JO5O(=j?K?cAQfHnX^02l{A6@Xm;c%6U|Pdcj_pV#sVFk8TQfGr1X zD`0kTsK|!{mX{Ue1URA||D$IxdWe1TvAp$&v35GiOC;6@-sW2gZD4*==`a0lQ63Y>(_vd_B5=b6Av4s--e z4A>mN<^!e-oz;QPI>6DH4ABULXbg=4und6B09XT%g`fn4gACyabO1sK#6locfiM8V z8-O;n3iQOs4uA>c+E-+K^5cv)IMX1*nMME~92!DTMXUj%!jjny*juP)F96{H!~&oJ zfEncC&|(1g0q`CGD}Mws1`u>0h$sP7gi?U`0f-794nr030Ax47V)Fcw?mFlQCj%vL zG7t)*z&`~ToS(Lt02T-s7d&o9&lY}y)%yYfbmdw=VJsAqpb>LuM1t;_4F&H0HVByx zB{(RdKuE)2R75Z;{G(8B3lM>jrTL@*rt6>-a+wJA3_TH}K@k>eCBeQ_*q4CT3Y`5p0beVuSidWrlE zi|TQrp=n4Ke{HAU#S-m>v56cr+n@}4?)x$qb_jN{hBhO#YPhhd-Re3GP-9a<7Jr8m zbtd(?M_Cs0?IkCM&l%whj)=5+Kse#PfJ_l3mS*`7dS7rtN$UoLP3{ZH6y6t*@Do50 zwFZRw>;>c>KHMw%fx;{b4I}f;W^n`B9hn38L{4Z5l~M3|cdyV=d0;$R=WY>&emyiS zDH4#2pB$Op&Vbk#Gx$aOCNQo=%x-2d%khj~_BK0cRz_V9N9^VUv7C<>Z6SholM=3H=X+Xrf&z()9H zM&I-Ia&uRlxjgeZTt~Q1t1q4=HA~d~<*5;RMU;{`PPJ+q!QL zCy0WLsfQEzG){Nu&xaEX)}b|N$%WVd^)P^02kNIEPT*ZX{rCZ`hQA8sy6V3@oZ$P0 z>C!iUFGU)@J)9u*Tj{CBzwNuM@!L}d%o?X3POxbC_E~;o(+?Tw(Ejrw19HDGX2nPR zI8zhJf{>vf+1!E`_!#AmiREm)Mf=zrlol_(a`4f*wX-cgf2ZlHRetNR;hZ(I7A-zE zc&K;%;d^a6NheMEMv@*7FOqLKy(r^7Ug$Nb_N2IZPCBl{bj7AqtZ@;iGDihr4lP`k z&Wo)!p*{U=CCCj%yJENtmZken_@7@drXfG&tV~t(vBR|IHwPP~tc=h0E5S#kVr%rTP%4ll_Dndr7hQL$PrhvKJe9Mz0B)?BuT2I!~Q-y*vDBNBlVOoe{E2 zxVl(|FShe=u?}BoiR+39Ub&0SBaMHh^KB#=k7eIsnl2=77M~E@F~78*a*6s0K4QU- zONZ6NNKFz^cD|rHwC6H^omu+DH=?%TwInU;kp`+9uUt`lGWIqg$t zPJbKS3K%GKz#dEu*w(r}>jf9o4vg%%`|{P#c{@&j`fX0MOcwpy@#ek1H8yA6^v&kg z7*X{}0t$ZU?)TSA=Qt)}Be;>p)RA>!?W{NK5m&JZ8l>%A(c-Jx ze#?hdyJGpS7k2GEZJi^z14RBsKWhUodaYMmUP6-S^`NA+^>diXML&0gp_cnc3wX@DSANvPEyMG9S@Q+2gcSKgnsGw@ZOcn$ZZ{P%gt#$FE1+? zb;GonvBOFO>NRs}$BcCFXFHkn@jo9Bs^`ZNBE244=QxagpC92^Iq~uE=j9=b zTlVOlQT-Y`APrFi)G_*hCHf-DjO9V>+dj$3bEDbCZEC(zPtO%?G>@C>0 zzR!?@ZTR4J#yl|iTE!WPU`xQ3fuVQ5se);Mtp-~QrVX|UY%AC{Fnwr$#x5`;FjFw} z!AL)V?FTyub{NbO>=f7uFdHy?urpw1!F~oi4|W0U5?Bxz9_%vMHL#mt=+OKMb{8xG zEE((}*zaJ-GkD1Wdja+etPrditOBeYj0RQ(RtxqKtRAch>jFOTb!NrVlk!w;&F7O#d=<3!+}@-wv@> zt$#bj-f#W(5qllF0nfuikdEo0feNSoMWZ7G|NguO@gN9j;br&<$JPXl=h8hZDpQBG z-aT8um;xp{x~qPQ(9$V->!ytI9>XwSh_ z_f94!wz=!`+=wlqc{vx7TFnzBh#8lfQex-Q#rm0%5}ofJAL!he@G$kbVN$w&Ok47i zo%pTcBkNMljf}SH?qvGS>5Nq_&;umJ$Z@3ihb#B2$ zwJK$`;WlRXS5|Iu6?MP_U(GNGXT;rnAk;UHq!lM0VF|}ZHfl##kaqH~BOQCK{l1go z*bj;BWh5$T%L8jo+(ltiuHicQu)7@5?$bMZ zSUmA@Z7f$`;cHENU!j5;CZ1S* zNvz4qiRpVL-^B1LNlQs({4;D`%YgEcc;5iZ7GjATMh=ORTu1AFY+Y;mi%)aduYETrx0k$Yr4pzv z#yr=MPN51jRm+<8#{KnnVwO&-W{fGuZ?=Ofc6>vdEOHkCJbfxMrj0aD2&yjiR$qz? zJ});cibVM6-p0L)1p=e*1UK0`p=|6pp7{d2&We_o>=R#a!GwvAxKoY zfmDWtf50btf8iTLfc7|CaL9Ru_cdAasKry>x|_R-ge%%v7+-I-&v3rKvsy2wUA6W7 zh>C?!$n<^0JY3B_8N8TzoAr*bO4MZ`eZ1Nd-6~%-YQzuUnFn2A0`XTlP6FYQtsFuzCXC@j9i|9lg75%+>9 zIA?UeB+_bUc$8JmTd9L1HfDc`H}ge#yqUWzQ%~_N-+<`U)!6V~ z*6fa{6+7c^7vlVJlAs+wX(hLM-i_UmE`R=XIB4o5SEU!t2%Bb^cm8#fD|fc@Z&%`^ zc796}{jf20C60UdKhuQs^sh6}D{;^)hGvM9pus8Ry^>o?_bk26*}Al;Lcy3}@ZtxP zkMfJ|9*ZiUyKL`OXML?lTG943v$d{Tmgj9-^mg~+#ZPRL{ZEQ*+fHgd&Url~XkPh6 z;pt?rWm_nR_@MrSQDEpe(v$P5uq<_`*WZ!%i4v1D zqUZiSMQ$ST>K+xp7Ja(lCax$a#a7hziA1UrcUF*ejVvX(o+gQn*%-f03%7AqY-@Av zfPI#da%9cO%f=Dyts1e<6hvu$A4cUH9}!Hw{ELN;E#We?iTgAy)6kYQaH=#>~%FrMciaMEx43X?hDCAz3TOpb!V@O5a zB56=`oaQ>sU3-}CwG^<8VP&l>jH>wI?u&z4j7@i+e2 zr2LE&y*WPV)AN^%<3HW=C})vsP4(UGSIr+1$``jUbM;1lXm}v)RMzf$NqW~7?@Ygv z7xgCMg|GLYpCW3?9V*jvIwscN3#_efUX`kLZHia9g?;7oO21dOr5^%p)Ay$Cvwaa@ zma1Ru+g5V^eGhlHZn0dS%PW3a>g1%Fn&|VczJl~Ge&y&}JIA&lK({LH`oP*iGkzs~Fo3T0adoMlUN6@HOw ziL=owW%oo>mY5YCririO&(vaym_G4BUQa>vu|ED-L$W(>q_#zmPZTx z9V&SK5lb5*3J1zKN*x_3{N9ts>_7Y7oa-3FJ9Vda`hsrnH#U6>`WpWbc;)sLWgMN% z)m9R#{&5ESktddv@P(sr&buK8df|AkYjuv7^8C>D{4>@`@-~|5-KN?F-*Q_tZvA1C zTmKAj3QP;UU2{JPu5={ibwqvX-E$yAK{uz!z@^r?*xxUH8`oG8LYnuPkLR?(taSL#ppQ`Hhd=6RFUZ5}tdUWZcU99aY@Q z8Xgb-kXm}|y`!LLm}h2nv|QQ29D7bG7ZsH<9A@Gv=OpV^rv7-}J-=~5a*l#_Ws8G3 zB?_GqCZ30y#=4aqbyPGbY)RlV_Z?m({-5II6Wm|8bE@K_6YK3hwVQeLC?vRNxtn>R*R=6U3H4d(?%b;Q zLy7f<9z9A4YR}!Xyh;WVa}oq2++DRlExybwD-E@SWUiS32#CAK!o&l9%I2b^TEJ#4p?Q%L2q*Xp60Ps&f+cB$R+W7(_x7{q{735~{kz?FU*%9HJ>06kA^YEqjbGOmGq8mlM_=qN zcKOqTrxfm6`T5)sRda>-;_y87qp^KS?B$^b-PBEvq3|O^H@rXdo7^e%VetBo!So+k z@B==RLo}P2(f#r|i^p%1pP2v z=U)Ud${B`v_GOc_OV57oN<8wy;2badbVF|Q{(!1K@B9(|Ese_Q?SH7R=2R*O&7FKE z#$Vp4dDlf={sya#t8YH7XjE7>vsNql?||pt%Vgj6@m5@jvxr#FUDAJumq=HbmmX8` zUmE0NlYF#e(yhoty$?P(JbIAfF=BPbH1E{lT;nad;n}><(<`=do;B3$k>8NLyhVY| z=m>FO^KAH6R+R3u?wNRUako}-{*@I)E#|GIP9N%5aaZPj?*M`vZMO}Xlmv8!Z( ze;IFonPjek$CQbs0h!6%DOLL$d~*#>H+;FD5t!Ze%q2JWbcQTy`*Fdm4Obj43c7qO zcnTc@ye=t;O%7KIYk$05_WY;yaXVQb{fsZ7_0gdxef;7h*5{*seZBnUs^fECUS2cx zWct7~#>YxSEewVww1uHBz|-B4D+P%^Z*+b7;4G&;|@R#4o%DZYQUy+dx1U0mBE zx4sSW>F1(7oom?-+H&0b0^-xJMQ7v`**$2RS=_xUK0PEl<8_g1TpL~7{cC(fPg38< zNVSuZ%Cj@8?N|wqtD{HHMjyi)EOqvdJ79Cq?`{y+iS=3@J?w<5S+F8sd>!*A4s3!!RE?Hg~Uj<8K z$=MQ%7w+j-J-4PvjH#X%!mN&K8u8aKCI&jcPUH*P);f_dXgBJZ?`7n_rx@Nf_!On7*+0FU+e@bKp*xRN%tuX!+v%bMMc!^4}JaR6AUWzb0# zHIwjgKk+^k^?J9E@NjqwF1uJDQg*0-golj;I6Q2KelUN1>vv7VSa$_jk5#KkF|k%s zOt%$^;s4`JY06Qw!&Y&&q@&vRxH4Cv^QVbQ9@>Ndyw-)8rD&dFPo%_=g_bC(Vqjol zCPK$02ekns^OYA^A{3@;tkE)t^h}pm)<2{#$})rW3jb&;ONdSh3y7fv#<3*JOte6z z^DApew_K-XESm0!Dunb%n}gb-=@m#Iq*n}hc33b-g!W^sAcC2CAv&co1Q!S*iiLtS z3UCD#LdB%T6{tl}fu&F|N!Qqi8(>NN!s}Qr#VyVxg1@Ju2634S5dlJn25J$cX9^XQ z3eXrdr=#@9pYt!nJrF4rEF2m;++&WeSg`Q=nTv4`JMN3-;w~4qj2RQuGNM>m09tH` zDi%YEGZ6^WgHVN*=saj87A#B`lf-ib3H}U%h3Sr{9wz^C;gE6hm}rm!mTP|;;|fwi z=Ff8iwFoYRT7V|eUsF8%i zOvnK#!o%@a$_tog=vb5qpK?KM7E5}5ysJh~_+0>#0WzU`2_mVQ$QlO*1|UfsSvlf> zP#QQO+;+eLB?t!`LO7r#;eb+v1H#T7$^xPYE|)ohP_7Jd01hJ@a5&+BBM1kSB^(fh z!$ko{5)LR&I3R?GArBw~#eplp-9DO}0748I9Doot2M(x2IG{4&fMW>kM;s;*@D9Xbaslr|93~y`F2rFn0`Eo~CMob9 z#9{IR??qgk;an6YM<<9gj()`3dw;xBPD4&~kiP-!$ef@R1|`F_LBs##RRmOG4D-(f z5mRL5$T-O6BKrl|LS##jEk|a9>{n!V$krp20PNV+&Duw>zoq5TO%#`T-S5nP5*CQbNvf1cRlxxYF4myjk3!%=uc~vsCT`+ zC$PEe+r+f=O_3dlNmt3$q=j&=nP%K;cT20Kb=bD2BP#r*x?iHA)>8#DD>c$Of_b;E zx}|B#<}b)K;F`Wzu^ae~_nzii?d+3>THkaXNttcWMY`=c#uw~; zD^<^4tQalDKOzX`#@^24tZeM9G_9Ff&r{~7IqVMzd|R8{9&u0h2|tX(nRRCWir76- z?{f`qJZ+08FdO#kfR%rPpxFDGmBmAzR*L84$#WK*IMVV^E&FU$QqNycdaZbqeb`=| z*Ajo(A4Pj6%zxS&Jd3+9FS+A%ubdRW&Bt3eYuz$E+Z!GW^BQyIj;bbIT!`9Ow|Kzv zrTmhVjymNWrTpMObIL-r9m4Xx4Upy-_O?se6DPHe%kbTK{s6{z z?>F~RnmY3;(ZHm0ILf;^*Pjn`h(u?J1*$Tpe+N-*FY+`OaFx%f-P}L=ZSa;I)4z-^ zl;XeZpg##VC`Q*^{LB3Fo(MjbWFDG3>Exlx8so0yOTJ1rszWLl7f3zX?_SKmF1v{@i&*OKt8y$Lf~o>YMe49Yb%{=I1G$%y#(FGiq{W zP-6G`^c64I=bi54>T+ZDhBTJkC^P+~aP0H1VZ9cso>_VMtq61o9@cxP^1=GRh(gcT z1zQ3ve=Cu4oAV~j%ri2*H7}7X7P!mIe;n(_dv^+RCyTzt$Ru|*8{uB@&5mLR|LwjW%m4RnFI2x(Q~SEXq6T7C3~JU{n?mOq~&<5(X1mZ+Ur+Jo4U)Q27kx6 zfkn|y9C1sQ5k=phMp<$DBz{zJpDZO1WrVd3PNUGrUhxf~DU0V>jXcU5`p;sk!%>2v z&z2{=w`ObJzG{2#{=E;&ZLHs|pSA4ZvXEyNtd_5AlH7iZepdT#?}@9PJKyvh>$sK{ z3@`86%VpF41vSM?%HCRq7jXO2hYkaKCVFi}qE()D&rSMzD{uyRIo@?{G z12Pi#P!{L=R^Qt3x5L-|Z~GrgrXA~%d(EG`^K>3h;ef63#>)fOzHQ{*IOFSjCE_>k z(obX8*z{dp^VINF|3?3A`C8Ygj8!#bYw0BJoLw!OW^EO8wOTiiQ9XJ*{n@F`s^FMs z9EX;^m*oL5vCp|PCiGZ`BQl}^Tf_n;O70Uj+Wwvy0-o=KU9Ttha{u#WJ4cqcR z(VVo)0$)ApABS@UJ{9}^mLL<2CvzYN+HrE3CpJ8FHIQi{vm&TPIG{G+fa3`VoIp6> zM8W}e2nW<99B>lhfO-r^k8mK9i31Ss0}uf?m2g0P!T}8k2Q(xc(1>urX@mnB6An0? zaKIU`g@DRtAcMUOaKM>_1I{8G5cWEd2XHpwfM$dP!d?jS0L~#C(1LJ44&huNmgEEh zvrnFj5R8os_R7Ek)d&YvB^(g;%8&<8g>b;Jgag7>8S(%s5sr|{d{Y32A~M)30|y*U zI3VoEARQ3)%D@5T2?vBZgw`U)hb9)Yn}d9iHk_ox4sRIYkR}6LNXP_NkPc~*ghSd8!XZt9a7cq05BVXDML4916Ao!`Kpt@>tkolfmrW)%87@tqv!JwD zY0|W~C8fdYkft3?(VE&|L20p!hr-G1sg*KtMSM*_;Wk?&9_a|_=m-*rc;vzV@A1e` zEroF`i{ZGVGdYwBYt=C3`NZ-W#&k3lJB$jwt~iE%yIEYD)iMkoPjkhG)7LGj#Vlz# zT=09`@aS+DB{m)6DlWm&7(rj(hK8(sI2~7W^ufjCYMg&!f^KZ>q6MX3*|7B^L5(d&*-s;pyj zwB~74>8Mfk<68)u<><#psCg{ok@UUOlqGdxBo%sJb8%><@@Q7|NIK#?wUV_^o^C!* z&8Mx7q4wXFrz2^~n!XMvUGj9=F={Jo`Y2j1jAB#kHjko>!l?PIY(@IqaY}7sWH3}M zCI-)YvCU39y?x!B#l)9+ZgF;74Ksb2v-j37qp&0=MJ#E#0+tk_fF(663`&|e8cVu0 z8cVu%5~&z61|)4{*^IgPJL>hT0Ap!xFlEH*7()-7rPi_*DuPfedf6#Thjj>THNwz6 z@<0)bRUQZ3q&9wKBM}QX`A#<{kAIPBq=co$s9>q#N?2;C5|(PAJSa6p8B1+Z7LmGY zES7q0EF-lBfJn7g!BWFiu++;TNa|Y^EOkHyOLb;5QY9TbnIXLrhK4k792V81jzxVN zheheBVo|}WgQ7}Ru_%t3h^PoPEUH$G5j9hNP}DhfEUHo+i!#21M5(c{sHJQy>b^Gp z_zI<_9VElTgAMDVm#@>7pGW#N8!KIa3c%!>u7Q>A*1$^NYYZx#q=}UVYlNrr^>L1yD=}-9ocy%y=yB zybcz}9goF59*@OoO&ApCGXaY$njj+1WFi)Ka3Uiv7l4S<)xqNYbg;NFe<5)Xb+EW9 z9Y)*)^T~L1#-Di0f;v)+XpK~5wDK=eYOKw=SZlr>)|#V>waQJxT6azw)cRx+)~clk z@~r8%;i$15dZ3X?(xXD%jFoBS3zV)j+>h=~JB?`hDdH;PdRUzHWZZKV`c&vS)9)Sk zb|xdn@H)k2-I|QW?AFI(5EW<3{N-qBfwa{zVk? zMd5dSEGkPMPrT{Vn29G@Vacdjc@F)e!Y>9`&1)m9Ce{F}X*R%WRv8Yei891$B#js~ zs@jFs9;;c;)2*S~;rLEmj0QW8x^MKK1lAd0f#arOfkRAS8C0ax|Dt3jj&vR#%B+R3 z2XS+9-{ic@XBxUioP3;jZF6#44HZUAV;_91nEzZk#fG_bxSq%0VX? z)0OF-yOcF+@(kMQZ|XPJ2?o{Z)W0bXRWO4J)lgHVRidd4tRmCF7Dzfq$f6d|xqngW ztevQ7Xv0BWHxW_#%0xtInkiPgXeL$~#o*6M8)u3r<;)UMdJUa?SK2!ZD_w62NR&pI ziYQ$^oA!&QCQDX$hyQ^@-iby}gdvu+mpSTxjTmY;D}6TY6hmn!STc`8{FBwt=VHyo zaWClHbr_(f!W`qxa0%g4?7PE`ID}7(?~aqif!Ak|OQjKqaE<(4qKP>0QY2DB+niL+ z-1j1mHN=7MK@rCZ;=s3qh$B^asS)tX4>^PlY9tQf)co$ynnNm|L`n!-YD*m7O9&mu zg(W0O31Jtc{9HolXdn(pX;MPiH<}i>^6zyB9lsKX;{sAb*lEXpE+KRz3rmQT5_lCw zF?6AWnbOcDq?建议对顶点数较高的模型启用此选项。', + }, + vertexFetch: { + name: '顶点提取', + title: '优化顶点缓冲区以提高顶点提取效率。
建议对顶点数较高的模型启用此选项。', + }, + overdraw: { + name: '过度绘制', + title: '优化顶点缓冲区以减少过度绘制。
建议对顶点数较高的模型启用此选项。', + }, + }, meshSimplify: { - name: 'Mesh 简化', - title: 'Mesh 简化可以被用来简化导入的模型,可以在需要模型减面时使用。
在一些少数情况下减面后的模型可能会出现显示异常,如发生这种情况请尝试调整参数并重试。', - simplification: { - name: 'Simplification', - title: 'Simplification', - si: { - name: 'Achieve The Ratio R', - title: 'Achieve The Ratio R', - }, - sa: { - name: 'Aggressively Simplify', - title: 'Aggressively Simplify', - }, + name: '网格简化', + title: '是否简化网格数据。', + targetRatio: { + name: '目标比率', + title: '简化网格数据的目标顶点数的比例。
建议将此值设置为 0.5。', }, - scene: { - name: 'Scene', - title: 'Scene', - kn: { - name: 'Keep Nodes Transform', - title: 'Keep Nodes Transform', - }, - ke: { - name: 'Keep Extras Data', - title: 'Keep Extras Data', - }, + autoErrorRate: { + name: '自动误差率', + title: '是否自动计算简化网格数据的误差率。', }, - miscellaneous: { - name: 'Miscellaneous', - title: 'Miscellaneous', - noq: { - name: 'Disable Quantization', - title: 'Disable Quantization', - }, - v: { - name: 'Verbose Output', - title: 'Verbose Output', - }, + errorRate: { + name: '误差率', + title: '简化网格数据的最大误差率。
此值还会影响结果大小。
建议调整直到获得良好的结果。', }, - algorithm: { - name: '减面算法', - simplify: 'simplify', - gltfpack: 'gltfpack (已废弃)', + lockBoundary: { + name: '锁定边界', + title: '是否锁定简化网格数据的边界。', }, - simplify: { - targetRatio: { - name: 'LOD 压缩比例', - title: '减面之后的目标面数比例,0 代表减面至最少,1 代表没有减面的原模型。', - }, - preserveSurfaceCurvature: { - name: '保留表面曲率', - title: 'Preserve Surface Curvature', - }, - preserveBorderEdges: { - name: '保留边界边', - title: 'Preserve Border Edges', - }, - preserveUVSeamEdges: { - name: '保留 UV 缝合边', - title: 'Preserve UV Seam Edges', - }, - preserveUVFoldoverEdges: { - name: '保留 UV 折叠边', - title: 'Preserve UV Foldover Edges', - }, - agressiveness: { - name: '误差距离', - title: '模型减面算法的激进程度。
当设置数值越高时,算法的减面策略会越激进,但是过于激进的策略更有可能导致结果错误。', - }, - maxIterationCount: { - name: '计算迭代次数', - title: '最大重复计数代表减面算法运行的重复次数。
高数值可以使算法运行结果更接近目标,但也会增加运行时间和结果错误的概率。', - }, + }, + meshCluster: { + name: '网格切块', + title: '是否分割网格数据。', + generateBounding: { + name: '生成包围体', + title: '是否为聚类的网格数据生成包围球和法线锥。', + }, + }, + meshCompress:{ + name: '网格压缩', + title: '是否压缩网格数据。', + encode: { + name: '编码', + title: '对网格数据进行编码以减少数据大小。', + }, + compress: { + name: '压缩', + title: '对网格数据进行压缩以减少数据大小。', }, - gltfpack: { - warn: '当前资源使用的减面算法 gltfpack 已被废弃,请选用新的 simplify 减面算法。', + quantize: { + name: '量化', + title: '对网格数据进行量化以减少数据大小。', }, - warn: '警告:优化后,网格资源的数量和名称会发生改变,这将会造成组件引用的资源丢失,请及时手动更新;(另外,对于模型资源中预生成的预制体,资源同步机制会自动更新)', }, animationBakeRate: { name: '动画烘焙速率', diff --git a/editor/i18n/zh/localization.js b/editor/i18n/zh/localization.js index b1047daed39..63ad5d1e62d 100755 --- a/editor/i18n/zh/localization.js +++ b/editor/i18n/zh/localization.js @@ -397,6 +397,13 @@ module.exports = link(mixin({ font_underline: '字体加下划线', spacing_x: '文本字符之间的间距。仅在使用 BMFont 位图字体时生效', underline_height: '下划线高度', + outline_enable: '是否启用描边', + outline_width: '描边宽度', + outline_color: '描边颜色', + shadow_enable: '是否启用阴影', + shadow_color: '阴影颜色', + shadow_offset: '阴影偏移量', + shadow_blur: '阴影模糊程度', }, labelOutline: { color: '描边的颜色', @@ -1008,6 +1015,10 @@ module.exports = link(mixin({ label: "基于 Box2D 的 2D 物理系统", description: "基于 Box2D 的 2D 物理系统支持。", }, + physics_2d_box2d_wasm: { + label: "基于 Box2D-wasm 的 2D 物理系统", + description: "基于 Box2D-wasm 的 2D 物理系统支持。", + }, intersection_2d: { label: "2D 相交检测算法", description: "包含用于二维相交检测的算法。", diff --git a/editor/inspector/components.js b/editor/inspector/components.js index 37fd3598924..1dd378709ac 100644 --- a/editor/inspector/components.js +++ b/editor/inspector/components.js @@ -15,8 +15,6 @@ module.exports = { 'cc.SkeletalAnimation': join(__dirname, './components/skeletal-animation.js'), 'cc.SphereLight': join(__dirname, './components/sphere-light.js'), 'cc.SpotLight': join(__dirname, './components/spot-light.js'), - 'cc.PointLight': join(__dirname, './components/point-light.js'), - 'cc.RangedDirectionalLight': join(__dirname, './components/ranged-directional-light.js'), 'cc.Sprite': join(__dirname, './components/sprite.js'), 'cc.Terrain': join(__dirname, './components/terrain.js'), 'cc.VideoPlayer': join(__dirname, './components/video-player.js'), diff --git a/editor/inspector/components/widget.js b/editor/inspector/components/widget.js index a4b52964acd..85e97d536a5 100644 --- a/editor/inspector/components/widget.js +++ b/editor/inspector/components/widget.js @@ -8,6 +8,7 @@ const Vue = require('vue/dist/vue.min.js'); const propUtils = require('../utils/prop'); const cssMediaWidth = 340; +let layout = 'vertical'; exports.template = `