diff options
55 files changed, 2589 insertions, 660 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7ffae10a2a..6803d9afd5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,72 +1,72 @@ name: Bug report description: Report a bug in Godot -body: -- type: markdown - attributes: - value: | - When reporting bugs, please follow the guidelines in this template. This helps identify the problem precisely and thus enables contributors to fix it faster. - - Write a descriptive issue title above. - - The golden rule is to **always open *one* issue for *one* bug**. If you notice several bugs and want to report them, make sure to create one new issue for each of them. - - Search [open](https://github.com/godotengine/godot/issues) and [closed](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported. If you don't find a relevant match or if you're unsure, don't hesitate to **open a new issue**. The bugsquad will handle it from there if it's a duplicate. - - Verify that you are using a [supported Godot version](https://docs.godotengine.org/en/latest/about/release_policy.html). Please always check if your issue is reproducible in the latest version – it may already have been fixed! - - If you use a custom build, please test if your issue is reproducible in official builds too. Likewise if you use any C++ modules, GDExtensions, or editor plugins, you should check if the bug is reproducible in a project without these. +body: + - type: markdown + attributes: + value: | + When reporting bugs, please follow the guidelines in this template. This helps identify the problem precisely and thus enables contributors to fix it faster. + - Write a descriptive issue title above. + - The golden rule is to **always open *one* issue for *one* bug**. If you notice several bugs and want to report them, make sure to create one new issue for each of them. + - Search [open](https://github.com/godotengine/godot/issues) and [closed](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported. If you don't find a relevant match or if you're unsure, don't hesitate to **open a new issue**. The bugsquad will handle it from there if it's a duplicate. + - Verify that you are using a [supported Godot version](https://docs.godotengine.org/en/latest/about/release_policy.html). Please always check if your issue is reproducible in the latest version – it may already have been fixed! + - If you use a custom build, please test if your issue is reproducible in official builds too. Likewise if you use any C++ modules, GDExtensions, or editor plugins, you should check if the bug is reproducible in a project without these. -- type: textarea - attributes: - label: Tested versions - description: | - To properly fix a bug, we need to identify if the bug was recently introduced in the engine, or if it was always present. - - Please specify the Godot version you found the issue in, including the **Git commit hash** if using a development or non-official build. The exact Godot version (including the commit hash) can be copied by clicking the version shown in the editor (bottom bar) or in the project manager (top bar). - - If you can, **please test earlier Godot versions** (previous stable branch, and development snapshots of the current feature release) and, if applicable, newer versions (development snapshots for the next feature release). Mention whether the bug is reproducible or not in the versions you tested. You can find all Godot releases in our [download archive](https://godotengine.org/download/archive/). - - The aim is for us to identify whether a bug is a **regression**, i.e. an issue that didn't exist in a previous version, but was introduced later on, breaking existing functionality. For example, if a bug is reproducible in 4.2.stable but not in 4.1.stable, we would like you to test intermediate 4.2 dev and beta snapshots to find which snapshot is the first one where the issue can be reproduced. - placeholder: | + - type: textarea + attributes: + label: Tested versions + description: | + To properly fix a bug, we need to identify if the bug was recently introduced in the engine, or if it was always present. + - Please specify the Godot version you found the issue in, including the **Git commit hash** if using a development or non-official build. The exact Godot version (including the commit hash) can be copied by clicking the version shown in the editor (bottom bar) or in the project manager (top bar). + - If you can, **please test earlier Godot versions** (previous stable branch, and development snapshots of the current feature release) and, if applicable, newer versions (development snapshots for the next feature release). Mention whether the bug is reproducible or not in the versions you tested. You can find all Godot releases in our [download archive](https://godotengine.org/download/archive/). + - The aim is for us to identify whether a bug is a **regression**, i.e. an issue that didn't exist in a previous version, but was introduced later on, breaking existing functionality. For example, if a bug is reproducible in 4.2.stable but not in 4.1.stable, we would like you to test intermediate 4.2 dev and beta snapshots to find which snapshot is the first one where the issue can be reproduced. + placeholder: | - - Reproducible in: 4.3.dev [d76c1d0e5], 4.2.stable, 4.2.dev5 and later 4.2 snapshots. - - Not reproducible in: 4.1.3.stable, 4.2.dev4 and earlier 4.2 snapshots. - validations: - required: true + - Reproducible in: 4.3.dev [d76c1d0e5], 4.2.stable, 4.2.dev5 and later 4.2 snapshots. + - Not reproducible in: 4.1.3.stable, 4.2.dev4 and earlier 4.2 snapshots. + validations: + required: true -- type: input - attributes: - label: System information - description: | - - Specify the OS version, and when relevant hardware information. - - For issues that are likely OS-specific and/or graphics-related, please specify the CPU model and architecture. - - For graphics-related issues, specify the GPU model, driver version, and the rendering backend (GLES2, GLES3, Vulkan). - - **Bug reports not including the required information may be closed at the maintainers' discretion.** If in doubt, always include all the requested information; it's better to include too much information than not enough information. - - **Starting from Godot 4.1, you can copy this information to your clipboard by using *Help > Copy System Info* at the top of the editor window.** - placeholder: Windows 10 - Godot v4.0.3.stable - Vulkan (Forward+) - dedicated NVIDIA GeForce GTX 970 (nvidia, 510.85.02) - Intel Core i7-10700KF CPU @ 3.80GHz (16 Threads) - validations: - required: true + - type: input + attributes: + label: System information + description: | + - Specify the OS version, and when relevant hardware information. + - For issues that are likely OS-specific and/or graphics-related, please specify the CPU model and architecture. + - For graphics-related issues, specify the GPU model, driver version, and the rendering backend (GLES2, GLES3, Vulkan). + - **Bug reports not including the required information may be closed at the maintainers' discretion.** If in doubt, always include all the requested information; it's better to include too much information than not enough information. + - **Starting from Godot 4.1, you can copy this information to your clipboard by using *Help > Copy System Info* at the top of the editor window.** + placeholder: Windows 10 - Godot v4.0.3.stable - Vulkan (Forward+) - dedicated NVIDIA GeForce GTX 970 (nvidia, 510.85.02) - Intel Core i7-10700KF CPU @ 3.80GHz (16 Threads) + validations: + required: true -- type: textarea - attributes: - label: Issue description - description: | - Describe your issue briefly. What doesn't work, and how do you expect it to work instead? - You can include images or videos with drag and drop, and format code blocks or logs with <code>\`\`\`</code> tags, on separate lines before and after the text. (Use <code>\`\`\`gdscript</code> to add GDScript syntax highlighting.) - Please do not add code examples or error messages as screenshots, but as text, this helps searching for issues and testing the code. If you are reporting a bug in the editor interface, like the script editor, please provide both a screenshot *and* the text of the code to help with testing. - validations: - required: true + - type: textarea + attributes: + label: Issue description + description: | + Describe your issue briefly. What doesn't work, and how do you expect it to work instead? + You can include images or videos with drag and drop, and format code blocks or logs with <code>\`\`\`</code> tags, on separate lines before and after the text. (Use <code>\`\`\`gdscript</code> to add GDScript syntax highlighting.) + Please do not add code examples or error messages as screenshots, but as text, this helps searching for issues and testing the code. If you are reporting a bug in the editor interface, like the script editor, please provide both a screenshot *and* the text of the code to help with testing. + validations: + required: true -- type: textarea - attributes: - label: Steps to reproduce - description: | - List of steps or sample code that reproduces the issue. Having reproducible issues is a prerequisite for contributors to be able to solve them. - If you include a minimal reproduction project below, you can detail how to use it here. - validations: - required: true + - type: textarea + attributes: + label: Steps to reproduce + description: | + List of steps or sample code that reproduces the issue. Having reproducible issues is a prerequisite for contributors to be able to solve them. + If you include a minimal reproduction project below, you can detail how to use it here. + validations: + required: true -- type: textarea - attributes: - label: Minimal reproduction project (MRP) - description: | - - A small Godot project which reproduces the issue, with no unnecessary files included. Be sure to not include the `.godot` folder in the archive (but keep `project.godot`). - - Having an MRP is very important for contributors to be able to reproduce the bug in the same way that you are experiencing it. When testing a potential fix for the issue, contributors will use the MRP to validate that the fix is working as intended. - - If the reproduction steps are not project dependent (e.g. the bug is visible in a brand new project), you can write "N/A" in the field. - - Drag and drop a ZIP archive to upload it (max 10 MB). **Do not select another field until the project is done uploading.** - - **Note for C# users:** If your issue is *not* C#-specific, please upload a minimal reproduction project written in GDScript. This will make it easier for contributors to reproduce the issue locally as not everyone has a .NET setup available. - validations: - required: true + - type: textarea + attributes: + label: Minimal reproduction project (MRP) + description: | + - A small Godot project which reproduces the issue, with no unnecessary files included. Be sure to not include the `.godot` folder in the archive (but keep `project.godot`). + - Having an MRP is very important for contributors to be able to reproduce the bug in the same way that you are experiencing it. When testing a potential fix for the issue, contributors will use the MRP to validate that the fix is working as intended. + - If the reproduction steps are not project dependent (e.g. the bug is visible in a brand new project), you can write "N/A" in the field. + - Drag and drop a ZIP archive to upload it (max 10 MB). **Do not select another field until the project is done uploading.** + - **Note for C# users:** If your issue is *not* C#-specific, please upload a minimal reproduction project written in GDScript. This will make it easier for contributors to reproduce the issue locally as not everyone has a .NET setup available. + validations: + required: true diff --git a/.github/actions/download-artifact/action.yml b/.github/actions/download-artifact/action.yml index 534b3251c5..c2a3d777c4 100644 --- a/.github/actions/download-artifact/action.yml +++ b/.github/actions/download-artifact/action.yml @@ -1,15 +1,17 @@ name: Download Godot artifact description: Download the Godot artifact. + inputs: name: description: The artifact name. - default: "${{ github.job }}" + default: ${{ github.job }} path: description: The path to download and extract to. required: true - default: "./" + default: ./ + runs: - using: "composite" + using: composite steps: - name: Download Godot Artifact uses: actions/download-artifact@v4 diff --git a/.github/actions/godot-api-dump/action.yml b/.github/actions/godot-api-dump/action.yml index 7730688661..dee70298c4 100644 --- a/.github/actions/godot-api-dump/action.yml +++ b/.github/actions/godot-api-dump/action.yml @@ -1,11 +1,13 @@ name: Dump Godot API description: Dump Godot API for GDExtension + inputs: bin: description: The path to the Godot executable required: true + runs: - using: "composite" + using: composite steps: # Dump GDExtension interface and API - name: Dump GDExtension interface and API for godot-cpp build @@ -19,5 +21,5 @@ runs: - name: Upload API dump uses: ./.github/actions/upload-artifact with: - name: 'godot-api-dump' - path: './godot-api/*' + name: godot-api-dump + path: ./godot-api/* diff --git a/.github/actions/godot-build/action.yml b/.github/actions/godot-build/action.yml index 61613d2628..93d6f076b7 100644 --- a/.github/actions/godot-build/action.yml +++ b/.github/actions/godot-build/action.yml @@ -1,9 +1,10 @@ name: Build Godot description: Build Godot with the provided options. + inputs: target: description: Build target (editor, template_release, template_debug). - default: "editor" + default: editor tests: description: Unit tests. default: false @@ -13,25 +14,26 @@ inputs: required: false sconsflags: description: Additional SCons flags. - default: "" + default: '' required: false scons-cache: description: The SCons cache path. - default: "${{ github.workspace }}/.scons-cache/" + default: ${{ github.workspace }}/.scons-cache/ scons-cache-limit: description: The SCons cache size limit. # actions/cache has 10 GiB limit, and GitHub runners have a 14 GiB disk. # Limit to 7 GiB to avoid having the extracted cache fill the disk. default: 7168 + runs: - using: "composite" + using: composite steps: - - name: Scons Build + - name: SCons Build shell: sh env: - SCONSFLAGS: ${{ inputs.sconsflags }} - SCONS_CACHE: ${{ inputs.scons-cache }} - SCONS_CACHE_LIMIT: ${{ inputs.scons-cache-limit }} + SCONSFLAGS: ${{ inputs.sconsflags }} + SCONS_CACHE: ${{ inputs.scons-cache }} + SCONS_CACHE_LIMIT: ${{ inputs.scons-cache-limit }} run: | echo "Building with flags:" platform=${{ inputs.platform }} target=${{ inputs.target }} tests=${{ inputs.tests }} ${{ env.SCONSFLAGS }} diff --git a/.github/actions/godot-cache-restore/action.yml b/.github/actions/godot-cache-restore/action.yml index eb955affef..7abec20a28 100644 --- a/.github/actions/godot-cache-restore/action.yml +++ b/.github/actions/godot-cache-restore/action.yml @@ -3,18 +3,19 @@ description: Restore Godot build cache. inputs: cache-name: description: The cache base name (job name by default). - default: "${{github.job}}" + default: ${{ github.job }} scons-cache: description: The SCons cache path. - default: "${{github.workspace}}/.scons-cache/" + default: ${{ github.workspace }}/.scons-cache/ + runs: - using: "composite" + using: composite steps: - name: Restore SCons cache directory uses: actions/cache/restore@v4 with: - path: ${{inputs.scons-cache}} - key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + path: ${{ inputs.scons-cache }} + key: ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }} # We try to match an existing cache to restore from it. Each potential key is checked against # all existing caches as a prefix. E.g. 'linux-template-minimal' would match any cache that @@ -28,7 +29,7 @@ runs: # 4. A partial match for the same base branch only (not ideal, matches any PR with the same base branch). restore-keys: | - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-refs/heads/${{env.GODOT_BASE_BRANCH}} - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}} + ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }} + ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }} + ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-refs/heads/${{ env.GODOT_BASE_BRANCH }} + ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }} diff --git a/.github/actions/godot-cache-save/action.yml b/.github/actions/godot-cache-save/action.yml index b7cbf91f94..df877cec67 100644 --- a/.github/actions/godot-cache-save/action.yml +++ b/.github/actions/godot-cache-save/action.yml @@ -3,15 +3,16 @@ description: Save Godot build cache. inputs: cache-name: description: The cache base name (job name by default). - default: "${{github.job}}" + default: ${{ github.job }} scons-cache: description: The SCons cache path. - default: "${{github.workspace}}/.scons-cache/" + default: ${{ github.workspace }}/.scons-cache/ + runs: - using: "composite" + using: composite steps: - name: Save SCons cache directory uses: actions/cache/save@v4 with: - path: ${{inputs.scons-cache}} - key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + path: ${{ inputs.scons-cache }} + key: ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }} diff --git a/.github/actions/godot-converter-test/action.yml b/.github/actions/godot-converter-test/action.yml index 919a76e693..ffd558d98b 100644 --- a/.github/actions/godot-converter-test/action.yml +++ b/.github/actions/godot-converter-test/action.yml @@ -1,11 +1,13 @@ name: Test Godot project converter description: Test the Godot project converter. + inputs: bin: description: The path to the Godot executable required: true + runs: - using: "composite" + using: composite steps: - name: Test 3-to-4 conversion shell: sh diff --git a/.github/actions/godot-deps/action.yml b/.github/actions/godot-deps/action.yml index 99404657c6..eb9bdef1e7 100644 --- a/.github/actions/godot-deps/action.yml +++ b/.github/actions/godot-deps/action.yml @@ -1,17 +1,19 @@ name: Setup Python and SCons description: Setup Python, install the pip version of SCons. + inputs: python-version: description: The Python version to use. - default: "3.x" + default: 3.x python-arch: description: The Python architecture. - default: "x64" + default: x64 scons-version: description: The SCons version to use. - default: "4.8.0" + default: 4.8.0 + runs: - using: "composite" + using: composite steps: - name: Set up Python 3.x uses: actions/setup-python@v5 diff --git a/.github/actions/godot-project-test/action.yml b/.github/actions/godot-project-test/action.yml index fd8c024a37..36448cd50a 100644 --- a/.github/actions/godot-project-test/action.yml +++ b/.github/actions/godot-project-test/action.yml @@ -1,11 +1,13 @@ name: Test Godot project description: Run the test Godot project. + inputs: bin: description: The path to the Godot executable required: true + runs: - using: "composite" + using: composite steps: # Download and extract zip archive with project, folder is renamed to be able to easy change used project - name: Download test project diff --git a/.github/actions/upload-artifact/action.yml b/.github/actions/upload-artifact/action.yml index 80c680103d..8524afdf59 100644 --- a/.github/actions/upload-artifact/action.yml +++ b/.github/actions/upload-artifact/action.yml @@ -1,15 +1,17 @@ name: Upload Godot artifact description: Upload the Godot artifact. + inputs: name: description: The artifact name. - default: "${{ github.job }}" + default: ${{ github.job }} path: description: The path to upload. required: true - default: "bin/*" + default: bin/* + runs: - using: "composite" + using: composite steps: - name: Upload Godot Artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml index 2f49f84e90..43e709e24a 100644 --- a/.github/workflows/android_builds.yml +++ b/.github/workflows/android_builds.yml @@ -9,12 +9,12 @@ env: SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no module_text_server_fb_enabled=yes strict_checks=yes concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-android + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-android cancel-in-progress: true jobs: build-android: - runs-on: "ubuntu-20.04" + runs-on: ubuntu-20.04 name: ${{ matrix.name }} strategy: fail-fast: false @@ -39,7 +39,8 @@ jobs: sconsflags: arch=arm64 steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive diff --git a/.github/workflows/godot_cpp_test.yml b/.github/workflows/godot_cpp_test.yml index e3223c799b..61bb0d1d8d 100644 --- a/.github/workflows/godot_cpp_test.yml +++ b/.github/workflows/godot_cpp_test.yml @@ -7,18 +7,19 @@ env: # Used for the cache key. Add version suffix to force clean build. GODOT_BASE_BRANCH: master # Used for the godot-cpp checkout. - GODOT_CPP_BRANCH: '4.3' + GODOT_CPP_BRANCH: 4.3 concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-cpp-tests + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-cpp-tests cancel-in-progress: true jobs: godot-cpp-tests: - runs-on: "ubuntu-20.04" - name: "Build and test Godot CPP" + runs-on: ubuntu-20.04 + name: Build and test Godot CPP steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive @@ -31,15 +32,15 @@ jobs: with: repository: godotengine/godot-cpp ref: ${{ env.GODOT_CPP_BRANCH }} - submodules: 'recursive' - path: 'godot-cpp' + submodules: recursive + path: godot-cpp # Download generated API dump - name: Download GDExtension interface and API dump uses: ./.github/actions/download-artifact with: - name: 'godot-api-dump' - path: './godot-api' + name: godot-api-dump + path: ./godot-api # Extract and override existing files with generated files - name: Extract GDExtension interface and API dump @@ -58,11 +59,11 @@ jobs: cd ../.. gdextension-c-compile: - runs-on: "ubuntu-20.04" - name: "Check GDExtension header with a C compiler" + runs-on: 'ubuntu-20.04' + name: 'Check GDExtension header with a C compiler' steps: - uses: actions/checkout@v4 - - name: "Run C compiler on gdextension_interface.h" + - name: 'Run C compiler on gdextension_interface.h' run: | gcc -c core/extension/gdextension_interface.h diff --git a/.github/workflows/ios_builds.yml b/.github/workflows/ios_builds.yml index 207219f9f4..8513429f9a 100644 --- a/.github/workflows/ios_builds.yml +++ b/.github/workflows/ios_builds.yml @@ -9,16 +9,17 @@ env: SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no module_text_server_fb_enabled=yes strict_checks=yes concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-ios + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-ios cancel-in-progress: true jobs: ios-template: - runs-on: "macos-latest" + runs-on: macos-latest name: Template (target=template_release) steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 010a13d921..dc3d9f3786 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -12,12 +12,12 @@ env: TSAN_OPTIONS: suppressions=misc/error_suppressions/tsan.txt concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-linux + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-linux cancel-in-progress: true jobs: build-linux: - runs-on: "ubuntu-20.04" + runs-on: ubuntu-20.04 name: ${{ matrix.name }} strategy: fail-fast: false @@ -27,7 +27,7 @@ jobs: cache-name: linux-editor-mono target: editor sconsflags: module_mono_enabled=yes - bin: "./bin/godot.linuxbsd.editor.x86_64.mono" + bin: ./bin/godot.linuxbsd.editor.x86_64.mono build-mono: true tests: false # Disabled due freeze caused by mix Mono build and CI doc-test: true @@ -40,7 +40,7 @@ jobs: target: editor # Debug symbols disabled as they're huge on this build and we hit the 14 GB limit for runners. sconsflags: dev_build=yes scu_build=yes debug_symbols=no precision=double use_asan=yes use_ubsan=yes linker=gold - bin: "./bin/godot.linuxbsd.editor.dev.double.x86_64.san" + bin: ./bin/godot.linuxbsd.editor.dev.double.x86_64.san build-mono: false tests: true proj-test: true @@ -53,7 +53,7 @@ jobs: cache-name: linux-editor-llvm-sanitizers target: editor sconsflags: dev_build=yes use_asan=yes use_ubsan=yes use_llvm=yes linker=lld - bin: "./bin/godot.linuxbsd.editor.dev.x86_64.llvm.san" + bin: ./bin/godot.linuxbsd.editor.dev.x86_64.llvm.san build-mono: false tests: true # Skip 2GiB artifact speeding up action. @@ -66,36 +66,37 @@ jobs: target: editor tests: true sconsflags: dev_build=yes use_tsan=yes use_llvm=yes linker=lld - bin: "./bin/godot.linuxbsd.editor.dev.x86_64.llvm.san" + bin: ./bin/godot.linuxbsd.editor.dev.x86_64.llvm.san build-mono: false # Skip 2GiB artifact speeding up action. artifact: false - - name: Template w/ Mono (target=template_release) + - name: Template w/ Mono (target=template_release, tests=yes) cache-name: linux-template-mono target: template_release - sconsflags: module_mono_enabled=yes tests=yes - bin: "./bin/godot.linuxbsd.template_release.x86_64.mono" + sconsflags: module_mono_enabled=yes + bin: ./bin/godot.linuxbsd.template_release.x86_64.mono build-mono: false tests: true artifact: true - - name: Minimal template (target=template_release, everything disabled) + - name: Minimal template (target=template_release, tests=yes, everything disabled) cache-name: linux-template-minimal target: template_release - sconsflags: modules_enabled_by_default=no disable_3d=yes disable_advanced_gui=yes deprecated=no minizip=no tests=yes - bin: "./bin/godot.linuxbsd.template_release.x86_64" + sconsflags: modules_enabled_by_default=no disable_3d=yes disable_advanced_gui=yes deprecated=no minizip=no + bin: ./bin/godot.linuxbsd.template_release.x86_64 tests: true artifact: true steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive # Need newer mesa for lavapipe to work properly. - name: Linux dependencies for tests - if: ${{ matrix.proj-test }} + if: matrix.proj-test run: | sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys EB8B81E14DA65431D7504EA8F63F0F2B90935439 @@ -120,11 +121,11 @@ jobs: continue-on-error: true - name: Setup Python and SCons - if: ${{ ! matrix.legacy-scons }} + if: '!matrix.legacy-scons' uses: ./.github/actions/godot-deps - name: Setup Python and SCons (legacy versions) - if: ${{ matrix.legacy-scons }} + if: matrix.legacy-scons uses: ./.github/actions/godot-deps with: # Sync with Ensure*Version in SConstruct. @@ -149,48 +150,48 @@ jobs: continue-on-error: true - name: Generate C# glue - if: ${{ matrix.build-mono }} + if: matrix.build-mono run: | ${{ matrix.bin }} --headless --generate-mono-glue ./modules/mono/glue - name: Build .NET solutions - if: ${{ matrix.build-mono }} + if: matrix.build-mono run: | ./modules/mono/build_scripts/build_assemblies.py --godot-output-dir=./bin --godot-platform=linuxbsd - name: Prepare artifact - if: ${{ matrix.artifact }} + if: matrix.artifact run: | strip bin/godot.* chmod +x bin/godot.* - name: Upload artifact uses: ./.github/actions/upload-artifact - if: ${{ matrix.artifact }} + if: matrix.artifact with: name: ${{ matrix.cache-name }} - name: Dump Godot API uses: ./.github/actions/godot-api-dump - if: ${{ matrix.api-dump }} + if: matrix.api-dump with: bin: ${{ matrix.bin }} - name: Unit tests - if: ${{ matrix.tests }} + if: matrix.tests run: | ${{ matrix.bin }} --version ${{ matrix.bin }} --help ${{ matrix.bin }} --headless --test --force-colors - name: .NET source generators tests - if: ${{ matrix.build-mono }} + if: matrix.build-mono run: | dotnet test modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests # Check class reference - name: Check for class reference updates - if: ${{ matrix.doc-test }} + if: matrix.doc-test run: | echo "Running --doctool to see if this changes the public API without updating the documentation." echo -e "If a diff is shown, it means that your code/doc changes are incomplete and you should update the class reference with --doctool.\n\n" @@ -199,20 +200,20 @@ jobs: # Check API backwards compatibility - name: Check for GDExtension compatibility - if: ${{ matrix.api-compat }} + if: matrix.api-compat run: | ./misc/scripts/validate_extension_api.sh "${{ matrix.bin }}" # Download and run the test project - name: Test Godot project uses: ./.github/actions/godot-project-test - if: ${{ matrix.proj-test }} + if: matrix.proj-test with: bin: ${{ matrix.bin }} # Test the project converter - name: Test project converter uses: ./.github/actions/godot-converter-test - if: ${{ matrix.proj-conv }} + if: matrix.proj-conv with: bin: ${{ matrix.bin }} diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml index 938b25f134..fcf4b00afb 100644 --- a/.github/workflows/macos_builds.yml +++ b/.github/workflows/macos_builds.yml @@ -9,12 +9,12 @@ env: SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes strict_checks=yes concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-macos + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-macos cancel-in-progress: true jobs: build-macos: - runs-on: "macos-latest" + runs-on: macos-latest name: ${{ matrix.name }} strategy: fail-fast: false @@ -24,17 +24,18 @@ jobs: cache-name: macos-editor target: editor tests: true - bin: "./bin/godot.macos.editor.universal" + bin: ./bin/godot.macos.editor.universal - - name: Template (target=template_release) + - name: Template (target=template_release, tests=yes) cache-name: macos-template target: template_release tests: true - sconsflags: debug_symbols=no tests=yes - bin: "./bin/godot.macos.template_release.universal" + sconsflags: debug_symbols=no + bin: ./bin/godot.macos.template_release.universal steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive @@ -86,7 +87,7 @@ jobs: name: ${{ matrix.cache-name }} - name: Unit tests - if: ${{ matrix.tests }} + if: matrix.tests run: | ${{ matrix.bin }} --version ${{ matrix.bin }} --help diff --git a/.github/workflows/runner.yml b/.github/workflows/runner.yml index 34b6af4307..d2d0e3571f 100644 --- a/.github/workflows/runner.yml +++ b/.github/workflows/runner.yml @@ -2,51 +2,45 @@ name: 🔗 GHA on: [push, pull_request] concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-runner + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-runner cancel-in-progress: true jobs: # First stage: Only static checks, fast and prevent expensive builds from running. static-checks: - if: ${{ vars.DISABLE_GODOT_CI == '' }} + if: '!vars.DISABLE_GODOT_CI' name: 📊 Static checks uses: ./.github/workflows/static_checks.yml # Second stage: Run all the builds and some of the tests. android-build: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🤖 Android needs: static-checks uses: ./.github/workflows/android_builds.yml ios-build: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🍏 iOS needs: static-checks uses: ./.github/workflows/ios_builds.yml linux-build: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🐧 Linux needs: static-checks uses: ./.github/workflows/linux_builds.yml macos-build: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🍎 macOS needs: static-checks uses: ./.github/workflows/macos_builds.yml windows-build: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🏁 Windows needs: static-checks uses: ./.github/workflows/windows_builds.yml web-build: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🌐 Web needs: static-checks uses: ./.github/workflows/web_builds.yml @@ -56,7 +50,6 @@ jobs: # Can be turned off for PRs that intentionally break compat with godot-cpp, # until both the upstream PR and the matching godot-cpp changes are merged. godot-cpp-test: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🪲 Godot CPP # This can be changed to depend on another platform, if we decide to use it for # godot-cpp instead. Make sure to move the .github/actions/godot-api-dump step diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 9a8a4a8f19..233ab569a2 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -3,7 +3,7 @@ on: workflow_call: concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-static + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-static cancel-in-progress: true jobs: @@ -48,7 +48,7 @@ jobs: - name: Style checks via pre-commit uses: pre-commit/action@v3.0.1 with: - extra_args: --verbose --files ${{ env.CHANGED_FILES }} + extra_args: --files ${{ env.CHANGED_FILES }} - name: Python builders checks via pytest run: | diff --git a/.github/workflows/web_builds.yml b/.github/workflows/web_builds.yml index 11ef7f01c9..8e30c99fbc 100644 --- a/.github/workflows/web_builds.yml +++ b/.github/workflows/web_builds.yml @@ -8,15 +8,15 @@ env: GODOT_BASE_BRANCH: master SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no use_closure_compiler=yes strict_checks=yes EM_VERSION: 3.1.64 - EM_CACHE_FOLDER: "emsdk-cache" + EM_CACHE_FOLDER: emsdk-cache concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-web + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-web cancel-in-progress: true jobs: web-template: - runs-on: "ubuntu-22.04" + runs-on: ubuntu-22.04 name: ${{ matrix.name }} strategy: fail-fast: false @@ -37,16 +37,17 @@ jobs: artifact: true steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive - name: Set up Emscripten latest uses: mymindstorm/setup-emsdk@v14 with: - version: ${{env.EM_VERSION}} - actions-cache-folder: ${{env.EM_CACHE_FOLDER}} - cache-key: emsdk-${{ matrix.cache-name }}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + version: ${{ env.EM_VERSION }} + actions-cache-folder: ${{ env.EM_CACHE_FOLDER }} + cache-key: emsdk-${{ matrix.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }} - name: Verify Emscripten setup run: | @@ -77,6 +78,6 @@ jobs: - name: Upload artifact uses: ./.github/actions/upload-artifact - if: ${{ matrix.artifact }} + if: matrix.artifact with: name: ${{ matrix.cache-name }} diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index 0d2e258308..95e3d4a553 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -7,17 +7,17 @@ on: env: # Used for the cache key. Add version suffix to force clean build. GODOT_BASE_BRANCH: master - SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes d3d12=yes strict_checks=yes "angle_libs=${{github.workspace}}/" + SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes d3d12=yes strict_checks=yes "angle_libs=${{ github.workspace }}/" SCONS_CACHE_MSVC_CONFIG: true concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-windows + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-windows cancel-in-progress: true jobs: build-windows: # Windows 10 with latest image - runs-on: "windows-latest" + runs-on: windows-latest name: ${{ matrix.name }} strategy: fail-fast: false @@ -29,7 +29,7 @@ jobs: tests: true # Skip debug symbols, they're way too big with MSVC. sconsflags: debug_symbols=no vsproj=yes vsproj_gen_only=no windows_subsystem=console - bin: "./bin/godot.windows.editor.x86_64.exe" + bin: ./bin/godot.windows.editor.x86_64.exe artifact: true - name: Editor w/ clang-cl (target=editor, tests=yes, use_llvm=yes) @@ -39,16 +39,17 @@ jobs: sconsflags: debug_symbols=no windows_subsystem=console use_llvm=yes bin: ./bin/godot.windows.editor.x86_64.llvm.exe - - name: Template (target=template_release) + - name: Template (target=template_release, tests=yes) cache-name: windows-template target: template_release tests: true - sconsflags: debug_symbols=no tests=yes - bin: "./bin/godot.windows.template_release.x86_64.console.exe" + sconsflags: debug_symbols=no + bin: ./bin/godot.windows.template_release.x86_64.console.exe artifact: true steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive @@ -73,7 +74,7 @@ jobs: target: angle/angle.zip - name: Extract pre-built ANGLE static libraries - run: Expand-Archive -Force angle/angle.zip ${{github.workspace}}/ + run: Expand-Archive -Force angle/angle.zip ${{ github.workspace }}/ - name: Setup MSVC problem matcher uses: ammaraskar/msvc-problem-matcher@master @@ -104,7 +105,7 @@ jobs: name: ${{ matrix.cache-name }} - name: Unit tests - if: ${{ matrix.tests }} + if: matrix.tests run: | ${{ matrix.bin }} --version ${{ matrix.bin }} --help diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6cc6a211f1..70cbe47665 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -96,16 +96,21 @@ repos: language: node entry: eslint files: ^(platform/web/js/|modules/|misc/dist/html/).*\.(js|html)$ - args: [--fix, --no-warn-ignored, --no-config-lookup, --config, platform/web/eslint.config.cjs] + args: + - --fix, + - --no-warn-ignored, + - --no-config-lookup, + - --config, + - platform/web/eslint.config.cjs, additional_dependencies: - '@eslint/js@^9.3.0' - '@html-eslint/eslint-plugin@^0.24.1' - '@html-eslint/parser@^0.24.1' - '@stylistic/eslint-plugin@^2.1.0' - - 'eslint@^9.3.0' - - 'eslint-plugin-html@^8.1.1' - - 'globals@^15.3.0' - - 'espree@^10.0.1' + - eslint@^9.3.0 + - eslint-plugin-html@^8.1.1 + - globals@^15.3.0 + - espree@^10.0.1 - id: jsdoc name: jsdoc @@ -123,7 +128,7 @@ repos: - -d - dry-run pass_filenames: false - additional_dependencies: ['jsdoc@^4.0.3'] + additional_dependencies: [jsdoc@^4.0.3] - id: svgo name: svgo @@ -131,7 +136,7 @@ repos: entry: svgo files: \.svg$ args: [--quiet, --config, misc/utility/svgo.config.mjs] - additional_dependencies: ["svgo@3.3.2"] + additional_dependencies: [svgo@3.3.2] - id: copyright-headers name: copyright-headers @@ -179,7 +184,7 @@ repos: language: python entry: python misc/scripts/dotnet_format.py types_or: [c#] - +# # End of upstream Godot pre-commit hooks. # # Keep this separation to let downstream forks add their own hooks to this file, diff --git a/core/io/image.cpp b/core/io/image.cpp index fcbe483e38..bc018bd45c 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -2751,6 +2751,19 @@ Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels } break; + case COMPRESS_S3TC: { + // BC3 is unsupported currently. + if ((p_channels == USED_CHANNELS_RGB || p_channels == USED_CHANNELS_L) && _image_compress_bc_rd_func) { + Error result = _image_compress_bc_rd_func(this, p_channels); + + // If the image was compressed successfully, we return here. If not, we fall back to the default compression scheme. + if (result == OK) { + return OK; + } + } + + } break; + default: { } } @@ -3138,6 +3151,7 @@ void (*Image::_image_compress_etc1_func)(Image *) = nullptr; void (*Image::_image_compress_etc2_func)(Image *, Image::UsedChannels) = nullptr; void (*Image::_image_compress_astc_func)(Image *, Image::ASTCFormat) = nullptr; Error (*Image::_image_compress_bptc_rd_func)(Image *, Image::UsedChannels) = nullptr; +Error (*Image::_image_compress_bc_rd_func)(Image *, Image::UsedChannels) = nullptr; void (*Image::_image_decompress_bc)(Image *) = nullptr; void (*Image::_image_decompress_bptc)(Image *) = nullptr; void (*Image::_image_decompress_etc1)(Image *) = nullptr; diff --git a/core/io/image.h b/core/io/image.h index 4461ae71a6..78757246e0 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -160,6 +160,7 @@ public: static void (*_image_compress_astc_func)(Image *, ASTCFormat p_format); static Error (*_image_compress_bptc_rd_func)(Image *, UsedChannels p_channels); + static Error (*_image_compress_bc_rd_func)(Image *, UsedChannels p_channels); static void (*_image_decompress_bc)(Image *); static void (*_image_decompress_bptc)(Image *); diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index c53fd3d330..c85201a973 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -319,11 +319,11 @@ Vector3 AStar3D::get_closest_position_in_segment(const Vector3 &p_point) const { return closest_point; } -bool AStar3D::_solve(Point *begin_point, Point *end_point) { +bool AStar3D::_solve(Point *begin_point, Point *end_point, bool p_allow_partial_path) { last_closest_point = nullptr; pass++; - if (!end_point->enabled) { + if (!end_point->enabled && !p_allow_partial_path) { return false; } @@ -443,7 +443,7 @@ Vector<Vector3> AStar3D::get_point_path(int64_t p_from_id, int64_t p_to_id, bool Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return Vector<Vector3>(); @@ -497,7 +497,7 @@ Vector<int64_t> AStar3D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_ Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return Vector<int64_t>(); @@ -726,7 +726,7 @@ Vector<Vector2> AStar2D::get_point_path(int64_t p_from_id, int64_t p_to_id, bool AStar3D::Point *begin_point = a; AStar3D::Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || astar.last_closest_point == nullptr) { return Vector<Vector2>(); @@ -780,7 +780,7 @@ Vector<int64_t> AStar2D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_ AStar3D::Point *begin_point = a; AStar3D::Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || astar.last_closest_point == nullptr) { return Vector<int64_t>(); @@ -816,11 +816,11 @@ Vector<int64_t> AStar2D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_ return path; } -bool AStar2D::_solve(AStar3D::Point *begin_point, AStar3D::Point *end_point) { +bool AStar2D::_solve(AStar3D::Point *begin_point, AStar3D::Point *end_point, bool p_allow_partial_path) { astar.last_closest_point = nullptr; astar.pass++; - if (!end_point->enabled) { + if (!end_point->enabled && !p_allow_partial_path) { return false; } diff --git a/core/math/a_star.h b/core/math/a_star.h index 143a3bec61..cbaafc1018 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -115,7 +115,7 @@ class AStar3D : public RefCounted { HashSet<Segment, Segment> segments; Point *last_closest_point = nullptr; - bool _solve(Point *begin_point, Point *end_point); + bool _solve(Point *begin_point, Point *end_point, bool p_allow_partial_path); protected: static void _bind_methods(); @@ -171,7 +171,7 @@ class AStar2D : public RefCounted { GDCLASS(AStar2D, RefCounted); AStar3D astar; - bool _solve(AStar3D::Point *begin_point, AStar3D::Point *end_point); + bool _solve(AStar3D::Point *begin_point, AStar3D::Point *end_point, bool p_allow_partial_path); protected: static void _bind_methods(); diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp index c40ee5b4d7..7e0e982c62 100644 --- a/core/math/a_star_grid_2d.cpp +++ b/core/math/a_star_grid_2d.cpp @@ -491,11 +491,11 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) { } } -bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) { +bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point, bool p_allow_partial_path) { last_closest_point = nullptr; pass++; - if (_get_solid_unchecked(p_end_point->id)) { + if (_get_solid_unchecked(p_end_point->id) && !p_allow_partial_path) { return false; } @@ -647,7 +647,7 @@ Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vec Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return Vector<Vector2>(); @@ -700,7 +700,7 @@ TypedArray<Vector2i> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const V Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return TypedArray<Vector2i>(); diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h index f5ac472f09..0536b8109b 100644 --- a/core/math/a_star_grid_2d.h +++ b/core/math/a_star_grid_2d.h @@ -159,8 +159,8 @@ private: // Internal routines. void _get_nbors(Point *p_point, LocalVector<Point *> &r_nbors); Point *_jump(Point *p_from, Point *p_to); + bool _solve(Point *p_begin_point, Point *p_end_point, bool p_allow_partial_path); Point *_forced_successor(int32_t p_x, int32_t p_y, int32_t p_dx, int32_t p_dy, bool p_inclusive = false); - bool _solve(Point *p_begin_point, Point *p_end_point); protected: static void _bind_methods(); diff --git a/doc/classes/AStar2D.xml b/doc/classes/AStar2D.xml index f3a1f6b985..a41da4c318 100644 --- a/doc/classes/AStar2D.xml +++ b/doc/classes/AStar2D.xml @@ -143,6 +143,7 @@ <description> Returns an array with the IDs of the points that form the path found by AStar2D between the given points. The array is ordered from the starting point to the ending point of the path. If there is no valid path to the target, and [param allow_partial_path] is [code]true[/code], returns a path to the point closest to the target that can be reached. + [b]Note:[/b] When [param allow_partial_path] is [code]true[/code] and [param to_id] is disabled the search may take an unusually long time to finish. [codeblocks] [gdscript] var astar = AStar2D.new() @@ -235,6 +236,7 @@ Returns an array with the points that are in the path found by AStar2D between the given points. The array is ordered from the starting point to the ending point of the path. If there is no valid path to the target, and [param allow_partial_path] is [code]true[/code], returns a path to the point closest to the target that can be reached. [b]Note:[/b] This method is not thread-safe. If called from a [Thread], it will return an empty array and will print an error message. + Additionally, when [param allow_partial_path] is [code]true[/code] and [param to_id] is disabled the search may take an unusually long time to finish. </description> </method> <method name="get_point_position" qualifiers="const"> diff --git a/doc/classes/AStar3D.xml b/doc/classes/AStar3D.xml index dad77cc7a8..2e8ae37a20 100644 --- a/doc/classes/AStar3D.xml +++ b/doc/classes/AStar3D.xml @@ -172,6 +172,7 @@ <description> Returns an array with the IDs of the points that form the path found by AStar3D between the given points. The array is ordered from the starting point to the ending point of the path. If there is no valid path to the target, and [param allow_partial_path] is [code]true[/code], returns a path to the point closest to the target that can be reached. + [b]Note:[/b] When [param allow_partial_path] is [code]true[/code] and [param to_id] is disabled the search may take an unusually long time to finish. [codeblocks] [gdscript] var astar = AStar3D.new() @@ -262,6 +263,7 @@ Returns an array with the points that are in the path found by AStar3D between the given points. The array is ordered from the starting point to the ending point of the path. If there is no valid path to the target, and [param allow_partial_path] is [code]true[/code], returns a path to the point closest to the target that can be reached. [b]Note:[/b] This method is not thread-safe. If called from a [Thread], it will return an empty array and will print an error message. + Additionally, when [param allow_partial_path] is [code]true[/code] and [param to_id] is disabled the search may take an unusually long time to finish. </description> </method> <method name="get_point_position" qualifiers="const"> diff --git a/doc/classes/AStarGrid2D.xml b/doc/classes/AStarGrid2D.xml index 2ee61bd939..8e1972af11 100644 --- a/doc/classes/AStarGrid2D.xml +++ b/doc/classes/AStarGrid2D.xml @@ -79,6 +79,7 @@ <description> Returns an array with the IDs of the points that form the path found by AStar2D between the given points. The array is ordered from the starting point to the ending point of the path. If there is no valid path to the target, and [param allow_partial_path] is [code]true[/code], returns a path to the point closest to the target that can be reached. + [b]Note:[/b] When [param allow_partial_path] is [code]true[/code] and [param to_id] is solid the search may take an unusually long time to finish. </description> </method> <method name="get_point_data_in_region" qualifiers="const"> @@ -97,6 +98,7 @@ Returns an array with the points that are in the path found by [AStarGrid2D] between the given points. The array is ordered from the starting point to the ending point of the path. If there is no valid path to the target, and [param allow_partial_path] is [code]true[/code], returns a path to the point closest to the target that can be reached. [b]Note:[/b] This method is not thread-safe. If called from a [Thread], it will return an empty array and will print an error message. + Additionally, when [param allow_partial_path] is [code]true[/code] and [param to_id] is solid the search may take an unusually long time to finish. </description> </method> <method name="get_point_position" qualifiers="const"> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b205b862a3..d35d30efd8 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2894,10 +2894,13 @@ <member name="rendering/textures/lossless_compression/force_png" type="bool" setter="" getter="" default="false"> If [code]true[/code], the texture importer will import lossless textures using the PNG format. Otherwise, it will default to using WebP. </member> + <member name="rendering/textures/vram_compression/cache_gpu_compressor" type="bool" setter="" getter="" default="true"> + If [code]true[/code], the GPU texture compressor will cache the local RenderingDevice and its resources (shaders and pipelines), allowing for faster subsequent imports at a memory cost. + </member> <member name="rendering/textures/vram_compression/compress_with_gpu" type="bool" setter="" getter="" default="true"> - If [code]true[/code], the texture importer will utilize the GPU for compressing textures, which makes large textures import significantly faster. + If [code]true[/code], the texture importer will utilize the GPU for compressing textures, improving the import time of large images. [b]Note:[/b] This setting requires either Vulkan or D3D12 available as a rendering backend. - [b]Note:[/b] Currently this only affects BC6H compression, which is used on Desktop and Console for HDR images. + [b]Note:[/b] Currently this only affects BC1 and BC6H compression, which are used on Desktop and Console for fully opaque and HDR images respectively. </member> <member name="rendering/textures/vram_compression/import_etc2_astc" type="bool" setter="" getter="" default="false"> If [code]true[/code], the texture importer will import VRAM-compressed textures using the Ericsson Texture Compression 2 algorithm for lower quality textures and normal maps and Adaptable Scalable Texture Compression algorithm for high quality textures (in 4×4 block size). diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 350fd65197..85663e10b2 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -151,7 +151,7 @@ <method name="gui_is_dragging" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the viewport is currently performing a drag operation. + Returns [code]true[/code] if a drag operation is currently ongoing and where the drop action could happen in this viewport. Alternative to [constant Node.NOTIFICATION_DRAG_BEGIN] and [constant Node.NOTIFICATION_DRAG_END] when you prefer polling the value. </description> </method> diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 8b6d3e3268..39aad6bfbf 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -1363,38 +1363,25 @@ void RasterizerSceneGLES3::_fill_render_list(RenderListType p_render_list, const GeometryInstanceSurface *surf = inst->surface_caches; + float lod_distance = 0.0; + + if (p_render_data->cam_orthogonal) { + lod_distance = 1.0; + } else { + Vector3 aabb_min = inst->transformed_aabb.position; + Vector3 aabb_max = inst->transformed_aabb.position + inst->transformed_aabb.size; + Vector3 camera_position = p_render_data->main_cam_transform.origin; + Vector3 surface_distance = Vector3(0.0, 0.0, 0.0).max(aabb_min - camera_position).max(camera_position - aabb_max); + + lod_distance = surface_distance.length(); + } + while (surf) { // LOD if (p_render_data->screen_mesh_lod_threshold > 0.0 && mesh_storage->mesh_surface_has_lod(surf->surface)) { - float distance = 0.0; - - // Check if camera is NOT inside the mesh AABB. - if (!inst->transformed_aabb.has_point(p_render_data->main_cam_transform.origin)) { - // Get the LOD support points on the mesh AABB. - Vector3 lod_support_min = inst->transformed_aabb.get_support(p_render_data->main_cam_transform.basis.get_column(Vector3::AXIS_Z)); - Vector3 lod_support_max = inst->transformed_aabb.get_support(-p_render_data->main_cam_transform.basis.get_column(Vector3::AXIS_Z)); - - // Get the distances to those points on the AABB from the camera origin. - float distance_min = (float)p_render_data->main_cam_transform.origin.distance_to(lod_support_min); - float distance_max = (float)p_render_data->main_cam_transform.origin.distance_to(lod_support_max); - - if (distance_min * distance_max < 0.0) { - //crossing plane - distance = 0.0; - } else if (distance_min >= 0.0) { - distance = distance_min; - } else if (distance_max <= 0.0) { - distance = -distance_max; - } - } - - if (p_render_data->cam_orthogonal) { - distance = 1.0; - } - uint32_t indices = 0; - surf->lod_index = mesh_storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, distance * p_render_data->lod_distance_multiplier, p_render_data->screen_mesh_lod_threshold, indices); + surf->lod_index = mesh_storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, lod_distance * p_render_data->lod_distance_multiplier, p_render_data->screen_mesh_lod_threshold, indices); surf->index_count = indices; if (p_render_data->render_info) { diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 85ceaa662e..87053acfb6 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -3067,6 +3067,35 @@ void EditorFileSystem::move_group_file(const String &p_path, const String &p_new } } +void EditorFileSystem::add_new_directory(const String &p_path) { + String path = p_path.get_base_dir(); + EditorFileSystemDirectory *parent = filesystem; + int base = p_path.count("/"); + int max_bit = base + 1; + + while (path != "res://") { + EditorFileSystemDirectory *dir = get_filesystem_path(path); + if (dir) { + parent = dir; + break; + } + path = path.get_base_dir(); + base--; + } + + for (int i = base; i < max_bit; i++) { + EditorFileSystemDirectory *efd = memnew(EditorFileSystemDirectory); + efd->parent = parent; + efd->name = p_path.get_slice("/", i); + parent->subdirs.push_back(efd); + + if (i == base) { + parent->subdirs.sort_custom<DirectoryComparator>(); + } + parent = efd; + } +} + ResourceUID::ID EditorFileSystem::_resource_saver_get_resource_id_for_path(const String &p_path, bool p_generate) { if (!p_path.is_resource_file() || p_path.begins_with(ProjectSettings::get_singleton()->get_project_data_path())) { // Saved externally (configuration file) or internal file, do not assign an ID. diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 9adab1ed24..be299800d8 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -224,6 +224,12 @@ class EditorFileSystem : public Node { void increment(); }; + struct DirectoryComparator { + bool operator()(const EditorFileSystemDirectory *p_a, const EditorFileSystemDirectory *p_b) const { + return p_a->name.filenocasecmp_to(p_b->name) < 0; + } + }; + void _save_filesystem_cache(); void _save_filesystem_cache(EditorFileSystemDirectory *p_dir, Ref<FileAccess> p_file); @@ -364,6 +370,8 @@ public: bool is_group_file(const String &p_path) const; void move_group_file(const String &p_path, const String &p_new_path); + void add_new_directory(const String &p_path); + static bool _should_skip_directory(const String &p_path); void add_import_format_support_query(Ref<EditorFileSystemImportFormatSupportQuery> p_query); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 25725635e3..faaab4aeec 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1318,6 +1318,15 @@ void FileSystemDock::_fs_changed() { set_process(false); } +void FileSystemDock::_directory_created(const String &p_path) { + if (!DirAccess::exists(p_path)) { + return; + } + EditorFileSystem::get_singleton()->add_new_directory(p_path); + _update_tree(get_uncollapsed_paths()); + _update_file_list(true); +} + void FileSystemDock::_set_scanning_mode() { button_hist_prev->set_disabled(true); button_hist_next->set_disabled(true); @@ -4175,7 +4184,7 @@ FileSystemDock::FileSystemDock() { make_dir_dialog = memnew(DirectoryCreateDialog); add_child(make_dir_dialog); - make_dir_dialog->connect("dir_created", callable_mp(this, &FileSystemDock::_rescan).unbind(1)); + make_dir_dialog->connect("dir_created", callable_mp(this, &FileSystemDock::_directory_created)); make_scene_dialog = memnew(SceneCreateDialog); add_child(make_scene_dialog); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 2f54cb91db..907f843523 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -260,6 +260,7 @@ private: void _toggle_file_display(); void _set_file_display(bool p_active); void _fs_changed(); + void _directory_created(const String &p_path); void _select_file(const String &p_path, bool p_select_in_favorites = false); void _tree_activate_file(); diff --git a/modules/betsy/SCsub b/modules/betsy/SCsub index 9930e1f4cf..ed5dcbf58b 100644 --- a/modules/betsy/SCsub +++ b/modules/betsy/SCsub @@ -4,6 +4,7 @@ Import("env_modules") env_betsy = env_modules.Clone() env_betsy.GLSL_HEADER("bc6h.glsl") +env_betsy.GLSL_HEADER("bc1.glsl") env_betsy.Depends(Glob("*.glsl.gen.h"), ["#glsl_builders.py"]) # Thirdparty source files diff --git a/modules/betsy/bc1.glsl b/modules/betsy/bc1.glsl new file mode 100644 index 0000000000..f1b2c28254 --- /dev/null +++ b/modules/betsy/bc1.glsl @@ -0,0 +1,483 @@ +#[versions] + +standard = ""; +dithered = "#define BC1_DITHER"; + +#[compute] +#version 450 + +#include "CrossPlatformSettings_piece_all.glsl" +#include "UavCrossPlatform_piece_all.glsl" + +#define FLT_MAX 340282346638528859811704183484516925440.0f + +layout(binding = 0) uniform sampler2D srcTex; +layout(binding = 1, rg32ui) uniform restrict writeonly uimage2D dstTexture; + +layout(std430, binding = 2) readonly restrict buffer globalBuffer { + float2 c_oMatch5[256]; + float2 c_oMatch6[256]; +}; + +layout(push_constant, std430) uniform Params { + uint p_numRefinements; + uint p_padding[3]; +} +params; + +layout(local_size_x = 8, // + local_size_y = 8, // + local_size_z = 1) in; + +float3 rgb565to888(float rgb565) { + float3 retVal; + retVal.x = floor(rgb565 / 2048.0f); + retVal.y = floor(mod(rgb565, 2048.0f) / 32.0f); + retVal.z = floor(mod(rgb565, 32.0f)); + + // This is the correct 565 to 888 conversion: + // rgb = floor( rgb * ( 255.0f / float3( 31.0f, 63.0f, 31.0f ) ) + 0.5f ) + // + // However stb_dxt follows a different one: + // rb = floor( rb * ( 256 / 32 + 8 / 32 ) ); + // g = floor( g * ( 256 / 64 + 4 / 64 ) ); + // + // I'm not sure exactly why but it's possible this is how the S3TC specifies it should be decoded + // It's quite possible this is the reason: + // http://www.ludicon.com/castano/blog/2009/03/gpu-dxt-decompression/ + // + // Or maybe it's just because it's cheap to do with integer shifts. + // Anyway, we follow stb_dxt's conversion just in case + // (gives almost the same result, with 1 or -1 of difference for a very few values) + // + // Perhaps when we make 888 -> 565 -> 888 it doesn't matter + // because they end up mapping to the original number + + return floor(retVal * float3(8.25f, 4.0625f, 8.25f)); +} + +float rgb888to565(float3 rgbValue) { + rgbValue.rb = floor(rgbValue.rb * 31.0f / 255.0f + 0.5f); + rgbValue.g = floor(rgbValue.g * 63.0f / 255.0f + 0.5f); + + return rgbValue.r * 2048.0f + rgbValue.g * 32.0f + rgbValue.b; +} + +// linear interpolation at 1/3 point between a and b, using desired rounding type +float3 lerp13(float3 a, float3 b) { +#ifdef STB_DXT_USE_ROUNDING_BIAS + // with rounding bias + return a + floor((b - a) * (1.0f / 3.0f) + 0.5f); +#else + // without rounding bias + return floor((2.0f * a + b) / 3.0f); +#endif +} + +/// Unpacks a block of 4 colors from two 16-bit endpoints +void EvalColors(out float3 colors[4], float c0, float c1) { + colors[0] = rgb565to888(c0); + colors[1] = rgb565to888(c1); + colors[2] = lerp13(colors[0], colors[1]); + colors[3] = lerp13(colors[1], colors[0]); +} + +/** The color optimization function. (Clever code, part 1) +@param outMinEndp16 [out] + Minimum endpoint, in RGB565 +@param outMaxEndp16 [out] + Maximum endpoint, in RGB565 +*/ +void OptimizeColorsBlock(const uint srcPixelsBlock[16], out float outMinEndp16, out float outMaxEndp16) { + // determine color distribution + float3 avgColor; + float3 minColor; + float3 maxColor; + + avgColor = minColor = maxColor = unpackUnorm4x8(srcPixelsBlock[0]).xyz; + for (int i = 1; i < 16; ++i) { + const float3 currColorUnorm = unpackUnorm4x8(srcPixelsBlock[i]).xyz; + avgColor += currColorUnorm; + minColor = min(minColor, currColorUnorm); + maxColor = max(maxColor, currColorUnorm); + } + + avgColor = round(avgColor * 255.0f / 16.0f); + maxColor *= 255.0f; + minColor *= 255.0f; + + // determine covariance matrix + float cov[6]; + for (int i = 0; i < 6; ++i) + cov[i] = 0; + + for (int i = 0; i < 16; ++i) { + const float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f; + float3 rgbDiff = currColor - avgColor; + + cov[0] += rgbDiff.r * rgbDiff.r; + cov[1] += rgbDiff.r * rgbDiff.g; + cov[2] += rgbDiff.r * rgbDiff.b; + cov[3] += rgbDiff.g * rgbDiff.g; + cov[4] += rgbDiff.g * rgbDiff.b; + cov[5] += rgbDiff.b * rgbDiff.b; + } + + // convert covariance matrix to float, find principal axis via power iter + for (int i = 0; i < 6; ++i) + cov[i] /= 255.0f; + + float3 vF = maxColor - minColor; + + const int nIterPower = 4; + for (int iter = 0; iter < nIterPower; ++iter) { + const float r = vF.r * cov[0] + vF.g * cov[1] + vF.b * cov[2]; + const float g = vF.r * cov[1] + vF.g * cov[3] + vF.b * cov[4]; + const float b = vF.r * cov[2] + vF.g * cov[4] + vF.b * cov[5]; + + vF.r = r; + vF.g = g; + vF.b = b; + } + + float magn = max3(abs(vF.r), abs(vF.g), abs(vF.b)); + float3 v; + + if (magn < 4.0f) { // too small, default to luminance + v.r = 299.0f; // JPEG YCbCr luma coefs, scaled by 1000. + v.g = 587.0f; + v.b = 114.0f; + } else { + v = trunc(vF * (512.0f / magn)); + } + + // Pick colors at extreme points + float3 minEndpoint, maxEndpoint; + float minDot = FLT_MAX; + float maxDot = -FLT_MAX; + for (int i = 0; i < 16; ++i) { + const float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f; + const float dotValue = dot(currColor, v); + + if (dotValue < minDot) { + minDot = dotValue; + minEndpoint = currColor; + } + + if (dotValue > maxDot) { + maxDot = dotValue; + maxEndpoint = currColor; + } + } + + outMinEndp16 = rgb888to565(minEndpoint); + outMaxEndp16 = rgb888to565(maxEndpoint); +} + +// The color matching function +uint MatchColorsBlock(const uint srcPixelsBlock[16], float3 color[4]) { + uint mask = 0u; + float3 dir = color[0] - color[1]; + float stops[4]; + + for (int i = 0; i < 4; ++i) + stops[i] = dot(color[i], dir); + + // think of the colors as arranged on a line; project point onto that line, then choose + // next color out of available ones. we compute the crossover points for "best color in top + // half"/"best in bottom half" and then the same inside that subinterval. + // + // relying on this 1d approximation isn't always optimal in terms of euclidean distance, + // but it's very close and a lot faster. + // http://cbloomrants.blogspot.com/2008/12/12-08-08-dxtc-summary.html + + float c0Point = trunc((stops[1] + stops[3]) * 0.5f); + float halfPoint = trunc((stops[3] + stops[2]) * 0.5f); + float c3Point = trunc((stops[2] + stops[0]) * 0.5f); + +#ifndef BC1_DITHER + // the version without dithering is straightforward + for (uint i = 16u; i-- > 0u;) { + const float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f; + + const float dotValue = dot(currColor, dir); + mask <<= 2u; + + if (dotValue < halfPoint) + mask |= ((dotValue < c0Point) ? 1u : 3u); + else + mask |= ((dotValue < c3Point) ? 2u : 0u); + } +#else + // with floyd-steinberg dithering + float4 ep1 = float4(0, 0, 0, 0); + float4 ep2 = float4(0, 0, 0, 0); + + c0Point *= 16.0f; + halfPoint *= 16.0f; + c3Point *= 16.0f; + + for (uint y = 0u; y < 4u; ++y) { + float ditherDot; + uint lmask, step; + + float3 currColor; + float dotValue; + + currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 0]).xyz * 255.0f; + dotValue = dot(currColor, dir); + + ditherDot = (dotValue * 16.0f) + (3 * ep2[1] + 5 * ep2[0]); + if (ditherDot < halfPoint) + step = (ditherDot < c0Point) ? 1u : 3u; + else + step = (ditherDot < c3Point) ? 2u : 0u; + ep1[0] = dotValue - stops[step]; + lmask = step; + + currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 1]).xyz * 255.0f; + dotValue = dot(currColor, dir); + + ditherDot = (dotValue * 16.0f) + (7 * ep1[0] + 3 * ep2[2] + 5 * ep2[1] + ep2[0]); + if (ditherDot < halfPoint) + step = (ditherDot < c0Point) ? 1u : 3u; + else + step = (ditherDot < c3Point) ? 2u : 0u; + ep1[1] = dotValue - stops[step]; + lmask |= step << 2u; + + currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 2]).xyz * 255.0f; + dotValue = dot(currColor, dir); + + ditherDot = (dotValue * 16.0f) + (7 * ep1[1] + 3 * ep2[3] + 5 * ep2[2] + ep2[1]); + if (ditherDot < halfPoint) + step = (ditherDot < c0Point) ? 1u : 3u; + else + step = (ditherDot < c3Point) ? 2u : 0u; + ep1[2] = dotValue - stops[step]; + lmask |= step << 4u; + + currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 2]).xyz * 255.0f; + dotValue = dot(currColor, dir); + + ditherDot = (dotValue * 16.0f) + (7 * ep1[2] + 5 * ep2[3] + ep2[2]); + if (ditherDot < halfPoint) + step = (ditherDot < c0Point) ? 1u : 3u; + else + step = (ditherDot < c3Point) ? 2u : 0u; + ep1[3] = dotValue - stops[step]; + lmask |= step << 6u; + + mask |= lmask << (y * 8u); + { + float4 tmp = ep1; + ep1 = ep2; + ep2 = tmp; + } // swap + } +#endif + + return mask; +} + +// The refinement function. (Clever code, part 2) +// Tries to optimize colors to suit block contents better. +// (By solving a least squares system via normal equations+Cramer's rule) +bool RefineBlock(const uint srcPixelsBlock[16], uint mask, inout float inOutMinEndp16, + inout float inOutMaxEndp16) { + float newMin16, newMax16; + const float oldMin = inOutMinEndp16; + const float oldMax = inOutMaxEndp16; + + if ((mask ^ (mask << 2u)) < 4u) // all pixels have the same index? + { + // yes, linear system would be singular; solve using optimal + // single-color match on average color + float3 rgbVal = float3(8.0f / 255.0f, 8.0f / 255.0f, 8.0f / 255.0f); + for (int i = 0; i < 16; ++i) + rgbVal += unpackUnorm4x8(srcPixelsBlock[i]).xyz; + + rgbVal = floor(rgbVal * (255.0f / 16.0f)); + + newMax16 = c_oMatch5[uint(rgbVal.r)][0] * 2048.0f + // + c_oMatch6[uint(rgbVal.g)][0] * 32.0f + // + c_oMatch5[uint(rgbVal.b)][0]; + newMin16 = c_oMatch5[uint(rgbVal.r)][1] * 2048.0f + // + c_oMatch6[uint(rgbVal.g)][1] * 32.0f + // + c_oMatch5[uint(rgbVal.b)][1]; + } else { + const float w1Tab[4] = { 3, 0, 2, 1 }; + const float prods[4] = { 589824.0f, 2304.0f, 262402.0f, 66562.0f }; + // ^some magic to save a lot of multiplies in the accumulating loop... + // (precomputed products of weights for least squares system, accumulated inside one 32-bit + // register) + + float akku = 0.0f; + uint cm = mask; + float3 at1 = float3(0, 0, 0); + float3 at2 = float3(0, 0, 0); + for (int i = 0; i < 16; ++i, cm >>= 2u) { + const float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f; + + const uint step = cm & 3u; + const float w1 = w1Tab[step]; + akku += prods[step]; + at1 += currColor * w1; + at2 += currColor; + } + + at2 = 3.0f * at2 - at1; + + // extract solutions and decide solvability + const float xx = floor(akku / 65535.0f); + const float yy = floor(mod(akku, 65535.0f) / 256.0f); + const float xy = mod(akku, 256.0f); + + float2 f_rb_g; + f_rb_g.x = 3.0f * 31.0f / 255.0f / (xx * yy - xy * xy); + f_rb_g.y = f_rb_g.x * 63.0f / 31.0f; + + // solve. + const float3 newMaxVal = clamp(floor((at1 * yy - at2 * xy) * f_rb_g.xyx + 0.5f), + float3(0.0f, 0.0f, 0.0f), float3(31, 63, 31)); + newMax16 = newMaxVal.x * 2048.0f + newMaxVal.y * 32.0f + newMaxVal.z; + + const float3 newMinVal = clamp(floor((at2 * xx - at1 * xy) * f_rb_g.xyx + 0.5f), + float3(0.0f, 0.0f, 0.0f), float3(31, 63, 31)); + newMin16 = newMinVal.x * 2048.0f + newMinVal.y * 32.0f + newMinVal.z; + } + + inOutMinEndp16 = newMin16; + inOutMaxEndp16 = newMax16; + + return oldMin != newMin16 || oldMax != newMax16; +} + +#ifdef BC1_DITHER +/// Quantizes 'srcValue' which is originally in 888 (full range), +/// converting it to 565 and then back to 888 (quantized) +float3 quant(float3 srcValue) { + srcValue = clamp(srcValue, 0.0f, 255.0f); + // Convert 888 -> 565 + srcValue = floor(srcValue * float3(31.0f / 255.0f, 63.0f / 255.0f, 31.0f / 255.0f) + 0.5f); + // Convert 565 -> 888 back + srcValue = floor(srcValue * float3(8.25f, 4.0625f, 8.25f)); + + return srcValue; +} + +void DitherBlock(const uint srcPixBlck[16], out uint dthPixBlck[16]) { + float3 ep1[4] = { float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0) }; + float3 ep2[4] = { float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0) }; + + for (uint y = 0u; y < 16u; y += 4u) { + float3 srcPixel, dithPixel; + + srcPixel = unpackUnorm4x8(srcPixBlck[y + 0u]).xyz * 255.0f; + dithPixel = quant(srcPixel + trunc((3 * ep2[1] + 5 * ep2[0]) * (1.0f / 16.0f))); + ep1[0] = srcPixel - dithPixel; + dthPixBlck[y + 0u] = packUnorm4x8(float4(dithPixel * (1.0f / 255.0f), 1.0f)); + + srcPixel = unpackUnorm4x8(srcPixBlck[y + 1u]).xyz * 255.0f; + dithPixel = quant( + srcPixel + trunc((7 * ep1[0] + 3 * ep2[2] + 5 * ep2[1] + ep2[0]) * (1.0f / 16.0f))); + ep1[1] = srcPixel - dithPixel; + dthPixBlck[y + 1u] = packUnorm4x8(float4(dithPixel * (1.0f / 255.0f), 1.0f)); + + srcPixel = unpackUnorm4x8(srcPixBlck[y + 2u]).xyz * 255.0f; + dithPixel = quant( + srcPixel + trunc((7 * ep1[1] + 3 * ep2[3] + 5 * ep2[2] + ep2[1]) * (1.0f / 16.0f))); + ep1[2] = srcPixel - dithPixel; + dthPixBlck[y + 2u] = packUnorm4x8(float4(dithPixel * (1.0f / 255.0f), 1.0f)); + + srcPixel = unpackUnorm4x8(srcPixBlck[y + 3u]).xyz * 255.0f; + dithPixel = quant(srcPixel + trunc((7 * ep1[2] + 5 * ep2[3] + ep2[2]) * (1.0f / 16.0f))); + ep1[3] = srcPixel - dithPixel; + dthPixBlck[y + 3u] = packUnorm4x8(float4(dithPixel * (1.0f / 255.0f), 1.0f)); + + // swap( ep1, ep2 ) + for (uint i = 0u; i < 4u; ++i) { + float3 tmp = ep1[i]; + ep1[i] = ep2[i]; + ep2[i] = tmp; + } + } +} +#endif + +void main() { + uint srcPixelsBlock[16]; + + bool bAllColorsEqual = true; + + // Load the whole 4x4 block + const uint2 pixelsToLoadBase = gl_GlobalInvocationID.xy << 2u; + for (uint i = 0u; i < 16u; ++i) { + const uint2 pixelsToLoad = pixelsToLoadBase + uint2(i & 0x03u, i >> 2u); + const float3 srcPixels0 = OGRE_Load2D(srcTex, int2(pixelsToLoad), 0).xyz; + srcPixelsBlock[i] = packUnorm4x8(float4(srcPixels0, 1.0f)); + bAllColorsEqual = bAllColorsEqual && srcPixelsBlock[0] == srcPixelsBlock[i]; + } + + float maxEndp16, minEndp16; + uint mask = 0u; + + if (bAllColorsEqual) { + const uint3 rgbVal = uint3(unpackUnorm4x8(srcPixelsBlock[0]).xyz * 255.0f); + mask = 0xAAAAAAAAu; + maxEndp16 = + c_oMatch5[rgbVal.r][0] * 2048.0f + c_oMatch6[rgbVal.g][0] * 32.0f + c_oMatch5[rgbVal.b][0]; + minEndp16 = + c_oMatch5[rgbVal.r][1] * 2048.0f + c_oMatch6[rgbVal.g][1] * 32.0f + c_oMatch5[rgbVal.b][1]; + } else { +#ifdef BC1_DITHER + uint ditherPixelsBlock[16]; + // first step: compute dithered version for PCA if desired + DitherBlock(srcPixelsBlock, ditherPixelsBlock); +#else +#define ditherPixelsBlock srcPixelsBlock +#endif + + // second step: pca+map along principal axis + OptimizeColorsBlock(ditherPixelsBlock, minEndp16, maxEndp16); + if (minEndp16 != maxEndp16) { + float3 colors[4]; + EvalColors(colors, maxEndp16, minEndp16); // Note min/max are inverted + mask = MatchColorsBlock(srcPixelsBlock, colors); + } + + // third step: refine (multiple times if requested) + bool bStopRefinement = false; + for (uint i = 0u; i < params.p_numRefinements && !bStopRefinement; ++i) { + const uint lastMask = mask; + + if (RefineBlock(ditherPixelsBlock, mask, minEndp16, maxEndp16)) { + if (minEndp16 != maxEndp16) { + float3 colors[4]; + EvalColors(colors, maxEndp16, minEndp16); // Note min/max are inverted + mask = MatchColorsBlock(srcPixelsBlock, colors); + } else { + mask = 0u; + bStopRefinement = true; + } + } + + bStopRefinement = mask == lastMask || bStopRefinement; + } + } + + // write the color block + if (maxEndp16 < minEndp16) { + const float tmpValue = minEndp16; + minEndp16 = maxEndp16; + maxEndp16 = tmpValue; + mask ^= 0x55555555u; + } + + uint2 outputBytes; + outputBytes.x = uint(maxEndp16) | (uint(minEndp16) << 16u); + outputBytes.y = mask; + + uint2 dstUV = gl_GlobalInvocationID.xy; + imageStore(dstTexture, int2(dstUV), uint4(outputBytes.xy, 0u, 0u)); +} diff --git a/modules/betsy/betsy_bc1.h b/modules/betsy/betsy_bc1.h new file mode 100644 index 0000000000..2274ed0a81 --- /dev/null +++ b/modules/betsy/betsy_bc1.h @@ -0,0 +1,1061 @@ +/**************************************************************************/ +/* betsy_bc1.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BETSY_BC1_H +#define BETSY_BC1_H + +constexpr const float dxt1_encoding_table[1024] = { + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 1, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 1, + 1, + 1, + 2, + 0, + 2, + 0, + 0, + 4, + 2, + 1, + 2, + 1, + 2, + 1, + 3, + 0, + 3, + 0, + 3, + 0, + 3, + 1, + 1, + 5, + 3, + 2, + 3, + 2, + 4, + 0, + 4, + 0, + 4, + 1, + 4, + 1, + 4, + 2, + 4, + 2, + 4, + 2, + 3, + 5, + 5, + 1, + 5, + 1, + 5, + 2, + 4, + 4, + 5, + 3, + 5, + 3, + 5, + 3, + 6, + 2, + 6, + 2, + 6, + 2, + 6, + 3, + 5, + 5, + 6, + 4, + 6, + 4, + 4, + 8, + 7, + 3, + 7, + 3, + 7, + 3, + 7, + 4, + 7, + 4, + 7, + 4, + 7, + 5, + 5, + 9, + 7, + 6, + 7, + 6, + 8, + 4, + 8, + 4, + 8, + 5, + 8, + 5, + 8, + 6, + 8, + 6, + 8, + 6, + 7, + 9, + 9, + 5, + 9, + 5, + 9, + 6, + 8, + 8, + 9, + 7, + 9, + 7, + 9, + 7, + 10, + 6, + 10, + 6, + 10, + 6, + 10, + 7, + 9, + 9, + 10, + 8, + 10, + 8, + 8, + 12, + 11, + 7, + 11, + 7, + 11, + 7, + 11, + 8, + 11, + 8, + 11, + 8, + 11, + 9, + 9, + 13, + 11, + 10, + 11, + 10, + 12, + 8, + 12, + 8, + 12, + 9, + 12, + 9, + 12, + 10, + 12, + 10, + 12, + 10, + 11, + 13, + 13, + 9, + 13, + 9, + 13, + 10, + 12, + 12, + 13, + 11, + 13, + 11, + 13, + 11, + 14, + 10, + 14, + 10, + 14, + 10, + 14, + 11, + 13, + 13, + 14, + 12, + 14, + 12, + 12, + 16, + 15, + 11, + 15, + 11, + 15, + 11, + 15, + 12, + 15, + 12, + 15, + 12, + 15, + 13, + 13, + 17, + 15, + 14, + 15, + 14, + 16, + 12, + 16, + 12, + 16, + 13, + 16, + 13, + 16, + 14, + 16, + 14, + 16, + 14, + 15, + 17, + 17, + 13, + 17, + 13, + 17, + 14, + 16, + 16, + 17, + 15, + 17, + 15, + 17, + 15, + 18, + 14, + 18, + 14, + 18, + 14, + 18, + 15, + 17, + 17, + 18, + 16, + 18, + 16, + 16, + 20, + 19, + 15, + 19, + 15, + 19, + 15, + 19, + 16, + 19, + 16, + 19, + 16, + 19, + 17, + 17, + 21, + 19, + 18, + 19, + 18, + 20, + 16, + 20, + 16, + 20, + 17, + 20, + 17, + 20, + 18, + 20, + 18, + 20, + 18, + 19, + 21, + 21, + 17, + 21, + 17, + 21, + 18, + 20, + 20, + 21, + 19, + 21, + 19, + 21, + 19, + 22, + 18, + 22, + 18, + 22, + 18, + 22, + 19, + 21, + 21, + 22, + 20, + 22, + 20, + 20, + 24, + 23, + 19, + 23, + 19, + 23, + 19, + 23, + 20, + 23, + 20, + 23, + 20, + 23, + 21, + 21, + 25, + 23, + 22, + 23, + 22, + 24, + 20, + 24, + 20, + 24, + 21, + 24, + 21, + 24, + 22, + 24, + 22, + 24, + 22, + 23, + 25, + 25, + 21, + 25, + 21, + 25, + 22, + 24, + 24, + 25, + 23, + 25, + 23, + 25, + 23, + 26, + 22, + 26, + 22, + 26, + 22, + 26, + 23, + 25, + 25, + 26, + 24, + 26, + 24, + 24, + 28, + 27, + 23, + 27, + 23, + 27, + 23, + 27, + 24, + 27, + 24, + 27, + 24, + 27, + 25, + 25, + 29, + 27, + 26, + 27, + 26, + 28, + 24, + 28, + 24, + 28, + 25, + 28, + 25, + 28, + 26, + 28, + 26, + 28, + 26, + 27, + 29, + 29, + 25, + 29, + 25, + 29, + 26, + 28, + 28, + 29, + 27, + 29, + 27, + 29, + 27, + 30, + 26, + 30, + 26, + 30, + 26, + 30, + 27, + 29, + 29, + 30, + 28, + 30, + 28, + 30, + 28, + 31, + 27, + 31, + 27, + 31, + 27, + 31, + 28, + 31, + 28, + 31, + 28, + 31, + 29, + 31, + 29, + 31, + 30, + 31, + 30, + 31, + 30, + 31, + 31, + 31, + 31, + 0, + 0, + 0, + 1, + 1, + 0, + 1, + 0, + 1, + 1, + 2, + 0, + 2, + 1, + 3, + 0, + 3, + 0, + 3, + 1, + 4, + 0, + 4, + 0, + 4, + 1, + 5, + 0, + 5, + 1, + 6, + 0, + 6, + 0, + 6, + 1, + 7, + 0, + 7, + 0, + 7, + 1, + 8, + 0, + 8, + 1, + 8, + 1, + 8, + 2, + 9, + 1, + 9, + 2, + 9, + 2, + 9, + 3, + 10, + 2, + 10, + 3, + 10, + 3, + 10, + 4, + 11, + 3, + 11, + 4, + 11, + 4, + 11, + 5, + 12, + 4, + 12, + 5, + 12, + 5, + 12, + 6, + 13, + 5, + 13, + 6, + 8, + 16, + 13, + 7, + 14, + 6, + 14, + 7, + 9, + 17, + 14, + 8, + 15, + 7, + 15, + 8, + 11, + 16, + 15, + 9, + 15, + 10, + 16, + 8, + 16, + 9, + 16, + 10, + 15, + 13, + 17, + 9, + 17, + 10, + 17, + 11, + 15, + 16, + 18, + 10, + 18, + 11, + 18, + 12, + 16, + 16, + 19, + 11, + 19, + 12, + 19, + 13, + 17, + 17, + 20, + 12, + 20, + 13, + 20, + 14, + 19, + 16, + 21, + 13, + 21, + 14, + 21, + 15, + 20, + 17, + 22, + 14, + 22, + 15, + 25, + 10, + 22, + 16, + 23, + 15, + 23, + 16, + 26, + 11, + 23, + 17, + 24, + 16, + 24, + 17, + 27, + 12, + 24, + 18, + 25, + 17, + 25, + 18, + 28, + 13, + 25, + 19, + 26, + 18, + 26, + 19, + 29, + 14, + 26, + 20, + 27, + 19, + 27, + 20, + 30, + 15, + 27, + 21, + 28, + 20, + 28, + 21, + 28, + 21, + 28, + 22, + 29, + 21, + 29, + 22, + 24, + 32, + 29, + 23, + 30, + 22, + 30, + 23, + 25, + 33, + 30, + 24, + 31, + 23, + 31, + 24, + 27, + 32, + 31, + 25, + 31, + 26, + 32, + 24, + 32, + 25, + 32, + 26, + 31, + 29, + 33, + 25, + 33, + 26, + 33, + 27, + 31, + 32, + 34, + 26, + 34, + 27, + 34, + 28, + 32, + 32, + 35, + 27, + 35, + 28, + 35, + 29, + 33, + 33, + 36, + 28, + 36, + 29, + 36, + 30, + 35, + 32, + 37, + 29, + 37, + 30, + 37, + 31, + 36, + 33, + 38, + 30, + 38, + 31, + 41, + 26, + 38, + 32, + 39, + 31, + 39, + 32, + 42, + 27, + 39, + 33, + 40, + 32, + 40, + 33, + 43, + 28, + 40, + 34, + 41, + 33, + 41, + 34, + 44, + 29, + 41, + 35, + 42, + 34, + 42, + 35, + 45, + 30, + 42, + 36, + 43, + 35, + 43, + 36, + 46, + 31, + 43, + 37, + 44, + 36, + 44, + 37, + 44, + 37, + 44, + 38, + 45, + 37, + 45, + 38, + 40, + 48, + 45, + 39, + 46, + 38, + 46, + 39, + 41, + 49, + 46, + 40, + 47, + 39, + 47, + 40, + 43, + 48, + 47, + 41, + 47, + 42, + 48, + 40, + 48, + 41, + 48, + 42, + 47, + 45, + 49, + 41, + 49, + 42, + 49, + 43, + 47, + 48, + 50, + 42, + 50, + 43, + 50, + 44, + 48, + 48, + 51, + 43, + 51, + 44, + 51, + 45, + 49, + 49, + 52, + 44, + 52, + 45, + 52, + 46, + 51, + 48, + 53, + 45, + 53, + 46, + 53, + 47, + 52, + 49, + 54, + 46, + 54, + 47, + 57, + 42, + 54, + 48, + 55, + 47, + 55, + 48, + 58, + 43, + 55, + 49, + 56, + 48, + 56, + 49, + 59, + 44, + 56, + 50, + 57, + 49, + 57, + 50, + 60, + 45, + 57, + 51, + 58, + 50, + 58, + 51, + 61, + 46, + 58, + 52, + 59, + 51, + 59, + 52, + 62, + 47, + 59, + 53, + 60, + 52, + 60, + 53, + 60, + 53, + 60, + 54, + 61, + 53, + 61, + 54, + 61, + 54, + 61, + 55, + 62, + 54, + 62, + 55, + 62, + 55, + 62, + 56, + 63, + 55, + 63, + 56, + 63, + 56, + 63, + 57, + 63, + 58, + 63, + 59, + 63, + 59, + 63, + 60, + 63, + 61, + 63, + 62, + 63, + 62, + 63, + 63, +}; + +#endif // BETSY_BC1_H diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp index bc72203b2f..7b4d8b3dfb 100644 --- a/modules/betsy/image_compress_betsy.cpp +++ b/modules/betsy/image_compress_betsy.cpp @@ -30,39 +30,17 @@ #include "image_compress_betsy.h" -#include "servers/rendering/rendering_device_binds.h" -#include "servers/rendering/rendering_server_default.h" +#include "core/config/project_settings.h" -#if defined(VULKAN_ENABLED) -#include "drivers/vulkan/rendering_context_driver_vulkan.h" -#endif -#if defined(METAL_ENABLED) -#include "drivers/metal/rendering_context_driver_metal.h" -#endif +#include "betsy_bc1.h" +#include "bc1.glsl.gen.h" #include "bc6h.glsl.gen.h" -struct BC6PushConstant { - float sizeX; - float sizeY; - uint32_t padding[2]; -}; - -static int get_next_multiple(int n, int m) { - return n + (m - (n % m)); -} - -Error _compress_betsy(BetsyFormat p_format, Image *r_img) { - uint64_t start_time = OS::get_singleton()->get_ticks_msec(); - - if (r_img->is_compressed()) { - return ERR_INVALID_DATA; - } - - ERR_FAIL_COND_V_MSG(r_img->get_format() < Image::FORMAT_RF || r_img->get_format() > Image::FORMAT_RGBE9995, ERR_INVALID_DATA, "Image is not an HDR image."); - - Error err = OK; +static Mutex betsy_mutex; +static BetsyCompressor *betsy = nullptr; +void BetsyCompressor::_init() { // Create local RD. RenderingContextDriver *rcd = nullptr; RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device(); @@ -81,7 +59,7 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) { #endif #endif if (rcd != nullptr && rd != nullptr) { - err = rcd->initialize(); + Error err = rcd->initialize(); if (err == OK) { err = rd->initialize(rcd); } @@ -95,58 +73,201 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) { } } - ERR_FAIL_NULL_V_MSG(rd, err, "Unable to create a local RenderingDevice."); + ERR_FAIL_NULL_MSG(rd, "Unable to create a local RenderingDevice."); + + compress_rd = rd; + compress_rcd = rcd; + + // Create the sampler state. + RD::SamplerState src_sampler_state; + { + src_sampler_state.repeat_u = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE; + src_sampler_state.repeat_v = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE; + src_sampler_state.mag_filter = RD::SAMPLER_FILTER_NEAREST; + src_sampler_state.min_filter = RD::SAMPLER_FILTER_NEAREST; + src_sampler_state.mip_filter = RD::SAMPLER_FILTER_NEAREST; + } + + src_sampler = compress_rd->sampler_create(src_sampler_state); +} + +void BetsyCompressor::init() { + WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &BetsyCompressor::_thread_loop), true); + command_queue.set_pump_task_id(tid); + command_queue.push(this, &BetsyCompressor::_assign_mt_ids, tid); + command_queue.push_and_sync(this, &BetsyCompressor::_init); + DEV_ASSERT(task_id == tid); +} + +void BetsyCompressor::_assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id) { + task_id = p_pump_task_id; +} + +// Yield thread to WTP so other tasks can be done on it. +// Automatically regains control as soon a task is pushed to the command queue. +void BetsyCompressor::_thread_loop() { + while (!exit) { + WorkerThreadPool::get_singleton()->yield(); + command_queue.flush_all(); + } +} + +void BetsyCompressor::_thread_exit() { + exit = true; + + if (compress_rd != nullptr) { + if (dxt1_encoding_table_buffer.is_valid()) { + compress_rd->free(dxt1_encoding_table_buffer); + } + + compress_rd->free(src_sampler); + + // Clear the shader cache, pipelines will be unreferenced automatically. + for (KeyValue<String, BetsyShader> &E : cached_shaders) { + if (E.value.compiled.is_valid()) { + compress_rd->free(E.value.compiled); + } + } + cached_shaders.clear(); + } +} + +void BetsyCompressor::finish() { + command_queue.push(this, &BetsyCompressor::_thread_exit); + if (task_id != WorkerThreadPool::INVALID_TASK_ID) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(task_id); + task_id = WorkerThreadPool::INVALID_TASK_ID; + } + + if (compress_rd != nullptr) { + // Free the RD (and RCD if necessary). + memdelete(compress_rd); + compress_rd = nullptr; + if (compress_rcd != nullptr) { + memdelete(compress_rcd); + compress_rcd = nullptr; + } + } +} + +// Helper functions. + +static int get_next_multiple(int n, int m) { + return n + (m - (n % m)); +} + +static String get_shader_name(BetsyFormat p_format) { + switch (p_format) { + case BETSY_FORMAT_BC1: + case BETSY_FORMAT_BC1_DITHER: + return "BC1"; + + case BETSY_FORMAT_BC3: + return "BC3"; + + case BETSY_FORMAT_BC6_SIGNED: + case BETSY_FORMAT_BC6_UNSIGNED: + return "BC6"; + + default: + return ""; + } +} + +Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) { + uint64_t start_time = OS::get_singleton()->get_ticks_msec(); + + if (r_img->is_compressed()) { + return ERR_INVALID_DATA; + } - Ref<RDShaderFile> compute_shader; - compute_shader.instantiate(); + Error err = OK; // Destination format. Image::Format dest_format = Image::FORMAT_MAX; + RD::DataFormat dst_rd_format = RD::DATA_FORMAT_MAX; String version = ""; switch (p_format) { - case BETSY_FORMAT_BC6: { - err = compute_shader->parse_versions_from_text(bc6h_shader_glsl); - - if (r_img->detect_signed(true)) { - dest_format = Image::FORMAT_BPTC_RGBF; - version = "signed"; - } else { - dest_format = Image::FORMAT_BPTC_RGBFU; - version = "unsigned"; - } + case BETSY_FORMAT_BC1: + version = "standard"; + dst_rd_format = RD::DATA_FORMAT_R32G32_UINT; + dest_format = Image::FORMAT_DXT1; + break; + + case BETSY_FORMAT_BC1_DITHER: + version = "dithered"; + dst_rd_format = RD::DATA_FORMAT_R32G32_UINT; + dest_format = Image::FORMAT_DXT1; + break; - } break; + case BETSY_FORMAT_BC6_SIGNED: + version = "signed"; + dst_rd_format = RD::DATA_FORMAT_R32G32B32A32_UINT; + dest_format = Image::FORMAT_BPTC_RGBF; + break; + + case BETSY_FORMAT_BC6_UNSIGNED: + version = "unsigned"; + dst_rd_format = RD::DATA_FORMAT_R32G32B32A32_UINT; + dest_format = Image::FORMAT_BPTC_RGBFU; + break; default: err = ERR_INVALID_PARAMETER; break; } - if (err != OK) { - compute_shader->print_errors("Betsy compress shader"); - memdelete(rd); - if (rcd != nullptr) { - memdelete(rcd); + const String shader_name = get_shader_name(p_format) + "-" + version; + BetsyShader shader; + + if (cached_shaders.has(shader_name)) { + shader = cached_shaders[shader_name]; + + } else { + Ref<RDShaderFile> source; + source.instantiate(); + + switch (p_format) { + case BETSY_FORMAT_BC1: + case BETSY_FORMAT_BC1_DITHER: + err = source->parse_versions_from_text(bc1_shader_glsl); + break; + + case BETSY_FORMAT_BC6_UNSIGNED: + case BETSY_FORMAT_BC6_SIGNED: + err = source->parse_versions_from_text(bc6h_shader_glsl); + break; + + default: + err = ERR_INVALID_PARAMETER; + break; } - return err; - } + if (err != OK) { + source->print_errors("Betsy compress shader"); + return err; + } - // Compile the shader, return early if invalid. - RID shader = rd->shader_create_from_spirv(compute_shader->get_spirv_stages(version)); + // Compile the shader, return early if invalid. + shader.compiled = compress_rd->shader_create_from_spirv(source->get_spirv_stages(version)); + if (shader.compiled.is_null()) { + return ERR_CANT_CREATE; + } - if (shader.is_null()) { - memdelete(rd); - if (rcd != nullptr) { - memdelete(rcd); + // Compile the pipeline, return early if invalid. + shader.pipeline = compress_rd->compute_pipeline_create(shader.compiled); + if (shader.pipeline.is_null()) { + return ERR_CANT_CREATE; } - return err; + cached_shaders[shader_name] = shader; } - RID pipeline = rd->compute_pipeline_create(shader); + if (shader.compiled.is_null() || shader.pipeline.is_null()) { + return ERR_INVALID_DATA; + } // src_texture format information. RD::TextureFormat src_texture_format; @@ -159,6 +280,33 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) { } switch (r_img->get_format()) { + case Image::FORMAT_L8: + r_img->convert(Image::FORMAT_RGBA8); + src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + break; + + case Image::FORMAT_LA8: + r_img->convert(Image::FORMAT_RGBA8); + src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + break; + + case Image::FORMAT_R8: + src_texture_format.format = RD::DATA_FORMAT_R8_UNORM; + break; + + case Image::FORMAT_RG8: + src_texture_format.format = RD::DATA_FORMAT_R8G8_UNORM; + break; + + case Image::FORMAT_RGB8: + r_img->convert(Image::FORMAT_RGBA8); + src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + break; + + case Image::FORMAT_RGBA8: + src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + break; + case Image::FORMAT_RH: src_texture_format.format = RD::DATA_FORMAT_R16_SFLOAT; break; @@ -198,33 +346,23 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) { break; default: { - rd->free(shader); - - memdelete(rd); - if (rcd != nullptr) { - memdelete(rcd); - } - return err; } } - // Create the sampler state. - RD::SamplerState src_sampler_state; - { - src_sampler_state.repeat_u = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE; - src_sampler_state.repeat_v = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE; - src_sampler_state.mag_filter = RD::SAMPLER_FILTER_NEAREST; - src_sampler_state.min_filter = RD::SAMPLER_FILTER_NEAREST; - src_sampler_state.mip_filter = RD::SAMPLER_FILTER_NEAREST; - } - - RID src_sampler = rd->sampler_create(src_sampler_state); - // For the destination format just copy the source format and change the usage bits. RD::TextureFormat dst_texture_format = src_texture_format; dst_texture_format.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT; - dst_texture_format.format = RD::DATA_FORMAT_R32G32B32A32_UINT; + dst_texture_format.format = dst_rd_format; + + // Encoding table setup. + if (dest_format == Image::FORMAT_DXT1 && dxt1_encoding_table_buffer.is_null()) { + Vector<uint8_t> data; + data.resize(1024 * 4); + memcpy(data.ptrw(), dxt1_encoding_table, 1024 * 4); + + dxt1_encoding_table_buffer = compress_rd->storage_buffer_create(1024 * 4, data); + } const int mip_count = r_img->get_mipmap_count() + 1; @@ -256,8 +394,41 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) { memcpy(src_image_ptr[0].ptrw(), r_img->ptr() + ofs, size); // Create the textures on the GPU. - RID src_texture = rd->texture_create(src_texture_format, RD::TextureView(), src_images); - RID dst_texture = rd->texture_create(dst_texture_format, RD::TextureView()); + RID src_texture = compress_rd->texture_create(src_texture_format, RD::TextureView(), src_images); + RID dst_texture = compress_rd->texture_create(dst_texture_format, RD::TextureView()); + + Vector<RD::Uniform> uniforms; + { + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE; + u.binding = 0; + u.append_id(src_sampler); + u.append_id(src_texture); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 1; + u.append_id(dst_texture); + uniforms.push_back(u); + } + + if (dest_format == Image::FORMAT_DXT1) { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 2; + u.append_id(dxt1_encoding_table_buffer); + uniforms.push_back(u); + } + } + + RID uniform_set = compress_rd->uniform_set_create(uniforms, shader.compiled, 0); + RD::ComputeListID compute_list = compress_rd->compute_list_begin(); + + compress_rd->compute_list_bind_compute_pipeline(compute_list, shader.pipeline); + compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0); if (dest_format == Image::FORMAT_BPTC_RGBFU || dest_format == Image::FORMAT_BPTC_RGBF) { BC6PushConstant push_constant; @@ -266,47 +437,33 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) { push_constant.padding[0] = 0; push_constant.padding[1] = 0; - Vector<RD::Uniform> uniforms; - { - { - RD::Uniform u; - u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE; - u.binding = 0; - u.append_id(src_sampler); - u.append_id(src_texture); - uniforms.push_back(u); - } - { - RD::Uniform u; - u.uniform_type = RD::UNIFORM_TYPE_IMAGE; - u.binding = 1; - u.append_id(dst_texture); - uniforms.push_back(u); - } - } + compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant)); - RID uniform_set = rd->uniform_set_create(uniforms, shader, 0); - RD::ComputeListID compute_list = rd->compute_list_begin(); + } else { + BC1PushConstant push_constant; + push_constant.num_refines = 2; + push_constant.padding[0] = 0; + push_constant.padding[1] = 0; + push_constant.padding[2] = 0; - rd->compute_list_bind_compute_pipeline(compute_list, pipeline); - rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0); - rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant)); - rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1); - rd->compute_list_end(); + compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC1PushConstant)); } - rd->submit(); - rd->sync(); + compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1); + compress_rd->compute_list_end(); + + compress_rd->submit(); + compress_rd->sync(); // Copy data from the GPU to the buffer. - const Vector<uint8_t> texture_data = rd->texture_get_data(dst_texture, 0); + const Vector<uint8_t> texture_data = compress_rd->texture_get_data(dst_texture, 0); int64_t dst_ofs = Image::get_image_mipmap_offset(r_img->get_width(), r_img->get_height(), dest_format, i); memcpy(dst_data_ptr + dst_ofs, texture_data.ptr(), texture_data.size()); // Free the source and dest texture. - rd->free(dst_texture); - rd->free(src_texture); + compress_rd->free(dst_texture); + compress_rd->free(src_texture); } src_images.clear(); @@ -314,26 +471,67 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) { // Set the compressed data to the image. r_img->set_data(r_img->get_width(), r_img->get_height(), r_img->has_mipmaps(), dest_format, dst_data); - // Free the shader (dependencies will be cleared automatically). - rd->free(src_sampler); - rd->free(shader); - - memdelete(rd); - if (rcd != nullptr) { - memdelete(rcd); - } - print_verbose(vformat("Betsy: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time)); return OK; } +void ensure_betsy_exists() { + betsy_mutex.lock(); + if (betsy == nullptr) { + betsy = memnew(BetsyCompressor); + betsy->init(); + } + betsy_mutex.unlock(); +} + Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels) { + ensure_betsy_exists(); Image::Format format = r_img->get_format(); + Error result = ERR_UNAVAILABLE; if (format >= Image::FORMAT_RF && format <= Image::FORMAT_RGBE9995) { - return _compress_betsy(BETSY_FORMAT_BC6, r_img); + if (r_img->detect_signed()) { + result = betsy->compress(BETSY_FORMAT_BC6_SIGNED, r_img); + } else { + result = betsy->compress(BETSY_FORMAT_BC6_UNSIGNED, r_img); + } + } + + if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) { + free_device(); + } + + return result; +} + +Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels) { + ensure_betsy_exists(); + Error result = ERR_UNAVAILABLE; + + switch (p_channels) { + case Image::USED_CHANNELS_RGB: + result = betsy->compress(BETSY_FORMAT_BC1_DITHER, r_img); + break; + + case Image::USED_CHANNELS_L: + result = betsy->compress(BETSY_FORMAT_BC1, r_img); + break; + + default: + break; } - return ERR_UNAVAILABLE; + if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) { + free_device(); + } + + return result; +} + +void free_device() { + if (betsy != nullptr) { + betsy->finish(); + memdelete(betsy); + } } diff --git a/modules/betsy/image_compress_betsy.h b/modules/betsy/image_compress_betsy.h index a64e586c76..70e4ae85ed 100644 --- a/modules/betsy/image_compress_betsy.h +++ b/modules/betsy/image_compress_betsy.h @@ -32,13 +32,79 @@ #define IMAGE_COMPRESS_BETSY_H #include "core/io/image.h" +#include "core/object/worker_thread_pool.h" +#include "core/os/thread.h" +#include "core/templates/command_queue_mt.h" + +#include "servers/rendering/rendering_device_binds.h" +#include "servers/rendering/rendering_server_default.h" + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_context_driver_vulkan.h" +#endif +#if defined(METAL_ENABLED) +#include "drivers/metal/rendering_context_driver_metal.h" +#endif enum BetsyFormat { - BETSY_FORMAT_BC6, + BETSY_FORMAT_BC1, + BETSY_FORMAT_BC1_DITHER, + BETSY_FORMAT_BC3, + BETSY_FORMAT_BC6_SIGNED, + BETSY_FORMAT_BC6_UNSIGNED, +}; + +struct BC6PushConstant { + float sizeX; + float sizeY; + uint32_t padding[2]; }; -Error _compress_betsy(BetsyFormat p_format, Image *r_img); +struct BC1PushConstant { + uint32_t num_refines; + uint32_t padding[3]; +}; + +void free_device(); Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels); +Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels); + +class BetsyCompressor : public Object { + mutable CommandQueueMT command_queue; + bool exit = false; + WorkerThreadPool::TaskID task_id = WorkerThreadPool::INVALID_TASK_ID; + + struct BetsyShader { + RID compiled; + RID pipeline; + }; + + // Resources shared by all compression formats. + RenderingDevice *compress_rd = nullptr; + RenderingContextDriver *compress_rcd = nullptr; + HashMap<String, BetsyShader> cached_shaders; + RID src_sampler = RID(); + + // Format-specific resources. + RID dxt1_encoding_table_buffer = RID(); + + void _init(); + void _assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id); + void _thread_loop(); + void _thread_exit(); + + Error _compress(BetsyFormat p_format, Image *r_img); + +public: + void init(); + void finish(); + + Error compress(BetsyFormat p_format, Image *r_img) { + Error err; + command_queue.push_and_ret(this, &BetsyCompressor::_compress, p_format, r_img, &err); + return err; + } +}; #endif // IMAGE_COMPRESS_BETSY_H diff --git a/modules/betsy/register_types.cpp b/modules/betsy/register_types.cpp index 019099e67c..a3a3b5a99b 100644 --- a/modules/betsy/register_types.cpp +++ b/modules/betsy/register_types.cpp @@ -38,10 +38,13 @@ void initialize_betsy_module(ModuleInitializationLevel p_level) { } Image::_image_compress_bptc_rd_func = _betsy_compress_bptc; + Image::_image_compress_bc_rd_func = _betsy_compress_s3tc; } void uninitialize_betsy_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } + + free_device(); } diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 840cadace3..499df55bef 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -1787,12 +1787,6 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { _send_window_event(windows[p_id], WINDOW_EVENT_MOUSE_EXIT); } - window_set_rect_changed_callback(Callable(), p_id); - window_set_window_event_callback(Callable(), p_id); - window_set_input_event_callback(Callable(), p_id); - window_set_input_text_callback(Callable(), p_id); - window_set_drop_files_callback(Callable(), p_id); - while (wd.transient_children.size()) { window_set_transient(*wd.transient_children.begin(), INVALID_WINDOW_ID); } @@ -1836,6 +1830,12 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { XUnmapWindow(x11_display, wd.x11_window); XDestroyWindow(x11_display, wd.x11_window); + window_set_rect_changed_callback(Callable(), p_id); + window_set_window_event_callback(Callable(), p_id); + window_set_input_event_callback(Callable(), p_id); + window_set_input_text_callback(Callable(), p_id); + window_set_drop_files_callback(Callable(), p_id); + windows.erase(p_id); } diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index ed7e0de0e2..72299f788d 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -144,7 +144,9 @@ void BaseButton::_toggled(bool p_pressed) { } void BaseButton::on_action_event(Ref<InputEvent> p_event) { - if (p_event->is_pressed()) { + Ref<InputEventMouseButton> mouse_button = p_event; + + if (p_event->is_pressed() && (mouse_button.is_null() || status.hovering)) { status.press_attempt = true; status.pressing_inside = true; emit_signal(SNAME("button_down")); @@ -174,12 +176,6 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { } if (!p_event->is_pressed()) { - Ref<InputEventMouseButton> mouse_button = p_event; - if (mouse_button.is_valid()) { - if (!has_point(mouse_button->get_position())) { - status.hovering = false; - } - } status.press_attempt = false; status.pressing_inside = false; emit_signal(SNAME("button_up")); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index de2332125f..a7ff213d65 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1235,14 +1235,16 @@ Ref<World2D> Viewport::find_world_2d() const { } } -void Viewport::_propagate_viewport_notification(Node *p_node, int p_what) { +void Viewport::_propagate_drag_notification(Node *p_node, int p_what) { + // Send notification to p_node and all children and descendant nodes of p_node, except to SubViewports which are not children of a SubViewportContainer. p_node->notification(p_what); + bool is_svc = Object::cast_to<SubViewportContainer>(p_node); for (int i = 0; i < p_node->get_child_count(); i++) { Node *c = p_node->get_child(i); - if (Object::cast_to<Viewport>(c)) { + if (!is_svc && Object::cast_to<SubViewport>(c)) { continue; } - _propagate_viewport_notification(c, p_what); + Viewport::_propagate_drag_notification(c, p_what); } } @@ -1345,7 +1347,7 @@ Ref<InputEvent> Viewport::_make_input_local(const Ref<InputEvent> &ev) { Vector2 Viewport::get_mouse_position() const { ERR_READ_THREAD_GUARD_V(Vector2()); - if (!is_directly_attached_to_screen()) { + if (get_section_root_viewport() != SceneTree::get_singleton()->get_root()) { // Rely on the most recent mouse coordinate from an InputEventMouse in push_input. // In this case get_screen_transform is not applicable, because it is ambiguous. return gui.last_mouse_pos; @@ -1701,14 +1703,15 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_ } bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check) { - // Attempt grab, try parent controls too. + // Attempt drop, try parent controls too. CanvasItem *ci = p_at_control; + Viewport *section_root = get_section_root_viewport(); while (ci) { Control *control = Object::cast_to<Control>(ci); if (control) { - if (control->can_drop_data(p_at_pos, gui.drag_data)) { + if (control->can_drop_data(p_at_pos, section_root->gui.drag_data)) { if (!p_just_check) { - control->drop_data(p_at_pos, gui.drag_data); + control->drop_data(p_at_pos, section_root->gui.drag_data); } return true; @@ -1780,7 +1783,9 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *control = Object::cast_to<Control>(ci); if (control) { if (control->get_focus_mode() != Control::FOCUS_NONE) { - if (control != gui.key_focus) { + // Grabbing unhovered focus can cause issues when mouse is dragged + // with another button held down. + if (control != gui.key_focus && gui.mouse_over_hierarchy.has(control)) { control->grab_focus(); } break; @@ -1806,13 +1811,13 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.dragging && mb->get_button_index() == MouseButton::LEFT) { // Alternate drop use (when using force_drag(), as proposed by #5342). - _perform_drop(gui.mouse_focus, pos); + _perform_drop(gui.mouse_focus); } _gui_cancel_tooltip(); } else { if (gui.dragging && mb->get_button_index() == MouseButton::LEFT) { - _perform_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos); + _perform_drop(gui.drag_mouse_over); } gui.mouse_focus_mask.clear_flag(mouse_button_to_mask(mb->get_button_index())); // Remove from mask. @@ -1848,7 +1853,8 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Point2 mpos = mm->get_position(); // Drag & drop. - if (!gui.drag_attempted && gui.mouse_focus && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { + Viewport *section_root = get_section_root_viewport(); + if (!gui.drag_attempted && gui.mouse_focus && section_root && !section_root->gui.global_dragging && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { gui.drag_accum += mm->get_relative(); float len = gui.drag_accum.length(); if (len > 10) { @@ -1857,11 +1863,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { while (ci) { Control *control = Object::cast_to<Control>(ci); if (control) { - gui.dragging = true; - gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos - gui.drag_accum)); - if (gui.drag_data.get_type() != Variant::NIL) { + section_root->gui.global_dragging = true; + section_root->gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos - gui.drag_accum)); + if (section_root->gui.drag_data.get_type() != Variant::NIL) { gui.mouse_focus = nullptr; gui.mouse_focus_mask.clear(); + gui.dragging = true; break; } else { Control *drag_preview = _gui_get_drag_preview(); @@ -1870,7 +1877,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { memdelete(drag_preview); gui.drag_preview_id = ObjectID(); } - gui.dragging = false; + section_root->gui.global_dragging = false; } if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { @@ -1888,7 +1895,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.drag_attempted = true; if (gui.dragging) { - _propagate_viewport_notification(this, NOTIFICATION_DRAG_BEGIN); + Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_BEGIN); } } } @@ -1986,105 +1993,29 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (gui.dragging) { - // Handle drag & drop. + // Handle drag & drop. This happens in the viewport where dragging started. Control *drag_preview = _gui_get_drag_preview(); if (drag_preview) { drag_preview->set_position(mpos); } - gui.drag_mouse_over = over; - gui.drag_mouse_over_pos = Vector2(); - - // Find the window this is above of. - // See if there is an embedder. - Viewport *embedder = nullptr; - Vector2 viewport_pos; - - if (is_embedding_subwindows()) { - embedder = this; - viewport_pos = mpos; - } else { - // Not an embedder, but may be a subwindow of an embedder. - Window *w = Object::cast_to<Window>(this); - if (w) { - if (w->is_embedded()) { - embedder = w->get_embedder(); - - viewport_pos = get_final_transform().xform(mpos) + w->get_position(); // To parent coords. - } - } - } - - Viewport *viewport_under = nullptr; - - if (embedder) { - // Use embedder logic. - - for (int i = embedder->gui.sub_windows.size() - 1; i >= 0; i--) { - Window *sw = embedder->gui.sub_windows[i].window; - Rect2 swrect = Rect2i(sw->get_position(), sw->get_size()); - if (!sw->get_flag(Window::FLAG_BORDERLESS)) { - int title_height = sw->theme_cache.title_height; - swrect.position.y -= title_height; - swrect.size.y += title_height; - } - - if (swrect.has_point(viewport_pos)) { - viewport_under = sw; - viewport_pos -= sw->get_position(); - } - } - - if (!viewport_under) { - // Not in a subwindow, likely in embedder. - viewport_under = embedder; - } - } else { - // Use DisplayServer logic. - Vector2i screen_mouse_pos = DisplayServer::get_singleton()->mouse_get_position(); - - DisplayServer::WindowID window_id = DisplayServer::get_singleton()->get_window_at_screen_position(screen_mouse_pos); - - if (window_id != DisplayServer::INVALID_WINDOW_ID) { - ObjectID object_under = DisplayServer::get_singleton()->window_get_attached_instance_id(window_id); - - if (object_under != ObjectID()) { // Fetch window. - Window *w = Object::cast_to<Window>(ObjectDB::get_instance(object_under)); - if (w) { - viewport_under = w; - viewport_pos = w->get_final_transform().affine_inverse().xform(screen_mouse_pos - w->get_position()); - } - } + gui.drag_mouse_over = section_root->gui.target_control; + if (gui.drag_mouse_over) { + if (!_gui_drop(gui.drag_mouse_over, gui.drag_mouse_over->get_local_mouse_position(), true)) { + gui.drag_mouse_over = nullptr; } - } - - if (viewport_under) { - if (viewport_under != this) { - Transform2D ai = viewport_under->get_final_transform().affine_inverse(); - viewport_pos = ai.xform(viewport_pos); - } - // Find control under at position. - gui.drag_mouse_over = viewport_under->gui_find_control(viewport_pos); if (gui.drag_mouse_over) { - Transform2D localizer = gui.drag_mouse_over->get_global_transform_with_canvas().affine_inverse(); - gui.drag_mouse_over_pos = localizer.xform(viewport_pos); - - bool can_drop = _gui_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos, true); - - if (!can_drop) { - ds_cursor_shape = DisplayServer::CURSOR_FORBIDDEN; - } else { - ds_cursor_shape = DisplayServer::CURSOR_CAN_DROP; - } + ds_cursor_shape = DisplayServer::CURSOR_CAN_DROP; + } else { + ds_cursor_shape = DisplayServer::CURSOR_FORBIDDEN; } - - } else { - gui.drag_mouse_over = nullptr; } } - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE) && !Object::cast_to<SubViewportContainer>(over)) { + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE) && (gui.dragging || (!section_root->gui.global_dragging && !Object::cast_to<SubViewportContainer>(over)))) { + // If dragging is active, then set the cursor shape only from the Viewport where dragging started. + // If dragging is inactive, then set the cursor shape only when not over a SubViewportContainer. DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape); } } @@ -2284,10 +2215,10 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } -void Viewport::_perform_drop(Control *p_control, Point2 p_pos) { +void Viewport::_perform_drop(Control *p_control) { // Without any arguments, simply cancel Drag and Drop. if (p_control) { - gui.drag_successful = _gui_drop(p_control, p_pos, false); + gui.drag_successful = _gui_drop(p_control, p_control->get_local_mouse_position(), false); } else { gui.drag_successful = false; } @@ -2298,10 +2229,12 @@ void Viewport::_perform_drop(Control *p_control, Point2 p_pos) { gui.drag_preview_id = ObjectID(); } - gui.drag_data = Variant(); + Viewport *section_root = get_section_root_viewport(); + section_root->gui.drag_data = Variant(); gui.dragging = false; + section_root->gui.global_dragging = false; gui.drag_mouse_over = nullptr; - _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); + Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_END); // Display the new cursor shape instantly. update_mouse_cursor_state(); } @@ -2331,14 +2264,16 @@ void Viewport::_gui_force_drag(Control *p_base, const Variant &p_data, Control * ERR_FAIL_COND_MSG(p_data.get_type() == Variant::NIL, "Drag data must be a value."); gui.dragging = true; - gui.drag_data = p_data; + Viewport *section_root = get_section_root_viewport(); + section_root->gui.global_dragging = true; + section_root->gui.drag_data = p_data; gui.mouse_focus = nullptr; gui.mouse_focus_mask.clear(); if (p_control) { _gui_set_drag_preview(p_base, p_control); } - _propagate_viewport_notification(this, NOTIFICATION_DRAG_BEGIN); + Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_BEGIN); } void Viewport::_gui_set_drag_preview(Control *p_base, Control *p_control) { @@ -3022,6 +2957,7 @@ void Viewport::_update_mouse_over() { } void Viewport::_update_mouse_over(Vector2 p_pos) { + gui.last_mouse_pos = p_pos; // Necessary, because mouse cursor can be over Viewports that are not reached by the InputEvent. // Look for embedded windows at mouse position. if (is_embedding_subwindows()) { for (int i = gui.sub_windows.size() - 1; i >= 0; i--) { @@ -3073,6 +3009,7 @@ void Viewport::_update_mouse_over(Vector2 p_pos) { // Look for Controls at mouse position. Control *over = gui_find_control(p_pos); + get_section_root_viewport()->gui.target_control = over; bool notify_embedded_viewports = false; if (over != gui.mouse_over || (!over && !gui.mouse_over_hierarchy.is_empty())) { // Find the common ancestor of `gui.mouse_over` and `over`. @@ -3195,6 +3132,10 @@ void Viewport::_drop_mouse_over(Control *p_until_control) { if (gui.mouse_over && gui.mouse_over->is_inside_tree()) { gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF); } + Viewport *section_root = get_section_root_viewport(); + if (section_root && section_root->gui.target_control == gui.mouse_over) { + section_root->gui.target_control = nullptr; + } gui.mouse_over = nullptr; // Send Mouse Exit notifications to children first. Don't send to p_until_control or above. @@ -3403,7 +3344,7 @@ bool Viewport::is_input_disabled() const { Variant Viewport::gui_get_drag_data() const { ERR_READ_THREAD_GUARD_V(Variant()); - return gui.drag_data; + return get_section_root_viewport()->gui.drag_data; } PackedStringArray Viewport::get_configuration_warnings() const { @@ -3597,7 +3538,7 @@ bool Viewport::is_snap_2d_vertices_to_pixel_enabled() const { bool Viewport::gui_is_dragging() const { ERR_READ_THREAD_GUARD_V(false); - return gui.dragging; + return get_section_root_viewport()->gui.global_dragging; } bool Viewport::gui_is_drag_successful() const { @@ -5156,9 +5097,12 @@ Transform2D SubViewport::get_popup_base_transform() const { return c->get_screen_transform() * container_transform * get_final_transform(); } -bool SubViewport::is_directly_attached_to_screen() const { - // SubViewports, that are used as Textures are not considered to be directly attached to screen. - return Object::cast_to<SubViewportContainer>(get_parent()) && get_parent()->get_viewport() && get_parent()->get_viewport()->is_directly_attached_to_screen(); +Viewport *SubViewport::get_section_root_viewport() const { + if (Object::cast_to<SubViewportContainer>(get_parent()) && get_parent()->get_viewport()) { + return get_parent()->get_viewport()->get_section_root_viewport(); + } + SubViewport *vp = const_cast<SubViewport *>(this); + return vp; } bool SubViewport::is_attached_in_viewport() const { diff --git a/scene/main/viewport.h b/scene/main/viewport.h index faa36851e9..f6a461d54e 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -281,7 +281,7 @@ private: bool disable_3d = false; - void _propagate_viewport_notification(Node *p_node, int p_what); + static void _propagate_drag_notification(Node *p_node, int p_what); void _update_global_transform(); @@ -362,7 +362,6 @@ private: Window *subwindow_over = nullptr; // mouse_over and subwindow_over are mutually exclusive. At all times at least one of them is nullptr. Window *windowmanager_window_over = nullptr; // Only used in root Viewport. Control *drag_mouse_over = nullptr; - Vector2 drag_mouse_over_pos; Control *tooltip_control = nullptr; Window *tooltip_popup = nullptr; Label *tooltip_label = nullptr; @@ -371,7 +370,7 @@ private: Point2 last_mouse_pos; Point2 drag_accum; bool drag_attempted = false; - Variant drag_data; + Variant drag_data; // Only used in root-Viewport and SubViewports, that are not children of a SubViewportContainer. ObjectID drag_preview_id; Ref<SceneTreeTimer> tooltip_timer; double tooltip_delay = 0.0; @@ -379,8 +378,10 @@ private: List<Control *> roots; HashSet<ObjectID> canvas_parents_with_dirty_order; int canvas_sort_index = 0; //for sorting items with canvas as root - bool dragging = false; + bool dragging = false; // Is true in the viewport in which dragging started while dragging is active. + bool global_dragging = false; // Is true while dragging is active. Only used in root-Viewport and SubViewports that are not children of a SubViewportContainer. bool drag_successful = false; + Control *target_control = nullptr; // Control that the mouse is over in the innermost nested Viewport. Only used in root-Viewport and SubViewports, that are not children of a SubViewportContainer. bool embed_subwindows_hint = false; Window *subwindow_focused = nullptr; @@ -408,7 +409,7 @@ private: Control *_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_global, const Transform2D &p_xform); void _gui_input_event(Ref<InputEvent> p_event); - void _perform_drop(Control *p_control = nullptr, Point2 p_pos = Point2()); + void _perform_drop(Control *p_control = nullptr); void _gui_cleanup_internal_state(Ref<InputEvent> p_event); void _push_unhandled_input_internal(const Ref<InputEvent> &p_event); @@ -672,9 +673,9 @@ public: Transform2D get_screen_transform() const; virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const; virtual Transform2D get_popup_base_transform() const { return Transform2D(); } - virtual bool is_directly_attached_to_screen() const { return false; }; - virtual bool is_attached_in_viewport() const { return false; }; - virtual bool is_sub_viewport() const { return false; }; + virtual Viewport *get_section_root_viewport() const { return nullptr; } + virtual bool is_attached_in_viewport() const { return false; } + virtual bool is_sub_viewport() const { return false; } private: // 2D audio, camera, and physics. (don't put World2D here because World2D is needed for Control nodes). @@ -836,9 +837,9 @@ public: virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const override; virtual Transform2D get_popup_base_transform() const override; - virtual bool is_directly_attached_to_screen() const override; + virtual Viewport *get_section_root_viewport() const override; virtual bool is_attached_in_viewport() const override; - virtual bool is_sub_viewport() const override { return true; }; + virtual bool is_sub_viewport() const override { return true; } void _validate_property(PropertyInfo &p_property) const; SubViewport(); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 6b299eab6e..803ce89bc9 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -2755,12 +2755,16 @@ Transform2D Window::get_popup_base_transform() const { return popup_base_transform; } -bool Window::is_directly_attached_to_screen() const { +Viewport *Window::get_section_root_viewport() const { if (get_embedder()) { - return get_embedder()->is_directly_attached_to_screen(); + return get_embedder()->get_section_root_viewport(); } - // Distinguish between the case that this is a native Window and not inside the tree. - return is_inside_tree(); + if (is_inside_tree()) { + // Native window. + return SceneTree::get_singleton()->get_root(); + } + Window *vp = const_cast<Window *>(this); + return vp; } bool Window::is_attached_in_viewport() const { diff --git a/scene/main/window.h b/scene/main/window.h index 84d2febe51..47aaf73728 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -469,7 +469,7 @@ public: virtual Transform2D get_final_transform() const override; virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const override; virtual Transform2D get_popup_base_transform() const override; - virtual bool is_directly_attached_to_screen() const override; + virtual Viewport *get_section_root_viewport() const override; virtual bool is_attached_in_viewport() const override; Rect2i get_parent_rect() const; diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 36bd22b723..aca85ce497 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -962,40 +962,27 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con GeometryInstanceSurfaceDataCache *surf = inst->surface_caches; + float lod_distance = 0.0; + + if (p_render_data->scene_data->cam_orthogonal) { + lod_distance = 1.0; + } else { + Vector3 aabb_min = inst->transformed_aabb.position; + Vector3 aabb_max = inst->transformed_aabb.position + inst->transformed_aabb.size; + Vector3 camera_position = p_render_data->scene_data->main_cam_transform.origin; + Vector3 surface_distance = Vector3(0.0, 0.0, 0.0).max(aabb_min - camera_position).max(camera_position - aabb_max); + + lod_distance = surface_distance.length(); + } + while (surf) { surf->sort.uses_forward_gi = 0; surf->sort.uses_lightmap = 0; // LOD - if (p_render_data->scene_data->screen_mesh_lod_threshold > 0.0 && mesh_storage->mesh_surface_has_lod(surf->surface)) { - float distance = 0.0; - - // Check if camera is NOT inside the mesh AABB. - if (!inst->transformed_aabb.has_point(p_render_data->scene_data->main_cam_transform.origin)) { - // Get the LOD support points on the mesh AABB. - Vector3 lod_support_min = inst->transformed_aabb.get_support(p_render_data->scene_data->main_cam_transform.basis.get_column(Vector3::AXIS_Z)); - Vector3 lod_support_max = inst->transformed_aabb.get_support(-p_render_data->scene_data->main_cam_transform.basis.get_column(Vector3::AXIS_Z)); - - // Get the distances to those points on the AABB from the camera origin. - float distance_min = (float)p_render_data->scene_data->main_cam_transform.origin.distance_to(lod_support_min); - float distance_max = (float)p_render_data->scene_data->main_cam_transform.origin.distance_to(lod_support_max); - - if (distance_min * distance_max < 0.0) { - //crossing plane - distance = 0.0; - } else if (distance_min >= 0.0) { - distance = distance_min; - } else if (distance_max <= 0.0) { - distance = -distance_max; - } - } - if (p_render_data->scene_data->cam_orthogonal) { - distance = 1.0; - } - uint32_t indices = 0; - surf->sort.lod_index = mesh_storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, distance * p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, indices); + surf->sort.lod_index = mesh_storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, lod_distance * p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, indices); if (p_render_data->render_info) { indices = _indices_to_primitives(surf->primitive, indices); if (p_render_list == RENDER_LIST_OPAQUE) { //opaque diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 48c226133d..8a02ec0eb5 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -1884,39 +1884,27 @@ void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const GeometryInstanceSurfaceDataCache *surf = inst->surface_caches; + float lod_distance = 0.0; + + if (p_render_data->scene_data->cam_orthogonal) { + lod_distance = 1.0; + } else { + Vector3 aabb_min = inst->transformed_aabb.position; + Vector3 aabb_max = inst->transformed_aabb.position + inst->transformed_aabb.size; + Vector3 camera_position = p_render_data->scene_data->main_cam_transform.origin; + Vector3 surface_distance = Vector3(0.0, 0.0, 0.0).max(aabb_min - camera_position).max(camera_position - aabb_max); + + lod_distance = surface_distance.length(); + } + while (surf) { surf->sort.uses_lightmap = 0; // LOD if (p_render_data->scene_data->screen_mesh_lod_threshold > 0.0 && mesh_storage->mesh_surface_has_lod(surf->surface)) { - float distance = 0.0; - - // Check if camera is NOT inside the mesh AABB. - if (!inst->transformed_aabb.has_point(p_render_data->scene_data->main_cam_transform.origin)) { - // Get the LOD support points on the mesh AABB. - Vector3 lod_support_min = inst->transformed_aabb.get_support(p_render_data->scene_data->main_cam_transform.basis.get_column(Vector3::AXIS_Z)); - Vector3 lod_support_max = inst->transformed_aabb.get_support(-p_render_data->scene_data->main_cam_transform.basis.get_column(Vector3::AXIS_Z)); - - // Get the distances to those points on the AABB from the camera origin. - float distance_min = (float)p_render_data->scene_data->main_cam_transform.origin.distance_to(lod_support_min); - float distance_max = (float)p_render_data->scene_data->main_cam_transform.origin.distance_to(lod_support_max); - - if (distance_min * distance_max < 0.0) { - //crossing plane - distance = 0.0; - } else if (distance_min >= 0.0) { - distance = distance_min; - } else if (distance_max <= 0.0) { - distance = -distance_max; - } - } - if (p_render_data->scene_data->cam_orthogonal) { - distance = 1.0; - } - uint32_t indices = 0; - surf->lod_index = mesh_storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, distance * p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, indices); + surf->lod_index = mesh_storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, lod_distance * p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, indices); if (p_render_data->render_info) { indices = _indices_to_primitives(surf->primitive, indices); if (p_render_list == RENDER_LIST_OPAQUE) { //opaque diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index b0851efe5f..88a22c6fc3 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -2146,23 +2146,24 @@ void RendererCanvasRenderRD::_render_batch_items(RenderTarget p_to_render_target current_batch->material_data = material_data; } - Transform2D base_transform = p_canvas_transform_inverse * ci->final_transform; - if (!ci->repeat_size.x && !ci->repeat_size.y) { - _record_item_commands(ci, p_to_render_target, base_transform, current_clip, p_lights, instance_index, batch_broken, r_sdf_used); + if (ci->repeat_source_item == nullptr || ci->repeat_size == Vector2()) { + Transform2D base_transform = p_canvas_transform_inverse * ci->final_transform; + _record_item_commands(ci, p_to_render_target, base_transform, current_clip, p_lights, instance_index, batch_broken, r_sdf_used, current_batch); } else { Point2 start_pos = ci->repeat_size * -(ci->repeat_times / 2); - Point2 end_pos = ci->repeat_size * ci->repeat_times + ci->repeat_size + start_pos; - Point2 pos = start_pos; - do { - do { - Transform2D transform = base_transform * Transform2D(0, pos / ci->xform_curr.get_scale()); - _record_item_commands(ci, p_to_render_target, transform, current_clip, p_lights, instance_index, batch_broken, r_sdf_used); - pos.y += ci->repeat_size.y; - } while (pos.y < end_pos.y); - - pos.x += ci->repeat_size.x; - pos.y = start_pos.y; - } while (pos.x < end_pos.x); + Point2 offset; + int repeat_times_x = ci->repeat_size.x ? ci->repeat_times : 0; + int repeat_times_y = ci->repeat_size.y ? ci->repeat_times : 0; + for (int ry = 0; ry <= repeat_times_y; ry++) { + offset.y = start_pos.y + ry * ci->repeat_size.y; + for (int rx = 0; rx <= repeat_times_x; rx++) { + offset.x = start_pos.x + rx * ci->repeat_size.x; + Transform2D base_transform = ci->final_transform; + base_transform.columns[2] += ci->repeat_source_item->final_transform.basis_xform(offset); + base_transform = p_canvas_transform_inverse * base_transform; + _record_item_commands(ci, p_to_render_target, base_transform, current_clip, p_lights, instance_index, batch_broken, r_sdf_used, current_batch); + } + } } } @@ -2262,9 +2263,7 @@ void RendererCanvasRenderRD::_render_batch_items(RenderTarget p_to_render_target state.last_instance_index += instance_index; } -void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTarget p_render_target, const Transform2D &p_base_transform, Item *&r_current_clip, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used) { - Batch *current_batch = &state.canvas_instance_batches[state.current_batch_index]; - +void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTarget p_render_target, const Transform2D &p_base_transform, Item *&r_current_clip, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used, Batch *&r_current_batch) { RenderingServer::CanvasItemTextureFilter texture_filter = p_item->texture_filter == RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT ? default_filter : p_item->texture_filter; RenderingServer::CanvasItemTextureRepeat texture_repeat = p_item->texture_repeat == RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT ? default_repeat : p_item->texture_repeat; @@ -2310,9 +2309,9 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar light_mode = (light_count > 0 || using_directional_lights) ? PIPELINE_LIGHT_MODE_ENABLED : PIPELINE_LIGHT_MODE_DISABLED; - if (light_mode != current_batch->light_mode) { - current_batch = _new_batch(r_batch_broken); - current_batch->light_mode = light_mode; + if (light_mode != r_current_batch->light_mode) { + r_current_batch = _new_batch(r_batch_broken); + r_current_batch->light_mode = light_mode; } // new_instance_data should be called after the current_batch is set. @@ -2338,11 +2337,11 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar instance_data->world[i] = world[i]; } - instance_data->flags = base_flags | current_batch->tex_flags; // Reset on each command for safety, keep canvas texture binding config. + instance_data->flags = base_flags | r_current_batch->tex_flags; // Reset on each command for safety, keep canvas texture binding config. - instance_data->color_texture_pixel_size[0] = current_batch->tex_texpixel_size.width; - instance_data->color_texture_pixel_size[1] = current_batch->tex_texpixel_size.height; - instance_data->specular_shininess = current_batch->tex_specular_shininess; + instance_data->color_texture_pixel_size[0] = r_current_batch->tex_texpixel_size.width; + instance_data->color_texture_pixel_size[1] = r_current_batch->tex_texpixel_size.height; + instance_data->specular_shininess = r_current_batch->tex_specular_shininess; return instance_data; }; @@ -2359,12 +2358,12 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar const Item::CommandRect *rect = static_cast<const Item::CommandRect *>(c); // 1: If commands are different, start a new batch. - if (current_batch->command_type != Item::Command::TYPE_RECT) { - current_batch = _new_batch(r_batch_broken); - current_batch->command_type = Item::Command::TYPE_RECT; - current_batch->command = c; + if (r_current_batch->command_type != Item::Command::TYPE_RECT) { + r_current_batch = _new_batch(r_batch_broken); + r_current_batch->command_type = Item::Command::TYPE_RECT; + r_current_batch->command = c; // default variant - current_batch->pipeline_variant = PIPELINE_VARIANT_QUAD; + r_current_batch->pipeline_variant = PIPELINE_VARIANT_QUAD; } if (bool(rect->flags & CANVAS_RECT_TILE)) { @@ -2374,10 +2373,10 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar bool has_msdf = bool(rect->flags & CANVAS_RECT_MSDF); TextureState tex_state(rect->texture, texture_filter, texture_repeat, has_msdf, use_linear_colors); - if (tex_state != current_batch->tex_state) { - current_batch = _new_batch(r_batch_broken); - current_batch->set_tex_state(tex_state); - _prepare_batch_texture(current_batch, rect->texture); + if (tex_state != r_current_batch->tex_state) { + r_current_batch = _new_batch(r_batch_broken); + r_current_batch->set_tex_state(tex_state); + _prepare_batch_texture(r_current_batch, rect->texture); } Color modulated = rect->modulate * base_color; @@ -2388,11 +2387,11 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar bool has_blend = bool(rect->flags & CANVAS_RECT_LCD); // Start a new batch if the blend mode has changed, // or blend mode is enabled and the modulation has changed. - if (has_blend != current_batch->has_blend || (has_blend && modulated != current_batch->modulate)) { - current_batch = _new_batch(r_batch_broken); - current_batch->has_blend = has_blend; - current_batch->modulate = modulated; - current_batch->pipeline_variant = has_blend ? PIPELINE_VARIANT_QUAD_LCD_BLEND : PIPELINE_VARIANT_QUAD; + if (has_blend != r_current_batch->has_blend || (has_blend && modulated != r_current_batch->modulate)) { + r_current_batch = _new_batch(r_batch_broken); + r_current_batch->has_blend = has_blend; + r_current_batch->modulate = modulated; + r_current_batch->pipeline_variant = has_blend ? PIPELINE_VARIANT_QUAD_LCD_BLEND : PIPELINE_VARIANT_QUAD; } InstanceData *instance_data = new_instance_data(); @@ -2400,7 +2399,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar Rect2 dst_rect; if (rect->texture.is_valid()) { - src_rect = (rect->flags & CANVAS_RECT_REGION) ? Rect2(rect->source.position * current_batch->tex_texpixel_size, rect->source.size * current_batch->tex_texpixel_size) : Rect2(0, 0, 1, 1); + src_rect = (rect->flags & CANVAS_RECT_REGION) ? Rect2(rect->source.position * r_current_batch->tex_texpixel_size, rect->source.size * r_current_batch->tex_texpixel_size) : Rect2(0, 0, 1, 1); dst_rect = Rect2(rect->rect.position, rect->rect.size); if (dst_rect.size.width < 0) { @@ -2470,24 +2469,24 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar instance_data->dst_rect[2] = dst_rect.size.width; instance_data->dst_rect[3] = dst_rect.size.height; - _add_to_batch(r_index, r_batch_broken, current_batch); + _add_to_batch(r_index, r_batch_broken, r_current_batch); } break; case Item::Command::TYPE_NINEPATCH: { const Item::CommandNinePatch *np = static_cast<const Item::CommandNinePatch *>(c); - if (current_batch->command_type != Item::Command::TYPE_NINEPATCH) { - current_batch = _new_batch(r_batch_broken); - current_batch->command_type = Item::Command::TYPE_NINEPATCH; - current_batch->command = c; - current_batch->pipeline_variant = PipelineVariant::PIPELINE_VARIANT_NINEPATCH; + if (r_current_batch->command_type != Item::Command::TYPE_NINEPATCH) { + r_current_batch = _new_batch(r_batch_broken); + r_current_batch->command_type = Item::Command::TYPE_NINEPATCH; + r_current_batch->command = c; + r_current_batch->pipeline_variant = PipelineVariant::PIPELINE_VARIANT_NINEPATCH; } TextureState tex_state(np->texture, texture_filter, texture_repeat, false, use_linear_colors); - if (tex_state != current_batch->tex_state) { - current_batch = _new_batch(r_batch_broken); - current_batch->set_tex_state(tex_state); - _prepare_batch_texture(current_batch, np->texture); + if (tex_state != r_current_batch->tex_state) { + r_current_batch = _new_batch(r_batch_broken); + r_current_batch->set_tex_state(tex_state); + _prepare_batch_texture(r_current_batch, np->texture); } InstanceData *instance_data = new_instance_data(); @@ -2499,7 +2498,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar src_rect = Rect2(0, 0, 1, 1); } else { if (np->source != Rect2()) { - src_rect = Rect2(np->source.position.x * current_batch->tex_texpixel_size.width, np->source.position.y * current_batch->tex_texpixel_size.height, np->source.size.x * current_batch->tex_texpixel_size.width, np->source.size.y * current_batch->tex_texpixel_size.height); + src_rect = Rect2(np->source.position.x * r_current_batch->tex_texpixel_size.width, np->source.position.y * r_current_batch->tex_texpixel_size.height, np->source.size.x * r_current_batch->tex_texpixel_size.width, np->source.size.y * r_current_batch->tex_texpixel_size.height); instance_data->color_texture_pixel_size[0] = 1.0 / np->source.size.width; instance_data->color_texture_pixel_size[1] = 1.0 / np->source.size.height; } else { @@ -2539,30 +2538,30 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar instance_data->ninepatch_margins[2] = np->margin[SIDE_RIGHT]; instance_data->ninepatch_margins[3] = np->margin[SIDE_BOTTOM]; - _add_to_batch(r_index, r_batch_broken, current_batch); + _add_to_batch(r_index, r_batch_broken, r_current_batch); } break; case Item::Command::TYPE_POLYGON: { const Item::CommandPolygon *polygon = static_cast<const Item::CommandPolygon *>(c); // Polygon's can't be batched, so always create a new batch - current_batch = _new_batch(r_batch_broken); + r_current_batch = _new_batch(r_batch_broken); - current_batch->command_type = Item::Command::TYPE_POLYGON; - current_batch->command = c; + r_current_batch->command_type = Item::Command::TYPE_POLYGON; + r_current_batch->command = c; TextureState tex_state(polygon->texture, texture_filter, texture_repeat, false, use_linear_colors); - if (tex_state != current_batch->tex_state) { - current_batch = _new_batch(r_batch_broken); - current_batch->set_tex_state(tex_state); - _prepare_batch_texture(current_batch, polygon->texture); + if (tex_state != r_current_batch->tex_state) { + r_current_batch = _new_batch(r_batch_broken); + r_current_batch->set_tex_state(tex_state); + _prepare_batch_texture(r_current_batch, polygon->texture); } // pipeline variant { static const PipelineVariant variant[RS::PRIMITIVE_MAX] = { PIPELINE_VARIANT_ATTRIBUTE_POINTS, PIPELINE_VARIANT_ATTRIBUTE_LINES, PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP }; ERR_CONTINUE(polygon->primitive < 0 || polygon->primitive >= RS::PRIMITIVE_MAX); - current_batch->pipeline_variant = variant[polygon->primitive]; + r_current_batch->pipeline_variant = variant[polygon->primitive]; } InstanceData *instance_data = new_instance_data(); @@ -2577,27 +2576,27 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar instance_data->modulation[2] = color.b; instance_data->modulation[3] = color.a; - _add_to_batch(r_index, r_batch_broken, current_batch); + _add_to_batch(r_index, r_batch_broken, r_current_batch); } break; case Item::Command::TYPE_PRIMITIVE: { const Item::CommandPrimitive *primitive = static_cast<const Item::CommandPrimitive *>(c); - if (primitive->point_count != current_batch->primitive_points || current_batch->command_type != Item::Command::TYPE_PRIMITIVE) { - current_batch = _new_batch(r_batch_broken); - current_batch->command_type = Item::Command::TYPE_PRIMITIVE; - current_batch->command = c; - current_batch->primitive_points = primitive->point_count; + if (primitive->point_count != r_current_batch->primitive_points || r_current_batch->command_type != Item::Command::TYPE_PRIMITIVE) { + r_current_batch = _new_batch(r_batch_broken); + r_current_batch->command_type = Item::Command::TYPE_PRIMITIVE; + r_current_batch->command = c; + r_current_batch->primitive_points = primitive->point_count; static const PipelineVariant variant[4] = { PIPELINE_VARIANT_PRIMITIVE_POINTS, PIPELINE_VARIANT_PRIMITIVE_LINES, PIPELINE_VARIANT_PRIMITIVE_TRIANGLES, PIPELINE_VARIANT_PRIMITIVE_TRIANGLES }; ERR_CONTINUE(primitive->point_count == 0 || primitive->point_count > 4); - current_batch->pipeline_variant = variant[primitive->point_count - 1]; + r_current_batch->pipeline_variant = variant[primitive->point_count - 1]; TextureState tex_state(primitive->texture, texture_filter, texture_repeat, false, use_linear_colors); - if (tex_state != current_batch->tex_state) { - current_batch = _new_batch(r_batch_broken); - current_batch->set_tex_state(tex_state); - _prepare_batch_texture(current_batch, primitive->texture); + if (tex_state != r_current_batch->tex_state) { + r_current_batch = _new_batch(r_batch_broken); + r_current_batch->set_tex_state(tex_state); + _prepare_batch_texture(r_current_batch, primitive->texture); } } @@ -2616,7 +2615,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar instance_data->colors[j * 2 + 1] = (uint32_t(Math::make_half_float(col.a)) << 16) | Math::make_half_float(col.b); } - _add_to_batch(r_index, r_batch_broken, current_batch); + _add_to_batch(r_index, r_batch_broken, r_current_batch); if (primitive->point_count == 4) { instance_data = new_instance_data(); @@ -2636,7 +2635,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar instance_data->colors[j * 2 + 1] = (uint32_t(Math::make_half_float(col.a)) << 16) | Math::make_half_float(col.b); } - _add_to_batch(r_index, r_batch_broken, current_batch); + _add_to_batch(r_index, r_batch_broken, r_current_batch); } } break; @@ -2644,9 +2643,9 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar case Item::Command::TYPE_MULTIMESH: case Item::Command::TYPE_PARTICLES: { // Mesh's can't be batched, so always create a new batch - current_batch = _new_batch(r_batch_broken); - current_batch->command = c; - current_batch->command_type = c->type; + r_current_batch = _new_batch(r_batch_broken); + r_current_batch->command = c; + r_current_batch->command_type = c->type; InstanceData *instance_data = nullptr; @@ -2654,11 +2653,11 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar if (c->type == Item::Command::TYPE_MESH) { const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(c); TextureState tex_state(m->texture, texture_filter, texture_repeat, false, use_linear_colors); - current_batch->set_tex_state(tex_state); - _prepare_batch_texture(current_batch, m->texture); + r_current_batch->set_tex_state(tex_state); + _prepare_batch_texture(r_current_batch, m->texture); instance_data = new_instance_data(); - current_batch->mesh_instance_count = 1; + r_current_batch->mesh_instance_count = 1; _update_transform_2d_to_mat2x3(base_transform * draw_transform * m->transform, instance_data->world); modulate = m->modulate; } else if (c->type == Item::Command::TYPE_MULTIMESH) { @@ -2671,14 +2670,14 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar break; } - current_batch->mesh_instance_count = mesh_storage->multimesh_get_instances_to_draw(multimesh); - if (current_batch->mesh_instance_count == 0) { + r_current_batch->mesh_instance_count = mesh_storage->multimesh_get_instances_to_draw(multimesh); + if (r_current_batch->mesh_instance_count == 0) { break; } TextureState tex_state(mm->texture, texture_filter, texture_repeat, false, use_linear_colors); - current_batch->set_tex_state(tex_state); - _prepare_batch_texture(current_batch, mm->texture); + r_current_batch->set_tex_state(tex_state); + _prepare_batch_texture(r_current_batch, mm->texture); instance_data = new_instance_data(); instance_data->flags |= 1; // multimesh, trails disabled @@ -2695,15 +2694,15 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(c); TextureState tex_state(pt->texture, texture_filter, texture_repeat, false, use_linear_colors); - current_batch->set_tex_state(tex_state); - _prepare_batch_texture(current_batch, pt->texture); + r_current_batch->set_tex_state(tex_state); + _prepare_batch_texture(r_current_batch, pt->texture); instance_data = new_instance_data(); uint32_t divisor = 1; - current_batch->mesh_instance_count = particles_storage->particles_get_amount(pt->particles, divisor); + r_current_batch->mesh_instance_count = particles_storage->particles_get_amount(pt->particles, divisor); instance_data->flags |= (divisor & FLAGS_INSTANCING_MASK); - current_batch->mesh_instance_count /= divisor; + r_current_batch->mesh_instance_count /= divisor; RID particles = pt->particles; @@ -2741,7 +2740,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar instance_data->modulation[2] = modulated.b; instance_data->modulation[3] = modulated.a; - _add_to_batch(r_index, r_batch_broken, current_batch); + _add_to_batch(r_index, r_batch_broken, r_current_batch); } break; case Item::Command::TYPE_TRANSFORM: { @@ -2754,12 +2753,12 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar const Item::CommandClipIgnore *ci = static_cast<const Item::CommandClipIgnore *>(c); if (r_current_clip) { if (ci->ignore != reclip) { - current_batch = _new_batch(r_batch_broken); + r_current_batch = _new_batch(r_batch_broken); if (ci->ignore) { - current_batch->clip = nullptr; + r_current_batch->clip = nullptr; reclip = true; } else { - current_batch->clip = r_current_clip; + r_current_batch->clip = r_current_clip; reclip = false; } } diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h index 87de07464e..8d90cd23ce 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h @@ -559,7 +559,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender { }; void _render_batch_items(RenderTarget p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr); - void _record_item_commands(const Item *p_item, RenderTarget p_render_target, const Transform2D &p_base_transform, Item *&r_current_clip, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used); + void _record_item_commands(const Item *p_item, RenderTarget p_render_target, const Transform2D &p_base_transform, Item *&r_current_clip, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used, Batch *&r_current_batch); void _render_batch(RD::DrawListID p_draw_list, PipelineVariants *p_pipeline_variants, RenderingDevice::FramebufferFormatID p_framebuffer_format, Light *p_lights, Batch const *p_batch, RenderingMethod::RenderInfo *r_render_info = nullptr); void _prepare_batch_texture(Batch *p_current_batch, RID p_texture) const; void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_uniform_set); diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index f354e83893..020fa94e9d 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -3528,6 +3528,7 @@ void RenderingServer::init() { GLOBAL_DEF_RST("rendering/textures/vram_compression/import_s3tc_bptc", false); GLOBAL_DEF_RST("rendering/textures/vram_compression/import_etc2_astc", false); GLOBAL_DEF("rendering/textures/vram_compression/compress_with_gpu", true); + GLOBAL_DEF("rendering/textures/vram_compression/cache_gpu_compressor", true); GLOBAL_DEF("rendering/textures/lossless_compression/force_png", false); diff --git a/tests/scene/test_viewport.h b/tests/scene/test_viewport.h index 1341cc0332..9d02c41719 100644 --- a/tests/scene/test_viewport.h +++ b/tests/scene/test_viewport.h @@ -119,8 +119,23 @@ public: class DragTarget : public NotificationControlViewport { GDCLASS(DragTarget, NotificationControlViewport); +protected: + void _notification(int p_what) { + switch (p_what) { + case NOTIFICATION_DRAG_BEGIN: { + during_drag = true; + } break; + + case NOTIFICATION_DRAG_END: { + during_drag = false; + } break; + } + } + public: Variant drag_data; + bool valid_drop = false; + bool during_drag = false; virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override { StringName string_data = p_data; // Verify drag data is compatible. @@ -136,6 +151,7 @@ public: virtual void drop_data(const Point2 &p_point, const Variant &p_data) override { drag_data = p_data; + valid_drop = true; } }; @@ -1107,12 +1123,10 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { SUBCASE("[Viewport][GuiInputEvent] Drag and Drop") { // FIXME: Drag-Preview will likely change. Tests for this part would have to be rewritten anyway. // See https://github.com/godotengine/godot/pull/67531#issuecomment-1385353430 for details. - // FIXME: Testing Drag and Drop with non-embedded windows would require DisplayServerMock additions - // FIXME: Drag and Drop currently doesn't work with embedded Windows and SubViewports - not testing. - // See https://github.com/godotengine/godot/issues/28522 for example. + // Note: Testing Drag and Drop with non-embedded windows would require DisplayServerMock additions. int min_grab_movement = 11; - SUBCASE("[Viewport][GuiInputEvent] Drag from one Control to another in the same viewport.") { - SUBCASE("[Viewport][GuiInputEvent] Perform successful Drag and Drop on a different Control.") { + SUBCASE("[Viewport][GuiInputEvent][DnD] Drag from one Control to another in the same viewport.") { + SUBCASE("[Viewport][GuiInputEvent][DnD] Perform successful Drag and Drop on a different Control.") { SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); CHECK_FALSE(root->gui_is_dragging()); @@ -1131,7 +1145,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK((StringName)node_d->drag_data == SNAME("Drag Data")); } - SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop on Control.") { + SUBCASE("[Viewport][GuiInputEvent][DnD] Perform unsuccessful drop on Control.") { SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); CHECK_FALSE(root->gui_is_dragging()); @@ -1157,7 +1171,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(root->gui_is_drag_successful()); } - SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop on No-Control.") { + SUBCASE("[Viewport][GuiInputEvent][DnD] Perform unsuccessful drop on No-Control.") { SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); CHECK_FALSE(root->gui_is_dragging()); @@ -1171,7 +1185,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { // Move away from Controls. SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::LEFT, Key::NONE); - CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW); // This could also be CURSOR_FORBIDDEN. + CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW); CHECK(root->gui_is_dragging()); SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_background, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); @@ -1179,7 +1193,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(root->gui_is_drag_successful()); } - SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop outside of window.") { + SUBCASE("[Viewport][GuiInputEvent][DnD] Perform unsuccessful drop outside of window.") { SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); CHECK_FALSE(root->gui_is_dragging()); @@ -1192,7 +1206,6 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { // Move outside of window. SEND_GUI_MOUSE_MOTION_EVENT(on_outside, MouseButtonMask::LEFT, Key::NONE); - CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW); CHECK(root->gui_is_dragging()); SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_outside, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); @@ -1200,7 +1213,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(root->gui_is_drag_successful()); } - SUBCASE("[Viewport][GuiInputEvent] Drag and Drop doesn't work with other Mouse Buttons than LMB.") { + SUBCASE("[Viewport][GuiInputEvent][DnD] Drag and Drop doesn't work with other Mouse Buttons than LMB.") { SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::MIDDLE, MouseButtonMask::MIDDLE, Key::NONE); CHECK_FALSE(root->gui_is_dragging()); @@ -1209,7 +1222,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::MIDDLE, MouseButtonMask::NONE, Key::NONE); } - SUBCASE("[Viewport][GuiInputEvent] Drag and Drop parent propagation.") { + SUBCASE("[Viewport][GuiInputEvent][DnD] Drag and Drop parent propagation.") { Node2D *node_aa = memnew(Node2D); Control *node_aaa = memnew(Control); Node2D *node_dd = memnew(Node2D); @@ -1318,7 +1331,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { memdelete(node_aa); } - SUBCASE("[Viewport][GuiInputEvent] Force Drag and Drop.") { + SUBCASE("[Viewport][GuiInputEvent][DnD] Force Drag and Drop.") { SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); CHECK_FALSE(root->gui_is_dragging()); node_a->force_drag(SNAME("Drag Data"), nullptr); @@ -1339,6 +1352,111 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); } } + + SUBCASE("[Viewport][GuiInputEvent][DnD] Drag to a different Viewport.") { + SubViewportContainer *svc = memnew(SubViewportContainer); + svc->set_size(Size2(100, 100)); + svc->set_position(Point2(200, 50)); + root->add_child(svc); + + SubViewport *sv = memnew(SubViewport); + sv->set_embedding_subwindows(true); + sv->set_size(Size2i(100, 100)); + svc->add_child(sv); + + DragStart *sv_a = memnew(DragStart); + sv_a->set_position(Point2(10, 10)); + sv_a->set_size(Size2(10, 10)); + sv->add_child(sv_a); + Point2i on_sva = Point2i(215, 65); + + DragTarget *sv_b = memnew(DragTarget); + sv_b->set_position(Point2(30, 30)); + sv_b->set_size(Size2(20, 20)); + sv->add_child(sv_b); + Point2i on_svb = Point2i(235, 85); + + Window *ew = memnew(Window); + ew->set_position(Point2(50, 200)); + ew->set_size(Size2(100, 100)); + root->add_child(ew); + + DragStart *ew_a = memnew(DragStart); + ew_a->set_position(Point2(10, 10)); + ew_a->set_size(Size2(10, 10)); + ew->add_child(ew_a); + Point2i on_ewa = Point2i(65, 215); + + DragTarget *ew_b = memnew(DragTarget); + ew_b->set_position(Point2(30, 30)); + ew_b->set_size(Size2(20, 20)); + ew->add_child(ew_b); + Point2i on_ewb = Point2i(85, 235); + + SUBCASE("[Viewport][GuiInputEvent][DnD] Drag to SubViewport") { + sv_b->valid_drop = false; + SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(root->gui_is_dragging()); + CHECK(sv_b->during_drag); + SEND_GUI_MOUSE_MOTION_EVENT(on_svb, MouseButtonMask::LEFT, Key::NONE); + CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP); + + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_svb, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK(sv_b->valid_drop); + CHECK(!sv_b->during_drag); + } + + SUBCASE("[Viewport][GuiInputEvent][DnD] Drag from SubViewport") { + node_d->valid_drop = false; + SEND_GUI_MOUSE_BUTTON_EVENT(on_sva, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_MOTION_EVENT(on_sva + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(sv->gui_is_dragging()); + CHECK(node_d->during_drag); + SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::LEFT, Key::NONE); + CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP); + + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK(node_d->valid_drop); + CHECK(!node_d->during_drag); + } + + SUBCASE("[Viewport][GuiInputEvent][DnD] Drag to embedded Window") { + ew_b->valid_drop = false; + SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(root->gui_is_dragging()); + CHECK(ew_b->during_drag); + SEND_GUI_MOUSE_MOTION_EVENT(on_ewb, MouseButtonMask::LEFT, Key::NONE); + CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP); + + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_ewb, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK(ew_b->valid_drop); + CHECK(!ew_b->during_drag); + } + + SUBCASE("[Viewport][GuiInputEvent][DnD] Drag from embedded Window") { + node_d->valid_drop = false; + SEND_GUI_MOUSE_BUTTON_EVENT(on_ewa, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_MOTION_EVENT(on_ewa + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(ew->gui_is_dragging()); + CHECK(node_d->during_drag); + SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::LEFT, Key::NONE); + CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP); + + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK(node_d->valid_drop); + CHECK(!node_d->during_drag); + } + + memdelete(ew_a); + memdelete(ew_b); + memdelete(ew); + memdelete(sv_a); + memdelete(sv_b); + memdelete(sv); + memdelete(svc); + } } memdelete(node_j); diff --git a/thirdparty/README.md b/thirdparty/README.md index dbe20ba2a5..ca6be902e3 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -78,7 +78,7 @@ fix build with our own copy of zstd (patch in `patches`). Files extracted from upstream source: -- `bc6h.glsl`, `CrossPlatformSettings_piece_all.glsl` and `UavCrossPlatform_piece_all.glsl`. +- `bc6h.glsl`, `bc1.glsl`, `CrossPlatformSettings_piece_all.glsl` and `UavCrossPlatform_piece_all.glsl`. - `LICENSE.md` |