diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18ce4881f9..962e946b8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: include: - profile: ubuntu-bionic-double-standard-unity-makefile - os: ubuntu-20.04 + os: ubuntu-22.04 config: Standard unity: YES generator: Unix Makefiles @@ -29,9 +29,10 @@ jobs: python: YES eigen: NO double: YES + asan: YES - profile: ubuntu-bionic-coverage-ninja - os: ubuntu-20.04 + os: ubuntu-22.04 config: Coverage unity: NO generator: Ninja @@ -40,9 +41,10 @@ jobs: python: YES eigen: NO double: NO + asan: YES #- profile: macos-coverage-unity-xcode - # os: macOS-13 + # os: macOS-15 # config: Coverage # unity: YES # generator: Xcode @@ -53,7 +55,7 @@ jobs: # double: NO - profile: macos-nometa-standard-makefile - os: macOS-13 + os: macOS-15 config: Standard unity: NO generator: Unix Makefiles @@ -62,6 +64,7 @@ jobs: python: YES eigen: NO double: NO + asan: YES - profile: windows-standard-unity-msvc os: windows-2022 @@ -88,7 +91,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 10 @@ -110,7 +113,7 @@ jobs: - name: Set up XCode (macOS) if: runner.os == 'macOS' - run: sudo xcode-select -s /Applications/Xcode_14.3.1.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.app/Contents/Developer - name: Install dependencies (Ubuntu) if: startsWith(matrix.os, 'ubuntu') @@ -127,7 +130,7 @@ jobs: - name: Cache dependencies (Windows) if: runner.os == 'Windows' - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: thirdparty key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.15-r1 @@ -144,7 +147,7 @@ jobs: - name: ccache (non-Windows) if: runner.os != 'Windows' - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ccache key: ci-cmake-ccache-${{ matrix.profile }} @@ -182,8 +185,13 @@ jobs: -D HAVE_PYTHON=${{ runner.os != 'Windows' && matrix.python || 'NO' }} -D HAVE_EIGEN=${{ matrix.eigen }} -D STDFLOAT_DOUBLE=${{ matrix.double }} + -D ENABLE_ASAN=${{ matrix.asan || 'NO' }} .. + echo "$PWD/${{ matrix.config }}/bin" >> $GITHUB_PATH + + echo "$PWD/bin" >> $GITHUB_PATH + - name: Build (no Python) if: contains(matrix.python, 'NO') # BEGIN A @@ -193,7 +201,7 @@ jobs: - name: Setup Python (Python 3.8) if: contains(matrix.python, 'YES') - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.8' - name: Configure (Python 3.8) @@ -216,16 +224,17 @@ jobs: shell: bash env: PYTHONPATH: ${{ matrix.config }} + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0:log_path=asan.log.py38 run: | PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//') $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw - $PYTHON_EXECUTABLE -m pytest ../tests --cov=. + run_pytest ../tests --cov=. # END B - name: Setup Python (Python 3.9) if: contains(matrix.python, 'YES') - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.9' - name: Configure (Python 3.9) @@ -248,16 +257,17 @@ jobs: shell: bash env: PYTHONPATH: ${{ matrix.config }} + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0:log_path=asan.log.py39 run: | PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//') $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw - $PYTHON_EXECUTABLE -m pytest ../tests --cov=. + run_pytest ../tests --cov=. # END B - name: Setup Python (Python 3.10) if: contains(matrix.python, 'YES') - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.10' - name: Configure (Python 3.10) @@ -280,16 +290,17 @@ jobs: shell: bash env: PYTHONPATH: ${{ matrix.config }} + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0:log_path=asan.log.py310 run: | PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//') $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw - $PYTHON_EXECUTABLE -m pytest ../tests --cov=. + run_pytest ../tests --cov=. # END B - name: Setup Python (Python 3.11) if: contains(matrix.python, 'YES') - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.11' - name: Configure (Python 3.11) @@ -312,16 +323,17 @@ jobs: shell: bash env: PYTHONPATH: ${{ matrix.config }} + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0:log_path=asan.log.py311 run: | PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//') $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw - $PYTHON_EXECUTABLE -m pytest ../tests --cov=. + run_pytest ../tests --cov=. # END B - name: Setup Python (Python 3.12) if: contains(matrix.python, 'YES') - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.12' - name: Configure (Python 3.12) @@ -344,13 +356,26 @@ jobs: shell: bash env: PYTHONPATH: ${{ matrix.config }} + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0:log_path=asan.log.py312 run: | PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//') $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw - $PYTHON_EXECUTABLE -m pytest ../tests --cov=. + run_pytest ../tests --cov=. # END B + - name: Show ASan errors + if: failure() && contains(matrix.asan, 'YES') + working-directory: build + run: | + ls -lAh asan.log.* || true + if cat asan.log.* 2>/dev/null | grep -q .; then + echo '### Address Sanitizer Report' >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat asan.log.* >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "No ASan errors found" + echo '```' >> $GITHUB_STEP_SUMMARY + fi + - name: Upload coverage reports if: always() && matrix.config == 'Coverage' working-directory: build @@ -364,6 +389,13 @@ jobs: python -m pip install coverage python -m coverage combine $(find . -name '.coverage.*') + TOTAL=$(python -m coverage report --format=total) + echo '### Coverage Report' >> $GITHUB_STEP_SUMMARY + echo "
Coverage: ${TOTAL}%" >> $GITHUB_STEP_SUMMARY + echo '' >> $GITHUB_STEP_SUMMARY + python -m coverage report --format=markdown >> $GITHUB_STEP_SUMMARY + echo '
' >> $GITHUB_STEP_SUMMARY + llvm-profdata merge pid-*.profraw -o coverage.profdata llvm-cov show $(grep -Rl LLVM_PROFILE_FILE . | sed 's/^/-object /') -instr-profile=coverage.profdata > coverage.txt bash <(curl -s https://codecov.io/bash) -y ../.github/codecov.yml @@ -372,12 +404,12 @@ jobs: if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')" strategy: matrix: - os: [ubuntu-20.04, windows-2019, macOS-13] + os: [ubuntu-22.04, windows-2022, macOS-15] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install dependencies (Ubuntu) - if: matrix.os == 'ubuntu-20.04' + if: matrix.os == 'ubuntu-22.04' run: | sudo apt-get update sudo apt-get install build-essential bison flex libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev @@ -386,30 +418,30 @@ jobs: shell: powershell run: | $wc = New-Object System.Net.WebClient - $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.15/panda3d-1.10.15-tools-win64.zip", "thirdparty-tools.zip") + $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.16/panda3d-1.10.16-tools-win64.zip", "thirdparty-tools.zip") Expand-Archive -Path thirdparty-tools.zip - Move-Item -Path thirdparty-tools/panda3d-1.10.15/thirdparty -Destination . + Move-Item -Path thirdparty-tools/panda3d-1.10.16/thirdparty -Destination . - name: Get thirdparty packages (macOS) if: runner.os == 'macOS' run: | - curl -O https://www.panda3d.org/download/panda3d-1.10.15/panda3d-1.10.15-tools-mac.tar.gz - tar -xf panda3d-1.10.15-tools-mac.tar.gz - mv panda3d-1.10.15/thirdparty thirdparty - rmdir panda3d-1.10.15 + curl -O https://www.panda3d.org/download/panda3d-1.10.16/panda3d-1.10.16-tools-mac.tar.gz + tar -xf panda3d-1.10.16-tools-mac.tar.gz + mv panda3d-1.10.16/thirdparty thirdparty + rmdir panda3d-1.10.16 (cd thirdparty/darwin-libs-a && rm -rf rocket) - name: Set up XCode (macOS) if: runner.os == 'macOS' - run: sudo xcode-select -s /Applications/Xcode_14.3.1.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.app/Contents/Developer - name: Set up Python 3.13 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.13' - name: Build Python 3.13 shell: bash run: | - python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 --msvc-version=14.2 + python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 --msvc-version=14.3 - name: Test Python 3.13 shell: bash run: | @@ -417,13 +449,13 @@ jobs: PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest - name: Set up Python 3.12 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.12' - name: Build Python 3.12 (double) shell: bash run: | - python makepanda/makepanda.py --override STDFLOAT_DOUBLE=1 --git-commit=${{github.sha}} --outputdir=built_dbl --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 --msvc-version=14.2 + python makepanda/makepanda.py --override STDFLOAT_DOUBLE=1 --git-commit=${{github.sha}} --outputdir=built_dbl --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 --msvc-version=14.3 - name: Test Python 3.12 (double) shell: bash run: | @@ -435,13 +467,13 @@ jobs: python makepanda/makepackage.py --verbose --lzma --outputdir=built_dbl - name: Set up Python 3.11 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.11' - name: Build Python 3.11 shell: bash run: | - python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 --msvc-version=14.2 + python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 --msvc-version=14.3 - name: Test Python 3.11 shell: bash run: | @@ -449,13 +481,13 @@ jobs: PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest - name: Set up Python 3.10 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.10' - name: Build Python 3.10 shell: bash run: | - python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 --msvc-version=14.2 + python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 --msvc-version=14.3 - name: Test Python 3.10 shell: bash run: | @@ -463,13 +495,13 @@ jobs: PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest - name: Set up Python 3.9 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.9' - name: Build Python 3.9 shell: bash run: | - python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 --msvc-version=14.2 + python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 --msvc-version=14.3 - name: Test Python 3.9 shell: bash run: | @@ -477,13 +509,13 @@ jobs: PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest - name: Set up Python 3.8 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.8' - name: Build Python 3.8 shell: bash run: | - python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 --msvc-version=14.2 + python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 --msvc-version=14.3 - name: Test Python 3.8 shell: bash run: | @@ -498,7 +530,7 @@ jobs: if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install dependencies run: | @@ -508,32 +540,32 @@ jobs: - name: Setup emsdk uses: mymindstorm/setup-emsdk@v14 with: - version: 3.1.70 + version: 4.0.2 actions-cache-folder: 'emsdk-cache' - name: Restore Python build cache id: cache-emscripten-python-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: ~/python - key: cache-emscripten-python-3.12.7 + key: cache-emscripten-python-3.12.8 - name: Build Python 3.12 if: steps.cache-emscripten-python-restore.outputs.cache-hit != 'true' run: | - wget https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tar.xz - tar -xJf Python-3.12.7.tar.xz - (cd Python-3.12.7 && EM_CONFIG=$EMSDK/.emscripten python3 Tools/wasm/wasm_build.py emscripten-browser) - (cd Python-3.12.7/builddir/emscripten-browser && make install DESTDIR=~/python) - cp Python-3.12.7/builddir/emscripten-browser/Modules/_hacl/libHacl_Hash_SHA2.a ~/python/usr/local/lib - cp Python-3.12.7/builddir/emscripten-browser/Modules/_decimal/libmpdec/libmpdec.a ~/python/usr/local/lib - cp Python-3.12.7/builddir/emscripten-browser/Modules/expat/libexpat.a ~/python/usr/local/lib - rm -rf Python-3.12.7 + wget https://www.python.org/ftp/python/3.12.8/Python-3.12.8.tar.xz + tar -xJf Python-3.12.8.tar.xz + (cd Python-3.12.8 && EM_CONFIG=$EMSDK/.emscripten python3 Tools/wasm/wasm_build.py emscripten-browser) + (cd Python-3.12.8/builddir/emscripten-browser && make install DESTDIR=~/python) + cp Python-3.12.8/builddir/emscripten-browser/Modules/_hacl/libHacl_Hash_SHA2.a ~/python/usr/local/lib + cp Python-3.12.8/builddir/emscripten-browser/Modules/_decimal/libmpdec/libmpdec.a ~/python/usr/local/lib + cp Python-3.12.8/builddir/emscripten-browser/Modules/expat/libexpat.a ~/python/usr/local/lib + rm -rf Python-3.12.8 - name: Save Python build cache id: cache-emscripten-python-save if: steps.cache-emscripten-python-restore.outputs.cache-hit != 'true' - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 with: path: ~/python key: ${{ steps.cache-emscripten-python-restore.outputs.cache-primary-key }} @@ -552,3 +584,156 @@ jobs: shell: bash run: | PYTHONHOME=~/python/usr/local PYTHONPATH=built node built/bin/run_tests.js tests + + android: + if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')" + + strategy: + fail-fast: false + + matrix: + include: + - android-abi: arm64-v8a + android-arch: arm64 + android-triple: aarch64-linux-android + ndk-version: 29.0.14206865 + runner: ubuntu-latest + + - android-abi: armeabi-v7a + android-arch: arm + android-triple: arm-linux-androideabi + ndk-version: 29.0.14206865 + runner: ubuntu-latest + + - android-abi: x86_64 + android-arch: x86_64 + android-triple: x86_64-linux-android + ndk-version: 29.0.14206865 + runner: ubuntu-latest + + - android-abi: x86 + android-arch: x86 + android-triple: i686-linux-android + ndk-version: 29.0.14206865 + runner: ubuntu-latest + + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v6 + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + with: + packages: 'tools platform-tools platforms;android-21 ndk;${{ matrix.ndk-version }}' + + - name: Set up Android NDK + run: | + echo "ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/${{ matrix.ndk-version }}" >> $GITHUB_ENV + + - uses: actions/setup-python@v6 + with: + python-version: '3.13' + + - name: Restore Python build cache + id: cache-android-python-restore + uses: actions/cache/restore@v5 + with: + path: ~/python-prefix + key: cache-android-python-3.13.9-${{ matrix.android-abi }}-ndk${{ matrix.ndk-version }} + + - name: Build Python 3.13 + if: steps.cache-android-python-restore.outputs.cache-hit != 'true' + run: | + wget https://www.python.org/ftp/python/3.13.9/Python-3.13.9.tar.xz + tar -xJf Python-3.13.9.tar.xz + sed -i.bak "s/aarch64-linux-android/${{ matrix.android-triple }}/" Python-3.13.9/Android/android.py + sed -i.bak "s/ndk_version=[0-9.]\{1,\}/ndk_version=${{ matrix.ndk-version }}/" Python-3.13.9/Android/android-env.sh + (cd Python-3.13.9/Android && python3 android.py build ${{ matrix.android-triple }}) + cp -R Python-3.13.9/cross-build/${{ matrix.android-triple }}/prefix ~/python-prefix + rm -rf Python-3.13.9 + + - name: Save Python build cache + id: cache-android-python-save + if: steps.cache-android-python-restore.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: ~/python-prefix + key: ${{ steps.cache-android-python-restore.outputs.cache-primary-key }} + + - name: Restore thirdparty build cache + id: cache-android-thirdparty-restore + uses: actions/cache/restore@v5 + with: + path: ./thirdparty + key: cache-android-thirdparty-935c80380ca171e08587c570e9dad678d29db3c8-${{ matrix.android-abi }}-ndk${{ matrix.ndk-version }} + + - name: Install yasm + if: steps.cache-android-thirdparty-restore.outputs.cache-hit != 'true' + run: ${{runner.os == 'macOS' && 'brew' || 'sudo apt-get'}} install yasm + + - name: Build thirdparty packages + if: steps.cache-android-thirdparty-restore.outputs.cache-hit != 'true' + run: | + git clone --revision=935c80380ca171e08587c570e9dad678d29db3c8 --depth=1 https://github.com/rdb/panda3d-thirdparty.git thirdparty + cd thirdparty + cmake -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake \ + -DCMAKE_ANDROID_ARCH_ABI=${{ matrix.android-abi }} \ + -DCMAKE_ANDROID_ARCH=${{ matrix.android-arch }} \ + -DCMAKE_SYSTEM_VERSION=21 \ + -DANDROID_ABI=${{ matrix.android-abi }} \ + -DANDROID_PLATFORM=android-21 \ + -DBUILD_FCOLLADA=OFF \ + -DBUILD_OPENSSL=OFF \ + -DBUILD_VRPN=OFF \ + -DBUILD_ARTOOLKIT=OFF + cmake --build build --config Release -j4 + rm -rf build + + - name: Save thirdparty build cache + id: cache-android-thirdparty-save + if: steps.cache-android-thirdparty-restore.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: ./thirdparty + key: ${{ steps.cache-android-thirdparty-restore.outputs.cache-primary-key }} + + - name: Build for Android + shell: bash + run: | + python makepanda/makepanda.py --git-commit=${{github.sha}} --target android-21 --python-incdir=~/python-prefix/include --python-libdir=~/python-prefix/lib --arch ${{ matrix.android-arch }} --everything --no-openssl --no-tinydisplay --no-pandatool --verbose --threads=4 + + - name: Install test dependencies + shell: bash + run: | + python -m pip install -t ~/python-prefix/lib/python3.13/site-packages -r requirements-test.txt + + - name: Enable KVM group perms + if: startsWith(matrix.runner, 'ubuntu') + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Setup Android Emulator and Run Tests + if: startsWith(matrix.android-abi, 'x86') + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 27 + arch: ${{ matrix.android-abi }} + target: default + profile: pixel_6 + script: | + adb shell "rm -rf /data/local/tmp/panda3d-tests" + adb push built/bin/run_tests /data/local/tmp/panda3d-tests/run_tests + adb shell "mkdir /data/local/tmp/panda3d-tests/lib" + adb push built/lib/*.so /data/local/tmp/panda3d-tests/lib + adb push ~/python-prefix/lib/*.so /data/local/tmp/panda3d-tests/lib + adb push ~/python-prefix/lib/python3.13 /data/local/tmp/panda3d-tests/lib/python3.13 + adb push built/panda3d /data/local/tmp/panda3d-tests/lib/python3.13/site-packages/ + adb push built/direct /data/local/tmp/panda3d-tests/lib/python3.13/site-packages/ + adb push built/models /data/local/tmp/panda3d-tests/models + adb push built/etc /data/local/tmp/panda3d-tests/etc + adb push tests /data/local/tmp/panda3d-tests/tests + adb shell "cd /data/local/tmp/panda3d-tests && LD_LIBRARY_PATH=/data/local/tmp/panda3d-tests/lib PYTHONPATH=/data/local/tmp/panda3d-tests/lib/python3.13/site-packages ./run_tests -v tests" diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 6e70577204..83a9d8138f 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -7,17 +7,17 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.8', '3.11'] + python-version: ['3.9', '3.13'] fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip - pip install mypy==1.4.0 + pip install mypy==1.14.1 - name: Run mypy on direct run: python tests/run_mypy.py diff --git a/BACKERS.md b/BACKERS.md index 2b48940fc3..269e617d67 100644 --- a/BACKERS.md +++ b/BACKERS.md @@ -5,10 +5,12 @@ This is a list of all the people who are contributing financially to Panda3D. I ## Bronze Sponsors [Route4Me](https://route4me.com/) +[TestMu AI](https://www.testmu.ai/?utm_source=panda3d&utm_medium=sponsor) * [Daniel Stokes](https://opencollective.com/daniel-stokes) * [David Rose](https://opencollective.com/david-rose) * [Route4Me](https://route4me.com/) +* [TestMu AI](https://www.testmu.ai/?utm_source=panda3d&utm_medium=sponsor) ## Benefactors diff --git a/CMakeLists.txt b/CMakeLists.txt index fba23f92d6..6a3362badb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,7 @@ option(BUILD_DIRECT "Build the direct source tree." ON) option(BUILD_PANDATOOL "Build the pandatool source tree." ON) option(BUILD_CONTRIB "Build the contrib source tree." ON) option(BUILD_MODELS "Build/install the built-in models." ON) +option(BUILD_TESTS "Build unit test runner." ON) option(BUILD_TOOLS "Build binary tools." ON) # Include Panda3D packages @@ -166,17 +167,8 @@ if(BUILD_MODELS) COMPONENT Models DESTINATION ${CMAKE_INSTALL_DATADIR}/panda3d) endif() -if(INTERROGATE_PYTHON_INTERFACE) - # If we built the Python interface, run the test suite. Note, we do NOT test - # for pytest before adding this test. If the user doesn't have pytest, we'd - # like for the tests to fail. - - # In the Coverage configuration, we also require pytest-cov - - add_test(NAME pytest - COMMAND "${PYTHON_EXECUTABLE}" -m pytest "${PROJECT_SOURCE_DIR}/tests" - $<$:--cov=.> - WORKING_DIRECTORY "${PANDA_OUTPUT_DIR}") +if(BUILD_TESTS) + add_subdirectory(tests "${CMAKE_BINARY_DIR}/tests") endif() # Generate the Panda3DConfig.cmake file so find_package(Panda3D) works, and diff --git a/README.md b/README.md index 6e4e78784b..cbfd7fc84d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Installing Panda3D ================== The latest Panda3D SDK can be downloaded from -[this page](https://www.panda3d.org/download/sdk-1-10-15/). +[this page](https://www.panda3d.org/download/sdk-1-10-16/). If you are familiar with installing Python packages, you can use the following command: @@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on building them from source. -- https://www.panda3d.org/download/panda3d-1.10.15/panda3d-1.10.15-tools-win64.zip -- https://www.panda3d.org/download/panda3d-1.10.15/panda3d-1.10.15-tools-win32.zip +- https://www.panda3d.org/download/panda3d-1.10.16/panda3d-1.10.16-tools-win64.zip +- https://www.panda3d.org/download/panda3d-1.10.16/panda3d-1.10.16-tools-win32.zip After acquiring these dependencies, you can build Panda3D from the command prompt using the following command. Change the `--msvc-version` option based @@ -136,7 +136,7 @@ macOS ----- On macOS, you will need to download a set of precompiled thirdparty packages in order to -compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.15/panda3d-1.10.15-tools-mac.tar.gz). +compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.16/panda3d-1.10.16-tools-mac.tar.gz). After placing the thirdparty directory inside the panda3d source directory, you may build Panda3D using a command like the following: @@ -181,22 +181,21 @@ Although it's possible to build Panda3D on an Android device using the [termux](https://termux.com/) shell, the recommended route is to cross-compile .whl files using the SDK and NDK, which can then be used by the `build_apps` command to build a Python application into an .apk or .aab bundle. You will -need to get the latest thirdparty packages, which can be obtained from the -artifacts page of the last successful run here: +need to get the latest thirdparty packages, which can be obtained from here: -https://github.com/rdb/panda3d-thirdparty/actions?query=branch%3Amain+is%3Asuccess+event%3Apush +https://rdb.name/thirdparty-android.tar.gz -This does not include Python at the moment, which can be extracted from -[this archive](https://rdb.name/thirdparty-android.tar.gz) instead. +This includes a copy of Python 3.13 compiled for Android. You will need to +use Python 3.13 on the host as well. These commands show how to compile wheels for the supported Android ABIs: ```bash export ANDROID_SDK_ROOT=/home/rdb/local/android -python3.8 makepanda/makepanda.py --everything --outputdir built-droid-arm64 --arch arm64 --target android-21 --threads 6 --wheel -python3.8 makepanda/makepanda.py --everything --outputdir built-droid-armv7a --arch armv7a --target android-19 --threads 6 --wheel -python3.8 makepanda/makepanda.py --everything --outputdir built-droid-x86_64 --arch x86_64 --target android-21 --threads 6 --wheel -python3.8 makepanda/makepanda.py --everything --outputdir built-droid-x86 --arch x86 --target android-19 --threads 6 --wheel +python3.13 makepanda/makepanda.py --everything --outputdir built-droid-arm64 --arch arm64 --target android-21 --threads 6 --wheel +python3.13 makepanda/makepanda.py --everything --outputdir built-droid-armv7a --arch arm --target android-21 --threads 6 --wheel +python3.13 makepanda/makepanda.py --everything --outputdir built-droid-x86_64 --arch x86_64 --target android-21 --threads 6 --wheel +python3.13 makepanda/makepanda.py --everything --outputdir built-droid-x86 --arch x86 --target android-21 --threads 6 --wheel ``` It is now possible to use the generated wheels with `build_apps`, as explained @@ -229,8 +228,8 @@ information as possible to help the developers track down the issue, such as your version of Panda3D, operating system, architecture, and any code and models that are necessary for the developers to reproduce the issue. -If you're not sure whether you've encountered a bug, feel free to ask about -it in the forums or the IRC channel first. +If you're unsure whether you've encountered a bug, feel free to ask in the [forums](https://discourse.panda3d.org) or the [IRC channel](https://web.libera.chat/#panda3d) before opening an issue. + Supporting the Project ====================== @@ -239,9 +238,12 @@ If you would like to support the project financially, visit [our campaign on OpenCollective](https://opencollective.com/panda3d). Your contributions help us accelerate the development of Panda3D. -For the list of backers, see the [BACKERS.md](BACKERS.md) file or visit the -[Sponsors page](https://www.panda3d.org/sponsors) on our web site. Thank you -to everyone who has donated! +For the complete list of backers, see the [BACKERS.md](BACKERS.md) file or +visit the [Sponsors page](https://www.panda3d.org/sponsors) on our web site. +Thank you to everyone who has donated! + +[Route4Me](https://route4me.com/) +[TestMu AI](https://www.testmu.ai/?utm_source=panda3d&utm_medium=sponsor) diff --git a/cmake/macros/Interrogate.cmake b/cmake/macros/Interrogate.cmake index 5f26cebbe9..7533be7564 100644 --- a/cmake/macros/Interrogate.cmake +++ b/cmake/macros/Interrogate.cmake @@ -283,7 +283,7 @@ endfunction(interrogate_sources) # # Function: add_python_module(module [lib1 [lib2 ...]] [LINK lib1 ...] -# [IMPORT mod1 ...]) +# [IMPORT mod1 ...] [INIT func1 ...]) # Uses interrogate to create a Python module. If the LINK keyword is specified, # the Python module is linked against the specified libraries instead of those # listed before. The IMPORT keyword makes the output module import another @@ -305,7 +305,7 @@ function(add_python_module module) set(keyword) foreach(arg ${ARGN}) - if(arg STREQUAL "LINK" OR arg STREQUAL "IMPORT" OR arg STREQUAL "COMPONENT") + if(arg STREQUAL "LINK" OR arg STREQUAL "IMPORT" OR arg STREQUAL "INIT" OR arg STREQUAL "COMPONENT") set(keyword "${arg}") elseif(keyword STREQUAL "LINK") @@ -316,6 +316,10 @@ function(add_python_module module) list(APPEND import_flags "-import" "${arg}") set(keyword) + elseif(keyword STREQUAL "INIT") + list(APPEND import_flags "-init" "${arg}") + set(keyword) + elseif(keyword STREQUAL "COMPONENT") set(component "${arg}") set(keyword) diff --git a/contrib/src/sceneeditor/seParticlePanel.py b/contrib/src/sceneeditor/seParticlePanel.py index 6ea49cc3b1..3791c62381 100644 --- a/contrib/src/sceneeditor/seParticlePanel.py +++ b/contrib/src/sceneeditor/seParticlePanel.py @@ -1,6 +1,7 @@ """PANDA3D Particle Panel""" # Import Tkinter, Pmw, and the floater code from this directory tree. +from panda3d.core import ConfigVariableSearchPath, Point2, Point3, Vec3, Vec4 from direct.tkwidgets.AppShell import AppShell import os, Pmw from direct.tkwidgets.Dial import AngleDial @@ -17,6 +18,10 @@ from tkinter.filedialog import * from tkinter.simpledialog import askstring +particlePath = ConfigVariableSearchPath("particle-path", + "The directories to search for particle files to be loaded.") + + class ParticlePanel(AppShell): # Override class variables appname = 'Particle Panel' @@ -1041,7 +1046,7 @@ class ParticlePanel(AppShell): def loadParticleEffectFromFile(self): # Find path to particle directory - pPath = getParticlePath() + pPath = particlePath if pPath.getNumDirectories() > 0: if repr(pPath.getDirectory(0)) == '.': path = '.' @@ -1070,7 +1075,7 @@ class ParticlePanel(AppShell): def saveParticleEffectToFile(self): # Find path to particle directory - pPath = getParticlePath() + pPath = particlePath if pPath.getNumDirectories() > 0: if repr(pPath.getDirectory(0)) == '.': path = '.' diff --git a/direct/src/dcparser/dcClass_ext.cxx b/direct/src/dcparser/dcClass_ext.cxx index c7135cfabb..0fc2ba3c14 100644 --- a/direct/src/dcparser/dcClass_ext.cxx +++ b/direct/src/dcparser/dcClass_ext.cxx @@ -113,7 +113,7 @@ receive_update(PyObject *distobj, DatagramIterator &di) const { int field_id = packer.raw_unpack_uint16(); DCField *field = _this->get_field_by_index(field_id); if (field == nullptr) { - ostringstream strm; + std::ostringstream strm; strm << "Received update for field " << field_id << ", not in class " << _this->get_name(); diff --git a/direct/src/directnotify/DirectNotify.py b/direct/src/directnotify/DirectNotify.py index f7c7e6c964..8b93088362 100644 --- a/direct/src/directnotify/DirectNotify.py +++ b/direct/src/directnotify/DirectNotify.py @@ -42,25 +42,25 @@ class DirectNotify: """ return list(self.__categories.keys()) - def getCategory(self, categoryName: str) -> Notifier.Notifier | None: + def getCategory(self, name: str) -> Notifier.Notifier | None: """getCategory(self, string) Return the category with given name if present, None otherwise """ - return self.__categories.get(categoryName, None) + return self.__categories.get(name, None) - def newCategory(self, categoryName: str, logger: Logger.Logger | None = None) -> Notifier.Notifier: + def newCategory(self, name: str, logger: Logger.Logger | None = None) -> Notifier.Notifier: """newCategory(self, string) - Make a new notify category named categoryName. Return new category + Make a new notify category named name. Return new category if no such category exists, else return existing category """ - if categoryName not in self.__categories: - self.__categories[categoryName] = Notifier.Notifier(categoryName, logger) - self.setDconfigLevel(categoryName) - notifier = self.getCategory(categoryName) + if name not in self.__categories: + self.__categories[name] = Notifier.Notifier(name, logger) + self.setDconfigLevel(name) + notifier = self.getCategory(name) assert notifier is not None return notifier - def setDconfigLevel(self, categoryName: str) -> None: + def setDconfigLevel(self, name: str) -> None: """ Check to see if this category has a dconfig variable to set the notify severity and then set that level. You cannot @@ -71,7 +71,7 @@ class DirectNotify: # we're running before ShowBase has finished initializing from panda3d.core import ConfigVariableString - dconfigParam = ("notify-level-" + categoryName) + dconfigParam = ("notify-level-" + name) cvar = ConfigVariableString(dconfigParam, "") level = cvar.getValue() @@ -82,11 +82,11 @@ class DirectNotify: if not level: level = 'error' - category = self.getCategory(categoryName) - assert category is not None, f'failed to find category: {categoryName!r}' + category = self.getCategory(name) + assert category is not None, f'failed to find category: {name!r}' # Note - this print statement is making it difficult to # achieve "no output unless there's an error" operation - Josh - # print ("Setting DirectNotify category: " + categoryName + + # print ("Setting DirectNotify category: " + name + # " to severity: " + level) if level == "error": category.setWarning(False) @@ -106,7 +106,7 @@ class DirectNotify: category.setDebug(True) else: print("DirectNotify: unknown notify level: " + str(level) - + " for category: " + str(categoryName)) + + " for category: " + str(name)) def setDconfigLevels(self) -> None: for categoryName in self.getCategories(): @@ -129,3 +129,12 @@ class DirectNotify: def giveNotify(self, cls) -> None: cls.notify = self.newCategory(cls.__name__) + + get_categories = getCategories + get_category = getCategory + new_category = newCategory + set_dconfig_level = setDconfigLevel + set_dconfig_levels = setDconfigLevels + set_verbose = setVerbose + popup_controls = popupControls + give_notify = giveNotify diff --git a/direct/src/directtools/DirectGeometry.py b/direct/src/directtools/DirectGeometry.py index 141d263c40..86ee2646b2 100644 --- a/direct/src/directtools/DirectGeometry.py +++ b/direct/src/directtools/DirectGeometry.py @@ -79,8 +79,8 @@ class LineNodePath(NodePath): def getVertex(self, index): return self.lineSegs.getVertex(index) - def getVertexColor(self): - return self.lineSegs.getVertexColor() + def getVertexColor(self, index): + return self.lineSegs.getVertexColor(index) def drawArrow(self, sv, ev, arrowAngle, arrowLength): """ diff --git a/direct/src/directtools/DirectGrid.py b/direct/src/directtools/DirectGrid.py index 33c6ac58dc..b1c578d692 100644 --- a/direct/src/directtools/DirectGrid.py +++ b/direct/src/directtools/DirectGrid.py @@ -33,7 +33,7 @@ class DirectGrid(NodePath, DirectObject): self.centerLines = LineNodePath(self.lines) self.centerLines.lineNode.setName('centerLines') - self.centerLines.setColor(VBase4(1, 0, 0, 0)) + self.centerLines.setColor(VBase4(1, 0, 0, 1)) self.centerLines.setThickness(3) # Small marker to hilight snap-to-grid point diff --git a/direct/src/directtools/DirectUtil.py b/direct/src/directtools/DirectUtil.py index 134832973d..7017f7b44a 100644 --- a/direct/src/directtools/DirectUtil.py +++ b/direct/src/directtools/DirectUtil.py @@ -1,4 +1,4 @@ -from panda3d.core import VBase4 +from panda3d.core import NodePath, VBase4 from direct.task.Task import Task from direct.task.TaskManagerGlobal import taskMgr @@ -55,7 +55,7 @@ def lerpBackgroundColor(r, g, b, duration): # Set direct drawing style for an object # Never light object or draw in wireframe -def useDirectRenderStyle(nodePath, priority = 0): +def useDirectRenderStyle(nodePath: NodePath, priority: int = 0) -> None: """ Function to force a node path to use direct render style: no lighting, and no wireframe diff --git a/direct/src/dist/FreezeTool.py b/direct/src/dist/FreezeTool.py index aad6dc4b24..a883ec5fa2 100644 --- a/direct/src/dist/FreezeTool.py +++ b/direct/src/dist/FreezeTool.py @@ -88,6 +88,7 @@ defaultHiddenImports = { 'numpy.core._dtype_ctypes', 'numpy.core._methods', ], + 'panda3d.core': ['enum'], 'pandas.compat': ['lzma', 'cmath'], 'pandas._libs.tslibs.conversion': ['pandas._libs.tslibs.base'], 'plyer': ['plyer.platforms'], @@ -101,6 +102,7 @@ defaultHiddenImports = { 'scipy.stats._stats': ['scipy.special.cython_special'], 'setuptools.monkey': ['setuptools.msvc'], 'shapely._geometry_helpers': ['shapely._geos'], + 'jnius': ['jnius_config'], } @@ -113,7 +115,7 @@ ignoreImports = { 'toml.encoder': ['numpy'], 'py._builtin': ['__builtin__'], - 'site': ['android_log'], + 'site': ['android_support'], } if sys.version_info >= (3, 8): @@ -138,12 +140,23 @@ def getline(filename, lineno, module_globals=None): return '' def clearcache(): - global cache - cache = {} + cache.clear() def getlines(filename, module_globals=None): return [] +def _getline_from_code(filename, lineno): + return '' + +def _make_key(code): + return (code.co_filename, code.co_qualname, code.co_firstlineno) + +def _getlines_from_code(code): + return [] + +def _source_unavailable(filename): + return True + def checkcache(filename=None): pass @@ -1236,8 +1249,12 @@ class Freezer: pass # Check if any new modules we found have "hidden" imports - for origName in list(self.mf.modules.keys()): + checkHiddenImports = set(self.mf.modules.keys()) + while checkHiddenImports: + origName = next(iter(checkHiddenImports)) + checkHiddenImports.discard(origName) hidden = self.hiddenImports.get(origName, []) + preModules = frozenset(self.mf.modules.keys()) for modname in hidden: if modname.endswith('.*'): mdefs = self._gatherSubmodules(modname, implicit = True) @@ -1251,6 +1268,9 @@ class Freezer: self.__loadModule(self.ModuleDef(modname, implicit = True)) except ImportError: pass + addedModules = set(self.mf.modules.keys()) + addedModules -= preModules + checkHiddenImports |= addedModules # Special case for sysconfig, which depends on a platform-specific # sysconfigdata module on POSIX systems. @@ -1834,7 +1854,8 @@ class Freezer: return target def generateRuntimeFromStub(self, target, stub_file, use_console, fields={}, - log_append=False, log_filename_strftime=False): + log_append=False, log_filename_strftime=False, + blob_path=None): self.__replacePaths() # We must have a __main__ module to make an exe file. @@ -1969,6 +1990,9 @@ class Freezer: elif self.platform.endswith('_aarch64') or self.platform.endswith('_arm64'): # Most arm64 operating systems are configured with 16 KiB pages. blob_align = 16384 + elif self.platform.replace('-', '_') == 'android_x86_64': + # Android nowadays requires 16 KiB pages on 64-bit Intel as well. + blob_align = 16384 else: # Align to page size, so that it can be mmapped. blob_align = 4096 @@ -1979,29 +2003,33 @@ class Freezer: pad = (blob_align - (blob_size & (blob_align - 1))) blob_size += pad - # TODO: Support creating custom sections in universal binaries. - append_blob = True - if self.platform.startswith('macosx') and len(bitnesses) == 1: - # If our deploy-stub has a __PANDA segment, we know we're meant to - # put our blob there rather than attach it to the end. - load_commands = self._parse_macho_load_commands(stub_data) - if b'__PANDA' in load_commands.keys(): - append_blob = False - - if self.platform.startswith("macosx") and not append_blob: - # Take this time to shift any Mach-O structures around to fit our - # blob. We don't need to worry about aligning the offset since the - # compiler already took care of that when creating the segment. - blob_offset = self._shift_macho_structures(stub_data, load_commands, blob_size) + if blob_path is not None: + # We'll be writing the blob to a separate location. + blob_offset = 0 else: - # Add padding before the blob if necessary. - blob_offset = len(stub_data) - if (blob_offset & (blob_align - 1)) != 0: - pad = (blob_align - (blob_offset & (blob_align - 1))) - stub_data += (b'\0' * pad) - blob_offset += pad - assert (blob_offset % blob_align) == 0 - assert blob_offset == len(stub_data) + # TODO: Support creating custom sections in universal binaries. + append_blob = True + if self.platform.startswith('macosx') and len(bitnesses) == 1: + # If our deploy-stub has a __PANDA segment, we know we're meant to + # put our blob there rather than attach it to the end. + load_commands = self._parse_macho_load_commands(stub_data) + if b'__PANDA' in load_commands.keys(): + append_blob = False + + if self.platform.startswith("macosx") and not append_blob: + # Take this time to shift any Mach-O structures around to fit our + # blob. We don't need to worry about aligning the offset since the + # compiler already took care of that when creating the segment. + blob_offset = self._shift_macho_structures(stub_data, load_commands, blob_size) + else: + # Add padding before the blob if necessary. + blob_offset = len(stub_data) + if (blob_offset & (blob_align - 1)) != 0: + pad = (blob_align - (blob_offset & (blob_align - 1))) + stub_data += (b'\0' * pad) + blob_offset += pad + assert (blob_offset % blob_align) == 0 + assert blob_offset == len(stub_data) # Calculate the offsets for the variables. These are pointers, # relative to the beginning of the blob. @@ -2087,7 +2115,9 @@ class Freezer: blob += struct.pack('\n\x08StagedId\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x11\n\tstaged_id\x18\x02 \x01(\r\"\x15\n\x07\x45ntryId\x12\n\n\x02id\x18\x01 \x01(\r\"\x8e\x02\n\x05\x45ntry\x12\"\n\x08\x65ntry_id\x18\x01 \x01(\x0b\x32\x10.aapt.pb.EntryId\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\'\n\nvisibility\x18\x03 \x01(\x0b\x32\x13.aapt.pb.Visibility\x12$\n\tallow_new\x18\x04 \x01(\x0b\x32\x11.aapt.pb.AllowNew\x12\x32\n\x10overlayable_item\x18\x05 \x01(\x0b\x32\x18.aapt.pb.OverlayableItem\x12*\n\x0c\x63onfig_value\x18\x06 \x03(\x0b\x32\x14.aapt.pb.ConfigValue\x12$\n\tstaged_id\x18\x07 \x01(\x0b\x32\x11.aapt.pb.StagedId\"T\n\x0b\x43onfigValue\x12&\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x16.aapt.pb.Configuration\x12\x1d\n\x05value\x18\x02 \x01(\x0b\x32\x0e.aapt.pb.Value\"\xa1\x01\n\x05Value\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12\x0c\n\x04weak\x18\x03 \x01(\x08\x12\x1d\n\x04item\x18\x04 \x01(\x0b\x32\r.aapt.pb.ItemH\x00\x12\x30\n\x0e\x63ompound_value\x18\x05 \x01(\x0b\x32\x16.aapt.pb.CompoundValueH\x00\x42\x07\n\x05value\"\x8d\x02\n\x04Item\x12!\n\x03ref\x18\x01 \x01(\x0b\x32\x12.aapt.pb.ReferenceH\x00\x12\x1e\n\x03str\x18\x02 \x01(\x0b\x32\x0f.aapt.pb.StringH\x00\x12%\n\x07raw_str\x18\x03 \x01(\x0b\x32\x12.aapt.pb.RawStringH\x00\x12+\n\nstyled_str\x18\x04 \x01(\x0b\x32\x15.aapt.pb.StyledStringH\x00\x12&\n\x04\x66ile\x18\x05 \x01(\x0b\x32\x16.aapt.pb.FileReferenceH\x00\x12\x19\n\x02id\x18\x06 \x01(\x0b\x32\x0b.aapt.pb.IdH\x00\x12\"\n\x04prim\x18\x07 \x01(\x0b\x32\x12.aapt.pb.PrimitiveH\x00\x42\x07\n\x05value\"\xef\x01\n\rCompoundValue\x12\"\n\x04\x61ttr\x18\x01 \x01(\x0b\x32\x12.aapt.pb.AttributeH\x00\x12\x1f\n\x05style\x18\x02 \x01(\x0b\x32\x0e.aapt.pb.StyleH\x00\x12\'\n\tstyleable\x18\x03 \x01(\x0b\x32\x12.aapt.pb.StyleableH\x00\x12\x1f\n\x05\x61rray\x18\x04 \x01(\x0b\x32\x0e.aapt.pb.ArrayH\x00\x12!\n\x06plural\x18\x05 \x01(\x0b\x32\x0f.aapt.pb.PluralH\x00\x12#\n\x05macro\x18\x06 \x01(\x0b\x32\x12.aapt.pb.MacroBodyH\x00\x42\x07\n\x05value\"\x18\n\x07\x42oolean\x12\r\n\x05value\x18\x01 \x01(\x08\"\xd0\x01\n\tReference\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.aapt.pb.Reference.Type\x12\n\n\x02id\x18\x02 \x01(\r\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0f\n\x07private\x18\x04 \x01(\x08\x12$\n\nis_dynamic\x18\x05 \x01(\x0b\x32\x10.aapt.pb.Boolean\x12\x12\n\ntype_flags\x18\x06 \x01(\r\x12\x11\n\tallow_raw\x18\x07 \x01(\x08\"$\n\x04Type\x12\r\n\tREFERENCE\x10\x00\x12\r\n\tATTRIBUTE\x10\x01\"\x04\n\x02Id\"\x17\n\x06String\x12\r\n\x05value\x18\x01 \x01(\t\"\x1a\n\tRawString\x12\r\n\x05value\x18\x01 \x01(\t\"\x83\x01\n\x0cStyledString\x12\r\n\x05value\x18\x01 \x01(\t\x12(\n\x04span\x18\x02 \x03(\x0b\x32\x1a.aapt.pb.StyledString.Span\x1a:\n\x04Span\x12\x0b\n\x03tag\x18\x01 \x01(\t\x12\x12\n\nfirst_char\x18\x02 \x01(\r\x12\x11\n\tlast_char\x18\x03 \x01(\r\"\x85\x01\n\rFileReference\x12\x0c\n\x04path\x18\x01 \x01(\t\x12)\n\x04type\x18\x02 \x01(\x0e\x32\x1b.aapt.pb.FileReference.Type\";\n\x04Type\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x07\n\x03PNG\x10\x01\x12\x0e\n\nBINARY_XML\x10\x02\x12\r\n\tPROTO_XML\x10\x03\"\x83\x04\n\tPrimitive\x12\x31\n\nnull_value\x18\x01 \x01(\x0b\x32\x1b.aapt.pb.Primitive.NullTypeH\x00\x12\x33\n\x0b\x65mpty_value\x18\x02 \x01(\x0b\x32\x1c.aapt.pb.Primitive.EmptyTypeH\x00\x12\x15\n\x0b\x66loat_value\x18\x03 \x01(\x02H\x00\x12\x19\n\x0f\x64imension_value\x18\r \x01(\rH\x00\x12\x18\n\x0e\x66raction_value\x18\x0e \x01(\rH\x00\x12\x1b\n\x11int_decimal_value\x18\x06 \x01(\x05H\x00\x12\x1f\n\x15int_hexadecimal_value\x18\x07 \x01(\rH\x00\x12\x17\n\rboolean_value\x18\x08 \x01(\x08H\x00\x12\x1b\n\x11\x63olor_argb8_value\x18\t \x01(\rH\x00\x12\x1a\n\x10\x63olor_rgb8_value\x18\n \x01(\rH\x00\x12\x1b\n\x11\x63olor_argb4_value\x18\x0b \x01(\rH\x00\x12\x1a\n\x10\x63olor_rgb4_value\x18\x0c \x01(\rH\x00\x12(\n\x1a\x64imension_value_deprecated\x18\x04 \x01(\x02\x42\x02\x18\x01H\x00\x12\'\n\x19\x66raction_value_deprecated\x18\x05 \x01(\x02\x42\x02\x18\x01H\x00\x1a\n\n\x08NullType\x1a\x0b\n\tEmptyTypeB\r\n\x0boneof_value\"\x90\x03\n\tAttribute\x12\x14\n\x0c\x66ormat_flags\x18\x01 \x01(\r\x12\x0f\n\x07min_int\x18\x02 \x01(\x05\x12\x0f\n\x07max_int\x18\x03 \x01(\x05\x12)\n\x06symbol\x18\x04 \x03(\x0b\x32\x19.aapt.pb.Attribute.Symbol\x1ay\n\x06Symbol\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12 \n\x04name\x18\x03 \x01(\x0b\x32\x12.aapt.pb.Reference\x12\r\n\x05value\x18\x04 \x01(\r\x12\x0c\n\x04type\x18\x05 \x01(\r\"\xa4\x01\n\x0b\x46ormatFlags\x12\x08\n\x04NONE\x10\x00\x12\t\n\x03\x41NY\x10\xff\xff\x03\x12\r\n\tREFERENCE\x10\x01\x12\n\n\x06STRING\x10\x02\x12\x0b\n\x07INTEGER\x10\x04\x12\x0b\n\x07\x42OOLEAN\x10\x08\x12\t\n\x05\x43OLOR\x10\x10\x12\t\n\x05\x46LOAT\x10 \x12\r\n\tDIMENSION\x10@\x12\r\n\x08\x46RACTION\x10\x80\x01\x12\n\n\x04\x45NUM\x10\x80\x80\x04\x12\x0b\n\x05\x46LAGS\x10\x80\x80\x08\"\xf1\x01\n\x05Style\x12\"\n\x06parent\x18\x01 \x01(\x0b\x32\x12.aapt.pb.Reference\x12&\n\rparent_source\x18\x02 \x01(\x0b\x32\x0f.aapt.pb.Source\x12#\n\x05\x65ntry\x18\x03 \x03(\x0b\x32\x14.aapt.pb.Style.Entry\x1aw\n\x05\x45ntry\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12\x1f\n\x03key\x18\x03 \x01(\x0b\x32\x12.aapt.pb.Reference\x12\x1b\n\x04item\x18\x04 \x01(\x0b\x32\r.aapt.pb.Item\"\x91\x01\n\tStyleable\x12\'\n\x05\x65ntry\x18\x01 \x03(\x0b\x32\x18.aapt.pb.Styleable.Entry\x1a[\n\x05\x45ntry\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12 \n\x04\x61ttr\x18\x03 \x01(\x0b\x32\x12.aapt.pb.Reference\"\x8a\x01\n\x05\x41rray\x12\'\n\x07\x65lement\x18\x01 \x03(\x0b\x32\x16.aapt.pb.Array.Element\x1aX\n\x07\x45lement\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12\x1b\n\x04item\x18\x03 \x01(\x0b\x32\r.aapt.pb.Item\"\xef\x01\n\x06Plural\x12$\n\x05\x65ntry\x18\x01 \x03(\x0b\x32\x15.aapt.pb.Plural.Entry\x1a|\n\x05\x45ntry\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12$\n\x05\x61rity\x18\x03 \x01(\x0e\x32\x15.aapt.pb.Plural.Arity\x12\x1b\n\x04item\x18\x04 \x01(\x0b\x32\r.aapt.pb.Item\"A\n\x05\x41rity\x12\x08\n\x04ZERO\x10\x00\x12\x07\n\x03ONE\x10\x01\x12\x07\n\x03TWO\x10\x02\x12\x07\n\x03\x46\x45W\x10\x03\x12\x08\n\x04MANY\x10\x04\x12\t\n\x05OTHER\x10\x05\"r\n\x07XmlNode\x12&\n\x07\x65lement\x18\x01 \x01(\x0b\x32\x13.aapt.pb.XmlElementH\x00\x12\x0e\n\x04text\x18\x02 \x01(\tH\x00\x12\'\n\x06source\x18\x03 \x01(\x0b\x32\x17.aapt.pb.SourcePositionB\x06\n\x04node\"\xb2\x01\n\nXmlElement\x12\x34\n\x15namespace_declaration\x18\x01 \x03(\x0b\x32\x15.aapt.pb.XmlNamespace\x12\x15\n\rnamespace_uri\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12(\n\tattribute\x18\x04 \x03(\x0b\x32\x15.aapt.pb.XmlAttribute\x12\x1f\n\x05\x63hild\x18\x05 \x03(\x0b\x32\x10.aapt.pb.XmlNode\"T\n\x0cXmlNamespace\x12\x0e\n\x06prefix\x18\x01 \x01(\t\x12\x0b\n\x03uri\x18\x02 \x01(\t\x12\'\n\x06source\x18\x03 \x01(\x0b\x32\x17.aapt.pb.SourcePosition\"\xa6\x01\n\x0cXmlAttribute\x12\x15\n\rnamespace_uri\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\t\x12\'\n\x06source\x18\x04 \x01(\x0b\x32\x17.aapt.pb.SourcePosition\x12\x13\n\x0bresource_id\x18\x05 \x01(\r\x12$\n\rcompiled_item\x18\x06 \x01(\x0b\x32\r.aapt.pb.Item\"\xe7\x01\n\tMacroBody\x12\x12\n\nraw_string\x18\x01 \x01(\t\x12*\n\x0cstyle_string\x18\x02 \x01(\x0b\x32\x14.aapt.pb.StyleString\x12?\n\x17untranslatable_sections\x18\x03 \x03(\x0b\x32\x1e.aapt.pb.UntranslatableSection\x12\x30\n\x0fnamespace_stack\x18\x04 \x03(\x0b\x32\x17.aapt.pb.NamespaceAlias\x12\'\n\x06source\x18\x05 \x01(\x0b\x32\x17.aapt.pb.SourcePosition\"J\n\x0eNamespaceAlias\x12\x0e\n\x06prefix\x18\x01 \x01(\t\x12\x14\n\x0cpackage_name\x18\x02 \x01(\t\x12\x12\n\nis_private\x18\x03 \x01(\x08\"\x82\x01\n\x0bStyleString\x12\x0b\n\x03str\x18\x01 \x01(\t\x12(\n\x05spans\x18\x02 \x03(\x0b\x32\x19.aapt.pb.StyleString.Span\x1a<\n\x04Span\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0bstart_index\x18\x02 \x01(\r\x12\x11\n\tend_index\x18\x03 \x01(\r\"?\n\x15UntranslatableSection\x12\x13\n\x0bstart_index\x18\x01 \x01(\x04\x12\x11\n\tend_index\x18\x02 \x01(\x04\x42\x12\n\x10\x63om.android.aaptb\x06proto3' - , - dependencies=[frameworks_dot_base_dot_tools_dot_aapt2_dot_Configuration__pb2.DESCRIPTOR,]) - - - -_VISIBILITY_LEVEL = _descriptor.EnumDescriptor( - name='Level', - full_name='aapt.pb.Visibility.Level', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='UNKNOWN', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='PRIVATE', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='PUBLIC', index=2, number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=869, - serialized_end=914, -) -_sym_db.RegisterEnumDescriptor(_VISIBILITY_LEVEL) - -_OVERLAYABLEITEM_POLICY = _descriptor.EnumDescriptor( - name='Policy', - full_name='aapt.pb.OverlayableItem.Policy', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='NONE', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='PUBLIC', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='SYSTEM', index=2, number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='VENDOR', index=3, number=3, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='PRODUCT', index=4, number=4, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='SIGNATURE', index=5, number=5, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ODM', index=6, number=6, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='OEM', index=7, number=7, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ACTOR', index=8, number=8, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='CONFIG_SIGNATURE', index=9, number=9, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=1200, - serialized_end=1333, -) -_sym_db.RegisterEnumDescriptor(_OVERLAYABLEITEM_POLICY) - -_REFERENCE_TYPE = _descriptor.EnumDescriptor( - name='Type', - full_name='aapt.pb.Reference.Type', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='REFERENCE', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ATTRIBUTE', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=2658, - serialized_end=2694, -) -_sym_db.RegisterEnumDescriptor(_REFERENCE_TYPE) - -_FILEREFERENCE_TYPE = _descriptor.EnumDescriptor( - name='Type', - full_name='aapt.pb.FileReference.Type', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='UNKNOWN', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='PNG', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='BINARY_XML', index=2, number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='PROTO_XML', index=3, number=3, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=2964, - serialized_end=3023, -) -_sym_db.RegisterEnumDescriptor(_FILEREFERENCE_TYPE) - -_ATTRIBUTE_FORMATFLAGS = _descriptor.EnumDescriptor( - name='FormatFlags', - full_name='aapt.pb.Attribute.FormatFlags', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='NONE', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ANY', index=1, number=65535, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='REFERENCE', index=2, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='STRING', index=3, number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='INTEGER', index=4, number=4, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='BOOLEAN', index=5, number=8, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='COLOR', index=6, number=16, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='FLOAT', index=7, number=32, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='DIMENSION', index=8, number=64, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='FRACTION', index=9, number=128, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ENUM', index=10, number=65536, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='FLAGS', index=11, number=131072, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=3780, - serialized_end=3944, -) -_sym_db.RegisterEnumDescriptor(_ATTRIBUTE_FORMATFLAGS) - -_PLURAL_ARITY = _descriptor.EnumDescriptor( - name='Arity', - full_name='aapt.pb.Plural.Arity', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='ZERO', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ONE', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='TWO', index=2, number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='FEW', index=3, number=3, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='MANY', index=4, number=4, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='OTHER', index=5, number=5, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=4654, - serialized_end=4719, -) -_sym_db.RegisterEnumDescriptor(_PLURAL_ARITY) - - -_STRINGPOOL = _descriptor.Descriptor( - name='StringPool', - full_name='aapt.pb.StringPool', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='data', full_name='aapt.pb.StringPool.data', index=0, - number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=b"", - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=105, - serialized_end=131, -) - - -_SOURCEPOSITION = _descriptor.Descriptor( - name='SourcePosition', - full_name='aapt.pb.SourcePosition', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='line_number', full_name='aapt.pb.SourcePosition.line_number', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='column_number', full_name='aapt.pb.SourcePosition.column_number', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=133, - serialized_end=193, -) - - -_SOURCE = _descriptor.Descriptor( - name='Source', - full_name='aapt.pb.Source', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='path_idx', full_name='aapt.pb.Source.path_idx', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='position', full_name='aapt.pb.Source.position', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=195, - serialized_end=264, -) - - -_TOOLFINGERPRINT = _descriptor.Descriptor( - name='ToolFingerprint', - full_name='aapt.pb.ToolFingerprint', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='tool', full_name='aapt.pb.ToolFingerprint.tool', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='version', full_name='aapt.pb.ToolFingerprint.version', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=266, - serialized_end=314, -) - - -_RESOURCETABLE = _descriptor.Descriptor( - name='ResourceTable', - full_name='aapt.pb.ResourceTable', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='source_pool', full_name='aapt.pb.ResourceTable.source_pool', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='package', full_name='aapt.pb.ResourceTable.package', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='overlayable', full_name='aapt.pb.ResourceTable.overlayable', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='tool_fingerprint', full_name='aapt.pb.ResourceTable.tool_fingerprint', index=3, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=317, - serialized_end=504, -) - - -_PACKAGEID = _descriptor.Descriptor( - name='PackageId', - full_name='aapt.pb.PackageId', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='aapt.pb.PackageId.id', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=506, - serialized_end=529, -) - - -_PACKAGE = _descriptor.Descriptor( - name='Package', - full_name='aapt.pb.Package', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='package_id', full_name='aapt.pb.Package.package_id', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='package_name', full_name='aapt.pb.Package.package_name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='type', full_name='aapt.pb.Package.type', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=531, - serialized_end=631, -) - - -_TYPEID = _descriptor.Descriptor( - name='TypeId', - full_name='aapt.pb.TypeId', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='aapt.pb.TypeId.id', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=633, - serialized_end=653, -) - - -_TYPE = _descriptor.Descriptor( - name='Type', - full_name='aapt.pb.Type', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='type_id', full_name='aapt.pb.Type.type_id', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='name', full_name='aapt.pb.Type.name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='entry', full_name='aapt.pb.Type.entry', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=655, - serialized_end=740, -) - - -_VISIBILITY = _descriptor.Descriptor( - name='Visibility', - full_name='aapt.pb.Visibility', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='level', full_name='aapt.pb.Visibility.level', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.Visibility.source', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='comment', full_name='aapt.pb.Visibility.comment', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='staged_api', full_name='aapt.pb.Visibility.staged_api', index=3, - number=4, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _VISIBILITY_LEVEL, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=743, - serialized_end=914, -) - - -_ALLOWNEW = _descriptor.Descriptor( - name='AllowNew', - full_name='aapt.pb.AllowNew', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.AllowNew.source', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='comment', full_name='aapt.pb.AllowNew.comment', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=916, - serialized_end=976, -) - - -_OVERLAYABLE = _descriptor.Descriptor( - name='Overlayable', - full_name='aapt.pb.Overlayable', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='aapt.pb.Overlayable.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.Overlayable.source', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='actor', full_name='aapt.pb.Overlayable.actor', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=978, - serialized_end=1053, -) - - -_OVERLAYABLEITEM = _descriptor.Descriptor( - name='OverlayableItem', - full_name='aapt.pb.OverlayableItem', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.OverlayableItem.source', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='comment', full_name='aapt.pb.OverlayableItem.comment', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='policy', full_name='aapt.pb.OverlayableItem.policy', index=2, - number=3, type=14, cpp_type=8, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='overlayable_idx', full_name='aapt.pb.OverlayableItem.overlayable_idx', index=3, - number=4, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _OVERLAYABLEITEM_POLICY, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1056, - serialized_end=1333, -) - - -_STAGEDID = _descriptor.Descriptor( - name='StagedId', - full_name='aapt.pb.StagedId', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.StagedId.source', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='staged_id', full_name='aapt.pb.StagedId.staged_id', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1335, - serialized_end=1397, -) - - -_ENTRYID = _descriptor.Descriptor( - name='EntryId', - full_name='aapt.pb.EntryId', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='aapt.pb.EntryId.id', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1399, - serialized_end=1420, -) - - -_ENTRY = _descriptor.Descriptor( - name='Entry', - full_name='aapt.pb.Entry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='entry_id', full_name='aapt.pb.Entry.entry_id', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='name', full_name='aapt.pb.Entry.name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='visibility', full_name='aapt.pb.Entry.visibility', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='allow_new', full_name='aapt.pb.Entry.allow_new', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='overlayable_item', full_name='aapt.pb.Entry.overlayable_item', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='config_value', full_name='aapt.pb.Entry.config_value', index=5, - number=6, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='staged_id', full_name='aapt.pb.Entry.staged_id', index=6, - number=7, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1423, - serialized_end=1693, -) - - -_CONFIGVALUE = _descriptor.Descriptor( - name='ConfigValue', - full_name='aapt.pb.ConfigValue', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='config', full_name='aapt.pb.ConfigValue.config', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='value', full_name='aapt.pb.ConfigValue.value', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1695, - serialized_end=1779, -) - - -_VALUE = _descriptor.Descriptor( - name='Value', - full_name='aapt.pb.Value', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.Value.source', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='comment', full_name='aapt.pb.Value.comment', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='weak', full_name='aapt.pb.Value.weak', index=2, - number=3, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='item', full_name='aapt.pb.Value.item', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='compound_value', full_name='aapt.pb.Value.compound_value', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='value', full_name='aapt.pb.Value.value', - index=0, containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[]), - ], - serialized_start=1782, - serialized_end=1943, -) - - -_ITEM = _descriptor.Descriptor( - name='Item', - full_name='aapt.pb.Item', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='ref', full_name='aapt.pb.Item.ref', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='str', full_name='aapt.pb.Item.str', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='raw_str', full_name='aapt.pb.Item.raw_str', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='styled_str', full_name='aapt.pb.Item.styled_str', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='file', full_name='aapt.pb.Item.file', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='id', full_name='aapt.pb.Item.id', index=5, - number=6, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='prim', full_name='aapt.pb.Item.prim', index=6, - number=7, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='value', full_name='aapt.pb.Item.value', - index=0, containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[]), - ], - serialized_start=1946, - serialized_end=2215, -) - - -_COMPOUNDVALUE = _descriptor.Descriptor( - name='CompoundValue', - full_name='aapt.pb.CompoundValue', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='attr', full_name='aapt.pb.CompoundValue.attr', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='style', full_name='aapt.pb.CompoundValue.style', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='styleable', full_name='aapt.pb.CompoundValue.styleable', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='array', full_name='aapt.pb.CompoundValue.array', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='plural', full_name='aapt.pb.CompoundValue.plural', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='macro', full_name='aapt.pb.CompoundValue.macro', index=5, - number=6, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='value', full_name='aapt.pb.CompoundValue.value', - index=0, containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[]), - ], - serialized_start=2218, - serialized_end=2457, -) - - -_BOOLEAN = _descriptor.Descriptor( - name='Boolean', - full_name='aapt.pb.Boolean', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='aapt.pb.Boolean.value', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2459, - serialized_end=2483, -) - - -_REFERENCE = _descriptor.Descriptor( - name='Reference', - full_name='aapt.pb.Reference', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='type', full_name='aapt.pb.Reference.type', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='id', full_name='aapt.pb.Reference.id', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='name', full_name='aapt.pb.Reference.name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='private', full_name='aapt.pb.Reference.private', index=3, - number=4, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='is_dynamic', full_name='aapt.pb.Reference.is_dynamic', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='type_flags', full_name='aapt.pb.Reference.type_flags', index=5, - number=6, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='allow_raw', full_name='aapt.pb.Reference.allow_raw', index=6, - number=7, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _REFERENCE_TYPE, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2486, - serialized_end=2694, -) - - -_ID = _descriptor.Descriptor( - name='Id', - full_name='aapt.pb.Id', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2696, - serialized_end=2700, -) - - -_STRING = _descriptor.Descriptor( - name='String', - full_name='aapt.pb.String', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='aapt.pb.String.value', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2702, - serialized_end=2725, -) - - -_RAWSTRING = _descriptor.Descriptor( - name='RawString', - full_name='aapt.pb.RawString', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='aapt.pb.RawString.value', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2727, - serialized_end=2753, -) - - -_STYLEDSTRING_SPAN = _descriptor.Descriptor( - name='Span', - full_name='aapt.pb.StyledString.Span', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='tag', full_name='aapt.pb.StyledString.Span.tag', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='first_char', full_name='aapt.pb.StyledString.Span.first_char', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='last_char', full_name='aapt.pb.StyledString.Span.last_char', index=2, - number=3, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2829, - serialized_end=2887, -) - -_STYLEDSTRING = _descriptor.Descriptor( - name='StyledString', - full_name='aapt.pb.StyledString', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='aapt.pb.StyledString.value', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='span', full_name='aapt.pb.StyledString.span', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[_STYLEDSTRING_SPAN, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2756, - serialized_end=2887, -) - - -_FILEREFERENCE = _descriptor.Descriptor( - name='FileReference', - full_name='aapt.pb.FileReference', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='path', full_name='aapt.pb.FileReference.path', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='type', full_name='aapt.pb.FileReference.type', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _FILEREFERENCE_TYPE, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2890, - serialized_end=3023, -) - - -_PRIMITIVE_NULLTYPE = _descriptor.Descriptor( - name='NullType', - full_name='aapt.pb.Primitive.NullType', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3503, - serialized_end=3513, -) - -_PRIMITIVE_EMPTYTYPE = _descriptor.Descriptor( - name='EmptyType', - full_name='aapt.pb.Primitive.EmptyType', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3515, - serialized_end=3526, -) - -_PRIMITIVE = _descriptor.Descriptor( - name='Primitive', - full_name='aapt.pb.Primitive', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='null_value', full_name='aapt.pb.Primitive.null_value', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='empty_value', full_name='aapt.pb.Primitive.empty_value', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='float_value', full_name='aapt.pb.Primitive.float_value', index=2, - number=3, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='dimension_value', full_name='aapt.pb.Primitive.dimension_value', index=3, - number=13, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='fraction_value', full_name='aapt.pb.Primitive.fraction_value', index=4, - number=14, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='int_decimal_value', full_name='aapt.pb.Primitive.int_decimal_value', index=5, - number=6, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='int_hexadecimal_value', full_name='aapt.pb.Primitive.int_hexadecimal_value', index=6, - number=7, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='boolean_value', full_name='aapt.pb.Primitive.boolean_value', index=7, - number=8, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='color_argb8_value', full_name='aapt.pb.Primitive.color_argb8_value', index=8, - number=9, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='color_rgb8_value', full_name='aapt.pb.Primitive.color_rgb8_value', index=9, - number=10, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='color_argb4_value', full_name='aapt.pb.Primitive.color_argb4_value', index=10, - number=11, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='color_rgb4_value', full_name='aapt.pb.Primitive.color_rgb4_value', index=11, - number=12, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='dimension_value_deprecated', full_name='aapt.pb.Primitive.dimension_value_deprecated', index=12, - number=4, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=b'\030\001', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='fraction_value_deprecated', full_name='aapt.pb.Primitive.fraction_value_deprecated', index=13, - number=5, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=b'\030\001', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[_PRIMITIVE_NULLTYPE, _PRIMITIVE_EMPTYTYPE, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='oneof_value', full_name='aapt.pb.Primitive.oneof_value', - index=0, containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[]), - ], - serialized_start=3026, - serialized_end=3541, -) - - -_ATTRIBUTE_SYMBOL = _descriptor.Descriptor( - name='Symbol', - full_name='aapt.pb.Attribute.Symbol', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.Attribute.Symbol.source', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='comment', full_name='aapt.pb.Attribute.Symbol.comment', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='name', full_name='aapt.pb.Attribute.Symbol.name', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='value', full_name='aapt.pb.Attribute.Symbol.value', index=3, - number=4, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='type', full_name='aapt.pb.Attribute.Symbol.type', index=4, - number=5, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3656, - serialized_end=3777, -) - -_ATTRIBUTE = _descriptor.Descriptor( - name='Attribute', - full_name='aapt.pb.Attribute', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='format_flags', full_name='aapt.pb.Attribute.format_flags', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='min_int', full_name='aapt.pb.Attribute.min_int', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='max_int', full_name='aapt.pb.Attribute.max_int', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='symbol', full_name='aapt.pb.Attribute.symbol', index=3, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[_ATTRIBUTE_SYMBOL, ], - enum_types=[ - _ATTRIBUTE_FORMATFLAGS, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3544, - serialized_end=3944, -) - - -_STYLE_ENTRY = _descriptor.Descriptor( - name='Entry', - full_name='aapt.pb.Style.Entry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.Style.Entry.source', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='comment', full_name='aapt.pb.Style.Entry.comment', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='key', full_name='aapt.pb.Style.Entry.key', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='item', full_name='aapt.pb.Style.Entry.item', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4069, - serialized_end=4188, -) - -_STYLE = _descriptor.Descriptor( - name='Style', - full_name='aapt.pb.Style', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='parent', full_name='aapt.pb.Style.parent', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='parent_source', full_name='aapt.pb.Style.parent_source', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='entry', full_name='aapt.pb.Style.entry', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[_STYLE_ENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3947, - serialized_end=4188, -) - - -_STYLEABLE_ENTRY = _descriptor.Descriptor( - name='Entry', - full_name='aapt.pb.Styleable.Entry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.Styleable.Entry.source', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='comment', full_name='aapt.pb.Styleable.Entry.comment', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='attr', full_name='aapt.pb.Styleable.Entry.attr', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4245, - serialized_end=4336, -) - -_STYLEABLE = _descriptor.Descriptor( - name='Styleable', - full_name='aapt.pb.Styleable', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='entry', full_name='aapt.pb.Styleable.entry', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[_STYLEABLE_ENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4191, - serialized_end=4336, -) - - -_ARRAY_ELEMENT = _descriptor.Descriptor( - name='Element', - full_name='aapt.pb.Array.Element', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.Array.Element.source', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='comment', full_name='aapt.pb.Array.Element.comment', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='item', full_name='aapt.pb.Array.Element.item', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4389, - serialized_end=4477, -) - -_ARRAY = _descriptor.Descriptor( - name='Array', - full_name='aapt.pb.Array', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='element', full_name='aapt.pb.Array.element', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[_ARRAY_ELEMENT, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4339, - serialized_end=4477, -) - - -_PLURAL_ENTRY = _descriptor.Descriptor( - name='Entry', - full_name='aapt.pb.Plural.Entry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.Plural.Entry.source', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='comment', full_name='aapt.pb.Plural.Entry.comment', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='arity', full_name='aapt.pb.Plural.Entry.arity', index=2, - number=3, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='item', full_name='aapt.pb.Plural.Entry.item', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4528, - serialized_end=4652, -) - -_PLURAL = _descriptor.Descriptor( - name='Plural', - full_name='aapt.pb.Plural', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='entry', full_name='aapt.pb.Plural.entry', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[_PLURAL_ENTRY, ], - enum_types=[ - _PLURAL_ARITY, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4480, - serialized_end=4719, -) - - -_XMLNODE = _descriptor.Descriptor( - name='XmlNode', - full_name='aapt.pb.XmlNode', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='element', full_name='aapt.pb.XmlNode.element', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='text', full_name='aapt.pb.XmlNode.text', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.XmlNode.source', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='node', full_name='aapt.pb.XmlNode.node', - index=0, containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[]), - ], - serialized_start=4721, - serialized_end=4835, -) - - -_XMLELEMENT = _descriptor.Descriptor( - name='XmlElement', - full_name='aapt.pb.XmlElement', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='namespace_declaration', full_name='aapt.pb.XmlElement.namespace_declaration', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='namespace_uri', full_name='aapt.pb.XmlElement.namespace_uri', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='name', full_name='aapt.pb.XmlElement.name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='attribute', full_name='aapt.pb.XmlElement.attribute', index=3, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='child', full_name='aapt.pb.XmlElement.child', index=4, - number=5, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4838, - serialized_end=5016, -) - - -_XMLNAMESPACE = _descriptor.Descriptor( - name='XmlNamespace', - full_name='aapt.pb.XmlNamespace', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='prefix', full_name='aapt.pb.XmlNamespace.prefix', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='uri', full_name='aapt.pb.XmlNamespace.uri', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.XmlNamespace.source', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5018, - serialized_end=5102, -) - - -_XMLATTRIBUTE = _descriptor.Descriptor( - name='XmlAttribute', - full_name='aapt.pb.XmlAttribute', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='namespace_uri', full_name='aapt.pb.XmlAttribute.namespace_uri', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='name', full_name='aapt.pb.XmlAttribute.name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='value', full_name='aapt.pb.XmlAttribute.value', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.XmlAttribute.source', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='resource_id', full_name='aapt.pb.XmlAttribute.resource_id', index=4, - number=5, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='compiled_item', full_name='aapt.pb.XmlAttribute.compiled_item', index=5, - number=6, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5105, - serialized_end=5271, -) - - -_MACROBODY = _descriptor.Descriptor( - name='MacroBody', - full_name='aapt.pb.MacroBody', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='raw_string', full_name='aapt.pb.MacroBody.raw_string', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='style_string', full_name='aapt.pb.MacroBody.style_string', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='untranslatable_sections', full_name='aapt.pb.MacroBody.untranslatable_sections', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='namespace_stack', full_name='aapt.pb.MacroBody.namespace_stack', index=3, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='source', full_name='aapt.pb.MacroBody.source', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5274, - serialized_end=5505, -) - - -_NAMESPACEALIAS = _descriptor.Descriptor( - name='NamespaceAlias', - full_name='aapt.pb.NamespaceAlias', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='prefix', full_name='aapt.pb.NamespaceAlias.prefix', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='package_name', full_name='aapt.pb.NamespaceAlias.package_name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='is_private', full_name='aapt.pb.NamespaceAlias.is_private', index=2, - number=3, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5507, - serialized_end=5581, -) - - -_STYLESTRING_SPAN = _descriptor.Descriptor( - name='Span', - full_name='aapt.pb.StyleString.Span', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='aapt.pb.StyleString.Span.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='start_index', full_name='aapt.pb.StyleString.Span.start_index', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='end_index', full_name='aapt.pb.StyleString.Span.end_index', index=2, - number=3, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5654, - serialized_end=5714, -) - -_STYLESTRING = _descriptor.Descriptor( - name='StyleString', - full_name='aapt.pb.StyleString', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='str', full_name='aapt.pb.StyleString.str', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='spans', full_name='aapt.pb.StyleString.spans', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[_STYLESTRING_SPAN, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5584, - serialized_end=5714, -) - - -_UNTRANSLATABLESECTION = _descriptor.Descriptor( - name='UntranslatableSection', - full_name='aapt.pb.UntranslatableSection', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='start_index', full_name='aapt.pb.UntranslatableSection.start_index', index=0, - number=1, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='end_index', full_name='aapt.pb.UntranslatableSection.end_index', index=1, - number=2, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5716, - serialized_end=5779, -) - -_SOURCE.fields_by_name['position'].message_type = _SOURCEPOSITION -_RESOURCETABLE.fields_by_name['source_pool'].message_type = _STRINGPOOL -_RESOURCETABLE.fields_by_name['package'].message_type = _PACKAGE -_RESOURCETABLE.fields_by_name['overlayable'].message_type = _OVERLAYABLE -_RESOURCETABLE.fields_by_name['tool_fingerprint'].message_type = _TOOLFINGERPRINT -_PACKAGE.fields_by_name['package_id'].message_type = _PACKAGEID -_PACKAGE.fields_by_name['type'].message_type = _TYPE -_TYPE.fields_by_name['type_id'].message_type = _TYPEID -_TYPE.fields_by_name['entry'].message_type = _ENTRY -_VISIBILITY.fields_by_name['level'].enum_type = _VISIBILITY_LEVEL -_VISIBILITY.fields_by_name['source'].message_type = _SOURCE -_VISIBILITY_LEVEL.containing_type = _VISIBILITY -_ALLOWNEW.fields_by_name['source'].message_type = _SOURCE -_OVERLAYABLE.fields_by_name['source'].message_type = _SOURCE -_OVERLAYABLEITEM.fields_by_name['source'].message_type = _SOURCE -_OVERLAYABLEITEM.fields_by_name['policy'].enum_type = _OVERLAYABLEITEM_POLICY -_OVERLAYABLEITEM_POLICY.containing_type = _OVERLAYABLEITEM -_STAGEDID.fields_by_name['source'].message_type = _SOURCE -_ENTRY.fields_by_name['entry_id'].message_type = _ENTRYID -_ENTRY.fields_by_name['visibility'].message_type = _VISIBILITY -_ENTRY.fields_by_name['allow_new'].message_type = _ALLOWNEW -_ENTRY.fields_by_name['overlayable_item'].message_type = _OVERLAYABLEITEM -_ENTRY.fields_by_name['config_value'].message_type = _CONFIGVALUE -_ENTRY.fields_by_name['staged_id'].message_type = _STAGEDID -_CONFIGVALUE.fields_by_name['config'].message_type = frameworks_dot_base_dot_tools_dot_aapt2_dot_Configuration__pb2._CONFIGURATION -_CONFIGVALUE.fields_by_name['value'].message_type = _VALUE -_VALUE.fields_by_name['source'].message_type = _SOURCE -_VALUE.fields_by_name['item'].message_type = _ITEM -_VALUE.fields_by_name['compound_value'].message_type = _COMPOUNDVALUE -_VALUE.oneofs_by_name['value'].fields.append( - _VALUE.fields_by_name['item']) -_VALUE.fields_by_name['item'].containing_oneof = _VALUE.oneofs_by_name['value'] -_VALUE.oneofs_by_name['value'].fields.append( - _VALUE.fields_by_name['compound_value']) -_VALUE.fields_by_name['compound_value'].containing_oneof = _VALUE.oneofs_by_name['value'] -_ITEM.fields_by_name['ref'].message_type = _REFERENCE -_ITEM.fields_by_name['str'].message_type = _STRING -_ITEM.fields_by_name['raw_str'].message_type = _RAWSTRING -_ITEM.fields_by_name['styled_str'].message_type = _STYLEDSTRING -_ITEM.fields_by_name['file'].message_type = _FILEREFERENCE -_ITEM.fields_by_name['id'].message_type = _ID -_ITEM.fields_by_name['prim'].message_type = _PRIMITIVE -_ITEM.oneofs_by_name['value'].fields.append( - _ITEM.fields_by_name['ref']) -_ITEM.fields_by_name['ref'].containing_oneof = _ITEM.oneofs_by_name['value'] -_ITEM.oneofs_by_name['value'].fields.append( - _ITEM.fields_by_name['str']) -_ITEM.fields_by_name['str'].containing_oneof = _ITEM.oneofs_by_name['value'] -_ITEM.oneofs_by_name['value'].fields.append( - _ITEM.fields_by_name['raw_str']) -_ITEM.fields_by_name['raw_str'].containing_oneof = _ITEM.oneofs_by_name['value'] -_ITEM.oneofs_by_name['value'].fields.append( - _ITEM.fields_by_name['styled_str']) -_ITEM.fields_by_name['styled_str'].containing_oneof = _ITEM.oneofs_by_name['value'] -_ITEM.oneofs_by_name['value'].fields.append( - _ITEM.fields_by_name['file']) -_ITEM.fields_by_name['file'].containing_oneof = _ITEM.oneofs_by_name['value'] -_ITEM.oneofs_by_name['value'].fields.append( - _ITEM.fields_by_name['id']) -_ITEM.fields_by_name['id'].containing_oneof = _ITEM.oneofs_by_name['value'] -_ITEM.oneofs_by_name['value'].fields.append( - _ITEM.fields_by_name['prim']) -_ITEM.fields_by_name['prim'].containing_oneof = _ITEM.oneofs_by_name['value'] -_COMPOUNDVALUE.fields_by_name['attr'].message_type = _ATTRIBUTE -_COMPOUNDVALUE.fields_by_name['style'].message_type = _STYLE -_COMPOUNDVALUE.fields_by_name['styleable'].message_type = _STYLEABLE -_COMPOUNDVALUE.fields_by_name['array'].message_type = _ARRAY -_COMPOUNDVALUE.fields_by_name['plural'].message_type = _PLURAL -_COMPOUNDVALUE.fields_by_name['macro'].message_type = _MACROBODY -_COMPOUNDVALUE.oneofs_by_name['value'].fields.append( - _COMPOUNDVALUE.fields_by_name['attr']) -_COMPOUNDVALUE.fields_by_name['attr'].containing_oneof = _COMPOUNDVALUE.oneofs_by_name['value'] -_COMPOUNDVALUE.oneofs_by_name['value'].fields.append( - _COMPOUNDVALUE.fields_by_name['style']) -_COMPOUNDVALUE.fields_by_name['style'].containing_oneof = _COMPOUNDVALUE.oneofs_by_name['value'] -_COMPOUNDVALUE.oneofs_by_name['value'].fields.append( - _COMPOUNDVALUE.fields_by_name['styleable']) -_COMPOUNDVALUE.fields_by_name['styleable'].containing_oneof = _COMPOUNDVALUE.oneofs_by_name['value'] -_COMPOUNDVALUE.oneofs_by_name['value'].fields.append( - _COMPOUNDVALUE.fields_by_name['array']) -_COMPOUNDVALUE.fields_by_name['array'].containing_oneof = _COMPOUNDVALUE.oneofs_by_name['value'] -_COMPOUNDVALUE.oneofs_by_name['value'].fields.append( - _COMPOUNDVALUE.fields_by_name['plural']) -_COMPOUNDVALUE.fields_by_name['plural'].containing_oneof = _COMPOUNDVALUE.oneofs_by_name['value'] -_COMPOUNDVALUE.oneofs_by_name['value'].fields.append( - _COMPOUNDVALUE.fields_by_name['macro']) -_COMPOUNDVALUE.fields_by_name['macro'].containing_oneof = _COMPOUNDVALUE.oneofs_by_name['value'] -_REFERENCE.fields_by_name['type'].enum_type = _REFERENCE_TYPE -_REFERENCE.fields_by_name['is_dynamic'].message_type = _BOOLEAN -_REFERENCE_TYPE.containing_type = _REFERENCE -_STYLEDSTRING_SPAN.containing_type = _STYLEDSTRING -_STYLEDSTRING.fields_by_name['span'].message_type = _STYLEDSTRING_SPAN -_FILEREFERENCE.fields_by_name['type'].enum_type = _FILEREFERENCE_TYPE -_FILEREFERENCE_TYPE.containing_type = _FILEREFERENCE -_PRIMITIVE_NULLTYPE.containing_type = _PRIMITIVE -_PRIMITIVE_EMPTYTYPE.containing_type = _PRIMITIVE -_PRIMITIVE.fields_by_name['null_value'].message_type = _PRIMITIVE_NULLTYPE -_PRIMITIVE.fields_by_name['empty_value'].message_type = _PRIMITIVE_EMPTYTYPE -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['null_value']) -_PRIMITIVE.fields_by_name['null_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['empty_value']) -_PRIMITIVE.fields_by_name['empty_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['float_value']) -_PRIMITIVE.fields_by_name['float_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['dimension_value']) -_PRIMITIVE.fields_by_name['dimension_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['fraction_value']) -_PRIMITIVE.fields_by_name['fraction_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['int_decimal_value']) -_PRIMITIVE.fields_by_name['int_decimal_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['int_hexadecimal_value']) -_PRIMITIVE.fields_by_name['int_hexadecimal_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['boolean_value']) -_PRIMITIVE.fields_by_name['boolean_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['color_argb8_value']) -_PRIMITIVE.fields_by_name['color_argb8_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['color_rgb8_value']) -_PRIMITIVE.fields_by_name['color_rgb8_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['color_argb4_value']) -_PRIMITIVE.fields_by_name['color_argb4_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['color_rgb4_value']) -_PRIMITIVE.fields_by_name['color_rgb4_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['dimension_value_deprecated']) -_PRIMITIVE.fields_by_name['dimension_value_deprecated'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( - _PRIMITIVE.fields_by_name['fraction_value_deprecated']) -_PRIMITIVE.fields_by_name['fraction_value_deprecated'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] -_ATTRIBUTE_SYMBOL.fields_by_name['source'].message_type = _SOURCE -_ATTRIBUTE_SYMBOL.fields_by_name['name'].message_type = _REFERENCE -_ATTRIBUTE_SYMBOL.containing_type = _ATTRIBUTE -_ATTRIBUTE.fields_by_name['symbol'].message_type = _ATTRIBUTE_SYMBOL -_ATTRIBUTE_FORMATFLAGS.containing_type = _ATTRIBUTE -_STYLE_ENTRY.fields_by_name['source'].message_type = _SOURCE -_STYLE_ENTRY.fields_by_name['key'].message_type = _REFERENCE -_STYLE_ENTRY.fields_by_name['item'].message_type = _ITEM -_STYLE_ENTRY.containing_type = _STYLE -_STYLE.fields_by_name['parent'].message_type = _REFERENCE -_STYLE.fields_by_name['parent_source'].message_type = _SOURCE -_STYLE.fields_by_name['entry'].message_type = _STYLE_ENTRY -_STYLEABLE_ENTRY.fields_by_name['source'].message_type = _SOURCE -_STYLEABLE_ENTRY.fields_by_name['attr'].message_type = _REFERENCE -_STYLEABLE_ENTRY.containing_type = _STYLEABLE -_STYLEABLE.fields_by_name['entry'].message_type = _STYLEABLE_ENTRY -_ARRAY_ELEMENT.fields_by_name['source'].message_type = _SOURCE -_ARRAY_ELEMENT.fields_by_name['item'].message_type = _ITEM -_ARRAY_ELEMENT.containing_type = _ARRAY -_ARRAY.fields_by_name['element'].message_type = _ARRAY_ELEMENT -_PLURAL_ENTRY.fields_by_name['source'].message_type = _SOURCE -_PLURAL_ENTRY.fields_by_name['arity'].enum_type = _PLURAL_ARITY -_PLURAL_ENTRY.fields_by_name['item'].message_type = _ITEM -_PLURAL_ENTRY.containing_type = _PLURAL -_PLURAL.fields_by_name['entry'].message_type = _PLURAL_ENTRY -_PLURAL_ARITY.containing_type = _PLURAL -_XMLNODE.fields_by_name['element'].message_type = _XMLELEMENT -_XMLNODE.fields_by_name['source'].message_type = _SOURCEPOSITION -_XMLNODE.oneofs_by_name['node'].fields.append( - _XMLNODE.fields_by_name['element']) -_XMLNODE.fields_by_name['element'].containing_oneof = _XMLNODE.oneofs_by_name['node'] -_XMLNODE.oneofs_by_name['node'].fields.append( - _XMLNODE.fields_by_name['text']) -_XMLNODE.fields_by_name['text'].containing_oneof = _XMLNODE.oneofs_by_name['node'] -_XMLELEMENT.fields_by_name['namespace_declaration'].message_type = _XMLNAMESPACE -_XMLELEMENT.fields_by_name['attribute'].message_type = _XMLATTRIBUTE -_XMLELEMENT.fields_by_name['child'].message_type = _XMLNODE -_XMLNAMESPACE.fields_by_name['source'].message_type = _SOURCEPOSITION -_XMLATTRIBUTE.fields_by_name['source'].message_type = _SOURCEPOSITION -_XMLATTRIBUTE.fields_by_name['compiled_item'].message_type = _ITEM -_MACROBODY.fields_by_name['style_string'].message_type = _STYLESTRING -_MACROBODY.fields_by_name['untranslatable_sections'].message_type = _UNTRANSLATABLESECTION -_MACROBODY.fields_by_name['namespace_stack'].message_type = _NAMESPACEALIAS -_MACROBODY.fields_by_name['source'].message_type = _SOURCEPOSITION -_STYLESTRING_SPAN.containing_type = _STYLESTRING -_STYLESTRING.fields_by_name['spans'].message_type = _STYLESTRING_SPAN -DESCRIPTOR.message_types_by_name['StringPool'] = _STRINGPOOL -DESCRIPTOR.message_types_by_name['SourcePosition'] = _SOURCEPOSITION -DESCRIPTOR.message_types_by_name['Source'] = _SOURCE -DESCRIPTOR.message_types_by_name['ToolFingerprint'] = _TOOLFINGERPRINT -DESCRIPTOR.message_types_by_name['ResourceTable'] = _RESOURCETABLE -DESCRIPTOR.message_types_by_name['PackageId'] = _PACKAGEID -DESCRIPTOR.message_types_by_name['Package'] = _PACKAGE -DESCRIPTOR.message_types_by_name['TypeId'] = _TYPEID -DESCRIPTOR.message_types_by_name['Type'] = _TYPE -DESCRIPTOR.message_types_by_name['Visibility'] = _VISIBILITY -DESCRIPTOR.message_types_by_name['AllowNew'] = _ALLOWNEW -DESCRIPTOR.message_types_by_name['Overlayable'] = _OVERLAYABLE -DESCRIPTOR.message_types_by_name['OverlayableItem'] = _OVERLAYABLEITEM -DESCRIPTOR.message_types_by_name['StagedId'] = _STAGEDID -DESCRIPTOR.message_types_by_name['EntryId'] = _ENTRYID -DESCRIPTOR.message_types_by_name['Entry'] = _ENTRY -DESCRIPTOR.message_types_by_name['ConfigValue'] = _CONFIGVALUE -DESCRIPTOR.message_types_by_name['Value'] = _VALUE -DESCRIPTOR.message_types_by_name['Item'] = _ITEM -DESCRIPTOR.message_types_by_name['CompoundValue'] = _COMPOUNDVALUE -DESCRIPTOR.message_types_by_name['Boolean'] = _BOOLEAN -DESCRIPTOR.message_types_by_name['Reference'] = _REFERENCE -DESCRIPTOR.message_types_by_name['Id'] = _ID -DESCRIPTOR.message_types_by_name['String'] = _STRING -DESCRIPTOR.message_types_by_name['RawString'] = _RAWSTRING -DESCRIPTOR.message_types_by_name['StyledString'] = _STYLEDSTRING -DESCRIPTOR.message_types_by_name['FileReference'] = _FILEREFERENCE -DESCRIPTOR.message_types_by_name['Primitive'] = _PRIMITIVE -DESCRIPTOR.message_types_by_name['Attribute'] = _ATTRIBUTE -DESCRIPTOR.message_types_by_name['Style'] = _STYLE -DESCRIPTOR.message_types_by_name['Styleable'] = _STYLEABLE -DESCRIPTOR.message_types_by_name['Array'] = _ARRAY -DESCRIPTOR.message_types_by_name['Plural'] = _PLURAL -DESCRIPTOR.message_types_by_name['XmlNode'] = _XMLNODE -DESCRIPTOR.message_types_by_name['XmlElement'] = _XMLELEMENT -DESCRIPTOR.message_types_by_name['XmlNamespace'] = _XMLNAMESPACE -DESCRIPTOR.message_types_by_name['XmlAttribute'] = _XMLATTRIBUTE -DESCRIPTOR.message_types_by_name['MacroBody'] = _MACROBODY -DESCRIPTOR.message_types_by_name['NamespaceAlias'] = _NAMESPACEALIAS -DESCRIPTOR.message_types_by_name['StyleString'] = _STYLESTRING -DESCRIPTOR.message_types_by_name['UntranslatableSection'] = _UNTRANSLATABLESECTION -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -StringPool = _reflection.GeneratedProtocolMessageType('StringPool', (_message.Message,), { - 'DESCRIPTOR' : _STRINGPOOL, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.StringPool) - }) -_sym_db.RegisterMessage(StringPool) - -SourcePosition = _reflection.GeneratedProtocolMessageType('SourcePosition', (_message.Message,), { - 'DESCRIPTOR' : _SOURCEPOSITION, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.SourcePosition) - }) -_sym_db.RegisterMessage(SourcePosition) - -Source = _reflection.GeneratedProtocolMessageType('Source', (_message.Message,), { - 'DESCRIPTOR' : _SOURCE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Source) - }) -_sym_db.RegisterMessage(Source) - -ToolFingerprint = _reflection.GeneratedProtocolMessageType('ToolFingerprint', (_message.Message,), { - 'DESCRIPTOR' : _TOOLFINGERPRINT, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.ToolFingerprint) - }) -_sym_db.RegisterMessage(ToolFingerprint) - -ResourceTable = _reflection.GeneratedProtocolMessageType('ResourceTable', (_message.Message,), { - 'DESCRIPTOR' : _RESOURCETABLE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.ResourceTable) - }) -_sym_db.RegisterMessage(ResourceTable) - -PackageId = _reflection.GeneratedProtocolMessageType('PackageId', (_message.Message,), { - 'DESCRIPTOR' : _PACKAGEID, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.PackageId) - }) -_sym_db.RegisterMessage(PackageId) - -Package = _reflection.GeneratedProtocolMessageType('Package', (_message.Message,), { - 'DESCRIPTOR' : _PACKAGE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Package) - }) -_sym_db.RegisterMessage(Package) - -TypeId = _reflection.GeneratedProtocolMessageType('TypeId', (_message.Message,), { - 'DESCRIPTOR' : _TYPEID, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.TypeId) - }) -_sym_db.RegisterMessage(TypeId) - -Type = _reflection.GeneratedProtocolMessageType('Type', (_message.Message,), { - 'DESCRIPTOR' : _TYPE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Type) - }) -_sym_db.RegisterMessage(Type) - -Visibility = _reflection.GeneratedProtocolMessageType('Visibility', (_message.Message,), { - 'DESCRIPTOR' : _VISIBILITY, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Visibility) - }) -_sym_db.RegisterMessage(Visibility) - -AllowNew = _reflection.GeneratedProtocolMessageType('AllowNew', (_message.Message,), { - 'DESCRIPTOR' : _ALLOWNEW, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.AllowNew) - }) -_sym_db.RegisterMessage(AllowNew) - -Overlayable = _reflection.GeneratedProtocolMessageType('Overlayable', (_message.Message,), { - 'DESCRIPTOR' : _OVERLAYABLE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Overlayable) - }) -_sym_db.RegisterMessage(Overlayable) - -OverlayableItem = _reflection.GeneratedProtocolMessageType('OverlayableItem', (_message.Message,), { - 'DESCRIPTOR' : _OVERLAYABLEITEM, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.OverlayableItem) - }) -_sym_db.RegisterMessage(OverlayableItem) - -StagedId = _reflection.GeneratedProtocolMessageType('StagedId', (_message.Message,), { - 'DESCRIPTOR' : _STAGEDID, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.StagedId) - }) -_sym_db.RegisterMessage(StagedId) - -EntryId = _reflection.GeneratedProtocolMessageType('EntryId', (_message.Message,), { - 'DESCRIPTOR' : _ENTRYID, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.EntryId) - }) -_sym_db.RegisterMessage(EntryId) - -Entry = _reflection.GeneratedProtocolMessageType('Entry', (_message.Message,), { - 'DESCRIPTOR' : _ENTRY, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Entry) - }) -_sym_db.RegisterMessage(Entry) - -ConfigValue = _reflection.GeneratedProtocolMessageType('ConfigValue', (_message.Message,), { - 'DESCRIPTOR' : _CONFIGVALUE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.ConfigValue) - }) -_sym_db.RegisterMessage(ConfigValue) - -Value = _reflection.GeneratedProtocolMessageType('Value', (_message.Message,), { - 'DESCRIPTOR' : _VALUE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Value) - }) -_sym_db.RegisterMessage(Value) - -Item = _reflection.GeneratedProtocolMessageType('Item', (_message.Message,), { - 'DESCRIPTOR' : _ITEM, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Item) - }) -_sym_db.RegisterMessage(Item) - -CompoundValue = _reflection.GeneratedProtocolMessageType('CompoundValue', (_message.Message,), { - 'DESCRIPTOR' : _COMPOUNDVALUE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.CompoundValue) - }) -_sym_db.RegisterMessage(CompoundValue) - -Boolean = _reflection.GeneratedProtocolMessageType('Boolean', (_message.Message,), { - 'DESCRIPTOR' : _BOOLEAN, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Boolean) - }) -_sym_db.RegisterMessage(Boolean) - -Reference = _reflection.GeneratedProtocolMessageType('Reference', (_message.Message,), { - 'DESCRIPTOR' : _REFERENCE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Reference) - }) -_sym_db.RegisterMessage(Reference) - -Id = _reflection.GeneratedProtocolMessageType('Id', (_message.Message,), { - 'DESCRIPTOR' : _ID, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Id) - }) -_sym_db.RegisterMessage(Id) - -String = _reflection.GeneratedProtocolMessageType('String', (_message.Message,), { - 'DESCRIPTOR' : _STRING, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.String) - }) -_sym_db.RegisterMessage(String) - -RawString = _reflection.GeneratedProtocolMessageType('RawString', (_message.Message,), { - 'DESCRIPTOR' : _RAWSTRING, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.RawString) - }) -_sym_db.RegisterMessage(RawString) - -StyledString = _reflection.GeneratedProtocolMessageType('StyledString', (_message.Message,), { - - 'Span' : _reflection.GeneratedProtocolMessageType('Span', (_message.Message,), { - 'DESCRIPTOR' : _STYLEDSTRING_SPAN, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.StyledString.Span) - }) - , - 'DESCRIPTOR' : _STYLEDSTRING, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.StyledString) - }) -_sym_db.RegisterMessage(StyledString) -_sym_db.RegisterMessage(StyledString.Span) - -FileReference = _reflection.GeneratedProtocolMessageType('FileReference', (_message.Message,), { - 'DESCRIPTOR' : _FILEREFERENCE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.FileReference) - }) -_sym_db.RegisterMessage(FileReference) - -Primitive = _reflection.GeneratedProtocolMessageType('Primitive', (_message.Message,), { - - 'NullType' : _reflection.GeneratedProtocolMessageType('NullType', (_message.Message,), { - 'DESCRIPTOR' : _PRIMITIVE_NULLTYPE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Primitive.NullType) - }) - , - - 'EmptyType' : _reflection.GeneratedProtocolMessageType('EmptyType', (_message.Message,), { - 'DESCRIPTOR' : _PRIMITIVE_EMPTYTYPE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Primitive.EmptyType) - }) - , - 'DESCRIPTOR' : _PRIMITIVE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Primitive) - }) -_sym_db.RegisterMessage(Primitive) -_sym_db.RegisterMessage(Primitive.NullType) -_sym_db.RegisterMessage(Primitive.EmptyType) - -Attribute = _reflection.GeneratedProtocolMessageType('Attribute', (_message.Message,), { - - 'Symbol' : _reflection.GeneratedProtocolMessageType('Symbol', (_message.Message,), { - 'DESCRIPTOR' : _ATTRIBUTE_SYMBOL, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Attribute.Symbol) - }) - , - 'DESCRIPTOR' : _ATTRIBUTE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Attribute) - }) -_sym_db.RegisterMessage(Attribute) -_sym_db.RegisterMessage(Attribute.Symbol) - -Style = _reflection.GeneratedProtocolMessageType('Style', (_message.Message,), { - - 'Entry' : _reflection.GeneratedProtocolMessageType('Entry', (_message.Message,), { - 'DESCRIPTOR' : _STYLE_ENTRY, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Style.Entry) - }) - , - 'DESCRIPTOR' : _STYLE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Style) - }) -_sym_db.RegisterMessage(Style) -_sym_db.RegisterMessage(Style.Entry) - -Styleable = _reflection.GeneratedProtocolMessageType('Styleable', (_message.Message,), { - - 'Entry' : _reflection.GeneratedProtocolMessageType('Entry', (_message.Message,), { - 'DESCRIPTOR' : _STYLEABLE_ENTRY, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Styleable.Entry) - }) - , - 'DESCRIPTOR' : _STYLEABLE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Styleable) - }) -_sym_db.RegisterMessage(Styleable) -_sym_db.RegisterMessage(Styleable.Entry) - -Array = _reflection.GeneratedProtocolMessageType('Array', (_message.Message,), { - - 'Element' : _reflection.GeneratedProtocolMessageType('Element', (_message.Message,), { - 'DESCRIPTOR' : _ARRAY_ELEMENT, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Array.Element) - }) - , - 'DESCRIPTOR' : _ARRAY, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Array) - }) -_sym_db.RegisterMessage(Array) -_sym_db.RegisterMessage(Array.Element) - -Plural = _reflection.GeneratedProtocolMessageType('Plural', (_message.Message,), { - - 'Entry' : _reflection.GeneratedProtocolMessageType('Entry', (_message.Message,), { - 'DESCRIPTOR' : _PLURAL_ENTRY, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Plural.Entry) - }) - , - 'DESCRIPTOR' : _PLURAL, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.Plural) - }) -_sym_db.RegisterMessage(Plural) -_sym_db.RegisterMessage(Plural.Entry) - -XmlNode = _reflection.GeneratedProtocolMessageType('XmlNode', (_message.Message,), { - 'DESCRIPTOR' : _XMLNODE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.XmlNode) - }) -_sym_db.RegisterMessage(XmlNode) - -XmlElement = _reflection.GeneratedProtocolMessageType('XmlElement', (_message.Message,), { - 'DESCRIPTOR' : _XMLELEMENT, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.XmlElement) - }) -_sym_db.RegisterMessage(XmlElement) - -XmlNamespace = _reflection.GeneratedProtocolMessageType('XmlNamespace', (_message.Message,), { - 'DESCRIPTOR' : _XMLNAMESPACE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.XmlNamespace) - }) -_sym_db.RegisterMessage(XmlNamespace) - -XmlAttribute = _reflection.GeneratedProtocolMessageType('XmlAttribute', (_message.Message,), { - 'DESCRIPTOR' : _XMLATTRIBUTE, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.XmlAttribute) - }) -_sym_db.RegisterMessage(XmlAttribute) - -MacroBody = _reflection.GeneratedProtocolMessageType('MacroBody', (_message.Message,), { - 'DESCRIPTOR' : _MACROBODY, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.MacroBody) - }) -_sym_db.RegisterMessage(MacroBody) - -NamespaceAlias = _reflection.GeneratedProtocolMessageType('NamespaceAlias', (_message.Message,), { - 'DESCRIPTOR' : _NAMESPACEALIAS, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.NamespaceAlias) - }) -_sym_db.RegisterMessage(NamespaceAlias) - -StyleString = _reflection.GeneratedProtocolMessageType('StyleString', (_message.Message,), { - - 'Span' : _reflection.GeneratedProtocolMessageType('Span', (_message.Message,), { - 'DESCRIPTOR' : _STYLESTRING_SPAN, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.StyleString.Span) - }) - , - 'DESCRIPTOR' : _STYLESTRING, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.StyleString) - }) -_sym_db.RegisterMessage(StyleString) -_sym_db.RegisterMessage(StyleString.Span) - -UntranslatableSection = _reflection.GeneratedProtocolMessageType('UntranslatableSection', (_message.Message,), { - 'DESCRIPTOR' : _UNTRANSLATABLESECTION, - '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' - # @@protoc_insertion_point(class_scope:aapt.pb.UntranslatableSection) - }) -_sym_db.RegisterMessage(UntranslatableSection) - - -DESCRIPTOR._options = None -_PRIMITIVE.fields_by_name['dimension_value_deprecated']._options = None -_PRIMITIVE.fields_by_name['fraction_value_deprecated']._options = None +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n+frameworks/base/tools/aapt2/Resources.proto\x12\x07\x61\x61pt.pb\x1a/frameworks/base/tools/aapt2/Configuration.proto\"\x1a\n\nStringPool\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"<\n\x0eSourcePosition\x12\x13\n\x0bline_number\x18\x01 \x01(\r\x12\x15\n\rcolumn_number\x18\x02 \x01(\r\"E\n\x06Source\x12\x10\n\x08path_idx\x18\x01 \x01(\r\x12)\n\x08position\x18\x02 \x01(\x0b\x32\x17.aapt.pb.SourcePosition\"0\n\x0fToolFingerprint\x12\x0c\n\x04tool\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\"O\n\x0f\x44ynamicRefTable\x12&\n\npackage_id\x18\x01 \x01(\x0b\x32\x12.aapt.pb.PackageId\x12\x14\n\x0cpackage_name\x18\x02 \x01(\t\"\xf0\x01\n\rResourceTable\x12(\n\x0bsource_pool\x18\x01 \x01(\x0b\x32\x13.aapt.pb.StringPool\x12!\n\x07package\x18\x02 \x03(\x0b\x32\x10.aapt.pb.Package\x12)\n\x0boverlayable\x18\x03 \x03(\x0b\x32\x14.aapt.pb.Overlayable\x12\x32\n\x10tool_fingerprint\x18\x04 \x03(\x0b\x32\x18.aapt.pb.ToolFingerprint\x12\x33\n\x11\x64ynamic_ref_table\x18\x05 \x03(\x0b\x32\x18.aapt.pb.DynamicRefTable\"\x17\n\tPackageId\x12\n\n\x02id\x18\x01 \x01(\r\"d\n\x07Package\x12&\n\npackage_id\x18\x01 \x01(\x0b\x32\x12.aapt.pb.PackageId\x12\x14\n\x0cpackage_name\x18\x02 \x01(\t\x12\x1b\n\x04type\x18\x03 \x03(\x0b\x32\r.aapt.pb.Type\"\x14\n\x06TypeId\x12\n\n\x02id\x18\x01 \x01(\r\"U\n\x04Type\x12 \n\x07type_id\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.TypeId\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x1d\n\x05\x65ntry\x18\x03 \x03(\x0b\x32\x0e.aapt.pb.Entry\"\xab\x01\n\nVisibility\x12(\n\x05level\x18\x01 \x01(\x0e\x32\x19.aapt.pb.Visibility.Level\x12\x1f\n\x06source\x18\x02 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x03 \x01(\t\x12\x12\n\nstaged_api\x18\x04 \x01(\x08\"-\n\x05Level\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0b\n\x07PRIVATE\x10\x01\x12\n\n\x06PUBLIC\x10\x02\"<\n\x08\x41llowNew\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\"K\n\x0bOverlayable\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1f\n\x06source\x18\x02 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\r\n\x05\x61\x63tor\x18\x03 \x01(\t\"\x95\x02\n\x0fOverlayableItem\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12/\n\x06policy\x18\x03 \x03(\x0e\x32\x1f.aapt.pb.OverlayableItem.Policy\x12\x17\n\x0foverlayable_idx\x18\x04 \x01(\r\"\x85\x01\n\x06Policy\x12\x08\n\x04NONE\x10\x00\x12\n\n\x06PUBLIC\x10\x01\x12\n\n\x06SYSTEM\x10\x02\x12\n\n\x06VENDOR\x10\x03\x12\x0b\n\x07PRODUCT\x10\x04\x12\r\n\tSIGNATURE\x10\x05\x12\x07\n\x03ODM\x10\x06\x12\x07\n\x03OEM\x10\x07\x12\t\n\x05\x41\x43TOR\x10\x08\x12\x14\n\x10\x43ONFIG_SIGNATURE\x10\t\">\n\x08StagedId\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x11\n\tstaged_id\x18\x02 \x01(\r\"\x15\n\x07\x45ntryId\x12\n\n\x02id\x18\x01 \x01(\r\"\xc8\x02\n\x05\x45ntry\x12\"\n\x08\x65ntry_id\x18\x01 \x01(\x0b\x32\x10.aapt.pb.EntryId\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\'\n\nvisibility\x18\x03 \x01(\x0b\x32\x13.aapt.pb.Visibility\x12$\n\tallow_new\x18\x04 \x01(\x0b\x32\x11.aapt.pb.AllowNew\x12\x32\n\x10overlayable_item\x18\x05 \x01(\x0b\x32\x18.aapt.pb.OverlayableItem\x12*\n\x0c\x63onfig_value\x18\x06 \x03(\x0b\x32\x14.aapt.pb.ConfigValue\x12$\n\tstaged_id\x18\x07 \x01(\x0b\x32\x11.aapt.pb.StagedId\x12\x38\n\x1a\x66lag_disabled_config_value\x18\x08 \x03(\x0b\x32\x14.aapt.pb.ConfigValue\"Z\n\x0b\x43onfigValue\x12&\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x16.aapt.pb.Configuration\x12\x1d\n\x05value\x18\x02 \x01(\x0b\x32\x0e.aapt.pb.ValueJ\x04\x08\x03\x10\x04\"\xa1\x01\n\x05Value\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12\x0c\n\x04weak\x18\x03 \x01(\x08\x12\x1d\n\x04item\x18\x04 \x01(\x0b\x32\r.aapt.pb.ItemH\x00\x12\x30\n\x0e\x63ompound_value\x18\x05 \x01(\x0b\x32\x16.aapt.pb.CompoundValueH\x00\x42\x07\n\x05value\"\xcb\x02\n\x04Item\x12!\n\x03ref\x18\x01 \x01(\x0b\x32\x12.aapt.pb.ReferenceH\x00\x12\x1e\n\x03str\x18\x02 \x01(\x0b\x32\x0f.aapt.pb.StringH\x00\x12%\n\x07raw_str\x18\x03 \x01(\x0b\x32\x12.aapt.pb.RawStringH\x00\x12+\n\nstyled_str\x18\x04 \x01(\x0b\x32\x15.aapt.pb.StyledStringH\x00\x12&\n\x04\x66ile\x18\x05 \x01(\x0b\x32\x16.aapt.pb.FileReferenceH\x00\x12\x19\n\x02id\x18\x06 \x01(\x0b\x32\x0b.aapt.pb.IdH\x00\x12\"\n\x04prim\x18\x07 \x01(\x0b\x32\x12.aapt.pb.PrimitiveH\x00\x12\x13\n\x0b\x66lag_status\x18\x08 \x01(\r\x12\x14\n\x0c\x66lag_negated\x18\t \x01(\x08\x12\x11\n\tflag_name\x18\n \x01(\tB\x07\n\x05value\"\xad\x02\n\rCompoundValue\x12\"\n\x04\x61ttr\x18\x01 \x01(\x0b\x32\x12.aapt.pb.AttributeH\x00\x12\x1f\n\x05style\x18\x02 \x01(\x0b\x32\x0e.aapt.pb.StyleH\x00\x12\'\n\tstyleable\x18\x03 \x01(\x0b\x32\x12.aapt.pb.StyleableH\x00\x12\x1f\n\x05\x61rray\x18\x04 \x01(\x0b\x32\x0e.aapt.pb.ArrayH\x00\x12!\n\x06plural\x18\x05 \x01(\x0b\x32\x0f.aapt.pb.PluralH\x00\x12#\n\x05macro\x18\x06 \x01(\x0b\x32\x12.aapt.pb.MacroBodyH\x00\x12\x13\n\x0b\x66lag_status\x18\x07 \x01(\r\x12\x14\n\x0c\x66lag_negated\x18\x08 \x01(\x08\x12\x11\n\tflag_name\x18\t \x01(\tB\x07\n\x05value\"\x18\n\x07\x42oolean\x12\r\n\x05value\x18\x01 \x01(\x08\"\xd0\x01\n\tReference\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.aapt.pb.Reference.Type\x12\n\n\x02id\x18\x02 \x01(\r\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0f\n\x07private\x18\x04 \x01(\x08\x12$\n\nis_dynamic\x18\x05 \x01(\x0b\x32\x10.aapt.pb.Boolean\x12\x12\n\ntype_flags\x18\x06 \x01(\r\x12\x11\n\tallow_raw\x18\x07 \x01(\x08\"$\n\x04Type\x12\r\n\tREFERENCE\x10\x00\x12\r\n\tATTRIBUTE\x10\x01\"\x04\n\x02Id\"\x17\n\x06String\x12\r\n\x05value\x18\x01 \x01(\t\"\x1a\n\tRawString\x12\r\n\x05value\x18\x01 \x01(\t\"\x83\x01\n\x0cStyledString\x12\r\n\x05value\x18\x01 \x01(\t\x12(\n\x04span\x18\x02 \x03(\x0b\x32\x1a.aapt.pb.StyledString.Span\x1a:\n\x04Span\x12\x0b\n\x03tag\x18\x01 \x01(\t\x12\x12\n\nfirst_char\x18\x02 \x01(\r\x12\x11\n\tlast_char\x18\x03 \x01(\r\"\x85\x01\n\rFileReference\x12\x0c\n\x04path\x18\x01 \x01(\t\x12)\n\x04type\x18\x02 \x01(\x0e\x32\x1b.aapt.pb.FileReference.Type\";\n\x04Type\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x07\n\x03PNG\x10\x01\x12\x0e\n\nBINARY_XML\x10\x02\x12\r\n\tPROTO_XML\x10\x03\"\x83\x04\n\tPrimitive\x12\x31\n\nnull_value\x18\x01 \x01(\x0b\x32\x1b.aapt.pb.Primitive.NullTypeH\x00\x12\x33\n\x0b\x65mpty_value\x18\x02 \x01(\x0b\x32\x1c.aapt.pb.Primitive.EmptyTypeH\x00\x12\x15\n\x0b\x66loat_value\x18\x03 \x01(\x02H\x00\x12\x19\n\x0f\x64imension_value\x18\r \x01(\rH\x00\x12\x18\n\x0e\x66raction_value\x18\x0e \x01(\rH\x00\x12\x1b\n\x11int_decimal_value\x18\x06 \x01(\x05H\x00\x12\x1f\n\x15int_hexadecimal_value\x18\x07 \x01(\rH\x00\x12\x17\n\rboolean_value\x18\x08 \x01(\x08H\x00\x12\x1b\n\x11\x63olor_argb8_value\x18\t \x01(\rH\x00\x12\x1a\n\x10\x63olor_rgb8_value\x18\n \x01(\rH\x00\x12\x1b\n\x11\x63olor_argb4_value\x18\x0b \x01(\rH\x00\x12\x1a\n\x10\x63olor_rgb4_value\x18\x0c \x01(\rH\x00\x12(\n\x1a\x64imension_value_deprecated\x18\x04 \x01(\x02\x42\x02\x18\x01H\x00\x12\'\n\x19\x66raction_value_deprecated\x18\x05 \x01(\x02\x42\x02\x18\x01H\x00\x1a\n\n\x08NullType\x1a\x0b\n\tEmptyTypeB\r\n\x0boneof_value\"\x90\x03\n\tAttribute\x12\x14\n\x0c\x66ormat_flags\x18\x01 \x01(\r\x12\x0f\n\x07min_int\x18\x02 \x01(\x05\x12\x0f\n\x07max_int\x18\x03 \x01(\x05\x12)\n\x06symbol\x18\x04 \x03(\x0b\x32\x19.aapt.pb.Attribute.Symbol\x1ay\n\x06Symbol\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12 \n\x04name\x18\x03 \x01(\x0b\x32\x12.aapt.pb.Reference\x12\r\n\x05value\x18\x04 \x01(\r\x12\x0c\n\x04type\x18\x05 \x01(\r\"\xa4\x01\n\x0b\x46ormatFlags\x12\x08\n\x04NONE\x10\x00\x12\t\n\x03\x41NY\x10\xff\xff\x03\x12\r\n\tREFERENCE\x10\x01\x12\n\n\x06STRING\x10\x02\x12\x0b\n\x07INTEGER\x10\x04\x12\x0b\n\x07\x42OOLEAN\x10\x08\x12\t\n\x05\x43OLOR\x10\x10\x12\t\n\x05\x46LOAT\x10 \x12\r\n\tDIMENSION\x10@\x12\r\n\x08\x46RACTION\x10\x80\x01\x12\n\n\x04\x45NUM\x10\x80\x80\x04\x12\x0b\n\x05\x46LAGS\x10\x80\x80\x08\"\xf1\x01\n\x05Style\x12\"\n\x06parent\x18\x01 \x01(\x0b\x32\x12.aapt.pb.Reference\x12&\n\rparent_source\x18\x02 \x01(\x0b\x32\x0f.aapt.pb.Source\x12#\n\x05\x65ntry\x18\x03 \x03(\x0b\x32\x14.aapt.pb.Style.Entry\x1aw\n\x05\x45ntry\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12\x1f\n\x03key\x18\x03 \x01(\x0b\x32\x12.aapt.pb.Reference\x12\x1b\n\x04item\x18\x04 \x01(\x0b\x32\r.aapt.pb.Item\"\x91\x01\n\tStyleable\x12\'\n\x05\x65ntry\x18\x01 \x03(\x0b\x32\x18.aapt.pb.Styleable.Entry\x1a[\n\x05\x45ntry\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12 \n\x04\x61ttr\x18\x03 \x01(\x0b\x32\x12.aapt.pb.Reference\"\x8a\x01\n\x05\x41rray\x12\'\n\x07\x65lement\x18\x01 \x03(\x0b\x32\x16.aapt.pb.Array.Element\x1aX\n\x07\x45lement\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12\x1b\n\x04item\x18\x03 \x01(\x0b\x32\r.aapt.pb.Item\"\xef\x01\n\x06Plural\x12$\n\x05\x65ntry\x18\x01 \x03(\x0b\x32\x15.aapt.pb.Plural.Entry\x1a|\n\x05\x45ntry\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12$\n\x05\x61rity\x18\x03 \x01(\x0e\x32\x15.aapt.pb.Plural.Arity\x12\x1b\n\x04item\x18\x04 \x01(\x0b\x32\r.aapt.pb.Item\"A\n\x05\x41rity\x12\x08\n\x04ZERO\x10\x00\x12\x07\n\x03ONE\x10\x01\x12\x07\n\x03TWO\x10\x02\x12\x07\n\x03\x46\x45W\x10\x03\x12\x08\n\x04MANY\x10\x04\x12\t\n\x05OTHER\x10\x05\"r\n\x07XmlNode\x12&\n\x07\x65lement\x18\x01 \x01(\x0b\x32\x13.aapt.pb.XmlElementH\x00\x12\x0e\n\x04text\x18\x02 \x01(\tH\x00\x12\'\n\x06source\x18\x03 \x01(\x0b\x32\x17.aapt.pb.SourcePositionB\x06\n\x04node\"\xb2\x01\n\nXmlElement\x12\x34\n\x15namespace_declaration\x18\x01 \x03(\x0b\x32\x15.aapt.pb.XmlNamespace\x12\x15\n\rnamespace_uri\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12(\n\tattribute\x18\x04 \x03(\x0b\x32\x15.aapt.pb.XmlAttribute\x12\x1f\n\x05\x63hild\x18\x05 \x03(\x0b\x32\x10.aapt.pb.XmlNode\"T\n\x0cXmlNamespace\x12\x0e\n\x06prefix\x18\x01 \x01(\t\x12\x0b\n\x03uri\x18\x02 \x01(\t\x12\'\n\x06source\x18\x03 \x01(\x0b\x32\x17.aapt.pb.SourcePosition\"\xa6\x01\n\x0cXmlAttribute\x12\x15\n\rnamespace_uri\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\t\x12\'\n\x06source\x18\x04 \x01(\x0b\x32\x17.aapt.pb.SourcePosition\x12\x13\n\x0bresource_id\x18\x05 \x01(\r\x12$\n\rcompiled_item\x18\x06 \x01(\x0b\x32\r.aapt.pb.Item\"\xe7\x01\n\tMacroBody\x12\x12\n\nraw_string\x18\x01 \x01(\t\x12*\n\x0cstyle_string\x18\x02 \x01(\x0b\x32\x14.aapt.pb.StyleString\x12?\n\x17untranslatable_sections\x18\x03 \x03(\x0b\x32\x1e.aapt.pb.UntranslatableSection\x12\x30\n\x0fnamespace_stack\x18\x04 \x03(\x0b\x32\x17.aapt.pb.NamespaceAlias\x12\'\n\x06source\x18\x05 \x01(\x0b\x32\x17.aapt.pb.SourcePosition\"J\n\x0eNamespaceAlias\x12\x0e\n\x06prefix\x18\x01 \x01(\t\x12\x14\n\x0cpackage_name\x18\x02 \x01(\t\x12\x12\n\nis_private\x18\x03 \x01(\x08\"\x82\x01\n\x0bStyleString\x12\x0b\n\x03str\x18\x01 \x01(\t\x12(\n\x05spans\x18\x02 \x03(\x0b\x32\x19.aapt.pb.StyleString.Span\x1a<\n\x04Span\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0bstart_index\x18\x02 \x01(\r\x12\x11\n\tend_index\x18\x03 \x01(\r\"?\n\x15UntranslatableSection\x12\x13\n\x0bstart_index\x18\x01 \x01(\x04\x12\x11\n\tend_index\x18\x02 \x01(\x04\x42\x12\n\x10\x63om.android.aaptb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'frameworks.base.tools.aapt2.Resources_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\020com.android.aapt' + _globals['_PRIMITIVE'].fields_by_name['dimension_value_deprecated']._loaded_options = None + _globals['_PRIMITIVE'].fields_by_name['dimension_value_deprecated']._serialized_options = b'\030\001' + _globals['_PRIMITIVE'].fields_by_name['fraction_value_deprecated']._loaded_options = None + _globals['_PRIMITIVE'].fields_by_name['fraction_value_deprecated']._serialized_options = b'\030\001' + _globals['_STRINGPOOL']._serialized_start=105 + _globals['_STRINGPOOL']._serialized_end=131 + _globals['_SOURCEPOSITION']._serialized_start=133 + _globals['_SOURCEPOSITION']._serialized_end=193 + _globals['_SOURCE']._serialized_start=195 + _globals['_SOURCE']._serialized_end=264 + _globals['_TOOLFINGERPRINT']._serialized_start=266 + _globals['_TOOLFINGERPRINT']._serialized_end=314 + _globals['_DYNAMICREFTABLE']._serialized_start=316 + _globals['_DYNAMICREFTABLE']._serialized_end=395 + _globals['_RESOURCETABLE']._serialized_start=398 + _globals['_RESOURCETABLE']._serialized_end=638 + _globals['_PACKAGEID']._serialized_start=640 + _globals['_PACKAGEID']._serialized_end=663 + _globals['_PACKAGE']._serialized_start=665 + _globals['_PACKAGE']._serialized_end=765 + _globals['_TYPEID']._serialized_start=767 + _globals['_TYPEID']._serialized_end=787 + _globals['_TYPE']._serialized_start=789 + _globals['_TYPE']._serialized_end=874 + _globals['_VISIBILITY']._serialized_start=877 + _globals['_VISIBILITY']._serialized_end=1048 + _globals['_VISIBILITY_LEVEL']._serialized_start=1003 + _globals['_VISIBILITY_LEVEL']._serialized_end=1048 + _globals['_ALLOWNEW']._serialized_start=1050 + _globals['_ALLOWNEW']._serialized_end=1110 + _globals['_OVERLAYABLE']._serialized_start=1112 + _globals['_OVERLAYABLE']._serialized_end=1187 + _globals['_OVERLAYABLEITEM']._serialized_start=1190 + _globals['_OVERLAYABLEITEM']._serialized_end=1467 + _globals['_OVERLAYABLEITEM_POLICY']._serialized_start=1334 + _globals['_OVERLAYABLEITEM_POLICY']._serialized_end=1467 + _globals['_STAGEDID']._serialized_start=1469 + _globals['_STAGEDID']._serialized_end=1531 + _globals['_ENTRYID']._serialized_start=1533 + _globals['_ENTRYID']._serialized_end=1554 + _globals['_ENTRY']._serialized_start=1557 + _globals['_ENTRY']._serialized_end=1885 + _globals['_CONFIGVALUE']._serialized_start=1887 + _globals['_CONFIGVALUE']._serialized_end=1977 + _globals['_VALUE']._serialized_start=1980 + _globals['_VALUE']._serialized_end=2141 + _globals['_ITEM']._serialized_start=2144 + _globals['_ITEM']._serialized_end=2475 + _globals['_COMPOUNDVALUE']._serialized_start=2478 + _globals['_COMPOUNDVALUE']._serialized_end=2779 + _globals['_BOOLEAN']._serialized_start=2781 + _globals['_BOOLEAN']._serialized_end=2805 + _globals['_REFERENCE']._serialized_start=2808 + _globals['_REFERENCE']._serialized_end=3016 + _globals['_REFERENCE_TYPE']._serialized_start=2980 + _globals['_REFERENCE_TYPE']._serialized_end=3016 + _globals['_ID']._serialized_start=3018 + _globals['_ID']._serialized_end=3022 + _globals['_STRING']._serialized_start=3024 + _globals['_STRING']._serialized_end=3047 + _globals['_RAWSTRING']._serialized_start=3049 + _globals['_RAWSTRING']._serialized_end=3075 + _globals['_STYLEDSTRING']._serialized_start=3078 + _globals['_STYLEDSTRING']._serialized_end=3209 + _globals['_STYLEDSTRING_SPAN']._serialized_start=3151 + _globals['_STYLEDSTRING_SPAN']._serialized_end=3209 + _globals['_FILEREFERENCE']._serialized_start=3212 + _globals['_FILEREFERENCE']._serialized_end=3345 + _globals['_FILEREFERENCE_TYPE']._serialized_start=3286 + _globals['_FILEREFERENCE_TYPE']._serialized_end=3345 + _globals['_PRIMITIVE']._serialized_start=3348 + _globals['_PRIMITIVE']._serialized_end=3863 + _globals['_PRIMITIVE_NULLTYPE']._serialized_start=3825 + _globals['_PRIMITIVE_NULLTYPE']._serialized_end=3835 + _globals['_PRIMITIVE_EMPTYTYPE']._serialized_start=3837 + _globals['_PRIMITIVE_EMPTYTYPE']._serialized_end=3848 + _globals['_ATTRIBUTE']._serialized_start=3866 + _globals['_ATTRIBUTE']._serialized_end=4266 + _globals['_ATTRIBUTE_SYMBOL']._serialized_start=3978 + _globals['_ATTRIBUTE_SYMBOL']._serialized_end=4099 + _globals['_ATTRIBUTE_FORMATFLAGS']._serialized_start=4102 + _globals['_ATTRIBUTE_FORMATFLAGS']._serialized_end=4266 + _globals['_STYLE']._serialized_start=4269 + _globals['_STYLE']._serialized_end=4510 + _globals['_STYLE_ENTRY']._serialized_start=4391 + _globals['_STYLE_ENTRY']._serialized_end=4510 + _globals['_STYLEABLE']._serialized_start=4513 + _globals['_STYLEABLE']._serialized_end=4658 + _globals['_STYLEABLE_ENTRY']._serialized_start=4567 + _globals['_STYLEABLE_ENTRY']._serialized_end=4658 + _globals['_ARRAY']._serialized_start=4661 + _globals['_ARRAY']._serialized_end=4799 + _globals['_ARRAY_ELEMENT']._serialized_start=4711 + _globals['_ARRAY_ELEMENT']._serialized_end=4799 + _globals['_PLURAL']._serialized_start=4802 + _globals['_PLURAL']._serialized_end=5041 + _globals['_PLURAL_ENTRY']._serialized_start=4850 + _globals['_PLURAL_ENTRY']._serialized_end=4974 + _globals['_PLURAL_ARITY']._serialized_start=4976 + _globals['_PLURAL_ARITY']._serialized_end=5041 + _globals['_XMLNODE']._serialized_start=5043 + _globals['_XMLNODE']._serialized_end=5157 + _globals['_XMLELEMENT']._serialized_start=5160 + _globals['_XMLELEMENT']._serialized_end=5338 + _globals['_XMLNAMESPACE']._serialized_start=5340 + _globals['_XMLNAMESPACE']._serialized_end=5424 + _globals['_XMLATTRIBUTE']._serialized_start=5427 + _globals['_XMLATTRIBUTE']._serialized_end=5593 + _globals['_MACROBODY']._serialized_start=5596 + _globals['_MACROBODY']._serialized_end=5827 + _globals['_NAMESPACEALIAS']._serialized_start=5829 + _globals['_NAMESPACEALIAS']._serialized_end=5903 + _globals['_STYLESTRING']._serialized_start=5906 + _globals['_STYLESTRING']._serialized_end=6036 + _globals['_STYLESTRING_SPAN']._serialized_start=5976 + _globals['_STYLESTRING_SPAN']._serialized_end=6036 + _globals['_UNTRANSLATABLESECTION']._serialized_start=6038 + _globals['_UNTRANSLATABLESECTION']._serialized_end=6101 # @@protoc_insertion_point(module_scope) diff --git a/direct/src/dist/_proto/config_pb2.py b/direct/src/dist/_proto/config_pb2.py index dde4c7165c..67a2c7cc95 100644 --- a/direct/src/dist/_proto/config_pb2.py +++ b/direct/src/dist/_proto/config_pb2.py @@ -1,11 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: config.proto +# Protobuf Python Version: 6.33.0 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 0, + '', + 'config.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -13,1033 +24,74 @@ _sym_db = _symbol_database.Default() -DESCRIPTOR = _descriptor.FileDescriptor( - name='config.proto', - package='android.bundle', - syntax='proto3', - serialized_options=b'\n\022com.android.bundle', - create_key=_descriptor._internal_create_key, - serialized_pb=b'\n\x0c\x63onfig.proto\x12\x0e\x61ndroid.bundle\"\x91\x04\n\x0c\x42undleConfig\x12.\n\nbundletool\x18\x01 \x01(\x0b\x32\x1a.android.bundle.Bundletool\x12\x34\n\roptimizations\x18\x02 \x01(\x0b\x32\x1d.android.bundle.Optimizations\x12\x30\n\x0b\x63ompression\x18\x03 \x01(\x0b\x32\x1b.android.bundle.Compression\x12\x39\n\x10master_resources\x18\x04 \x01(\x0b\x32\x1f.android.bundle.MasterResources\x12/\n\x0b\x61pex_config\x18\x05 \x01(\x0b\x32\x1a.android.bundle.ApexConfig\x12O\n\x1cunsigned_embedded_apk_config\x18\x06 \x03(\x0b\x32).android.bundle.UnsignedEmbeddedApkConfig\x12@\n\x14\x61sset_modules_config\x18\x07 \x01(\x0b\x32\".android.bundle.AssetModulesConfig\x12\x35\n\x04type\x18\x08 \x01(\x0e\x32\'.android.bundle.BundleConfig.BundleType\"3\n\nBundleType\x12\x0b\n\x07REGULAR\x10\x00\x12\x08\n\x04\x41PEX\x10\x01\x12\x0e\n\nASSET_ONLY\x10\x02\"#\n\nBundletool\x12\x0f\n\x07version\x18\x02 \x01(\tJ\x04\x08\x01\x10\x02\"\xe0\x01\n\x0b\x43ompression\x12\x19\n\x11uncompressed_glob\x18\x01 \x03(\t\x12i\n-install_time_asset_module_default_compression\x18\x02 \x01(\x0e\x32\x32.android.bundle.Compression.AssetModuleCompression\"K\n\x16\x41ssetModuleCompression\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x10\n\x0cUNCOMPRESSED\x10\x01\x12\x0e\n\nCOMPRESSED\x10\x02\"?\n\x0fMasterResources\x12\x14\n\x0cresource_ids\x18\x01 \x03(\x05\x12\x16\n\x0eresource_names\x18\x02 \x03(\t\"\x8f\x03\n\rOptimizations\x12\x33\n\rsplits_config\x18\x01 \x01(\x0b\x32\x1c.android.bundle.SplitsConfig\x12N\n\x1buncompress_native_libraries\x18\x02 \x01(\x0b\x32).android.bundle.UncompressNativeLibraries\x12@\n\x14uncompress_dex_files\x18\x03 \x01(\x0b\x32\".android.bundle.UncompressDexFiles\x12;\n\x11standalone_config\x18\x04 \x01(\x0b\x32 .android.bundle.StandaloneConfig\x12\x45\n\x16resource_optimizations\x18\x05 \x01(\x0b\x32%.android.bundle.ResourceOptimizations\x12\x33\n\rstore_archive\x18\x06 \x01(\x0b\x32\x1c.android.bundle.StoreArchive\"\x97\x01\n\x15ResourceOptimizations\x12M\n\x0fsparse_encoding\x18\x01 \x01(\x0e\x32\x34.android.bundle.ResourceOptimizations.SparseEncoding\"/\n\x0eSparseEncoding\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x0c\n\x08\x45NFORCED\x10\x01\",\n\x19UncompressNativeLibraries\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\"%\n\x12UncompressDexFiles\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\"\x1f\n\x0cStoreArchive\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\"G\n\x0cSplitsConfig\x12\x37\n\x0fsplit_dimension\x18\x01 \x03(\x0b\x32\x1e.android.bundle.SplitDimension\"\xfa\x01\n\x10StandaloneConfig\x12\x37\n\x0fsplit_dimension\x18\x01 \x03(\x0b\x32\x1e.android.bundle.SplitDimension\x12\x1e\n\x16strip_64_bit_libraries\x18\x02 \x01(\x08\x12Q\n\x14\x64\x65x_merging_strategy\x18\x03 \x01(\x0e\x32\x33.android.bundle.StandaloneConfig.DexMergingStrategy\":\n\x12\x44\x65xMergingStrategy\x12\x13\n\x0fMERGE_IF_NEEDED\x10\x00\x12\x0f\n\x0bNEVER_MERGE\x10\x01\"\x8c\x02\n\x0eSplitDimension\x12\x33\n\x05value\x18\x01 \x01(\x0e\x32$.android.bundle.SplitDimension.Value\x12\x0e\n\x06negate\x18\x02 \x01(\x08\x12\x39\n\x10suffix_stripping\x18\x03 \x01(\x0b\x32\x1f.android.bundle.SuffixStripping\"z\n\x05Value\x12\x15\n\x11UNSPECIFIED_VALUE\x10\x00\x12\x07\n\x03\x41\x42I\x10\x01\x12\x12\n\x0eSCREEN_DENSITY\x10\x02\x12\x0c\n\x08LANGUAGE\x10\x03\x12\x1e\n\x1aTEXTURE_COMPRESSION_FORMAT\x10\x04\x12\x0f\n\x0b\x44\x45VICE_TIER\x10\x06\":\n\x0fSuffixStripping\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x16\n\x0e\x64\x65\x66\x61ult_suffix\x18\x02 \x01(\t\"U\n\nApexConfig\x12G\n\x18\x61pex_embedded_apk_config\x18\x01 \x03(\x0b\x32%.android.bundle.ApexEmbeddedApkConfig\";\n\x15\x41pexEmbeddedApkConfig\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\")\n\x19UnsignedEmbeddedApkConfig\x12\x0c\n\x04path\x18\x01 \x01(\t\"D\n\x12\x41ssetModulesConfig\x12\x13\n\x0b\x61pp_version\x18\x01 \x03(\x03\x12\x19\n\x11\x61sset_version_tag\x18\x02 \x01(\tB\x14\n\x12\x63om.android.bundleb\x06proto3' -) +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x63onfig.proto\x12\x0e\x61ndroid.bundle\"\xbb\x04\n\x0c\x42undleConfig\x12.\n\nbundletool\x18\x01 \x01(\x0b\x32\x1a.android.bundle.Bundletool\x12\x34\n\roptimizations\x18\x02 \x01(\x0b\x32\x1d.android.bundle.Optimizations\x12\x30\n\x0b\x63ompression\x18\x03 \x01(\x0b\x32\x1b.android.bundle.Compression\x12\x39\n\x10master_resources\x18\x04 \x01(\x0b\x32\x1f.android.bundle.MasterResources\x12/\n\x0b\x61pex_config\x18\x05 \x01(\x0b\x32\x1a.android.bundle.ApexConfig\x12O\n\x1cunsigned_embedded_apk_config\x18\x06 \x03(\x0b\x32).android.bundle.UnsignedEmbeddedApkConfig\x12@\n\x14\x61sset_modules_config\x18\x07 \x01(\x0b\x32\".android.bundle.AssetModulesConfig\x12\x35\n\x04type\x18\x08 \x01(\x0e\x32\'.android.bundle.BundleConfig.BundleType\x12(\n\x07locales\x18\t \x01(\x0b\x32\x17.android.bundle.Locales\"3\n\nBundleType\x12\x0b\n\x07REGULAR\x10\x00\x12\x08\n\x04\x41PEX\x10\x01\x12\x0e\n\nASSET_ONLY\x10\x02\"#\n\nBundletool\x12\x0f\n\x07version\x18\x02 \x01(\tJ\x04\x08\x01\x10\x02\"\x85\x03\n\x0b\x43ompression\x12\x19\n\x11uncompressed_glob\x18\x01 \x03(\t\x12i\n-install_time_asset_module_default_compression\x18\x02 \x01(\x0e\x32\x32.android.bundle.Compression.AssetModuleCompression\x12V\n\x19\x61pk_compression_algorithm\x18\x03 \x01(\x0e\x32\x33.android.bundle.Compression.ApkCompressionAlgorithm\"K\n\x16\x41ssetModuleCompression\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x10\n\x0cUNCOMPRESSED\x10\x01\x12\x0e\n\nCOMPRESSED\x10\x02\"K\n\x17\x41pkCompressionAlgorithm\x12%\n!DEFAULT_APK_COMPRESSION_ALGORITHM\x10\x00\x12\t\n\x05P7ZIP\x10\x01\"?\n\x0fMasterResources\x12\x14\n\x0cresource_ids\x18\x01 \x03(\x05\x12\x16\n\x0eresource_names\x18\x02 \x03(\t\"\xa7\x03\n\rOptimizations\x12\x33\n\rsplits_config\x18\x01 \x01(\x0b\x32\x1c.android.bundle.SplitsConfig\x12N\n\x1buncompress_native_libraries\x18\x02 \x01(\x0b\x32).android.bundle.UncompressNativeLibraries\x12@\n\x14uncompress_dex_files\x18\x03 \x01(\x0b\x32\".android.bundle.UncompressDexFiles\x12;\n\x11standalone_config\x18\x04 \x01(\x0b\x32 .android.bundle.StandaloneConfig\x12\x45\n\x16resource_optimizations\x18\x05 \x01(\x0b\x32%.android.bundle.ResourceOptimizations\x12\x33\n\rstore_archive\x18\x06 \x01(\x0b\x32\x1c.android.bundle.StoreArchive\x12\x16\n\x0einject_min_sdk\x18\x07 \x01(\x08\"\xa4\x04\n\x15ResourceOptimizations\x12M\n\x0fsparse_encoding\x18\x01 \x01(\x0e\x32\x34.android.bundle.ResourceOptimizations.SparseEncoding\x12^\n\x18\x63ollapsed_resource_names\x18\x02 \x01(\x0b\x32<.android.bundle.ResourceOptimizations.CollapsedResourceNames\x1a\x31\n\x13ResourceTypeAndName\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x1a\xdd\x01\n\x16\x43ollapsedResourceNames\x12\x1f\n\x17\x63ollapse_resource_names\x18\x01 \x01(\x08\x12X\n\x15no_collapse_resources\x18\x02 \x03(\x0b\x32\x39.android.bundle.ResourceOptimizations.ResourceTypeAndName\x12\"\n\x1ano_collapse_resource_types\x18\x04 \x03(\t\x12$\n\x1c\x64\x65\x64uplicate_resource_entries\x18\x03 \x01(\x08\"I\n\x0eSparseEncoding\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x16\n\x12VARIANT_FOR_SDK_32\x10\x02\"\x04\x08\x01\x10\x01*\x08\x45NFORCED\"\xf0\x01\n\x19UncompressNativeLibraries\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12J\n\talignment\x18\x02 \x01(\x0e\x32\x37.android.bundle.UncompressNativeLibraries.PageAlignment\"v\n\rPageAlignment\x12\x1e\n\x1aPAGE_ALIGNMENT_UNSPECIFIED\x10\x00\x12\x15\n\x11PAGE_ALIGNMENT_4K\x10\x01\x12\x16\n\x12PAGE_ALIGNMENT_16K\x10\x02\x12\x16\n\x12PAGE_ALIGNMENT_64K\x10\x03\"\xd2\x01\n\x12UncompressDexFiles\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12`\n\x1buncompressed_dex_target_sdk\x18\x02 \x01(\x0e\x32;.android.bundle.UncompressDexFiles.UncompressedDexTargetSdk\"I\n\x18UncompressedDexTargetSdk\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\n\n\x06SDK_31\x10\x01\x12\n\n\x06SDK_33\x10\x03\"\x04\x08\x02\x10\x02\"\x1f\n\x0cStoreArchive\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\"\'\n\x07Locales\x12\x1c\n\x14inject_locale_config\x18\x01 \x01(\x08\"G\n\x0cSplitsConfig\x12\x37\n\x0fsplit_dimension\x18\x01 \x03(\x0b\x32\x1e.android.bundle.SplitDimension\"\x9c\x03\n\x10StandaloneConfig\x12\x37\n\x0fsplit_dimension\x18\x01 \x03(\x0b\x32\x1e.android.bundle.SplitDimension\x12\x1e\n\x16strip_64_bit_libraries\x18\x02 \x01(\x08\x12Q\n\x14\x64\x65x_merging_strategy\x18\x03 \x01(\x0e\x32\x33.android.bundle.StandaloneConfig.DexMergingStrategy\x12Q\n\x14\x66\x65\x61ture_modules_mode\x18\x04 \x01(\x0e\x32\x33.android.bundle.StandaloneConfig.FeatureModulesMode\":\n\x12\x44\x65xMergingStrategy\x12\x13\n\x0fMERGE_IF_NEEDED\x10\x00\x12\x0f\n\x0bNEVER_MERGE\x10\x01\"M\n\x12\x46\x65\x61tureModulesMode\x12\x19\n\x15\x46USED_FEATURE_MODULES\x10\x00\x12\x1c\n\x18SEPARATE_FEATURE_MODULES\x10\x01\"\xca\x02\n\x0eSplitDimension\x12\x33\n\x05value\x18\x01 \x01(\x0e\x32$.android.bundle.SplitDimension.Value\x12\x0e\n\x06negate\x18\x02 \x01(\x08\x12\x39\n\x10suffix_stripping\x18\x03 \x01(\x0b\x32\x1f.android.bundle.SuffixStripping\"\xb7\x01\n\x05Value\x12\x15\n\x11UNSPECIFIED_VALUE\x10\x00\x12\x07\n\x03\x41\x42I\x10\x01\x12\x12\n\x0eSCREEN_DENSITY\x10\x02\x12\x0c\n\x08LANGUAGE\x10\x03\x12\x1e\n\x1aTEXTURE_COMPRESSION_FORMAT\x10\x04\x12\x13\n\x0b\x44\x45VICE_TIER\x10\x06\x1a\x02\x08\x01\x12\x0f\n\x0b\x43OUNTRY_SET\x10\x07\x12\x14\n\x10\x41I_MODEL_VERSION\x10\x08\x12\x10\n\x0c\x44\x45VICE_GROUP\x10\t\":\n\x0fSuffixStripping\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x16\n\x0e\x64\x65\x66\x61ult_suffix\x18\x02 \x01(\t\"\x91\x01\n\nApexConfig\x12G\n\x18\x61pex_embedded_apk_config\x18\x01 \x03(\x0b\x32%.android.bundle.ApexEmbeddedApkConfig\x12:\n\x11supported_abi_set\x18\x02 \x03(\x0b\x32\x1f.android.bundle.SupportedAbiSet\"\x1e\n\x0fSupportedAbiSet\x12\x0b\n\x03\x61\x62i\x18\x01 \x03(\t\";\n\x15\x41pexEmbeddedApkConfig\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\")\n\x19UnsignedEmbeddedApkConfig\x12\x0c\n\x04path\x18\x01 \x01(\t\"D\n\x12\x41ssetModulesConfig\x12\x13\n\x0b\x61pp_version\x18\x01 \x03(\x03\x12\x19\n\x11\x61sset_version_tag\x18\x02 \x01(\tB\x14\n\x12\x63om.android.bundleb\x06proto3') - - -_BUNDLECONFIG_BUNDLETYPE = _descriptor.EnumDescriptor( - name='BundleType', - full_name='android.bundle.BundleConfig.BundleType', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='REGULAR', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='APEX', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ASSET_ONLY', index=2, number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=511, - serialized_end=562, -) -_sym_db.RegisterEnumDescriptor(_BUNDLECONFIG_BUNDLETYPE) - -_COMPRESSION_ASSETMODULECOMPRESSION = _descriptor.EnumDescriptor( - name='AssetModuleCompression', - full_name='android.bundle.Compression.AssetModuleCompression', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='UNSPECIFIED', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='UNCOMPRESSED', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='COMPRESSED', index=2, number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=751, - serialized_end=826, -) -_sym_db.RegisterEnumDescriptor(_COMPRESSION_ASSETMODULECOMPRESSION) - -_RESOURCEOPTIMIZATIONS_SPARSEENCODING = _descriptor.EnumDescriptor( - name='SparseEncoding', - full_name='android.bundle.ResourceOptimizations.SparseEncoding', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='UNSPECIFIED', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ENFORCED', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=1400, - serialized_end=1447, -) -_sym_db.RegisterEnumDescriptor(_RESOURCEOPTIMIZATIONS_SPARSEENCODING) - -_STANDALONECONFIG_DEXMERGINGSTRATEGY = _descriptor.EnumDescriptor( - name='DexMergingStrategy', - full_name='android.bundle.StandaloneConfig.DexMergingStrategy', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='MERGE_IF_NEEDED', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='NEVER_MERGE', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=1833, - serialized_end=1891, -) -_sym_db.RegisterEnumDescriptor(_STANDALONECONFIG_DEXMERGINGSTRATEGY) - -_SPLITDIMENSION_VALUE = _descriptor.EnumDescriptor( - name='Value', - full_name='android.bundle.SplitDimension.Value', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='UNSPECIFIED_VALUE', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ABI', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='SCREEN_DENSITY', index=2, number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='LANGUAGE', index=3, number=3, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='TEXTURE_COMPRESSION_FORMAT', index=4, number=4, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='DEVICE_TIER', index=5, number=6, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=2040, - serialized_end=2162, -) -_sym_db.RegisterEnumDescriptor(_SPLITDIMENSION_VALUE) - - -_BUNDLECONFIG = _descriptor.Descriptor( - name='BundleConfig', - full_name='android.bundle.BundleConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='bundletool', full_name='android.bundle.BundleConfig.bundletool', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='optimizations', full_name='android.bundle.BundleConfig.optimizations', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='compression', full_name='android.bundle.BundleConfig.compression', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='master_resources', full_name='android.bundle.BundleConfig.master_resources', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='apex_config', full_name='android.bundle.BundleConfig.apex_config', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='unsigned_embedded_apk_config', full_name='android.bundle.BundleConfig.unsigned_embedded_apk_config', index=5, - number=6, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='asset_modules_config', full_name='android.bundle.BundleConfig.asset_modules_config', index=6, - number=7, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='type', full_name='android.bundle.BundleConfig.type', index=7, - number=8, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _BUNDLECONFIG_BUNDLETYPE, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=33, - serialized_end=562, -) - - -_BUNDLETOOL = _descriptor.Descriptor( - name='Bundletool', - full_name='android.bundle.Bundletool', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='version', full_name='android.bundle.Bundletool.version', index=0, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=564, - serialized_end=599, -) - - -_COMPRESSION = _descriptor.Descriptor( - name='Compression', - full_name='android.bundle.Compression', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='uncompressed_glob', full_name='android.bundle.Compression.uncompressed_glob', index=0, - number=1, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='install_time_asset_module_default_compression', full_name='android.bundle.Compression.install_time_asset_module_default_compression', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _COMPRESSION_ASSETMODULECOMPRESSION, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=602, - serialized_end=826, -) - - -_MASTERRESOURCES = _descriptor.Descriptor( - name='MasterResources', - full_name='android.bundle.MasterResources', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='resource_ids', full_name='android.bundle.MasterResources.resource_ids', index=0, - number=1, type=5, cpp_type=1, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='resource_names', full_name='android.bundle.MasterResources.resource_names', index=1, - number=2, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=828, - serialized_end=891, -) - - -_OPTIMIZATIONS = _descriptor.Descriptor( - name='Optimizations', - full_name='android.bundle.Optimizations', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='splits_config', full_name='android.bundle.Optimizations.splits_config', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='uncompress_native_libraries', full_name='android.bundle.Optimizations.uncompress_native_libraries', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='uncompress_dex_files', full_name='android.bundle.Optimizations.uncompress_dex_files', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='standalone_config', full_name='android.bundle.Optimizations.standalone_config', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='resource_optimizations', full_name='android.bundle.Optimizations.resource_optimizations', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='store_archive', full_name='android.bundle.Optimizations.store_archive', index=5, - number=6, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=894, - serialized_end=1293, -) - - -_RESOURCEOPTIMIZATIONS = _descriptor.Descriptor( - name='ResourceOptimizations', - full_name='android.bundle.ResourceOptimizations', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='sparse_encoding', full_name='android.bundle.ResourceOptimizations.sparse_encoding', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _RESOURCEOPTIMIZATIONS_SPARSEENCODING, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1296, - serialized_end=1447, -) - - -_UNCOMPRESSNATIVELIBRARIES = _descriptor.Descriptor( - name='UncompressNativeLibraries', - full_name='android.bundle.UncompressNativeLibraries', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='enabled', full_name='android.bundle.UncompressNativeLibraries.enabled', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1449, - serialized_end=1493, -) - - -_UNCOMPRESSDEXFILES = _descriptor.Descriptor( - name='UncompressDexFiles', - full_name='android.bundle.UncompressDexFiles', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='enabled', full_name='android.bundle.UncompressDexFiles.enabled', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1495, - serialized_end=1532, -) - - -_STOREARCHIVE = _descriptor.Descriptor( - name='StoreArchive', - full_name='android.bundle.StoreArchive', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='enabled', full_name='android.bundle.StoreArchive.enabled', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1534, - serialized_end=1565, -) - - -_SPLITSCONFIG = _descriptor.Descriptor( - name='SplitsConfig', - full_name='android.bundle.SplitsConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='split_dimension', full_name='android.bundle.SplitsConfig.split_dimension', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1567, - serialized_end=1638, -) - - -_STANDALONECONFIG = _descriptor.Descriptor( - name='StandaloneConfig', - full_name='android.bundle.StandaloneConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='split_dimension', full_name='android.bundle.StandaloneConfig.split_dimension', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='strip_64_bit_libraries', full_name='android.bundle.StandaloneConfig.strip_64_bit_libraries', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='dex_merging_strategy', full_name='android.bundle.StandaloneConfig.dex_merging_strategy', index=2, - number=3, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _STANDALONECONFIG_DEXMERGINGSTRATEGY, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1641, - serialized_end=1891, -) - - -_SPLITDIMENSION = _descriptor.Descriptor( - name='SplitDimension', - full_name='android.bundle.SplitDimension', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='android.bundle.SplitDimension.value', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='negate', full_name='android.bundle.SplitDimension.negate', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='suffix_stripping', full_name='android.bundle.SplitDimension.suffix_stripping', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _SPLITDIMENSION_VALUE, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1894, - serialized_end=2162, -) - - -_SUFFIXSTRIPPING = _descriptor.Descriptor( - name='SuffixStripping', - full_name='android.bundle.SuffixStripping', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='enabled', full_name='android.bundle.SuffixStripping.enabled', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='default_suffix', full_name='android.bundle.SuffixStripping.default_suffix', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2164, - serialized_end=2222, -) - - -_APEXCONFIG = _descriptor.Descriptor( - name='ApexConfig', - full_name='android.bundle.ApexConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='apex_embedded_apk_config', full_name='android.bundle.ApexConfig.apex_embedded_apk_config', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2224, - serialized_end=2309, -) - - -_APEXEMBEDDEDAPKCONFIG = _descriptor.Descriptor( - name='ApexEmbeddedApkConfig', - full_name='android.bundle.ApexEmbeddedApkConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='package_name', full_name='android.bundle.ApexEmbeddedApkConfig.package_name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='path', full_name='android.bundle.ApexEmbeddedApkConfig.path', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2311, - serialized_end=2370, -) - - -_UNSIGNEDEMBEDDEDAPKCONFIG = _descriptor.Descriptor( - name='UnsignedEmbeddedApkConfig', - full_name='android.bundle.UnsignedEmbeddedApkConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='path', full_name='android.bundle.UnsignedEmbeddedApkConfig.path', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2372, - serialized_end=2413, -) - - -_ASSETMODULESCONFIG = _descriptor.Descriptor( - name='AssetModulesConfig', - full_name='android.bundle.AssetModulesConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='app_version', full_name='android.bundle.AssetModulesConfig.app_version', index=0, - number=1, type=3, cpp_type=2, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='asset_version_tag', full_name='android.bundle.AssetModulesConfig.asset_version_tag', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2415, - serialized_end=2483, -) - -_BUNDLECONFIG.fields_by_name['bundletool'].message_type = _BUNDLETOOL -_BUNDLECONFIG.fields_by_name['optimizations'].message_type = _OPTIMIZATIONS -_BUNDLECONFIG.fields_by_name['compression'].message_type = _COMPRESSION -_BUNDLECONFIG.fields_by_name['master_resources'].message_type = _MASTERRESOURCES -_BUNDLECONFIG.fields_by_name['apex_config'].message_type = _APEXCONFIG -_BUNDLECONFIG.fields_by_name['unsigned_embedded_apk_config'].message_type = _UNSIGNEDEMBEDDEDAPKCONFIG -_BUNDLECONFIG.fields_by_name['asset_modules_config'].message_type = _ASSETMODULESCONFIG -_BUNDLECONFIG.fields_by_name['type'].enum_type = _BUNDLECONFIG_BUNDLETYPE -_BUNDLECONFIG_BUNDLETYPE.containing_type = _BUNDLECONFIG -_COMPRESSION.fields_by_name['install_time_asset_module_default_compression'].enum_type = _COMPRESSION_ASSETMODULECOMPRESSION -_COMPRESSION_ASSETMODULECOMPRESSION.containing_type = _COMPRESSION -_OPTIMIZATIONS.fields_by_name['splits_config'].message_type = _SPLITSCONFIG -_OPTIMIZATIONS.fields_by_name['uncompress_native_libraries'].message_type = _UNCOMPRESSNATIVELIBRARIES -_OPTIMIZATIONS.fields_by_name['uncompress_dex_files'].message_type = _UNCOMPRESSDEXFILES -_OPTIMIZATIONS.fields_by_name['standalone_config'].message_type = _STANDALONECONFIG -_OPTIMIZATIONS.fields_by_name['resource_optimizations'].message_type = _RESOURCEOPTIMIZATIONS -_OPTIMIZATIONS.fields_by_name['store_archive'].message_type = _STOREARCHIVE -_RESOURCEOPTIMIZATIONS.fields_by_name['sparse_encoding'].enum_type = _RESOURCEOPTIMIZATIONS_SPARSEENCODING -_RESOURCEOPTIMIZATIONS_SPARSEENCODING.containing_type = _RESOURCEOPTIMIZATIONS -_SPLITSCONFIG.fields_by_name['split_dimension'].message_type = _SPLITDIMENSION -_STANDALONECONFIG.fields_by_name['split_dimension'].message_type = _SPLITDIMENSION -_STANDALONECONFIG.fields_by_name['dex_merging_strategy'].enum_type = _STANDALONECONFIG_DEXMERGINGSTRATEGY -_STANDALONECONFIG_DEXMERGINGSTRATEGY.containing_type = _STANDALONECONFIG -_SPLITDIMENSION.fields_by_name['value'].enum_type = _SPLITDIMENSION_VALUE -_SPLITDIMENSION.fields_by_name['suffix_stripping'].message_type = _SUFFIXSTRIPPING -_SPLITDIMENSION_VALUE.containing_type = _SPLITDIMENSION -_APEXCONFIG.fields_by_name['apex_embedded_apk_config'].message_type = _APEXEMBEDDEDAPKCONFIG -DESCRIPTOR.message_types_by_name['BundleConfig'] = _BUNDLECONFIG -DESCRIPTOR.message_types_by_name['Bundletool'] = _BUNDLETOOL -DESCRIPTOR.message_types_by_name['Compression'] = _COMPRESSION -DESCRIPTOR.message_types_by_name['MasterResources'] = _MASTERRESOURCES -DESCRIPTOR.message_types_by_name['Optimizations'] = _OPTIMIZATIONS -DESCRIPTOR.message_types_by_name['ResourceOptimizations'] = _RESOURCEOPTIMIZATIONS -DESCRIPTOR.message_types_by_name['UncompressNativeLibraries'] = _UNCOMPRESSNATIVELIBRARIES -DESCRIPTOR.message_types_by_name['UncompressDexFiles'] = _UNCOMPRESSDEXFILES -DESCRIPTOR.message_types_by_name['StoreArchive'] = _STOREARCHIVE -DESCRIPTOR.message_types_by_name['SplitsConfig'] = _SPLITSCONFIG -DESCRIPTOR.message_types_by_name['StandaloneConfig'] = _STANDALONECONFIG -DESCRIPTOR.message_types_by_name['SplitDimension'] = _SPLITDIMENSION -DESCRIPTOR.message_types_by_name['SuffixStripping'] = _SUFFIXSTRIPPING -DESCRIPTOR.message_types_by_name['ApexConfig'] = _APEXCONFIG -DESCRIPTOR.message_types_by_name['ApexEmbeddedApkConfig'] = _APEXEMBEDDEDAPKCONFIG -DESCRIPTOR.message_types_by_name['UnsignedEmbeddedApkConfig'] = _UNSIGNEDEMBEDDEDAPKCONFIG -DESCRIPTOR.message_types_by_name['AssetModulesConfig'] = _ASSETMODULESCONFIG -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -BundleConfig = _reflection.GeneratedProtocolMessageType('BundleConfig', (_message.Message,), { - 'DESCRIPTOR' : _BUNDLECONFIG, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.BundleConfig) - }) -_sym_db.RegisterMessage(BundleConfig) - -Bundletool = _reflection.GeneratedProtocolMessageType('Bundletool', (_message.Message,), { - 'DESCRIPTOR' : _BUNDLETOOL, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.Bundletool) - }) -_sym_db.RegisterMessage(Bundletool) - -Compression = _reflection.GeneratedProtocolMessageType('Compression', (_message.Message,), { - 'DESCRIPTOR' : _COMPRESSION, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.Compression) - }) -_sym_db.RegisterMessage(Compression) - -MasterResources = _reflection.GeneratedProtocolMessageType('MasterResources', (_message.Message,), { - 'DESCRIPTOR' : _MASTERRESOURCES, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.MasterResources) - }) -_sym_db.RegisterMessage(MasterResources) - -Optimizations = _reflection.GeneratedProtocolMessageType('Optimizations', (_message.Message,), { - 'DESCRIPTOR' : _OPTIMIZATIONS, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.Optimizations) - }) -_sym_db.RegisterMessage(Optimizations) - -ResourceOptimizations = _reflection.GeneratedProtocolMessageType('ResourceOptimizations', (_message.Message,), { - 'DESCRIPTOR' : _RESOURCEOPTIMIZATIONS, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.ResourceOptimizations) - }) -_sym_db.RegisterMessage(ResourceOptimizations) - -UncompressNativeLibraries = _reflection.GeneratedProtocolMessageType('UncompressNativeLibraries', (_message.Message,), { - 'DESCRIPTOR' : _UNCOMPRESSNATIVELIBRARIES, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.UncompressNativeLibraries) - }) -_sym_db.RegisterMessage(UncompressNativeLibraries) - -UncompressDexFiles = _reflection.GeneratedProtocolMessageType('UncompressDexFiles', (_message.Message,), { - 'DESCRIPTOR' : _UNCOMPRESSDEXFILES, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.UncompressDexFiles) - }) -_sym_db.RegisterMessage(UncompressDexFiles) - -StoreArchive = _reflection.GeneratedProtocolMessageType('StoreArchive', (_message.Message,), { - 'DESCRIPTOR' : _STOREARCHIVE, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.StoreArchive) - }) -_sym_db.RegisterMessage(StoreArchive) - -SplitsConfig = _reflection.GeneratedProtocolMessageType('SplitsConfig', (_message.Message,), { - 'DESCRIPTOR' : _SPLITSCONFIG, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.SplitsConfig) - }) -_sym_db.RegisterMessage(SplitsConfig) - -StandaloneConfig = _reflection.GeneratedProtocolMessageType('StandaloneConfig', (_message.Message,), { - 'DESCRIPTOR' : _STANDALONECONFIG, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.StandaloneConfig) - }) -_sym_db.RegisterMessage(StandaloneConfig) - -SplitDimension = _reflection.GeneratedProtocolMessageType('SplitDimension', (_message.Message,), { - 'DESCRIPTOR' : _SPLITDIMENSION, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.SplitDimension) - }) -_sym_db.RegisterMessage(SplitDimension) - -SuffixStripping = _reflection.GeneratedProtocolMessageType('SuffixStripping', (_message.Message,), { - 'DESCRIPTOR' : _SUFFIXSTRIPPING, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.SuffixStripping) - }) -_sym_db.RegisterMessage(SuffixStripping) - -ApexConfig = _reflection.GeneratedProtocolMessageType('ApexConfig', (_message.Message,), { - 'DESCRIPTOR' : _APEXCONFIG, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.ApexConfig) - }) -_sym_db.RegisterMessage(ApexConfig) - -ApexEmbeddedApkConfig = _reflection.GeneratedProtocolMessageType('ApexEmbeddedApkConfig', (_message.Message,), { - 'DESCRIPTOR' : _APEXEMBEDDEDAPKCONFIG, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.ApexEmbeddedApkConfig) - }) -_sym_db.RegisterMessage(ApexEmbeddedApkConfig) - -UnsignedEmbeddedApkConfig = _reflection.GeneratedProtocolMessageType('UnsignedEmbeddedApkConfig', (_message.Message,), { - 'DESCRIPTOR' : _UNSIGNEDEMBEDDEDAPKCONFIG, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.UnsignedEmbeddedApkConfig) - }) -_sym_db.RegisterMessage(UnsignedEmbeddedApkConfig) - -AssetModulesConfig = _reflection.GeneratedProtocolMessageType('AssetModulesConfig', (_message.Message,), { - 'DESCRIPTOR' : _ASSETMODULESCONFIG, - '__module__' : 'config_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.AssetModulesConfig) - }) -_sym_db.RegisterMessage(AssetModulesConfig) - - -DESCRIPTOR._options = None +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'config_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\022com.android.bundle' + _globals['_SPLITDIMENSION_VALUE'].values_by_name["DEVICE_TIER"]._loaded_options = None + _globals['_SPLITDIMENSION_VALUE'].values_by_name["DEVICE_TIER"]._serialized_options = b'\010\001' + _globals['_BUNDLECONFIG']._serialized_start=33 + _globals['_BUNDLECONFIG']._serialized_end=604 + _globals['_BUNDLECONFIG_BUNDLETYPE']._serialized_start=553 + _globals['_BUNDLECONFIG_BUNDLETYPE']._serialized_end=604 + _globals['_BUNDLETOOL']._serialized_start=606 + _globals['_BUNDLETOOL']._serialized_end=641 + _globals['_COMPRESSION']._serialized_start=644 + _globals['_COMPRESSION']._serialized_end=1033 + _globals['_COMPRESSION_ASSETMODULECOMPRESSION']._serialized_start=881 + _globals['_COMPRESSION_ASSETMODULECOMPRESSION']._serialized_end=956 + _globals['_COMPRESSION_APKCOMPRESSIONALGORITHM']._serialized_start=958 + _globals['_COMPRESSION_APKCOMPRESSIONALGORITHM']._serialized_end=1033 + _globals['_MASTERRESOURCES']._serialized_start=1035 + _globals['_MASTERRESOURCES']._serialized_end=1098 + _globals['_OPTIMIZATIONS']._serialized_start=1101 + _globals['_OPTIMIZATIONS']._serialized_end=1524 + _globals['_RESOURCEOPTIMIZATIONS']._serialized_start=1527 + _globals['_RESOURCEOPTIMIZATIONS']._serialized_end=2075 + _globals['_RESOURCEOPTIMIZATIONS_RESOURCETYPEANDNAME']._serialized_start=1727 + _globals['_RESOURCEOPTIMIZATIONS_RESOURCETYPEANDNAME']._serialized_end=1776 + _globals['_RESOURCEOPTIMIZATIONS_COLLAPSEDRESOURCENAMES']._serialized_start=1779 + _globals['_RESOURCEOPTIMIZATIONS_COLLAPSEDRESOURCENAMES']._serialized_end=2000 + _globals['_RESOURCEOPTIMIZATIONS_SPARSEENCODING']._serialized_start=2002 + _globals['_RESOURCEOPTIMIZATIONS_SPARSEENCODING']._serialized_end=2075 + _globals['_UNCOMPRESSNATIVELIBRARIES']._serialized_start=2078 + _globals['_UNCOMPRESSNATIVELIBRARIES']._serialized_end=2318 + _globals['_UNCOMPRESSNATIVELIBRARIES_PAGEALIGNMENT']._serialized_start=2200 + _globals['_UNCOMPRESSNATIVELIBRARIES_PAGEALIGNMENT']._serialized_end=2318 + _globals['_UNCOMPRESSDEXFILES']._serialized_start=2321 + _globals['_UNCOMPRESSDEXFILES']._serialized_end=2531 + _globals['_UNCOMPRESSDEXFILES_UNCOMPRESSEDDEXTARGETSDK']._serialized_start=2458 + _globals['_UNCOMPRESSDEXFILES_UNCOMPRESSEDDEXTARGETSDK']._serialized_end=2531 + _globals['_STOREARCHIVE']._serialized_start=2533 + _globals['_STOREARCHIVE']._serialized_end=2564 + _globals['_LOCALES']._serialized_start=2566 + _globals['_LOCALES']._serialized_end=2605 + _globals['_SPLITSCONFIG']._serialized_start=2607 + _globals['_SPLITSCONFIG']._serialized_end=2678 + _globals['_STANDALONECONFIG']._serialized_start=2681 + _globals['_STANDALONECONFIG']._serialized_end=3093 + _globals['_STANDALONECONFIG_DEXMERGINGSTRATEGY']._serialized_start=2956 + _globals['_STANDALONECONFIG_DEXMERGINGSTRATEGY']._serialized_end=3014 + _globals['_STANDALONECONFIG_FEATUREMODULESMODE']._serialized_start=3016 + _globals['_STANDALONECONFIG_FEATUREMODULESMODE']._serialized_end=3093 + _globals['_SPLITDIMENSION']._serialized_start=3096 + _globals['_SPLITDIMENSION']._serialized_end=3426 + _globals['_SPLITDIMENSION_VALUE']._serialized_start=3243 + _globals['_SPLITDIMENSION_VALUE']._serialized_end=3426 + _globals['_SUFFIXSTRIPPING']._serialized_start=3428 + _globals['_SUFFIXSTRIPPING']._serialized_end=3486 + _globals['_APEXCONFIG']._serialized_start=3489 + _globals['_APEXCONFIG']._serialized_end=3634 + _globals['_SUPPORTEDABISET']._serialized_start=3636 + _globals['_SUPPORTEDABISET']._serialized_end=3666 + _globals['_APEXEMBEDDEDAPKCONFIG']._serialized_start=3668 + _globals['_APEXEMBEDDEDAPKCONFIG']._serialized_end=3727 + _globals['_UNSIGNEDEMBEDDEDAPKCONFIG']._serialized_start=3729 + _globals['_UNSIGNEDEMBEDDEDAPKCONFIG']._serialized_end=3770 + _globals['_ASSETMODULESCONFIG']._serialized_start=3772 + _globals['_ASSETMODULESCONFIG']._serialized_end=3840 # @@protoc_insertion_point(module_scope) diff --git a/direct/src/dist/_proto/files_pb2.py b/direct/src/dist/_proto/files_pb2.py index f4ac2a9275..368f088bbc 100644 --- a/direct/src/dist/_proto/files_pb2.py +++ b/direct/src/dist/_proto/files_pb2.py @@ -1,11 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: files.proto +# Protobuf Python Version: 6.33.0 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 0, + '', + 'files.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -14,294 +25,24 @@ _sym_db = _symbol_database.Default() from . import targeting_pb2 as targeting__pb2 -DESCRIPTOR = _descriptor.FileDescriptor( - name='files.proto', - package='android.bundle', - syntax='proto3', - serialized_options=b'\n\022com.android.bundle', - create_key=_descriptor._internal_create_key, - serialized_pb=b'\n\x0b\x66iles.proto\x12\x0e\x61ndroid.bundle\x1a\x0ftargeting.proto\"D\n\x06\x41ssets\x12:\n\tdirectory\x18\x01 \x03(\x0b\x32\'.android.bundle.TargetedAssetsDirectory\"M\n\x0fNativeLibraries\x12:\n\tdirectory\x18\x01 \x03(\x0b\x32\'.android.bundle.TargetedNativeDirectory\"D\n\nApexImages\x12\x30\n\x05image\x18\x01 \x03(\x0b\x32!.android.bundle.TargetedApexImageJ\x04\x08\x02\x10\x03\"d\n\x17TargetedAssetsDirectory\x12\x0c\n\x04path\x18\x01 \x01(\t\x12;\n\ttargeting\x18\x02 \x01(\x0b\x32(.android.bundle.AssetsDirectoryTargeting\"d\n\x17TargetedNativeDirectory\x12\x0c\n\x04path\x18\x01 \x01(\t\x12;\n\ttargeting\x18\x02 \x01(\x0b\x32(.android.bundle.NativeDirectoryTargeting\"q\n\x11TargetedApexImage\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x17\n\x0f\x62uild_info_path\x18\x03 \x01(\t\x12\x35\n\ttargeting\x18\x02 \x01(\x0b\x32\".android.bundle.ApexImageTargetingB\x14\n\x12\x63om.android.bundleb\x06proto3' - , - dependencies=[targeting__pb2.DESCRIPTOR,]) +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x66iles.proto\x12\x0e\x61ndroid.bundle\x1a\x0ftargeting.proto\"D\n\x06\x41ssets\x12:\n\tdirectory\x18\x01 \x03(\x0b\x32\'.android.bundle.TargetedAssetsDirectory\"M\n\x0fNativeLibraries\x12:\n\tdirectory\x18\x01 \x03(\x0b\x32\'.android.bundle.TargetedNativeDirectory\"D\n\nApexImages\x12\x30\n\x05image\x18\x01 \x03(\x0b\x32!.android.bundle.TargetedApexImageJ\x04\x08\x02\x10\x03\"d\n\x17TargetedAssetsDirectory\x12\x0c\n\x04path\x18\x01 \x01(\t\x12;\n\ttargeting\x18\x02 \x01(\x0b\x32(.android.bundle.AssetsDirectoryTargeting\"d\n\x17TargetedNativeDirectory\x12\x0c\n\x04path\x18\x01 \x01(\t\x12;\n\ttargeting\x18\x02 \x01(\x0b\x32(.android.bundle.NativeDirectoryTargeting\"q\n\x11TargetedApexImage\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x17\n\x0f\x62uild_info_path\x18\x03 \x01(\t\x12\x35\n\ttargeting\x18\x02 \x01(\x0b\x32\".android.bundle.ApexImageTargetingB\x14\n\x12\x63om.android.bundleb\x06proto3') - - - -_ASSETS = _descriptor.Descriptor( - name='Assets', - full_name='android.bundle.Assets', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='directory', full_name='android.bundle.Assets.directory', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=48, - serialized_end=116, -) - - -_NATIVELIBRARIES = _descriptor.Descriptor( - name='NativeLibraries', - full_name='android.bundle.NativeLibraries', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='directory', full_name='android.bundle.NativeLibraries.directory', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=118, - serialized_end=195, -) - - -_APEXIMAGES = _descriptor.Descriptor( - name='ApexImages', - full_name='android.bundle.ApexImages', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='image', full_name='android.bundle.ApexImages.image', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=197, - serialized_end=265, -) - - -_TARGETEDASSETSDIRECTORY = _descriptor.Descriptor( - name='TargetedAssetsDirectory', - full_name='android.bundle.TargetedAssetsDirectory', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='path', full_name='android.bundle.TargetedAssetsDirectory.path', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='targeting', full_name='android.bundle.TargetedAssetsDirectory.targeting', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=267, - serialized_end=367, -) - - -_TARGETEDNATIVEDIRECTORY = _descriptor.Descriptor( - name='TargetedNativeDirectory', - full_name='android.bundle.TargetedNativeDirectory', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='path', full_name='android.bundle.TargetedNativeDirectory.path', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='targeting', full_name='android.bundle.TargetedNativeDirectory.targeting', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=369, - serialized_end=469, -) - - -_TARGETEDAPEXIMAGE = _descriptor.Descriptor( - name='TargetedApexImage', - full_name='android.bundle.TargetedApexImage', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='path', full_name='android.bundle.TargetedApexImage.path', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='build_info_path', full_name='android.bundle.TargetedApexImage.build_info_path', index=1, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='targeting', full_name='android.bundle.TargetedApexImage.targeting', index=2, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=471, - serialized_end=584, -) - -_ASSETS.fields_by_name['directory'].message_type = _TARGETEDASSETSDIRECTORY -_NATIVELIBRARIES.fields_by_name['directory'].message_type = _TARGETEDNATIVEDIRECTORY -_APEXIMAGES.fields_by_name['image'].message_type = _TARGETEDAPEXIMAGE -_TARGETEDASSETSDIRECTORY.fields_by_name['targeting'].message_type = targeting__pb2._ASSETSDIRECTORYTARGETING -_TARGETEDNATIVEDIRECTORY.fields_by_name['targeting'].message_type = targeting__pb2._NATIVEDIRECTORYTARGETING -_TARGETEDAPEXIMAGE.fields_by_name['targeting'].message_type = targeting__pb2._APEXIMAGETARGETING -DESCRIPTOR.message_types_by_name['Assets'] = _ASSETS -DESCRIPTOR.message_types_by_name['NativeLibraries'] = _NATIVELIBRARIES -DESCRIPTOR.message_types_by_name['ApexImages'] = _APEXIMAGES -DESCRIPTOR.message_types_by_name['TargetedAssetsDirectory'] = _TARGETEDASSETSDIRECTORY -DESCRIPTOR.message_types_by_name['TargetedNativeDirectory'] = _TARGETEDNATIVEDIRECTORY -DESCRIPTOR.message_types_by_name['TargetedApexImage'] = _TARGETEDAPEXIMAGE -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -Assets = _reflection.GeneratedProtocolMessageType('Assets', (_message.Message,), { - 'DESCRIPTOR' : _ASSETS, - '__module__' : 'files_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.Assets) - }) -_sym_db.RegisterMessage(Assets) - -NativeLibraries = _reflection.GeneratedProtocolMessageType('NativeLibraries', (_message.Message,), { - 'DESCRIPTOR' : _NATIVELIBRARIES, - '__module__' : 'files_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.NativeLibraries) - }) -_sym_db.RegisterMessage(NativeLibraries) - -ApexImages = _reflection.GeneratedProtocolMessageType('ApexImages', (_message.Message,), { - 'DESCRIPTOR' : _APEXIMAGES, - '__module__' : 'files_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.ApexImages) - }) -_sym_db.RegisterMessage(ApexImages) - -TargetedAssetsDirectory = _reflection.GeneratedProtocolMessageType('TargetedAssetsDirectory', (_message.Message,), { - 'DESCRIPTOR' : _TARGETEDASSETSDIRECTORY, - '__module__' : 'files_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.TargetedAssetsDirectory) - }) -_sym_db.RegisterMessage(TargetedAssetsDirectory) - -TargetedNativeDirectory = _reflection.GeneratedProtocolMessageType('TargetedNativeDirectory', (_message.Message,), { - 'DESCRIPTOR' : _TARGETEDNATIVEDIRECTORY, - '__module__' : 'files_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.TargetedNativeDirectory) - }) -_sym_db.RegisterMessage(TargetedNativeDirectory) - -TargetedApexImage = _reflection.GeneratedProtocolMessageType('TargetedApexImage', (_message.Message,), { - 'DESCRIPTOR' : _TARGETEDAPEXIMAGE, - '__module__' : 'files_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.TargetedApexImage) - }) -_sym_db.RegisterMessage(TargetedApexImage) - - -DESCRIPTOR._options = None +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'files_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\022com.android.bundle' + _globals['_ASSETS']._serialized_start=48 + _globals['_ASSETS']._serialized_end=116 + _globals['_NATIVELIBRARIES']._serialized_start=118 + _globals['_NATIVELIBRARIES']._serialized_end=195 + _globals['_APEXIMAGES']._serialized_start=197 + _globals['_APEXIMAGES']._serialized_end=265 + _globals['_TARGETEDASSETSDIRECTORY']._serialized_start=267 + _globals['_TARGETEDASSETSDIRECTORY']._serialized_end=367 + _globals['_TARGETEDNATIVEDIRECTORY']._serialized_start=369 + _globals['_TARGETEDNATIVEDIRECTORY']._serialized_end=469 + _globals['_TARGETEDAPEXIMAGE']._serialized_start=471 + _globals['_TARGETEDAPEXIMAGE']._serialized_end=584 # @@protoc_insertion_point(module_scope) diff --git a/direct/src/dist/_proto/targeting_pb2.py b/direct/src/dist/_proto/targeting_pb2.py index b475f55af4..895eb1d1da 100644 --- a/direct/src/dist/_proto/targeting_pb2.py +++ b/direct/src/dist/_proto/targeting_pb2.py @@ -1,11 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: targeting.proto +# Protobuf Python Version: 6.33.0 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 0, + '', + 'targeting.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -14,1457 +25,84 @@ _sym_db = _symbol_database.Default() from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 -DESCRIPTOR = _descriptor.FileDescriptor( - name='targeting.proto', - package='android.bundle', - syntax='proto3', - serialized_options=b'\n\022com.android.bundle', - create_key=_descriptor._internal_create_key, - serialized_pb=b'\n\x0ftargeting.proto\x12\x0e\x61ndroid.bundle\x1a\x1egoogle/protobuf/wrappers.proto\"\xf6\x02\n\x10VariantTargeting\x12\x42\n\x15sdk_version_targeting\x18\x01 \x01(\x0b\x32#.android.bundle.SdkVersionTargeting\x12\x33\n\rabi_targeting\x18\x02 \x01(\x0b\x32\x1c.android.bundle.AbiTargeting\x12H\n\x18screen_density_targeting\x18\x03 \x01(\x0b\x32&.android.bundle.ScreenDensityTargeting\x12>\n\x13multi_abi_targeting\x18\x04 \x01(\x0b\x32!.android.bundle.MultiAbiTargeting\x12_\n$texture_compression_format_targeting\x18\x05 \x01(\x0b\x32\x31.android.bundle.TextureCompressionFormatTargeting\"\xbc\x04\n\x0c\x41pkTargeting\x12\x33\n\rabi_targeting\x18\x01 \x01(\x0b\x32\x1c.android.bundle.AbiTargeting\x12=\n\x12language_targeting\x18\x03 \x01(\x0b\x32!.android.bundle.LanguageTargeting\x12H\n\x18screen_density_targeting\x18\x04 \x01(\x0b\x32&.android.bundle.ScreenDensityTargeting\x12\x42\n\x15sdk_version_targeting\x18\x05 \x01(\x0b\x32#.android.bundle.SdkVersionTargeting\x12_\n$texture_compression_format_targeting\x18\x06 \x01(\x0b\x32\x31.android.bundle.TextureCompressionFormatTargeting\x12>\n\x13multi_abi_targeting\x18\x07 \x01(\x0b\x32!.android.bundle.MultiAbiTargeting\x12?\n\x13sanitizer_targeting\x18\x08 \x01(\x0b\x32\".android.bundle.SanitizerTargeting\x12\x42\n\x15\x64\x65vice_tier_targeting\x18\t \x01(\x0b\x32#.android.bundle.DeviceTierTargetingJ\x04\x08\x02\x10\x03\"\xbb\x02\n\x0fModuleTargeting\x12\x42\n\x15sdk_version_targeting\x18\x01 \x01(\x0b\x32#.android.bundle.SdkVersionTargeting\x12H\n\x18\x64\x65vice_feature_targeting\x18\x02 \x03(\x0b\x32&.android.bundle.DeviceFeatureTargeting\x12H\n\x18user_countries_targeting\x18\x03 \x01(\x0b\x32&.android.bundle.UserCountriesTargeting\x12J\n\x16\x64\x65vice_group_targeting\x18\x05 \x01(\x0b\x32*.android.bundle.DeviceGroupModuleTargetingJ\x04\x08\x04\x10\x05\"@\n\x16UserCountriesTargeting\x12\x15\n\rcountry_codes\x18\x01 \x03(\t\x12\x0f\n\x07\x65xclude\x18\x02 \x01(\x08\"\xfd\x01\n\rScreenDensity\x12\x43\n\rdensity_alias\x18\x01 \x01(\x0e\x32*.android.bundle.ScreenDensity.DensityAliasH\x00\x12\x15\n\x0b\x64\x65nsity_dpi\x18\x02 \x01(\x05H\x00\"\x7f\n\x0c\x44\x65nsityAlias\x12\x17\n\x13\x44\x45NSITY_UNSPECIFIED\x10\x00\x12\t\n\x05NODPI\x10\x01\x12\x08\n\x04LDPI\x10\x02\x12\x08\n\x04MDPI\x10\x03\x12\t\n\x05TVDPI\x10\x04\x12\x08\n\x04HDPI\x10\x05\x12\t\n\x05XHDPI\x10\x06\x12\n\n\x06XXHDPI\x10\x07\x12\x0b\n\x07XXXHDPI\x10\x08\x42\x0f\n\rdensity_oneof\"6\n\nSdkVersion\x12(\n\x03min\x18\x01 \x01(\x0b\x32\x1b.google.protobuf.Int32Value\"\xb0\x02\n\x18TextureCompressionFormat\x12U\n\x05\x61lias\x18\x01 \x01(\x0e\x32\x46.android.bundle.TextureCompressionFormat.TextureCompressionFormatAlias\"\xbc\x01\n\x1dTextureCompressionFormatAlias\x12*\n&UNSPECIFIED_TEXTURE_COMPRESSION_FORMAT\x10\x00\x12\r\n\tETC1_RGB8\x10\x01\x12\x0c\n\x08PALETTED\x10\x02\x12\x0c\n\x08THREE_DC\x10\x03\x12\x07\n\x03\x41TC\x10\x04\x12\x08\n\x04LATC\x10\x05\x12\x08\n\x04\x44XT1\x10\x06\x12\x08\n\x04S3TC\x10\x07\x12\t\n\x05PVRTC\x10\x08\x12\x08\n\x04\x41STC\x10\t\x12\x08\n\x04\x45TC2\x10\n\"\xb9\x01\n\x03\x41\x62i\x12+\n\x05\x61lias\x18\x01 \x01(\x0e\x32\x1c.android.bundle.Abi.AbiAlias\"\x84\x01\n\x08\x41\x62iAlias\x12 \n\x1cUNSPECIFIED_CPU_ARCHITECTURE\x10\x00\x12\x0b\n\x07\x41RMEABI\x10\x01\x12\x0f\n\x0b\x41RMEABI_V7A\x10\x02\x12\r\n\tARM64_V8A\x10\x03\x12\x07\n\x03X86\x10\x04\x12\n\n\x06X86_64\x10\x05\x12\x08\n\x04MIPS\x10\x06\x12\n\n\x06MIPS64\x10\x07\",\n\x08MultiAbi\x12 \n\x03\x61\x62i\x18\x01 \x03(\x0b\x32\x13.android.bundle.Abi\"o\n\tSanitizer\x12\x37\n\x05\x61lias\x18\x01 \x01(\x0e\x32(.android.bundle.Sanitizer.SanitizerAlias\")\n\x0eSanitizerAlias\x12\x08\n\x04NONE\x10\x00\x12\r\n\tHWADDRESS\x10\x01\">\n\rDeviceFeature\x12\x14\n\x0c\x66\x65\x61ture_name\x18\x01 \x01(\t\x12\x17\n\x0f\x66\x65\x61ture_version\x18\x02 \x01(\x05\"\x91\x02\n\x18\x41ssetsDirectoryTargeting\x12)\n\x03\x61\x62i\x18\x01 \x01(\x0b\x32\x1c.android.bundle.AbiTargeting\x12U\n\x1atexture_compression_format\x18\x03 \x01(\x0b\x32\x31.android.bundle.TextureCompressionFormatTargeting\x12\x33\n\x08language\x18\x04 \x01(\x0b\x32!.android.bundle.LanguageTargeting\x12\x38\n\x0b\x64\x65vice_tier\x18\x05 \x01(\x0b\x32#.android.bundle.DeviceTierTargetingJ\x04\x08\x02\x10\x03\"\xbe\x01\n\x18NativeDirectoryTargeting\x12 \n\x03\x61\x62i\x18\x01 \x01(\x0b\x32\x13.android.bundle.Abi\x12L\n\x1atexture_compression_format\x18\x03 \x01(\x0b\x32(.android.bundle.TextureCompressionFormat\x12,\n\tsanitizer\x18\x04 \x01(\x0b\x32\x19.android.bundle.SanitizerJ\x04\x08\x02\x10\x03\"J\n\x12\x41pexImageTargeting\x12\x34\n\tmulti_abi\x18\x01 \x01(\x0b\x32!.android.bundle.MultiAbiTargeting\"]\n\x0c\x41\x62iTargeting\x12\"\n\x05value\x18\x01 \x03(\x0b\x32\x13.android.bundle.Abi\x12)\n\x0c\x61lternatives\x18\x02 \x03(\x0b\x32\x13.android.bundle.Abi\"l\n\x11MultiAbiTargeting\x12\'\n\x05value\x18\x01 \x03(\x0b\x32\x18.android.bundle.MultiAbi\x12.\n\x0c\x61lternatives\x18\x02 \x03(\x0b\x32\x18.android.bundle.MultiAbi\"{\n\x16ScreenDensityTargeting\x12,\n\x05value\x18\x01 \x03(\x0b\x32\x1d.android.bundle.ScreenDensity\x12\x33\n\x0c\x61lternatives\x18\x02 \x03(\x0b\x32\x1d.android.bundle.ScreenDensity\"8\n\x11LanguageTargeting\x12\r\n\x05value\x18\x01 \x03(\t\x12\x14\n\x0c\x61lternatives\x18\x02 \x03(\t\"r\n\x13SdkVersionTargeting\x12)\n\x05value\x18\x01 \x03(\x0b\x32\x1a.android.bundle.SdkVersion\x12\x30\n\x0c\x61lternatives\x18\x02 \x03(\x0b\x32\x1a.android.bundle.SdkVersion\"\x9c\x01\n!TextureCompressionFormatTargeting\x12\x37\n\x05value\x18\x01 \x03(\x0b\x32(.android.bundle.TextureCompressionFormat\x12>\n\x0c\x61lternatives\x18\x02 \x03(\x0b\x32(.android.bundle.TextureCompressionFormat\">\n\x12SanitizerTargeting\x12(\n\x05value\x18\x01 \x03(\x0b\x32\x19.android.bundle.Sanitizer\"Q\n\x16\x44\x65viceFeatureTargeting\x12\x37\n\x10required_feature\x18\x01 \x01(\x0b\x32\x1d.android.bundle.DeviceFeature\"\x80\x01\n\x13\x44\x65viceTierTargeting\x12*\n\x05value\x18\x03 \x03(\x0b\x32\x1b.google.protobuf.Int32Value\x12\x31\n\x0c\x61lternatives\x18\x04 \x03(\x0b\x32\x1b.google.protobuf.Int32ValueJ\x04\x08\x01\x10\x02J\x04\x08\x02\x10\x03\"+\n\x1a\x44\x65viceGroupModuleTargeting\x12\r\n\x05value\x18\x01 \x03(\tB\x14\n\x12\x63om.android.bundleb\x06proto3' - , - dependencies=[google_dot_protobuf_dot_wrappers__pb2.DESCRIPTOR,]) - - - -_SCREENDENSITY_DENSITYALIAS = _descriptor.EnumDescriptor( - name='DensityAlias', - full_name='android.bundle.ScreenDensity.DensityAlias', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='DENSITY_UNSPECIFIED', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='NODPI', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='LDPI', index=2, number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='MDPI', index=3, number=3, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='TVDPI', index=4, number=4, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='HDPI', index=5, number=5, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='XHDPI', index=6, number=6, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='XXHDPI', index=7, number=7, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='XXXHDPI', index=8, number=8, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=1513, - serialized_end=1640, -) -_sym_db.RegisterEnumDescriptor(_SCREENDENSITY_DENSITYALIAS) - -_TEXTURECOMPRESSIONFORMAT_TEXTURECOMPRESSIONFORMATALIAS = _descriptor.EnumDescriptor( - name='TextureCompressionFormatAlias', - full_name='android.bundle.TextureCompressionFormat.TextureCompressionFormatAlias', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='UNSPECIFIED_TEXTURE_COMPRESSION_FORMAT', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ETC1_RGB8', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='PALETTED', index=2, number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='THREE_DC', index=3, number=3, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ATC', index=4, number=4, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='LATC', index=5, number=5, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='DXT1', index=6, number=6, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='S3TC', index=7, number=7, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='PVRTC', index=8, number=8, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ASTC', index=9, number=9, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ETC2', index=10, number=10, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=1832, - serialized_end=2020, -) -_sym_db.RegisterEnumDescriptor(_TEXTURECOMPRESSIONFORMAT_TEXTURECOMPRESSIONFORMATALIAS) - -_ABI_ABIALIAS = _descriptor.EnumDescriptor( - name='AbiAlias', - full_name='android.bundle.Abi.AbiAlias', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='UNSPECIFIED_CPU_ARCHITECTURE', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ARMEABI', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ARMEABI_V7A', index=2, number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='ARM64_V8A', index=3, number=3, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='X86', index=4, number=4, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='X86_64', index=5, number=5, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='MIPS', index=6, number=6, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='MIPS64', index=7, number=7, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=2076, - serialized_end=2208, -) -_sym_db.RegisterEnumDescriptor(_ABI_ABIALIAS) - -_SANITIZER_SANITIZERALIAS = _descriptor.EnumDescriptor( - name='SanitizerAlias', - full_name='android.bundle.Sanitizer.SanitizerAlias', - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name='NONE', index=0, number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - _descriptor.EnumValueDescriptor( - name='HWADDRESS', index=1, number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key), - ], - containing_type=None, - serialized_options=None, - serialized_start=2326, - serialized_end=2367, -) -_sym_db.RegisterEnumDescriptor(_SANITIZER_SANITIZERALIAS) - - -_VARIANTTARGETING = _descriptor.Descriptor( - name='VariantTargeting', - full_name='android.bundle.VariantTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='sdk_version_targeting', full_name='android.bundle.VariantTargeting.sdk_version_targeting', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='abi_targeting', full_name='android.bundle.VariantTargeting.abi_targeting', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='screen_density_targeting', full_name='android.bundle.VariantTargeting.screen_density_targeting', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='multi_abi_targeting', full_name='android.bundle.VariantTargeting.multi_abi_targeting', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='texture_compression_format_targeting', full_name='android.bundle.VariantTargeting.texture_compression_format_targeting', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=68, - serialized_end=442, -) - - -_APKTARGETING = _descriptor.Descriptor( - name='ApkTargeting', - full_name='android.bundle.ApkTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='abi_targeting', full_name='android.bundle.ApkTargeting.abi_targeting', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='language_targeting', full_name='android.bundle.ApkTargeting.language_targeting', index=1, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='screen_density_targeting', full_name='android.bundle.ApkTargeting.screen_density_targeting', index=2, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='sdk_version_targeting', full_name='android.bundle.ApkTargeting.sdk_version_targeting', index=3, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='texture_compression_format_targeting', full_name='android.bundle.ApkTargeting.texture_compression_format_targeting', index=4, - number=6, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='multi_abi_targeting', full_name='android.bundle.ApkTargeting.multi_abi_targeting', index=5, - number=7, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='sanitizer_targeting', full_name='android.bundle.ApkTargeting.sanitizer_targeting', index=6, - number=8, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='device_tier_targeting', full_name='android.bundle.ApkTargeting.device_tier_targeting', index=7, - number=9, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=445, - serialized_end=1017, -) - - -_MODULETARGETING = _descriptor.Descriptor( - name='ModuleTargeting', - full_name='android.bundle.ModuleTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='sdk_version_targeting', full_name='android.bundle.ModuleTargeting.sdk_version_targeting', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='device_feature_targeting', full_name='android.bundle.ModuleTargeting.device_feature_targeting', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='user_countries_targeting', full_name='android.bundle.ModuleTargeting.user_countries_targeting', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='device_group_targeting', full_name='android.bundle.ModuleTargeting.device_group_targeting', index=3, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1020, - serialized_end=1335, -) - - -_USERCOUNTRIESTARGETING = _descriptor.Descriptor( - name='UserCountriesTargeting', - full_name='android.bundle.UserCountriesTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='country_codes', full_name='android.bundle.UserCountriesTargeting.country_codes', index=0, - number=1, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='exclude', full_name='android.bundle.UserCountriesTargeting.exclude', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1337, - serialized_end=1401, -) - - -_SCREENDENSITY = _descriptor.Descriptor( - name='ScreenDensity', - full_name='android.bundle.ScreenDensity', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='density_alias', full_name='android.bundle.ScreenDensity.density_alias', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='density_dpi', full_name='android.bundle.ScreenDensity.density_dpi', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _SCREENDENSITY_DENSITYALIAS, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='density_oneof', full_name='android.bundle.ScreenDensity.density_oneof', - index=0, containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[]), - ], - serialized_start=1404, - serialized_end=1657, -) - - -_SDKVERSION = _descriptor.Descriptor( - name='SdkVersion', - full_name='android.bundle.SdkVersion', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='min', full_name='android.bundle.SdkVersion.min', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1659, - serialized_end=1713, -) - - -_TEXTURECOMPRESSIONFORMAT = _descriptor.Descriptor( - name='TextureCompressionFormat', - full_name='android.bundle.TextureCompressionFormat', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='alias', full_name='android.bundle.TextureCompressionFormat.alias', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _TEXTURECOMPRESSIONFORMAT_TEXTURECOMPRESSIONFORMATALIAS, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1716, - serialized_end=2020, -) - - -_ABI = _descriptor.Descriptor( - name='Abi', - full_name='android.bundle.Abi', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='alias', full_name='android.bundle.Abi.alias', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _ABI_ABIALIAS, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2023, - serialized_end=2208, -) - - -_MULTIABI = _descriptor.Descriptor( - name='MultiAbi', - full_name='android.bundle.MultiAbi', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='abi', full_name='android.bundle.MultiAbi.abi', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2210, - serialized_end=2254, -) - - -_SANITIZER = _descriptor.Descriptor( - name='Sanitizer', - full_name='android.bundle.Sanitizer', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='alias', full_name='android.bundle.Sanitizer.alias', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _SANITIZER_SANITIZERALIAS, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2256, - serialized_end=2367, -) - - -_DEVICEFEATURE = _descriptor.Descriptor( - name='DeviceFeature', - full_name='android.bundle.DeviceFeature', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='feature_name', full_name='android.bundle.DeviceFeature.feature_name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='feature_version', full_name='android.bundle.DeviceFeature.feature_version', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2369, - serialized_end=2431, -) - - -_ASSETSDIRECTORYTARGETING = _descriptor.Descriptor( - name='AssetsDirectoryTargeting', - full_name='android.bundle.AssetsDirectoryTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='abi', full_name='android.bundle.AssetsDirectoryTargeting.abi', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='texture_compression_format', full_name='android.bundle.AssetsDirectoryTargeting.texture_compression_format', index=1, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='language', full_name='android.bundle.AssetsDirectoryTargeting.language', index=2, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='device_tier', full_name='android.bundle.AssetsDirectoryTargeting.device_tier', index=3, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2434, - serialized_end=2707, -) - - -_NATIVEDIRECTORYTARGETING = _descriptor.Descriptor( - name='NativeDirectoryTargeting', - full_name='android.bundle.NativeDirectoryTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='abi', full_name='android.bundle.NativeDirectoryTargeting.abi', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='texture_compression_format', full_name='android.bundle.NativeDirectoryTargeting.texture_compression_format', index=1, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='sanitizer', full_name='android.bundle.NativeDirectoryTargeting.sanitizer', index=2, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2710, - serialized_end=2900, -) - - -_APEXIMAGETARGETING = _descriptor.Descriptor( - name='ApexImageTargeting', - full_name='android.bundle.ApexImageTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='multi_abi', full_name='android.bundle.ApexImageTargeting.multi_abi', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2902, - serialized_end=2976, -) - - -_ABITARGETING = _descriptor.Descriptor( - name='AbiTargeting', - full_name='android.bundle.AbiTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='android.bundle.AbiTargeting.value', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='alternatives', full_name='android.bundle.AbiTargeting.alternatives', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2978, - serialized_end=3071, -) - - -_MULTIABITARGETING = _descriptor.Descriptor( - name='MultiAbiTargeting', - full_name='android.bundle.MultiAbiTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='android.bundle.MultiAbiTargeting.value', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='alternatives', full_name='android.bundle.MultiAbiTargeting.alternatives', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3073, - serialized_end=3181, -) - - -_SCREENDENSITYTARGETING = _descriptor.Descriptor( - name='ScreenDensityTargeting', - full_name='android.bundle.ScreenDensityTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='android.bundle.ScreenDensityTargeting.value', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='alternatives', full_name='android.bundle.ScreenDensityTargeting.alternatives', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3183, - serialized_end=3306, -) - - -_LANGUAGETARGETING = _descriptor.Descriptor( - name='LanguageTargeting', - full_name='android.bundle.LanguageTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='android.bundle.LanguageTargeting.value', index=0, - number=1, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='alternatives', full_name='android.bundle.LanguageTargeting.alternatives', index=1, - number=2, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3308, - serialized_end=3364, -) - - -_SDKVERSIONTARGETING = _descriptor.Descriptor( - name='SdkVersionTargeting', - full_name='android.bundle.SdkVersionTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='android.bundle.SdkVersionTargeting.value', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='alternatives', full_name='android.bundle.SdkVersionTargeting.alternatives', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3366, - serialized_end=3480, -) - - -_TEXTURECOMPRESSIONFORMATTARGETING = _descriptor.Descriptor( - name='TextureCompressionFormatTargeting', - full_name='android.bundle.TextureCompressionFormatTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='android.bundle.TextureCompressionFormatTargeting.value', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='alternatives', full_name='android.bundle.TextureCompressionFormatTargeting.alternatives', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3483, - serialized_end=3639, -) - - -_SANITIZERTARGETING = _descriptor.Descriptor( - name='SanitizerTargeting', - full_name='android.bundle.SanitizerTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='android.bundle.SanitizerTargeting.value', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3641, - serialized_end=3703, -) - - -_DEVICEFEATURETARGETING = _descriptor.Descriptor( - name='DeviceFeatureTargeting', - full_name='android.bundle.DeviceFeatureTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='required_feature', full_name='android.bundle.DeviceFeatureTargeting.required_feature', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3705, - serialized_end=3786, -) - - -_DEVICETIERTARGETING = _descriptor.Descriptor( - name='DeviceTierTargeting', - full_name='android.bundle.DeviceTierTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='android.bundle.DeviceTierTargeting.value', index=0, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='alternatives', full_name='android.bundle.DeviceTierTargeting.alternatives', index=1, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3789, - serialized_end=3917, -) - - -_DEVICEGROUPMODULETARGETING = _descriptor.Descriptor( - name='DeviceGroupModuleTargeting', - full_name='android.bundle.DeviceGroupModuleTargeting', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='android.bundle.DeviceGroupModuleTargeting.value', index=0, - number=1, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3919, - serialized_end=3962, -) - -_VARIANTTARGETING.fields_by_name['sdk_version_targeting'].message_type = _SDKVERSIONTARGETING -_VARIANTTARGETING.fields_by_name['abi_targeting'].message_type = _ABITARGETING -_VARIANTTARGETING.fields_by_name['screen_density_targeting'].message_type = _SCREENDENSITYTARGETING -_VARIANTTARGETING.fields_by_name['multi_abi_targeting'].message_type = _MULTIABITARGETING -_VARIANTTARGETING.fields_by_name['texture_compression_format_targeting'].message_type = _TEXTURECOMPRESSIONFORMATTARGETING -_APKTARGETING.fields_by_name['abi_targeting'].message_type = _ABITARGETING -_APKTARGETING.fields_by_name['language_targeting'].message_type = _LANGUAGETARGETING -_APKTARGETING.fields_by_name['screen_density_targeting'].message_type = _SCREENDENSITYTARGETING -_APKTARGETING.fields_by_name['sdk_version_targeting'].message_type = _SDKVERSIONTARGETING -_APKTARGETING.fields_by_name['texture_compression_format_targeting'].message_type = _TEXTURECOMPRESSIONFORMATTARGETING -_APKTARGETING.fields_by_name['multi_abi_targeting'].message_type = _MULTIABITARGETING -_APKTARGETING.fields_by_name['sanitizer_targeting'].message_type = _SANITIZERTARGETING -_APKTARGETING.fields_by_name['device_tier_targeting'].message_type = _DEVICETIERTARGETING -_MODULETARGETING.fields_by_name['sdk_version_targeting'].message_type = _SDKVERSIONTARGETING -_MODULETARGETING.fields_by_name['device_feature_targeting'].message_type = _DEVICEFEATURETARGETING -_MODULETARGETING.fields_by_name['user_countries_targeting'].message_type = _USERCOUNTRIESTARGETING -_MODULETARGETING.fields_by_name['device_group_targeting'].message_type = _DEVICEGROUPMODULETARGETING -_SCREENDENSITY.fields_by_name['density_alias'].enum_type = _SCREENDENSITY_DENSITYALIAS -_SCREENDENSITY_DENSITYALIAS.containing_type = _SCREENDENSITY -_SCREENDENSITY.oneofs_by_name['density_oneof'].fields.append( - _SCREENDENSITY.fields_by_name['density_alias']) -_SCREENDENSITY.fields_by_name['density_alias'].containing_oneof = _SCREENDENSITY.oneofs_by_name['density_oneof'] -_SCREENDENSITY.oneofs_by_name['density_oneof'].fields.append( - _SCREENDENSITY.fields_by_name['density_dpi']) -_SCREENDENSITY.fields_by_name['density_dpi'].containing_oneof = _SCREENDENSITY.oneofs_by_name['density_oneof'] -_SDKVERSION.fields_by_name['min'].message_type = google_dot_protobuf_dot_wrappers__pb2._INT32VALUE -_TEXTURECOMPRESSIONFORMAT.fields_by_name['alias'].enum_type = _TEXTURECOMPRESSIONFORMAT_TEXTURECOMPRESSIONFORMATALIAS -_TEXTURECOMPRESSIONFORMAT_TEXTURECOMPRESSIONFORMATALIAS.containing_type = _TEXTURECOMPRESSIONFORMAT -_ABI.fields_by_name['alias'].enum_type = _ABI_ABIALIAS -_ABI_ABIALIAS.containing_type = _ABI -_MULTIABI.fields_by_name['abi'].message_type = _ABI -_SANITIZER.fields_by_name['alias'].enum_type = _SANITIZER_SANITIZERALIAS -_SANITIZER_SANITIZERALIAS.containing_type = _SANITIZER -_ASSETSDIRECTORYTARGETING.fields_by_name['abi'].message_type = _ABITARGETING -_ASSETSDIRECTORYTARGETING.fields_by_name['texture_compression_format'].message_type = _TEXTURECOMPRESSIONFORMATTARGETING -_ASSETSDIRECTORYTARGETING.fields_by_name['language'].message_type = _LANGUAGETARGETING -_ASSETSDIRECTORYTARGETING.fields_by_name['device_tier'].message_type = _DEVICETIERTARGETING -_NATIVEDIRECTORYTARGETING.fields_by_name['abi'].message_type = _ABI -_NATIVEDIRECTORYTARGETING.fields_by_name['texture_compression_format'].message_type = _TEXTURECOMPRESSIONFORMAT -_NATIVEDIRECTORYTARGETING.fields_by_name['sanitizer'].message_type = _SANITIZER -_APEXIMAGETARGETING.fields_by_name['multi_abi'].message_type = _MULTIABITARGETING -_ABITARGETING.fields_by_name['value'].message_type = _ABI -_ABITARGETING.fields_by_name['alternatives'].message_type = _ABI -_MULTIABITARGETING.fields_by_name['value'].message_type = _MULTIABI -_MULTIABITARGETING.fields_by_name['alternatives'].message_type = _MULTIABI -_SCREENDENSITYTARGETING.fields_by_name['value'].message_type = _SCREENDENSITY -_SCREENDENSITYTARGETING.fields_by_name['alternatives'].message_type = _SCREENDENSITY -_SDKVERSIONTARGETING.fields_by_name['value'].message_type = _SDKVERSION -_SDKVERSIONTARGETING.fields_by_name['alternatives'].message_type = _SDKVERSION -_TEXTURECOMPRESSIONFORMATTARGETING.fields_by_name['value'].message_type = _TEXTURECOMPRESSIONFORMAT -_TEXTURECOMPRESSIONFORMATTARGETING.fields_by_name['alternatives'].message_type = _TEXTURECOMPRESSIONFORMAT -_SANITIZERTARGETING.fields_by_name['value'].message_type = _SANITIZER -_DEVICEFEATURETARGETING.fields_by_name['required_feature'].message_type = _DEVICEFEATURE -_DEVICETIERTARGETING.fields_by_name['value'].message_type = google_dot_protobuf_dot_wrappers__pb2._INT32VALUE -_DEVICETIERTARGETING.fields_by_name['alternatives'].message_type = google_dot_protobuf_dot_wrappers__pb2._INT32VALUE -DESCRIPTOR.message_types_by_name['VariantTargeting'] = _VARIANTTARGETING -DESCRIPTOR.message_types_by_name['ApkTargeting'] = _APKTARGETING -DESCRIPTOR.message_types_by_name['ModuleTargeting'] = _MODULETARGETING -DESCRIPTOR.message_types_by_name['UserCountriesTargeting'] = _USERCOUNTRIESTARGETING -DESCRIPTOR.message_types_by_name['ScreenDensity'] = _SCREENDENSITY -DESCRIPTOR.message_types_by_name['SdkVersion'] = _SDKVERSION -DESCRIPTOR.message_types_by_name['TextureCompressionFormat'] = _TEXTURECOMPRESSIONFORMAT -DESCRIPTOR.message_types_by_name['Abi'] = _ABI -DESCRIPTOR.message_types_by_name['MultiAbi'] = _MULTIABI -DESCRIPTOR.message_types_by_name['Sanitizer'] = _SANITIZER -DESCRIPTOR.message_types_by_name['DeviceFeature'] = _DEVICEFEATURE -DESCRIPTOR.message_types_by_name['AssetsDirectoryTargeting'] = _ASSETSDIRECTORYTARGETING -DESCRIPTOR.message_types_by_name['NativeDirectoryTargeting'] = _NATIVEDIRECTORYTARGETING -DESCRIPTOR.message_types_by_name['ApexImageTargeting'] = _APEXIMAGETARGETING -DESCRIPTOR.message_types_by_name['AbiTargeting'] = _ABITARGETING -DESCRIPTOR.message_types_by_name['MultiAbiTargeting'] = _MULTIABITARGETING -DESCRIPTOR.message_types_by_name['ScreenDensityTargeting'] = _SCREENDENSITYTARGETING -DESCRIPTOR.message_types_by_name['LanguageTargeting'] = _LANGUAGETARGETING -DESCRIPTOR.message_types_by_name['SdkVersionTargeting'] = _SDKVERSIONTARGETING -DESCRIPTOR.message_types_by_name['TextureCompressionFormatTargeting'] = _TEXTURECOMPRESSIONFORMATTARGETING -DESCRIPTOR.message_types_by_name['SanitizerTargeting'] = _SANITIZERTARGETING -DESCRIPTOR.message_types_by_name['DeviceFeatureTargeting'] = _DEVICEFEATURETARGETING -DESCRIPTOR.message_types_by_name['DeviceTierTargeting'] = _DEVICETIERTARGETING -DESCRIPTOR.message_types_by_name['DeviceGroupModuleTargeting'] = _DEVICEGROUPMODULETARGETING -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -VariantTargeting = _reflection.GeneratedProtocolMessageType('VariantTargeting', (_message.Message,), { - 'DESCRIPTOR' : _VARIANTTARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.VariantTargeting) - }) -_sym_db.RegisterMessage(VariantTargeting) - -ApkTargeting = _reflection.GeneratedProtocolMessageType('ApkTargeting', (_message.Message,), { - 'DESCRIPTOR' : _APKTARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.ApkTargeting) - }) -_sym_db.RegisterMessage(ApkTargeting) - -ModuleTargeting = _reflection.GeneratedProtocolMessageType('ModuleTargeting', (_message.Message,), { - 'DESCRIPTOR' : _MODULETARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.ModuleTargeting) - }) -_sym_db.RegisterMessage(ModuleTargeting) - -UserCountriesTargeting = _reflection.GeneratedProtocolMessageType('UserCountriesTargeting', (_message.Message,), { - 'DESCRIPTOR' : _USERCOUNTRIESTARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.UserCountriesTargeting) - }) -_sym_db.RegisterMessage(UserCountriesTargeting) - -ScreenDensity = _reflection.GeneratedProtocolMessageType('ScreenDensity', (_message.Message,), { - 'DESCRIPTOR' : _SCREENDENSITY, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.ScreenDensity) - }) -_sym_db.RegisterMessage(ScreenDensity) - -SdkVersion = _reflection.GeneratedProtocolMessageType('SdkVersion', (_message.Message,), { - 'DESCRIPTOR' : _SDKVERSION, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.SdkVersion) - }) -_sym_db.RegisterMessage(SdkVersion) - -TextureCompressionFormat = _reflection.GeneratedProtocolMessageType('TextureCompressionFormat', (_message.Message,), { - 'DESCRIPTOR' : _TEXTURECOMPRESSIONFORMAT, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.TextureCompressionFormat) - }) -_sym_db.RegisterMessage(TextureCompressionFormat) - -Abi = _reflection.GeneratedProtocolMessageType('Abi', (_message.Message,), { - 'DESCRIPTOR' : _ABI, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.Abi) - }) -_sym_db.RegisterMessage(Abi) - -MultiAbi = _reflection.GeneratedProtocolMessageType('MultiAbi', (_message.Message,), { - 'DESCRIPTOR' : _MULTIABI, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.MultiAbi) - }) -_sym_db.RegisterMessage(MultiAbi) - -Sanitizer = _reflection.GeneratedProtocolMessageType('Sanitizer', (_message.Message,), { - 'DESCRIPTOR' : _SANITIZER, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.Sanitizer) - }) -_sym_db.RegisterMessage(Sanitizer) - -DeviceFeature = _reflection.GeneratedProtocolMessageType('DeviceFeature', (_message.Message,), { - 'DESCRIPTOR' : _DEVICEFEATURE, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.DeviceFeature) - }) -_sym_db.RegisterMessage(DeviceFeature) - -AssetsDirectoryTargeting = _reflection.GeneratedProtocolMessageType('AssetsDirectoryTargeting', (_message.Message,), { - 'DESCRIPTOR' : _ASSETSDIRECTORYTARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.AssetsDirectoryTargeting) - }) -_sym_db.RegisterMessage(AssetsDirectoryTargeting) - -NativeDirectoryTargeting = _reflection.GeneratedProtocolMessageType('NativeDirectoryTargeting', (_message.Message,), { - 'DESCRIPTOR' : _NATIVEDIRECTORYTARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.NativeDirectoryTargeting) - }) -_sym_db.RegisterMessage(NativeDirectoryTargeting) - -ApexImageTargeting = _reflection.GeneratedProtocolMessageType('ApexImageTargeting', (_message.Message,), { - 'DESCRIPTOR' : _APEXIMAGETARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.ApexImageTargeting) - }) -_sym_db.RegisterMessage(ApexImageTargeting) - -AbiTargeting = _reflection.GeneratedProtocolMessageType('AbiTargeting', (_message.Message,), { - 'DESCRIPTOR' : _ABITARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.AbiTargeting) - }) -_sym_db.RegisterMessage(AbiTargeting) - -MultiAbiTargeting = _reflection.GeneratedProtocolMessageType('MultiAbiTargeting', (_message.Message,), { - 'DESCRIPTOR' : _MULTIABITARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.MultiAbiTargeting) - }) -_sym_db.RegisterMessage(MultiAbiTargeting) - -ScreenDensityTargeting = _reflection.GeneratedProtocolMessageType('ScreenDensityTargeting', (_message.Message,), { - 'DESCRIPTOR' : _SCREENDENSITYTARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.ScreenDensityTargeting) - }) -_sym_db.RegisterMessage(ScreenDensityTargeting) - -LanguageTargeting = _reflection.GeneratedProtocolMessageType('LanguageTargeting', (_message.Message,), { - 'DESCRIPTOR' : _LANGUAGETARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.LanguageTargeting) - }) -_sym_db.RegisterMessage(LanguageTargeting) - -SdkVersionTargeting = _reflection.GeneratedProtocolMessageType('SdkVersionTargeting', (_message.Message,), { - 'DESCRIPTOR' : _SDKVERSIONTARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.SdkVersionTargeting) - }) -_sym_db.RegisterMessage(SdkVersionTargeting) - -TextureCompressionFormatTargeting = _reflection.GeneratedProtocolMessageType('TextureCompressionFormatTargeting', (_message.Message,), { - 'DESCRIPTOR' : _TEXTURECOMPRESSIONFORMATTARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.TextureCompressionFormatTargeting) - }) -_sym_db.RegisterMessage(TextureCompressionFormatTargeting) - -SanitizerTargeting = _reflection.GeneratedProtocolMessageType('SanitizerTargeting', (_message.Message,), { - 'DESCRIPTOR' : _SANITIZERTARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.SanitizerTargeting) - }) -_sym_db.RegisterMessage(SanitizerTargeting) - -DeviceFeatureTargeting = _reflection.GeneratedProtocolMessageType('DeviceFeatureTargeting', (_message.Message,), { - 'DESCRIPTOR' : _DEVICEFEATURETARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.DeviceFeatureTargeting) - }) -_sym_db.RegisterMessage(DeviceFeatureTargeting) - -DeviceTierTargeting = _reflection.GeneratedProtocolMessageType('DeviceTierTargeting', (_message.Message,), { - 'DESCRIPTOR' : _DEVICETIERTARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.DeviceTierTargeting) - }) -_sym_db.RegisterMessage(DeviceTierTargeting) - -DeviceGroupModuleTargeting = _reflection.GeneratedProtocolMessageType('DeviceGroupModuleTargeting', (_message.Message,), { - 'DESCRIPTOR' : _DEVICEGROUPMODULETARGETING, - '__module__' : 'targeting_pb2' - # @@protoc_insertion_point(class_scope:android.bundle.DeviceGroupModuleTargeting) - }) -_sym_db.RegisterMessage(DeviceGroupModuleTargeting) - - -DESCRIPTOR._options = None +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0ftargeting.proto\x12\x0e\x61ndroid.bundle\x1a\x1egoogle/protobuf/wrappers.proto\"\xba\x03\n\x10VariantTargeting\x12\x42\n\x15sdk_version_targeting\x18\x01 \x01(\x0b\x32#.android.bundle.SdkVersionTargeting\x12\x33\n\rabi_targeting\x18\x02 \x01(\x0b\x32\x1c.android.bundle.AbiTargeting\x12H\n\x18screen_density_targeting\x18\x03 \x01(\x0b\x32&.android.bundle.ScreenDensityTargeting\x12>\n\x13multi_abi_targeting\x18\x04 \x01(\x0b\x32!.android.bundle.MultiAbiTargeting\x12_\n$texture_compression_format_targeting\x18\x05 \x01(\x0b\x32\x31.android.bundle.TextureCompressionFormatTargeting\x12\x42\n\x15sdk_runtime_targeting\x18\x06 \x01(\x0b\x32#.android.bundle.SdkRuntimeTargeting\"\xce\x05\n\x0c\x41pkTargeting\x12\x33\n\rabi_targeting\x18\x01 \x01(\x0b\x32\x1c.android.bundle.AbiTargeting\x12=\n\x12language_targeting\x18\x03 \x01(\x0b\x32!.android.bundle.LanguageTargeting\x12H\n\x18screen_density_targeting\x18\x04 \x01(\x0b\x32&.android.bundle.ScreenDensityTargeting\x12\x42\n\x15sdk_version_targeting\x18\x05 \x01(\x0b\x32#.android.bundle.SdkVersionTargeting\x12_\n$texture_compression_format_targeting\x18\x06 \x01(\x0b\x32\x31.android.bundle.TextureCompressionFormatTargeting\x12>\n\x13multi_abi_targeting\x18\x07 \x01(\x0b\x32!.android.bundle.MultiAbiTargeting\x12?\n\x13sanitizer_targeting\x18\x08 \x01(\x0b\x32\".android.bundle.SanitizerTargeting\x12\x46\n\x15\x64\x65vice_tier_targeting\x18\t \x01(\x0b\x32#.android.bundle.DeviceTierTargetingB\x02\x18\x01\x12\x46\n\x15\x63ountry_set_targeting\x18\n \x01(\x0b\x32#.android.bundle.CountrySetTargetingB\x02\x18\x01\x12\x44\n\x16\x64\x65vice_group_targeting\x18\x0b \x01(\x0b\x32$.android.bundle.DeviceGroupTargetingJ\x04\x08\x02\x10\x03\"\xbb\x02\n\x0fModuleTargeting\x12\x42\n\x15sdk_version_targeting\x18\x01 \x01(\x0b\x32#.android.bundle.SdkVersionTargeting\x12H\n\x18\x64\x65vice_feature_targeting\x18\x02 \x03(\x0b\x32&.android.bundle.DeviceFeatureTargeting\x12H\n\x18user_countries_targeting\x18\x03 \x01(\x0b\x32&.android.bundle.UserCountriesTargeting\x12J\n\x16\x64\x65vice_group_targeting\x18\x05 \x01(\x0b\x32*.android.bundle.DeviceGroupModuleTargetingJ\x04\x08\x04\x10\x05\"\xac\x01\n\x14\x41ssetModuleTargeting\x12H\n\x18user_countries_targeting\x18\x01 \x01(\x0b\x32&.android.bundle.UserCountriesTargeting\x12J\n\x16\x64\x65vice_group_targeting\x18\x02 \x01(\x0b\x32*.android.bundle.DeviceGroupModuleTargeting\"@\n\x16UserCountriesTargeting\x12\x15\n\rcountry_codes\x18\x01 \x03(\t\x12\x0f\n\x07\x65xclude\x18\x02 \x01(\x08\"\xfd\x01\n\rScreenDensity\x12\x43\n\rdensity_alias\x18\x01 \x01(\x0e\x32*.android.bundle.ScreenDensity.DensityAliasH\x00\x12\x15\n\x0b\x64\x65nsity_dpi\x18\x02 \x01(\x05H\x00\"\x7f\n\x0c\x44\x65nsityAlias\x12\x17\n\x13\x44\x45NSITY_UNSPECIFIED\x10\x00\x12\t\n\x05NODPI\x10\x01\x12\x08\n\x04LDPI\x10\x02\x12\x08\n\x04MDPI\x10\x03\x12\t\n\x05TVDPI\x10\x04\x12\x08\n\x04HDPI\x10\x05\x12\t\n\x05XHDPI\x10\x06\x12\n\n\x06XXHDPI\x10\x07\x12\x0b\n\x07XXXHDPI\x10\x08\x42\x0f\n\rdensity_oneof\"6\n\nSdkVersion\x12(\n\x03min\x18\x01 \x01(\x0b\x32\x1b.google.protobuf.Int32Value\"\xb0\x02\n\x18TextureCompressionFormat\x12U\n\x05\x61lias\x18\x01 \x01(\x0e\x32\x46.android.bundle.TextureCompressionFormat.TextureCompressionFormatAlias\"\xbc\x01\n\x1dTextureCompressionFormatAlias\x12*\n&UNSPECIFIED_TEXTURE_COMPRESSION_FORMAT\x10\x00\x12\r\n\tETC1_RGB8\x10\x01\x12\x0c\n\x08PALETTED\x10\x02\x12\x0c\n\x08THREE_DC\x10\x03\x12\x07\n\x03\x41TC\x10\x04\x12\x08\n\x04LATC\x10\x05\x12\x08\n\x04\x44XT1\x10\x06\x12\x08\n\x04S3TC\x10\x07\x12\t\n\x05PVRTC\x10\x08\x12\x08\n\x04\x41STC\x10\t\x12\x08\n\x04\x45TC2\x10\n\"\xc6\x01\n\x03\x41\x62i\x12+\n\x05\x61lias\x18\x01 \x01(\x0e\x32\x1c.android.bundle.Abi.AbiAlias\"\x91\x01\n\x08\x41\x62iAlias\x12 \n\x1cUNSPECIFIED_CPU_ARCHITECTURE\x10\x00\x12\x0b\n\x07\x41RMEABI\x10\x01\x12\x0f\n\x0b\x41RMEABI_V7A\x10\x02\x12\r\n\tARM64_V8A\x10\x03\x12\x07\n\x03X86\x10\x04\x12\n\n\x06X86_64\x10\x05\x12\x08\n\x04MIPS\x10\x06\x12\n\n\x06MIPS64\x10\x07\x12\x0b\n\x07RISCV64\x10\x08\",\n\x08MultiAbi\x12 \n\x03\x61\x62i\x18\x01 \x03(\x0b\x32\x13.android.bundle.Abi\"o\n\tSanitizer\x12\x37\n\x05\x61lias\x18\x01 \x01(\x0e\x32(.android.bundle.Sanitizer.SanitizerAlias\")\n\x0eSanitizerAlias\x12\x08\n\x04NONE\x10\x00\x12\r\n\tHWADDRESS\x10\x01\">\n\rDeviceFeature\x12\x14\n\x0c\x66\x65\x61ture_name\x18\x01 \x01(\t\x12\x17\n\x0f\x66\x65\x61ture_version\x18\x02 \x01(\x05\"\x8f\x03\n\x18\x41ssetsDirectoryTargeting\x12)\n\x03\x61\x62i\x18\x01 \x01(\x0b\x32\x1c.android.bundle.AbiTargeting\x12U\n\x1atexture_compression_format\x18\x03 \x01(\x0b\x32\x31.android.bundle.TextureCompressionFormatTargeting\x12\x33\n\x08language\x18\x04 \x01(\x0b\x32!.android.bundle.LanguageTargeting\x12<\n\x0b\x64\x65vice_tier\x18\x05 \x01(\x0b\x32#.android.bundle.DeviceTierTargetingB\x02\x18\x01\x12<\n\x0b\x63ountry_set\x18\x06 \x01(\x0b\x32#.android.bundle.CountrySetTargetingB\x02\x18\x01\x12:\n\x0c\x64\x65vice_group\x18\x07 \x01(\x0b\x32$.android.bundle.DeviceGroupTargetingJ\x04\x08\x02\x10\x03\"\xbe\x01\n\x18NativeDirectoryTargeting\x12 \n\x03\x61\x62i\x18\x01 \x01(\x0b\x32\x13.android.bundle.Abi\x12L\n\x1atexture_compression_format\x18\x03 \x01(\x0b\x32(.android.bundle.TextureCompressionFormat\x12,\n\tsanitizer\x18\x04 \x01(\x0b\x32\x19.android.bundle.SanitizerJ\x04\x08\x02\x10\x03\"J\n\x12\x41pexImageTargeting\x12\x34\n\tmulti_abi\x18\x01 \x01(\x0b\x32!.android.bundle.MultiAbiTargeting\"]\n\x0c\x41\x62iTargeting\x12\"\n\x05value\x18\x01 \x03(\x0b\x32\x13.android.bundle.Abi\x12)\n\x0c\x61lternatives\x18\x02 \x03(\x0b\x32\x13.android.bundle.Abi\"l\n\x11MultiAbiTargeting\x12\'\n\x05value\x18\x01 \x03(\x0b\x32\x18.android.bundle.MultiAbi\x12.\n\x0c\x61lternatives\x18\x02 \x03(\x0b\x32\x18.android.bundle.MultiAbi\"{\n\x16ScreenDensityTargeting\x12,\n\x05value\x18\x01 \x03(\x0b\x32\x1d.android.bundle.ScreenDensity\x12\x33\n\x0c\x61lternatives\x18\x02 \x03(\x0b\x32\x1d.android.bundle.ScreenDensity\"8\n\x11LanguageTargeting\x12\r\n\x05value\x18\x01 \x03(\t\x12\x14\n\x0c\x61lternatives\x18\x02 \x03(\t\"r\n\x13SdkVersionTargeting\x12)\n\x05value\x18\x01 \x03(\x0b\x32\x1a.android.bundle.SdkVersion\x12\x30\n\x0c\x61lternatives\x18\x02 \x03(\x0b\x32\x1a.android.bundle.SdkVersion\"\x9c\x01\n!TextureCompressionFormatTargeting\x12\x37\n\x05value\x18\x01 \x03(\x0b\x32(.android.bundle.TextureCompressionFormat\x12>\n\x0c\x61lternatives\x18\x02 \x03(\x0b\x32(.android.bundle.TextureCompressionFormat\">\n\x12SanitizerTargeting\x12(\n\x05value\x18\x01 \x03(\x0b\x32\x19.android.bundle.Sanitizer\"Q\n\x16\x44\x65viceFeatureTargeting\x12\x37\n\x10required_feature\x18\x01 \x01(\x0b\x32\x1d.android.bundle.DeviceFeature\"\x80\x01\n\x13\x44\x65viceTierTargeting\x12*\n\x05value\x18\x03 \x03(\x0b\x32\x1b.google.protobuf.Int32Value\x12\x31\n\x0c\x61lternatives\x18\x04 \x03(\x0b\x32\x1b.google.protobuf.Int32ValueJ\x04\x08\x01\x10\x02J\x04\x08\x02\x10\x03\":\n\x13\x43ountrySetTargeting\x12\r\n\x05value\x18\x01 \x03(\t\x12\x14\n\x0c\x61lternatives\x18\x02 \x03(\t\";\n\x14\x44\x65viceGroupTargeting\x12\r\n\x05value\x18\x01 \x03(\t\x12\x14\n\x0c\x61lternatives\x18\x02 \x03(\t\"+\n\x1a\x44\x65viceGroupModuleTargeting\x12\r\n\x05value\x18\x01 \x03(\t\"3\n\x13SdkRuntimeTargeting\x12\x1c\n\x14requires_sdk_runtime\x18\x01 \x01(\x08\x42\x14\n\x12\x63om.android.bundleb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'targeting_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\022com.android.bundle' + _globals['_APKTARGETING'].fields_by_name['device_tier_targeting']._loaded_options = None + _globals['_APKTARGETING'].fields_by_name['device_tier_targeting']._serialized_options = b'\030\001' + _globals['_APKTARGETING'].fields_by_name['country_set_targeting']._loaded_options = None + _globals['_APKTARGETING'].fields_by_name['country_set_targeting']._serialized_options = b'\030\001' + _globals['_ASSETSDIRECTORYTARGETING'].fields_by_name['device_tier']._loaded_options = None + _globals['_ASSETSDIRECTORYTARGETING'].fields_by_name['device_tier']._serialized_options = b'\030\001' + _globals['_ASSETSDIRECTORYTARGETING'].fields_by_name['country_set']._loaded_options = None + _globals['_ASSETSDIRECTORYTARGETING'].fields_by_name['country_set']._serialized_options = b'\030\001' + _globals['_VARIANTTARGETING']._serialized_start=68 + _globals['_VARIANTTARGETING']._serialized_end=510 + _globals['_APKTARGETING']._serialized_start=513 + _globals['_APKTARGETING']._serialized_end=1231 + _globals['_MODULETARGETING']._serialized_start=1234 + _globals['_MODULETARGETING']._serialized_end=1549 + _globals['_ASSETMODULETARGETING']._serialized_start=1552 + _globals['_ASSETMODULETARGETING']._serialized_end=1724 + _globals['_USERCOUNTRIESTARGETING']._serialized_start=1726 + _globals['_USERCOUNTRIESTARGETING']._serialized_end=1790 + _globals['_SCREENDENSITY']._serialized_start=1793 + _globals['_SCREENDENSITY']._serialized_end=2046 + _globals['_SCREENDENSITY_DENSITYALIAS']._serialized_start=1902 + _globals['_SCREENDENSITY_DENSITYALIAS']._serialized_end=2029 + _globals['_SDKVERSION']._serialized_start=2048 + _globals['_SDKVERSION']._serialized_end=2102 + _globals['_TEXTURECOMPRESSIONFORMAT']._serialized_start=2105 + _globals['_TEXTURECOMPRESSIONFORMAT']._serialized_end=2409 + _globals['_TEXTURECOMPRESSIONFORMAT_TEXTURECOMPRESSIONFORMATALIAS']._serialized_start=2221 + _globals['_TEXTURECOMPRESSIONFORMAT_TEXTURECOMPRESSIONFORMATALIAS']._serialized_end=2409 + _globals['_ABI']._serialized_start=2412 + _globals['_ABI']._serialized_end=2610 + _globals['_ABI_ABIALIAS']._serialized_start=2465 + _globals['_ABI_ABIALIAS']._serialized_end=2610 + _globals['_MULTIABI']._serialized_start=2612 + _globals['_MULTIABI']._serialized_end=2656 + _globals['_SANITIZER']._serialized_start=2658 + _globals['_SANITIZER']._serialized_end=2769 + _globals['_SANITIZER_SANITIZERALIAS']._serialized_start=2728 + _globals['_SANITIZER_SANITIZERALIAS']._serialized_end=2769 + _globals['_DEVICEFEATURE']._serialized_start=2771 + _globals['_DEVICEFEATURE']._serialized_end=2833 + _globals['_ASSETSDIRECTORYTARGETING']._serialized_start=2836 + _globals['_ASSETSDIRECTORYTARGETING']._serialized_end=3235 + _globals['_NATIVEDIRECTORYTARGETING']._serialized_start=3238 + _globals['_NATIVEDIRECTORYTARGETING']._serialized_end=3428 + _globals['_APEXIMAGETARGETING']._serialized_start=3430 + _globals['_APEXIMAGETARGETING']._serialized_end=3504 + _globals['_ABITARGETING']._serialized_start=3506 + _globals['_ABITARGETING']._serialized_end=3599 + _globals['_MULTIABITARGETING']._serialized_start=3601 + _globals['_MULTIABITARGETING']._serialized_end=3709 + _globals['_SCREENDENSITYTARGETING']._serialized_start=3711 + _globals['_SCREENDENSITYTARGETING']._serialized_end=3834 + _globals['_LANGUAGETARGETING']._serialized_start=3836 + _globals['_LANGUAGETARGETING']._serialized_end=3892 + _globals['_SDKVERSIONTARGETING']._serialized_start=3894 + _globals['_SDKVERSIONTARGETING']._serialized_end=4008 + _globals['_TEXTURECOMPRESSIONFORMATTARGETING']._serialized_start=4011 + _globals['_TEXTURECOMPRESSIONFORMATTARGETING']._serialized_end=4167 + _globals['_SANITIZERTARGETING']._serialized_start=4169 + _globals['_SANITIZERTARGETING']._serialized_end=4231 + _globals['_DEVICEFEATURETARGETING']._serialized_start=4233 + _globals['_DEVICEFEATURETARGETING']._serialized_end=4314 + _globals['_DEVICETIERTARGETING']._serialized_start=4317 + _globals['_DEVICETIERTARGETING']._serialized_end=4445 + _globals['_COUNTRYSETTARGETING']._serialized_start=4447 + _globals['_COUNTRYSETTARGETING']._serialized_end=4505 + _globals['_DEVICEGROUPTARGETING']._serialized_start=4507 + _globals['_DEVICEGROUPTARGETING']._serialized_end=4566 + _globals['_DEVICEGROUPMODULETARGETING']._serialized_start=4568 + _globals['_DEVICEGROUPMODULETARGETING']._serialized_end=4611 + _globals['_SDKRUNTIMETARGETING']._serialized_start=4613 + _globals['_SDKRUNTIMETARGETING']._serialized_end=4664 # @@protoc_insertion_point(module_scope) diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index cfddcd89af..259ce58289 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -79,6 +79,10 @@ def _model_to_bam(_build_cmd, srcpath, dstpath): _register_python_loaders() + if src_fn.get_extension() == 'egg' and zipfile.is_zipfile(src_fn.to_os_specific()): + _build_cmd.warn('Skipping %s as it appears to be a Python .egg archive, was this included by mistake?' % (src_fn.to_os_specific())) + return + loader = p3d.Loader.get_global_ptr() options = p3d.LoaderOptions(p3d.LoaderOptions.LF_report_errors | p3d.LoaderOptions.LF_no_ram_cache) @@ -192,14 +196,14 @@ SITE_PY_ANDROID = """ # module. import sys, os from importlib import _bootstrap, _bootstrap_external +from android_support import log_write as android_log_write +from android_support import find_library class AndroidExtensionFinder: @classmethod def find_spec(cls, fullname, path=None, target=None): - soname = 'libpy.' + fullname + '.so' - path = os.path.join(sys.platlibdir, soname) - - if os.path.exists(path): + path = find_library('py.' + fullname) + if path: loader = _bootstrap_external.ExtensionFileLoader(fullname, path) return _bootstrap.ModuleSpec(fullname, loader, origin=path) @@ -210,8 +214,6 @@ sys.meta_path.append(AndroidExtensionFinder) from _frozen_importlib import _imp, FrozenImporter from io import RawIOBase, TextIOWrapper -from android_log import write as android_log_write - sys.frozen = True @@ -300,7 +302,8 @@ class build_apps(setuptools.Command): self.android_version_code = 1 self.android_min_sdk_version = 21 self.android_max_sdk_version = None - self.android_target_sdk_version = 30 + self.android_target_sdk_version = 35 + self.android_manifest_file = None self.gui_apps = {} self.console_apps = {} self.macos_main_app = None @@ -316,7 +319,10 @@ class build_apps(setuptools.Command): 'win_amd64', ] - if sys.version_info >= (3, 13): + if sys.version_info >= (3, 14): + # This version of Python is only available for 10.15+. + self.platforms[1] = 'macosx_10_15_x86_64' + elif sys.version_info >= (3, 13): # This version of Python is only available for 10.13+. self.platforms[1] = 'macosx_10_13_x86_64' @@ -586,6 +592,10 @@ class build_apps(setuptools.Command): data_dir = os.path.join(build_dir, 'assets') os.makedirs(data_dir, exist_ok=True) + res_dir = os.path.join(build_dir, 'res') + res_raw_dir = os.path.join(res_dir, 'raw') + os.makedirs(res_raw_dir, exist_ok=True) + for abi in self.android_abis: lib_dir = os.path.join(build_dir, 'lib', abi) os.makedirs(lib_dir, exist_ok=True) @@ -602,7 +612,7 @@ class build_apps(setuptools.Command): # We end up copying the data multiple times to the same # directory, but that's probably fine for now. - self.build_binaries(platform + suffix, lib_dir, data_dir) + self.build_binaries(platform + suffix, lib_dir, data_dir, res_raw_dir) # Write out the icons to the res directory. for appname, icon in self.icon_objects.items(): @@ -610,9 +620,9 @@ class build_apps(setuptools.Command): # Conventional name for icon on Android. basename = 'ic_launcher.png' else: - basename = f'ic_{appname}.png' + appname_sane = appname.replace(' ', '_') + basename = f'ic_{appname_sane}.png' - res_dir = os.path.join(build_dir, 'res') icon.writeSize(48, os.path.join(res_dir, 'mipmap-mdpi-v4', basename)) icon.writeSize(72, os.path.join(res_dir, 'mipmap-hdpi-v4', basename)) icon.writeSize(96, os.path.join(res_dir, 'mipmap-xhdpi-v4', basename)) @@ -623,8 +633,17 @@ class build_apps(setuptools.Command): self.build_assets(platform, data_dir) - # Generate an AndroidManifest.xml - self.generate_android_manifest(os.path.join(build_dir, 'AndroidManifest.xml')) + # Generate an AndroidManifest.xml if none was provided + manifest_path = os.path.join(build_dir, 'AndroidManifest.xml') + if self.android_manifest_file: + try: + self.check_android_manifest(self.android_manifest_file) + except Exception as e: + self.announce(f"Failed to use provided manifest file from {self.android_manifest_file}", distutils.log.FATAL) + raise + self.copy(self.android_manifest_file, manifest_path) + else: + self.generate_android_manifest(manifest_path) else: self.build_binaries(platform, build_dir, build_dir) self.build_assets(platform, build_dir) @@ -691,8 +710,14 @@ class build_apps(setuptools.Command): subprocess.check_call([sys.executable, '-m', 'pip'] + pip_args) except: # Display a more helpful message for these common issues. - if platform.startswith('macosx_10_9_') and sys.version_info >= (3, 13): - new_platform = platform.replace('macosx_10_9_', 'macosx_10_13_') + if platform.startswith('macosx_10_13_') and sys.version_info >= (3, 14): + new_platform = platform.replace('macosx_10_13_', 'macosx_10_15_') + self.announce('This error likely occurs because {} is not a supported target as of Python 3.14.\nChange the target platform to {} instead.'.format(platform, new_platform), distutils.log.ERROR) + elif platform.startswith('macosx_10_9_') and sys.version_info >= (3, 13): + if sys.version_info >= (3, 14): + new_platform = platform.replace('macosx_10_9_', 'macosx_10_15_') + else: + new_platform = platform.replace('macosx_10_9_', 'macosx_10_13_') self.announce('This error likely occurs because {} is not a supported target as of Python 3.13.\nChange the target platform to {} instead.'.format(platform, new_platform), distutils.log.ERROR) elif platform.startswith('manylinux2010_') and sys.version_info >= (3, 11): new_platform = platform.replace('manylinux2010_', 'manylinux2014_') @@ -701,7 +726,9 @@ class build_apps(setuptools.Command): new_platform = platform.replace('manylinux1_', 'manylinux2014_') self.announce('This error likely occurs because {} is not a supported target as of Python 3.10.\nChange the target platform to {} instead.'.format(platform, new_platform), distutils.log.ERROR) elif platform.startswith('macosx_10_6_') and sys.version_info >= (3, 8): - if sys.version_info >= (3, 13): + if sys.version_info >= (3, 14): + new_platform = platform.replace('macosx_10_6_', 'macosx_10_15_') + elif sys.version_info >= (3, 13): new_platform = platform.replace('macosx_10_6_', 'macosx_10_13_') else: new_platform = platform.replace('macosx_10_6_', 'macosx_10_9_') @@ -855,7 +882,7 @@ class build_apps(setuptools.Command): if category: application.set('android:appCategory', category) application.set('android:debuggable', ('false', 'true')[self.android_debuggable]) - application.set('android:extractNativeLibs', 'true') + application.set('android:extractNativeLibs', 'false') application.set('android:hardwareAccelerated', 'true') app_icon = self.icon_objects.get('*', self.icon_objects.get(self.macos_main_app)) @@ -863,6 +890,8 @@ class build_apps(setuptools.Command): application.set('android:icon', '@mipmap/ic_launcher') for appname in self.gui_apps: + appname_sane = appname.replace(' ', '_') + activity = ET.SubElement(application, 'activity') activity.set('android:name', 'org.panda3d.android.PythonActivity') activity.set('android:label', appname) @@ -871,14 +900,19 @@ class build_apps(setuptools.Command): activity.set('android:configChanges', 'layoutDirection|locale|grammaticalGender|fontScale|fontWeightAdjustment|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation') activity.set('android:launchMode', 'singleInstance') activity.set('android:preferMinimalPostProcessing', 'true') + activity.set('android:exported', 'true') act_icon = self.icon_objects.get(appname) if act_icon and act_icon is not app_icon: - activity.set('android:icon', '@mipmap/ic_' + appname) + activity.set('android:icon', '@mipmap/ic_' + appname_sane) meta_data = ET.SubElement(activity, 'meta-data') meta_data.set('android:name', 'android.app.lib_name') - meta_data.set('android:value', appname) + meta_data.set('android:value', appname_sane) + + meta_data = ET.SubElement(activity, 'meta-data') + meta_data.set('android:name', 'org.panda3d.android.BLOB_RESOURCE') + meta_data.set('android:resource', '@raw/' + appname_sane + '.so') intent_filter = ET.SubElement(activity, 'intent-filter') ET.SubElement(intent_filter, 'action').set('android:name', 'android.intent.action.MAIN') @@ -886,10 +920,44 @@ class build_apps(setuptools.Command): ET.SubElement(intent_filter, 'category').set('android:name', 'android.intent.category.LEANBACK_LAUNCHER') tree = ET.ElementTree(manifest) + if sys.version_info >= (3, 9): + ET.indent(tree) with open(path, 'wb') as fh: tree.write(fh, encoding='utf-8', xml_declaration=True) - def build_binaries(self, platform, binary_dir, data_dir=None): + def check_android_manifest(self, path): + """ Checks that the user-provided manifest file seems OK. """ + + # This function doesn't aim to check everything as it's the user's + # responsibility, just a basic sanity check, but if we change anything + # in our own generation logic then it would be good to check those + # things here and warn if anything needs to be updated. + + import xml.etree.ElementTree as ET + + android = '{http://schemas.android.com/apk/res/android}' + + tree = ET.parse(path) + root = tree.getroot() + if root.tag != 'manifest': + raise RuntimeError(f"Expected in {path}") + + if root.attrib['package'] != self.application_id: + raise RuntimeError(f" package attribute does not match given application_id {self.application_id}") + + apps = root.findall('application') + if len(apps) != 1: + raise RuntimeError(" must contain exactly one ") + + application = apps[0] + for activity in application.iter('activity'): + if f'{android}name' not in activity.attrib: + raise RuntimeError(" element must have android:name attribute") + + if self.android_target_sdk_version >= 31 and f'{android}exported' not in activity.attrib: + raise RuntimeError(" element must have android:exported attribute when targeting Android API 31+") + + def build_binaries(self, platform, binary_dir, data_dir=None, blob_dir=None): """ Builds the binary data for the given platform. """ use_wheels = True @@ -1085,13 +1153,26 @@ class build_apps(setuptools.Command): stub_name = 'deploy-stub' target_name = appname + appname_sane = appname if platform.startswith('win') or 'macosx' in platform: if not use_console: stub_name = 'deploy-stubw' elif platform.startswith('android'): if not use_console: stub_name = 'libdeploy-stubw.so' - target_name = 'lib' + target_name + '.so' + appname_sane = appname.replace(' ', '_') + target_name = 'lib' + appname_sane + '.so' + + if use_wheels: + dexfile = os.path.join(binary_dir, '..', '..', 'classes.dex') + self.copy(os.path.join(p3dwhlfn, 'deploy_libs', 'classes.dex'), dexfile) + + # Can this wheel load the blob as a raw resource? + with open(dexfile, 'rb') as fh: + supports_blob_resource = b'org.panda3d.android.BLOB_RESOURCE' in fh.read() + + assert supports_blob_resource, \ + "Please use a newer Panda3D wheel to build for Android using this version of build_apps" if platform.startswith('win'): stub_name += '.exe' @@ -1123,6 +1204,14 @@ class build_apps(setuptools.Command): if not self.log_filename or '%' not in self.log_filename: use_strftime = False + blob_path = None + if blob_dir is not None: + if platform.startswith('android'): + # Not really a .so file, but it forces bundletool to align it + blob_path = os.path.join(blob_dir, appname_sane + '.so') + else: + blob_path = os.path.join(blob_dir, appname_sane) + target_path = os.path.join(binary_dir, target_name) freezer.generateRuntimeFromStub(target_path, stub_file, use_console, { 'prc_data': prcexport if self.embed_prc_data else None, @@ -1136,7 +1225,7 @@ class build_apps(setuptools.Command): 'prc_executable_args_envvar': None, 'main_dir': None, 'log_filename': self.expand_path(self.log_filename, platform), - }, self.log_append, use_strftime) + }, self.log_append, use_strftime, blob_path) stub_file.close() if temp_file: @@ -1251,11 +1340,6 @@ class build_apps(setuptools.Command): search_path = get_search_path_for(source_path) self.copy_with_dependencies(source_path, target_path, search_path) - # Copy classes.dex on Android - if use_wheels and platform.startswith('android'): - self.copy(os.path.join(p3dwhlfn, 'deploy_libs', 'classes.dex'), - os.path.join(binary_dir, '..', '..', 'classes.dex')) - # Extract any other data files from dependency packages. if data_dir is None: return diff --git a/direct/src/dist/installers.py b/direct/src/dist/installers.py index f09cef7841..c5b1475190 100644 --- a/direct/src/dist/installers.py +++ b/direct/src/dist/installers.py @@ -207,7 +207,7 @@ def create_aab(command, basename, build_dir): and use it to convert an .aab into an .apk. """ - from ._android import AndroidManifest, AbiAlias, BundleConfig, NativeLibraries, ResourceTable + from ._android import AndroidManifest, AbiAlias, BundleConfig, NativeLibraries, ResourceTable, UncompressNativeLibraries bundle_fn = p3d.Filename.from_os_specific(command.dist_dir) / (basename + '.aab') build_dir_fn = p3d.Filename.from_os_specific(build_dir) @@ -230,7 +230,13 @@ def create_aab(command, basename, build_dir): config = BundleConfig() config.bundletool.version = '1.1.0' config.optimizations.splits_config.Clear() - config.optimizations.uncompress_native_libraries.enabled = False + if axml.extract_native_libs: + config.optimizations.uncompress_native_libraries.enabled = False + else: + config.optimizations.uncompress_native_libraries.enabled = True + config.optimizations.uncompress_native_libraries.alignment = \ + UncompressNativeLibraries.PageAlignment.PAGE_ALIGNMENT_16K + config.compression.uncompressed_glob.append('res/raw/**') bundle.add_subfile('BundleConfig.pb', p3d.StringStream(config.SerializeToString()), 9) resources = ResourceTable() @@ -251,13 +257,25 @@ def create_aab(command, basename, build_dir): entry.entry_id.id = entry_id entry.name = res_name - for density, tag in (160, 'mdpi'), (240, 'hdpi'), (320, 'xhdpi'), (480, 'xxhdpi'), (640, 'xxxhdpi'): - path = f'res/mipmap-{tag}-v4/{res_name}.png' - if (build_dir_fn / path).exists(): - bundle.add_subfile('base/' + path, build_dir_fn / path, 0) - config_value = entry.config_value.add() - config_value.config.density = density - config_value.value.item.file.path = path + if type_name == 'raw': + path = f'res/raw/{res_name}' + if not (build_dir_fn / path).exists(): + command.announce( + f'\tRaw resource {path} was not found on disk', distutils.log.ERROR) + return + + # These are aligned to page size for mmap. + bundle.add_subfile('base/' + path, build_dir_fn / path, 0, 16384) + config_value = entry.config_value.add() + config_value.value.item.file.path = path + else: + for density, tag in (120, 'ldpi'), (160, 'mdpi'), (240, 'hdpi'), (320, 'xhdpi'), (480, 'xxhdpi'), (640, 'xxxhdpi'): + path = f'res/mipmap-{tag}-v4/{res_name}.png' + if (build_dir_fn / path).exists(): + bundle.add_subfile('base/' + path, build_dir_fn / path, 0) + config_value = entry.config_value.add() + config_value.config.density = density + config_value.value.item.file.path = path bundle.add_subfile('base/resources.pb', p3d.StringStream(resources.SerializeToString()), 9) @@ -273,13 +291,25 @@ def create_aab(command, basename, build_dir): # Add the classes.dex. bundle.add_subfile('base/dex/classes.dex', build_dir_fn / 'classes.dex', 9) - # Add libraries, compressed. + # Add libraries, compressed, unless extractNativeLibs is false, in which + # case they have to be aligned to page boundaries (16 KiB on 64-bit). + lib_compress = 9 if axml.extract_native_libs else 0 + for abi in os.listdir(os.path.join(build_dir, 'lib')): abi_dir = os.path.join(build_dir, 'lib', abi) + if axml.extract_native_libs: + lib_align = 0 + elif '64' in abi: + lib_align = 16384 + else: + lib_align = 4096 + for lib in os.listdir(abi_dir): if lib.startswith('lib') and lib.endswith('.so'): - bundle.add_subfile(f'base/lib/{abi}/{lib}', build_dir_fn / 'lib' / abi / lib, 9) + bundle.add_subfile(f'base/lib/{abi}/{lib}', + build_dir_fn / 'lib' / abi / lib, + lib_compress, lib_align) # Add assets, compressed. assets_dir = os.path.join(build_dir, 'assets') diff --git a/direct/src/distributed/ClockDelta.py b/direct/src/distributed/ClockDelta.py index 7f8da75706..ebba8a7c97 100644 --- a/direct/src/distributed/ClockDelta.py +++ b/direct/src/distributed/ClockDelta.py @@ -48,7 +48,7 @@ class ClockDelta(DirectObject.DirectObject): notify = DirectNotifyGlobal.directNotify.newCategory('ClockDelta') - def __init__(self): + def __init__(self) -> None: self.globalClock = ClockObject.getGlobalClock() # self.delta is the relative delta from our clock to the diff --git a/direct/src/distributed/ConnectionRepository.py b/direct/src/distributed/ConnectionRepository.py index a29c61cafa..f3ddc96f4f 100644 --- a/direct/src/distributed/ConnectionRepository.py +++ b/direct/src/distributed/ConnectionRepository.py @@ -1,4 +1,4 @@ -from panda3d.core import DocumentSpec, Filename, HTTPClient, VirtualFileSystem, getModelPath +from panda3d.core import DocumentSpec, Filename, VirtualFileSystem, getModelPath from panda3d.direct import CConnectionRepository, DCPacker from direct.task import Task from direct.task.TaskManagerGlobal import taskMgr @@ -590,6 +590,7 @@ class ConnectionRepository( if self.http is None: try: + from panda3d.core import HTTPClient self.http = HTTPClient() except Exception: pass diff --git a/direct/src/distributed/DistributedObjectAI.py b/direct/src/distributed/DistributedObjectAI.py index baacb4e998..c467845e68 100644 --- a/direct/src/distributed/DistributedObjectAI.py +++ b/direct/src/distributed/DistributedObjectAI.py @@ -317,7 +317,7 @@ class DistributedObjectAI(DistributedObjectBase): # setLocation destroys self._zoneData if we move away to # a different zone if self._zoneData is None: - from otp.ai.AIZoneData import AIZoneData # type: ignore[import] + from otp.ai.AIZoneData import AIZoneData # type: ignore[import-not-found] self._zoneData = AIZoneData(self.air, self.parentId, self.zoneId) return self._zoneData @@ -515,7 +515,7 @@ class DistributedObjectAI(DistributedObjectBase): # simultaneously on different lists of avatars, although they # should have different names. - from otp.ai import Barrier # type: ignore[import] + from otp.ai import Barrier # type: ignore[import-not-found] context = self.__nextBarrierContext # We assume the context number is passed as a uint16. self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff diff --git a/direct/src/distributed/DistributedObjectUD.py b/direct/src/distributed/DistributedObjectUD.py index bd473a9c54..3b02e032f4 100755 --- a/direct/src/distributed/DistributedObjectUD.py +++ b/direct/src/distributed/DistributedObjectUD.py @@ -432,7 +432,7 @@ class DistributedObjectUD(DistributedObjectBase): # simultaneously on different lists of avatars, although they # should have different names. - from otp.ai import Barrier # type: ignore[import] + from otp.ai import Barrier # type: ignore[import-not-found] context = self.__nextBarrierContext # We assume the context number is passed as a uint16. self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff diff --git a/direct/src/distributed/direct.dc b/direct/src/distributed/direct.dc index 6468d91b29..b9387b20dc 100644 --- a/direct/src/distributed/direct.dc +++ b/direct/src/distributed/direct.dc @@ -84,8 +84,8 @@ dclass DistributedSmoothNode: DistributedNode { suggestResync(uint32 avId, int16 timestampA, int16 timestampB, int32 serverTimeSec, uint16 serverTimeUSec, - uint16 / 100 uncertainty); + int16 / 100 uncertainty); returnResync(uint32 avId, int16 timestampB, int32 serverTimeSec, uint16 serverTimeUSec, - uint16 / 100 uncertainty); + int16 / 100 uncertainty); }; diff --git a/direct/src/extensions_native/extension_native_helpers.py b/direct/src/extensions_native/extension_native_helpers.py index 2a302f415e..59176d6851 100644 --- a/direct/src/extensions_native/extension_native_helpers.py +++ b/direct/src/extensions_native/extension_native_helpers.py @@ -1,15 +1,19 @@ +from __future__ import annotations + __all__ = ["Dtool_ObjectToDict", "Dtool_funcToMethod"] +from collections.abc import Callable + def Dtool_ObjectToDict(cls, name, obj): cls.DtoolClassDict[name] = obj -def Dtool_funcToMethod(func, cls, method_name=None): +def Dtool_funcToMethod(func: Callable, cls, method_name: str | None = None) -> None: """Adds func to class so it is an accessible method; use method_name to specify the name to be used for calling the method. The new method is accessible to any instance immediately.""" - func.__func__ = func - func.__self__ = None + func.__func__ = func # type: ignore[attr-defined] + func.__self__ = None # type: ignore[attr-defined] if not method_name: method_name = func.__name__ cls.DtoolClassDict[method_name] = func diff --git a/direct/src/gui/DirectButton.py b/direct/src/gui/DirectButton.py index 0ed5523854..c4d3a819d1 100644 --- a/direct/src/gui/DirectButton.py +++ b/direct/src/gui/DirectButton.py @@ -44,6 +44,7 @@ class DirectButton(DirectFrame): # Sounds to be used for button events ('rolloverSound', DGG.getDefaultRolloverSound(), self.setRolloverSound), ('clickSound', DGG.getDefaultClickSound(), self.setClickSound), + ('releaseSound', DGG.getDefaultReleaseSound(), self.setReleaseSound), # Can only be specified at time of widget contruction # Do the text/graphics appear to move when the button is clicked ('pressEffect', 1, DGG.INITOPT), @@ -108,6 +109,13 @@ class DirectButton(DirectFrame): # Pass any extra args to command self['command'](*self['extraArgs']) + def setRolloverSound(self): + rolloverSound = self['rolloverSound'] + if rolloverSound: + self.guiItem.setSound(DGG.ENTER + self.guiId, rolloverSound) + else: + self.guiItem.clearSound(DGG.ENTER + self.guiId) + def setClickSound(self): clickSound = self['clickSound'] # Clear out sounds @@ -122,9 +130,16 @@ class DirectButton(DirectFrame): if DGG.RMB in self['commandButtons']: self.guiItem.setSound(DGG.B3PRESS + self.guiId, clickSound) - def setRolloverSound(self): - rolloverSound = self['rolloverSound'] - if rolloverSound: - self.guiItem.setSound(DGG.ENTER + self.guiId, rolloverSound) - else: - self.guiItem.clearSound(DGG.ENTER + self.guiId) + def setReleaseSound(self): + releaseSound = self['releaseSound'] + # Clear out sounds + self.guiItem.clearSound(DGG.B1RELEASE + self.guiId) + self.guiItem.clearSound(DGG.B2RELEASE + self.guiId) + self.guiItem.clearSound(DGG.B3RELEASE + self.guiId) + if releaseSound: + if DGG.LMB in self['commandButtons']: + self.guiItem.setSound(DGG.B1RELEASE + self.guiId, releaseSound) + if DGG.MMB in self['commandButtons']: + self.guiItem.setSound(DGG.B2RELEASE + self.guiId, releaseSound) + if DGG.RMB in self['commandButtons']: + self.guiItem.setSound(DGG.B3RELEASE + self.guiId, releaseSound) diff --git a/direct/src/gui/DirectCheckBox.py b/direct/src/gui/DirectCheckBox.py index fc0ce5f5ca..d1cff642a8 100755 --- a/direct/src/gui/DirectCheckBox.py +++ b/direct/src/gui/DirectCheckBox.py @@ -28,6 +28,7 @@ class DirectCheckBox(DirectButton): # Sounds to be used for button events ('rolloverSound', DGG.getDefaultRolloverSound(), self.setRolloverSound), ('clickSound', DGG.getDefaultClickSound(), self.setClickSound), + ('releaseSound', DGG.getDefaultReleaseSound(), self.setReleaseSound), # Can only be specified at time of widget contruction # Do the text/graphics appear to move when the button is clicked ('pressEffect', 1, DGG.INITOPT), diff --git a/direct/src/gui/DirectGuiGlobals.py b/direct/src/gui/DirectGuiGlobals.py index d41199af0e..1048f8d724 100644 --- a/direct/src/gui/DirectGuiGlobals.py +++ b/direct/src/gui/DirectGuiGlobals.py @@ -17,8 +17,9 @@ from panda3d.core import ( defaultFont = None defaultFontFunc = TextNode.getDefaultFont -defaultClickSound = None defaultRolloverSound = None +defaultClickSound = None +defaultReleaseSound = None defaultDialogGeom = None defaultDialogRelief = PGFrameStyle.TBevelOut drawOrder = 100 @@ -129,6 +130,13 @@ def setDefaultClickSound(newSound): global defaultClickSound defaultClickSound = newSound +def getDefaultReleaseSound(): + return defaultReleaseSound + +def setDefaultReleaseSound(newSound): + global defaultReleaseSound + defaultReleaseSound = newSound + def getDefaultFont(): global defaultFont if defaultFont is None: diff --git a/direct/src/interval/IntervalManager.py b/direct/src/interval/IntervalManager.py index f3e3f7d13d..57476dc347 100644 --- a/direct/src/interval/IntervalManager.py +++ b/direct/src/interval/IntervalManager.py @@ -1,11 +1,14 @@ """Defines the IntervalManager class as well as the global instance of this class, ivalMgr.""" +from __future__ import annotations + __all__ = ['IntervalManager', 'ivalMgr'] from panda3d.core import EventQueue -from panda3d.direct import CIntervalManager, Dtool_BorrowThisReference +from panda3d.direct import CInterval, CIntervalManager, Dtool_BorrowThisReference from direct.showbase import EventManager +from . import Interval import fnmatch class IntervalManager(CIntervalManager): @@ -15,7 +18,7 @@ class IntervalManager(CIntervalManager): # the Python extensions is to add support for Python-based # intervals (like MetaIntervals). - def __init__(self, globalPtr = 0): + def __init__(self, globalPtr: bool = False) -> None: # Pass globalPtr == 1 to the constructor to trick it into # "constructing" a Python wrapper around the global # CIntervalManager object. @@ -28,8 +31,8 @@ class IntervalManager(CIntervalManager): self.eventQueue = EventQueue() self.MyEventmanager = EventManager.EventManager(self.eventQueue) self.setEventQueue(self.eventQueue) - self.ivals = [] - self.removedIvals = {} + self.ivals: list[Interval.Interval | CInterval | None] = [] + self.removedIvals: dict = {} def addInterval(self, interval): index = self.addCInterval(interval, 1) @@ -86,7 +89,7 @@ class IntervalManager(CIntervalManager): ival.pause() return len(ivals) - def step(self): + def step(self) -> None: # This method should be called once per frame to perform all # of the per-frame processing on the active intervals. # Call C++ step, then do the Python stuff. @@ -101,7 +104,7 @@ class IntervalManager(CIntervalManager): CIntervalManager.interrupt(self) self.__doPythonCallbacks() - def __doPythonCallbacks(self): + def __doPythonCallbacks(self) -> None: # This method does all of the required Python post-processing # after performing some C++-level action. # It is important to call all of the python callbacks on the @@ -137,4 +140,4 @@ class IntervalManager(CIntervalManager): self.ivals[index] = interval #: The global IntervalManager object. -ivalMgr = IntervalManager(1) +ivalMgr = IntervalManager(True) diff --git a/direct/src/interval/LerpInterval.py b/direct/src/interval/LerpInterval.py index e303b3a5d1..3058395d22 100644 --- a/direct/src/interval/LerpInterval.py +++ b/direct/src/interval/LerpInterval.py @@ -122,7 +122,7 @@ class LerpPosInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndPos, self.endPos) self.setupParam(self.setStartPos, self.startPos) LerpNodePathInterval.privDoEvent(self, t, event) @@ -153,7 +153,7 @@ class LerpHprInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndHpr, self.endHpr) self.setupParam(self.setStartHpr, self.startHpr) self.setupParam(self.setStartQuat, self.startQuat) @@ -192,7 +192,7 @@ class LerpQuatInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndQuat, self.endQuat) self.setupParam(self.setStartHpr, self.startHpr) self.setupParam(self.setStartQuat, self.startQuat) @@ -219,7 +219,7 @@ class LerpScaleInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndScale, self.endScale) self.setupParam(self.setStartScale, self.startScale) LerpNodePathInterval.privDoEvent(self, t, event) @@ -245,7 +245,7 @@ class LerpShearInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndShear, self.endShear) self.setupParam(self.setStartShear, self.startShear) LerpNodePathInterval.privDoEvent(self, t, event) @@ -280,7 +280,7 @@ class LerpPosHprInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndPos, self.endPos) self.setupParam(self.setStartPos, self.startPos) self.setupParam(self.setEndHpr, self.endHpr) @@ -326,7 +326,7 @@ class LerpPosQuatInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndPos, self.endPos) self.setupParam(self.setStartPos, self.startPos) self.setupParam(self.setEndQuat, self.endQuat) @@ -365,7 +365,7 @@ class LerpHprScaleInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndHpr, self.endHpr) self.setupParam(self.setStartHpr, self.startHpr) self.setupParam(self.setStartQuat, self.startQuat) @@ -414,7 +414,7 @@ class LerpQuatScaleInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndQuat, self.endQuat) self.setupParam(self.setStartHpr, self.startHpr) self.setupParam(self.setStartQuat, self.startQuat) @@ -459,7 +459,7 @@ class LerpPosHprScaleInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndPos, self.endPos) self.setupParam(self.setStartPos, self.startPos) self.setupParam(self.setEndHpr, self.endHpr) @@ -516,7 +516,7 @@ class LerpPosQuatScaleInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndPos, self.endPos) self.setupParam(self.setStartPos, self.startPos) self.setupParam(self.setEndQuat, self.endQuat) @@ -569,7 +569,7 @@ class LerpPosHprScaleShearInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndPos, self.endPos) self.setupParam(self.setStartPos, self.startPos) self.setupParam(self.setEndHpr, self.endHpr) @@ -636,7 +636,7 @@ class LerpPosQuatScaleShearInterval(LerpNodePathInterval): def privDoEvent(self, t, event): # This function is only used if Python functors were passed in # for some of the input parameters. - if self.paramSetup and event == CInterval.ETInitialize: + if self.paramSetup and (event == CInterval.ETInitialize or event == CInterval.ETInstant): self.setupParam(self.setEndPos, self.endPos) self.setupParam(self.setStartPos, self.startPos) self.setupParam(self.setEndQuat, self.endQuat) diff --git a/direct/src/showbase/BufferViewer.py b/direct/src/showbase/BufferViewer.py index 4a7babcbc2..9afdbccdb7 100644 --- a/direct/src/showbase/BufferViewer.py +++ b/direct/src/showbase/BufferViewer.py @@ -12,6 +12,8 @@ Or, you can enable the following variable in your Config.prc:: show-buffers true """ +from __future__ import annotations + __all__ = ['BufferViewer'] from panda3d.core import ( @@ -39,14 +41,19 @@ from direct.task.TaskManagerGlobal import taskMgr from direct.directnotify.DirectNotifyGlobal import directNotify from direct.showbase.DirectObject import DirectObject import math +from typing import Literal, Union + +# The following variables are typing constructs used in annotations +# to succinctly express complex type structures. +_Texture = Union[Texture, GraphicsOutput, Literal['all']] class BufferViewer(DirectObject): notify = directNotify.newCategory('BufferViewer') - def __init__(self, win, parent): + def __init__(self, win: GraphicsOutput | None, parent: NodePath) -> None: """Access: private. Constructor.""" - self.enabled = 0 + self.enabled = False size = ConfigVariableDouble('buffer-viewer-size', '0 0') self.sizex = size[0] self.sizey = size[1] @@ -59,23 +66,23 @@ class BufferViewer(DirectObject): self.win = win self.engine = GraphicsEngine.getGlobalPtr() self.renderParent = parent - self.cards = [] + self.cards: list[NodePath] = [] self.cardindex = 0 self.cardmaker = CardMaker("cubemaker") self.cardmaker.setFrame(-1,1,-1,1) self.task = 0 - self.dirty = 1 + self.dirty = True self.accept("render-texture-targets-changed", self.refreshReadout) if ConfigVariableBool("show-buffers", 0): - self.enable(1) + self.enable(True) - def refreshReadout(self): + def refreshReadout(self) -> None: """Force the readout to be refreshed. This is usually invoked by GraphicsOutput::add_render_texture (via an event handler). However, it is also possible to invoke it manually. Currently, the only time I know of that this is necessary is after a window resize (and I ought to fix that).""" - self.dirty = 1 + self.dirty = True # Call enabled again, mainly to ensure that the task has been # started. @@ -95,14 +102,14 @@ class BufferViewer(DirectObject): """Returns true if the buffer viewer is currently enabled.""" return self.enabled - def enable(self, x): + def enable(self, x: bool) -> None: """Turn the buffer viewer on or off. The initial state of the buffer viewer depends on the Config variable 'show-buffers'.""" if x != 0 and x != 1: BufferViewer.notify.error('invalid parameter to BufferViewer.enable') return self.enabled = x - self.dirty = 1 + self.dirty = True if (x and self.task == 0): self.task = taskMgr.add(self.maintainReadout, "buffer-viewer-maintain-readout", priority=1) @@ -218,7 +225,11 @@ class BufferViewer(DirectObject): self.renderParent = renderParent self.dirty = 1 - def analyzeTextureSet(self, x, set): + def analyzeTextureSet( + self, + x: _Texture | GraphicsEngine | list[_Texture | GraphicsEngine], + set: dict[Texture, int], + ) -> None: """Access: private. Converts a list of GraphicsObject, GraphicsEngine, and Texture into a table of Textures.""" @@ -240,7 +251,7 @@ class BufferViewer(DirectObject): else: return - def makeFrame(self, sizex, sizey): + def makeFrame(self, sizex: int, sizey: int) -> NodePath: """Access: private. Each texture card is displayed with a two-pixel wide frame (a ring of black and a ring of white). This routine builds the frame geometry. It is necessary to @@ -287,7 +298,7 @@ class BufferViewer(DirectObject): geomnode.addGeom(geom) return NodePath(geomnode) - def maintainReadout(self, task): + def maintainReadout(self, task: object) -> int: """Access: private. Whenever necessary, rebuilds the entire display from scratch. This is only done when the configuration parameters have changed.""" @@ -295,7 +306,7 @@ class BufferViewer(DirectObject): # If nothing has changed, don't update. if not self.dirty: return Task.cont - self.dirty = 0 + self.dirty = False # Delete the old set of cards. for card in self.cards: @@ -308,8 +319,8 @@ class BufferViewer(DirectObject): return Task.done # Generate the include and exclude sets. - exclude = {} - include = {} + exclude: dict[Texture, int] = {} + include: dict[Texture, int] = {} self.analyzeTextureSet(self.exclude, exclude) self.analyzeTextureSet(self.include, include) @@ -407,6 +418,7 @@ class BufferViewer(DirectObject): bordersize = 4.0 + assert self.win is not None if float(self.sizex) == 0.0 and float(self.sizey) == 0.0: sizey = int(0.4266666667 * self.win.getYSize()) sizex = (sizey * aspectx) // aspecty diff --git a/direct/src/showbase/DirectObject.py b/direct/src/showbase/DirectObject.py index 8365d9f94f..bdbae6fc3f 100644 --- a/direct/src/showbase/DirectObject.py +++ b/direct/src/showbase/DirectObject.py @@ -1,8 +1,14 @@ """Defines the DirectObject class, a convenient class to inherit from if the object needs to be able to respond to events.""" +from __future__ import annotations + __all__ = ['DirectObject'] +from typing import Callable + +from panda3d.core import AsyncTask + from direct.directnotify.DirectNotifyGlobal import directNotify from direct.task.TaskManagerGlobal import taskMgr from .MessengerGlobal import messenger @@ -12,6 +18,9 @@ class DirectObject: """ This is the class that all Direct/SAL classes should inherit from """ + _MSGRmessengerId: tuple[str, int] + _taskList: dict[int, AsyncTask] + #def __del__(self): # This next line is useful for debugging leaks #print "Destructing: ", self.__class__.__name__ @@ -19,16 +28,16 @@ class DirectObject: # Wrapper functions to have a cleaner, more object oriented approach to # the messenger functionality. - def accept(self, event, method, extraArgs=[]): - return messenger.accept(event, self, method, extraArgs, 1) + def accept(self, event: str, method: Callable, extraArgs: list = []) -> None: + return messenger.accept(event, self, method, extraArgs, True) def acceptOnce(self, event, method, extraArgs=[]): return messenger.accept(event, self, method, extraArgs, 0) - def ignore(self, event): + def ignore(self, event: str) -> None: return messenger.ignore(event, self) - def ignoreAll(self): + def ignoreAll(self) -> None: return messenger.ignoreAll(self) def isAccepting(self, event): @@ -41,7 +50,7 @@ class DirectObject: return messenger.isIgnoring(event, self) #This function must be used if you want a managed task - def addTask(self, *args, **kwargs): + def addTask(self, *args, **kwargs) -> AsyncTask: if not hasattr(self, "_taskList"): self._taskList = {} kwargs['owner'] = self diff --git a/direct/src/showbase/EventManager.py b/direct/src/showbase/EventManager.py index e603633810..14465120cb 100644 --- a/direct/src/showbase/EventManager.py +++ b/direct/src/showbase/EventManager.py @@ -1,21 +1,33 @@ """Contains the EventManager class. See :mod:`.EventManagerGlobal` for the global eventMgr instance.""" +from __future__ import annotations + __all__ = ['EventManager'] +from typing import Any + from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.directnotify.Notifier import Notifier from direct.task.TaskManagerGlobal import taskMgr from direct.showbase.MessengerGlobal import messenger -from panda3d.core import PStatCollector, EventQueue, EventHandler -from panda3d.core import ConfigVariableBool +from panda3d.core import ( + ConfigVariableBool, + Event, + EventHandler, + EventParameter, + EventQueue, + PStatCollector, + PythonTask, +) class EventManager: - notify = None + notify: Notifier | None = None - def __init__(self, eventQueue = None): + def __init__(self, eventQueue: EventQueue | None = None) -> None: """ Create a C++ event queue and handler """ @@ -24,11 +36,11 @@ class EventManager: EventManager.notify = directNotify.newCategory("EventManager") self.eventQueue = eventQueue - self.eventHandler = None + self.eventHandler: EventHandler | None = None self._wantPstats = ConfigVariableBool('pstats-eventmanager', False) - def doEvents(self): + def doEvents(self) -> None: """ Process all the events on the C++ event queue """ @@ -38,12 +50,13 @@ class EventManager: processFunc = self.processEventPstats else: processFunc = self.processEvent + assert self.eventQueue is not None isEmptyFunc = self.eventQueue.isQueueEmpty dequeueFunc = self.eventQueue.dequeueEvent while not isEmptyFunc(): processFunc(dequeueFunc()) - def eventLoopTask(self, task): + def eventLoopTask(self, task: PythonTask) -> int: """ Process all the events on the C++ event queue """ @@ -51,7 +64,7 @@ class EventManager: messenger.send("event-loop-done") return task.cont - def parseEventParameter(self, eventParameter): + def parseEventParameter(self, eventParameter: EventParameter) -> Any: """ Extract the actual data from the eventParameter """ @@ -63,6 +76,8 @@ class EventManager: return eventParameter.getStringValue() elif eventParameter.isWstring(): return eventParameter.getWstringValue() + elif hasattr(eventParameter, 'isBytes') and eventParameter.isBytes(): + return eventParameter.getBytesValue() elif eventParameter.isTypedRefCount(): return eventParameter.getTypedRefCountValue() elif eventParameter.isEmpty(): @@ -72,7 +87,7 @@ class EventManager: # which will be downcast to that type. return eventParameter.getPtr() - def processEvent(self, event): + def processEvent(self, event: Event) -> None: """ Process a C++ event Duplicate any changes in processEventPstats @@ -89,6 +104,7 @@ class EventManager: paramList.append(eventParameterData) # Do not print the new frame debug, it is too noisy! + assert EventManager.notify is not None if EventManager.notify.getDebug() and eventName != 'NewFrame': EventManager.notify.debug('received C++ event named: ' + eventName + ' parameters: ' + repr(paramList)) @@ -106,9 +122,10 @@ class EventManager: else: # An unnamed event from C++ is probably a bad thing + assert EventManager.notify is not None EventManager.notify.warning('unnamed event in processEvent') - def processEventPstats(self, event): + def processEventPstats(self, event: Event) -> None: """ Process a C++ event with pstats tracking Duplicate any changes in processEvent @@ -125,6 +142,7 @@ class EventManager: paramList.append(eventParameterData) # Do not print the new frame debug, it is too noisy! + assert EventManager.notify is not None if EventManager.notify.getDebug() and eventName != 'NewFrame': EventManager.notify.debug('received C++ event named: ' + eventName + ' parameters: ' + repr(paramList)) @@ -156,9 +174,10 @@ class EventManager: else: # An unnamed event from C++ is probably a bad thing + assert EventManager.notify is not None EventManager.notify.warning('unnamed event in processEvent') - def restart(self): + def restart(self) -> None: if self.eventQueue is None: self.eventQueue = EventQueue.getGlobalEventQueue() @@ -173,7 +192,7 @@ class EventManager: taskMgr.add(self.eventLoopTask, 'eventManager') - def shutdown(self): + def shutdown(self) -> None: taskMgr.remove('eventManager') # Flush the event queue. We do this after removing the task diff --git a/direct/src/showbase/ExceptionVarDump.py b/direct/src/showbase/ExceptionVarDump.py index aad5850e6f..308c368701 100755 --- a/direct/src/showbase/ExceptionVarDump.py +++ b/direct/src/showbase/ExceptionVarDump.py @@ -35,7 +35,7 @@ def _varDump__init__(self, *args, **kArgs): sReentry = 0 -def _varDump__print(exc): +def _varDump__print(exc) -> None: global sReentry global notify if sReentry > 0: @@ -176,7 +176,7 @@ def _excepthookDumpVars(eType, eValue, tb): oldExcepthook(eType, eValue, origTb) -def install(log, upload): +def install(log: bool, upload: bool) -> None: """Installs the exception hook.""" global oldExcepthook global wantStackDumpLog @@ -192,8 +192,8 @@ def install(log, upload): # thrown by the interpreter don't get created until the # stack has been unwound and an except block has been reached if not hasattr(Exception, '_moved__init__'): - Exception._moved__init__ = Exception.__init__ - Exception.__init__ = _varDump__init__ + Exception._moved__init__ = Exception.__init__ # type: ignore[attr-defined] + Exception.__init__ = _varDump__init__ # type: ignore[method-assign] else: if sys.excepthook is not _excepthookDumpVars: oldExcepthook = sys.excepthook diff --git a/direct/src/showbase/GarbageReport.py b/direct/src/showbase/GarbageReport.py index ca3d02b36a..d04892b1ff 100644 --- a/direct/src/showbase/GarbageReport.py +++ b/direct/src/showbase/GarbageReport.py @@ -1,5 +1,7 @@ """Contains utility classes for debugging memory leaks.""" +from __future__ import annotations + __all__ = ['FakeObject', '_createGarbage', 'GarbageReport', 'GarbageLogger'] from direct.directnotify.DirectNotifyGlobal import directNotify @@ -10,6 +12,7 @@ from direct.showbase.JobManagerGlobal import jobMgr from direct.showbase.MessengerGlobal import messenger from panda3d.core import ConfigVariableBool import gc +from collections.abc import Callable GarbageCycleCountAnnounceEvent = 'announceGarbageCycleDesc2num' @@ -41,9 +44,21 @@ class GarbageReport(Job): If you just want to dump the report to the log, use GarbageLogger.""" notify = directNotify.newCategory("GarbageReport") - def __init__(self, name, log=True, verbose=False, fullReport=False, findCycles=True, - threaded=False, doneCallback=None, autoDestroy=False, priority=None, - safeMode=False, delOnly=False, collect=True): + def __init__( + self, + name: str, + log: bool = True, + verbose: bool = False, + fullReport: bool = False, + findCycles: bool = True, + threaded: bool = False, + doneCallback: Callable[[GarbageReport], object] | None = None, + autoDestroy: bool = False, + priority: int | None = None, + safeMode: bool = False, + delOnly: bool = False, + collect: bool = True + ) -> None: # if autoDestroy is True, GarbageReport will self-destroy after logging # if false, caller is responsible for calling destroy() # if threaded is True, processing will be performed over multiple frames @@ -399,7 +414,7 @@ class GarbageReport(Job): if self._args.autoDestroy: self.destroy() - def destroy(self): + def destroy(self) -> None: #print 'GarbageReport.destroy' del self._args del self.garbage @@ -417,13 +432,13 @@ class GarbageReport(Job): del self._reportStr Job.destroy(self) - def getNumCycles(self): + def getNumCycles(self) -> int: # if the job hasn't run yet, we don't have a numCycles yet return self.numCycles - def getDesc2numDict(self): + def getDesc2numDict(self) -> dict[str, int]: # dict of python-syntax leak -> number of that type of leak - desc2num = {} + desc2num: dict[str, int] = {} for cycleBySyntax in self.cyclesBySyntax: desc2num.setdefault(cycleBySyntax, 0) desc2num[cycleBySyntax] += 1 @@ -563,7 +578,7 @@ class _CFGLGlobals: LastNumCycles = 0 -def checkForGarbageLeaks(): +def checkForGarbageLeaks() -> int: gc.collect() numGarbage = len(gc.garbage) if numGarbage > 0 and ConfigVariableBool('auto-garbage-logging', False): @@ -576,6 +591,7 @@ def checkForGarbageLeaks(): messenger.send(GarbageCycleCountAnnounceEvent, [gr.getDesc2numDict()]) gr.destroy() notify = directNotify.newCategory("GarbageDetect") + func: Callable[[str], object] if ConfigVariableBool('allow-garbage-cycles', True): func = notify.warning else: @@ -584,7 +600,8 @@ def checkForGarbageLeaks(): return numGarbage -def b_checkForGarbageLeaks(wantReply=False): +def b_checkForGarbageLeaks(wantReply: bool = False) -> int: + from direct.showbase.ShowBaseGlobal import base, __dev__ if not __dev__: return 0 # does a garbage collect on the client and the AI @@ -592,10 +609,10 @@ def b_checkForGarbageLeaks(wantReply=False): # logs leak info and terminates (if configured to do so) try: # if this is the client, tell the AI to check for leaks too - base.cr.timeManager + base.cr.timeManager # type: ignore[attr-defined] except Exception: pass else: - if base.cr.timeManager: - base.cr.timeManager.d_checkForGarbageLeaks(wantReply=wantReply) + if base.cr.timeManager: # type: ignore[attr-defined] + base.cr.timeManager.d_checkForGarbageLeaks(wantReply=wantReply) # type: ignore[attr-defined] return checkForGarbageLeaks() diff --git a/direct/src/showbase/JobManager.py b/direct/src/showbase/JobManager.py index e6f034f08c..c491be941d 100755 --- a/direct/src/showbase/JobManager.py +++ b/direct/src/showbase/JobManager.py @@ -1,3 +1,7 @@ +from __future__ import annotations + +from collections.abc import Iterator + from panda3d.core import ConfigVariableBool, ConfigVariableDouble, ClockObject from direct.directnotify.DirectNotifyGlobal import directNotify from direct.task.TaskManagerGlobal import taskMgr @@ -17,28 +21,28 @@ class JobManager: # there's one task for the JobManager, all jobs run in this task TaskName = 'jobManager' - def __init__(self, timeslice=None): + def __init__(self, timeslice: float | None = None) -> None: # how long do we run per frame self._timeslice = timeslice # store the jobs in these structures to allow fast lookup by various keys # priority -> jobId -> job - self._pri2jobId2job = {} + self._pri2jobId2job: dict[int, dict[int, Job]] = {} # priority -> chronological list of jobIds - self._pri2jobIds = {} + self._pri2jobIds: dict[int, list[int]] = {} # jobId -> priority - self._jobId2pri = {} + self._jobId2pri: dict[int, int] = {} # how many timeslices to give each job; this is used to efficiently implement # the relative job priorities - self._jobId2timeslices = {} + self._jobId2timeslices: dict[int, int] = {} # how much time did the job use beyond the allotted timeslice, used to balance # out CPU usage - self._jobId2overflowTime = {} - self._useOverflowTime = None + self._jobId2overflowTime: dict[int, float] = {} + self._useOverflowTime: bool | None = None # this is a generator that we use to give high-priority jobs more timeslices, # it yields jobIds in a sequence that includes high-priority jobIds more often # than low-priority - self._jobIdGenerator = None - self._highestPriority = Job.Priorities.Normal + self._jobIdGenerator: Iterator[int] | None = None + self._highestPriority: int = Job.Priorities.Normal # type: ignore[attr-defined] def destroy(self): taskMgr.remove(JobManager.TaskName) diff --git a/direct/src/showbase/Loader.py b/direct/src/showbase/Loader.py index 141c71a368..9381c8f4e7 100644 --- a/direct/src/showbase/Loader.py +++ b/direct/src/showbase/Loader.py @@ -2,9 +2,13 @@ sound, music, shaders and fonts from disk. """ +from __future__ import annotations + __all__ = ['Loader'] from panda3d.core import ( + AsyncTask, + AudioManager, ConfigVariableBool, Filename, FontPool, @@ -13,6 +17,7 @@ from panda3d.core import ( ModelFlattenRequest, ModelNode, ModelPool, + MovieAudio, NodePath, PandaNode, SamplerState, @@ -24,13 +29,20 @@ from panda3d.core import ( from panda3d.core import Loader as PandaLoader from direct.directnotify.DirectNotifyGlobal import directNotify from direct.showbase.DirectObject import DirectObject +from . import ShowBase import warnings import sys +import os +from typing import Any, Callable, Iterable, Union + +# Type aliases for loadModel, loadSound, etc. annotations +_ModelPath = Union[str, Filename, os.PathLike] +_SoundPath = Union[str, Filename, os.PathLike, MovieAudio] # You can specify a phaseChecker callback to check # a modelPath to see if it is being loaded in the correct # phase -phaseChecker = None +phaseChecker: Callable[[_ModelPath, LoaderOptions], object] | None = None class Loader(DirectObject): @@ -49,16 +61,23 @@ class Loader(DirectObject): # This indicates that this class behaves like a Future. _asyncio_future_blocking = False - def __init__(self, loader, numObjects, gotList, callback, extraArgs): + def __init__( + self, + loader: Loader | None, + numObjects: int, + gotList: bool, + callback: Callable[..., object] | None, + extraArgs: list, + ) -> None: self._loader = loader - self.objects = [None] * numObjects + self.objects: list[Any | None] = [None] * numObjects self.gotList = gotList self.callback = callback self.extraArgs = extraArgs - self.requests = set() - self.requestList = [] + self.requests: set[AsyncTask] = set() + self.requestList: list[AsyncTask] = [] - def gotObject(self, index, object): + def gotObject(self, index: int, object) -> None: self.objects[index] = object if not self.requests: @@ -79,7 +98,7 @@ class Loader(DirectObject): self.requests = None self.requestList = None - def cancelled(self): + def cancelled(self) -> bool: "Returns true if the request was cancelled." return self.requestList is None @@ -87,7 +106,7 @@ class Loader(DirectObject): "Returns true if all the requests were finished or cancelled." return not self.requests - def result(self): + def result(self) -> Any: "Returns the results, suspending the thread to wait if necessary." for r in list(self.requests): r.wait() @@ -126,26 +145,26 @@ class Loader(DirectObject): yield await req # special methods - def __init__(self, base): + def __init__(self, base: ShowBase.ShowBase | None = None) -> None: self.base = base self.loader = PandaLoader.getGlobalPtr() - self._requests = {} + self._requests: dict[AsyncTask, tuple[Loader._Callback, int]] = {} self.hook = "async_loader_%s" % (Loader.loaderIndex) Loader.loaderIndex += 1 - self.accept(self.hook, self.__gotAsyncObject) - self._loadPythonFileTypes() - - def destroy(self): + def destroy(self) -> None: self.ignore(self.hook) self.loader.stopThreads() del self.base - del self.loader + + def _init_base(self, base: ShowBase.ShowBase) -> None: + self.base = base + self.accept(self.hook, self.__gotAsyncObject) @classmethod - def _loadPythonFileTypes(cls): + def _loadPythonFileTypes(cls) -> None: if cls._loadedPythonFileTypes: return @@ -168,10 +187,18 @@ class Loader(DirectObject): cls._loadedPythonFileTypes = True # model loading funcs - def loadModel(self, modelPath, loaderOptions = None, noCache = None, - allowInstance = False, okMissing = None, - callback = None, extraArgs = [], priority = None, - blocking = None): + def loadModel( + self, + modelPath: _ModelPath | list[_ModelPath] | tuple[_ModelPath, ...] | set[_ModelPath], + loaderOptions: LoaderOptions | None = None, + noCache: bool | None = None, + allowInstance: bool = False, + okMissing: bool | None = None, + callback: Callable[..., object] | None = None, + extraArgs: list = [], + priority: int | None = None, + blocking: bool | None = None, + ) -> Any: """ Attempts to load a model or models from one or more relative pathnames. If the input modelPath is a string (a single model @@ -229,6 +256,10 @@ class Loader(DirectObject): """ assert Loader.notify.debug("Loading model: %s" % (modelPath,)) + + if not self._loadedPythonFileTypes: + self._loadPythonFileTypes() + if loaderOptions is None: loaderOptions = LoaderOptions() else: @@ -251,6 +282,7 @@ class Loader(DirectObject): if allowInstance: loaderOptions.setFlags(loaderOptions.getFlags() | LoaderOptions.LFAllowInstance) + modelList: Iterable[_ModelPath] if not isinstance(modelPath, (tuple, list, set)): # We were given a single model pathname. modelList = [modelPath] @@ -416,6 +448,9 @@ class Loader(DirectObject): a callback is used, the model is saved asynchronously, and the true/false status is passed to the callback function. """ + if not self._loadedPythonFileTypes: + self._loadPythonFileTypes() + if loaderOptions is None: loaderOptions = LoaderOptions() else: @@ -942,7 +977,13 @@ class Loader(DirectObject): TexturePool.releaseTexture(texture) # sound loading funcs - def loadSfx(self, *args, **kw): + def loadSfx( + self, + soundPath: _SoundPath | tuple[_SoundPath, ...] | list[_SoundPath] | set[_SoundPath], + positional: bool = False, + callback: Callable[..., object] | None = None, + extraArgs: list = [], + ) -> Any: """Loads one or more sound files, specifically designated as a "sound effect" file (that is, uses the sfxManager to load the sound). There is no distinction between sound effect files @@ -952,11 +993,18 @@ class Loader(DirectObject): independently of the other group.""" # showbase-created sfxManager should always be at front of list + assert self.base is not None if self.base.sfxManagerList: - return self.loadSound(self.base.sfxManagerList[0], *args, **kw) + return self.loadSound(self.base.sfxManagerList[0], soundPath, positional, callback, extraArgs) return None - def loadMusic(self, *args, **kw): + def loadMusic( + self, + soundPath: _SoundPath | tuple[_SoundPath, ...] | list[_SoundPath] | set[_SoundPath], + positional: bool = False, + callback: Callable[..., object] | None = None, + extraArgs: list = [], + ) -> Any: """Loads one or more sound files, specifically designated as a "music" file (that is, uses the musicManager to load the sound). There is no distinction between sound effect files @@ -964,13 +1012,20 @@ class Loader(DirectObject): to load the sound file, but this distinction allows the sound effects and/or the music files to be adjusted as a group, independently of the other group.""" + assert self.base is not None if self.base.musicManager: - return self.loadSound(self.base.musicManager, *args, **kw) + return self.loadSound(self.base.musicManager, soundPath, positional, callback, extraArgs) else: return None - def loadSound(self, manager, soundPath, positional = False, - callback = None, extraArgs = []): + def loadSound( + self, + manager: AudioManager, + soundPath: _SoundPath | tuple[_SoundPath, ...] | list[_SoundPath] | set[_SoundPath], + positional: bool = False, + callback: Callable[..., object] | None = None, + extraArgs: list = [], + ) -> Any: """Loads one or more sound files, specifying the particular AudioManager that should be used to load them. The soundPath may be either a single filename, or a list of filenames. If a @@ -980,6 +1035,7 @@ class Loader(DirectObject): from panda3d.core import AudioLoadRequest + soundList: Iterable[_SoundPath] if not isinstance(soundPath, (tuple, list, set)): # We were given a single sound pathname or a MovieAudio instance. soundList = [soundPath] @@ -1105,7 +1161,7 @@ class Loader(DirectObject): else: callback(*(origModelList + extraArgs)) - def __gotAsyncObject(self, request): + def __gotAsyncObject(self, request: AsyncTask) -> None: """A model or sound file or some such thing has just been loaded asynchronously by the sub-thread. Add it to the list of loaded objects, and call the appropriate callback when it's diff --git a/direct/src/showbase/Messenger.py b/direct/src/showbase/Messenger.py index e6ef2153c2..c6957819ea 100644 --- a/direct/src/showbase/Messenger.py +++ b/direct/src/showbase/Messenger.py @@ -2,19 +2,40 @@ :ref:`event handling ` that happens on the Python side. """ +from __future__ import annotations + __all__ = ['Messenger'] +import types +from collections.abc import Callable +from typing import Protocol +# These can be replaced with their builtin counterparts once support for Python 3.8 is dropped. +from typing import Dict, Tuple + +from panda3d.core import AsyncTask + from direct.stdpy.threading import Lock from direct.directnotify import DirectNotifyGlobal from .PythonUtil import safeRepr -import types + +# The following variables are typing constructs used in annotations +# to succinctly express complex type structures. +_ObjMsgrId = Tuple[str, int] +_CallbackInfo = list # [Callable, list, bool] +_ListenerObject = list # [int, DirectObject] +_AcceptorDict = Dict[_ObjMsgrId, _CallbackInfo] +_EventTuple = Tuple[_AcceptorDict, str, list, bool] + + +class _HasMessengerID(Protocol): + _MSGRmessengerId: _ObjMsgrId class Messenger: notify = DirectNotifyGlobal.directNotify.newCategory("Messenger") - def __init__(self): + def __init__(self) -> None: """ One is keyed off the event name. It has the following structure:: @@ -34,16 +55,16 @@ class Messenger: {'mouseDown': {avatar: [avatar.jump, [2.0], 1]}} """ # eventName->objMsgrId->callbackInfo - self.__callbacks = {} + self.__callbacks: dict[str, _AcceptorDict] = {} # objMsgrId->set(eventName) - self.__objectEvents = {} + self.__objectEvents: dict[_ObjMsgrId, dict[str, None]] = {} self._messengerIdGen = 0 # objMsgrId->listenerObject - self._id2object = {} + self._id2object: dict[_ObjMsgrId, _ListenerObject] = {} # A mapping of taskChain -> eventList, used for sending events # across task chains (and therefore across threads). - self._eventQueuesByTaskChain = {} + self._eventQueuesByTaskChain: dict[str, list[_EventTuple]] = {} # This protects the data structures within this object from # multithreaded access. @@ -51,7 +72,7 @@ class Messenger: if __debug__: self.__isWatching=0 - self.__watching={} + self.__watching: dict[str, bool] = {} # I'd like this to be in the __debug__, but I fear that someone will # want this in a release build. If you're sure that that will not be # then please remove this comment and put the quiet/verbose stuff @@ -62,7 +83,7 @@ class Messenger: 'collisionLoopFinished':1, } # see def quiet() - def _getMessengerId(self, object): + def _getMessengerId(self, object: _HasMessengerID) -> _ObjMsgrId: # TODO: allocate this id in DirectObject.__init__ and get derived # classes to call down (speed optimization, assuming objects # accept/ignore more than once over their lifetime) @@ -73,7 +94,7 @@ class Messenger: self._messengerIdGen += 1 return object._MSGRmessengerId - def _storeObject(self, object): + def _storeObject(self, object: _HasMessengerID) -> None: # store reference-counted reference to object in case we need to # retrieve it later. assumes lock is held. id = self._getMessengerId(object) @@ -82,7 +103,7 @@ class Messenger: else: self._id2object[id][0] += 1 - def _getObject(self, id): + def _getObject(self, id: _ObjMsgrId) -> _HasMessengerID: return self._id2object[id][1] def _getObjects(self): @@ -101,7 +122,7 @@ class Messenger: def _getEvents(self): return list(self.__callbacks.keys()) - def _releaseObject(self, object): + def _releaseObject(self, object: _HasMessengerID) -> None: # assumes lock is held. id = self._getMessengerId(object) if id in self._id2object: @@ -117,7 +138,14 @@ class Messenger: from .EventManagerGlobal import eventMgr return eventMgr.eventHandler.get_future(event) - def accept(self, event, object, method, extraArgs=[], persistent=1): + def accept( + self, + event: str, + object: _HasMessengerID, + method: Callable, + extraArgs: list = [], + persistent: bool = True, + ) -> None: """ accept(self, string, DirectObject, Function, List, Boolean) Make this object accept this event. When the event is @@ -174,7 +202,7 @@ class Messenger: finally: self.lock.release() - def ignore(self, event, object): + def ignore(self, event: str, object: _HasMessengerID) -> None: """ ignore(self, string, DirectObject) Make this object no longer respond to this event. It is safe to call even if it was not already accepting @@ -208,7 +236,7 @@ class Messenger: finally: self.lock.release() - def ignoreAll(self, object): + def ignoreAll(self, object: _HasMessengerID) -> None: """ Make this object no longer respond to any events it was accepting Useful for cleanup @@ -283,7 +311,7 @@ class Messenger: """ return not self.isAccepting(event, object) - def send(self, event, sentArgs=[], taskChain=None): + def send(self, event: str, sentArgs: list = [], taskChain: str | None = None) -> None: """ Send this event, optionally passing in arguments. @@ -305,12 +333,12 @@ class Messenger: self.lock.acquire() try: - foundWatch = 0 + foundWatch = False if __debug__: if self.__isWatching: for i in self.__watching: if str(event).find(i) >= 0: - foundWatch = 1 + foundWatch = True break acceptorDict = self.__callbacks.get(event) if not acceptorDict: @@ -336,7 +364,7 @@ class Messenger: finally: self.lock.release() - def __taskChainDispatch(self, taskChain, task): + def __taskChainDispatch(self, taskChain: str, task: AsyncTask) -> int: """ This task is spawned each time an event is sent across task chains. Its job is to empty the task events on the queue for this particular task chain. This guarantees that events @@ -365,7 +393,13 @@ class Messenger: return task.done - def __dispatch(self, acceptorDict, event, sentArgs, foundWatch): + def __dispatch( + self, + acceptorDict: _AcceptorDict, + event: str, + sentArgs: list, + foundWatch: bool, + ) -> None: for id in list(acceptorDict.keys()): # We have to make this apparently redundant check, because # it is possible that one object removes its own hooks @@ -562,7 +596,7 @@ class Messenger: break return matches - def __methodRepr(self, method): + def __methodRepr(self, method: object) -> str: """ return string version of class.method or method. """ diff --git a/direct/src/showbase/OnScreenDebug.py b/direct/src/showbase/OnScreenDebug.py index 0d690e3806..16292fa628 100755 --- a/direct/src/showbase/OnScreenDebug.py +++ b/direct/src/showbase/OnScreenDebug.py @@ -1,7 +1,11 @@ """Contains the OnScreenDebug class.""" +from __future__ import annotations + __all__ = ['OnScreenDebug'] +from typing import Any + from panda3d.core import ( ConfigVariableBool, ConfigVariableDouble, @@ -18,13 +22,13 @@ class OnScreenDebug: enabled = ConfigVariableBool("on-screen-debug-enabled", False) - def __init__(self): - self.onScreenText = None + def __init__(self) -> None: + self.onScreenText: OnscreenText.OnscreenText | None = None self.frame = 0 self.text = "" - self.data = {} + self.data: dict[str, tuple[int, Any]] = {} - def load(self): + def load(self) -> None: if self.onScreenText: return @@ -40,6 +44,7 @@ class OnScreenDebug: fgColor.setW(ConfigVariableDouble("on-screen-debug-fg-alpha", 0.85).value) bgColor.setW(ConfigVariableDouble("on-screen-debug-bg-alpha", 0.85).value) + from direct.showbase.ShowBaseGlobal import base font = base.loader.loadFont(fontPath) if not font.isValid(): print("failed to load OnScreenDebug font %s" % fontPath) @@ -47,15 +52,16 @@ class OnScreenDebug: self.onScreenText = OnscreenText.OnscreenText( parent = base.a2dTopLeft, pos = (0.0, -0.1), fg=fgColor, bg=bgColor, scale = (fontScale, fontScale, 0.0), - align = TextNode.ALeft, mayChange = 1, font = font) + align = TextNode.ALeft, mayChange = True, font = font) # Make sure readout is never lit or drawn in wireframe DirectUtil.useDirectRenderStyle(self.onScreenText) - def render(self): + def render(self) -> None: if not self.enabled: return if not self.onScreenText: self.load() + assert self.onScreenText is not None self.onScreenText.clearText() for k, v in sorted(self.data.items()): if v[0] == self.frame: @@ -75,7 +81,7 @@ class OnScreenDebug: self.onScreenText.appendText(self.text) self.frame += 1 - def clear(self): + def clear(self) -> None: self.text = "" if self.onScreenText: self.onScreenText.clearText() diff --git a/direct/src/showbase/PythonUtil.py b/direct/src/showbase/PythonUtil.py index d9ffa2aa35..2d37df1284 100644 --- a/direct/src/showbase/PythonUtil.py +++ b/direct/src/showbase/PythonUtil.py @@ -41,13 +41,17 @@ import time import builtins import importlib import functools -from typing import Callable +from collections.abc import Callable, Container, Iterable, Mapping +from typing import Any, Generic, TypeVar __report_indent = 3 from panda3d.core import ConfigVariableBool, ConfigVariableString, ConfigFlags from panda3d.core import ClockObject +_T = TypeVar('_T') +_KT = TypeVar('_KT') +_VT = TypeVar('_VT') ## with one integer positional arg, this uses about 4/5 of the memory of the Functor class below #def Functor(function, *args, **kArgs): @@ -100,9 +104,9 @@ class Functor: return s -class Stack: - def __init__(self): - self.__list = [] +class Stack(Generic[_T]): + def __init__(self) -> None: + self.__list: list[_T] = [] def push(self, item): self.__list.append(item) @@ -229,7 +233,7 @@ if __debug__: #----------------------------------------------------------------------------- - def traceFunctionCall(frame): + def traceFunctionCall(frame: types.FrameType) -> str: """ return a string that shows the call frame with calling arguments. e.g. @@ -270,7 +274,7 @@ if __debug__: r+="*** undefined ***" return r+')' - def traceParentCall(): + def traceParentCall() -> str: return traceFunctionCall(sys._getframe(2)) def printThisCall(): @@ -411,7 +415,7 @@ def list2dict(L, value=None): return dict([(k, value) for k in L]) -def listToIndex2item(L): +def listToIndex2item(L: Iterable[_VT]) -> dict[int, _VT]: """converts list to dict of list index->list item""" d = {} for i, item in enumerate(L): @@ -422,7 +426,7 @@ def listToIndex2item(L): assert listToIndex2item(['a','b']) == {0: 'a', 1: 'b',} -def listToItem2index(L): +def listToItem2index(L: Iterable[_KT]) -> dict[_KT, int]: """converts list to dict of list item->list index This is lossy if there are duplicate list items""" d = {} @@ -451,7 +455,7 @@ def invertDict(D, lossy=False): return n -def invertDictLossless(D): +def invertDictLossless(D: Mapping[_KT, _VT]) -> dict[_VT, list[_KT]]: """similar to invertDict, but values of new dict are lists of keys from old dict. No information is lost. @@ -459,7 +463,7 @@ def invertDictLossless(D): >>> invertDictLossless(old) {1: ['key1'], 2: ['key2', 'keyA']} """ - n = {} + n: dict[_VT, list[_KT]] = {} for key, value in D.items(): n.setdefault(value, []) n[value].append(key) @@ -706,7 +710,7 @@ if __debug__: movedDumpFuncs: list[Callable] = [] movedLoadFuncs: list[Callable] = [] profileFilenames = set() - profileFilenameList = Stack() + profileFilenameList = Stack[str]() profileFilename2file = {} profileFilename2marshalData = {} @@ -998,7 +1002,7 @@ def lineupPos(i, num, spacing): return pos - ((float(spacing) * (num-1))/2.) -def formatElapsedSeconds(seconds): +def formatElapsedSeconds(seconds: float) -> str: """ Returns a string of the form "mm:ss" or "hh:mm:ss" or "n days", representing the indicated elapsed time in seconds. @@ -1278,7 +1282,7 @@ def randInt32(rng=random.random): class SerialNumGen: """generates serial numbers""" - def __init__(self, start=None): + def __init__(self, start: int | None = None) -> None: if start is None: start = 0 self.__counter = start-1 @@ -1305,7 +1309,7 @@ class SerialMaskedGen(SerialNumGen): _serialGen = SerialNumGen() -def serialNum(): +def serialNum() -> int: return _serialGen.next() @@ -1404,14 +1408,16 @@ def _getSafeReprNotify(): return safeReprNotify -def safeRepr(obj): +def safeRepr(obj: object) -> str: global dtoolSuperBase if dtoolSuperBase is None: _getDtoolSuperBase() + assert dtoolSuperBase is not None global safeReprNotify if safeReprNotify is None: _getSafeReprNotify() + assert safeReprNotify is not None if isinstance(obj, dtoolSuperBase): # repr of C++ object could crash, particularly if the object has been deleted @@ -1443,7 +1449,12 @@ def safeReprTypeOnFail(obj): return '<** FAILED REPR OF %s instance at %s **>' % (obj.__class__.__name__, hex(id(obj))) -def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None): +def fastRepr( + obj: object, + maxLen: int = 200, + strFactor: int = 10, + _visitedIds: set[int] | None = None, +) -> str: """ caps the length of iterable types, so very large objects will print faster. also prevents infinite recursion """ try: @@ -1452,9 +1463,9 @@ def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None): if id(obj) in _visitedIds: return '' % itype(obj) if type(obj) in (tuple, list): + assert isinstance(obj, (tuple, list)) s = '' - s += {tuple: '(', - list: '[',}[type(obj)] + s += '(' if type(obj) == tuple else '[' if maxLen is not None and len(obj) > maxLen: o = obj[:maxLen] ellips = '...' @@ -1467,8 +1478,7 @@ def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None): s += ', ' _visitedIds.remove(id(obj)) s += ellips - s += {tuple: ')', - list: ']',}[type(obj)] + s += ')' if type(obj) == tuple else ']' return s elif type(obj) is dict: s = '{' @@ -1588,7 +1598,7 @@ class ScratchPad: class Sync: _SeriesGen = SerialNumGen() - def __init__(self, name, other=None): + def __init__(self, name: str, other: Sync | None = None) -> None: self._name = name if other is None: self._series = self._SeriesGen.next() @@ -1620,7 +1630,7 @@ class Sync: self._name, self._series, self._value) -def itype(obj): +def itype(obj: object) -> type | str: # version of type that gives more complete information about instance types global dtoolSuperBase t = type(obj) @@ -1628,6 +1638,7 @@ def itype(obj): # check if this is a C++ object if dtoolSuperBase is None: _getDtoolSuperBase() + assert dtoolSuperBase is not None if isinstance(obj, dtoolSuperBase): return "" % (obj.__class__) return t @@ -1972,7 +1983,13 @@ def pstatcollect(scope, level = None): __report_indent = 0 -def report(types = [], prefix = '', xform = None, notifyFunc = None, dConfigParam = []): +def report( + types: Container[str] = [], + prefix: str = '', + xform: Callable[[Any], object] | None = None, + notifyFunc: Callable[[str], object] | None = None, + dConfigParam: str | list[str] | tuple[str, ...] = [], +) -> Callable[[_T], _T]: """ This is a decorator generating function. Use is similar to a @decorator, except you must be sure to call it as a function. @@ -2031,7 +2048,7 @@ def report(types = [], prefix = '', xform = None, notifyFunc = None, dConfigPara return f try: - if not __dev__ and not ConfigVariableBool('force-reports', False): + if not __dev__ and not ConfigVariableBool('force-reports', False): # type: ignore[name-defined] return decorator # determine whether we should use the decorator @@ -2041,6 +2058,7 @@ def report(types = [], prefix = '', xform = None, notifyFunc = None, dConfigPara if not dConfigParam: doPrint = True else: + dConfigParams: list[str] | tuple[str, ...] if not isinstance(dConfigParam, (list,tuple)): dConfigParams = (dConfigParam,) else: @@ -2070,7 +2088,7 @@ def report(types = [], prefix = '', xform = None, notifyFunc = None, dConfigPara globalClockDelta = importlib.import_module("direct.distributed.ClockDelta").globalClockDelta - def decorator(f): + def decorator(f): # type: ignore[no-redef] def wrap(*args, **kwargs): if args: rArgs = [args[0].__class__.__name__ + ', '] diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 0ffbc6fba8..78ecaaf763 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -62,6 +62,7 @@ from panda3d.core import ( DepthTestAttrib, DepthWriteAttrib, DriveInterface, + EventQueue, ExecutionEnvironment, Filename, FisheyeMaker, @@ -114,7 +115,7 @@ from panda3d.core import ( WindowProperties, getModelPath, ) -from panda3d.direct import throw_new_frame, init_app_for_gui +from panda3d.direct import init_app_for_gui from panda3d.direct import storeAccessibilityShortcutKeys, allowAccessibilityShortcutKeys from . import DConfig @@ -177,6 +178,10 @@ class ShowBase(DirectObject.DirectObject): aspect2d: NodePath pixel2d: NodePath + a2dTopLeft: NodePath + + cluster: Any | None + def __init__(self, fStartDirect: bool = True, windowType: str | None = None) -> None: """Opens a window, sets up a 3-D and several 2-D scene graphs, and everything else needed to render the scene graph to the window. @@ -380,7 +385,7 @@ class ShowBase(DirectObject.DirectObject): #: yourself every frame. self.cTrav: CollisionTraverser | Literal[0] = 0 self.shadowTrav: CollisionTraverser | Literal[0] = 0 - self.cTravStack = Stack() + self.cTravStack = Stack[CollisionTraverser]() # Ditto for an AppTraverser. self.appTrav: Any | Literal[0] = 0 @@ -436,9 +441,9 @@ class ShowBase(DirectObject.DirectObject): self.useTrackball() #: `.Loader.Loader` object. - self.loader = Loader.Loader(self) + self.loader: Loader.Loader = ShowBaseGlobal.loader + self.loader._init_base(self) self.graphicsEngine.setDefaultLoader(self.loader.loader) - ShowBaseGlobal.loader = self.loader #: The global event manager, as imported from `.EventManagerGlobal`. self.eventMgr = eventMgr @@ -652,7 +657,7 @@ class ShowBase(DirectObject.DirectObject): def getExitErrorCode(self): return 0 - def printEnvDebugInfo(self): + def printEnvDebugInfo(self) -> None: """Print some information about the environment that we are running in. Stuff like the model paths and other paths. Feel free to add stuff to this. @@ -706,7 +711,8 @@ class ShowBase(DirectObject.DirectObject): allowAccessibilityShortcutKeys(True) self.__disabledStickyKeys = False - self.__directObject.ignoreAll() + if hasattr(self, '__directObject'): + self.__directObject.ignoreAll() self.ignoreAll() self.shutdown() @@ -1208,7 +1214,7 @@ class ShowBase(DirectObject.DirectObject): self.taskMgr.remove('clientSleep') self.taskMgr.add(self.__sleepCycleTask, 'clientSleep', sort = 55) - def __sleepCycleTask(self, task): + def __sleepCycleTask(self, task: object) -> int: Thread.sleep(self.clientSleep) #time.sleep(self.clientSleep) return Task.cont @@ -1444,7 +1450,7 @@ class ShowBase(DirectObject.DirectObject): self.__configAspectRatio = aspectRatio self.adjustWindowAspectRatio(self.getAspectRatio()) - def getAspectRatio(self, win = None): + def getAspectRatio(self, win: GraphicsOutput | None = None) -> float: # Returns the actual aspect ratio of the indicated (or main # window), or the default aspect ratio if there is not yet a # main window. @@ -1453,7 +1459,7 @@ class ShowBase(DirectObject.DirectObject): if self.__configAspectRatio: return self.__configAspectRatio - aspectRatio = 1 + aspectRatio: float = 1 if win is None: win = self.win @@ -1476,7 +1482,7 @@ class ShowBase(DirectObject.DirectObject): return aspectRatio - def getSize(self, win = None): + def getSize(self, win: GraphicsOutput | None = None) -> tuple[int, int]: """ Returns the actual size of the indicated (or main window), or the default size if there is not yet a main window. @@ -2176,7 +2182,7 @@ class ShowBase(DirectObject.DirectObject): music.setLoop(looping) music.play() - def __resetPrevTransform(self, state): + def __resetPrevTransform(self, state: object) -> int: # Clear out the previous velocity deltas now, after we have # rendered (the previous frame). We do this after the render, # so that we have a chance to draw a representation of spheres @@ -2187,7 +2193,7 @@ class ShowBase(DirectObject.DirectObject): PandaNode.resetAllPrevTransform() return Task.cont - def __dataLoop(self, state): + def __dataLoop(self, state: object) -> int: # Check if there were newly connected devices. self.devices.update() @@ -2197,7 +2203,7 @@ class ShowBase(DirectObject.DirectObject): self.dgTrav.traverse(self.dataRootNode) return Task.cont - def __ivalLoop(self, state): + def __ivalLoop(self, state: object) -> int: # Execute all intervals in the global ivalMgr. IntervalManager.ivalMgr.step() return Task.cont @@ -2215,7 +2221,7 @@ class ShowBase(DirectObject.DirectObject): self.shadowTrav.traverse(self.render) return Task.cont - def __collisionLoop(self, state): + def __collisionLoop(self, state: object) -> int: # run the collision traversal if we have a # CollisionTraverser set. if self.cTrav: @@ -2227,14 +2233,14 @@ class ShowBase(DirectObject.DirectObject): messenger.send("collisionLoopFinished") return Task.cont - def __audioLoop(self, state): + def __audioLoop(self, state: object) -> int: if self.musicManager is not None: self.musicManager.update() for x in self.sfxManagerList: x.update() return Task.cont - def __garbageCollectStates(self, state): + def __garbageCollectStates(self, state: object) -> int: """ This task is started only when we have garbage-collect-states set in the Config.prc file, in which case we're responsible for taking out Panda's garbage from @@ -2245,7 +2251,7 @@ class ShowBase(DirectObject.DirectObject): RenderState.garbageCollect() return Task.cont - def __igLoop(self, state): + def __igLoop(self, state: object) -> int: if __debug__: # We render the watch variables for the onScreenDebug as soon # as we reasonably can before the renderFrame(). @@ -2280,12 +2286,11 @@ class ShowBase(DirectObject.DirectObject): # now until someone complains. time.sleep(0.1) - # Lerp stuff needs this event, and it must be generated in - # C++, not in Python. - throw_new_frame() + # Lerp stuff needs this event, thrown directly on the C++ queue. + EventQueue.getGlobalEventQueue().queueEvent("NewFrame") return Task.cont - def __igLoopSync(self, state): + def __igLoopSync(self, state: object) -> int: if __debug__: # We render the watch variables for the onScreenDebug as soon # as we reasonably can before the renderFrame(). @@ -2294,6 +2299,7 @@ class ShowBase(DirectObject.DirectObject): if self.recorder: self.recorder.recordFrame() + assert self.cluster is not None self.cluster.collectData() # Finally, render the frame. @@ -2323,12 +2329,12 @@ class ShowBase(DirectObject.DirectObject): time.sleep(0.1) self.graphicsEngine.readyFlip() + assert self.cluster is not None self.cluster.waitForFlipCommand() self.graphicsEngine.flipFrame() - # Lerp stuff needs this event, and it must be generated in - # C++, not in Python. - throw_new_frame() + # Lerp stuff needs this event, thrown directly on the C++ queue. + EventQueue.getGlobalEventQueue().queueEvent("NewFrame") return Task.cont def restart(self, clusterSync: bool = False, cluster=None) -> None: @@ -2753,7 +2759,7 @@ class ShowBase(DirectObject.DirectObject): self.oobeVis.reparentTo(self.camera) self.oobeMode = 1 - def __oobeButton(self, suffix, button): + def __oobeButton(self, suffix: str, button: str) -> None: if button.startswith('mouse'): # Eat mouse buttons. return @@ -3073,7 +3079,7 @@ class ShowBase(DirectObject.DirectObject): else: return Task.cont - def windowEvent(self, win): + def windowEvent(self, win: GraphicsOutput) -> None: if win != self.win: # This event isn't about our window. return @@ -3092,9 +3098,9 @@ class ShowBase(DirectObject.DirectObject): self.userExit() if properties.getForeground() and not self.mainWinForeground: - self.mainWinForeground = 1 + self.mainWinForeground = True elif not properties.getForeground() and self.mainWinForeground: - self.mainWinForeground = 0 + self.mainWinForeground = False if __debug__: if self.__autoGarbageLogging: GarbageReport.b_checkForGarbageLeaks() @@ -3102,12 +3108,12 @@ class ShowBase(DirectObject.DirectObject): if properties.getMinimized() and not self.mainWinMinimized: # If the main window is minimized, throw an event to # stop the music. - self.mainWinMinimized = 1 + self.mainWinMinimized = True messenger.send('PandaPaused') elif not properties.getMinimized() and self.mainWinMinimized: # If the main window is restored, throw an event to # restart the music. - self.mainWinMinimized = 0 + self.mainWinMinimized = False messenger.send('PandaRestarted') # If we have not forced the aspect ratio, let's see if it has @@ -3125,7 +3131,7 @@ class ShowBase(DirectObject.DirectObject): if self.wantRender2dp: self.pixel2dp.setScale(2.0 / xsize, 1.0, 2.0 / ysize) - def adjustWindowAspectRatio(self, aspectRatio): + def adjustWindowAspectRatio(self, aspectRatio: float) -> None: """ This function is normally called internally by `windowEvent()`, but it may also be called to explicitly adjust the aspect ratio of the render/render2d DisplayRegion, by a @@ -3512,6 +3518,7 @@ class ShowBase(DirectObject.DirectObject): remove_camera_frustum = removeCameraFrustum save_cube_map = saveCubeMap save_sphere_map = saveSphereMap + user_exit = userExit start_wx = startWx start_tk = startTk start_direct = startDirect diff --git a/direct/src/showbase/ShowBaseGlobal.py b/direct/src/showbase/ShowBaseGlobal.py index 4cd4e8623b..fe891e9c1b 100644 --- a/direct/src/showbase/ShowBaseGlobal.py +++ b/direct/src/showbase/ShowBaseGlobal.py @@ -62,7 +62,8 @@ aspect2d = render2d.attachNewNode(PGTop("aspect2d")) #: A dummy scene graph that is not being rendered by anything. hidden = NodePath("hidden") -loader: Loader +#: The global Loader instance for models, textures, etc. +loader = Loader() # Set direct notify categories now that we have config directNotify.setDconfigLevels() diff --git a/direct/src/showbase/VFSImporter.py b/direct/src/showbase/VFSImporter.py index c6e69c5ca7..3bfa2336f1 100644 --- a/direct/src/showbase/VFSImporter.py +++ b/direct/src/showbase/VFSImporter.py @@ -5,477 +5,267 @@ Calling the :func:`register()` function to register the import hooks should be sufficient to enable this functionality. """ -__all__ = ['register', 'sharedPackages', - 'reloadSharedPackage', 'reloadSharedPackages'] +from __future__ import annotations -from panda3d.core import Filename, VirtualFileSystem, VirtualFileMountSystem, OFileStream, copyStream -from direct.stdpy.file import open +__all__ = ['register'] + +from panda3d.core import Filename, VirtualFile, VirtualFileSystem, VirtualFileMountSystem +from panda3d.core import OFileStream, copy_stream import sys import marshal -import imp -import types +import _imp +import atexit +from importlib.abc import Loader, SourceLoader +from importlib.util import MAGIC_NUMBER, decode_source +from importlib.machinery import ModuleSpec, EXTENSION_SUFFIXES, BYTECODE_SUFFIXES +from types import ModuleType +from typing import Any -#: The sharedPackages dictionary lists all of the "shared packages", -#: special Python packages that automatically span multiple directories -#: via magic in the VFSImporter. You can make a package "shared" -#: simply by adding its name into this dictionary (and then calling -#: reloadSharedPackages() if it's already been imported). -#: -#: When a package name is in this dictionary at import time, *all* -#: instances of the package are located along sys.path, and merged into -#: a single Python module with a __path__ setting that represents the -#: union. Thus, you can have a direct.showbase.foo in your own -#: application, and loading it won't shadow the system -#: direct.showbase.ShowBase which is in a different directory on disk. -sharedPackages = {} - -vfs = VirtualFileSystem.getGlobalPtr() - -compiledExtensions = ['pyc', 'pyo'] -if not __debug__: - # In optimized mode, we prefer loading .pyo files over .pyc files. - # We implement that by reversing the extension names. - compiledExtensions = ['pyo', 'pyc'] +vfs = VirtualFileSystem.get_global_ptr() -class VFSImporter: +def _make_spec(fullname: str, loader: VFSLoader, *, is_package: bool) -> ModuleSpec: + filename = loader._vfile.get_filename() + spec = ModuleSpec(fullname, loader, origin=filename.to_os_specific(), is_package=is_package) + if is_package: + assert spec.submodule_search_locations is not None + spec.submodule_search_locations.append(Filename(filename.get_dirname()).to_os_specific()) + spec.has_location = True + return spec + + +class VFSFinder: """ This class serves as a Python importer to support loading Python .py and .pyc/.pyo files from Panda's Virtual File System, which allows loading Python source files from mounted .mf files (among other places). """ - def __init__(self, path): - if isinstance(path, Filename): - self.dir_path = Filename(path) - else: - self.dir_path = Filename.fromOsSpecific(path) + def __init__(self, path: str) -> None: + self.path = path - def find_module(self, fullname, path = None): - if path is None: - dir_path = self.dir_path - else: - dir_path = path - #print >>sys.stderr, "find_module(%s), dir_path = %s" % (fullname, dir_path) + def find_spec(self, fullname: str, target: ModuleType | None = None) -> ModuleSpec | None: + #print(f"find_spec({fullname}), dir_path = {dir_path}", file=sys.stderr) basename = fullname.split('.')[-1] - path = Filename(dir_path, basename) + filename = Filename(Filename.from_os_specific(self.path), basename) + + loader: VFSLoader # First, look for Python files. - filename = Filename(path) - filename.setExtension('py') - vfile = vfs.getFile(filename, True) + vfile = vfs.get_file(filename + '.py', True) if vfile: - return VFSLoader(dir_path, vfile, filename, - desc=('.py', 'r', imp.PY_SOURCE)) + loader = VFSSourceLoader(fullname, vfile) + return _make_spec(fullname, loader, is_package=False) # If there's no .py file, but there's a .pyc file, load that # anyway. - for ext in compiledExtensions: - filename = Filename(path) - filename.setExtension(ext) - vfile = vfs.getFile(filename, True) + for suffix in BYTECODE_SUFFIXES: + vfile = vfs.get_file(filename + suffix, True) if vfile: - return VFSLoader(dir_path, vfile, filename, - desc=('.'+ext, 'rb', imp.PY_COMPILED)) + loader = VFSCompiledLoader(fullname, vfile) + return _make_spec(fullname, loader, is_package=False) # Look for a C/C++ extension module. - for desc in imp.get_suffixes(): - if desc[2] != imp.C_EXTENSION: - continue - - filename = Filename(path + desc[0]) - vfile = vfs.getFile(filename, True) + for suffix in EXTENSION_SUFFIXES: + vfile = vfs.get_file(filename + suffix, True) if vfile: - return VFSLoader(dir_path, vfile, filename, desc=desc) + loader = VFSExtensionLoader(fullname, vfile) + return _make_spec(fullname, loader, is_package=False) - # Finally, consider a package, i.e. a directory containing - # __init__.py. - filename = Filename(path, '__init__.py') - vfile = vfs.getFile(filename, True) + # Consider a package, i.e. a directory containing __init__.py. + init_filename = Filename(filename, '__init__.py') + vfile = vfs.get_file(init_filename, True) if vfile: - return VFSLoader(dir_path, vfile, filename, packagePath=path, - desc=('.py', 'r', imp.PY_SOURCE)) - for ext in compiledExtensions: - filename = Filename(path, '__init__.' + ext) - vfile = vfs.getFile(filename, True) - if vfile: - return VFSLoader(dir_path, vfile, filename, packagePath=path, - desc=('.'+ext, 'rb', imp.PY_COMPILED)) + loader = VFSSourceLoader(fullname, vfile) + return _make_spec(fullname, loader, is_package=True) - #print >>sys.stderr, "not found." + for suffix in BYTECODE_SUFFIXES: + init_filename = Filename(filename, '__init__' + suffix) + vfile = vfs.get_file(init_filename, True) + if vfile: + loader = VFSCompiledLoader(fullname, vfile) + return _make_spec(fullname, loader, is_package=True) + + # Consider a namespace package. + if vfs.is_directory(filename): + spec = ModuleSpec(fullname, VFSNamespaceLoader(), is_package=True) + assert spec.submodule_search_locations is not None + spec.submodule_search_locations.append(filename.to_os_specific()) + return spec + + #print("not found.", file=sys.stderr) return None -class VFSLoader: - """ The second part of VFSImporter, this is created for a - particular .py file or directory. """ - - def __init__(self, dir_path, vfile, filename, desc, packagePath=None): - self.dir_path = dir_path - self.timestamp = None - if vfile: - self.timestamp = vfile.getTimestamp() - self.filename = filename - self.desc = desc - self.packagePath = packagePath - - def load_module(self, fullname, loadingShared = False): - #print >>sys.stderr, "load_module(%s), dir_path = %s, filename = %s" % (fullname, self.dir_path, self.filename) - if self.desc[2] == imp.PY_FROZEN: - return self._import_frozen_module(fullname) - if self.desc[2] == imp.C_EXTENSION: - return self._import_extension_module(fullname) - - # Check if this is a child of a shared package. - if not loadingShared and self.packagePath and '.' in fullname: - parentname = fullname.rsplit('.', 1)[0] - if parentname in sharedPackages: - # It is. That means it's a shared package too. - parent = sys.modules[parentname] - path = getattr(parent, '__path__', None) - importer = VFSSharedImporter() - sharedPackages[fullname] = True - loader = importer.find_module(fullname, path = path) - assert loader - return loader.load_module(fullname) - - code = self._read_code() - if not code: - raise ImportError('No Python code in %s' % (fullname)) - - mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) - mod.__file__ = self.filename.toOsSpecific() - mod.__loader__ = self - if self.packagePath: - mod.__path__ = [self.packagePath.toOsSpecific()] - #print >> sys.stderr, "loaded %s, path = %s" % (fullname, mod.__path__) - - exec(code, mod.__dict__) - return sys.modules[fullname] - - def getdata(self, path): - path = Filename(self.dir_path, Filename.fromOsSpecific(path)) - vfile = vfs.getFile(path) - if not vfile: - raise IOError("Could not find '%s'" % (path)) - return vfile.readFile(True) +class VFSLoader(Loader): + def __init__(self, fullname: str, vfile: VirtualFile) -> None: + self.name = fullname + self._vfile = vfile def is_package(self, fullname): - return bool(self.packagePath) + if fullname is not None and self.name != fullname: + raise ImportError + filename = self._vfile.get_filename().get_basename() + filename_base = filename.rsplit('.', 1)[0] + tail_name = fullname.rpartition('.')[2] + return filename_base == '__init__' and tail_name != '__init__' + + def create_module(self, spec: ModuleSpec) -> ModuleType | None: + """Use default semantics for module creation.""" + + def exec_module(self, module: ModuleType) -> None: + """Execute the module.""" + code = self.get_code(module.__name__) # type: ignore[attr-defined] + exec(code, module.__dict__) + + def get_filename(self, fullname: str) -> str: + if fullname is not None and self.name != fullname: + raise ImportError + + return self._vfile.get_filename().to_os_specific() + + @staticmethod + def get_data(path: str) -> bytes: + vfile = vfs.get_file(Filename.from_os_specific(path)) + if vfile: + return vfile.read_file(True) + else: + raise OSError + + @staticmethod + def path_stats(path: str) -> dict[str, Any]: + vfile = vfs.get_file(Filename.from_os_specific(path)) + if vfile: + return {'mtime': vfile.get_timestamp(), 'size': vfile.get_file_size()} + else: + raise OSError + + @staticmethod + def path_mtime(path): + vfile = vfs.get_file(Filename.from_os_specific(path)) + if vfile: + return vfile.get_timestamp() + else: + raise OSError + + +class VFSSourceLoader(VFSLoader, SourceLoader): # type: ignore[misc] + def get_source(self, fullname): + if fullname is not None and self.name != fullname: + raise ImportError + + return decode_source(self._vfile.read_file(True)) + + +class VFSCompiledLoader(VFSLoader): def get_code(self, fullname): - return self._read_code() + if fullname is not None and self.name != fullname: + raise ImportError + + vfile = self._vfile + data = vfile.read_file(True) + if data[:4] != MAGIC_NUMBER: + raise ImportError("Bad magic number in %s" % (vfile)) + + return marshal.loads(data[16:]) def get_source(self, fullname): - return self._read_source() + return None - def get_filename(self, fullname): - return self.filename.toOsSpecific() - def _read_source(self): - """ Returns the Python source for this file, if it is - available, or None if it is not. May raise IOError. """ - - if self.desc[2] == imp.PY_COMPILED or \ - self.desc[2] == imp.C_EXTENSION: - return None - - filename = Filename(self.filename) - filename.setExtension('py') - filename.setText() - - # Use the tokenize module to detect the encoding. - import tokenize - fh = open(self.filename, 'rb') - encoding, lines = tokenize.detect_encoding(fh.readline) - return (b''.join(lines) + fh.read()).decode(encoding) - - def _import_extension_module(self, fullname): - """ Loads the binary shared object as a Python module, and - returns it. """ - - vfile = vfs.getFile(self.filename, False) +class VFSExtensionLoader(VFSLoader): + def create_module(self, spec): + vfile = self._vfile + filename = vfile.get_filename() # We can only import an extension module if it already exists on # disk. This means if it's a truly virtual file that has no # on-disk equivalent, we have to write it to a temporary file # first. - if hasattr(vfile, 'getMount') and \ - isinstance(vfile.getMount(), VirtualFileMountSystem): + if isinstance(vfile.get_mount(), VirtualFileMountSystem): # It's a real file. - filename = self.filename - elif self.filename.exists(): + pass + elif filename.exists(): # It's a virtual file, but it's shadowing a real file in # the same directory. Assume they're the same, and load # the real one. - filename = self.filename - else: - # It's a virtual file with no real-world existence. Dump - # it to disk. TODO: clean up this filename. - filename = Filename.temporary('', self.filename.getBasenameWoExtension(), - '.' + self.filename.getExtension(), - type = Filename.TDso) - filename.setExtension(self.filename.getExtension()) - filename.setBinary() - sin = vfile.openReadFile(True) - sout = OFileStream() - if not filename.openWrite(sout): - raise IOError - if not copyStream(sin, sout): - raise IOError - vfile.closeReadFile(sin) - del sout - - module = imp.load_module(fullname, None, filename.toOsSpecific(), - self.desc) - module.__file__ = self.filename.toOsSpecific() - return module - - def _import_frozen_module(self, fullname): - """ Imports the frozen module without messing around with - searching any more. """ - #print >>sys.stderr, "importing frozen %s" % (fullname) - module = imp.load_module(fullname, None, fullname, - ('', '', imp.PY_FROZEN)) - module.__path__ = [] - return module - - def _read_code(self): - """ Returns the Python compiled code object for this file, if - it is available, or None if it is not. May raise IOError, - ValueError, SyntaxError, or a number of other errors generated - by the low-level system. """ - - if self.desc[2] == imp.PY_COMPILED: - # It's a pyc file; just read it directly. - pycVfile = vfs.getFile(self.filename, False) - if pycVfile: - return self._loadPyc(pycVfile, None) - raise IOError('Could not read %s' % (self.filename)) - - elif self.desc[2] == imp.C_EXTENSION: - return None - - # It's a .py file (or an __init__.py file; same thing). Read - # the .pyc file if it is available and current; otherwise read - # the .py file and compile it. - t_pyc = None - for ext in compiledExtensions: - pycFilename = Filename(self.filename) - pycFilename.setExtension(ext) - pycVfile = vfs.getFile(pycFilename, False) - if pycVfile: - t_pyc = pycVfile.getTimestamp() - break - - code = None - if t_pyc and t_pyc >= self.timestamp: - try: - code = self._loadPyc(pycVfile, self.timestamp) - except ValueError: - code = None - - if not code: - source = self._read_source() - filename = Filename(self.filename) - filename.setExtension('py') - code = self._compile(filename, source) - - return code - - def _loadPyc(self, vfile, timestamp): - """ Reads and returns the marshal data from a .pyc file. - Raises ValueError if there is a problem. """ - - code = None - data = vfile.readFile(True) - if data[:4] != imp.get_magic(): - raise ValueError("Bad magic number in %s" % (vfile)) - - t = int.from_bytes(data[4:8], 'little') - data = data[12:] - - if not timestamp or t == timestamp: - return marshal.loads(data) - else: - raise ValueError("Timestamp wrong on %s" % (vfile)) - - def _compile(self, filename, source): - """ Compiles the Python source code to a code object and - attempts to write it to an appropriate .pyc file. May raise - SyntaxError or other errors generated by the compiler. """ - - if source and source[-1] != '\n': - source = source + '\n' - code = compile(source, filename.toOsSpecific(), 'exec') - - # try to cache the compiled code - pycFilename = Filename(filename) - pycFilename.setExtension(compiledExtensions[0]) - try: - f = open(pycFilename.toOsSpecific(), 'wb') - except IOError: pass else: - f.write(imp.get_magic()) - f.write((self.timestamp & 0xffffffff).to_bytes(4, 'little')) - f.write(b'\0\0\0\0') - f.write(marshal.dumps(code)) - f.close() - - return code - - -class VFSSharedImporter: - """ This is a special importer that is added onto the meta_path - list, so that it is called before sys.path is traversed. It uses - special logic to load one of the "shared" packages, by searching - the entire sys.path for all instances of this shared package, and - merging them. """ - - def __init__(self): - pass - - def find_module(self, fullname, path = None, reload = False): - #print >>sys.stderr, "shared find_module(%s), path = %s" % (fullname, path) - - if fullname not in sharedPackages: - # Not a shared package; fall back to normal import. - return None - - if path is None: - path = sys.path - - excludePaths = [] - if reload: - # If reload is true, we are simply reloading the module, - # looking for new paths to add. - mod = sys.modules[fullname] - excludePaths = getattr(mod, '_vfs_shared_path', None) - if excludePaths is None: - # If there isn't a _vfs_shared_path symbol already, - # the module must have been loaded through - # conventional means. Try to guess which path it was - # found on. - d = self.getLoadedDirname(mod) - excludePaths = [d] - - loaders = [] - for dir in path: - if dir in excludePaths: - continue - - importer = sys.path_importer_cache.get(dir, None) - if importer is None: - try: - importer = VFSImporter(dir) - except ImportError: - continue - - sys.path_importer_cache[dir] = importer - + # It's a virtual file with no real-world existence. Dump + # it to disk. + ext = filename.get_extension() + tmp_filename = Filename.temporary('', filename.get_basename_wo_extension(), + '.' + ext, + type = Filename.T_dso) + tmp_filename.set_extension(ext) + tmp_filename.set_binary() + sin = vfile.open_read_file(True) try: - loader = importer.find_module(fullname) - if not loader: - continue - except ImportError: - continue + sout = OFileStream() + if not tmp_filename.open_write(sout): + raise IOError + if not copy_stream(sin, sout): + raise IOError + finally: + vfile.close_read_file(sin) + del sout - loaders.append(loader) + # Delete when the process ends. + atexit.register(tmp_filename.unlink) - if not loaders: - return None - return VFSSharedLoader(loaders, reload = reload) + # Make a dummy spec to pass to create_dynamic with the path to + # our temporary file. + spec = ModuleSpec(spec.name, spec.loader, + origin=tmp_filename.to_os_specific(), + is_package=False) - def getLoadedDirname(self, mod): - """ Returns the directory name that the indicated - conventionally-loaded module must have been loaded from. """ + module = _imp.create_dynamic(spec) + module.__file__ = filename.to_os_specific() + return module - if not getattr(mod, '__file__', None): - return None + def exec_module(self, module): + _imp.exec_dynamic(module) - fullname = mod.__name__ - dirname = Filename.fromOsSpecific(mod.__file__).getDirname() + def is_package(self, fullname): + return False - parentname = None - basename = fullname - if '.' in fullname: - parentname, basename = fullname.rsplit('.', 1) + def get_code(self, fullname): + return None - path = None - if parentname: - parent = sys.modules[parentname] - path = parent.__path__ - if path is None: - path = sys.path - - for dir in path: - pdir = str(Filename.fromOsSpecific(dir)) - if pdir + '/' + basename == dirname: - # We found it! - return dir - - # Couldn't figure it out. + def get_source(self, fullname): return None -class VFSSharedLoader: - """ The second part of VFSSharedImporter, this imports a list of - packages and combines them. """ +class VFSNamespaceLoader(Loader): + def create_module(self, spec: ModuleSpec) -> ModuleType | None: + """Use default semantics for module creation.""" - def __init__(self, loaders, reload): - self.loaders = loaders - self.reload = reload + def exec_module(self, module: ModuleType) -> None: + pass - def load_module(self, fullname): - #print >>sys.stderr, "shared load_module(%s), loaders = %s" % (fullname, map(lambda l: l.dir_path, self.loaders)) + def is_package(self, fullname): + return True - mod = None - message = None - path = [] - vfs_shared_path = [] - if self.reload: - mod = sys.modules[fullname] - path = mod.__path__ or [] - if path == fullname: - # Work around Python bug setting __path__ of frozen modules. - path = [] - vfs_shared_path = getattr(mod, '_vfs_shared_path', []) + def get_source(self, fullname): + return '' - for loader in self.loaders: - try: - mod = loader.load_module(fullname, loadingShared = True) - except ImportError: - etype, evalue, etraceback = sys.exc_info() - print("%s on %s: %s" % (etype.__name__, fullname, evalue)) - if not message: - message = '%s: %s' % (fullname, evalue) - continue - for dir in getattr(mod, '__path__', []): - if dir not in path: - path.append(dir) + def get_code(self, fullname): + return compile('', '', 'exec', dont_inherit=True) - if mod is None: - # If all of them failed to load, raise ImportError. - raise ImportError(message) - # If at least one of them loaded successfully, return the - # union of loaded modules. - mod.__path__ = path - mod.__package__ = fullname - - # Also set this special symbol, which records that this is a - # shared package, and also lists the paths we have already - # loaded. - mod._vfs_shared_path = vfs_shared_path + [l.dir_path for l in self.loaders] - - return mod +def _path_hook(entry: str) -> VFSFinder: + # If this is a directory in the VFS, create a VFSFinder for this entry. + vfile = vfs.get_file(Filename.from_os_specific(entry), False) + if vfile and vfile.is_directory() and not isinstance(vfile.get_mount(), VirtualFileMountSystem): + return VFSFinder(entry) + else: + raise ImportError _registered = False - -def register(): - """ Register the VFSImporter on the path_hooks, if it has not +def register() -> None: + """ Register the VFSFinder on the path_hooks, if it has not already been registered, so that future Python import statements will vector through here (and therefore will take advantage of Panda's virtual file system). """ @@ -483,55 +273,9 @@ def register(): global _registered if not _registered: _registered = True - sys.path_hooks.insert(0, VFSImporter) - sys.meta_path.insert(0, VFSSharedImporter()) + sys.path_hooks.insert(0, _path_hook) # Blow away the importer cache, so we'll come back through the - # VFSImporter for every folder in the future, even those + # VFSFinder for every folder in the future, even those # folders that previously were loaded directly. sys.path_importer_cache = {} - - -def reloadSharedPackage(mod): - """ Reloads the specific module as a shared package, adding any - new directories that might have appeared on the search path. """ - - fullname = mod.__name__ - path = None - if '.' in fullname: - parentname = fullname.rsplit('.', 1)[0] - parent = sys.modules[parentname] - path = parent.__path__ - - importer = VFSSharedImporter() - loader = importer.find_module(fullname, path = path, reload = True) - if loader: - loader.load_module(fullname) - - # Also force any child packages to become shared packages, if - # they aren't already. - for basename, child in list(mod.__dict__.items()): - if isinstance(child, types.ModuleType): - childname = child.__name__ - if childname == fullname + '.' + basename and \ - hasattr(child, '__path__') and \ - childname not in sharedPackages: - sharedPackages[childname] = True - reloadSharedPackage(child) - - -def reloadSharedPackages(): - """ Walks through the sharedPackages list, and forces a reload of - any modules on that list that have already been loaded. This - allows new directories to be added to the search path. """ - - #print >> sys.stderr, "reloadSharedPackages, path = %s, sharedPackages = %s" % (sys.path, sharedPackages.keys()) - - # Sort the list, just to make sure parent packages are reloaded - # before child packages are. - for fullname in sorted(sharedPackages.keys()): - mod = sys.modules.get(fullname, None) - if not mod: - continue - - reloadSharedPackage(mod) diff --git a/direct/src/showbase/showBase.cxx b/direct/src/showbase/showBase.cxx index c4aa38b7d5..43a8a16f8e 100644 --- a/direct/src/showbase/showBase.cxx +++ b/direct/src/showbase/showBase.cxx @@ -45,15 +45,6 @@ ConfigureDef(config_showbase); ConfigureFn(config_showbase) { } -ConfigVariableSearchPath particle_path -("particle-path", - PRC_DESC("The directories to search for particle files to be loaded.")); - -ConfigVariableSearchPath & -get_particle_path() { - return particle_path; -} - // Throw the "NewFrame" event in the C++ world. Some of the lerp code depends // on receiving this. void diff --git a/direct/src/showbase/showBase.h b/direct/src/showbase/showBase.h index a578c5159c..25cf5fdf14 100644 --- a/direct/src/showbase/showBase.h +++ b/direct/src/showbase/showBase.h @@ -22,8 +22,6 @@ #include "animControl.h" #include "pointerTo.h" #include "dconfig.h" -#include "dSearchPath.h" -#include "configVariableSearchPath.h" #include "nodePath.h" ConfigureDecl(config_showbase, EXPCL_DIRECT_SHOWBASE, EXPTP_DIRECT_SHOWBASE); @@ -34,8 +32,6 @@ class GraphicsEngine; BEGIN_PUBLISH -EXPCL_DIRECT_SHOWBASE ConfigVariableSearchPath &get_particle_path(); - EXPCL_DIRECT_SHOWBASE void throw_new_frame(); EXPCL_DIRECT_SHOWBASE void init_app_for_gui(); diff --git a/direct/src/stdpy/thread.py b/direct/src/stdpy/thread.py index f20a2a54bc..7439ba5492 100644 --- a/direct/src/stdpy/thread.py +++ b/direct/src/stdpy/thread.py @@ -5,6 +5,8 @@ in some compilation models, Panda's threading constructs are incompatible with the OS-provided threads used by Python's thread module. """ +from __future__ import annotations + __all__ = [ 'error', 'LockType', 'start_new_thread', @@ -19,6 +21,9 @@ __all__ = [ from panda3d import core import sys +from collections.abc import Callable, Iterable, Mapping +from typing import Any + if sys.platform == "win32": TIMEOUT_MAX = float(0xffffffff // 1000) else: @@ -41,12 +46,12 @@ class LockType: allows a different thread to release the lock than the one that acquired it. """ - def __init__(self): + def __init__(self) -> None: self.__lock = core.Mutex('PythonLock') self.__cvar = core.ConditionVar(self.__lock) self.__locked = False - def acquire(self, waitflag = 1, timeout = -1): + def acquire(self, waitflag: bool = True, timeout: float = -1) -> bool: self.__lock.acquire() try: if self.__locked and not waitflag: @@ -65,7 +70,7 @@ class LockType: finally: self.__lock.release() - def release(self): + def release(self) -> None: self.__lock.acquire() try: if not self.__locked: @@ -90,18 +95,23 @@ class LockType: _counter = 0 -def _newname(template="Thread-%d"): +def _newname(template: str = "Thread-%d") -> str: global _counter _counter = _counter + 1 return template % _counter -_threads = {} +_threads: dict[int, tuple[core.Thread, dict[int, dict[str, Any]], Any | None]] = {} _nextThreadId = 0 _threadsLock = core.Mutex('thread._threadsLock') -def start_new_thread(function, args, kwargs = {}, name = None): +def start_new_thread( + function: Callable[..., object], + args: Iterable[Any], + kwargs: Mapping[str, Any] = {}, + name: str | None = None, +) -> int: def threadFunc(threadId, function = function, args = args, kwargs = kwargs): try: try: @@ -132,7 +142,7 @@ def start_new_thread(function, args, kwargs = {}, name = None): _threadsLock.release() -def _add_thread(thread, wrapper): +def _add_thread(thread: core.Thread, wrapper: Any) -> int: """ Adds the indicated core.Thread object, with the indicated Python wrapper, to the thread list. Returns the new thread ID. """ @@ -150,7 +160,7 @@ def _add_thread(thread, wrapper): _threadsLock.release() -def _get_thread_wrapper(thread, wrapperClass): +def _get_thread_wrapper(thread: core.Thread, wrapperClass: Callable[[core.Thread, int], Any]) -> Any: """ Returns the thread wrapper for the indicated thread. If there is not one, creates an instance of the indicated wrapperClass instead. """ @@ -222,7 +232,7 @@ def _get_thread_locals(thread, i): _threadsLock.release() -def _remove_thread_id(threadId): +def _remove_thread_id(threadId: int) -> None: """ Removes the thread with the indicated ID from the thread list. """ # On interpreter shutdown, Python may set module globals to None. @@ -250,11 +260,11 @@ def exit(): raise SystemExit -def allocate_lock(): +def allocate_lock() -> LockType: return LockType() -def get_ident(): +def get_ident() -> int: return core.Thread.getCurrentThread().this diff --git a/direct/src/stdpy/threading.py b/direct/src/stdpy/threading.py index 3b82b61466..e34b24342c 100644 --- a/direct/src/stdpy/threading.py +++ b/direct/src/stdpy/threading.py @@ -21,12 +21,17 @@ easier to use and understand. It is permissible to mix-and-match both threading and threading2 within the same application. """ +from __future__ import annotations + from panda3d import core from direct.stdpy import thread as _thread import sys as _sys import weakref +from collections.abc import Callable, Iterable, Mapping +from typing import Any, NoReturn + __all__ = [ 'Thread', 'Lock', 'RLock', @@ -54,10 +59,14 @@ class ThreadBase: """ A base class for both Thread and ExternalThread in this module. """ - def __init__(self): + name: str + ident: int + daemon: bool + + def __init__(self) -> None: pass - def getName(self): + def getName(self) -> str: return self.name def isDaemon(self): @@ -92,7 +101,15 @@ class Thread(ThreadBase): object. The wrapper is designed to emulate Python's own threading.Thread object. """ - def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, daemon=None): + def __init__( + self, + group: None = None, + target: Callable[..., object] | None = None, + name: str | None = None, + args: Iterable[Any] = (), + kwargs: Mapping[str, Any] = {}, + daemon: bool | None = None, + ) -> None: ThreadBase.__init__(self) assert group is None @@ -131,7 +148,7 @@ class Thread(ThreadBase): isAlive = is_alive - def start(self): + def start(self) -> None: thread = self.__thread if thread is None or thread.is_started(): raise RuntimeError @@ -147,7 +164,7 @@ class Thread(ThreadBase): self.__target(*self.__args, **self.__kwargs) - def join(self, timeout = None): + def join(self, timeout: float | None = None) -> None: # We don't support a timed join here, sorry. assert timeout is None thread = self.__thread @@ -157,7 +174,7 @@ class Thread(ThreadBase): self.__thread = None _thread._remove_thread_id(self.ident) - def setName(self, name): + def setName(self, name: str) -> None: self.__dict__['name'] = name self.__thread.setName(name) @@ -166,7 +183,7 @@ class ExternalThread(ThreadBase): """ Returned for a Thread object that wasn't created by this interface. """ - def __init__(self, extThread, threadId): + def __init__(self, extThread: core.Thread, threadId: int) -> None: ThreadBase.__init__(self) self.__thread = extThread @@ -196,7 +213,7 @@ class ExternalThread(ThreadBase): class MainThread(ExternalThread): """ Returned for the MainThread object. """ - def __init__(self, extThread, threadId): + def __init__(self, extThread: core.Thread, threadId: int) -> None: ExternalThread.__init__(self, extThread, threadId) self.__dict__['daemon'] = False @@ -206,7 +223,7 @@ class Lock(core.Mutex): The wrapper is designed to emulate Python's own threading.Lock object. """ - def __init__(self, name = "PythonLock"): + def __init__(self, name: str = "PythonLock") -> None: core.Mutex.__init__(self, name) @@ -224,7 +241,7 @@ class Condition(core.ConditionVar): object. The wrapper is designed to emulate Python's own threading.Condition object. """ - def __init__(self, lock = None): + def __init__(self, lock: Lock | RLock | None = None) -> None: if not lock: lock = Lock() @@ -241,7 +258,7 @@ class Condition(core.ConditionVar): def release(self): self.__lock.release() - def wait(self, timeout = None): + def wait(self, timeout: float | None = None) -> None: if timeout is None: core.ConditionVar.wait(self) else: @@ -373,8 +390,9 @@ class Timer(Thread): self.finished.set() -def _create_thread_wrapper(t, threadId): +def _create_thread_wrapper(t: core.Thread, threadId: int) -> ExternalThread: """ Creates a thread wrapper for the indicated external thread. """ + pyt: ExternalThread if isinstance(t, core.MainThread): pyt = MainThread(t, threadId) else: @@ -383,7 +401,7 @@ def _create_thread_wrapper(t, threadId): return pyt -def current_thread(): +def current_thread() -> ThreadBase: t = core.Thread.getCurrentThread() return _thread._get_thread_wrapper(t, _create_thread_wrapper) @@ -430,5 +448,5 @@ def setprofile(func): _setprofile_func = func -def stack_size(size = None): +def stack_size(size: object = None) -> NoReturn: raise ThreadError diff --git a/direct/src/stdpy/threading2.py b/direct/src/stdpy/threading2.py index 09d0a8d083..cde6e0aa7e 100644 --- a/direct/src/stdpy/threading2.py +++ b/direct/src/stdpy/threading2.py @@ -13,6 +13,8 @@ to import Panda's thread reimplementation instead of the system thread module, and so it is therefore layered on top of Panda's thread implementation. """ +from __future__ import annotations + import sys as _sys import atexit as _atexit @@ -21,8 +23,10 @@ from direct.stdpy.thread import stack_size, _newname, _local as local from panda3d import core _sleep = core.Thread.sleep +from collections.abc import Callable, Iterable, Mapping from time import time as _time from traceback import format_exc as _format_exc +from typing import Any __all__ = ['get_ident', 'active_count', 'Condition', 'current_thread', 'enumerate', 'main_thread', 'TIMEOUT_MAX', @@ -51,12 +55,12 @@ if __debug__: class _Verbose(object): - def __init__(self, verbose=None): + def __init__(self, verbose: bool | None = None) -> None: if verbose is None: verbose = _VERBOSE self.__verbose = verbose - def _note(self, format, *args): + def _note(self, format: str, *args: Any) -> None: if self.__verbose: format = format % args format = "%s: %s\n" % ( @@ -66,9 +70,9 @@ if __debug__: else: # Disable this when using "python -O" class _Verbose(object): # type: ignore[no-redef] - def __init__(self, verbose=None): + def __init__(self, verbose: bool | None = None) -> None: pass - def _note(self, *args): + def _note(self, *args) -> None: pass # Support for profile and trace hooks @@ -88,15 +92,15 @@ def settrace(func): Lock = _allocate_lock -def RLock(*args, **kwargs): - return _RLock(*args, **kwargs) +def RLock(verbose: bool | None = None) -> _RLock: + return _RLock(verbose) class _RLock(_Verbose): - def __init__(self, verbose=None): + def __init__(self, verbose: bool | None = None) -> None: _Verbose.__init__(self, verbose) self.__block = _allocate_lock() - self.__owner = None + self.__owner: Thread | None = None self.__count = 0 def __repr__(self): @@ -105,13 +109,13 @@ class _RLock(_Verbose): self.__owner and self.__owner.getName(), self.__count) - def acquire(self, blocking=1): + def acquire(self, blocking: bool = True) -> bool: me = currentThread() if self.__owner is me: self.__count = self.__count + 1 if __debug__: self._note("%s.acquire(%s): recursive success", self, blocking) - return 1 + return True rc = self.__block.acquire(blocking) if rc: self.__owner = me @@ -125,7 +129,7 @@ class _RLock(_Verbose): __enter__ = acquire - def release(self): + def release(self) -> None: me = currentThread() assert self.__owner is me, "release() of un-acquire()d lock" self.__count = count = self.__count - 1 @@ -163,12 +167,12 @@ class _RLock(_Verbose): return self.__owner is currentThread() -def Condition(*args, **kwargs): - return _Condition(*args, **kwargs) +def Condition(lock: _thread.LockType | _RLock | None = None, verbose: bool | None = None) -> _Condition: + return _Condition(lock, verbose) class _Condition(_Verbose): - def __init__(self, lock=None, verbose=None): + def __init__(self, lock: _thread.LockType | _RLock | None = None, verbose: bool | None = None) -> None: _Verbose.__init__(self, verbose) if lock is None: lock = RLock() @@ -180,18 +184,18 @@ class _Condition(_Verbose): # these override the default implementations (which just call # release() and acquire() on the lock). Ditto for _is_owned(). try: - self._release_save = lock._release_save + self._release_save = lock._release_save # type: ignore[method-assign, union-attr] except AttributeError: pass try: - self._acquire_restore = lock._acquire_restore + self._acquire_restore = lock._acquire_restore # type: ignore[method-assign, union-attr] except AttributeError: pass try: - self._is_owned = lock._is_owned + self._is_owned = lock._is_owned # type: ignore[method-assign, union-attr] except AttributeError: pass - self.__waiters = [] + self.__waiters: list[_thread.LockType] = [] def __enter__(self): return self.__lock.__enter__() @@ -202,22 +206,22 @@ class _Condition(_Verbose): def __repr__(self): return "" % (self.__lock, len(self.__waiters)) - def _release_save(self): # pylint: disable=method-hidden + def _release_save(self) -> Any: # pylint: disable=method-hidden self.__lock.release() # No state to save - def _acquire_restore(self, x): # pylint: disable=method-hidden + def _acquire_restore(self, x) -> None: # pylint: disable=method-hidden self.__lock.acquire() # Ignore saved state - def _is_owned(self): # pylint: disable=method-hidden + def _is_owned(self) -> bool: # pylint: disable=method-hidden # Return True if lock is owned by currentThread. # This method is called only if __lock doesn't have _is_owned(). - if self.__lock.acquire(0): + if self.__lock.acquire(False): self.__lock.release() return False else: return True - def wait(self, timeout=None): + def wait(self, timeout: float | None = None) -> None: assert self._is_owned(), "wait() of un-acquire()d lock" waiter = _allocate_lock() waiter.acquire() @@ -237,7 +241,7 @@ class _Condition(_Verbose): endtime = _time() + timeout delay = 0.0005 # 500 us -> initial delay of 1 ms while True: - gotit = waiter.acquire(0) + gotit = waiter.acquire(False) if gotit: break remaining = endtime - _time() @@ -258,7 +262,7 @@ class _Condition(_Verbose): finally: self._acquire_restore(saved_state) - def notify(self, n=1): + def notify(self, n: int = 1) -> None: assert self._is_owned(), "notify() of un-acquire()d lock" __waiters = self.__waiters waiters = __waiters[:n] @@ -275,7 +279,7 @@ class _Condition(_Verbose): except ValueError: pass - def notifyAll(self): + def notifyAll(self) -> None: self.notify(len(self.__waiters)) @@ -381,7 +385,7 @@ class _Event(_Verbose): # Active thread administration _active_limbo_lock = _allocate_lock() -_active = {} # maps thread id to Thread object +_active: dict[int, Thread] = {} # maps thread id to Thread object _limbo = {} @@ -400,8 +404,16 @@ class Thread(_Verbose): # Protected by _active_limbo_lock. __registered_atexit = False - def __init__(self, group=None, target=None, name=None, - args=(), kwargs=None, verbose=None, daemon=None): + def __init__( + self, + group: None = None, + target: Callable[..., object] | None = None, + name: object = None, + args: Iterable[Any] = (), + kwargs: Mapping[str, Any] | None = None, + verbose: bool | None = None, + daemon: bool | None = None, + ) -> None: assert group is None, "group argument must be None for now" _Verbose.__init__(self, verbose) if kwargs is None: @@ -422,7 +434,7 @@ class Thread(_Verbose): # sys.exc_info since it can be changed between instances self.__stderr = _sys.stderr - def _set_daemon(self): + def _set_daemon(self) -> bool: # Overridden in _MainThread and _DummyThread return currentThread().isDaemon() @@ -437,7 +449,7 @@ class Thread(_Verbose): status = status + " daemon" return "<%s(%s, %s)>" % (self.__class__.__name__, self.__name, status) - def start(self): + def start(self) -> None: assert self.__initialized, "Thread.__init__() not called" assert not self.__started, "thread already started" if __debug__: @@ -457,11 +469,11 @@ class Thread(_Verbose): self.__started = True _sleep(0.000001) # 1 usec, to let the thread run (Solaris hack) - def run(self): + def run(self) -> None: if self.__target: self.__target(*self.__args, **self.__kwargs) - def __bootstrap(self): + def __bootstrap(self) -> None: try: self.__started = True _active_limbo_lock.acquire() @@ -497,7 +509,7 @@ class Thread(_Verbose): # Do the best job possible w/o a huge amt. of code to # approximate a traceback (code ideas from # Lib/traceback.py) - exc_type, exc_value, exc_tb = self.__exc_info() + exc_type, exc_value, exc_tb = self.__exc_info() # type: ignore[misc] try: self.__stderr.write("Exception in thread " + self.getName() + " (most likely raised during interpreter shutdown):\n") @@ -523,13 +535,13 @@ class Thread(_Verbose): except: pass - def __stop(self): + def __stop(self) -> None: self.__block.acquire() self.__stopped = True self.__block.notifyAll() self.__block.release() - def __delete(self): + def __delete(self) -> None: "Remove current thread from the dict of currently running threads." # Notes about running with dummy_thread: @@ -563,7 +575,7 @@ class Thread(_Verbose): finally: _active_limbo_lock.release() - def join(self, timeout=None): + def join(self, timeout: float | None = None) -> None: assert self.__initialized, "Thread.__init__() not called" assert self.__started, "cannot join thread before it is started" assert self is not currentThread(), "cannot join current thread" @@ -592,11 +604,11 @@ class Thread(_Verbose): finally: self.__block.release() - def getName(self): + def getName(self) -> str: assert self.__initialized, "Thread.__init__() not called" return self.__name - def setName(self, name): + def setName(self, name: object) -> None: assert self.__initialized, "Thread.__init__() not called" self.__name = str(name) @@ -606,7 +618,7 @@ class Thread(_Verbose): isAlive = is_alive - def isDaemon(self): + def isDaemon(self) -> bool: assert self.__initialized, "Thread.__init__() not called" return self.__daemonic @@ -654,7 +666,7 @@ class _Timer(Thread): class _MainThread(Thread): - def __init__(self): + def __init__(self) -> None: Thread.__init__(self, name="MainThread") self._Thread__started = True _active_limbo_lock.acquire() @@ -688,13 +700,13 @@ class _MainThread(Thread): class _DummyThread(Thread): - def __init__(self): + def __init__(self) -> None: Thread.__init__(self, name=_newname("Dummy-%d"), daemon=True) # Thread.__block consumes an OS-level locking primitive, which # can never be used by a _DummyThread. Since a _DummyThread # instance is immortal, that's bad, so release this resource. - del self._Thread__block + del self._Thread__block # type: ignore[attr-defined] self._Thread__started = True _active_limbo_lock.acquire() @@ -710,7 +722,7 @@ class _DummyThread(Thread): # Global API functions -def current_thread(): +def current_thread() -> Thread: try: return _active[get_ident()] except KeyError: diff --git a/direct/src/task/Task.py b/direct/src/task/Task.py index 9896b9c5ee..f879cbd542 100644 --- a/direct/src/task/Task.py +++ b/direct/src/task/Task.py @@ -28,7 +28,7 @@ if hasattr(sys, 'getandroidapilevel'): signal = None else: try: - import _signal as signal # type: ignore[import, no-redef] + import _signal as signal # type: ignore[import-not-found, no-redef] except ImportError: signal = None diff --git a/direct/src/tkpanels/Inspector.py b/direct/src/tkpanels/Inspector.py index 51e448f30d..5dbf79c1f9 100644 --- a/direct/src/tkpanels/Inspector.py +++ b/direct/src/tkpanels/Inspector.py @@ -48,7 +48,7 @@ def inspectorFor(anObject): ### initializing -def initializeInspectorMap(): +def initializeInspectorMap() -> None: global _InspectorMap notFinishedTypes = ['BufferType', 'EllipsisType', 'FrameType', 'TracebackType', 'XRangeType'] diff --git a/direct/src/tkpanels/ParticlePanel.py b/direct/src/tkpanels/ParticlePanel.py index 6d41ba808f..f17e9f022f 100644 --- a/direct/src/tkpanels/ParticlePanel.py +++ b/direct/src/tkpanels/ParticlePanel.py @@ -3,7 +3,7 @@ __all__ = ['ParticlePanel'] # Import Tkinter, Pmw, and the floater code from this directory tree. -from panda3d.core import ColorBlendAttrib, Filename, Point2, Point3, Vec3, Vec4, getModelPath +from panda3d.core import ColorBlendAttrib, ConfigVariableSearchPath, Filename, Point2, Point3, Vec3, Vec4, getModelPath from panda3d.physics import ( BaseParticleEmitter, BaseParticleRenderer, @@ -36,7 +36,6 @@ from panda3d.physics import ( SpriteParticleRenderer, TangentRingEmitter, ) -from panda3d.direct import getParticlePath from direct.tkwidgets.AppShell import AppShell from direct.tkwidgets import Dial from direct.tkwidgets import Floater @@ -53,6 +52,10 @@ import os import tkinter as tk +particlePath = ConfigVariableSearchPath("particle-path", + "The directories to search for particle files to be loaded.") + + class ParticlePanel(AppShell): # Override class variables appname = 'Particle Panel' @@ -1275,7 +1278,7 @@ class ParticlePanel(AppShell): def loadParticleEffectFromFile(self): # Find path to particle directory - pPath = getParticlePath() + pPath = particlePath if pPath.getNumDirectories() > 0: if repr(pPath.getDirectory(0)) == '.': path = '.' @@ -1303,7 +1306,7 @@ class ParticlePanel(AppShell): def saveParticleEffectToFile(self): # Find path to particle directory - pPath = getParticlePath() + pPath = particlePath if pPath.getNumDirectories() > 0: if repr(pPath.getDirectory(0)) == '.': path = '.' @@ -2872,6 +2875,12 @@ class ParticlePanel(AppShell): if type == 'FT_ONE_OVER_R_CUBED': #f.setFalloffType(LinearDistanceForce.FTONEOVERRCUBED) f.setFalloffType(2) + if type == 'FT_ONE_OVER_R_OVER_DISTANCE': + f.setFalloffType(3) + if type == 'FT_ONE_OVER_R_OVER_DISTANCE_SQUARED': + f.setFalloffType(4) + if type == 'FT_ONE_OVER_R_OVER_DISTANCE_CUBED': + f.setFalloffType(5) def setForceCenter(vec, f = force): f.setForceCenter(Point3(vec[0], vec[1], vec[2])) @@ -2886,7 +2895,10 @@ class ParticlePanel(AppShell): 'Set force falloff type', ('FT_ONE_OVER_R', 'FT_ONE_OVER_R_SQUARED', - 'FT_ONE_OVER_R_CUBED'), + 'FT_ONE_OVER_R_CUBED', + 'FT_ONE_OVER_R_OVER_DISTANCE', + 'FT_ONE_OVER_R_OVER_DISTANCE_SQUARED', + 'FT_ONE_OVER_R_OVER_DISTANCE_CUBED'), command = setFalloffType) self.getWidget(pageName, forceName + ' Falloff').configure( label_width = 16) @@ -2897,6 +2909,12 @@ class ParticlePanel(AppShell): var.set('FT_ONE_OVER_R_SQUARED') elif falloff == LinearDistanceForce.FTONEOVERRCUBED: var.set('FT_ONE_OVER_R_CUBED') + elif falloff == LinearDistanceForce.FTONEOVERROVERDISTANCE: + var.set('FT_ONE_OVER_R_OVER_DISTANCE') + elif falloff == LinearDistanceForce.FTONEOVERROVERDISTANCESQUARED: + var.set('FT_ONE_OVER_R_OVER_DISTANCE_SQUARED') + elif falloff == LinearDistanceForce.FTONEOVERROVERDISTANCECUBED: + var.set('FT_ONE_OVER_R_OVER_DISTANCE_CUBED') vec = force.getForceCenter() self.createVector3Entry(frame, pageName, forceName + ' Center', 'Set center of force', diff --git a/doc/ReleaseNotes b/doc/ReleaseNotes index e5651145b7..d0769cd785 100644 --- a/doc/ReleaseNotes +++ b/doc/ReleaseNotes @@ -1,3 +1,32 @@ +----------------------- RELEASE 1.10.16 ----------------------- + +This maintenance release fixes some minor defects and stability issues. + +* OpenAL: Support non-default coordinate systems when playing 3D audio +* Tasks: Coroutine detect now also handles coroutine subclass to support Nuitka +* Tasks: Now properly handles generators without send() +* Windows: Fixes a hang when adjusting Z-order in some situations +* Fix SparseArray methods get_lowest_off_bit() and get_lowest_on_bit() +* Fix linecache error when distributing for newer Python versions +* Fix `await AsyncFuture.gather()` returning first item instead of tuple (#1738) +* Fix use-after-free in collision system with transform cache disabled (#1733) +* Egg: Add limited forward compatibility for metallic-roughness textures +* OpenGL: fix error blitting depth texture on macOS (#1719) +* OpenGL: fix SSBO not being rebound if deleted and recreated immediately +* OpenGL: fix SSBO support not being detected in certain situations +* GLSL: Add p3d_MetallicRoughnessTexture input mapped to M_metallic_roughness +* X11: Add config variable to enable detection of autorepeat key events (#1735) +* GUI: Fix bug with PGSliderBar dragging (#1722) +* Minor thread safety things for free-threaded Python builds +* Add forward compatibility for bam version 6.46 +* Fixes a harmless buffer overflow in pdtoa +* Fix compilation issues with SDL version of tinydisplay (#1708) +* bam2egg: Fix issue when having more than two tags (#1725) +* Fix "Detected leak for ... which interrogate cannot delete." error (#1743) +* Fix PythonCallbackObject crash upon destruction in some cases +* PStats: Fix crash when receiving frames out of order +* PandaFramework::close_framework() now clears task manager of tasks + ----------------------- RELEASE 1.10.15 ----------------------- This release adds support for Python 3.13, and fixes some significant bugs. diff --git a/dtool/CompilerFlags.cmake b/dtool/CompilerFlags.cmake index 31d96279e3..d50e5e2238 100644 --- a/dtool/CompilerFlags.cmake +++ b/dtool/CompilerFlags.cmake @@ -51,6 +51,16 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GCC") endif() +# AddressSanitizer +if(ENABLE_ASAN) + if(CMAKE_CXX_COMPILER_ID MATCHES "(AppleClang|Clang|GNU)") + add_compile_options(-fsanitize=address -fno-omit-frame-pointer) + add_link_options(-fsanitize=address) + else() + message(FATAL_ERROR "ENABLE_ASAN requires GCC or Clang") + endif() +endif() + # Panda3D is now a C++14 project. set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/dtool/Config.cmake b/dtool/Config.cmake index f3063d86ad..cfc2dbfca3 100644 --- a/dtool/Config.cmake +++ b/dtool/Config.cmake @@ -265,7 +265,7 @@ if(BUILD_INTERROGATE) panda3d-interrogate GIT_REPOSITORY https://github.com/panda3d/interrogate.git - GIT_TAG 03418d6d7ddda7fb99abf27230aa42d1d8bd607e + GIT_TAG 7cf2550d2c8d95b8c268aa4bb0b5602e85a086dc PREFIX ${_interrogate_dir} CMAKE_ARGS @@ -425,6 +425,14 @@ endif() unset(_prefer_mimalloc) +# +# Sanitizers +# + +option(ENABLE_ASAN + "Enable AddressSanitizer for detecting memory errors such as +buffer overflows, use-after-free, etc. Requires GCC or Clang." OFF) + # # This section relates to mobile-device/phone support and options # diff --git a/dtool/LocalSetup.cmake b/dtool/LocalSetup.cmake index b92fb8614c..5ddea90f9a 100644 --- a/dtool/LocalSetup.cmake +++ b/dtool/LocalSetup.cmake @@ -133,6 +133,7 @@ check_include_file_cxx(dirent.h PHAVE_DIRENT_H) check_include_file_cxx(ucontext.h PHAVE_UCONTEXT_H) #TODO doesn't work on OSX, use sys/ucontext.h check_include_file_cxx(linux/input.h PHAVE_LINUX_INPUT_H) check_include_file_cxx(stdint.h PHAVE_STDINT_H) +check_include_file_cxx(execinfo.h PHAVE_EXECINFO_H) # Do we have Posix threads? #set(HAVE_POSIX_THREADS ${CMAKE_USE_PTHREADS_INIT}) diff --git a/dtool/src/dtoolbase/atomicAdjust.h b/dtool/src/dtoolbase/atomicAdjust.h index 804224e5c3..014c4c4445 100644 --- a/dtool/src/dtoolbase/atomicAdjust.h +++ b/dtool/src/dtoolbase/atomicAdjust.h @@ -30,7 +30,7 @@ struct AtomicAdjust { #include "atomicAdjustDummyImpl.h" typedef AtomicAdjustDummyImpl AtomicAdjust; -#elif (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) || (defined(__clang__) && (__clang_major__ >= 3)) +#elif (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) || (defined(__clang__) && (__clang_major__ >= 3) && !defined(WIN32_VC) ) // GCC 4.7 and above has built-in __atomic functions for atomic operations. // Clang 3.0 and above also supports them. diff --git a/dtool/src/dtoolbase/deletedBufferChain.cxx b/dtool/src/dtoolbase/deletedBufferChain.cxx index 9821129254..13df8a8b71 100644 --- a/dtool/src/dtoolbase/deletedBufferChain.cxx +++ b/dtool/src/dtoolbase/deletedBufferChain.cxx @@ -88,7 +88,14 @@ allocate(size_t size, TypeHandle type_handle) { return ptr; #else // USE_DELETED_CHAIN - return PANDA_MALLOC_SINGLE(_buffer_size); + void *ptr = PANDA_MALLOC_SINGLE(_buffer_size); + +#ifdef DO_MEMORY_USAGE + type_handle.inc_memory_usage(TypeHandle::MC_singleton, _buffer_size); +#endif // DO_MEMORY_USAGE + + return ptr; + #endif // USE_DELETED_CHAIN } @@ -133,6 +140,11 @@ deallocate(void *ptr, TypeHandle type_handle) { _lock.unlock(); #else // USE_DELETED_CHAIN + +#ifdef DO_MEMORY_USAGE + type_handle.dec_memory_usage(TypeHandle::MC_singleton, _buffer_size); +#endif // DO_MEMORY_USAGE + PANDA_FREE_SINGLE(ptr); #endif // USE_DELETED_CHAIN } @@ -155,8 +167,9 @@ get_deleted_chain(size_t buffer_size) { static MutexImpl lock; lock.lock(); - static std::set deleted_chains; - DeletedBufferChain *result = (DeletedBufferChain *)&*deleted_chains.insert(DeletedBufferChain(buffer_size)).first; + alignas(std::set) static char storage[sizeof(std::set)]; + static auto *deleted_chains = new (storage) std::set; + DeletedBufferChain *result = (DeletedBufferChain *)&*deleted_chains->insert(DeletedBufferChain(buffer_size)).first; lock.unlock(); return result; } diff --git a/dtool/src/dtoolbase/deletedChain.h b/dtool/src/dtoolbase/deletedChain.h index a1b08a31bf..f418d4ee59 100644 --- a/dtool/src/dtoolbase/deletedChain.h +++ b/dtool/src/dtoolbase/deletedChain.h @@ -122,6 +122,50 @@ public: #define ALLOC_DELETED_CHAIN_DEF(Type) \ DeletedChain< Type > Type::_deleted_chain; +#elif defined(DO_MEMORY_USAGE) + +#define ALLOC_DELETED_CHAIN(Type) \ + inline void *operator new(size_t size) RETURNS_ALIGNED(MEMORY_HOOK_ALIGNMENT) { \ + void *ptr = PANDA_MALLOC_SINGLE(size); \ + get_type_handle(Type).inc_memory_usage(TypeHandle::MC_singleton, sizeof(Type)); \ + return ptr; \ + } \ + inline void *operator new(size_t size, void *ptr) { \ + (void) size; \ + return ptr; \ + } \ + inline void operator delete(void *ptr) { \ + if (ptr != nullptr) { \ + get_type_handle(Type).dec_memory_usage(TypeHandle::MC_singleton, sizeof(Type)); \ + PANDA_FREE_SINGLE(ptr); \ + } \ + } \ + inline void operator delete(void *, void *) { \ + } \ + inline void *operator new[](size_t size) RETURNS_ALIGNED(MEMORY_HOOK_ALIGNMENT) { \ + void *ptr = PANDA_MALLOC_SINGLE(size); \ + get_type_handle(Type).inc_memory_usage(TypeHandle::MC_array, sizeof(Type)); \ + return ptr; \ + } \ + inline void *operator new[](size_t size, void *ptr) { \ + (void) size; \ + return ptr; \ + } \ + inline void operator delete[](void *ptr) { \ + if (ptr != nullptr) { \ + get_type_handle(Type).dec_memory_usage(TypeHandle::MC_array, sizeof(Type)); \ + PANDA_FREE_SINGLE(ptr); \ + } \ + } \ + inline void operator delete[](void *, void *) { \ + } \ + inline static bool validate_ptr(const void *ptr) { \ + return (ptr != nullptr); \ + } + +#define ALLOC_DELETED_CHAIN_DECL(Type) ALLOC_DELETED_CHAIN(Type) +#define ALLOC_DELETED_CHAIN_DEF(Type) + #else // USE_DELETED_CHAIN #define ALLOC_DELETED_CHAIN(Type) \ diff --git a/dtool/src/dtoolbase/dtool_platform.h b/dtool/src/dtoolbase/dtool_platform.h index dbfcb26556..dce9e989f3 100644 --- a/dtool/src/dtoolbase/dtool_platform.h +++ b/dtool/src/dtoolbase/dtool_platform.h @@ -96,6 +96,9 @@ #elif defined(__arm__) #define DTOOL_PLATFORM "linux_arm" +#elif defined(__riscv) +#define DTOOL_PLATFORM "linux_riscv" + #elif defined(__ppc__) #define DTOOL_PLATFORM "linux_ppc" diff --git a/dtool/src/dtoolbase/memoryHook.cxx b/dtool/src/dtoolbase/memoryHook.cxx index 37b36d8633..b2c51c06a0 100644 --- a/dtool/src/dtoolbase/memoryHook.cxx +++ b/dtool/src/dtoolbase/memoryHook.cxx @@ -618,6 +618,9 @@ determine_page_size() const { _page_size = (size_t)sysinfo.dwPageSize; +#elif defined(ANDROID) + _page_size = getpagesize(); + #else // Posix case. _page_size = sysconf(_SC_PAGESIZE); diff --git a/dtool/src/dtoolbase/patomic.I b/dtool/src/dtoolbase/patomic.I index d1395c9e90..b679834713 100644 --- a/dtool/src/dtoolbase/patomic.I +++ b/dtool/src/dtoolbase/patomic.I @@ -454,6 +454,12 @@ patomic_notify_one(volatile uint32_t *value) { #elif defined(_WIN32) _patomic_wake_one_func((void *)value); #elif defined(__APPLE__) +#ifndef __arm64__ + if (UNLIKELY(__ulock_wake == nullptr || __ulock_wait == nullptr)) { + _patomic_notify_all(value); + return; + } +#endif __ulock_wake(UL_COMPARE_AND_WAIT, (void *)value, 0); #elif defined(HAVE_POSIX_THREADS) _patomic_notify_all(value); @@ -472,6 +478,12 @@ patomic_notify_all(volatile uint32_t *value) { #elif defined(_WIN32) _patomic_wake_all_func((void *)value); #elif defined(__APPLE__) +#ifndef __arm64__ + if (UNLIKELY(__ulock_wake == nullptr || __ulock_wait == nullptr)) { + _patomic_notify_all(value); + return; + } +#endif __ulock_wake(UL_COMPARE_AND_WAIT | ULF_WAKE_ALL, (void *)value, 0); #elif defined(HAVE_POSIX_THREADS) _patomic_notify_all(value); diff --git a/dtool/src/dtoolbase/patomic.cxx b/dtool/src/dtoolbase/patomic.cxx index 1b939a58c6..26949e564a 100644 --- a/dtool/src/dtoolbase/patomic.cxx +++ b/dtool/src/dtoolbase/patomic.cxx @@ -125,7 +125,7 @@ initialize_wait(volatile VOID *addr, PVOID cmp, SIZE_T size, DWORD timeout) { return emulated_wait(addr, cmp, size, timeout); } -#elif !defined(CPPPARSER) && !defined(__linux__) && !defined(__APPLE__) && defined(HAVE_POSIX_THREADS) +#elif !defined(CPPPARSER) && !defined(__linux__) && (!defined(__APPLE__) || !defined(__arm64__)) && defined(HAVE_POSIX_THREADS) // Same as above, but using pthreads. struct alignas(64) WaitTableEntry { diff --git a/dtool/src/dtoolbase/patomic.h b/dtool/src/dtoolbase/patomic.h index 02d1ab6ebc..2b0fdaacc0 100644 --- a/dtool/src/dtoolbase/patomic.h +++ b/dtool/src/dtoolbase/patomic.h @@ -36,9 +36,6 @@ // Undocumented API, see https://outerproduct.net/futex-dictionary.html #define UL_COMPARE_AND_WAIT 1 #define ULF_WAKE_ALL 0x00000100 - -extern "C" int __ulock_wait(uint32_t op, void *addr, uint64_t value, uint32_t timeout); -extern "C" int __ulock_wake(uint32_t op, void *addr, uint64_t wake_value); #endif #if defined(THREAD_DUMMY_IMPL) || defined(THREAD_SIMPLE_IMPL) @@ -173,9 +170,17 @@ ALWAYS_INLINE void patomic_notify_all(volatile uint32_t *value); EXPCL_DTOOL_DTOOLBASE extern BOOL (__stdcall *_patomic_wait_func)(volatile VOID *, PVOID, SIZE_T, DWORD); EXPCL_DTOOL_DTOOLBASE extern void (__stdcall *_patomic_wake_one_func)(PVOID); EXPCL_DTOOL_DTOOLBASE extern void (__stdcall *_patomic_wake_all_func)(PVOID); -#elif !defined(__linux__) && !defined(__APPLE__) && defined(HAVE_POSIX_THREADS) +#elif defined(__APPLE__) && defined(__arm64__) +extern "C" int __ulock_wait(uint32_t op, void *addr, uint64_t value, uint32_t timeout); +extern "C" int __ulock_wake(uint32_t op, void *addr, uint64_t wake_value); +#elif !defined(__linux__) && defined(HAVE_POSIX_THREADS) EXPCL_DTOOL_DTOOLBASE void _patomic_wait(const volatile uint32_t *value, uint32_t old); EXPCL_DTOOL_DTOOLBASE void _patomic_notify_all(volatile uint32_t *value); +#ifdef __APPLE__ +// Use conditionally since we can't count on support before 10.12. +extern "C" int __ulock_wait(uint32_t op, void *addr, uint64_t value, uint32_t timeout) __attribute__((weak_import)); +extern "C" int __ulock_wake(uint32_t op, void *addr, uint64_t wake_value) __attribute__((weak_import)); +#endif #endif #include "patomic.I" diff --git a/dtool/src/dtoolbase/pdtoa.cxx b/dtool/src/dtoolbase/pdtoa.cxx index 552391a6fc..ce47acb339 100644 --- a/dtool/src/dtoolbase/pdtoa.cxx +++ b/dtool/src/dtoolbase/pdtoa.cxx @@ -252,7 +252,7 @@ inline static unsigned CountDecimalDigit32(uint32_t n) { } inline static void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) { - static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 0, 0, 0, 0, 0 }; + static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 0, 0, 0, 0, 0, 0 }; const DiyFp one(uint64_t(1) << -Mp.e, Mp.e); const DiyFp wp_w = Mp - W; uint32_t p1 = static_cast(Mp.f >> -one.e); diff --git a/dtool/src/dtoolbase/pvector.h b/dtool/src/dtoolbase/pvector.h index be60897d21..edd6cca579 100644 --- a/dtool/src/dtoolbase/pvector.h +++ b/dtool/src/dtoolbase/pvector.h @@ -46,13 +46,16 @@ public: typedef std::vector base_class; typedef typename base_class::size_type size_type; - explicit pvector(TypeHandle type_handle = pvector_type_handle) : base_class(allocator(type_handle)) { } + pvector() : base_class(allocator(get_type_handle(pvector))) { } + explicit pvector(TypeHandle type_handle) : base_class(allocator(type_handle)) { } pvector(const pvector ©) : base_class(copy) { } pvector(pvector &&from) noexcept : base_class(std::move(from)) {}; - explicit pvector(size_type n, TypeHandle type_handle = pvector_type_handle) : base_class(n, Type(), allocator(type_handle)) { } - explicit pvector(size_type n, const Type &value, TypeHandle type_handle = pvector_type_handle) : base_class(n, value, allocator(type_handle)) { } - pvector(const Type *begin, const Type *end, TypeHandle type_handle = pvector_type_handle) : base_class(begin, end, allocator(type_handle)) { } - pvector(std::initializer_list init, TypeHandle type_handle = pvector_type_handle) : base_class(std::move(init), allocator(type_handle)) { } + explicit pvector(size_type n, TypeHandle type_handle = get_type_handle(pvector)) : base_class(n, Type(), allocator(type_handle)) { } + explicit pvector(size_type n, const Type &value, TypeHandle type_handle = get_type_handle(pvector)) : base_class(n, value, allocator(type_handle)) { } + pvector(const Type *begin, const Type *end, TypeHandle type_handle = get_type_handle(pvector)) : base_class(allocator(type_handle)) { + this->insert(this->end(), begin, end); + } + pvector(std::initializer_list init, TypeHandle type_handle = get_type_handle(pvector)) : base_class(std::move(init), allocator(type_handle)) { } pvector &operator =(const pvector ©) { base_class::operator =(copy); diff --git a/dtool/src/dtoolbase/register_type.cxx b/dtool/src/dtoolbase/register_type.cxx index b67475e4f6..649c4ebe11 100644 --- a/dtool/src/dtoolbase/register_type.cxx +++ b/dtool/src/dtoolbase/register_type.cxx @@ -26,6 +26,7 @@ TypeHandle double_type_handle; TypeHandle float_type_handle; TypeHandle string_type_handle; TypeHandle wstring_type_handle; +TypeHandle vector_uchar_type_handle; TypeHandle long_p_type_handle; TypeHandle int_p_type_handle; @@ -59,6 +60,7 @@ void init_system_type_handles() { register_type(float_type_handle, "float"); register_type(string_type_handle, "string"); register_type(wstring_type_handle, "wstring"); + register_type(vector_uchar_type_handle, "vector_uchar"); register_type(int_p_type_handle, "int*"); register_type(short_p_type_handle, "short*"); diff --git a/dtool/src/dtoolbase/register_type.h b/dtool/src/dtoolbase/register_type.h index a15cc9ddb1..95b2c48548 100644 --- a/dtool/src/dtoolbase/register_type.h +++ b/dtool/src/dtoolbase/register_type.h @@ -19,6 +19,9 @@ #include "typeHandle.h" #include "typeRegistry.h" +template +class pvector; + /** * This inline function is just a convenient way to call * TypeRegistry::register_type(), along with zero to four @@ -88,6 +91,7 @@ extern TypeHandle EXPCL_DTOOL_DTOOLBASE double_type_handle; extern TypeHandle EXPCL_DTOOL_DTOOLBASE float_type_handle; extern TypeHandle EXPCL_DTOOL_DTOOLBASE string_type_handle; extern TypeHandle EXPCL_DTOOL_DTOOLBASE wstring_type_handle; +extern TypeHandle EXPCL_DTOOL_DTOOLBASE vector_uchar_type_handle; extern TypeHandle long_p_type_handle; extern TypeHandle int_p_type_handle; @@ -175,6 +179,16 @@ INLINE TypeHandle _get_type_handle(const std::wstring *) { return wstring_type_handle; } +template +INLINE TypeHandle _get_type_handle(const pvector *) { + return pvector_type_handle; +} + +template<> +INLINE TypeHandle _get_type_handle(const pvector *) { + return vector_uchar_type_handle; +} + template<> INLINE TypeHandle _get_type_handle(const long * const *) { return long_p_type_handle; diff --git a/dtool/src/dtoolutil/CMakeLists.txt b/dtool/src/dtoolutil/CMakeLists.txt index 5885534178..fdab210e14 100644 --- a/dtool/src/dtoolutil/CMakeLists.txt +++ b/dtool/src/dtoolutil/CMakeLists.txt @@ -69,6 +69,8 @@ set(P3DTOOLUTIL_IGATEEXT globPattern_ext.h iostream_ext.cxx iostream_ext.h + pyenv_init.cxx + pyenv_init.h textEncoder_ext.cxx textEncoder_ext.h ) diff --git a/dtool/src/dtoolutil/console_preamble.js b/dtool/src/dtoolutil/console_preamble.js new file mode 100644 index 0000000000..151f5c1e03 --- /dev/null +++ b/dtool/src/dtoolutil/console_preamble.js @@ -0,0 +1,50 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file console_preamble.js + * @author rdb + * @date 2025-02-03 + */ + +if (ENVIRONMENT_IS_NODE) { + Module["preInit"] = Module["preInit"] || []; + Module["preInit"].push(function() { + if (typeof process === "object" && typeof process.env === "object") { + // These are made up by emscripten if we don't set them to undefined + ENV['USER'] = undefined; + ENV['LOGNAME'] = undefined; + ENV['PATH'] = undefined; + ENV['PWD'] = undefined; + ENV['HOME'] = undefined; + ENV['LANG'] = undefined; + ENV['_'] = undefined; + for (var variable in process.env) { + ENV[variable] = process.env[variable]; + } + } + + addOnPreMain(function preloadNodeEnv() { + var sp = stackSave(); + var set_binary_name = wasmExports["_set_binary_name"]; + if (set_binary_name && typeof __filename === "string") { + set_binary_name(stringToUTF8OnStack(__filename)); + } + + var set_env_var = wasmExports["_set_env_var"]; + if (set_env_var) { + for (var variable in ENV) { + var value = ENV[variable]; + if (value !== undefined) { + set_env_var(stringToUTF8OnStack(variable), stringToUTF8OnStack(value)); + } + } + } + stackRestore(sp); + }); + }); +} diff --git a/dtool/src/dtoolutil/executionEnvironment.cxx b/dtool/src/dtoolutil/executionEnvironment.cxx index 71083fa7ab..371a211612 100644 --- a/dtool/src/dtoolutil/executionEnvironment.cxx +++ b/dtool/src/dtoolutil/executionEnvironment.cxx @@ -125,12 +125,18 @@ static const char *const libp3dtool_filenames[] = { #if defined(__EMSCRIPTEN__) && !defined(CPPPARSER) extern "C" void EMSCRIPTEN_KEEPALIVE -_set_env_var(ExecutionEnvironment *ptr, const char *var, const char *value) { +_set_env_var(const char *var, const char *value) { + ExecutionEnvironment *ptr = ExecutionEnvironment::get_ptr(); ptr->_variables[std::string(var)] = std::string(value); } + +extern "C" void EMSCRIPTEN_KEEPALIVE +_set_binary_name(const char *path) { + ExecutionEnvironment::set_binary_name(std::string(path)); +} #endif -// Linux with GNU libc does have global argvargc variables, but we can't +// Linux with GNU libc does have global argv/argc variables, but we can't // safely access them at stat init time--at least, not in libc5. (It does seem // to work with glibc2, however.) @@ -584,17 +590,10 @@ read_environment_variables() { } } #elif defined(__EMSCRIPTEN__) - // We only have environment variables if we're running in node.js. -#ifndef CPPPARSER - EM_ASM({ - if (typeof process === 'object' && typeof process.env === 'object') { - for (var variable in process.env) { - __set_env_var($0, stringToUTF8OnStack(variable), - stringToUTF8OnStack(process.env[variable])); - } - } - }, this); -#endif + // The environment variables get loaded in by the .js file before main() + // using the _set_env_var exported function, defined above. Trying to load + // env vars at static init time otherwise makes some optimizations more + // difficult, notably wasm-ctor-eval/wizer. #elif defined(HAVE_PROC_SELF_ENVIRON) // In some cases, we may have a file called procselfenviron that may be read diff --git a/dtool/src/dtoolutil/executionEnvironment.h b/dtool/src/dtoolutil/executionEnvironment.h index e54f87c40f..ddb83fcecc 100644 --- a/dtool/src/dtoolutil/executionEnvironment.h +++ b/dtool/src/dtoolutil/executionEnvironment.h @@ -22,10 +22,10 @@ #include #if defined(__EMSCRIPTEN__) && !defined(CPPPARSER) -class ExecutionEnvironment; - extern "C" void EMSCRIPTEN_KEEPALIVE -_set_env_var(ExecutionEnvironment *ptr, const char *var, const char *value); +_set_env_var(const char *var, const char *value); +extern "C" void EMSCRIPTEN_KEEPALIVE +_set_binary_name(const char *path); #endif /** @@ -98,7 +98,7 @@ private: static ExecutionEnvironment *_global_ptr; #ifdef __EMSCRIPTEN__ - friend void ::_set_env_var(ExecutionEnvironment *ptr, const char *var, const char *value); + friend void ::_set_env_var(const char *var, const char *value); #endif }; diff --git a/dtool/src/dtoolutil/filename_ext.cxx b/dtool/src/dtoolutil/filename_ext.cxx index 12b85111d0..4a040708b1 100644 --- a/dtool/src/dtoolutil/filename_ext.cxx +++ b/dtool/src/dtoolutil/filename_ext.cxx @@ -67,7 +67,7 @@ __init__(PyObject *path) { return; } - PyObject *path_str = PyObject_CallFunctionObjArgs(fspath, path, nullptr); + PyObject *path_str = PyObject_CallOneArg(fspath, path); Py_DECREF(fspath); if (path_str == nullptr) { return; diff --git a/dtool/src/dtoolutil/iostream_ext.cxx b/dtool/src/dtoolutil/iostream_ext.cxx index 459f95c229..08272da20f 100644 --- a/dtool/src/dtoolutil/iostream_ext.cxx +++ b/dtool/src/dtoolutil/iostream_ext.cxx @@ -134,8 +134,9 @@ readinto(PyObject *b) { Py_buffer view; if (PyObject_GetBuffer(b, &view, PyBUF_CONTIG) == -1) { - PyErr_SetString(PyExc_TypeError, - "write() requires a contiguous, read-write bytes-like object"); + PyErr_Format(PyExc_TypeError, + "readinto() requires a contiguous, read-write bytes-like object, not '%.100s'", + Py_TYPE(b)->tp_name); return 0; } @@ -260,7 +261,9 @@ write(PyObject *b) { Py_buffer view; if (PyObject_GetBuffer(b, &view, PyBUF_CONTIG_RO) == -1) { - PyErr_SetString(PyExc_TypeError, "write() requires a contiguous buffer"); + PyErr_Format(PyExc_TypeError, + "write() requires a bytes-like object, not '%.100s'", + Py_TYPE(b)->tp_name); return; } diff --git a/dtool/src/dtoolutil/iostream_ext.h b/dtool/src/dtoolutil/iostream_ext.h index 54d3ab1a43..a3963d158d 100644 --- a/dtool/src/dtoolutil/iostream_ext.h +++ b/dtool/src/dtoolutil/iostream_ext.h @@ -22,6 +22,9 @@ #include #include "py_panda.h" +using std::ostream; +using std::istream; + /** * These classes define the extension methods for istream and ostream, which * are called instead of any C++ methods with the same prototype. diff --git a/dtool/src/dtoolutil/load_dso.cxx b/dtool/src/dtoolutil/load_dso.cxx index 95650b88c2..ea212009a7 100644 --- a/dtool/src/dtoolutil/load_dso.cxx +++ b/dtool/src/dtoolutil/load_dso.cxx @@ -19,11 +19,15 @@ using std::string; static Filename resolve_dso(const DSearchPath &path, const Filename &filename) { if (filename.is_local()) { if ((path.get_num_directories()==1)&&(path.get_directory(0)=="")) { +#ifdef ANDROID + return filename; +#else // This is a special case, meaning to search in the same directory in // which libp3dtool.dll, or the exe, was started from. Filename dtoolpath = ExecutionEnvironment::get_dtool_name(); DSearchPath spath(dtoolpath.get_dirname()); return spath.find_file(filename); +#endif } else { return path.find_file(filename); } @@ -119,7 +123,13 @@ get_dso_symbol(void *handle, const string &name) { void * load_dso(const DSearchPath &path, const Filename &filename) { Filename abspath = resolve_dso(path, filename); +#ifdef ANDROID + // We just try to load it on Android, because we can't verify right now + // whether it might just be an unextracted library. + if (abspath.empty()) { +#else if (!abspath.is_regular_file()) { +#endif // Make sure the error flag is cleared, to prevent a subsequent call to // load_dso_error() from returning a previously stored error. dlerror(); diff --git a/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx b/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx index ceb2f01bf1..3a8de69d4d 100644 --- a/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx +++ b/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx @@ -1,4 +1,5 @@ #include "filename_ext.cxx" #include "globPattern_ext.cxx" #include "iostream_ext.cxx" +#include "pyenv_init.cxx" #include "textEncoder_ext.cxx" diff --git a/dtool/src/dtoolutil/pyenv_init.cxx b/dtool/src/dtoolutil/pyenv_init.cxx new file mode 100644 index 0000000000..d806a8ef7c --- /dev/null +++ b/dtool/src/dtoolutil/pyenv_init.cxx @@ -0,0 +1,80 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pyenv_init.cxx + * @author rdb + * @date 2025-02-02 + */ + +#include "pyenv_init.h" +#include "py_panda.h" +#include "executionEnvironment.h" + +/** + * Called when panda3d.core is initialized, does some initialization specific + * to the Python environment. + */ +void +pyenv_init() { + // MAIN_DIR needs to be set very early; this seems like a convenient place + // to do that. Perhaps we'll find a better place for this in the future. + static bool initialized_main_dir = false; + if (!initialized_main_dir) { + /*if (interrogatedb_cat.is_debug()) { + // Good opportunity to print this out once, at startup. + interrogatedb_cat.debug() + << "Python " << version << "\n"; + }*/ + + if (!ExecutionEnvironment::has_environment_variable("MAIN_DIR")) { + // Grab the __main__ module and extract its __file__ attribute. + Filename main_dir; + PyObject *main_module = PyImport_ImportModule("__main__"); + PyObject *file_attr = nullptr; + if (main_module != nullptr) { + file_attr = PyObject_GetAttrString(main_module, "__file__"); + } else { + std::cerr << "Warning: unable to import __main__\n"; + } + if (file_attr == nullptr) { + // Must be running in the interactive interpreter. Use the CWD. + main_dir = ExecutionEnvironment::get_cwd(); + } else { +#if PY_MAJOR_VERSION >= 3 + Py_ssize_t length; + wchar_t *buffer = PyUnicode_AsWideCharString(file_attr, &length); + if (buffer != nullptr) { + main_dir = Filename::from_os_specific_w(std::wstring(buffer, length)); + main_dir.make_absolute(); + main_dir = main_dir.get_dirname(); + PyMem_Free(buffer); + } +#else + char *buffer; + Py_ssize_t length; + if (PyString_AsStringAndSize(file_attr, &buffer, &length) != -1) { + main_dir = Filename::from_os_specific(std::string(buffer, length)); + main_dir.make_absolute(); + main_dir = main_dir.get_dirname(); + } +#endif + else { + std::cerr << "Invalid string for __main__.__file__\n"; + } + } + ExecutionEnvironment::shadow_environment_variable("MAIN_DIR", main_dir.to_os_specific()); + PyErr_Clear(); + } + initialized_main_dir = true; + } + + // Also, while we are at it, initialize the thread swap hook. +#if defined(HAVE_THREADS) && defined(SIMPLE_THREADS) + global_thread_state_swap = PyThreadState_Swap; +#endif +} diff --git a/dtool/src/dtoolutil/pyenv_init.h b/dtool/src/dtoolutil/pyenv_init.h new file mode 100644 index 0000000000..2369ca1871 --- /dev/null +++ b/dtool/src/dtoolutil/pyenv_init.h @@ -0,0 +1,14 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pyenv_init.h + * @author rdb + * @date 2025-02-02 + */ + +extern "C" void pyenv_init(); diff --git a/dtool/src/dtoolutil/textEncoder_ext.cxx b/dtool/src/dtoolutil/textEncoder_ext.cxx index 4774286c5b..21faa26a67 100644 --- a/dtool/src/dtoolutil/textEncoder_ext.cxx +++ b/dtool/src/dtoolutil/textEncoder_ext.cxx @@ -94,7 +94,7 @@ encode_wchar(char32_t ch, TextEncoder::Encoding encoding) { * given encoding. */ PyObject *Extension:: -encode_wtext(const wstring &wtext, TextEncoder::Encoding encoding) { +encode_wtext(const std::wstring &wtext, TextEncoder::Encoding encoding) { std::string value = TextEncoder::encode_wtext(wtext, encoding); return PyBytes_FromStringAndSize((char *)value.data(), (Py_ssize_t)value.size()); } diff --git a/dtool/src/dtoolutil/vector_uchar.cxx b/dtool/src/dtoolutil/vector_uchar.cxx index 78750619bf..2087d20587 100644 --- a/dtool/src/dtoolutil/vector_uchar.cxx +++ b/dtool/src/dtoolutil/vector_uchar.cxx @@ -19,3 +19,25 @@ #define NAME vector_uchar #include "vector_src.cxx" + +/** + * Writes a hex representation of the bytes to the output stream. + */ +std::ostream & +operator << (std::ostream &out, const vector_uchar &data) { + static const char nibble_to_hex[] = "0123456789abcdef"; + + if (data.empty()) { + return out; + } + + out << nibble_to_hex[(data[0] & 0xf0) >> 4]; + out << nibble_to_hex[data[0] & 0xf]; + + for (size_t i = 1; i < data.size(); ++i) { + out << ' '; + out << nibble_to_hex[(data[i] & 0xf0) >> 4]; + out << nibble_to_hex[data[i] & 0xf]; + } + return out; +} diff --git a/dtool/src/dtoolutil/vector_uchar.h b/dtool/src/dtoolutil/vector_uchar.h index 3700b4ba8d..e04fd89289 100644 --- a/dtool/src/dtoolutil/vector_uchar.h +++ b/dtool/src/dtoolutil/vector_uchar.h @@ -30,4 +30,7 @@ #include "vector_src.h" +EXPCL_DTOOL_DTOOLUTIL std::ostream & +operator << (std::ostream &out, const vector_uchar &data); + #endif diff --git a/dtool/src/interrogatedb/CMakeLists.txt b/dtool/src/interrogatedb/CMakeLists.txt index f137a7b802..0457184feb 100644 --- a/dtool/src/interrogatedb/CMakeLists.txt +++ b/dtool/src/interrogatedb/CMakeLists.txt @@ -2,7 +2,6 @@ set(P3IGATERUNTIME_HEADERS interrogate_request.h py_compat.h py_panda.h py_panda.I - py_wrappers.h ) install(FILES ${P3IGATERUNTIME_HEADERS} COMPONENT CoreDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d) diff --git a/dtool/src/interrogatedb/interrogate_request.h b/dtool/src/interrogatedb/interrogate_request.h index 46a80b27b6..fdc2f093b8 100644 --- a/dtool/src/interrogatedb/interrogate_request.h +++ b/dtool/src/interrogatedb/interrogate_request.h @@ -44,12 +44,12 @@ EXPCL_INTERROGATEDB void interrogate_request_database(const char *database_filen /* The more sophisticated interface uses these structures. */ -typedef struct { +typedef struct InterrogateUniqueNameDef { const char *name; int index_offset; } InterrogateUniqueNameDef; -typedef struct { +typedef struct InterrogateModuleDef { int file_identifier; const char *library_name; diff --git a/dtool/src/interrogatedb/py_compat.h b/dtool/src/interrogatedb/py_compat.h index 70c8b43e91..bfcea61e76 100644 --- a/dtool/src/interrogatedb/py_compat.h +++ b/dtool/src/interrogatedb/py_compat.h @@ -219,6 +219,14 @@ INLINE PyObject *_PyLong_Lshift(PyObject *a, size_t shiftby) { } #endif +#ifndef PY_VECTORCALL_ARGUMENTS_OFFSET +# define PY_VECTORCALL_ARGUMENTS_OFFSET (((size_t)1) << (8 * sizeof(size_t) - 1)) +#endif + +#if PY_VERSION_HEX < 0x030800B1 +# define PyVectorcall_NARGS(n) ((n) & ~PY_VECTORCALL_ARGUMENTS_OFFSET) +#endif + /* Python 3.9 */ #if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE) @@ -229,6 +237,10 @@ INLINE PyObject *_PyLong_Lshift(PyObject *a, size_t shiftby) { # define PyCFunction_CheckExact(op) (Py_IS_TYPE(op, &PyCFunction_Type)) #endif +#if PY_VERSION_HEX < 0x030900A4 +# define PyObject_Vectorcall(callable, args, nargsf, kwnames) (_PyObject_Vectorcall((callable), (args), (nargsf), (kwnames))) +#endif + #if PY_VERSION_HEX < 0x03090000 INLINE PyObject *PyObject_CallNoArgs(PyObject *func) { #if PY_VERSION_HEX >= 0x03080000 @@ -368,6 +380,12 @@ PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result) { # define Py_END_CRITICAL_SECTION2() } #endif +/* Python 3.14 */ + +#if PY_VERSION_HEX < 0x030E00A8 +# define PyUnstable_Object_IsUniquelyReferenced(op) (Py_REFCNT((op)) == 1) +#endif + /* Other Python implementations */ #endif // HAVE_PYTHON diff --git a/dtool/src/interrogatedb/py_panda.I b/dtool/src/interrogatedb/py_panda.I index f3cb5a1d12..edfd0690f8 100644 --- a/dtool/src/interrogatedb/py_panda.I +++ b/dtool/src/interrogatedb/py_panda.I @@ -59,63 +59,6 @@ DtoolInstance_GetPointer(PyObject *self, T *&into, Dtool_PyTypedObject &target_c return false; } -/** - * Function to create a hash from a wrapped Python object. - */ -INLINE Py_hash_t DtoolInstance_HashPointer(PyObject *self) { - if (self != nullptr && DtoolInstance_Check(self)) { - return (Py_hash_t)(intptr_t)DtoolInstance_VOID_PTR(self); - } - return -1; -} - -/** - * Python 2-style comparison function that compares objects by pointer. - */ -INLINE int DtoolInstance_ComparePointers(PyObject *v1, PyObject *v2) { - void *v1_this = DtoolInstance_Check(v1) ? DtoolInstance_VOID_PTR(v1) : nullptr; - void *v2_this = DtoolInstance_Check(v2) ? DtoolInstance_VOID_PTR(v2) : nullptr; - if (v1_this != nullptr && v2_this != nullptr) { - return (v1_this > v2_this) - (v1_this < v2_this); - } else { - return (v1 > v2) - (v1 < v2); - } -} - -/** - * Rich comparison function that compares objects by pointer. - */ -INLINE PyObject *DtoolInstance_RichComparePointers(PyObject *v1, PyObject *v2, int op) { - int cmpval = DtoolInstance_ComparePointers(v1, v2); - Py_RETURN_RICHCOMPARE(cmpval, 0, op); -} - -/** - * Utility function for assigning a PyObject pointer while managing refcounts. - */ -ALWAYS_INLINE void -Dtool_Assign_PyObject(PyObject *&ptr, PyObject *value) { - PyObject *prev_value = ptr; - if (prev_value != value) { - ptr = Py_XNewRef(value); - Py_XDECREF(prev_value); - } -} - -/** - * Converts the enum value to a C long. - */ -INLINE long Dtool_EnumValue_AsLong(PyObject *value) { - PyObject *val = PyObject_GetAttrString(value, "value"); - if (val != nullptr) { - long as_long = PyLongOrInt_AS_LONG(val); - Py_DECREF(val); - return as_long; - } else { - return -1; - } -} - /** * These functions wrap a pointer for a class that defines get_type_handle(). */ @@ -167,23 +110,6 @@ DTool_PyInit_Finalize(PyObject *self, void *local_this, Dtool_PyTypedObject *typ return 0; } -/** - * Checks that the tuple is empty. - */ -ALWAYS_INLINE bool -Dtool_CheckNoArgs(PyObject *args) { - return PyTuple_GET_SIZE(args) == 0; -} - -/** - * Checks that the tuple is empty, and that the dict is empty or NULL. - */ -ALWAYS_INLINE bool -Dtool_CheckNoArgs(PyObject *args, PyObject *kwds) { - return PyTuple_GET_SIZE(args) == 0 && - (kwds == nullptr || PyDict_GET_SIZE(kwds) == 0); -} - /** * The following functions wrap an arbitrary C++ value into a PyObject. */ @@ -324,7 +250,8 @@ ALWAYS_INLINE PyObject *Dtool_WrapValue(PyObject *value) { return value; } -ALWAYS_INLINE PyObject *Dtool_WrapValue(const vector_uchar &value) { +template +ALWAYS_INLINE PyObject *Dtool_WrapValue(const std::vector &value) { #if PY_MAJOR_VERSION >= 3 return PyBytes_FromStringAndSize((char *)value.data(), (Py_ssize_t)value.size()); #else diff --git a/dtool/src/interrogatedb/py_panda.h b/dtool/src/interrogatedb/py_panda.h index d57af784f7..29a9cd4b28 100644 --- a/dtool/src/interrogatedb/py_panda.h +++ b/dtool/src/interrogatedb/py_panda.h @@ -8,15 +8,14 @@ #include #include #include +#include #ifdef USE_DEBUG_PYTHON #define Py_DEBUG #endif #include "pnotify.h" -#include "vector_uchar.h" #include "register_type.h" -#include "interrogate_request.h" #if defined(HAVE_PYTHON) && !defined(CPPPARSER) @@ -24,8 +23,6 @@ #include "py_compat.h" #include -using namespace std; - // this is tempory .. untill this is glued better into the panda build system #if defined(_WIN32) && !defined(LINK_ALL_STATIC) @@ -86,83 +83,6 @@ struct Dtool_PyTypedObject { CoerceFunction _Dtool_Coerce; }; -// This is now simply a forward declaration. The actual definition is created -// by the code generator. -#define Define_Dtool_Class(MODULE_NAME, CLASS_NAME, PUBLIC_NAME) \ - extern Dtool_PyTypedObject Dtool_##CLASS_NAME; - -// More Macro(s) to Implement class functions.. Usually used if C++ needs type -// information -#define Define_Dtool_new(CLASS_NAME,CNAME)\ -static PyObject *Dtool_new_##CLASS_NAME(PyTypeObject *type, PyObject *args, PyObject *kwds) {\ - (void) args; (void) kwds;\ - PyObject *self = type->tp_alloc(type, 0);\ - ((Dtool_PyInstDef *)self)->_signature = PY_PANDA_SIGNATURE;\ - ((Dtool_PyInstDef *)self)->_My_Type = &Dtool_##CLASS_NAME;\ - return self;\ -} - -// The following used to be in the above macro, but it doesn't seem to be -// necessary as tp_alloc memsets the object to 0. -// ((Dtool_PyInstDef *)self)->_ptr_to_object = NULL; -// ((Dtool_PyInstDef *)self)->_memory_rules = false; -// ((Dtool_PyInstDef *)self)->_is_const = false; - -// Delete functions.. -#ifdef NDEBUG -#define Define_Dtool_FreeInstance_Private(CLASS_NAME,CNAME)\ -static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\ - Py_TYPE(self)->tp_free(self);\ -} -#else // NDEBUG -#define Define_Dtool_FreeInstance_Private(CLASS_NAME,CNAME)\ -static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\ - if (DtoolInstance_VOID_PTR(self) != nullptr) {\ - if (((Dtool_PyInstDef *)self)->_memory_rules) {\ - std::cerr << "Detected leak for " << #CLASS_NAME \ - << " which interrogate cannot delete.\n"; \ - }\ - }\ - Py_TYPE(self)->tp_free(self);\ -} -#endif // NDEBUG - -#define Define_Dtool_FreeInstance(CLASS_NAME,CNAME)\ -static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\ - if (DtoolInstance_VOID_PTR(self) != nullptr) {\ - if (((Dtool_PyInstDef *)self)->_memory_rules) {\ - delete (CNAME *)DtoolInstance_VOID_PTR(self);\ - }\ - }\ - Py_TYPE(self)->tp_free(self);\ -} - -#define Define_Dtool_FreeInstanceRef(CLASS_NAME,CNAME)\ -static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\ - if (DtoolInstance_VOID_PTR(self) != nullptr) {\ - if (((Dtool_PyInstDef *)self)->_memory_rules) {\ - unref_delete((CNAME *)DtoolInstance_VOID_PTR(self));\ - }\ - }\ - Py_TYPE(self)->tp_free(self);\ -} - -#define Define_Dtool_FreeInstanceRef_Private(CLASS_NAME,CNAME)\ -static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\ - if (DtoolInstance_VOID_PTR(self) != nullptr) {\ - if (((Dtool_PyInstDef *)self)->_memory_rules) {\ - unref_delete((ReferenceCount *)(CNAME *)DtoolInstance_VOID_PTR(self));\ - }\ - }\ - Py_TYPE(self)->tp_free(self);\ -} - -#define Define_Dtool_Simple_FreeInstance(CLASS_NAME, CNAME)\ -static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\ - ((Dtool_InstDef_##CLASS_NAME *)self)->_value.~##CLASS_NAME();\ - Py_TYPE(self)->tp_free(self);\ -} - // Extract the PyTypeObject pointer corresponding to a Dtool_PyTypedObject. #define Dtool_GetPyTypeObject(type) (&(type)->_PyType) @@ -178,31 +98,12 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\ #define DtoolInstance_INIT_PTR(obj, ptr) { ((Dtool_PyInstDef *)obj)->_ptr_to_object = (void*)(ptr); } #define DtoolInstance_UPCAST(obj, type) (((Dtool_PyInstDef *)(obj))->_My_Type->_Dtool_UpcastInterface((obj), &(type))) -// ** HACK ** allert.. Need to keep a runtime type dictionary ... that is -// forward declared of typed object. We rely on the fact that typed objects -// are uniquly defined by an integer. - -#if PY_VERSION_HEX >= 0x030d0000 -class Dtool_TypeMap : public std::map { -public: - PyMutex _lock { 0 }; -}; -#else -typedef std::map Dtool_TypeMap; -#endif - -EXPCL_PYPANDA Dtool_TypeMap *Dtool_GetGlobalTypeMap(); - class DtoolProxy { public: mutable PyObject *_self; TypeHandle _type; }; -EXPCL_PYPANDA void DtoolProxy_Init(DtoolProxy *proxy, PyObject *self, - Dtool_PyTypedObject &classdef, - TypeRegistry::PythonWrapFunc *wrap_func); - /** */ @@ -218,23 +119,11 @@ EXPCL_PYPANDA bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_ template INLINE bool DtoolInstance_GetPointer(PyObject *self, T *&into); template INLINE bool DtoolInstance_GetPointer(PyObject *self, T *&into, Dtool_PyTypedObject &classdef); -INLINE Py_hash_t DtoolInstance_HashPointer(PyObject *self); -INLINE int DtoolInstance_ComparePointers(PyObject *v1, PyObject *v2); -INLINE PyObject *DtoolInstance_RichComparePointers(PyObject *v1, PyObject *v2, int op); - -// Functions related to error reporting. -EXPCL_PYPANDA bool _Dtool_CheckErrorOccurred(); - -#ifdef NDEBUG -#define Dtool_CheckErrorOccurred() (UNLIKELY(PyErr_Occurred() != nullptr)) -#else -#define Dtool_CheckErrorOccurred() (UNLIKELY(_Dtool_CheckErrorOccurred())) -#endif - EXPCL_PYPANDA PyObject *Dtool_Raise_AssertionError(); EXPCL_PYPANDA PyObject *Dtool_Raise_TypeError(const char *message); EXPCL_PYPANDA PyObject *Dtool_Raise_ArgTypeError(PyObject *obj, int param, const char *function_name, const char *type_name); EXPCL_PYPANDA PyObject *Dtool_Raise_AttributeError(PyObject *obj, const char *attribute); +EXPCL_PYPANDA int Dtool_Raise_CantDeleteAttributeError(const char *attribute); EXPCL_PYPANDA PyObject *_Dtool_Raise_BadArgumentsError(); EXPCL_PYPANDA PyObject *_Dtool_Raise_BadArgumentsError(const char *message); @@ -250,31 +139,6 @@ EXPCL_PYPANDA int _Dtool_Raise_BadArgumentsError_Int(const char *message); #define Dtool_Raise_BadArgumentsError_Int(x) _Dtool_Raise_BadArgumentsError_Int(x) #endif -// These functions are similar to Dtool_WrapValue, except that they also -// contain code for checking assertions and exceptions when compiling with -// NDEBUG mode on. -EXPCL_PYPANDA PyObject *_Dtool_Return_None(); -EXPCL_PYPANDA PyObject *Dtool_Return_Bool(bool value); -EXPCL_PYPANDA PyObject *_Dtool_Return(PyObject *value); - -#ifdef NDEBUG -#define Dtool_Return_None() (LIKELY(PyErr_Occurred() == nullptr) ? (Py_NewRef(Py_None)) : nullptr) -#define Dtool_Return(value) (LIKELY(PyErr_Occurred() == nullptr) ? value : nullptr) -#else -#define Dtool_Return_None() _Dtool_Return_None() -#define Dtool_Return(value) _Dtool_Return(value) -#endif - -ALWAYS_INLINE void Dtool_Assign_PyObject(PyObject *&ptr, PyObject *value); - -/** - * Wrapper around Python 3.4's enum library, which does not have a C API. - */ -EXPCL_PYPANDA PyTypeObject *Dtool_EnumType_Create(const char *name, PyObject *names, - const char *module = nullptr); -INLINE long Dtool_EnumValue_AsLong(PyObject *value); - - /** */ @@ -291,102 +155,9 @@ template INLINE PyObject *DTool_CreatePyInstance(T *obj, bool memory_ru template INLINE PyObject *DTool_CreatePyInstanceTyped(const T *obj, bool memory_rules); template INLINE PyObject *DTool_CreatePyInstanceTyped(T *obj, bool memory_rules); -// Macro(s) class definition .. Used to allocate storage and init some values -// for a Dtool Py Type object. - -// struct Dtool_PyTypedObject Dtool_##CLASS_NAME; - -#define Define_Module_Class_Internal(MODULE_NAME,CLASS_NAME,CNAME)\ -extern struct Dtool_PyTypedObject Dtool_##CLASS_NAME;\ -static int Dtool_Init_##CLASS_NAME(PyObject *self, PyObject *args, PyObject *kwds);\ -static PyObject *Dtool_new_##CLASS_NAME(PyTypeObject *type, PyObject *args, PyObject *kwds); - -#define Define_Module_Class(MODULE_NAME,CLASS_NAME,CNAME,PUBLIC_NAME)\ -Define_Module_Class_Internal(MODULE_NAME,CLASS_NAME,CNAME)\ -Define_Dtool_new(CLASS_NAME,CNAME)\ -Define_Dtool_FreeInstance(CLASS_NAME,CNAME)\ -Define_Dtool_Class(MODULE_NAME,CLASS_NAME,PUBLIC_NAME) - -#define Define_Module_Class_Private(MODULE_NAME,CLASS_NAME,CNAME,PUBLIC_NAME)\ -Define_Module_Class_Internal(MODULE_NAME,CLASS_NAME,CNAME)\ -Define_Dtool_new(CLASS_NAME,CNAME)\ -Define_Dtool_FreeInstance_Private(CLASS_NAME,CNAME)\ -Define_Dtool_Class(MODULE_NAME,CLASS_NAME,PUBLIC_NAME) - -#define Define_Module_ClassRef_Private(MODULE_NAME,CLASS_NAME,CNAME,PUBLIC_NAME)\ -Define_Module_Class_Internal(MODULE_NAME,CLASS_NAME,CNAME)\ -Define_Dtool_new(CLASS_NAME,CNAME)\ -Define_Dtool_FreeInstanceRef_Private(CLASS_NAME,CNAME)\ -Define_Dtool_Class(MODULE_NAME,CLASS_NAME,PUBLIC_NAME) - -#define Define_Module_ClassRef(MODULE_NAME,CLASS_NAME,CNAME,PUBLIC_NAME)\ -Define_Module_Class_Internal(MODULE_NAME,CLASS_NAME,CNAME)\ -Define_Dtool_new(CLASS_NAME,CNAME)\ -Define_Dtool_FreeInstanceRef(CLASS_NAME,CNAME)\ -Define_Dtool_Class(MODULE_NAME,CLASS_NAME,PUBLIC_NAME) - // The finalizer for simple instances. INLINE int DTool_PyInit_Finalize(PyObject *self, void *This, Dtool_PyTypedObject *type, bool memory_rules, bool is_const); -// A heler function to glu methed definition together .. that can not be done -// at code generation time becouse of multiple generation passes in -// interigate.. -typedef std::map MethodDefmap; - -// We need a way to runtime merge compile units into a python "Module" .. this -// is done with the fallowing structors and code.. along with the support of -// interigate_module - -struct Dtool_TypeDef { - const char *const name; - Dtool_PyTypedObject *type; -}; - -struct LibraryDef { - PyMethodDef *const _methods; - const Dtool_TypeDef *const _types; - Dtool_TypeDef *const _external_types; - const InterrogateModuleDef *const _module_def; -}; - -#if PY_MAJOR_VERSION >= 3 -EXPCL_PYPANDA PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], PyModuleDef *module_def); -#else -EXPCL_PYPANDA PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], const char *modulename); -#endif - -// HACK.... Be carefull Dtool_BorrowThisReference This function can be used to -// grab the "THIS" pointer from an object and use it Required to support fom -// historical inharatence in the for of "is this instance of".. -EXPCL_PYPANDA PyObject *Dtool_BorrowThisReference(PyObject *self, PyObject *args); - -#define DTOOL_PyObject_HashPointer DtoolInstance_HashPointer -#define DTOOL_PyObject_ComparePointers DtoolInstance_ComparePointers - -EXPCL_PYPANDA PyObject * -copy_from_make_copy(PyObject *self, PyObject *noargs); - -EXPCL_PYPANDA PyObject * -copy_from_copy_constructor(PyObject *self, PyObject *noargs); - -EXPCL_PYPANDA PyObject * -map_deepcopy_to_copy(PyObject *self, PyObject *args); - -/** - * These functions check whether the arguments passed to a function conform to - * certain expectations. - */ -ALWAYS_INLINE bool Dtool_CheckNoArgs(PyObject *args); -ALWAYS_INLINE bool Dtool_CheckNoArgs(PyObject *args, PyObject *kwds); -EXPCL_PYPANDA bool Dtool_ExtractArg(PyObject **result, PyObject *args, - PyObject *kwds, const char *keyword); -EXPCL_PYPANDA bool Dtool_ExtractArg(PyObject **result, PyObject *args, - PyObject *kwds); -EXPCL_PYPANDA bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, - PyObject *kwds, const char *keyword); -EXPCL_PYPANDA bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, - PyObject *kwds); - /** * These functions convert a C++ value into the corresponding Python object. * This used to be generated by the code generator, but it seems more reliable @@ -411,7 +182,8 @@ ALWAYS_INLINE PyObject *Dtool_WrapValue(char value); ALWAYS_INLINE PyObject *Dtool_WrapValue(wchar_t value); ALWAYS_INLINE PyObject *Dtool_WrapValue(std::nullptr_t); ALWAYS_INLINE PyObject *Dtool_WrapValue(PyObject *value); -ALWAYS_INLINE PyObject *Dtool_WrapValue(const vector_uchar &value); +template +ALWAYS_INLINE PyObject *Dtool_WrapValue(const std::vector &value); #if PY_MAJOR_VERSION >= 0x02060000 ALWAYS_INLINE PyObject *Dtool_WrapValue(Py_buffer *value); @@ -422,9 +194,12 @@ ALWAYS_INLINE PyObject *Dtool_WrapValue(const std::pair &value); EXPCL_PYPANDA Dtool_PyTypedObject *Dtool_GetSuperBase(); -#include "py_panda.I" +/** + * Creates a Python generator object from a next() function. + */ +EXPCL_PYPANDA PyObject *Dtool_NewGenerator(PyObject *self, iternextfunc func); -#include "py_wrappers.h" +#include "py_panda.I" #endif // HAVE_PYTHON && !CPPPARSER diff --git a/dtool/src/interrogatedb/py_wrappers.h b/dtool/src/interrogatedb/py_wrappers.h deleted file mode 100644 index eb5658cb6b..0000000000 --- a/dtool/src/interrogatedb/py_wrappers.h +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @file py_wrappers.h - * @author rdb - * @date 2017-11-26 - */ - -#ifndef PY_WRAPPERS_H -#define PY_WRAPPERS_H - -#include "py_panda.h" - -#ifdef HAVE_PYTHON - -/** - * These classes are returned from properties that require a subscript - * interface, ie. something.children[i] = 3. - */ -struct Dtool_WrapperBase { - PyObject_HEAD; - PyObject *_self; - const char *_name; -}; - -struct Dtool_SequenceWrapper { - Dtool_WrapperBase _base; - lenfunc _len_func; - ssizeargfunc _getitem_func; -}; - -struct Dtool_MutableSequenceWrapper { - Dtool_WrapperBase _base; - lenfunc _len_func; - ssizeargfunc _getitem_func; - ssizeobjargproc _setitem_func; - PyObject *(*_insert_func)(PyObject *, size_t, PyObject *); -}; - -struct Dtool_MappingWrapper { - union { - Dtool_WrapperBase _base; - Dtool_SequenceWrapper _keys; - }; - binaryfunc _getitem_func; - objobjargproc _setitem_func; -}; - -struct Dtool_GeneratorWrapper { - Dtool_WrapperBase _base; - iternextfunc _iternext_func; -}; - -EXPCL_PYPANDA Dtool_SequenceWrapper *Dtool_NewSequenceWrapper(PyObject *self, const char *name); -EXPCL_PYPANDA Dtool_MutableSequenceWrapper *Dtool_NewMutableSequenceWrapper(PyObject *self, const char *name); -EXPCL_PYPANDA Dtool_MappingWrapper *Dtool_NewMappingWrapper(PyObject *self, const char *name); -EXPCL_PYPANDA Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObject *self, const char *name); -EXPCL_PYPANDA PyObject *Dtool_NewGenerator(PyObject *self, iternextfunc func); -EXPCL_PYPANDA PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset); - -#endif // HAVE_PYTHON - -#endif // PY_WRAPPERS_H diff --git a/dtool/src/parser-inc/DbgHelp.h b/dtool/src/parser-inc/DbgHelp.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dtool/src/parser-inc/Python.h b/dtool/src/parser-inc/Python.h index ec9ad04008..f3e76bb568 100644 --- a/dtool/src/parser-inc/Python.h +++ b/dtool/src/parser-inc/Python.h @@ -52,4 +52,9 @@ PyObject _Py_FalseStruct; typedef void *visitproc; +typedef enum { + PyRefTracer_CREATE = 0, + PyRefTracer_DESTROY = 1, +} PyRefTracerEvent; + #endif // PYTHON_H diff --git a/dtool/src/parser-inc/emmintrin.h b/dtool/src/parser-inc/emmintrin.h new file mode 100644 index 0000000000..6589323c8a --- /dev/null +++ b/dtool/src/parser-inc/emmintrin.h @@ -0,0 +1 @@ +#include diff --git a/dtool/src/parser-inc/random b/dtool/src/parser-inc/random new file mode 100644 index 0000000000..e976462950 --- /dev/null +++ b/dtool/src/parser-inc/random @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace std { + class random_device; + class seed_seq; + + template class uniform_int_distribution; + template class uniform_real_distribution; + class bernoulli_distribution; + template class binomial_distribution; + template class geometric_distribution; + template class negative_binomial_distribution; + template class poisson_distribution; + template class exponential_distribution; + template class gamma_distribution; + template class weibull_distribution; + template class extreme_value_distribution; + template class normal_distribution; + template class lognormal_distribution; + template class chi_squared_distribution; + template class cauchy_distribution; + template class fisher_f_distribution; + template class student_t_distribution; + template class discrete_distribution; + template class piecewise_constant_distribution; + template class piecewise_linear_distribution; +} diff --git a/dtool/src/prc/configPageManager.cxx b/dtool/src/prc/configPageManager.cxx index 47c206e720..e3dbcfa69b 100644 --- a/dtool/src/prc/configPageManager.cxx +++ b/dtool/src/prc/configPageManager.cxx @@ -97,7 +97,6 @@ reload_implicit_pages() { } _implicit_pages.clear(); -#ifndef ANDROID // If we are running inside a deployed application, see if it exposes // information about how the PRC data should be initialized. struct BlobInfo { @@ -129,11 +128,13 @@ reload_implicit_pages() { // const BlobInfo *blobinfo = (const BlobInfo *)dlsym(RTLD_SELF, "blobinfo"); #elif defined(__EMSCRIPTEN__) const BlobInfo *blobinfo = nullptr; +#elif defined(ANDROID) + const BlobInfo *blobinfo = nullptr; #else const BlobInfo *blobinfo = (const BlobInfo *)dlsym(dlopen(nullptr, RTLD_NOW), "blobinfo"); #endif if (blobinfo == nullptr) { -#if !defined(_WIN32) && !defined(__EMSCRIPTEN__) +#if !defined(_WIN32) && !defined(__EMSCRIPTEN__) && !defined(ANDROID) // Clear the error flag. dlerror(); #endif @@ -482,7 +483,6 @@ reload_implicit_pages() { } } } -#endif // ANDROID if (!_loaded_implicit) { config_initialized(); @@ -492,6 +492,21 @@ reload_implicit_pages() { _currently_loading = false; invalidate_cache(); + // These important variables are updated here to avoid recursion, since + // they may be accessed by the PRC system. + ConfigVariableBool notify_timestamp + ("notify-timestamp", false, + PRC_DESC("Set true to output the date & time with each notify message.")); + NotifyCategory::_notify_timestamp = notify_timestamp; + +#ifndef NDEBUG + ConfigVariableBool check_debug_notify_protect + ("check-debug-notify-protect", false, + PRC_DESC("Set true to issue a warning message if a debug or spam " + "notify output is not protected within an if statement.")); + NotifyCategory::_check_debug_notify_protect = check_debug_notify_protect; +#endif + #ifdef USE_PANDAFILESTREAM // Update this very low-level config variable here, for lack of any better // place. diff --git a/dtool/src/prc/notify.cxx b/dtool/src/prc/notify.cxx index ce7df151c6..e3615b583a 100644 --- a/dtool/src/prc/notify.cxx +++ b/dtool/src/prc/notify.cxx @@ -27,6 +27,7 @@ #endif #ifdef ANDROID +#include #include #include "androidLogStream.h" #endif @@ -35,6 +36,18 @@ #include "emscriptenLogStream.h" #endif +#ifndef NDEBUG +#ifdef PHAVE_EXECINFO_H +#include // for backtrace() +#include +#include +#endif + +#ifdef _WIN32 +#include +#endif +#endif // NDEBUG + using std::cerr; using std::cout; using std::ostream; @@ -348,15 +361,16 @@ assert_failure(const char *expression, int line, << expression << " at line " << line << " of " << source_file; string message = message_str.str(); - if (!_assert_failed) { + Notify *self = ptr(); + if (!self->_assert_failed) { // We only save the first assertion failure message, as this is usually // the most meaningful when several occur in a row. - _assert_failed = true; - _assert_error_message = message; + self->_assert_failed = true; + self->_assert_error_message = message; } - if (has_assert_handler()) { - return (*_assert_handler)(expression, line, source_file); + if (self->has_assert_handler()) { + return (*self->_assert_handler)(expression, line, source_file); } #ifdef ANDROID @@ -380,6 +394,33 @@ assert_failure(const char *expression, int line, // Make sure the error message has been flushed to the output. nout.flush(); + // Capture and list a stack trace. +#ifdef NDEBUG +#elif defined(PHAVE_EXECINFO_H) + void *trace[64]; + int size = backtrace(trace, 64); + if (size > 0) { + // Remove the frame(s) corresponding to the current function. + void **tracep = trace; + void *return_addr = __builtin_return_address(0); + for (int i = 0; i < size; ++i) { + if (trace[i] == return_addr) { + tracep = trace + (i + 1); + size -= i + 1; + break; + } + } + write_backtrace(tracep, size); + } +#elif defined(_WIN32) + const ULONG max_size = 62; + void *trace[max_size]; + int size = CaptureStackBackTrace(1, max_size, trace, nullptr); + if (size > 0) { + write_backtrace(trace, size); + } +#endif + #ifdef _MSC_VER // How to trigger an exception in VC++ that offers to take us into the // debugger? abort() doesn't do it. We used to be able to assert(false), @@ -411,6 +452,141 @@ assert_failure(const char *expression, int line, return true; } +/** + * + */ +void Notify:: +write_backtrace(void **trace, int size) { + std::ostream &out = nout; + +#ifdef NDEBUG +#elif defined(PHAVE_EXECINFO_H) + char namebuf[128]; + + for (int i = 0; i < size; ++i) { + void *addr = trace[i]; + Dl_info info; + if (dladdr((char *)addr - 1, &info) != 0) { + const char *name = nullptr; + + if (info.dli_sname != nullptr) { + int status = 0; + size_t size = 0; + char *demangled = nullptr; + if (info.dli_sname[0] == '_') { + size = sizeof(namebuf) - 1; + demangled = abi::__cxa_demangle(info.dli_sname, namebuf, &size, &status); + } + if (status == 0 && demangled != nullptr) { + namebuf[size] = 0; + name = demangled; + //if (strncmp(name, "Notify::assert_failure", 22) == 0) { + //continue; + //} + } else { + name = info.dli_sname; + } + } + + out << "[" << addr << "] "; + + if (info.dli_fname != nullptr) { + const char *slash = strrchr(info.dli_fname, '/'); + out << std::left << std::setw(30) << (slash ? slash + 1 : info.dli_fname) << " "; + } + + if (name != nullptr) { + out << name; + } else { + out << info.dli_saddr; + } + + int offset = reinterpret_cast(addr) - reinterpret_cast(info.dli_saddr); + if (offset > 0) { + out << " + " << offset; + } + out << "\n"; + } else { + out << "[" << addr << "]\n"; + } + } +#elif defined(_WIN32) + HMODULE handle = LoadLibraryA("dbghelp.dll"); + if (!handle) { + return; + } + + auto pSymInitialize = (BOOL (WINAPI *)(HANDLE, PCSTR, BOOL))GetProcAddress(handle, "SymInitialize"); + auto pSymCleanup = (BOOL (WINAPI *)(HANDLE))GetProcAddress(handle, "SymCleanup"); + auto pSymFromAddr = (BOOL (WINAPI *)(HANDLE, DWORD64, DWORD64 *, PSYMBOL_INFO))GetProcAddress(handle, "SymFromAddr"); + auto pSymGetModuleInfo64 = (BOOL (WINAPI *)(HANDLE, DWORD64, PIMAGEHLP_MODULE64))GetProcAddress(handle, "SymGetModuleInfo64"); + auto pSymGetLineFromAddr64 = (BOOL (WINAPI *)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64))GetProcAddress(handle, "SymGetLineFromAddr64"); + if (!pSymInitialize || !pSymCleanup || !pSymFromAddr) { + FreeLibrary(handle); + return; + } + + HANDLE process = GetCurrentProcess(); + pSymInitialize(process, nullptr, TRUE); + + for (int i = 0; i < size; ++i) { + DWORD64 addr = (DWORD64)trace[i]; + + alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + 256] = {0}; + SYMBOL_INFO *symbol = (SYMBOL_INFO *)buffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = 255; + + out << "[" << (void *)addr << "]"; + + // Show the name of the library. + IMAGEHLP_MODULE64 module; + module.SizeOfStruct = sizeof(module); + const char *basename = "???"; + if (pSymGetModuleInfo64 && pSymGetModuleInfo64(process, addr, &module)) { + const char *slash = strrchr(module.ImageName, '\\'); + basename = (slash ? slash + 1 : module.ImageName); + } + + // Look up the symbol name. If the reported name comes from the export + // table and the address is way off, ignore it, it's probably not right. + if (pSymFromAddr(process, addr, nullptr, symbol) && + ((symbol->Flags & SYMFLAG_EXPORT) == 0 || addr - (DWORD64)symbol->Address < 0x4000)) { + + out << " " << std::left << std::setw(30) << basename << " " << symbol->Name; + + DWORD64 offset = addr - (DWORD64)(symbol->Address); + if (offset > 0) { + out << " + " << offset; + } + + // Look up filename + line number if available. + IMAGEHLP_LINE64 line; + DWORD displacement = 0; + line.SizeOfStruct = sizeof(line); + if (pSymGetLineFromAddr64(process, addr, &displacement, &line)) { + const char *slash = strrchr(line.FileName, '\\'); + const char *basename = (slash ? slash + 1 : line.FileName); + + out << " (" << basename << ":" << line.LineNumber; + //if (displacement > 0) { + // out << " + " << displacement; + //} + out << ")"; + } + } else { + out << " " << basename; + } + out << "\n"; + } + + pSymCleanup(process); + FreeLibrary(handle); +#endif + + out.flush(); +} + /** * Given a string, one of "debug", "info", "warning", etc., return the * corresponding Severity level, or NS_unspecified if none of the strings @@ -460,22 +636,7 @@ config_initialized() { // notify-output even after the initial import of Panda3D modules. However, // it cannot be changed after the first time it is set. -#if defined(ANDROID) - // Android redirects stdio and stderr to /dev/null, - // but does provide its own logging system. We use a special - // type of stream that redirects it to Android's log system. - - Notify *ptr = Notify::ptr(); - - for (int severity = 0; severity <= NS_fatal; ++severity) { - int priority = ANDROID_LOG_UNKNOWN; - if (severity != NS_unspecified) { - priority = severity + 1; - } - ptr->_log_streams[severity] = new AndroidLogStream(priority); - } - -#elif defined(__EMSCRIPTEN__) +#if defined(__EMSCRIPTEN__) // We have no writable filesystem in JavaScript. Instead, we set up a // special stream that logs straight into the Javascript console. @@ -540,11 +701,38 @@ config_initialized() { } #endif // BUILD_IPHONE } + #ifdef ANDROID + for (int severity = 0; severity <= NS_fatal; ++severity) { + ptr->_log_streams[severity] = ptr->_ostream_ptr; + } + } else { - // By default, we always redirect the notify stream to the Android log. + // By default, we always redirect the notify stream to the Android log, + // except if we are running from the adb shell. We decide this based + // on whether stderr is redirected to /dev/null. Notify *ptr = Notify::ptr(); - ptr->set_ostream_ptr(new AndroidLogStream(ANDROID_LOG_INFO), true); + struct stat a, b; + if (fstat(STDERR_FILENO, &a) == 0 && stat("/dev/null", &b) == 0 && + a.st_dev == b.st_dev && a.st_ino == b.st_ino) { + // Android redirects stdio and stderr to /dev/null, + // but does provide its own logging system. We use a special + // type of stream that redirects it to Android's log system. + for (int severity = 0; severity <= NS_fatal; ++severity) { + int priority = ANDROID_LOG_UNKNOWN; + if (severity != NS_unspecified) { + priority = severity + 1; + } + ptr->_log_streams[severity] = new AndroidLogStream(priority); + } + ptr->set_ostream_ptr(new AndroidLogStream(ANDROID_LOG_INFO), true); + } else { + // Running from the terminal, set all the log streams to point to the + // same output. + for (int severity = 0; severity <= NS_fatal; ++severity) { + ptr->_log_streams[severity] = &cerr; + } + } #endif } } diff --git a/dtool/src/prc/notifyCategory.cxx b/dtool/src/prc/notifyCategory.cxx index 17d2309ab8..3a0b161bf2 100644 --- a/dtool/src/prc/notifyCategory.cxx +++ b/dtool/src/prc/notifyCategory.cxx @@ -21,6 +21,9 @@ #include // for strftime(). #include +bool NotifyCategory::_notify_timestamp = false; +bool NotifyCategory::_check_debug_notify_protect = false; + long NotifyCategory::_server_delta = 0; /** @@ -55,7 +58,7 @@ std::ostream &NotifyCategory:: out(NotifySeverity severity, bool prefix) const { if (is_on(severity)) { if (prefix) { - if (get_notify_timestamp()) { + if (_notify_timestamp) { // Format a timestamp to include as a prefix as well. time_t now = time(nullptr) + _server_delta; struct tm atm; @@ -79,7 +82,7 @@ out(NotifySeverity severity, bool prefix) const { return Notify::out(severity); } - } else if (severity <= NS_debug && get_check_debug_notify_protect()) { + } else if (severity <= NS_debug && _check_debug_notify_protect) { // Someone issued a debug Notify output statement without protecting it // within an if statement. This can cause a significant runtime // performance hit, since it forces the iostream library to fully format @@ -172,36 +175,3 @@ update_severity_cache() { mark_cache_valid(_local_modified); } - -/** - * Returns the value of the notify-timestamp ConfigVariable. This is defined - * using a method accessor rather than a static ConfigVariableBool, to protect - * against the variable needing to be accessed at static init time. - */ -bool NotifyCategory:: -get_notify_timestamp() { - static ConfigVariableBool *notify_timestamp = nullptr; - if (notify_timestamp == nullptr) { - notify_timestamp = new ConfigVariableBool - ("notify-timestamp", false, - "Set true to output the date & time with each notify message."); - } - return *notify_timestamp; -} - -/** - * Returns the value of the check-debug-notify-protect ConfigVariable. This - * is defined using a method accessor rather than a static ConfigVariableBool, - * to protect against the variable needing to be accessed at static init time. - */ -bool NotifyCategory:: -get_check_debug_notify_protect() { - static ConfigVariableBool *check_debug_notify_protect = nullptr; - if (check_debug_notify_protect == nullptr) { - check_debug_notify_protect = new ConfigVariableBool - ("check-debug-notify-protect", false, - "Set true to issue a warning message if a debug or spam " - "notify output is not protected within an if statement."); - } - return *check_debug_notify_protect; -} diff --git a/dtool/src/prc/notifyCategory.h b/dtool/src/prc/notifyCategory.h index 96d2897921..1a0c82a37e 100644 --- a/dtool/src/prc/notifyCategory.h +++ b/dtool/src/prc/notifyCategory.h @@ -76,8 +76,6 @@ PUBLISHED: private: std::string get_config_name() const; void update_severity_cache(); - static bool get_notify_timestamp(); - static bool get_check_debug_notify_protect(); std::string _fullname; std::string _basename; @@ -86,12 +84,16 @@ private: typedef std::vector Children; Children _children; + static bool _notify_timestamp; + static bool _check_debug_notify_protect; + static long _server_delta; // not a time_t because server delta may be signed. AtomicAdjust::Integer _local_modified; NotifySeverity _severity_cache; friend class Notify; + friend class ConfigPageManager; }; INLINE std::ostream &operator << (std::ostream &out, const NotifyCategory &cat); diff --git a/dtool/src/prc/pnotify.h b/dtool/src/prc/pnotify.h index a4c9a6aca2..2c949f1e1a 100644 --- a/dtool/src/prc/pnotify.h +++ b/dtool/src/prc/pnotify.h @@ -76,10 +76,12 @@ PUBLISHED: public: static ios_fmtflags get_literal_flag(); - bool assert_failure(const std::string &expression, int line, - const char *source_file); - bool assert_failure(const char *expression, int line, - const char *source_file); + static bool assert_failure(const std::string &expression, int line, + const char *source_file); + static bool assert_failure(const char *expression, int line, + const char *source_file); + + static void write_backtrace(void **trace, int size); static NotifySeverity string_severity(const std::string &string); @@ -100,7 +102,7 @@ private: Categories _categories; #if defined(ANDROID) - AndroidLogStream *_log_streams[NS_fatal + 1]; + std::ostream *_log_streams[NS_fatal + 1]; #elif defined(__EMSCRIPTEN__) EmscriptenLogStream *_log_streams[NS_fatal + 1]; #endif @@ -204,7 +206,7 @@ private: #define nassertr(condition, return_value) \ { \ if (_nassert_check(condition)) { \ - if (Notify::ptr()->assert_failure(#condition, __LINE__, __FILE__)) { \ + if (Notify::assert_failure(#condition, __LINE__, __FILE__)) { \ return return_value; \ } \ } \ @@ -213,7 +215,7 @@ private: #define nassertv(condition) \ { \ if (_nassert_check(condition)) { \ - if (Notify::ptr()->assert_failure(#condition, __LINE__, __FILE__)) { \ + if (Notify::assert_failure(#condition, __LINE__, __FILE__)) { \ return; \ } \ } \ @@ -221,12 +223,12 @@ private: #define nassertd(condition) \ if (_nassert_check(condition) && \ - Notify::ptr()->assert_failure(#condition, __LINE__, __FILE__)) + Notify::assert_failure(#condition, __LINE__, __FILE__)) #define nassertr_always(condition, return_value) nassertr(condition, return_value) #define nassertv_always(condition) nassertv(condition) -#define nassert_raise(message) Notify::ptr()->assert_failure(message, __LINE__, __FILE__) +#define nassert_raise(message) Notify::assert_failure(message, __LINE__, __FILE__) #endif // NDEBUG diff --git a/dtool/src/prckeys/makePrcKey.cxx b/dtool/src/prckeys/makePrcKey.cxx index fea188c87c..f6598cab22 100644 --- a/dtool/src/prckeys/makePrcKey.cxx +++ b/dtool/src/prckeys/makePrcKey.cxx @@ -114,6 +114,56 @@ output_c_string(std::ostream &out, const string &string_name, */ EVP_PKEY * generate_key() { +#if OPENSSL_VERSION_MAJOR >= 3 + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr); + if (ctx == nullptr) { + EVP_PKEY_CTX_free(ctx); + output_ssl_errors(); + exit(1); + } + + if (EVP_PKEY_keygen_init(ctx) <= 0 || + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 1024) <= 0) { + EVP_PKEY_CTX_free(ctx); + output_ssl_errors(); + exit(1); + } + + BIGNUM *e = BN_new(); + if (e == nullptr || !BN_set_word(e, 7)) { + BN_free(e); + EVP_PKEY_CTX_free(ctx); + output_ssl_errors(); + exit(1); + } + + unsigned char *e_buf = nullptr; + int e_len = BN_num_bytes(e); + e_buf = (unsigned char *)OPENSSL_malloc(e_len); + BN_bn2bin(e, e_buf); + + size_t bits = 1024; + OSSL_PARAM params[] = { + OSSL_PARAM_construct_size_t("bits", &bits), + OSSL_PARAM_construct_BN("e", e_buf, e_len), + OSSL_PARAM_END + }; + + EVP_PKEY *pkey = nullptr; + if (EVP_PKEY_CTX_set_params(ctx, params) <= 0 || + EVP_PKEY_generate(ctx, &pkey) <= 0) { + OPENSSL_free(e_buf); + BN_free(e); + EVP_PKEY_CTX_free(ctx); + output_ssl_errors(); + exit(1); + } + + OPENSSL_free(e_buf); + BN_free(e); + EVP_PKEY_CTX_free(ctx); + return pkey; +#else RSA *rsa = RSA_new(); BIGNUM *e = BN_new(); if (rsa == nullptr || e == nullptr) { @@ -134,6 +184,7 @@ generate_key() { EVP_PKEY *pkey = EVP_PKEY_new(); EVP_PKEY_assign_RSA(pkey, rsa); return pkey; +#endif } /** diff --git a/makepanda/installer.nsi b/makepanda/installer.nsi index 7fc47bc9dd..f5bc41e01a 100644 --- a/makepanda/installer.nsi +++ b/makepanda/installer.nsi @@ -383,6 +383,7 @@ SectionGroup "Python modules" SecGroupPython !insertmacro PyBindingSection 3.12-32 .cp312-win32.pyd !insertmacro PyBindingSection 3.13-32 .cp313-win32.pyd !insertmacro PyBindingSection 3.14-32 .cp314-win32.pyd + !insertmacro PyBindingSection 3.15-32 .cp315-win32.pyd !else !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd @@ -394,6 +395,7 @@ SectionGroup "Python modules" SecGroupPython !insertmacro PyBindingSection 3.12 .cp312-win_amd64.pyd !insertmacro PyBindingSection 3.13 .cp313-win_amd64.pyd !insertmacro PyBindingSection 3.14 .cp314-win_amd64.pyd + !insertmacro PyBindingSection 3.15 .cp315-win_amd64.pyd !endif SectionGroupEnd @@ -506,6 +508,7 @@ Function .onInit !insertmacro MaybeEnablePyBindingSection 3.12-32 !insertmacro MaybeEnablePyBindingSection 3.13-32 !insertmacro MaybeEnablePyBindingSection 3.14-32 + !insertmacro MaybeEnablePyBindingSection 3.15-32 ${EndIf} !else !insertmacro MaybeEnablePyBindingSection 3.5 @@ -519,6 +522,7 @@ Function .onInit !insertmacro MaybeEnablePyBindingSection 3.12 !insertmacro MaybeEnablePyBindingSection 3.13 !insertmacro MaybeEnablePyBindingSection 3.14 + !insertmacro MaybeEnablePyBindingSection 3.15 ${EndIf} !endif @@ -548,6 +552,10 @@ Function .onInit SectionSetFlags ${SecPyBindings3.14} ${SF_RO} SectionSetInstTypes ${SecPyBindings3.14} 0 !endif + !ifdef SecPyBindings3.15 + SectionSetFlags ${SecPyBindings3.15} ${SF_RO} + SectionSetInstTypes ${SecPyBindings3.15} 0 + !endif ${EndUnless} FunctionEnd @@ -853,6 +861,7 @@ Section Uninstall !insertmacro RemovePythonPath 3.12-32 !insertmacro RemovePythonPath 3.13-32 !insertmacro RemovePythonPath 3.14-32 + !insertmacro RemovePythonPath 3.15-32 !else !insertmacro RemovePythonPath 3.5 !insertmacro RemovePythonPath 3.6 @@ -864,6 +873,7 @@ Section Uninstall !insertmacro RemovePythonPath 3.12 !insertmacro RemovePythonPath 3.13 !insertmacro RemovePythonPath 3.14 + !insertmacro RemovePythonPath 3.15 !endif SetDetailsPrint both @@ -936,6 +946,7 @@ SectionEnd !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.12-32} $(DESC_SecPyBindings3.12-32) !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.13-32} $(DESC_SecPyBindings3.13-32) !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.14-32} $(DESC_SecPyBindings3.14-32) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.15-32} $(DESC_SecPyBindings3.15-32) !else !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5) !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6) @@ -947,6 +958,7 @@ SectionEnd !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.12} $(DESC_SecPyBindings3.12) !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.13} $(DESC_SecPyBindings3.13) !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.14} $(DESC_SecPyBindings3.14) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.15} $(DESC_SecPyBindings3.15) !endif !ifdef INCLUDE_PYVER !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython) diff --git a/makepanda/makepackage.py b/makepanda/makepackage.py index a46684d7d0..0aff2cd8d3 100755 --- a/makepanda/makepackage.py +++ b/makepanda/makepackage.py @@ -335,8 +335,8 @@ def MakeInstallerLinux(version, debversion=None, rpmversion=None, rpmrelease=1, # Library dependencies are required, binary dependencies are recommended. # We explicitly exclude libphysx-extras since we don't want to depend on PhysX. - oscmd(f"cd targetroot && LD_LIBRARY_PATH=usr/{lib_dir}/panda3d {dpkg_shlibdeps} -Tdebian/substvars_dep --ignore-missing-info -x{pkg_name} -xlibphysx-extras {lib_pattern}") - oscmd(f"cd targetroot && LD_LIBRARY_PATH=usr/{lib_dir}/panda3d {dpkg_shlibdeps} -Tdebian/substvars_rec --ignore-missing-info -x{pkg_name} {bin_pattern}") + oscmd(f"cd targetroot && {dpkg_shlibdeps} -lusr/{lib_dir}/panda3d -Tdebian/substvars_dep --ignore-missing-info -x{pkg_name} -xlibphysx-extras {lib_pattern}") + oscmd(f"cd targetroot && {dpkg_shlibdeps} -lusr/{lib_dir}/panda3d -Tdebian/substvars_rec --ignore-missing-info -x{pkg_name} {bin_pattern}") # Parse the substvars files generated by dpkg-shlibdeps. depends = ReadFile("targetroot/debian/substvars_dep").replace("shlibs:Depends=", "").strip() diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 3d3ae9e6f1..ef61e39171 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -312,7 +312,7 @@ def parseopts(args): if GetTarget() == 'windows': if not MSVC_VERSION: print("No MSVC version specified. Defaulting to 14.1 (Visual Studio 2017).") - MSVC_VERSION = (14, 1) + MSVC_VERSION = (14, 3) else: try: MSVC_VERSION = tuple(int(d) for d in MSVC_VERSION.split('.'))[:2] @@ -487,6 +487,8 @@ elif not CrossCompiling(): else: if target_arch == 'amd64': target_arch = 'x86_64' + if target_arch == 'arm' and target == 'android': + target_arch = 'armv7a' PLATFORM = '{0}-{1}'.format(target, target_arch) @@ -1074,7 +1076,6 @@ if (COMPILER=="GCC"): LibName("ALWAYS", "-framework AppKit") LibName("IOKIT", "-framework IOKit") LibName("QUARTZ", "-framework Quartz") - LibName("AGL", "-framework AGL") LibName("CARBON", "-framework Carbon") LibName("COCOA", "-framework Cocoa") # Fix for a bug in OSX Leopard: @@ -1376,19 +1377,19 @@ def CompileCxx(obj,src,opts): cmd += ' -gcc-toolchain ' + SDK["ANDROID_GCC_TOOLCHAIN"].replace('\\', '/') cmd += ' -ffunction-sections -funwind-tables' cmd += ' -target ' + SDK["ANDROID_TRIPLE"] - if arch == 'armv7a': + if arch in ('armv7a', 'arm'): cmd += ' -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16' - elif arch == 'arm': - cmd += ' -march=armv5te -mtune=xscale -msoft-float' + #elif arch == 'arm': + # cmd += ' -march=armv5te -mtune=xscale -msoft-float' elif arch == 'mips': cmd += ' -mips32' elif arch == 'mips64': cmd += ' -fintegrated-as' elif arch == 'x86': - cmd += ' -march=i686 -mssse3 -mfpmath=sse -m32' + cmd += ' -march=i686 -mssse3 -mfpmath=sse' cmd += ' -mstackrealign' elif arch == 'x86_64': - cmd += ' -march=x86-64 -msse4.2 -mpopcnt -m64' + cmd += ' -march=x86-64 -msse4.2 -mpopcnt' cmd += " -Wa,--noexecstack" @@ -1431,7 +1432,7 @@ def CompileCxx(obj,src,opts): if optlevel >= 4 or target == "android": cmd += " -fno-rtti" - if ('SSE2' in opts or not PkgSkip("SSE2")) and not arch.startswith("arm") and arch != 'aarch64': + if ('SSE2' in opts or not PkgSkip("SSE2")) and arch.find('86') > 0: if GetTarget() != "emscripten": cmd += " -msse2" @@ -1654,6 +1655,9 @@ def CompileImod(wobj, wsrc, opts): importmod = GetValueOption(opts, "IMPORT:") if importmod: cmd += ' -import ' + importmod + initfunc = GetValueOption(opts, "INIT:") + if initfunc: + cmd += ' -init ' + initfunc for x in wsrc: cmd += ' ' + BracketNameWithQuotes(x) oscmd(cmd) CompileCxx(wobj,woutc,opts) @@ -1902,17 +1906,26 @@ def CompileLink(dll, obj, opts): cmd += ' -gcc-toolchain ' + SDK["ANDROID_GCC_TOOLCHAIN"].replace('\\', '/') cmd += " -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" cmd += ' -target ' + SDK["ANDROID_TRIPLE"] - if arch == 'armv7a': + if arch in ('armv7a', 'arm'): cmd += " -march=armv7-a -Wl,--fix-cortex-a8" elif arch == 'mips': cmd += ' -mips32' + + if arch.endswith('64'): + # See https://developer.android.com/guide/practices/page-sizes + cmd += ' -Wl,-z,max-page-size=16384' + cmd += ' -lc -lm' elif GetTarget() == 'emscripten': - cmd += " -s WARN_ON_UNDEFINED_SYMBOLS=1" + cmd += " -s WARN_ON_UNDEFINED_SYMBOLS=1 -mbulk-memory" + if GetOrigExt(dll) == ".exe": cmd += " -s EXIT_RUNTIME=1" + if dll.endswith(".js") and "SUBSYSTEM:WINDOWS" not in opts: + cmd += " --pre-js dtool/src/dtoolutil/console_preamble.js" + else: cmd += " -pthread" if "SYSROOT" in SDK: @@ -2420,6 +2433,7 @@ DTOOL_CONFIG=[ ("PHAVE_DIRENT_H", 'UNDEF', '1'), ("PHAVE_UCONTEXT_H", 'UNDEF', '1'), ("PHAVE_STDINT_H", '1', '1'), + ("PHAVE_EXECINFO_H", 'UNDEF', '1'), ("HAVE_RTTI", '1', '1'), ("HAVE_X11", 'UNDEF', '1'), ("IS_LINUX", 'UNDEF', '1'), @@ -2556,6 +2570,7 @@ def WriteConfigSettings(): dtool_config["PHAVE_GLOB_H"] = 'UNDEF' dtool_config["PHAVE_LOCKF"] = 'UNDEF' dtool_config["HAVE_VIDEO4LINUX"] = 'UNDEF' + dtool_config["PHAVE_EXECINFO_H"] = 'UNDEF' if (GetTarget() == "emscripten"): # There are no threads in JavaScript, so don't bother using them. @@ -2568,6 +2583,7 @@ def WriteConfigSettings(): dtool_config["PHAVE_LINUX_INPUT_H"] = 'UNDEF' dtool_config["HAVE_X11"] = 'UNDEF' dtool_config["HAVE_GLX"] = 'UNDEF' + dtool_config["PHAVE_EXECINFO_H"] = 'UNDEF' # There are no environment vars either, or default prc files. prc_parameters["DEFAULT_PRC_DIR"] = 'UNDEF' @@ -4051,7 +4067,9 @@ TargetAdd('libp3pgui.in', opts=['IMOD:panda3d.core', 'ILIB:libp3pgui', 'SRCDIR:p # DIRECTORY: panda/src/pnmimagetypes/ # -OPTS=['DIR:panda/src/pnmimagetypes', 'DIR:panda/src/pnmimage', 'BUILDING:PANDA', 'PNG', 'ZLIB', 'JPEG', 'TIFF', 'OPENEXR', 'EXCEPTIONS'] +OPTS=['DIR:panda/src/pnmimagetypes', 'DIR:panda/src/pnmimage', 'BUILDING:PANDA', 'PNG', 'ZLIB', 'JPEG', 'TIFF', 'OPENEXR'] +if not PkgSkip('OPENEXR') and GetTarget() != 'emscripten': + OPTS.append('EXCEPTIONS') TargetAdd('p3pnmimagetypes_composite1.obj', opts=OPTS, input='p3pnmimagetypes_composite1.cxx') TargetAdd('p3pnmimagetypes_composite2.obj', opts=OPTS, input='p3pnmimagetypes_composite2.cxx') @@ -4195,7 +4213,7 @@ if GetTarget() != "emscripten": if PkgSkip("FREETYPE")==0: PyTargetAdd('core_module.obj', input='libp3pnmtext.in') -PyTargetAdd('core_module.obj', opts=['IMOD:panda3d.core', 'ILIB:core']) +PyTargetAdd('core_module.obj', opts=['IMOD:panda3d.core', 'ILIB:core', 'INIT:pyenv_init']) PyTargetAdd('core.pyd', input='libp3dtoolbase_igate.obj') PyTargetAdd('core.pyd', input='p3dtoolbase_typeHandle_ext.obj') @@ -4956,13 +4974,17 @@ if GetTarget() == 'android': TargetAdd('org/panda3d/android/NativeOStream.class', opts=OPTS, input='NativeOStream.java') TargetAdd('org/panda3d/android/PandaActivity.class', opts=OPTS, input='PandaActivity.java') TargetAdd('org/panda3d/android/PandaActivity$1.class', opts=OPTS+['DEPENDENCYONLY'], input='PandaActivity.java') + TargetAdd('org/panda3d/android/PandaActivity$2.class', opts=OPTS+['DEPENDENCYONLY'], input='PandaActivity.java') TargetAdd('org/panda3d/android/PythonActivity.class', opts=OPTS, input='PythonActivity.java') + TargetAdd('org/panda3d/android/PythonActivity$ActivityResultListener.class', opts=OPTS+['DEPENDENCYONLY'], input='PythonActivity.java') TargetAdd('classes.dex', input='org/panda3d/android/NativeIStream.class') TargetAdd('classes.dex', input='org/panda3d/android/NativeOStream.class') TargetAdd('classes.dex', input='org/panda3d/android/PandaActivity.class') TargetAdd('classes.dex', input='org/panda3d/android/PandaActivity$1.class') + TargetAdd('classes.dex', input='org/panda3d/android/PandaActivity$2.class') TargetAdd('classes.dex', input='org/panda3d/android/PythonActivity.class') + TargetAdd('classes.dex', input='org/panda3d/android/PythonActivity$ActivityResultListener.class') TargetAdd('p3android_composite1.obj', opts=OPTS, input='p3android_composite1.cxx') TargetAdd('libp3android.dll', input='p3android_composite1.obj') @@ -5454,6 +5476,19 @@ if not PkgSkip("PANDATOOL"): TargetAdd('egg2bam.exe', input=COMMON_EGG2X_LIBS) TargetAdd('egg2bam.exe', opts=['ADVAPI', 'FFTW']) +# +# DIRECTORY: pandatool/src/converter/ +# + +if not PkgSkip("PANDATOOL"): + OPTS=['DIR:pandatool/src/converter'] + TargetAdd('txo-converter_txoConverter.obj', opts=OPTS, input='txoConverter.cxx') + TargetAdd('txo-converter.exe', input='txo-converter_txoConverter.obj') + TargetAdd('txo-converter.exe', input='libp3progbase.lib') + TargetAdd('txo-converter.exe', input='libp3pandatoolbase.lib') + TargetAdd('txo-converter.exe', input=COMMON_PANDA_LIBS) + TargetAdd('txo-converter.exe', opts=['ADVAPI', 'FFTW']) + # # DIRECTORY: pandatool/src/daeegg/ # @@ -6149,10 +6184,10 @@ if PkgSkip("PYTHON") == 0: TargetAdd('classes.dex', input='org/jnius/NativeInvocationHandler.class') PyTargetAdd('deploy-stubw_android_main.obj', opts=OPTS, input='android_main.cxx') - PyTargetAdd('deploy-stubw_android_log.obj', opts=OPTS, input='android_log.c') + PyTargetAdd('deploy-stubw_android_support.obj', opts=OPTS, input='android_support.cxx') PyTargetAdd('libdeploy-stubw.dll', input='android_native_app_glue.obj') PyTargetAdd('libdeploy-stubw.dll', input='deploy-stubw_android_main.obj') - PyTargetAdd('libdeploy-stubw.dll', input='deploy-stubw_android_log.obj') + PyTargetAdd('libdeploy-stubw.dll', input='deploy-stubw_android_support.obj') PyTargetAdd('libdeploy-stubw.dll', input=COMMON_PANDA_LIBS) PyTargetAdd('libdeploy-stubw.dll', input='libp3android.dll') PyTargetAdd('libdeploy-stubw.dll', opts=['DEPLOYSTUB', 'ANDROID']) @@ -6160,7 +6195,7 @@ if PkgSkip("PYTHON") == 0: # # Build the test runner for static builds # -if GetLinkAllStatic(): +if GetLinkAllStatic() or GetTarget() == 'android': if GetTarget() == 'emscripten': LinkFlag('RUN_TESTS_FLAGS', '-s NODERAWFS') LinkFlag('RUN_TESTS_FLAGS', '-s ASSERTIONS=2') @@ -6178,18 +6213,19 @@ if GetLinkAllStatic(): if not PkgSkip('BULLET'): DefSymbol('RUN_TESTS_FLAGS', 'HAVE_BULLET') - OPTS=['DIR:tests', 'PYTHON', 'RUN_TESTS_FLAGS'] + OPTS=['DIR:tests', 'PYTHON', 'RUN_TESTS_FLAGS', 'SUBSYSTEM:CONSOLE'] PyTargetAdd('run_tests-main.obj', opts=OPTS, input='main.c') PyTargetAdd('run_tests.exe', input='run_tests-main.obj') - PyTargetAdd('run_tests.exe', input='core.pyd') - if not PkgSkip('DIRECT'): - PyTargetAdd('run_tests.exe', input='direct.pyd') - if not PkgSkip('PANDAPHYSICS'): - PyTargetAdd('run_tests.exe', input='physics.pyd') - if not PkgSkip('EGG'): - PyTargetAdd('run_tests.exe', input='egg.pyd') - if not PkgSkip('BULLET'): - PyTargetAdd('run_tests.exe', input='bullet.pyd') + if GetLinkAllStatic(): + PyTargetAdd('run_tests.exe', input='core.pyd') + if not PkgSkip('DIRECT'): + PyTargetAdd('run_tests.exe', input='direct.pyd') + if not PkgSkip('PANDAPHYSICS'): + PyTargetAdd('run_tests.exe', input='physics.pyd') + if not PkgSkip('EGG'): + PyTargetAdd('run_tests.exe', input='egg.pyd') + if not PkgSkip('BULLET'): + PyTargetAdd('run_tests.exe', input='bullet.pyd') PyTargetAdd('run_tests.exe', input=COMMON_PANDA_LIBS) PyTargetAdd('run_tests.exe', opts=['PYTHON', 'BULLET', 'RUN_TESTS_FLAGS']) diff --git a/makepanda/makepandacore.py b/makepanda/makepandacore.py index f34b847dec..6e6503a10d 100644 --- a/makepanda/makepandacore.py +++ b/makepanda/makepandacore.py @@ -284,7 +284,7 @@ def GetHost(): return 'darwin' elif sys.platform.startswith('linux'): try: - # Python seems to offer no built-in way to check this. + # Python versions before 3.13 reported android as "linux" osname = subprocess.check_output(["uname", "-o"]) if osname.strip().lower() == b'android': return 'android' @@ -294,6 +294,9 @@ def GetHost(): return 'linux' elif sys.platform.startswith('freebsd'): return 'freebsd' + # Handle for Python >= 3.13 + elif sys.platform == 'android': + return 'android' else: exit('Unrecognized sys.platform: %s' % (sys.platform)) @@ -357,11 +360,11 @@ def SetTarget(target, arch=None): elif target == 'android' or target.startswith('android-'): if arch is None: - # If compiling on Android, default to same architecture. Otherwise, arm. + # If compiling on Android, default to same architecture. if host == 'android': arch = host_arch else: - arch = 'armv7a' + exit('Specify an Android architecture using --arch') if arch == 'aarch64': arch = 'arm64' @@ -371,12 +374,9 @@ def SetTarget(target, arch=None): target, _, api = target.partition('-') if api: ANDROID_API = int(api) - elif arch in ('mips64', 'arm64', 'x86_64'): - # 64-bit platforms were introduced in Android 21. - ANDROID_API = 21 else: # Default to the lowest API level still supported by Google. - ANDROID_API = 19 + ANDROID_API = 21 # Determine the prefix for our gcc tools, eg. arm-linux-androideabi-gcc global ANDROID_ABI, ANDROID_TRIPLE @@ -592,8 +592,8 @@ def GetInterrogateDir(): return INTERROGATE_DIR dir = os.path.join(GetOutputDir(), "tmp", "interrogate") - if not os.path.isdir(os.path.join(dir, "panda3d_interrogate-0.4.0.dist-info")): - oscmd("\"%s\" -m pip install --force-reinstall --upgrade -t \"%s\" panda3d-interrogate==0.4.0" % (sys.executable, dir)) + if not os.path.isdir(os.path.join(dir, "panda3d_interrogate-0.8.1.dist-info")): + oscmd("\"%s\" -m pip install --force-reinstall --upgrade -t \"%s\" panda3d-interrogate==0.8.1" % (sys.executable, dir)) INTERROGATE_DIR = dir @@ -669,11 +669,12 @@ def oscmd(cmd, ignoreError = False, cwd=None): print(GetColor("blue") + cmd.split(" ", 1)[0] + " " + GetColor("magenta") + cmd.split(" ", 1)[1] + GetColor()) sys.stdout.flush() + if cmd[0] == '"': + exe = cmd[1 : cmd.index('"', 1)] + else: + exe = cmd.split()[0] + if sys.platform == "win32": - if cmd[0] == '"': - exe = cmd[1 : cmd.index('"', 1)] - else: - exe = cmd.split()[0] exe_path = LocateBinary(exe) if exe_path is None: exit("Cannot find "+exe+" on search path") @@ -709,7 +710,7 @@ def oscmd(cmd, ignoreError = False, cwd=None): exit("") if res != 0 and not ignoreError: - if "interrogate" in cmd.split(" ", 1)[0] and GetVerbose(): + if "interrogate" in exe and "interrogate_module" not in exe and GetVerbose(): print(ColorText("red", "Interrogate failed, retrieving debug output...")) sys.stdout.flush() verbose_cmd = cmd.split(" ", 1)[0] + " -vv " + cmd.split(" ", 1)[1] @@ -1422,6 +1423,9 @@ def GetThirdpartyDir(): elif (target == 'android'): THIRDPARTYDIR = base + "/android-libs-%s/" % (target_arch) + if target_arch == 'armv7a' and not os.path.isdir(THIRDPARTYDIR): + THIRDPARTYDIR = base + "/android-libs-arm/" + elif (target == 'emscripten'): THIRDPARTYDIR = base + "/emscripten-libs/" @@ -2479,7 +2483,7 @@ def SdkLocateMacOSX(archs = []): # Prefer pre-10.14 for now so that we can keep building FMOD. sdk_versions += ["10.13", "10.12"] - sdk_versions += ["14.0", "13.3", "13.1", "13.0", "12.3", "11.3", "11.1", "11.0"] + sdk_versions += ["15.5", "15.4", "15.2", "15.1", "15.0", "14.5", "14.4", "14.2", "14.0", "13.3", "13.1", "13.0", "12.3", "11.3", "11.1", "11.0"] if 'arm64' not in archs: sdk_versions += ["10.15", "10.14"] @@ -2488,16 +2492,20 @@ def SdkLocateMacOSX(archs = []): sdkname = "MacOSX" + version if os.path.exists("/Library/Developer/CommandLineTools/SDKs/%s.sdk" % sdkname): SDK["MACOSX"] = "/Library/Developer/CommandLineTools/SDKs/%s.sdk" % sdkname - return elif os.path.exists("/Developer/SDKs/%s.sdk" % sdkname): SDK["MACOSX"] = "/Developer/SDKs/%s.sdk" % sdkname - return elif os.path.exists("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % sdkname): SDK["MACOSX"] = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % sdkname - return elif xcode_dir and os.path.exists("%s/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % (xcode_dir, sdkname)): SDK["MACOSX"] = "%s/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % (xcode_dir, sdkname) - return + else: + continue + + if GetVerbose(): + print("Using macOS %s SDK located at %s" % (version, SDK["MACOSX"])) + else: + print("Using macOS %s SDK" % version) + return exit("Couldn't find any suitable MacOSX SDK!") @@ -3030,6 +3038,13 @@ def SetupBuildEnvironment(compiler): libdir += '64' SYS_LIB_DIRS += [libdir] + if sys.platform != "darwin": + # Some Linux distributions (eg. Fedora) don't add /usr/local/lib64 + if ("/usr/lib64" in SYS_LIB_DIRS and + "/usr/local/lib64" not in SYS_LIB_DIRS and + os.path.isdir("/usr/local/lib64")): + SYS_LIB_DIRS.append("/usr/local/lib64") + # Now extract the preprocessor's include directories. cmd = GetCXX() + " -x c++ -v -E " + os.devnull cmd += sysroot_flag @@ -3428,6 +3443,10 @@ def GetExtensionSuffix(): abi = GetPythonABI() arch = GetTargetArch() return '.{0}-{1}-emscripten.so'.format(abi, arch) + elif target == 'android': + abi = GetPythonABI() + triple = ANDROID_TRIPLE.rstrip('0123456789') + return '.{0}-{1}.so'.format(abi, triple) elif CrossCompiling(): return '.{0}.so'.format(GetPythonABI()) else: diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index 1ccbc618b0..f88e1fd7e3 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -88,7 +88,9 @@ MANYLINUX_LIBS = [ # These are not mentioned in manylinux1 spec but should nonetheless always # be excluded. "linux-vdso.so.1", "linux-gate.so.1", "ld-linux.so.2", "libdrm.so.2", + "ld-linux-x86-64.so.2", "ld-linux-aarch64.so.1", "libEGL.so.1", "libOpenGL.so.0", "libGLX.so.0", "libGLdispatch.so.0", + "libGLESv2.so.2", ] # Binaries to never scan for dependencies on non-Windows systems. diff --git a/mypy.ini b/mypy.ini index 85a2d94031..696e4894b0 100644 --- a/mypy.ini +++ b/mypy.ini @@ -11,3 +11,6 @@ ignore_missing_imports = True [mypy-Pmw.*] ignore_missing_imports = True + +[mypy-imp] +ignore_missing_imports = True diff --git a/panda/CMakeLists.txt b/panda/CMakeLists.txt index cb33932d9c..e118a8a5b9 100644 --- a/panda/CMakeLists.txt +++ b/panda/CMakeLists.txt @@ -107,7 +107,7 @@ if(HAVE_FREETYPE) endif() if(INTERROGATE_PYTHON_INTERFACE) - add_python_module(panda3d.core ${CORE_MODULE_COMPONENTS} LINK panda) + add_python_module(panda3d.core ${CORE_MODULE_COMPONENTS} LINK panda INIT pyenv_init) # Generate our __init__.py if(WIN32) diff --git a/panda/src/android/PandaActivity.java b/panda/src/android/PandaActivity.java index d9b390373f..6c59886be4 100644 --- a/panda/src/android/PandaActivity.java +++ b/panda/src/android/PandaActivity.java @@ -17,7 +17,9 @@ import android.app.NativeActivity; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.res.AssetFileDescriptor; import android.net.Uri; +import android.os.ParcelFileDescriptor; import android.widget.Toast; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -25,6 +27,8 @@ import dalvik.system.BaseDexClassLoader; import org.panda3d.android.NativeIStream; import org.panda3d.android.NativeOStream; +import android.util.Log; + /** * The entry point for a Panda-based activity. Loads the Panda libraries and * also provides some utility functions. @@ -77,6 +81,44 @@ public class PandaActivity extends NativeActivity { return Thread.currentThread().getName(); } + /** + * Called by android_native_app_glue to spawn the application thread. + * Gets passed a function pointer and a data pointer to pass to it. + */ + protected void spawnAppThread(long ptr, long data) { + new Thread(() -> { + nativeThreadEntry(ptr, data); + }).start(); + } + + /** + * Maps the blob to memory and returns the pointer. + */ + public long mapBlobFromResource(long offset) { + int resourceId = 0; + try { + ActivityInfo ai = getPackageManager().getActivityInfo( + getIntent().getComponent(), PackageManager.GET_META_DATA); + if (ai.metaData == null) { + Log.e("Panda3D", "Failed to get activity metadata"); + return 0; + } + resourceId = ai.metaData.getInt("org.panda3d.android.BLOB_RESOURCE"); + if (resourceId == 0) { + return 0; + } + + AssetFileDescriptor afd = getResources().openRawResourceFd(resourceId); + ParcelFileDescriptor pfd = afd.getParcelFileDescriptor(); + long off = afd.getStartOffset() + offset; + long len = afd.getLength(); + return nativeMmap(pfd.getFd(), off, len); + } catch (Exception e) { + Log.e("Panda3D", "Received exception while trying to map blob: " + e); + return 0; + } + } + /** * Returns the path to the main native library. */ @@ -97,6 +139,14 @@ public class PandaActivity extends NativeActivity { return classLoader.findLibrary(libname); } + /** + * Returns the path to some other native library. + */ + public String findLibrary(String libname) { + BaseDexClassLoader classLoader = (BaseDexClassLoader)getClassLoader(); + return classLoader.findLibrary(libname); + } + public String getIntentDataPath() { Intent intent = getIntent(); Uri data = intent.getData(); @@ -119,6 +169,18 @@ public class PandaActivity extends NativeActivity { return getCacheDir().toString(); } + /** + * Sets the window title. + */ + public void setWindowTitle(final CharSequence title) { + final PandaActivity activity = this; + runOnUiThread(new Runnable() { + public void run() { + activity.setTitle(title); + } + }); + } + /** * Shows a pop-up notification. */ @@ -139,4 +201,7 @@ public class PandaActivity extends NativeActivity { // Contains our JNI calls. System.loadLibrary("p3android"); } + + private static native long nativeMmap(int fd, long off, long len); + private static native void nativeThreadEntry(long ptr, long data); } diff --git a/panda/src/android/PythonActivity.java b/panda/src/android/PythonActivity.java index 731ed4d54c..68b4720be8 100644 --- a/panda/src/android/PythonActivity.java +++ b/panda/src/android/PythonActivity.java @@ -15,6 +15,12 @@ package org.panda3d.android; import org.panda3d.android.PandaActivity; +import android.content.Intent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + /** * Extends PandaActivity with some things that are useful in a Python * application. @@ -26,4 +32,39 @@ public class PythonActivity extends PandaActivity { public PythonActivity() { mActivity = this; } + + // Helper code to further support plyer. + public interface ActivityResultListener { + void onActivityResult(int requestCode, int resultCode, Intent data); + } + + private List activityResultListeners = null; + + public void registerActivityResultListener(ActivityResultListener listener) { + if (this.activityResultListeners == null) { + this.activityResultListeners = Collections.synchronizedList(new ArrayList()); + } + this.activityResultListeners.add(listener); + } + + public void unregisterActivityResultListener(ActivityResultListener listener) { + if (this.activityResultListeners == null) { + return; + } + this.activityResultListeners.remove(listener); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + if (this.activityResultListeners == null) { + return; + } + this.onResume(); + synchronized (this.activityResultListeners) { + Iterator iterator = this.activityResultListeners.iterator(); + while (iterator.hasNext()) { + iterator.next().onActivityResult(requestCode, resultCode, intent); + } + } + } } diff --git a/panda/src/android/android_main.cxx b/panda/src/android/android_main.cxx index 55355533c3..177b9ec43d 100644 --- a/panda/src/android/android_main.cxx +++ b/panda/src/android/android_main.cxx @@ -271,10 +271,9 @@ void android_main(struct android_app* app) { // We still need to keep an event loop going until Android gives us leave // to end the process. while (!app->destroyRequested) { - int looper_id; - struct android_poll_source *source; - auto result = ALooper_pollOnce(-1, &looper_id, nullptr, (void **)&source); - if (looper_id == LOOPER_ID_MAIN) { + struct android_poll_source *source = nullptr; + int ident = ALooper_pollOnce(-1, nullptr, nullptr, (void **)&source); + if (ident == LOOPER_ID_MAIN) { int8_t cmd = android_app_read_cmd(app); android_app_pre_exec_cmd(app, cmd); android_app_post_exec_cmd(app, cmd); diff --git a/panda/src/android/android_native_app_glue.c b/panda/src/android/android_native_app_glue.c index 1e63c5ef59..38aff2df51 100644 --- a/panda/src/android/android_native_app_glue.c +++ b/panda/src/android/android_native_app_glue.c @@ -255,10 +255,19 @@ static struct android_app* android_app_create(ANativeActivity* activity, android_app->msgread = msgpipe[0]; android_app->msgwrite = msgpipe[1]; + // Spawn the app thread in Java so it gets a valid class loader. + JNIEnv *env = activity->env; + jclass activity_class = (*env)->GetObjectClass(env, activity->clazz); + jmethodID method = (*env)->GetMethodID(env, activity_class, "spawnAppThread", "(JJ)V"); + LOGE("got function pointer: %ld, data: %ld, env: %ld", (jlong)(void *)android_app_entry, (jlong)(void *)android_app, (jlong)(void *)env); + (*env)->CallVoidMethod(env, activity->clazz, method, (void *)android_app_entry, (void *)android_app); + + /* pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&android_app->thread, &attr, android_app_entry, android_app); + */ // Wait for thread to start. pthread_mutex_lock(&android_app->mutex); diff --git a/panda/src/android/android_native_app_glue.h b/panda/src/android/android_native_app_glue.h index 89eafe3ff9..7e4149e229 100644 --- a/panda/src/android/android_native_app_glue.h +++ b/panda/src/android/android_native_app_glue.h @@ -168,7 +168,7 @@ struct android_app { int msgread; int msgwrite; - pthread_t thread; + //pthread_t thread; struct android_poll_source cmdPollSource; struct android_poll_source inputPollSource; diff --git a/panda/src/android/config_android.cxx b/panda/src/android/config_android.cxx index 083cd7c192..75b2d42a8a 100644 --- a/panda/src/android/config_android.cxx +++ b/panda/src/android/config_android.cxx @@ -27,6 +27,8 @@ jmethodID jni_PandaActivity_readBitmap; jmethodID jni_PandaActivity_createBitmap; jmethodID jni_PandaActivity_compressBitmap; jmethodID jni_PandaActivity_showToast; +jmethodID jni_PandaActivity_setWindowTitle; +jmethodID jni_PandaActivity_findLibrary; jclass jni_BitmapFactory_Options; jfieldID jni_BitmapFactory_Options_outWidth; @@ -77,9 +79,15 @@ jint JNI_OnLoad(JavaVM *jvm, void *reserved) { jni_PandaActivity_compressBitmap = env->GetStaticMethodID(jni_PandaActivity, "compressBitmap", "(Landroid/graphics/Bitmap;IIJ)Z"); + jni_PandaActivity_setWindowTitle = env->GetMethodID(jni_PandaActivity, + "setWindowTitle", "(Ljava/lang/CharSequence;)V"); + jni_PandaActivity_showToast = env->GetMethodID(jni_PandaActivity, "showToast", "(Ljava/lang/String;I)V"); + jni_PandaActivity_findLibrary = env->GetMethodID(jni_PandaActivity, + "findLibrary", "(Ljava/lang/String;)Ljava/lang/String;"); + jni_BitmapFactory_Options = env->FindClass("android/graphics/BitmapFactory$Options"); jni_BitmapFactory_Options = (jclass) env->NewGlobalRef(jni_BitmapFactory_Options); @@ -135,6 +143,43 @@ void JNI_OnUnload(JavaVM *jvm, void *reserved) { } } +/** + * + */ +Filename android_find_library(ANativeActivity *activity, const std::string &lib) { + Thread *thread = Thread::get_current_thread(); + JNIEnv *env = thread->get_jni_env(); + nassertr(env != nullptr, Filename()); + + jstring jlib = env->NewStringUTF(lib.c_str()); + jstring jresult = (jstring)env->CallObjectMethod(activity->clazz, jni_PandaActivity_findLibrary, jlib); + env->DeleteLocalRef(jlib); + + Filename result; + if (jresult != nullptr) { + const char *c_str = env->GetStringUTFChars(jresult, nullptr); + result = c_str; + env->ReleaseStringUTFChars(jresult, c_str); + } + + return result; +} + +/** + * Sets the window title of the activity. + */ +void android_set_title(ANativeActivity *activity, const std::string &title) { + nassertv(jni_PandaActivity_setWindowTitle); + + Thread *thread = Thread::get_current_thread(); + JNIEnv *env = thread->get_jni_env(); + nassertv(env != nullptr); + + jstring jmsg = env->NewStringUTF(title.c_str()); + env->CallVoidMethod(activity->clazz, jni_PandaActivity_setWindowTitle, jmsg); + env->DeleteLocalRef(jmsg); +} + /** * Shows a toast notification at the bottom of the activity. The duration * should be 0 for short and 1 for long. @@ -148,3 +193,10 @@ void android_show_toast(ANativeActivity *activity, const std::string &message, i env->CallVoidMethod(activity->clazz, jni_PandaActivity_showToast, jmsg, (jint)duration); env->DeleteLocalRef(jmsg); } + +/** + * Returns the JNIEnv pointer corresponding to the current thread. + */ +void *SDL_AndroidGetJNIEnv() { + return Thread::get_current_thread()->get_jni_env(); +} diff --git a/panda/src/android/config_android.h b/panda/src/android/config_android.h index 4935db0718..9ff8b2107f 100644 --- a/panda/src/android/config_android.h +++ b/panda/src/android/config_android.h @@ -33,12 +33,20 @@ extern jmethodID jni_PandaActivity_readBitmapHeader; extern jmethodID jni_PandaActivity_readBitmap; extern jmethodID jni_PandaActivity_createBitmap; extern jmethodID jni_PandaActivity_compressBitmap; +extern jmethodID jni_PandaActivity_setWindowTitle; extern jmethodID jni_PandaActivity_showToast; extern jclass jni_BitmapFactory_Options; extern jfieldID jni_BitmapFactory_Options_outWidth; extern jfieldID jni_BitmapFactory_Options_outHeight; +EXPORT_CLASS Filename android_find_library(ANativeActivity *activity, const std::string &lib); +EXPORT_CLASS void android_set_title(ANativeActivity *activity, const std::string &title); EXPORT_CLASS void android_show_toast(ANativeActivity *activity, const std::string &message, int duration); +// Used to support pyjnius +extern "C" { + EXPORT_CLASS void *SDL_AndroidGetJNIEnv(void); +}; + #endif diff --git a/panda/src/android/jni_PandaActivity.cxx b/panda/src/android/jni_PandaActivity.cxx new file mode 100644 index 0000000000..4d51e98091 --- /dev/null +++ b/panda/src/android/jni_PandaActivity.cxx @@ -0,0 +1,48 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file jni_PandaActivity.cxx + * @author rdb + * @date 2025-11-09 + */ + +#include +#include +#include + +#if __GNUC__ >= 4 +#define EXPORT_JNI extern "C" __attribute__((visibility("default"))) +#else +#define EXPORT_JNI extern "C" +#endif + +/** + * + */ +EXPORT_JNI jlong +Java_org_panda3d_android_PandaActivity_nativeMmap(JNIEnv* env, jclass, jint fd, jlong off, jlong len) { + // Align the offset down to the page size boundary. + size_t page_size = getpagesize(); + off_t aligned = off & ~((off_t)page_size - 1); + size_t delta = (size_t)(off - aligned); + + void *ptr = mmap(nullptr, (size_t)len + delta, PROT_READ, MAP_PRIVATE, fd, aligned); + if (ptr != MAP_FAILED && ptr != nullptr) { + return (jlong)((uintptr_t)ptr + delta); + } else { + return (jlong)0; + } +} + +/** + * Calls the given function pointer, passing the given data pointer. + */ +EXPORT_JNI void +Java_org_panda3d_android_PandaActivity_nativeThreadEntry(JNIEnv* env, jobject self, jlong func, jlong data) { + ((void (*)(void *))(void *)func)((void *)data); +} diff --git a/panda/src/android/p3android_composite1.cxx b/panda/src/android/p3android_composite1.cxx index ea14d2b311..5a9154224e 100644 --- a/panda/src/android/p3android_composite1.cxx +++ b/panda/src/android/p3android_composite1.cxx @@ -1,6 +1,7 @@ #include "config_android.cxx" #include "jni_NativeIStream.cxx" #include "jni_NativeOStream.cxx" +#include "jni_PandaActivity.cxx" #include "pnmFileTypeAndroid.cxx" #include "pnmFileTypeAndroidReader.cxx" #include "pnmFileTypeAndroidWriter.cxx" diff --git a/panda/src/android/site.py b/panda/src/android/site.py index 7c8d22e207..ac04b2bf7c 100644 --- a/panda/src/android/site.py +++ b/panda/src/android/site.py @@ -6,6 +6,7 @@ from importlib.machinery import ModuleSpec from importlib import _bootstrap_external +# This should not be needed once we bump the minimum python version requirement to 3.13 sys.platform = "android" class AndroidExtensionFinder(MetaPathFinder): diff --git a/panda/src/androiddisplay/androidGraphicsPipe.cxx b/panda/src/androiddisplay/androidGraphicsPipe.cxx index 53f0f72310..e78c53878f 100644 --- a/panda/src/androiddisplay/androidGraphicsPipe.cxx +++ b/panda/src/androiddisplay/androidGraphicsPipe.cxx @@ -19,6 +19,8 @@ #include "config_androiddisplay.h" #include "frameBufferProperties.h" +extern IMPORT_CLASS struct android_app *panda_android_app; + TypeHandle AndroidGraphicsPipe::_type_handle; /** @@ -121,6 +123,9 @@ make_output(const std::string &name, // First thing to try: an eglGraphicsWindow if (retry == 0) { + if (panda_android_app == nullptr) { + return nullptr; + } if (((flags&BF_require_parasite)!=0)|| ((flags&BF_refuse_window)!=0)|| ((flags&BF_resizeable)!=0)|| diff --git a/panda/src/androiddisplay/androidGraphicsWindow.cxx b/panda/src/androiddisplay/androidGraphicsWindow.cxx index 1cc59d8298..e88d6dae6a 100644 --- a/panda/src/androiddisplay/androidGraphicsWindow.cxx +++ b/panda/src/androiddisplay/androidGraphicsWindow.cxx @@ -15,6 +15,7 @@ #include "androidGraphicsStateGuardian.h" #include "config_androiddisplay.h" #include "androidGraphicsPipe.h" +#include "config_android.h" #include "graphicsPipe.h" #include "keyboardButton.h" @@ -232,6 +233,11 @@ set_properties_now(WindowProperties &properties) { _properties.set_fullscreen(properties.get_fullscreen()); properties.clear_fullscreen(); } + + if (properties.has_title()) { + android_set_title(_app->activity, properties.get_title()); + properties.clear_title(); + } } /** diff --git a/panda/src/audio/audioLoadRequest.I b/panda/src/audio/audioLoadRequest.I index f5d9b8f5f8..baeb3847af 100644 --- a/panda/src/audio/audioLoadRequest.I +++ b/panda/src/audio/audioLoadRequest.I @@ -16,7 +16,7 @@ * to begin an asynchronous load. */ INLINE AudioLoadRequest:: -AudioLoadRequest(AudioManager *audio_manager, const std::string &filename, +AudioLoadRequest(AudioManager *audio_manager, const Filename &filename, bool positional) : _audio_manager(audio_manager), _filename(filename), @@ -24,6 +24,19 @@ AudioLoadRequest(AudioManager *audio_manager, const std::string &filename, { } +/** + * Create a new AudioLoadRequest, and add it to the loader via load_async(), + * to begin an asynchronous load. + */ +INLINE AudioLoadRequest:: +AudioLoadRequest(AudioManager *audio_manager, MovieAudio *source, + bool positional) : + _audio_manager(audio_manager), + _source(source), + _positional(positional) +{ +} + /** * Returns the AudioManager that will serve this asynchronous * AudioLoadRequest. @@ -36,7 +49,7 @@ get_audio_manager() const { /** * Returns the filename associated with this asynchronous AudioLoadRequest. */ -INLINE const std::string &AudioLoadRequest:: +INLINE const Filename &AudioLoadRequest:: get_filename() const { return _filename; } diff --git a/panda/src/audio/audioLoadRequest.cxx b/panda/src/audio/audioLoadRequest.cxx index e3a04d38b8..138979de83 100644 --- a/panda/src/audio/audioLoadRequest.cxx +++ b/panda/src/audio/audioLoadRequest.cxx @@ -21,7 +21,9 @@ TypeHandle AudioLoadRequest::_type_handle; */ AsyncTask::DoneStatus AudioLoadRequest:: do_task() { - set_result(_audio_manager->get_sound(_filename, _positional)); + set_result(_source != nullptr + ? _audio_manager->get_sound(_source, _positional) + : _audio_manager->get_sound(_filename, _positional)); // Don't continue the task; we're done. return DS_done; diff --git a/panda/src/audio/audioLoadRequest.h b/panda/src/audio/audioLoadRequest.h index 1c07098b91..5251a67188 100644 --- a/panda/src/audio/audioLoadRequest.h +++ b/panda/src/audio/audioLoadRequest.h @@ -33,11 +33,14 @@ public: PUBLISHED: INLINE explicit AudioLoadRequest(AudioManager *audio_manager, - const std::string &filename, + const Filename &filename, + bool positional); + INLINE explicit AudioLoadRequest(AudioManager *audio_manager, + MovieAudio *source, bool positional); INLINE AudioManager *get_audio_manager() const; - INLINE const std::string &get_filename() const; + INLINE const Filename &get_filename() const; INLINE bool get_positional() const; INLINE bool is_ready() const; @@ -48,7 +51,8 @@ protected: private: PT(AudioManager) _audio_manager; - std::string _filename; + Filename _filename; + PT(MovieAudio) _source; bool _positional; public: diff --git a/panda/src/audio/audioSound.cxx b/panda/src/audio/audioSound.cxx index 946d1bd9f0..e3c1a11c39 100644 --- a/panda/src/audio/audioSound.cxx +++ b/panda/src/audio/audioSound.cxx @@ -13,6 +13,7 @@ */ #include "audioSound.h" +#include "vector_string.h" using std::ostream; @@ -196,3 +197,60 @@ operator << (ostream &out, AudioSound::SoundStatus status) { return out << "**invalid AudioSound::SoundStatus(" << (int)status << ")**"; } + +static const vector_string empty; + +/** + * Get the comment attached to this AudioSound as a list of strings. + */ +const vector_string& AudioSound:: +get_raw_comment() const { + return empty; +} + +/** + * Returns true if this AudioSound has a comment with the given key, + * i.e. "author". Case-sensitive. + */ +bool AudioSound:: +has_comment(const std::string &key) const { + for (const std::string &st : get_raw_comment()) { + if (st.rfind(key + "=", 0) == 0) { + return true; + } + } + return false; +} + +/** + * Returns the value for a given key in the comment. If the key is not present, + * returns an empty string. + */ +std::string AudioSound:: +get_comment(const std::string &key) const { + for (const std::string &st : get_raw_comment()) { + if (st.rfind(key + "=", 0) == 0) { + return st.substr(key.length() + 1); + } + } + return ""; +} + +/** + * Returns the number of comments this sound has. + */ +int AudioSound:: +get_num_raw_comments() const { + return get_raw_comment().size(); +} + +/** + * Returns the comment at a given index. + */ +std::string AudioSound:: +get_raw_comment(int index) const { + if (index >= get_num_raw_comments() || index < 0) { + return ""; + } + return get_raw_comment()[index]; +} diff --git a/panda/src/audio/audioSound.h b/panda/src/audio/audioSound.h index c1aa059c35..a40bbdb31d 100644 --- a/panda/src/audio/audioSound.h +++ b/panda/src/audio/audioSound.h @@ -20,6 +20,7 @@ #include "pointerTo.h" #include "filterProperties.h" #include "luse.h" +#include "vector_string.h" class AudioManager; @@ -141,6 +142,16 @@ PUBLISHED: enum SoundStatus { BAD, READY, PLAYING }; virtual SoundStatus status() const = 0; + bool has_comment(const std::string &key) const; + std::string get_comment(const std::string &key) const; + MAKE_MAP_PROPERTY(comments, has_comment, get_comment); + + virtual const vector_string& get_raw_comment() const; + + int get_num_raw_comments() const; + std::string get_raw_comment(int index) const; + MAKE_SEQ_PROPERTY(raw_comments, get_num_raw_comments, get_raw_comment); + virtual void output(std::ostream &out) const; virtual void write(std::ostream &out) const; diff --git a/panda/src/audio/config_audio.cxx b/panda/src/audio/config_audio.cxx index de128896e6..5f2708cfae 100644 --- a/panda/src/audio/config_audio.cxx +++ b/panda/src/audio/config_audio.cxx @@ -76,7 +76,7 @@ ConfigVariableInt audio_preload_threshold "time - the hard drive seek time makes it stutter.")); ConfigVariableBool audio_want_hrtf -("audio-want-hrtf", true, +("audio-want-hrtf", false, PRC_DESC("This determines whether OpenAL-Soft should activate HRTF in " "certain hardware configurations. Set it to true to cause " "OpenAL to automatically apply HRTF processing to all output " diff --git a/panda/src/audiotraits/openalAudioManager.cxx b/panda/src/audiotraits/openalAudioManager.cxx index 5df2d0d880..451cd172f4 100644 --- a/panda/src/audiotraits/openalAudioManager.cxx +++ b/panda/src/audiotraits/openalAudioManager.cxx @@ -156,11 +156,10 @@ OpenALAudioManager() { // We managed to get a device open. alcGetError(_device); // clear errors - ALCboolean is_hrtf_present = alcIsExtensionPresent(_device, "ALC_SOFT_HRTF"); - ALCint attrs[3] = {0}; #ifndef HAVE_OPENAL_FRAMEWORK + ALCboolean is_hrtf_present = alcIsExtensionPresent(_device, "ALC_SOFT_HRTF"); if (is_hrtf_present) { attrs[0] = ALC_HRTF_SOFT; attrs[1] = audio_want_hrtf.get_value() ? ALC_TRUE : ALC_FALSE; @@ -441,6 +440,7 @@ get_sound_data(MovieAudio *movie, int mode) { sd->_rate = stream->audio_rate(); sd->_channels = stream->audio_channels(); sd->_length = stream->length(); + sd->_comment = stream->get_raw_comment(); audio_debug("Creating: " << sd->_movie->get_filename().get_basename()); audio_debug(" - Rate: " << sd->_rate); audio_debug(" - Channels: " << sd->_channels); @@ -716,21 +716,85 @@ get_active() const { void OpenALAudioManager:: audio_3d_set_listener_attributes(PN_stdfloat px, PN_stdfloat py, PN_stdfloat pz, PN_stdfloat vx, PN_stdfloat vy, PN_stdfloat vz, PN_stdfloat fx, PN_stdfloat fy, PN_stdfloat fz, PN_stdfloat ux, PN_stdfloat uy, PN_stdfloat uz) { ReMutexHolder holder(_lock); - _position[0] = px; - _position[1] = pz; - _position[2] = -py; + CoordinateSystem cs = get_default_coordinate_system(); - _velocity[0] = vx; - _velocity[1] = vz; - _velocity[2] = -vy; + switch (cs) { + case CS_yup_right: + _position[0] = px; + _position[1] = py; + _position[2] = pz; - _forward_up[0] = fx; - _forward_up[1] = fz; - _forward_up[2] = -fy; + _velocity[0] = vx; + _velocity[1] = vy; + _velocity[2] = vz; - _forward_up[3] = ux; - _forward_up[4] = uz; - _forward_up[5] = -uy; + _forward_up[0] = fx; + _forward_up[1] = fy; + _forward_up[2] = fz; + + _forward_up[3] = ux; + _forward_up[4] = uy; + _forward_up[5] = uz; + break; + + case CS_zup_right: + _position[0] = px; + _position[1] = pz; + _position[2] = -py; + + _velocity[0] = vx; + _velocity[1] = vz; + _velocity[2] = -vy; + + _forward_up[0] = fx; + _forward_up[1] = fz; + _forward_up[2] = -fy; + + _forward_up[3] = ux; + _forward_up[4] = uz; + _forward_up[5] = -uy; + break; + + case CS_yup_left: + _position[0] = px; + _position[1] = py; + _position[2] = -pz; + + _velocity[0] = vx; + _velocity[1] = vy; + _velocity[2] = -vz; + + _forward_up[0] = fx; + _forward_up[1] = fy; + _forward_up[2] = -fz; + + _forward_up[3] = ux; + _forward_up[4] = uy; + _forward_up[5] = -uz; + break; + + case CS_zup_left: + _position[0] = px; + _position[1] = pz; + _position[2] = py; + + _velocity[0] = vx; + _velocity[1] = vz; + _velocity[2] = vy; + + _forward_up[0] = fx; + _forward_up[1] = fz; + _forward_up[2] = fy; + + _forward_up[3] = ux; + _forward_up[4] = uz; + _forward_up[5] = uy; + break; + + default: + nassert_raise("invalid coordinate system"); + return; + } make_current(); @@ -750,21 +814,85 @@ audio_3d_set_listener_attributes(PN_stdfloat px, PN_stdfloat py, PN_stdfloat pz, void OpenALAudioManager:: audio_3d_get_listener_attributes(PN_stdfloat *px, PN_stdfloat *py, PN_stdfloat *pz, PN_stdfloat *vx, PN_stdfloat *vy, PN_stdfloat *vz, PN_stdfloat *fx, PN_stdfloat *fy, PN_stdfloat *fz, PN_stdfloat *ux, PN_stdfloat *uy, PN_stdfloat *uz) { ReMutexHolder holder(_lock); - *px = _position[0]; - *py = -_position[2]; - *pz = _position[1]; + CoordinateSystem cs = get_default_coordinate_system(); - *vx = _velocity[0]; - *vy = -_velocity[2]; - *vz = _velocity[1]; + switch (cs) { + case CS_yup_right: + *px = _position[0]; + *py = _position[1]; + *pz = _position[2]; - *fx = _forward_up[0]; - *fy = -_forward_up[2]; - *fz = _forward_up[1]; + *vx = _velocity[0]; + *vy = _velocity[1]; + *vz = _velocity[2]; - *ux = _forward_up[3]; - *uy = -_forward_up[5]; - *uz = _forward_up[4]; + *fx = _forward_up[0]; + *fy = _forward_up[1]; + *fz = _forward_up[2]; + + *ux = _forward_up[3]; + *uy = _forward_up[4]; + *uz = _forward_up[5]; + break; + + case CS_zup_right: + *px = _position[0]; + *py = -_position[2]; + *pz = _position[1]; + + *vx = _velocity[0]; + *vy = -_velocity[2]; + *vz = _velocity[1]; + + *fx = _forward_up[0]; + *fy = -_forward_up[2]; + *fz = _forward_up[1]; + + *ux = _forward_up[3]; + *uy = -_forward_up[5]; + *uz = _forward_up[4]; + break; + + case CS_yup_left: + *px = _position[0]; + *py = _position[1]; + *pz = -_position[2]; + + *vx = _velocity[0]; + *vy = _velocity[1]; + *vz = -_velocity[2]; + + *fx = _forward_up[0]; + *fy = _forward_up[1]; + *fz = -_forward_up[2]; + + *ux = _forward_up[3]; + *uy = _forward_up[4]; + *uz = -_forward_up[5]; + break; + + case CS_zup_left: + *px = _position[0]; + *py = _position[2]; + *pz = _position[1]; + + *vx = _velocity[0]; + *vy = _velocity[2]; + *vz = _velocity[1]; + + *fx = _forward_up[0]; + *fy = _forward_up[2]; + *fz = _forward_up[1]; + + *ux = _forward_up[3]; + *uy = _forward_up[5]; + *uz = _forward_up[4]; + break; + + default: + nassert_raise("invalid coordinate system"); + return; + } } /** diff --git a/panda/src/audiotraits/openalAudioManager.h b/panda/src/audiotraits/openalAudioManager.h index 56b01afc6c..236dc92f23 100644 --- a/panda/src/audiotraits/openalAudioManager.h +++ b/panda/src/audiotraits/openalAudioManager.h @@ -21,6 +21,7 @@ #include "pset.h" #include "movieAudioCursor.h" #include "reMutex.h" +#include "vector_string.h" // OSX uses the OpenAL framework #ifdef HAVE_OPENAL_FRAMEWORK @@ -173,6 +174,8 @@ private: int _channels; int _client_count; ExpirationQueue::iterator _expire; + + vector_string _comment; }; diff --git a/panda/src/audiotraits/openalAudioSound.cxx b/panda/src/audiotraits/openalAudioSound.cxx index 4bf8edf8b5..e548325476 100644 --- a/panda/src/audiotraits/openalAudioSound.cxx +++ b/panda/src/audiotraits/openalAudioSound.cxx @@ -14,8 +14,10 @@ // Panda Headers #include "throw_event.h" +#include "coordinateSystem.h" #include "openalAudioSound.h" #include "openalAudioManager.h" +#include "vector_string.h" TypeHandle OpenALAudioSound::_type_handle; @@ -87,6 +89,8 @@ OpenALAudioSound(OpenALAudioManager *manager, audio_warning("stereo sound " << movie->get_filename() << " will not be spatialized"); } } + + _comment = std::move(_sd->_comment); release_sound_data(false); } @@ -745,13 +749,53 @@ length() const { void OpenALAudioSound:: set_3d_attributes(PN_stdfloat px, PN_stdfloat py, PN_stdfloat pz, PN_stdfloat vx, PN_stdfloat vy, PN_stdfloat vz) { ReMutexHolder holder(OpenALAudioManager::_lock); - _location[0] = px; - _location[1] = pz; - _location[2] = -py; + CoordinateSystem cs = get_default_coordinate_system(); - _velocity[0] = vx; - _velocity[1] = vz; - _velocity[2] = -vy; + switch (cs) { + case CS_yup_right: + _location[0] = px; + _location[1] = py; + _location[2] = pz; + + _velocity[0] = vx; + _velocity[1] = vy; + _velocity[2] = vz; + break; + + case CS_zup_right: + _location[0] = px; + _location[1] = pz; + _location[2] = -py; + + _velocity[0] = vx; + _velocity[1] = vz; + _velocity[2] = -vy; + break; + + case CS_yup_left: + _location[0] = px; + _location[1] = py; + _location[2] = -pz; + + _velocity[0] = vx; + _velocity[1] = vy; + _velocity[2] = -vz; + break; + + case CS_zup_left: + _location[0] = px; + _location[1] = pz; + _location[2] = py; + + _velocity[0] = vx; + _velocity[1] = vz; + _velocity[2] = vy; + break; + + default: + nassert_raise("invalid coordinate system"); + return; + } if (is_playing()) { _manager->make_current(); @@ -1051,3 +1095,11 @@ status() const { return AudioSound::PLAYING; } } + +/** + * Returns the comments attached to this audio file. + */ +const vector_string& OpenALAudioSound:: +get_raw_comment() const { + return _comment; +} diff --git a/panda/src/audiotraits/openalAudioSound.h b/panda/src/audiotraits/openalAudioSound.h index 6dc30a936c..48394bd175 100644 --- a/panda/src/audiotraits/openalAudioSound.h +++ b/panda/src/audiotraits/openalAudioSound.h @@ -19,6 +19,7 @@ #include "movieAudioCursor.h" #include "trueClock.h" #include "openalAudioManager.h" +#include "vector_string.h" // OSX uses the OpenAL framework #ifdef HAVE_OPENAL_FRAMEWORK @@ -117,6 +118,8 @@ public: void finished(); + const vector_string& get_raw_comment() const; + private: OpenALAudioSound(OpenALAudioManager* manager, MovieAudio *movie, @@ -214,6 +217,8 @@ private: PN_stdfloat _cone_outer_angle; PN_stdfloat _cone_outer_gain; + vector_string _comment; + public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index 01c7ab8f63..5c4ec7c4f2 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -774,7 +774,10 @@ set_properties_now(WindowProperties &properties) { if (!_properties.get_fullscreen()) { // We use the view, not the window, to convert the frame size, expressed // in pixels, into points as the "dpi awareness" is managed by the view. - NSSize size = [_view convertSizeFromBacking:NSMakeSize(width, height)]; + NSSize size = NSMakeSize(width, height); + if (dpi_aware) { + size = [_view convertSizeFromBacking:NSMakeSize(width, height)]; + } if (_window != nil) { [_window setContentSize:size]; } @@ -782,7 +785,8 @@ set_properties_now(WindowProperties &properties) { if (cocoadisplay_cat.is_debug()) { cocoadisplay_cat.debug() - << "Setting size to " << width << ", " << height << "\n"; + << "Setting size to " << width << " x " << height + << " (scaled to " << size.width << " x " << size.height << ")\n"; } // Cocoa doesn't send an event, and the other resize-window handlers @@ -1379,7 +1383,11 @@ handle_resize_event() { [_view setFrameSize:contentRect.size]; } - NSRect frame = [_view convertRectToBacking:[_view bounds]]; + NSRect frame = [_view bounds]; + + if (dpi_aware) { + frame = [_view convertRectToBacking:frame]; + } WindowProperties properties; bool changed = false; diff --git a/panda/src/cocoagldisplay/cocoaGLGraphicsBuffer.mm b/panda/src/cocoagldisplay/cocoaGLGraphicsBuffer.mm index 3632becb0b..95e4e805a3 100644 --- a/panda/src/cocoagldisplay/cocoaGLGraphicsBuffer.mm +++ b/panda/src/cocoagldisplay/cocoaGLGraphicsBuffer.mm @@ -98,7 +98,8 @@ open_buffer() { // If the old gsg has the wrong pixel format, create a new one that shares // with the old gsg. DCAST_INTO_R(cocoagsg, _gsg, false); - if (!cocoagsg->get_fb_properties().subsumes(_fb_properties)) { + if (cocoagsg->get_engine() != _engine || + !cocoagsg->get_fb_properties().subsumes(_fb_properties)) { cocoagsg = new CocoaGLGraphicsStateGuardian(_engine, _pipe, cocoagsg); cocoagsg->choose_pixel_format(_fb_properties, cocoa_pipe->get_display_id(), false); _gsg = cocoagsg; diff --git a/panda/src/cocoagldisplay/cocoaGLGraphicsPipe.mm b/panda/src/cocoagldisplay/cocoaGLGraphicsPipe.mm index 7ad1317b0b..4864ae2f8b 100644 --- a/panda/src/cocoagldisplay/cocoaGLGraphicsPipe.mm +++ b/panda/src/cocoagldisplay/cocoaGLGraphicsPipe.mm @@ -125,12 +125,12 @@ make_output(const std::string &name, precertify = true; } } - if (host != nullptr) { + if (host != nullptr && host->get_engine() == engine) { return new GLGraphicsBuffer(engine, this, name, fb_prop, win_prop, flags, gsg, host); } else { return new CocoaGLGraphicsBuffer(engine, this, name, fb_prop, win_prop, - flags, gsg, host); + flags, gsg, nullptr); } } diff --git a/panda/src/cocoagldisplay/cocoaGLGraphicsWindow.mm b/panda/src/cocoagldisplay/cocoaGLGraphicsWindow.mm index 33a97a90f9..71a3f87ad3 100644 --- a/panda/src/cocoagldisplay/cocoaGLGraphicsWindow.mm +++ b/panda/src/cocoagldisplay/cocoaGLGraphicsWindow.mm @@ -200,7 +200,8 @@ open_window() { // If the old gsg has the wrong pixel format, create a new one that shares // with the old gsg. DCAST_INTO_R(cocoagsg, _gsg, false); - if (!cocoagsg->get_fb_properties().subsumes(_fb_properties)) { + if (cocoagsg->get_engine() != _engine || + !cocoagsg->get_fb_properties().subsumes(_fb_properties)) { cocoagsg = new CocoaGLGraphicsStateGuardian(_engine, _pipe, cocoagsg); cocoagsg->choose_pixel_format(_fb_properties, _display, false); _gsg = cocoagsg; diff --git a/panda/src/collide/CMakeLists.txt b/panda/src/collide/CMakeLists.txt index 296a40eeed..8dad7fe29a 100644 --- a/panda/src/collide/CMakeLists.txt +++ b/panda/src/collide/CMakeLists.txt @@ -74,6 +74,8 @@ set(P3COLLIDE_IGATEEXT collisionHandlerPhysical_ext.h collisionHandlerQueue_ext.cxx collisionHandlerQueue_ext.h + collisionNode_ext.cxx + collisionNode_ext.h collisionPolygon_ext.cxx collisionPolygon_ext.h collisionTraverser_ext.cxx diff --git a/panda/src/collide/collisionBox.cxx b/panda/src/collide/collisionBox.cxx index 5058dfaf37..d3445af004 100644 --- a/panda/src/collide/collisionBox.cxx +++ b/panda/src/collide/collisionBox.cxx @@ -343,7 +343,8 @@ test_intersection_from_line(const CollisionEntry &entry) const { const CollisionLine *line; DCAST_INTO_R(line, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = line->get_origin() * wrt_mat; LVector3 from_direction = line->get_direction() * wrt_mat; @@ -386,7 +387,8 @@ PT(CollisionEntry) CollisionBox:: test_intersection_from_ray(const CollisionEntry &entry) const { const CollisionRay *ray; DCAST_INTO_R(ray, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = ray->get_origin() * wrt_mat; LVector3 from_direction = ray->get_direction() * wrt_mat; @@ -511,7 +513,8 @@ PT(CollisionEntry) CollisionBox:: test_intersection_from_segment(const CollisionEntry &entry) const { const CollisionSegment *seg; DCAST_INTO_R(seg, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = seg->get_point_a() * wrt_mat; LPoint3 from_extent = seg->get_point_b() * wrt_mat; @@ -566,7 +569,8 @@ test_intersection_from_capsule(const CollisionEntry &entry) const { const CollisionCapsule *capsule; DCAST_INTO_R(capsule, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_a = capsule->get_point_a() * wrt_mat; LPoint3 from_b = capsule->get_point_b() * wrt_mat; @@ -711,7 +715,8 @@ test_intersection_from_box(const CollisionEntry &entry) const { const CollisionBox *box; DCAST_INTO_R(box, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 diff = wrt_mat.xform_point_general(box->get_center()) - _center; LVector3 from_extents = box->get_dimensions() * 0.5f; diff --git a/panda/src/collide/collisionCapsule.cxx b/panda/src/collide/collisionCapsule.cxx index 3eed758863..0c67a492ab 100644 --- a/panda/src/collide/collisionCapsule.cxx +++ b/panda/src/collide/collisionCapsule.cxx @@ -308,7 +308,8 @@ test_intersection_from_line(const CollisionEntry &entry) const { const CollisionLine *line; DCAST_INTO_R(line, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = line->get_origin() * wrt_mat; LVector3 from_direction = line->get_direction() * wrt_mat; @@ -356,7 +357,8 @@ test_intersection_from_ray(const CollisionEntry &entry) const { const CollisionRay *ray; DCAST_INTO_R(ray, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = ray->get_origin() * wrt_mat; LVector3 from_direction = ray->get_direction() * wrt_mat; @@ -417,7 +419,8 @@ test_intersection_from_segment(const CollisionEntry &entry) const { const CollisionSegment *segment; DCAST_INTO_R(segment, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_a = segment->get_point_a() * wrt_mat; LPoint3 from_b = segment->get_point_b() * wrt_mat; @@ -484,7 +487,8 @@ test_intersection_from_capsule(const CollisionEntry &entry) const { LPoint3 into_a = _a; LVector3 into_direction = _b - into_a; - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_a = capsule->get_point_a() * wrt_mat; LPoint3 from_b = capsule->get_point_b() * wrt_mat; @@ -545,7 +549,8 @@ test_intersection_from_parabola(const CollisionEntry &entry) const { const CollisionParabola *parabola; DCAST_INTO_R(parabola, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); // Convert the parabola into local coordinate space. LParabola local_p(parabola->get_parabola()); diff --git a/panda/src/collide/collisionEntry.I b/panda/src/collide/collisionEntry.I index 41a591c90e..8525182028 100644 --- a/panda/src/collide/collisionEntry.I +++ b/panda/src/collide/collisionEntry.I @@ -291,7 +291,7 @@ get_wrt_prev_space() const { /** * Returns the relative transform of the from node as seen from the into node. */ -INLINE const LMatrix4 &CollisionEntry:: +INLINE LMatrix4 CollisionEntry:: get_wrt_mat() const { return get_wrt_space()->get_mat(); } @@ -299,7 +299,7 @@ get_wrt_mat() const { /** * Returns the relative transform of the into node as seen from the from node. */ -INLINE const LMatrix4 &CollisionEntry:: +INLINE LMatrix4 CollisionEntry:: get_inv_wrt_mat() const { return get_inv_wrt_space()->get_mat(); } @@ -309,7 +309,7 @@ get_inv_wrt_mat() const { * as of the previous frame (according to set_prev_transform(), * set_fluid_pos(), etc.) */ -INLINE const LMatrix4 &CollisionEntry:: +INLINE LMatrix4 CollisionEntry:: get_wrt_prev_mat() const { return get_wrt_prev_space()->get_mat(); } diff --git a/panda/src/collide/collisionEntry.h b/panda/src/collide/collisionEntry.h index 03d39c03d5..a66e037e05 100644 --- a/panda/src/collide/collisionEntry.h +++ b/panda/src/collide/collisionEntry.h @@ -109,9 +109,9 @@ public: INLINE CPT(TransformState) get_inv_wrt_space() const; INLINE CPT(TransformState) get_wrt_prev_space() const; - INLINE const LMatrix4 &get_wrt_mat() const; - INLINE const LMatrix4 &get_inv_wrt_mat() const; - INLINE const LMatrix4 &get_wrt_prev_mat() const; + INLINE LMatrix4 get_wrt_mat() const; + INLINE LMatrix4 get_inv_wrt_mat() const; + INLINE LMatrix4 get_wrt_prev_mat() const; INLINE const ClipPlaneAttrib *get_into_clip_planes() const; diff --git a/panda/src/collide/collisionFloorMesh.cxx b/panda/src/collide/collisionFloorMesh.cxx index cde2eda522..a27144b437 100644 --- a/panda/src/collide/collisionFloorMesh.cxx +++ b/panda/src/collide/collisionFloorMesh.cxx @@ -130,7 +130,9 @@ PT(CollisionEntry) CollisionFloorMesh:: test_intersection_from_ray(const CollisionEntry &entry) const { const CollisionRay *ray; DCAST_INTO_R(ray, entry.get_from(), nullptr); - LPoint3 from_origin = ray->get_origin() * entry.get_wrt_mat(); + + CPT(TransformState) wrt_space = entry.get_wrt_space(); + LPoint3 from_origin = ray->get_origin() * wrt_space->get_mat(); double fx = from_origin[0]; double fy = from_origin[1]; @@ -195,7 +197,9 @@ PT(CollisionEntry) CollisionFloorMesh:: test_intersection_from_sphere(const CollisionEntry &entry) const { const CollisionSphere *sphere; DCAST_INTO_R(sphere, entry.get_from(), nullptr); - LPoint3 from_origin = sphere->get_center() * entry.get_wrt_mat(); + + CPT(TransformState) wrt_space = entry.get_wrt_space(); + LPoint3 from_origin = sphere->get_center() * wrt_space->get_mat(); double fx = from_origin[0]; double fy = from_origin[1]; diff --git a/panda/src/collide/collisionInvSphere.cxx b/panda/src/collide/collisionInvSphere.cxx index edc4350df2..e9110b262b 100644 --- a/panda/src/collide/collisionInvSphere.cxx +++ b/panda/src/collide/collisionInvSphere.cxx @@ -97,7 +97,8 @@ test_intersection_from_sphere(const CollisionEntry &entry) const { const CollisionSphere *sphere; DCAST_INTO_R(sphere, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_center = sphere->get_center() * wrt_mat; LVector3 from_radius_v = @@ -149,7 +150,8 @@ test_intersection_from_line(const CollisionEntry &entry) const { const CollisionLine *line; DCAST_INTO_R(line, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = line->get_origin() * wrt_mat; LVector3 from_direction = line->get_direction() * wrt_mat; @@ -190,7 +192,8 @@ test_intersection_from_ray(const CollisionEntry &entry) const { const CollisionRay *ray; DCAST_INTO_R(ray, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = ray->get_origin() * wrt_mat; LVector3 from_direction = ray->get_direction() * wrt_mat; @@ -233,7 +236,8 @@ test_intersection_from_segment(const CollisionEntry &entry) const { const CollisionSegment *segment; DCAST_INTO_R(segment, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_a = segment->get_point_a() * wrt_mat; LPoint3 from_b = segment->get_point_b() * wrt_mat; @@ -411,7 +415,8 @@ test_intersection_from_capsule(const CollisionEntry &entry) const { const CollisionCapsule *capsule; DCAST_INTO_R(capsule, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_a = capsule->get_point_a() * wrt_mat; LPoint3 from_b = capsule->get_point_b() * wrt_mat; @@ -466,7 +471,8 @@ test_intersection_from_box(const CollisionEntry &entry) const { const CollisionBox *box; DCAST_INTO_R(box, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 center = get_center(); PN_stdfloat radius_sq = get_radius(); diff --git a/panda/src/collide/collisionNode.I b/panda/src/collide/collisionNode.I index 51fe8002fb..f429fccfb1 100644 --- a/panda/src/collide/collisionNode.I +++ b/panda/src/collide/collisionNode.I @@ -166,3 +166,24 @@ INLINE CollideMask CollisionNode:: get_default_collide_mask() { return default_collision_node_collide_mask; } + +/** + * Returns the custom pointer set via set_owner(). + */ +INLINE void *CollisionNode:: +get_owner() const { + return _owner; +} + +/** + * Sets a custom pointer, together with an optional callback that will be + * called when the node is deleted. + * + * The owner or the callback will not be copied along with the CollisionNode. + */ +INLINE void CollisionNode:: +set_owner(void *owner, OwnerCallback *callback) { + clear_owner(); + _owner = owner; + _owner_callback = callback; +} diff --git a/panda/src/collide/collisionNode.cxx b/panda/src/collide/collisionNode.cxx index 2df893ef18..60d28960dd 100644 --- a/panda/src/collide/collisionNode.cxx +++ b/panda/src/collide/collisionNode.cxx @@ -40,7 +40,9 @@ CollisionNode:: CollisionNode(const std::string &name) : PandaNode(name), _from_collide_mask(get_default_collide_mask()), - _collider_sort(0) + _collider_sort(0), + _owner(nullptr), + _owner_callback(nullptr) { set_cull_callback(); set_renderable(); @@ -60,7 +62,9 @@ CollisionNode(const CollisionNode ©) : PandaNode(copy), _from_collide_mask(copy._from_collide_mask), _collider_sort(copy._collider_sort), - _solids(copy._solids) + _solids(copy._solids), + _owner(nullptr), + _owner_callback(nullptr) { } @@ -69,6 +73,10 @@ CollisionNode(const CollisionNode ©) : */ CollisionNode:: ~CollisionNode() { + if (_owner_callback != nullptr) { + _owner_callback(_owner); + _owner_callback = nullptr; + } } /** @@ -251,6 +259,18 @@ set_from_collide_mask(CollideMask mask) { _from_collide_mask = mask; } +/** + * Removes the owner that was previously set using set_owner(). + */ +void CollisionNode:: +clear_owner() { + if (_owner_callback != nullptr) { + _owner_callback(_owner); + } + _owner = nullptr; + _owner_callback = nullptr; +} + /** * Called when needed to recompute the node's _internal_bound object. Nodes * that contain anything of substance should redefine this to do the right diff --git a/panda/src/collide/collisionNode.h b/panda/src/collide/collisionNode.h index fee71fce78..14941a5336 100644 --- a/panda/src/collide/collisionNode.h +++ b/panda/src/collide/collisionNode.h @@ -77,6 +77,22 @@ PUBLISHED: INLINE static CollideMask get_default_collide_mask(); MAKE_PROPERTY(default_collide_mask, get_default_collide_mask); +public: + typedef void (OwnerCallback)(void *); + + INLINE void *get_owner() const; + +#ifndef CPPPARSER + INLINE void set_owner(void *owner, OwnerCallback *callback = nullptr); + void clear_owner(); +#endif + + EXTENSION(PyObject *get_owner() const); + EXTENSION(void set_owner(PyObject *owner)); + +PUBLISHED: + MAKE_PROPERTY(owner, get_owner, set_owner); + protected: virtual void compute_internal_bounds(CPT(BoundingVolume) &internal_bounds, int &internal_vertices, @@ -94,6 +110,9 @@ private: typedef pvector< COWPT(CollisionSolid) > Solids; Solids _solids; + void *_owner = nullptr; + OwnerCallback *_owner_callback = nullptr; + friend class CollisionTraverser; public: diff --git a/panda/src/collide/collisionNode_ext.cxx b/panda/src/collide/collisionNode_ext.cxx new file mode 100644 index 0000000000..62e7d5f874 --- /dev/null +++ b/panda/src/collide/collisionNode_ext.cxx @@ -0,0 +1,72 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file collisionNode_ext.cxx + * @author rdb + * @date 2024-12-12 + */ + +#include "collisionNode_ext.h" + +#ifdef HAVE_PYTHON + +#include "collisionNode.h" + +/** + * Returns the object previously set via set_owner(). If the object has been + * destroyed, returns None. + */ +PyObject *Extension:: +get_owner() const { + PyObject *owner = (PyObject *)_this->get_owner(); + +#if PY_VERSION_HEX >= 0x030D0000 // 3.13 + PyObject *strong_ref; + int result = 0; + if (owner != nullptr) { + result = PyWeakref_GetRef(owner, &strong_ref); + } + if (result > 0) { + return strong_ref; + } + else if (result == 0) { + return Py_NewRef(Py_None); + } + else { + return nullptr; + } +#else + return Py_NewRef(owner != nullptr ? PyWeakref_GetObject(owner) : Py_None); +#endif +} + +/** + * Stores a weak reference to the given object on the CollisionNode, for later + * use in collision events and handlers. + */ +void Extension:: +set_owner(PyObject *owner) { + if (owner != Py_None) { + PyObject *ref = PyWeakref_NewRef(owner, nullptr); + _this->set_owner(ref, [](void *obj) { +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + // Use PyGILState to protect this asynchronous call. + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); +#endif + Py_DECREF((PyObject *)obj); +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyGILState_Release(gstate); +#endif + }); + } else { + _this->clear_owner(); + } +} + +#endif diff --git a/panda/src/collide/collisionNode_ext.h b/panda/src/collide/collisionNode_ext.h new file mode 100644 index 0000000000..ea27e1bb7d --- /dev/null +++ b/panda/src/collide/collisionNode_ext.h @@ -0,0 +1,40 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file collisionNode_ext.h + * @author rdb + * @date 2024-12-12 + */ + +#ifndef COLLISIONNODE_EXT_H +#define COLLISIONNODE_EXT_H + +#include "pandabase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "collisionNode.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for CollisionNode, which are called + * instead of any C++ methods with the same prototype. + * + * @since 1.11.0 + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *get_owner() const; + void set_owner(PyObject *owner); +}; + +#endif // HAVE_PYTHON + +#endif diff --git a/panda/src/collide/collisionPlane.cxx b/panda/src/collide/collisionPlane.cxx index 2a133b1655..5d8edf9a97 100644 --- a/panda/src/collide/collisionPlane.cxx +++ b/panda/src/collide/collisionPlane.cxx @@ -109,7 +109,8 @@ test_intersection_from_sphere(const CollisionEntry &entry) const { const CollisionSphere *sphere; DCAST_INTO_R(sphere, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_center = sphere->get_center() * wrt_mat; LVector3 from_radius_v = @@ -148,7 +149,8 @@ test_intersection_from_line(const CollisionEntry &entry) const { const CollisionLine *line; DCAST_INTO_R(line, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = line->get_origin() * wrt_mat; LVector3 from_direction = line->get_direction() * wrt_mat; @@ -193,7 +195,8 @@ test_intersection_from_ray(const CollisionEntry &entry) const { const CollisionRay *ray; DCAST_INTO_R(ray, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = ray->get_origin() * wrt_mat; LVector3 from_direction = ray->get_direction() * wrt_mat; @@ -245,7 +248,8 @@ test_intersection_from_segment(const CollisionEntry &entry) const { const CollisionSegment *segment; DCAST_INTO_R(segment, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_a = segment->get_point_a() * wrt_mat; LPoint3 from_b = segment->get_point_b() * wrt_mat; @@ -302,7 +306,8 @@ test_intersection_from_capsule(const CollisionEntry &entry) const { const CollisionCapsule *capsule; DCAST_INTO_R(capsule, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_a = capsule->get_point_a() * wrt_mat; LPoint3 from_b = capsule->get_point_b() * wrt_mat; @@ -373,7 +378,8 @@ test_intersection_from_parabola(const CollisionEntry &entry) const { const CollisionParabola *parabola; DCAST_INTO_R(parabola, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); // Convert the parabola into local coordinate space. LParabola local_p(parabola->get_parabola()); @@ -439,7 +445,8 @@ test_intersection_from_box(const CollisionEntry &entry) const { const CollisionBox *box; DCAST_INTO_R(box, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_center = box->get_center() * wrt_mat; LVector3 from_extents = box->get_dimensions() * 0.5f; diff --git a/panda/src/collide/collisionPolygon.cxx b/panda/src/collide/collisionPolygon.cxx index ab78a388c0..0d3214f211 100644 --- a/panda/src/collide/collisionPolygon.cxx +++ b/panda/src/collide/collisionPolygon.cxx @@ -584,7 +584,8 @@ test_intersection_from_line(const CollisionEntry &entry) const { const CollisionLine *line; DCAST_INTO_R(line, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = line->get_origin() * wrt_mat; LVector3 from_direction = line->get_direction() * wrt_mat; @@ -652,7 +653,8 @@ test_intersection_from_ray(const CollisionEntry &entry) const { const CollisionRay *ray; DCAST_INTO_R(ray, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = ray->get_origin() * wrt_mat; LVector3 from_direction = ray->get_direction() * wrt_mat; @@ -725,7 +727,8 @@ test_intersection_from_segment(const CollisionEntry &entry) const { const CollisionSegment *segment; DCAST_INTO_R(segment, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_a = segment->get_point_a() * wrt_mat; LPoint3 from_b = segment->get_point_b() * wrt_mat; @@ -1047,7 +1050,8 @@ test_intersection_from_parabola(const CollisionEntry &entry) const { const CollisionParabola *parabola; DCAST_INTO_R(parabola, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); // Convert the parabola into local coordinate space. LParabola local_p(parabola->get_parabola()); diff --git a/panda/src/collide/collisionSphere.cxx b/panda/src/collide/collisionSphere.cxx index 8f9dcb0ccb..847de2e84f 100644 --- a/panda/src/collide/collisionSphere.cxx +++ b/panda/src/collide/collisionSphere.cxx @@ -242,7 +242,8 @@ test_intersection_from_line(const CollisionEntry &entry) const { const CollisionLine *line; DCAST_INTO_R(line, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = line->get_origin() * wrt_mat; LVector3 from_direction = line->get_direction() * wrt_mat; @@ -284,10 +285,11 @@ test_intersection_from_box(const CollisionEntry &entry) const { // Instead of transforming the box into the sphere's coordinate space, we do // it the other way around. It's easier that way. - const LMatrix4 &wrt_mat = entry.get_inv_wrt_mat(); + CPT(TransformState) inv_wrt_space = entry.get_inv_wrt_space(); + const LMatrix4 &inv_wrt_mat = inv_wrt_space->get_mat(); - LPoint3 center = wrt_mat.xform_point(_center); - PN_stdfloat radius_sq = wrt_mat.xform_vec(LVector3(0, 0, _radius)).length_squared(); + LPoint3 center = inv_wrt_mat.xform_point(_center); + PN_stdfloat radius_sq = inv_wrt_mat.xform_vec(LVector3(0, 0, _radius)).length_squared(); LPoint3 box_min = box->get_min(); LPoint3 box_max = box->get_max(); @@ -336,7 +338,8 @@ test_intersection_from_box(const CollisionEntry &entry) const { PT(CollisionEntry) new_entry = new CollisionEntry(entry); // To get the interior point, clamp the sphere center to the AABB. - LPoint3 interior = entry.get_wrt_mat().xform_point(center.fmax(box_min).fmin(box_max)); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + LPoint3 interior = wrt_space->get_mat().xform_point(center.fmax(box_min).fmin(box_max)); new_entry->set_interior_point(interior); // Now extrapolate the surface point and normal from that. @@ -358,7 +361,8 @@ test_intersection_from_ray(const CollisionEntry &entry) const { const CollisionRay *ray; DCAST_INTO_R(ray, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_origin = ray->get_origin() * wrt_mat; LVector3 from_direction = ray->get_direction() * wrt_mat; @@ -405,7 +409,8 @@ test_intersection_from_segment(const CollisionEntry &entry) const { const CollisionSegment *segment; DCAST_INTO_R(segment, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_a = segment->get_point_a() * wrt_mat; LPoint3 from_b = segment->get_point_b() * wrt_mat; @@ -454,7 +459,8 @@ test_intersection_from_capsule(const CollisionEntry &entry) const { const CollisionCapsule *capsule; DCAST_INTO_R(capsule, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); LPoint3 from_a = capsule->get_point_a() * wrt_mat; LPoint3 from_b = capsule->get_point_b() * wrt_mat; @@ -510,7 +516,8 @@ test_intersection_from_parabola(const CollisionEntry &entry) const { const CollisionParabola *parabola; DCAST_INTO_R(parabola, entry.get_from(), nullptr); - const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + CPT(TransformState) wrt_space = entry.get_wrt_space(); + const LMatrix4 &wrt_mat = wrt_space->get_mat(); // Convert the parabola into local coordinate space. LParabola local_p(parabola->get_parabola()); diff --git a/panda/src/collide/p3collide_ext_composite.cxx b/panda/src/collide/p3collide_ext_composite.cxx index 76e458c0f3..8e9f584c0c 100644 --- a/panda/src/collide/p3collide_ext_composite.cxx +++ b/panda/src/collide/p3collide_ext_composite.cxx @@ -1,5 +1,6 @@ #include "collisionHandlerEvent_ext.cxx" #include "collisionHandlerPhysical_ext.cxx" #include "collisionHandlerQueue_ext.cxx" +#include "collisionNode_ext.cxx" #include "collisionPolygon_ext.cxx" #include "collisionTraverser_ext.cxx" diff --git a/panda/src/cull/cullBinBackToFront.cxx b/panda/src/cull/cullBinBackToFront.cxx index a8fa169304..0254c4dfdd 100644 --- a/panda/src/cull/cullBinBackToFront.cxx +++ b/panda/src/cull/cullBinBackToFront.cxx @@ -40,7 +40,7 @@ add_object(CullableObject *object, Thread *current_thread) { // Determine the center of the bounding volume. CPT(BoundingVolume) volume = object->_geom->get_bounds(current_thread); if (volume->is_empty()) { - delete object; + // No point in culling objects with no volume. return; } diff --git a/panda/src/cull/cullBinFrontToBack.cxx b/panda/src/cull/cullBinFrontToBack.cxx index 146eaaab58..dfc85dd540 100644 --- a/panda/src/cull/cullBinFrontToBack.cxx +++ b/panda/src/cull/cullBinFrontToBack.cxx @@ -40,7 +40,7 @@ add_object(CullableObject *object, Thread *current_thread) { // Determine the center of the bounding volume. CPT(BoundingVolume) volume = object->_geom->get_bounds(); if (volume->is_empty()) { - delete object; + // No point in culling objects with no volume. return; } diff --git a/panda/src/display/displayRegion.cxx b/panda/src/display/displayRegion.cxx index 84c852d472..df0e7a7292 100644 --- a/panda/src/display/displayRegion.cxx +++ b/panda/src/display/displayRegion.cxx @@ -487,7 +487,9 @@ get_screenshot() { if (gsg->get_threading_model().get_draw_stage() != current_thread->get_pipeline_stage()) { // Ask the engine to do on the draw thread. GraphicsEngine *engine = window->get_engine(); - return engine->do_get_screenshot(this, gsg); + return engine->run_on_draw_thread([this] { + return get_screenshot(); + }); } // We are on the draw thread. diff --git a/panda/src/display/graphicsEngine.I b/panda/src/display/graphicsEngine.I index e9fc42ce44..ca9142a8a8 100644 --- a/panda/src/display/graphicsEngine.I +++ b/panda/src/display/graphicsEngine.I @@ -171,3 +171,53 @@ INLINE void GraphicsEngine:: dispatch_compute(const LVecBase3i &work_groups, const ShaderAttrib *sattr, GraphicsStateGuardian *gsg) { dispatch_compute(work_groups, RenderState::make(sattr), gsg); } + +#ifndef CPPPARSER +/** + * Waits for the draw thread to become idle, then runs the given function on it. + */ +template +INLINE auto GraphicsEngine:: +run_on_draw_thread(Callable &&callable) -> decltype(callable()) { + ReMutexHolder holder(_lock); + std::string draw_name = _threading_model.get_draw_name(); + if (draw_name.empty()) { + return std::move(callable)(); + } else { + WindowRenderer *wr = get_window_renderer(draw_name, 0); + RenderThread *thread = (RenderThread *)wr; + return thread->run_on_thread(std::move(callable)); + } +} + +/** + * Waits for this thread to become idle, then runs the given function on it. + */ +template +INLINE auto GraphicsEngine::RenderThread:: +run_on_thread(Callable &&callable) -> + typename std::enable_if::value, decltype(callable())>::type { + + using ReturnType = decltype(callable()); + alignas(ReturnType) unsigned char storage[sizeof(ReturnType)]; + + run_on_thread([] (RenderThread *data) { + new (data->_return_data) ReturnType(std::move(*(Callable *)data->_callback_data)()); + }, &callable, storage); + + return *(ReturnType *)storage; +} + +/** + * Waits for this thread to become idle, then runs the given function on it. + */ +template +INLINE auto GraphicsEngine::RenderThread:: +run_on_thread(Callable &&callable) -> + typename std::enable_if::value, decltype(callable())>::type { + + run_on_thread([] (RenderThread *data) { + std::move(*(Callable *)data->_callback_data)(); + }, &callable, nullptr); +} +#endif // CPPPARSER diff --git a/panda/src/display/graphicsEngine.cxx b/panda/src/display/graphicsEngine.cxx index eb346a2b7d..c8fcf9500f 100644 --- a/panda/src/display/graphicsEngine.cxx +++ b/panda/src/display/graphicsEngine.cxx @@ -152,8 +152,9 @@ INLINE static bool operator < (const CullKey &a, const CullKey &b) { * any Pipeline you choose. */ GraphicsEngine:: -GraphicsEngine(Pipeline *pipeline) : +GraphicsEngine(ClockObject *clock, Pipeline *pipeline) : _pipeline(pipeline), + _clock(clock), _app("app"), _lock("GraphicsEngine::_lock"), _loaded_textures_lock("GraphicsEngine::_loaded_textures_lock") @@ -339,7 +340,7 @@ make_output(GraphicsPipe *pipe, nassertr(pipe != nullptr, nullptr); if (gsg != nullptr) { nassertr(pipe == gsg->get_pipe(), nullptr); - nassertr(this == gsg->get_engine(), nullptr); + //nassertr(this == gsg->get_engine(), nullptr); } // Are we really asking for a callback window? @@ -729,11 +730,9 @@ render_frame() { // been rendered). open_windows(); - ClockObject *global_clock = ClockObject::get_global_clock(); - if (display_cat.is_spam()) { display_cat.spam() - << "render_frame() - frame " << global_clock->get_frame_count() << "\n"; + << "render_frame() - frame " << _clock->get_frame_count() << "\n"; } { @@ -846,8 +845,8 @@ render_frame() { } #endif // THREADED_PIPELINE - global_clock->tick(current_thread); - if (global_clock->check_errors(current_thread)) { + _clock->tick(current_thread); + if (_clock->check_errors(current_thread)) { throw_event("clock_error"); } @@ -1135,47 +1134,53 @@ flip_frame() { */ bool GraphicsEngine:: extract_texture_data(Texture *tex, GraphicsStateGuardian *gsg) { - ReMutexHolder holder(_lock); - - string draw_name = gsg->get_threading_model().get_draw_name(); - if (draw_name.empty()) { - // A single-threaded environment. No problem. + return run_on_draw_thread([=] () { return gsg->extract_texture_data(tex); + }); +} - } else { - // A multi-threaded environment. We have to wait until the draw thread - // has finished its current task. - WindowRenderer *wr = get_window_renderer(draw_name, 0); - RenderThread *thread = (RenderThread *)wr; - MutexHolder cv_holder(thread->_cv_mutex); - - while (thread->_thread_state != TS_wait) { - thread->_cv_done.wait(); +/** + * Asks the indicated GraphicsStateGuardian to retrieve the buffer memory + * image of the indicated ShaderBuffer and return it. + * + * This is mainly useful for debugging. It is a very slow call because it + * introduces a pipeline stall both of Panda's pipeline and the graphics + * pipeline. This is even when only a small amount of data is downloaded. + * + * The return value is empty if some kind of error occurred. + */ +vector_uchar GraphicsEngine:: +extract_shader_buffer_data(ShaderBuffer *buffer, GraphicsStateGuardian *gsg, + size_t start, size_t size) { + return run_on_draw_thread([=] () { + vector_uchar data; + if (!gsg->extract_shader_buffer_data(buffer, data, start, size)) { + data.clear(); } + return data; + }); +} - // Temporarily set this so that it accesses data from the current thread. - int pipeline_stage = Thread::get_current_pipeline_stage(); - int draw_pipeline_stage = thread->get_pipeline_stage(); - thread->set_pipeline_stage(pipeline_stage); - - // Now that the draw thread is idle, signal it to do the extraction task. - thread->_gsg = gsg; - thread->_texture = tex; - thread->_thread_state = TS_do_extract; - thread->_cv_mutex.release(); - thread->_cv_start.notify(); - thread->_cv_mutex.acquire(); - - // Wait for it to finish the extraction. - while (thread->_thread_state != TS_wait) { - thread->_cv_done.wait(); - } - - thread->set_pipeline_stage(draw_pipeline_stage); - thread->_gsg = nullptr; - thread->_texture = nullptr; - return thread->_result; +/** + * Updates shader buffer data from the CPU. This may be a subsection of the + * buffer. Returns true on success. + * + * Updating is only allowed for buffers with usage hint set to UH_dynamic. + */ +bool GraphicsEngine:: +update_shader_buffer_data(ShaderBuffer *buffer, GraphicsStateGuardian *gsg, + const vector_uchar &data, size_t offset) { + if (buffer->get_usage_hint() != GeomEnums::UH_dynamic) { + display_cat.error() + << "Cannot update ShaderBuffer with usage hint " << buffer->get_usage_hint() << "\n"; + return false; } + + const unsigned char *data_ptr = data.data(); + const size_t data_size = data.size(); + return run_on_draw_thread([=] () { + return gsg->update_shader_buffer_data(buffer, offset, data_size, data_ptr); + }); } /** @@ -1200,50 +1205,12 @@ dispatch_compute(const LVecBase3i &work_groups, const RenderState *state, Graphi nassertv(shader != nullptr); nassertv(gsg != nullptr); - ReMutexHolder holder(_lock); - - string draw_name = gsg->get_threading_model().get_draw_name(); - if (draw_name.empty()) { - // A single-threaded environment. No problem. + run_on_draw_thread([=] () { gsg->push_group_marker(std::string("Compute ") + shader->get_filename(Shader::ST_compute).get_basename()); gsg->set_state_and_transform(state, TransformState::make_identity()); gsg->dispatch_compute(work_groups[0], work_groups[1], work_groups[2]); gsg->pop_group_marker(); - - } else { - // A multi-threaded environment. We have to wait until the draw thread - // has finished its current task. - WindowRenderer *wr = get_window_renderer(draw_name, 0); - RenderThread *thread = (RenderThread *)wr; - MutexHolder cv_holder(thread->_cv_mutex); - - while (thread->_thread_state != TS_wait) { - thread->_cv_done.wait(); - } - - // Temporarily set this so that it accesses data from the current thread. - int pipeline_stage = Thread::get_current_pipeline_stage(); - int draw_pipeline_stage = thread->get_pipeline_stage(); - thread->set_pipeline_stage(pipeline_stage); - - // Now that the draw thread is idle, signal it to do the compute task. - thread->_gsg = gsg; - thread->_state = state; - thread->_work_groups = work_groups; - thread->_thread_state = TS_do_compute; - thread->_cv_mutex.release(); - thread->_cv_start.notify(); - thread->_cv_mutex.acquire(); - - // Wait for it to finish the compute task. - while (thread->_thread_state != TS_wait) { - thread->_cv_done.wait(); - } - - thread->set_pipeline_stage(draw_pipeline_stage); - thread->_gsg = nullptr; - thread->_state = nullptr; - } + }); } /** @@ -1279,43 +1246,6 @@ texture_uploaded(Texture *tex) { // Usually only called by DisplayRegion::do_cull. } -/** - * Called by DisplayRegion::do_get_screenshot - */ -PT(Texture) GraphicsEngine:: -do_get_screenshot(DisplayRegion *region, GraphicsStateGuardian *gsg) { - // A multi-threaded environment. We have to wait until the draw thread - // has finished its current task. - - ReMutexHolder holder(_lock); - - const std::string &draw_name = gsg->get_threading_model().get_draw_name(); - WindowRenderer *wr = get_window_renderer(draw_name, 0); - RenderThread *thread = (RenderThread *)wr; - MutexHolder cv_holder(thread->_cv_mutex); - - while (thread->_thread_state != TS_wait) { - thread->_cv_done.wait(); - } - - // Now that the draw thread is idle, signal it to do the extraction task. - thread->_region = region; - thread->_thread_state = TS_do_screenshot; - thread->_cv_mutex.release(); - thread->_cv_start.notify(); - thread->_cv_mutex.acquire(); - - // Wait for it to finish the extraction. - while (thread->_thread_state != TS_wait) { - thread->_cv_done.wait(); - } - - PT(Texture) tex = std::move(thread->_texture); - thread->_region = nullptr; - thread->_texture = nullptr; - return tex; -} - /** * Fires off a cull traversal using the indicated camera. */ @@ -2804,26 +2734,9 @@ thread_main() { do_pending(_engine, current_thread); break; - case TS_do_compute: - nassertd(_gsg != nullptr && _state != nullptr) break; - { - const ShaderAttrib *sattr; - _state->get_attrib(sattr); - _gsg->push_group_marker(std::string("Compute ") + sattr->get_shader()->get_filename(Shader::ST_compute).get_basename()); - _gsg->set_state_and_transform(_state, TransformState::make_identity()); - _gsg->dispatch_compute(_work_groups[0], _work_groups[1], _work_groups[2]); - _gsg->pop_group_marker(); - } - break; - - case TS_do_extract: - nassertd(_gsg != nullptr && _texture != nullptr) break; - _result = _gsg->extract_texture_data(_texture); - break; - - case TS_do_screenshot: - nassertd(_region != nullptr) break; - _texture = _region->get_screenshot(); + case TS_callback: + nassertd(_callback != nullptr) break; + _callback(this); break; case TS_terminate: @@ -2848,3 +2761,39 @@ thread_main() { } } } + +/** + * Waits for this thread to become idle, then runs the given function on it. + */ +void GraphicsEngine::RenderThread:: +run_on_thread(Callback *callback, void *callback_data, void *return_data) { + MutexHolder cv_holder(_cv_mutex); + + while (_thread_state != TS_wait) { + _cv_done.wait(); + } + + // Temporarily set this so that it accesses data from the current thread. + int pipeline_stage = Thread::get_current_pipeline_stage(); + int thread_pipeline_stage = get_pipeline_stage(); + set_pipeline_stage(pipeline_stage); + + // Now that the draw thread is idle, signal it to run the callback. + _callback = callback; + _callback_data = callback_data; + _return_data = return_data; + _thread_state = TS_callback; + _cv_mutex.release(); + _cv_start.notify(); + _cv_mutex.acquire(); + + // Wait for it to finish the job. + while (_thread_state != TS_wait) { + _cv_done.wait(); + } + + set_pipeline_stage(thread_pipeline_stage); + _callback = nullptr; + _callback_data = nullptr; + _return_data = nullptr; +} diff --git a/panda/src/display/graphicsEngine.h b/panda/src/display/graphicsEngine.h index f0ebeb5a66..9c982364c0 100644 --- a/panda/src/display/graphicsEngine.h +++ b/panda/src/display/graphicsEngine.h @@ -33,6 +33,9 @@ #include "loader.h" #include "referenceCount.h" #include "renderState.h" +#include "clockObject.h" + +#include class Pipeline; class DisplayRegion; @@ -53,7 +56,8 @@ class Texture; */ class EXPCL_PANDA_DISPLAY GraphicsEngine : public ReferenceCount { PUBLISHED: - explicit GraphicsEngine(Pipeline *pipeline = nullptr); + explicit GraphicsEngine(ClockObject *clock = ClockObject::get_global_clock(), + Pipeline *pipeline = nullptr); BLOCKING ~GraphicsEngine(); void set_threading_model(const GraphicsThreadingModel &threading_model); @@ -111,6 +115,11 @@ PUBLISHED: BLOCKING void flip_frame(); bool extract_texture_data(Texture *tex, GraphicsStateGuardian *gsg); + vector_uchar extract_shader_buffer_data(ShaderBuffer *buffer, GraphicsStateGuardian *gsg, + size_t start = 0, size_t size = (size_t)-1); + bool update_shader_buffer_data(ShaderBuffer *buffer, GraphicsStateGuardian *gsg, + const vector_uchar &data, size_t offset = 0); + void dispatch_compute(const LVecBase3i &work_groups, const RenderState *state, GraphicsStateGuardian *gsg); @@ -127,15 +136,17 @@ public: TS_do_flip, TS_do_release, TS_do_windows, - TS_do_compute, - TS_do_extract, - TS_do_screenshot, + TS_callback, TS_terminate, TS_done }; void texture_uploaded(Texture *tex); - PT(Texture) do_get_screenshot(DisplayRegion *region, GraphicsStateGuardian *gsg); + +#ifndef CPPPARSER + template + INLINE auto run_on_draw_thread(Callable &&callable) -> decltype(callable()); +#endif public: static void do_cull(CullHandler *cull_handler, SceneSetup *scene_setup, @@ -302,24 +313,37 @@ private: RenderThread(const std::string &name, GraphicsEngine *engine); virtual void thread_main(); + typedef void Callback(RenderThread *thread); + void run_on_thread(Callback *callback, + void *callback_data = nullptr, + void *return_data = nullptr); + +#ifndef CPPPARSER + template + INLINE auto run_on_thread(Callable &&callable) -> + typename std::enable_if::value, decltype(callable())>::type; + + template + INLINE auto run_on_thread(Callable &&callable) -> + typename std::enable_if::value, decltype(callable())>::type; +#endif + GraphicsEngine *_engine; Mutex _cv_mutex; ConditionVar _cv_start; ConditionVar _cv_done; ThreadState _thread_state; - // These are stored for extract_texture_data and dispatch_compute. - GraphicsStateGuardian *_gsg; - PT(Texture) _texture; - const RenderState *_state; - DisplayRegion *_region; - LVecBase3i _work_groups; - bool _result; + // Used for TS_callback. + Callback *_callback; + void *_callback_data; + void *_return_data; }; WindowRenderer *get_window_renderer(const std::string &name, int pipeline_stage); Pipeline *_pipeline; + ClockObject *const _clock; Windows _windows; bool _windows_sorted; diff --git a/panda/src/display/graphicsStateGuardian.I b/panda/src/display/graphicsStateGuardian.I index 09dd8c60b4..b7ff6eb2db 100644 --- a/panda/src/display/graphicsStateGuardian.I +++ b/panda/src/display/graphicsStateGuardian.I @@ -596,7 +596,7 @@ get_supports_tessellation_shaders() const { */ INLINE bool GraphicsStateGuardian:: get_supports_compute_shaders() const { - return _supports_compute_shaders; + return _max_compute_work_group_invocations > 0; } /** @@ -709,6 +709,45 @@ get_supports_dual_source_blending() const { return _supports_dual_source_blending; } +/** + * Returns the maximum number of work groups that can be submitted in a single + * compute dispatch. + * + * If compute shaders are supported, this will be at least 65535x65535x65535. + * Otherwise, it will be zero. + */ +INLINE LVecBase3i GraphicsStateGuardian:: +get_max_compute_work_group_count() const { + return _max_compute_work_group_count; +} + +/** + * Returns the maximum number of invocations in each work group split out + * separately to every x, y, z dimension. This limit applies in addition to + * the overall number of invocations, which is specified by + * get_max_compute_work_group_invocations(). + * + * If compute shaders are supported, this will be at least 128x128x64. + * Otherwise, it will be zero. + */ +INLINE LVecBase3i GraphicsStateGuardian:: +get_max_compute_work_group_size() const { + return _max_compute_work_group_size; +} + +/** + * Returns the maximum number of invocations in each work group as a product + * of the x, y, z dimensions. This limit applies in addition to the + * per-dimension limits specified by get_max_compute_work_group_size(). + * + * If compute shaders are supported, this will be at least 128. Otherwise, it + * will be zero. + */ +INLINE int GraphicsStateGuardian:: +get_max_compute_work_group_invocations() const { + return _max_compute_work_group_invocations; +} + /** * Deprecated. Use get_max_color_targets() instead, which returns the exact * same value. diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx index 11bffeb4a4..409b8ce440 100644 --- a/panda/src/display/graphicsStateGuardian.cxx +++ b/panda/src/display/graphicsStateGuardian.cxx @@ -100,6 +100,7 @@ PStatCollector GraphicsStateGuardian::_draw_primitive_pcollector("Draw:Primitive PStatCollector GraphicsStateGuardian::_draw_set_state_pcollector("Draw:Set State"); PStatCollector GraphicsStateGuardian::_flush_pcollector("Draw:Flush"); PStatCollector GraphicsStateGuardian::_compute_dispatch_pcollector("Draw:Compute dispatch"); +PStatCollector GraphicsStateGuardian::_compute_work_groups_pcollector("Compute work groups"); PStatCollector GraphicsStateGuardian::_wait_occlusion_pcollector("Wait:Occlusion"); PStatCollector GraphicsStateGuardian::_wait_timer_pcollector("Wait:Timer Queries"); @@ -244,10 +245,13 @@ GraphicsStateGuardian(CoordinateSystem internal_coordinate_system, _supports_basic_shaders = false; _supports_geometry_shaders = false; _supports_tessellation_shaders = false; - _supports_compute_shaders = false; _supports_glsl = false; _supports_hlsl = false; + _max_compute_work_group_count = LVecBase3i(0, 0, 0); + _max_compute_work_group_size = LVecBase3i(0, 0, 0); + _max_compute_work_group_invocations = 0; + _supports_stencil = false; _supports_stencil_wrap = false; _supports_two_sided_stencil = false; @@ -572,6 +576,23 @@ update_texture(TextureContext *, bool) { return true; } +/** + * Ensures that the current Texture data is refreshed onto the GSG. This + * means updating the texture properties and/or re-uploading the texture + * image, if necessary. This should only be called within the draw thread. + * + * If force is true, this function will not return until the texture has been + * fully uploaded. If force is false, the function may choose to upload a + * simple version of the texture instead, if the texture is not fully resident + * (and if get_incomplete_render() is true). + */ +bool GraphicsStateGuardian:: +update_texture(TextureContext *tc, bool force, CompletionToken token) { + bool result = update_texture(tc, force); + token.complete(result); + return result; +} + /** * Frees the resources previously allocated via a call to prepare_texture(), * including deleting the TextureContext itself, if it is non-NULL. @@ -745,6 +766,42 @@ release_shader_buffers(const pvector &contexts) { } } +/** + * This method should only be called by the GraphicsEngine. Do not call it + * directly; call GraphicsEngine::update_shader_buffer_data() instead. + * + * This method will be called in the draw thread to upload data to (a part of) + * the shader buffer from the CPU. If data is null, clears the buffer instead. + */ +bool GraphicsStateGuardian:: +update_shader_buffer_data(ShaderBuffer *buffer, size_t start, size_t size, + const unsigned char *data) { + return false; +} + +/** + * This method should only be called by the GraphicsEngine. Do not call it + * directly; call GraphicsEngine::extract_texture_data() instead. + * + * This method will be called in the draw thread to download the buffer's + * current contents synchronously. + */ +bool GraphicsStateGuardian:: +extract_shader_buffer_data(ShaderBuffer *buffer, vector_uchar &data, + size_t start, size_t size) { + return false; +} + +/** + * Asynchronous version of extract_shader_buffer_data. It is the caller's + * responsibility that the data argument outlasts the token. + */ +void GraphicsStateGuardian:: +async_extract_shader_buffer_data(ShaderBuffer *buffer, vector_uchar &data, + size_t start, size_t size, CompletionToken token) { + token.complete(extract_shader_buffer_data(buffer, data, start, size)); +} + /** * Begins a new occlusion query. After this call, you may call * begin_draw_primitives() and draw_triangles()/draw_whatever() repeatedly. @@ -2463,7 +2520,7 @@ fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler, return default_normal_height_tex; } - case Shader::STO_stage_selector_i: + case Shader::STO_stage_metallic_roughness_i: { const TextureAttrib *texattrib; if (_target_rs->get_attrib(texattrib)) { @@ -2472,7 +2529,8 @@ fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler, TextureStage *stage = texattrib->get_on_stage(i); TextureStage::Mode mode = stage->get_mode(); - if (mode == TextureStage::M_selector) { + if (mode == TextureStage::M_metallic_roughness || + mode == TextureStage::M_occlusion_metallic_roughness) { if (si++ == spec._stage) { sampler = texattrib->get_on_sampler(stage); view += stage->get_tex_view_offset(); @@ -2505,6 +2563,28 @@ fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler, } break; + case Shader::STO_stage_occlusion_i: + { + const TextureAttrib *texattrib; + if (_target_rs->get_attrib(texattrib)) { + int si = 0; + for (int i = 0; i < texattrib->get_num_on_stages(); ++i) { + TextureStage *stage = texattrib->get_on_stage(i); + TextureStage::Mode mode = stage->get_mode(); + + if (mode == TextureStage::M_occlusion || + mode == TextureStage::M_occlusion_metallic_roughness) { + if (si++ == spec._stage) { + sampler = texattrib->get_on_sampler(stage); + view += stage->get_tex_view_offset(); + return texattrib->get_on_texture(stage); + } + } + } + } + } + break; + default: nassertr(false, nullptr); break; @@ -2748,6 +2828,7 @@ end_frame(Thread *current_thread) { _vertices_tri_pcollector.flush_level(); _vertices_patch_pcollector.flush_level(); _vertices_other_pcollector.flush_level(); + _compute_work_groups_pcollector.flush_level(); _state_pcollector.flush_level(); _texture_state_pcollector.flush_level(); @@ -3396,6 +3477,7 @@ init_frame_pstats() { _vertices_tri_pcollector.clear_level(); _vertices_patch_pcollector.clear_level(); _vertices_other_pcollector.clear_level(); + _compute_work_groups_pcollector.clear_level(); _state_pcollector.clear_level(); _transform_state_pcollector.clear_level(); diff --git a/panda/src/display/graphicsStateGuardian.h b/panda/src/display/graphicsStateGuardian.h index 711ab5d74e..8d62382f18 100644 --- a/panda/src/display/graphicsStateGuardian.h +++ b/panda/src/display/graphicsStateGuardian.h @@ -114,6 +114,7 @@ PUBLISHED: GraphicsEngine *get_engine() const; INLINE const GraphicsThreadingModel &get_threading_model() const; MAKE_PROPERTY(pipe, get_pipe); + MAKE_PROPERTY(engine, get_engine); INLINE bool is_hardware() const; virtual INLINE bool prefers_triangle_strips() const; @@ -175,6 +176,12 @@ PUBLISHED: INLINE int get_maximum_simultaneous_render_targets() const; INLINE bool get_supports_dual_source_blending() const; +public: + INLINE LVecBase3i get_max_compute_work_group_count() const; + INLINE LVecBase3i get_max_compute_work_group_size() const; + INLINE int get_max_compute_work_group_invocations() const; + +PUBLISHED: MAKE_PROPERTY(max_vertices_per_array, get_max_vertices_per_array); MAKE_PROPERTY(max_vertices_per_primitive, get_max_vertices_per_primitive); MAKE_PROPERTY(max_texture_stages, get_max_texture_stages); @@ -221,6 +228,9 @@ PUBLISHED: MAKE_PROPERTY(timer_queries_active, get_timer_queries_active); MAKE_PROPERTY(max_color_targets, get_max_color_targets); MAKE_PROPERTY(supports_dual_source_blending, get_supports_dual_source_blending); + MAKE_PROPERTY(max_compute_work_group_count, get_max_compute_work_group_count); + MAKE_PROPERTY(max_compute_work_group_size, get_max_compute_work_group_size); + MAKE_PROPERTY(max_compute_work_group_invocations, get_max_compute_work_group_invocations); INLINE ShaderModel get_shader_model() const; INLINE void set_shader_model(ShaderModel shader_model); @@ -291,6 +301,7 @@ PUBLISHED: public: virtual TextureContext *prepare_texture(Texture *tex); virtual bool update_texture(TextureContext *tc, bool force); + virtual bool update_texture(TextureContext *tc, bool force, CompletionToken token); virtual void release_texture(TextureContext *tc); virtual void release_textures(const pvector &contexts); virtual bool extract_texture_data(Texture *tex); @@ -315,6 +326,13 @@ public: virtual BufferContext *prepare_shader_buffer(ShaderBuffer *data); virtual void release_shader_buffer(BufferContext *ibc); virtual void release_shader_buffers(const pvector &contexts); + virtual bool update_shader_buffer_data(ShaderBuffer *buffer, size_t start, + size_t size, const unsigned char *data); + virtual bool extract_shader_buffer_data(ShaderBuffer *buffer, vector_uchar &data, + size_t start = 0, size_t size = (size_t)-1); + virtual void async_extract_shader_buffer_data(ShaderBuffer *buffer, vector_uchar &data, + size_t start = 0, size_t size = (size_t)-1, + CompletionToken token = CompletionToken()); virtual void begin_occlusion_query(); virtual PT(OcclusionQueryContext) end_occlusion_query(); @@ -617,12 +635,15 @@ protected: bool _supports_basic_shaders; bool _supports_geometry_shaders; bool _supports_tessellation_shaders; - bool _supports_compute_shaders; bool _supports_glsl; bool _supports_hlsl; bool _supports_framebuffer_multisample; bool _supports_framebuffer_blit; + LVecBase3i _max_compute_work_group_count; + LVecBase3i _max_compute_work_group_size; + int _max_compute_work_group_invocations; + bool _supports_stencil; bool _supports_stencil_wrap; bool _supports_two_sided_stencil; @@ -691,6 +712,7 @@ public: static PStatCollector _draw_set_state_pcollector; static PStatCollector _flush_pcollector; static PStatCollector _compute_dispatch_pcollector; + static PStatCollector _compute_work_groups_pcollector; static PStatCollector _wait_occlusion_pcollector; static PStatCollector _wait_timer_pcollector; static PStatCollector _timer_queries_pcollector; diff --git a/panda/src/display/subprocessWindowBuffer.cxx b/panda/src/display/subprocessWindowBuffer.cxx index 168e7f0758..39771551d9 100644 --- a/panda/src/display/subprocessWindowBuffer.cxx +++ b/panda/src/display/subprocessWindowBuffer.cxx @@ -110,7 +110,7 @@ new_buffer(int &fd, size_t &mmap_size, string &filename, mmap_size = temp._mmap_size; // Ensure the disk file is large enough. - size_t zero_size = 1024; + const size_t zero_size = 1024; char zero[zero_size]; memset(zero, 0, zero_size); for (size_t bi = 0; bi < mmap_size; bi += zero_size) { diff --git a/panda/src/doc/eggSyntax.txt b/panda/src/doc/eggSyntax.txt index b81eb45c0e..c8225a66df 100644 --- a/panda/src/doc/eggSyntax.txt +++ b/panda/src/doc/eggSyntax.txt @@ -339,7 +339,10 @@ appear before they are referenced. *GLOW *GLOSS *HEIGHT - *SELECTOR + *EMISSION + *METALLIC_ROUGHNESS + *OCCLUSION + *OCCLUSION_METALLIC_ROUGHNESS The default environment type is MODULATE, which means the texture color is multiplied with the base polygon (or vertex) color. This diff --git a/panda/src/downloader/httpChannel_emscripten.cxx b/panda/src/downloader/httpChannel_emscripten.cxx index 859166fbb7..3054d065f7 100644 --- a/panda/src/downloader/httpChannel_emscripten.cxx +++ b/panda/src/downloader/httpChannel_emscripten.cxx @@ -373,9 +373,11 @@ run_headers_received() { var xhr = window._httpChannels[$0]; var loaded = 0; xhr.onprogress = function (ev) { - var chunk = this.responseText.slice(loaded, ev.loaded); - var ptr = __extend_string($1, chunk.length); - writeAsciiToMemory(chunk, ptr, true); + var body = this.responseText; + var ptr = __extend_string($1, ev.loaded - loaded); + for (var i = loaded; i < ev.loaded; ++i) { + HEAPU8[ptr + i] = body.charCodeAt(i); + } loaded = ev.loaded; }; }, this, dest); @@ -388,8 +390,11 @@ run_headers_received() { var loaded = 0; xhr.onprogress = function (ev) { while (loaded < ev.loaded) { + var body = this.responseText; var size = Math.min(ev.loaded - read, 4096); - writeAsciiToMemory(this.responseText.substr(read, size), $2, true); + for (var i = read; i < read + size; ++i) { + HEAPU8[$2 + i] = body.charCodeAt(i); + } __write_stream($1, $2, size); loaded += size; } @@ -700,7 +705,9 @@ download_to_ram(Ramfile *ramfile, bool subdocument_resumes) { var state = xhr.readyState; var body = xhr.responseText; var ptr = __extend_string($1, body.length); - writeAsciiToMemory(body, ptr, true); + for (var i = 0; i < body.length; ++i) { + HEAPU8[ptr + i] = body.charCodeAt(i); + } return state; }, this, &ramfile->_data); diff --git a/panda/src/downloader/virtualFileHTTP.cxx b/panda/src/downloader/virtualFileHTTP.cxx index ee36ea95bd..d96a948ae4 100644 --- a/panda/src/downloader/virtualFileHTTP.cxx +++ b/panda/src/downloader/virtualFileHTTP.cxx @@ -39,8 +39,7 @@ VirtualFileHTTP(VirtualFileMountHTTP *mount, const Filename &local_filename, _implicit_pz_file(implicit_pz_file), _status_only(open_flags != 0) { - URLSpec url(_mount->get_root()); - url.set_path(_mount->get_root().get_path() + _local_filename.c_str()); + URLSpec url = get_url(); _channel = _mount->get_channel(); if (_status_only) { _channel->get_header(url); @@ -89,6 +88,16 @@ get_filename() const { } } +/** + * Returns the full URL of this file. + */ +URLSpec VirtualFileHTTP:: +get_url() const { + URLSpec url(_mount->get_root()); + url.set_path(_mount->get_root().get_path() + _local_filename.c_str()); + return url; +} + /** * Returns true if this file exists, false otherwise. */ diff --git a/panda/src/downloader/virtualFileHTTP.h b/panda/src/downloader/virtualFileHTTP.h index 49e5f4bd19..ed9bdfa04e 100644 --- a/panda/src/downloader/virtualFileHTTP.h +++ b/panda/src/downloader/virtualFileHTTP.h @@ -39,6 +39,7 @@ public: virtual VirtualFileSystem *get_file_system() const; virtual Filename get_filename() const; + URLSpec get_url() const; virtual bool has_file() const; virtual bool is_directory() const; @@ -54,6 +55,9 @@ public: virtual bool read_file(std::string &result, bool auto_unwrap) const; virtual bool read_file(vector_uchar &result, bool auto_unwrap) const; +PUBLISHED: + MAKE_PROPERTY(url, get_url); + private: bool fetch_file(std::ostream *buffer_stream) const; std::istream *return_file(std::istream *buffer_stream, bool auto_unwrap) const; diff --git a/panda/src/egg/eggTexture.cxx b/panda/src/egg/eggTexture.cxx index 6c78d57b3d..8b4e261455 100644 --- a/panda/src/egg/eggTexture.cxx +++ b/panda/src/egg/eggTexture.cxx @@ -561,11 +561,11 @@ affects_polygon_alpha() const { case ET_height: case ET_normal_gloss: case ET_emission: + case ET_occlusion: + case ET_metallic_roughness: + case ET_occlusion_metallic_roughness: return false; - case ET_selector: - return true; - case ET_unspecified: break; } @@ -883,8 +883,9 @@ string_env_type(const string &string) { } else if (cmp_nocase_uh(string, "height") == 0) { return ET_height; - } else if (cmp_nocase_uh(string, "selector") == 0) { - return ET_selector; + } else if (cmp_nocase_uh(string, "metallic_roughness") == 0 || + cmp_nocase_uh(string, "selector") == 0) { + return ET_metallic_roughness; } else if (cmp_nocase_uh(string, "normal_gloss") == 0) { return ET_normal_gloss; @@ -892,6 +893,12 @@ string_env_type(const string &string) { } else if (cmp_nocase_uh(string, "emission") == 0) { return ET_emission; + } else if (cmp_nocase_uh(string, "occlusion") == 0) { + return ET_occlusion; + + } else if (cmp_nocase_uh(string, "occlusion_metallic_roughness") == 0) { + return ET_occlusion_metallic_roughness; + } else { return ET_unspecified; } @@ -1321,14 +1328,20 @@ ostream &operator << (ostream &out, EggTexture::EnvType type) { case EggTexture::ET_height: return out << "height"; - case EggTexture::ET_selector: - return out << "selector"; + case EggTexture::ET_metallic_roughness: + return out << "metallic_roughness"; case EggTexture::ET_normal_gloss: return out << "normal_gloss"; case EggTexture::ET_emission: return out << "emission"; + + case EggTexture::ET_occlusion: + return out << "occlusion"; + + case EggTexture::ET_occlusion_metallic_roughness: + return out << "occlusion_metallic_roughness"; } nassertr(false, out); diff --git a/panda/src/egg/eggTexture.h b/panda/src/egg/eggTexture.h index e67cc2cdb3..8a8435c9e9 100644 --- a/panda/src/egg/eggTexture.h +++ b/panda/src/egg/eggTexture.h @@ -106,9 +106,12 @@ PUBLISHED: ET_glow, ET_gloss, ET_height, - ET_selector, + ET_metallic_roughness, ET_normal_gloss, ET_emission, + ET_occlusion, + ET_occlusion_metallic_roughness, + ET_selector = ET_metallic_roughness, }; enum CombineMode { CM_unspecified, diff --git a/panda/src/egg2pg/eggLoader.cxx b/panda/src/egg2pg/eggLoader.cxx index e0dca70801..2b5a037cab 100644 --- a/panda/src/egg2pg/eggLoader.cxx +++ b/panda/src/egg2pg/eggLoader.cxx @@ -1579,8 +1579,8 @@ make_texture_stage(const EggTexture *egg_tex) { stage->set_mode(TextureStage::M_height); break; - case EggTexture::ET_selector: - stage->set_mode(TextureStage::M_selector); + case EggTexture::ET_metallic_roughness: + stage->set_mode(TextureStage::M_metallic_roughness); break; case EggTexture::ET_normal_gloss: @@ -1591,6 +1591,14 @@ make_texture_stage(const EggTexture *egg_tex) { stage->set_mode(TextureStage::M_emission); break; + case EggTexture::ET_occlusion: + stage->set_mode(TextureStage::M_occlusion); + break; + + case EggTexture::ET_occlusion_metallic_roughness: + stage->set_mode(TextureStage::M_occlusion_metallic_roughness); + break; + case EggTexture::ET_unspecified: break; } diff --git a/panda/src/egg2pg/eggSaver.cxx b/panda/src/egg2pg/eggSaver.cxx index c44aedd12a..141a09549d 100644 --- a/panda/src/egg2pg/eggSaver.cxx +++ b/panda/src/egg2pg/eggSaver.cxx @@ -852,8 +852,8 @@ convert_primitive(const GeomVertexData *vertex_data, case TextureStage::M_height: egg_tex->set_env_type(EggTexture::ET_height); break; - case TextureStage::M_selector: - egg_tex->set_env_type(EggTexture::ET_selector); + case TextureStage::M_metallic_roughness: + egg_tex->set_env_type(EggTexture::ET_metallic_roughness); break; case TextureStage::M_normal_gloss: egg_tex->set_env_type(EggTexture::ET_normal_gloss); @@ -861,6 +861,12 @@ convert_primitive(const GeomVertexData *vertex_data, case TextureStage::M_emission: egg_tex->set_env_type(EggTexture::ET_emission); break; + case TextureStage::M_occlusion: + egg_tex->set_env_type(EggTexture::ET_occlusion); + break; + case TextureStage::M_occlusion_metallic_roughness: + egg_tex->set_env_type(EggTexture::ET_occlusion_metallic_roughness); + break; default: break; } @@ -1229,34 +1235,16 @@ apply_state_properties(EggRenderMode *egg_render_mode, const RenderState *state) */ bool EggSaver:: apply_tags(EggGroup *egg_group, PandaNode *node) { - std::ostringstream strm; - char delimiter = '\n'; - string delimiter_str(1, delimiter); - node->list_tags(strm, delimiter_str); - - string data = strm.str(); - if (data.empty()) { - return false; - } - bool any_applied = false; - size_t p = 0; - size_t q = data.find(delimiter); - while (q != string::npos) { - string tag = data.substr(p, q); - if (apply_tag(egg_group, node, tag)) { + vector_string keys; + node->get_tag_keys(keys); + + for (size_t i = 0; i < keys.size(); ++i) { + if (apply_tag(egg_group, node, keys[i])) { any_applied = true; } - p = q + 1; - q = data.find(delimiter, p); } - - string tag = data.substr(p); - if (apply_tag(egg_group, node, tag)) { - any_applied = true; - } - return any_applied; } diff --git a/panda/src/egldisplay/eglGraphicsBuffer.cxx b/panda/src/egldisplay/eglGraphicsBuffer.cxx index a71f562611..b4fd714659 100644 --- a/panda/src/egldisplay/eglGraphicsBuffer.cxx +++ b/panda/src/egldisplay/eglGraphicsBuffer.cxx @@ -190,7 +190,8 @@ open_buffer() { // If the old gsg has the wrong pixel format, create a new one that shares // with the old gsg. DCAST_INTO_R(eglgsg, _gsg, false); - if (!eglgsg->get_fb_properties().subsumes(_fb_properties)) { + if (eglgsg->get_engine() != _engine || + !eglgsg->get_fb_properties().subsumes(_fb_properties)) { eglgsg = new eglGraphicsStateGuardian(_engine, _pipe, eglgsg); eglgsg->choose_pixel_format(_fb_properties, egl_pipe, false, true, false); _gsg = eglgsg; diff --git a/panda/src/egldisplay/eglGraphicsPipe.cxx b/panda/src/egldisplay/eglGraphicsPipe.cxx index aaa6ae0af7..ee03a267c0 100644 --- a/panda/src/egldisplay/eglGraphicsPipe.cxx +++ b/panda/src/egldisplay/eglGraphicsPipe.cxx @@ -288,6 +288,9 @@ make_output(const std::string &name, ((flags&BF_require_window)!=0)) { return nullptr; } + if (host->get_engine() != engine) { + return nullptr; + } // Early failure - if we are sure that this buffer WONT meet specs, we can // bail out early. if ((flags & BF_fb_props_optional) == 0) { diff --git a/panda/src/egldisplay/eglGraphicsWindow.cxx b/panda/src/egldisplay/eglGraphicsWindow.cxx index e82ea6862f..65d8812de9 100644 --- a/panda/src/egldisplay/eglGraphicsWindow.cxx +++ b/panda/src/egldisplay/eglGraphicsWindow.cxx @@ -223,7 +223,8 @@ open_window() { // If the old gsg has the wrong pixel format, create a new one that shares // with the old gsg. DCAST_INTO_R(eglgsg, _gsg, false); - if (!eglgsg->get_fb_properties().subsumes(_fb_properties)) { + if (eglgsg->get_engine() != _engine || + !eglgsg->get_fb_properties().subsumes(_fb_properties)) { eglgsg = new eglGraphicsStateGuardian(_engine, _pipe, eglgsg); eglgsg->choose_pixel_format(_fb_properties, egl_pipe, true, false, false); _gsg = eglgsg; diff --git a/panda/src/event/asyncFuture.cxx b/panda/src/event/asyncFuture.cxx index d8b8b91d97..a6aa341c07 100644 --- a/panda/src/event/asyncFuture.cxx +++ b/panda/src/event/asyncFuture.cxx @@ -389,6 +389,17 @@ wake_task(AsyncTask *task) { } } +/** + * Internal callback called when a CompletionToken created from this future + * completes. + */ +void AsyncFuture:: +token_callback(Completable::Data *data, bool success) { + AsyncFuture *future = (AsyncFuture *)data; + future->set_result(EventParameter(success)); + unref_delete(future); +} + /** * @see AsyncFuture::gather */ diff --git a/panda/src/event/asyncFuture.h b/panda/src/event/asyncFuture.h index acb6b1020f..f9f71472f8 100644 --- a/panda/src/event/asyncFuture.h +++ b/panda/src/event/asyncFuture.h @@ -20,6 +20,7 @@ #include "eventParameter.h" #include "patomic.h" #include "small_vector.h" +#include "completionToken.h" class AsyncTaskManager; class AsyncTask; @@ -58,7 +59,7 @@ class AsyncTask; * * @since 1.10.0 */ -class EXPCL_PANDA_EVENT AsyncFuture : public TypedReferenceCount { +class EXPCL_PANDA_EVENT AsyncFuture : public TypedReferenceCount, protected Completable::Data { PUBLISHED: INLINE AsyncFuture(); virtual ~AsyncFuture(); @@ -109,6 +110,8 @@ public: private: void wake_task(AsyncTask *task); + static void token_callback(Completable::Data *, bool success); + protected: enum FutureState : patomic_unsigned_lock_free::value_type { // Pending states @@ -136,6 +139,7 @@ protected: friend class AsyncGatheringFuture; friend class AsyncTaskChain; + friend class CompletionToken; friend class PythonTask; public: @@ -199,6 +203,33 @@ private: static TypeHandle _type_handle; }; +#ifndef CPPPARSER +// Allow passing a future into a method accepting a CompletionToken. +template<> +INLINE CompletionToken:: +CompletionToken(AsyncFuture *future) { + if (future != nullptr) { + future->ref(); + _callback._data = future; + if (_callback._data->_function == nullptr) { + _callback._data->_function = &AsyncFuture::token_callback; + } + } +} + +template<> +INLINE CompletionToken:: +CompletionToken(PT(AsyncFuture) future) { + if (future != nullptr) { + _callback._data = future; + if (_callback._data->_function == nullptr) { + _callback._data->_function = &AsyncFuture::token_callback; + } + future.cheat() = nullptr; + } +} +#endif + #include "asyncFuture.I" #endif // !ASYNCFUTURE_H diff --git a/panda/src/event/asyncFuture_ext.cxx b/panda/src/event/asyncFuture_ext.cxx index 5ec7d080cb..c181085280 100644 --- a/panda/src/event/asyncFuture_ext.cxx +++ b/panda/src/event/asyncFuture_ext.cxx @@ -63,13 +63,13 @@ static PyObject *get_done_result(const AsyncFuture *future) { // If it's an AsyncGatheringFuture, get the result for each future. const AsyncGatheringFuture *gather = (const AsyncGatheringFuture *)future; Py_ssize_t num_futures = (Py_ssize_t)gather->get_num_futures(); - PyObject *results = PyTuple_New(num_futures); + PyObject *results = PyList_New(num_futures); for (Py_ssize_t i = 0; i < num_futures; ++i) { PyObject *result = get_done_result(gather->get_future((size_t)i)); if (result != nullptr) { // This steals a reference. - PyTuple_SET_ITEM(results, i, result); + PyList_SET_ITEM(results, i, result); } else { Py_DECREF(results); return nullptr; @@ -145,9 +145,30 @@ static PyObject *gen_next_asyncfuture(PyObject *self) { else { PyObject *result = get_done_result(future); if (result != nullptr) { - PyErr_SetObject(PyExc_StopIteration, result); - // PyErr_SetObject increased the reference count, so we no longer need our reference. + // See python/cpython#101578 - PyErr_SetObject has a special case where + // it interprets a tuple specially, so we bypass that by creating the + // exception directly. +#if PY_VERSION_HEX >= 0x030C0000 // 3.12 + PyObject *exc = PyObject_CallOneArg(PyExc_StopIteration, result); Py_DECREF(result); + if (LIKELY(exc != nullptr)) { + // This function steals a reference to exc. + PyErr_SetRaisedException(exc); + } +#else + if (PyTuple_Check(result)) { + PyObject *exc = PyObject_CallOneArg(PyExc_StopIteration, result); + Py_DECREF(result); + if (LIKELY(exc != nullptr)) { + PyErr_SetObject(PyExc_StopIteration, exc); + Py_DECREF(exc); + } + } else { + PyErr_SetObject(PyExc_StopIteration, result); + // PyErr_SetObject increased the reference count, so we no longer need our reference. + Py_DECREF(result); + } +#endif } return nullptr; } @@ -311,8 +332,7 @@ add_done_callback(PyObject *self, PyObject *fn) { } PythonTask *task = new PythonTask(fn); - Py_DECREF(task->_args); - task->_args = PyTuple_Pack(1, self); + task->_args.assign(1, Py_NewRef(self)); task->_append_task = false; task->_ignore_return = true; @@ -352,7 +372,7 @@ gather(PyObject *args) { futures.push_back(fut); continue; } - } else if (PyCoro_CheckExact(item)) { + } else if (PyObject_TypeCheck(item, &PyCoro_Type)) { // We allow passing in a coroutine instead of a future. This causes it // to be scheduled as a task on the current task manager. PT(AsyncTask) task = new PythonTask(item); diff --git a/panda/src/event/asyncTaskChain.cxx b/panda/src/event/asyncTaskChain.cxx index a854596898..9ab2093fa3 100644 --- a/panda/src/event/asyncTaskChain.cxx +++ b/panda/src/event/asyncTaskChain.cxx @@ -1438,6 +1438,9 @@ AsyncTaskChainThread(const string &name, AsyncTaskChain *chain) : void AsyncTaskChain::AsyncTaskChainThread:: thread_main() { #ifdef HAVE_THREADS + // Let PStats know this thread exists. + PStatClient::thread_tick(); + MutexHolder holder(_chain->_manager->_lock); while (_chain->_state != S_shutdown && _chain->_state != S_interrupted) { thread_consider_yield(); diff --git a/panda/src/event/eventParameter.I b/panda/src/event/eventParameter.I index 1d3d168344..61cbacb0b8 100644 --- a/panda/src/event/eventParameter.I +++ b/panda/src/event/eventParameter.I @@ -63,6 +63,11 @@ EventParameter(const std::string &value) : _ptr(new EventStoreString(value)) { } INLINE EventParameter:: EventParameter(const std::wstring &value) : _ptr(new EventStoreWstring(value)) { } +/** + * Defines an EventParameter that stores a bytes value. + */ +INLINE EventParameter:: +EventParameter(const vector_uchar &value) : _ptr(new ParamBytes(value)) { } /** * @@ -185,6 +190,28 @@ get_wstring_value() const { return ((const EventStoreWstring *)_ptr.p())->get_value(); } +/** + * Returns true if the EventParameter stores a binary bytes value, false + * otherwise. + */ +INLINE bool EventParameter:: +is_bytes() const { + if (is_empty()) { + return false; + } + return _ptr->is_of_type(ParamBytes::get_class_type()); +} + +/** + * Retrieves the value stored in the EventParameter. It is only valid to call + * this if is_bytes() has already returned true. + */ +INLINE vector_uchar EventParameter:: +get_bytes_value() const { + nassertr(is_bytes(), vector_uchar()); + return ((const ParamBytes *)_ptr.p())->get_value(); +} + /** * Returns true if the EventParameter stores a TypedReferenceCount pointer, * false otherwise. Note that a TypedReferenceCount is not exactly the same diff --git a/panda/src/event/eventParameter.h b/panda/src/event/eventParameter.h index 5f817fc149..ef5a1e15c4 100644 --- a/panda/src/event/eventParameter.h +++ b/panda/src/event/eventParameter.h @@ -42,6 +42,7 @@ PUBLISHED: INLINE EventParameter(double value); INLINE EventParameter(const std::string &value); INLINE EventParameter(const std::wstring &value); + INLINE EventParameter(const vector_uchar &value); INLINE EventParameter(const EventParameter ©); INLINE EventParameter &operator = (const EventParameter ©); @@ -60,6 +61,8 @@ PUBLISHED: INLINE std::string get_string_value() const; INLINE bool is_wstring() const; INLINE std::wstring get_wstring_value() const; + INLINE bool is_bytes() const; + INLINE vector_uchar get_bytes_value() const; INLINE bool is_typed_ref_count() const; INLINE TypedReferenceCount *get_typed_ref_count_value() const; diff --git a/panda/src/event/pythonTask.cxx b/panda/src/event/pythonTask.cxx index d2515a9312..264442d27e 100644 --- a/panda/src/event/pythonTask.cxx +++ b/panda/src/event/pythonTask.cxx @@ -37,7 +37,6 @@ PythonTask:: PythonTask(PyObject *func_or_coro, const std::string &name) : AsyncTask(name), _function(nullptr), - _args(nullptr), _upon_death(nullptr), _owner(nullptr), _exception(nullptr), @@ -45,6 +44,7 @@ PythonTask(PyObject *func_or_coro, const std::string &name) : _exc_traceback(nullptr), _generator(nullptr), _fut_waiter(nullptr), + _append_task(true), _ignore_return(false), _registered_to_owner(false), _retrieved_exception(false) { @@ -53,19 +53,16 @@ PythonTask(PyObject *func_or_coro, const std::string &name) : if (func_or_coro == Py_None || PyCallable_Check(func_or_coro)) { _function = Py_NewRef(func_or_coro); } - else if (PyCoro_CheckExact(func_or_coro)) { - // We also allow passing in a coroutine, because why not. - _generator = Py_NewRef(func_or_coro); - } - else if (PyGen_CheckExact(func_or_coro)) { - // Something emulating a coroutine. + else if (PyCoro_CheckExact(func_or_coro) || + PyGen_CheckExact(func_or_coro) || + PyType_IsSubtype(Py_TYPE(func_or_coro), &PyCoro_Type)) { + // We also allow passing in a coroutine or something emulating it. _generator = Py_NewRef(func_or_coro); } else { nassert_raise("Invalid function passed to PythonTask"); } - set_args(Py_None, true); set_upon_death(Py_None); set_owner(Py_None); @@ -106,7 +103,9 @@ PythonTask:: // All of these may have already been cleared by __clear__. Py_XDECREF(_function); - Py_XDECREF(_args); + for (PyObject *arg : _args) { + Py_DECREF(arg); + } Py_XDECREF(__dict__); Py_XDECREF(_exception); Py_XDECREF(_exc_value); @@ -136,21 +135,23 @@ set_function(PyObject *function) { */ void PythonTask:: set_args(PyObject *args, bool append_task) { - Py_XDECREF(_args); - _args = nullptr; + pvector old_args = std::exchange(_args, {}); - if (args == Py_None) { - // None means no arguments; create an empty tuple. - _args = PyTuple_New(0); - } else { - if (PySequence_Check(args)) { - _args = PySequence_Tuple(args); + if (PySequence_Check(args)) { + Py_ssize_t len = PySequence_Size(args); + _args.resize(len); + + for (Py_ssize_t i = 0; i < len; ++i) { + _args[i] = PySequence_GetItem(args, i); } } - - if (_args == nullptr) { + else if (args != Py_None) { nassert_raise("Invalid args passed to PythonTask"); - _args = PyTuple_New(0); + return; + } + + for (PyObject *old_arg : old_args) { + Py_DECREF(old_arg); } _append_task = append_task; @@ -161,19 +162,18 @@ set_args(PyObject *args, bool append_task) { */ PyObject *PythonTask:: get_args() { + // If we want to append the task, we have to create a new tuple with space + // for one more at the end. We have to do this dynamically each time, to + // avoid storing the task itself in its own arguments list, and thereby + // creating a cyclical reference. + + size_t num_args = _args.size(); + PyObject *result = PyTuple_New(num_args + _append_task); + for (size_t i = 0; i < num_args; ++i) { + PyTuple_SET_ITEM(result, i, Py_NewRef(_args[i])); + } + if (_append_task) { - // If we want to append the task, we have to create a new tuple with space - // for one more at the end. We have to do this dynamically each time, to - // avoid storing the task itself in its own arguments list, and thereby - // creating a cyclical reference. - - int num_args = PyTuple_GET_SIZE(_args); - PyObject *with_task = PyTuple_New(num_args + 1); - for (int i = 0; i < num_args; ++i) { - PyObject *item = PyTuple_GET_ITEM(_args, i); - PyTuple_SET_ITEM(with_task, i, Py_NewRef(item)); - } - // Check whether we have a Python wrapper. This is not the case if the // object has been created by C++ and never been exposed to Python code. if (__self__ == nullptr) { @@ -182,12 +182,9 @@ get_args() { __self__ = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false); } - PyTuple_SET_ITEM(with_task, num_args, Py_NewRef(__self__)); - return with_task; - } - else { - return Py_NewRef(_args); + PyTuple_SET_ITEM(result, num_args, Py_NewRef(__self__)); } + return result; } /** @@ -364,7 +361,7 @@ __getattribute__(PyObject *self, PyObject *attr) const { // We consult the instance dict first, since the user may have overridden a // method or something. PyObject *item; - if (PyDict_GetItemRef(__dict__, attr, &item) > 0) { + if (PyDict_GetItemRef(__dict__, attr, &item) != 0) { return item; } @@ -378,7 +375,9 @@ int PythonTask:: __traverse__(visitproc visit, void *arg) { Py_VISIT(__self__); Py_VISIT(_function); - Py_VISIT(_args); + for (PyObject *arg : _args) { + Py_VISIT(arg); + } Py_VISIT(_upon_death); Py_VISIT(_owner); Py_VISIT(__dict__); @@ -392,7 +391,12 @@ __traverse__(visitproc visit, void *arg) { int PythonTask:: __clear__() { Py_CLEAR(_function); - Py_CLEAR(_args); + { + pvector old_args = std::exchange(_args, {}); + for (PyObject *arg : old_args) { + Py_DECREF(arg); + } + } Py_CLEAR(_upon_death); Py_CLEAR(_owner); Py_CLEAR(__dict__); @@ -503,7 +507,7 @@ cancel() { --_chain->_num_awaiting_tasks; return true; } - else if (must_cancel || _fut_waiter != nullptr) { + else if (_generator != nullptr && (must_cancel || _fut_waiter != nullptr)) { // We may be polling an external future, so we still need to throw a // CancelledException and allow it to be caught. if (must_cancel) { @@ -592,9 +596,21 @@ do_python_task() { // We are calling the function directly. nassertr(_function != nullptr, DS_interrupt); - PyObject *args = get_args(); - result = PythonThread::call_python_func(_function, args); - Py_DECREF(args); + size_t nargs = _args.size(); + PyObject **args = (PyObject **)alloca(sizeof(PyObject *) * (nargs + 2)) + 1; + std::copy(_args.begin(), _args.end(), args); + + if (_append_task) { + // Check whether we have a Python wrapper. This is not the case if the + // object has been created by C++ and never been exposed to Python code. + if (__self__ == nullptr) { + // A __self__ instance does not exist, let's create one now. + ref(); + __self__ = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false); + } + args[nargs++] = __self__; + } + result = PythonThread::call_python_func(_function, args, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET); if (result != nullptr && PyGen_Check(result)) { // The function has yielded a generator. We will call into that @@ -620,7 +636,7 @@ do_python_task() { Py_DECREF(str); Py_DECREF(str2); } - if (PyCoro_CheckExact(result)) { + if (PyObject_TypeCheck(result, &PyCoro_Type)) { // If a coroutine, am_await is possible but senseless, since we can // just call send(None) on the coroutine itself. _generator = result; @@ -638,17 +654,31 @@ do_python_task() { // We are calling a generator. Use "send" rather than PyIter_Next since // we need to be able to read the value from a StopIteration exception. PyObject *func = PyObject_GetAttrString(_generator, "send"); - nassertr(func != nullptr, DS_interrupt); - result = PyObject_CallOneArg(func, Py_None); - Py_DECREF(func); + if (func != nullptr) { + result = PyObject_CallOneArg(func, Py_None); + Py_DECREF(func); + } else { + // It has no send(), just call next() directly. + nassertr(Py_TYPE(_generator)->tp_iternext != nullptr, DS_interrupt); + PyErr_Clear(); + result = Py_TYPE(_generator)->tp_iternext(_generator); + } } else { // Throw a CancelledError into the generator. _must_cancel = false; - PyObject *exc = PyObject_CallNoArgs(Extension::get_cancelled_error_type()); + PyObject *exc_type = Extension::get_cancelled_error_type(); PyObject *func = PyObject_GetAttrString(_generator, "throw"); - result = PyObject_CallFunctionObjArgs(func, exc, nullptr); - Py_DECREF(func); - Py_DECREF(exc); + if (func != nullptr) { + PyObject *exc = PyObject_CallNoArgs(exc_type); + result = PyObject_CallOneArg(func, exc); + Py_DECREF(exc); + Py_DECREF(func); + } else { + // If it has no throw(), assume it propagated the CancelledException. + PyErr_Clear(); + PyErr_SetNone(exc_type); + result = nullptr; + } } if (result == nullptr) { @@ -733,7 +763,7 @@ do_python_task() { } } else if (result == Py_None) { - // Bare yield means to continue next frame. + // Bare yield from a coroutine means to continue next frame. Py_DECREF(result); return DS_cont; diff --git a/panda/src/event/pythonTask.h b/panda/src/event/pythonTask.h index 148252e999..67ae3ed17a 100644 --- a/panda/src/event/pythonTask.h +++ b/panda/src/event/pythonTask.h @@ -114,7 +114,7 @@ private: private: PyObject *_function; - PyObject *_args; + pvector _args; PyObject *_upon_death; PyObject *_owner; diff --git a/panda/src/express/memoryUsage.cxx b/panda/src/express/memoryUsage.cxx index 21161a025b..d5a7bb4a72 100644 --- a/panda/src/express/memoryUsage.cxx +++ b/panda/src/express/memoryUsage.cxx @@ -826,7 +826,8 @@ ns_record_void_pointer(void *ptr, size_t size) { if (_track_memory_usage) { if (express_cat.is_spam()) { express_cat.spam() - << "Recording void pointer " << (void *)ptr << "\n"; + << "Recording void pointer " << (void *)ptr + << " with size " << size << "\n"; } // We have to protect modifications to the table from recursive calls by @@ -846,11 +847,15 @@ ns_record_void_pointer(void *ptr, size_t size) { MemoryInfo *info = (*insert_result.first).second; - // We shouldn't already have a void pointer. + // If we already have a void pointer, that's okay, as long as the size + // matches. if (info->_void_ptr != nullptr) { - express_cat.error() - << "Void pointer " << (void *)ptr << " recorded twice!\n"; - nassertv(false); + if (info->_size != size) { + express_cat.error() + << "Void pointer " << (void *)ptr + << " was already recorded with size " << size << "!\n"; + nassertv(false); + } } if (info->_freeze_index == _freeze_index) { diff --git a/panda/src/express/multifile_ext.I b/panda/src/express/multifile_ext.I index 68e427af2e..0774a3745b 100644 --- a/panda/src/express/multifile_ext.I +++ b/panda/src/express/multifile_ext.I @@ -24,8 +24,11 @@ set_encryption_password(PyObject *encryption_password) const { // Have we been passed a string? if (PyUnicode_Check(encryption_password)) { const char *pass_str = PyUnicode_AsUTF8AndSize(encryption_password, &pass_len); + if (pass_str == NULL) { + return NULL; + } _this->set_encryption_password(std::string(pass_str, pass_len)); - return Dtool_Return_None(); + Py_RETURN_NONE; } // Have we been passed a bytes object? @@ -46,7 +49,7 @@ set_encryption_password(PyObject *encryption_password) const { } _this->set_encryption_password(std::string(pass_str, pass_len)); - return Dtool_Return_None(); + Py_RETURN_NONE; } return Dtool_Raise_BadArgumentsError( diff --git a/panda/src/express/pointerToArray_ext.I b/panda/src/express/pointerToArray_ext.I index 849f993cba..74188b9497 100644 --- a/panda/src/express/pointerToArray_ext.I +++ b/panda/src/express/pointerToArray_ext.I @@ -106,6 +106,9 @@ __init__(PyObject *self, PyObject *source) { // We need to initialize the this pointer before we can call push_back. DtoolInstance_INIT_PTR(self, this->_this); + PyObject *args[3]; + args[1] = self; + Py_BEGIN_CRITICAL_SECTION(source); Py_ssize_t size = PySequence_Size(source); this->_this->reserve(size); @@ -114,7 +117,8 @@ __init__(PyObject *self, PyObject *source) { if (item == nullptr) { break; } - PyObject *result = PyObject_CallFunctionObjArgs(push_back, self, item, nullptr); + args[2] = item; + PyObject *result = _PyObject_Vectorcall(push_back, args + 1, 2 | PY_VECTORCALL_ARGUMENTS_OFFSET, nullptr); Py_DECREF(item); if (result == nullptr) { // Unable to add item--probably it wasn't of the appropriate type. diff --git a/panda/src/express/trueClock.cxx b/panda/src/express/trueClock.cxx index 157bac657e..986d2f3c64 100644 --- a/panda/src/express/trueClock.cxx +++ b/panda/src/express/trueClock.cxx @@ -521,6 +521,7 @@ TrueClock() { #include // for perror static long _init_sec; +static time_t _init_sec_monotonic = 0; /** * @@ -553,25 +554,13 @@ get_long_time() { */ double TrueClock:: get_short_raw_time() { - struct timeval tv; - - int result; - -#ifdef GETTIMEOFDAY_ONE_PARAM - result = gettimeofday(&tv); -#else - result = gettimeofday(&tv, nullptr); -#endif - - if (result < 0) { - // Error in gettimeofday(). - return 0.0; +#if defined(CLOCK_MONOTONIC) && !defined(__APPLE__) + struct timespec spec; + if (clock_gettime(CLOCK_MONOTONIC, &spec) == 0) { + return (double)(spec.tv_sec - _init_sec_monotonic) + (double)spec.tv_nsec / 1000000000.0; } - - // We subtract out the time at which the clock was initialized, because we - // don't care about the number of seconds all the way back to 1970, and we - // want to leave the double with as much precision as it can get. - return (double)(tv.tv_sec - _init_sec) + (double)tv.tv_usec / 1000000.0; +#endif + return get_long_time(); } /** @@ -603,6 +592,16 @@ TrueClock() { } else { _init_sec = tv.tv_sec; } + +#if defined(CLOCK_MONOTONIC) && !defined(__APPLE__) + struct timespec spec; + if (clock_gettime(CLOCK_MONOTONIC, &spec) == 0) { + _init_sec_monotonic = spec.tv_sec; + } else { + perror("clock_gettime(CLOCK_MONOTONIC)"); + _init_sec_monotonic = 0; + } +#endif } #endif diff --git a/panda/src/express/virtualFileMountAndroidAsset.h b/panda/src/express/virtualFileMountAndroidAsset.h index 1caccf98f5..d640e8612e 100644 --- a/panda/src/express/virtualFileMountAndroidAsset.h +++ b/panda/src/express/virtualFileMountAndroidAsset.h @@ -74,7 +74,6 @@ private: private: AAsset *_asset; - off_t _offset; friend class VirtualFileMountAndroidAsset; }; diff --git a/panda/src/express/zipArchive.cxx b/panda/src/express/zipArchive.cxx index 06bd4f9e28..9008143159 100644 --- a/panda/src/express/zipArchive.cxx +++ b/panda/src/express/zipArchive.cxx @@ -28,6 +28,11 @@ #include "openSSLWrapper.h" +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#define EVP_MD_CTX_new() EVP_MD_CTX_create() +#define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx) +#endif + using std::streamoff; using std::streampos; using std::streamsize; @@ -312,7 +317,7 @@ close() { */ std::string ZipArchive:: add_subfile(const std::string &subfile_name, const Filename &filename, - int compression_level) { + int compression_level, size_t data_alignment) { nassertr(is_write_valid(), std::string()); #ifndef HAVE_ZLIB @@ -332,7 +337,7 @@ add_subfile(const std::string &subfile_name, const Filename &filename, return std::string(); } - std::string name = add_subfile(subfile_name, in, compression_level); + std::string name = add_subfile(subfile_name, in, compression_level, data_alignment); vfs->close_read_file(in); return name; } @@ -351,7 +356,7 @@ add_subfile(const std::string &subfile_name, const Filename &filename, */ std::string ZipArchive:: add_subfile(const std::string &subfile_name, std::istream *subfile_data, - int compression_level) { + int compression_level, size_t data_alignment) { nassertr(is_write_valid(), string()); #ifndef HAVE_ZLIB @@ -362,14 +367,14 @@ add_subfile(const std::string &subfile_name, std::istream *subfile_data, std::string name = standardize_subfile_name(subfile_name); if (!name.empty()) { - Subfile *subfile = new Subfile(subfile_name, compression_level); + Subfile *subfile = new Subfile(subfile_name, compression_level, data_alignment); // Write it straight away, overwriting the index at the end of the file. // This index will be rewritten at the next call to flush() or close(). std::streampos fpos = _index_start; _write->seekp(fpos); - if (!subfile->write_header(*_write, fpos)) { + if (!subfile->write_header(*_write, fpos, data_alignment)) { delete subfile; return ""; } @@ -401,7 +406,7 @@ add_subfile(const std::string &subfile_name, std::istream *subfile_data, */ string ZipArchive:: update_subfile(const std::string &subfile_name, const Filename &filename, - int compression_level) { + int compression_level, size_t data_alignment) { nassertr(is_write_valid(), string()); #ifndef HAVE_ZLIB @@ -426,7 +431,7 @@ update_subfile(const std::string &subfile_name, const Filename &filename, // The subfile does not already exist or it is different from the source // file. Add the new source file. - Subfile *subfile = new Subfile(name, compression_level); + Subfile *subfile = new Subfile(name, compression_level, data_alignment); add_new_subfile(subfile, compression_level); } @@ -571,11 +576,11 @@ add_jar_signature(X509 *cert, EVP_PKEY *pkey, const std::string &alias) { const std::string header_digest = "VmrRqAIgAm0FCZViZFzpaP8OfDbN4iY0MyYFuzTMPv8="; std::stringstream manifest; - SHA256_CTX manifest_ctx; - SHA256_Init(&manifest_ctx); + EVP_MD_CTX *manifest_ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(manifest_ctx, EVP_sha256(), nullptr); manifest << header; - SHA256_Update(&manifest_ctx, header.data(), header.size()); + EVP_DigestUpdate(manifest_ctx, header.data(), header.size()); std::ostringstream sigfile_body; @@ -594,20 +599,21 @@ add_jar_signature(X509 *cert, EVP_PKEY *pkey, const std::string &alias) { { std::istream *stream = open_read_subfile(subfile); - SHA256_CTX subfile_ctx; - SHA256_Init(&subfile_ctx); + EVP_MD_CTX *subfile_ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(subfile_ctx, EVP_sha256(), nullptr); char buffer[4096]; stream->read(buffer, sizeof(buffer)); size_t count = stream->gcount(); while (count > 0) { - SHA256_Update(&subfile_ctx, buffer, count); + EVP_DigestUpdate(subfile_ctx, buffer, count); stream->read(buffer, sizeof(buffer)); count = stream->gcount(); } delete stream; - SHA256_Final(digest, &subfile_ctx); + EVP_DigestFinal_ex(subfile_ctx, digest, nullptr); + EVP_MD_CTX_free(subfile_ctx); } // Encode to base64. @@ -616,24 +622,21 @@ add_jar_signature(X509 *cert, EVP_PKEY *pkey, const std::string &alias) { // Encode what we just wrote to the manifest file as well. { unsigned char digest[SHA256_DIGEST_LENGTH]; - - SHA256_CTX section_ctx; - SHA256_Init(§ion_ctx); - SHA256_Update(§ion_ctx, section.data(), section.size()); - SHA256_Final(digest, §ion_ctx); + EVP_Digest(section.data(), section.size(), digest, nullptr, EVP_sha256(), nullptr); sigfile_body << "SHA-256-Digest: " << base64_encode(digest, SHA256_DIGEST_LENGTH) << "\r\n\r\n"; } manifest << section; - SHA256_Update(&manifest_ctx, section.data(), section.size()); + EVP_DigestUpdate(manifest_ctx, section.data(), section.size()); } // The hash for the whole manifest file goes at the beginning of the .SF file. std::stringstream sigfile; { unsigned char digest[SHA256_DIGEST_LENGTH]; - SHA256_Final(digest, &manifest_ctx); + EVP_DigestFinal_ex(manifest_ctx, digest, nullptr); + EVP_MD_CTX_free(manifest_ctx); sigfile << "Signature-Version: 1.0\r\n"; sigfile << "SHA-256-Digest-Manifest-Main-Attributes: " << header_digest << "\r\n"; sigfile << "SHA-256-Digest-Manifest: " << base64_encode(digest, SHA256_DIGEST_LENGTH) << "\r\n\r\n"; @@ -761,7 +764,7 @@ repack() { // the checksum and sizes. subfile->_flags &= ~SF_data_descriptor; - if (!subfile->write_header(temp, fpos)) { + if (!subfile->write_header(temp, fpos, subfile->_data_alignment)) { success = false; continue; } @@ -1666,10 +1669,11 @@ write_index(std::ostream &write, std::streampos &fpos) { * Creates a new subfile record. */ ZipArchive::Subfile:: -Subfile(const std::string &name, int compression_level) : +Subfile(const std::string &name, int compression_level, size_t data_alignment) : _name(name), _timestamp(dos_epoch), - _compression_method((compression_level > 0) ? CM_deflate : CM_store) + _compression_method((compression_level > 0) ? CM_deflate : CM_store), + _data_alignment(data_alignment) { // If the name contains any non-ASCII characters, we set the UTF-8 flag. for (char c : name) { @@ -2093,7 +2097,7 @@ write_index(std::ostream &write, streampos &fpos) { * than the actual size of the subfile). */ bool ZipArchive::Subfile:: -write_header(std::ostream &write, std::streampos &fpos) { +write_header(std::ostream &write, std::streampos &fpos, size_t data_alignment) { nassertr(write.tellp() == fpos, false); std::string encoded_name; @@ -2106,13 +2110,29 @@ write_header(std::ostream &write, std::streampos &fpos) { std::streamoff header_size = 30 + encoded_name.size(); StreamWriter writer(write); - int modulo = (fpos + header_size) % 4; - if (!is_compressed() && modulo != 0) { + if (!is_compressed()) { // Align uncompressed files to 4-byte boundary. We don't really need to do // this, but it's needed when producing .apk files, and it doesn't really // cause harm to do it in other cases as well. - writer.pad_bytes(4 - modulo); - fpos += (4 - modulo); + if (data_alignment < 4) { + data_alignment = 4; + } + else if ((data_alignment % 4) != 0) { + data_alignment *= 2; + if ((data_alignment % 4) != 0) { + data_alignment *= 2; + } + } + } + + if (data_alignment > 0) { + // The data follows the header directly, so the actual padding has to be + // inserted before the header. + int modulo = (fpos + header_size) % data_alignment; + if (modulo != 0) { + writer.pad_bytes(data_alignment - modulo); + fpos += (data_alignment - modulo); + } } _header_start = fpos; diff --git a/panda/src/express/zipArchive.h b/panda/src/express/zipArchive.h index 8cfc49ac94..23a17594c6 100644 --- a/panda/src/express/zipArchive.h +++ b/panda/src/express/zipArchive.h @@ -66,11 +66,11 @@ PUBLISHED: INLINE bool get_record_timestamp() const; std::string add_subfile(const std::string &subfile_name, const Filename &filename, - int compression_level); + int compression_level, size_t data_alignment=0); std::string add_subfile(const std::string &subfile_name, std::istream *subfile_data, - int compression_level); + int compression_level, size_t data_alignment=0); std::string update_subfile(const std::string &subfile_name, const Filename &filename, - int compression_level); + int compression_level, size_t data_alignment=0); #ifdef HAVE_OPENSSL bool add_jar_signature(const Filename &certificate, const Filename &pkey, @@ -152,7 +152,8 @@ private: class Subfile { public: Subfile() = default; - Subfile(const std::string &name, int compression_level); + Subfile(const std::string &name, int compression_level, + size_t data_alignment=0); INLINE bool operator < (const Subfile &other) const; @@ -160,7 +161,7 @@ private: bool read_header(std::istream &read); bool verify_data(std::istream &read); bool write_index(std::ostream &write, std::streampos &fpos); - bool write_header(std::ostream &write, std::streampos &fpos); + bool write_header(std::ostream &write, std::streampos &fpos, size_t data_alignment=0); bool write_data(std::ostream &write, std::istream *read, std::streampos &fpos, int compression_level); INLINE bool is_compressed() const; @@ -180,6 +181,7 @@ private: std::string _comment; int _flags = SF_data_descriptor; CompressionMethod _compression_method = CM_store; + size_t _data_alignment = 0; }; void add_new_subfile(Subfile *subfile, int compression_level); diff --git a/panda/src/ffmpeg/ffmpegAudioCursor.cxx b/panda/src/ffmpeg/ffmpegAudioCursor.cxx index 69635549a6..89301ffa17 100644 --- a/panda/src/ffmpeg/ffmpegAudioCursor.cxx +++ b/panda/src/ffmpeg/ffmpegAudioCursor.cxx @@ -15,6 +15,7 @@ #include "ffmpegAudioCursor.h" #include "ffmpegAudio.h" +#include "vector_string.h" extern "C" { #include #include @@ -508,3 +509,19 @@ read_samples(int n, int16_t *data) { _samples_read += n; return n; } + +/** + * + */ +vector_string FfmpegAudioCursor:: +get_raw_comment() const { + AVDictionaryEntry *tag = nullptr; + vector_string comment_strings; + while ((tag = av_dict_get(_format_ctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + std::string comment(tag->key); + comment.append("="); + comment.append(tag->value); + comment_strings.push_back(std::move(comment)); + } + return comment_strings; +} diff --git a/panda/src/ffmpeg/ffmpegAudioCursor.h b/panda/src/ffmpeg/ffmpegAudioCursor.h index 77e8890d6c..02c53df700 100644 --- a/panda/src/ffmpeg/ffmpegAudioCursor.h +++ b/panda/src/ffmpeg/ffmpegAudioCursor.h @@ -21,6 +21,7 @@ #include "texture.h" #include "pointerTo.h" #include "ffmpegVirtualFile.h" +#include "vector_string.h" extern "C" { #include @@ -43,6 +44,7 @@ PUBLISHED: FfmpegAudioCursor(FfmpegAudio *src); virtual ~FfmpegAudioCursor(); virtual void seek(double offset); + vector_string get_raw_comment() const; public: virtual int read_samples(int n, int16_t *data); diff --git a/panda/src/gles2gsg/gles2gsg.h b/panda/src/gles2gsg/gles2gsg.h index 967f341703..04ad8f8257 100644 --- a/panda/src/gles2gsg/gles2gsg.h +++ b/panda/src/gles2gsg/gles2gsg.h @@ -147,6 +147,11 @@ typedef char GLchar; #define GL_FRAMEBUFFER_BARRIER_BIT 0x400 #define GL_TRANSFORM_FEEDBACK_BARRIER_BIT 0x800 #define GL_ATOMIC_COUNTER_BARRIER_BIT 0x1000 +#define GL_SHADER_STORAGE_BARRIER_BIT 0x2000 +#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 +#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 #define GL_HALF_FLOAT 0x140B #define GL_COLOR 0x1800 #define GL_DEPTH 0x1801 @@ -174,6 +179,7 @@ typedef char GLchar; #define GL_RG32UI 0x823C #define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257 #define GL_MAX_VERTEX_ATTRIB_STRIDE 0x82E5 +#define GL_TEXTURE_COMPRESSED 0x86A1 #define GL_PROGRAM_BINARY_LENGTH 0x8741 #define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE #define GL_PROGRAM_BINARY_FORMATS 0x87FF @@ -181,6 +187,7 @@ typedef char GLchar; #define GL_WRITE_ONLY 0x88B9 #define GL_READ_WRITE 0x88BA #define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC #define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF #define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 #define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 @@ -247,6 +254,10 @@ typedef char GLchar; #define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4 #define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7 #define GL_UNSIGNED_INT_SAMPLER_BUFFER 0x8DD8 +#define GL_COPY_READ_BUFFER 0x8F36 +#define GL_COPY_READ_BUFFER_BINDING 0x8F36 +#define GL_COPY_WRITE_BUFFER 0x8F36 +#define GL_COPY_WRITE_BUFFER_BINDING 0x8F36 #define GL_MAX_IMAGE_UNITS 0x8F38 #define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009 #define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY 0x900A @@ -272,6 +283,11 @@ typedef char GLchar; #define GL_UNSIGNED_INT_IMAGE_BUFFER 0x9067 #define GL_UNSIGNED_INT_IMAGE_2D_ARRAY 0x9069 #define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY 0x906A +#define GL_SHADER_STORAGE_BUFFER 0x90D2 +#define GL_SHADER_STORAGE_BUFFER_BINDING 0x90D3 +#define GL_SHADER_STORAGE_BUFFER_START 0x90D4 +#define GL_SHADER_STORAGE_BUFFER_SIZE 0x90D5 +#define GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 0x90EB #define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117 #define GL_UNSIGNALED 0x9118 #define GL_SIGNALED 0x9119 @@ -279,6 +295,8 @@ typedef char GLchar; #define GL_TIMEOUT_EXPIRED 0x911B #define GL_CONDITION_SATISFIED 0x911C #define GL_COMPUTE_SHADER 0x91B9 +#define GL_MAX_COMPUTE_WORK_GROUP_COUNT 0x91BE +#define GL_MAX_COMPUTE_WORK_GROUP_SIZE 0x91BF #define GL_FRAMEBUFFER_DEFAULT_WIDTH 0x9310 #define GL_FRAMEBUFFER_DEFAULT_HEIGHT 0x9311 #define GL_FRAMEBUFFER_DEFAULT_SAMPLES 0x9313 diff --git a/panda/src/glstuff/glBufferContext_src.cxx b/panda/src/glstuff/glBufferContext_src.cxx index f2e588f945..8fc4ae62a7 100644 --- a/panda/src/glstuff/glBufferContext_src.cxx +++ b/panda/src/glstuff/glBufferContext_src.cxx @@ -47,3 +47,13 @@ evict_lru() { update_data_size_bytes(0); set_resident(false); } + +/** + * Returns an implementation-defined handle or pointer that can be used + * to interface directly with the underlying API. + * Returns 0 if the underlying implementation does not support this. + */ +uint64_t CLP(BufferContext):: +get_native_id() const { + return _index; +} diff --git a/panda/src/glstuff/glBufferContext_src.h b/panda/src/glstuff/glBufferContext_src.h index b67a9d214e..f51cd590f5 100644 --- a/panda/src/glstuff/glBufferContext_src.h +++ b/panda/src/glstuff/glBufferContext_src.h @@ -27,11 +27,17 @@ public: virtual void evict_lru(); + virtual uint64_t get_native_id() const; + CLP(GraphicsStateGuardian) *_glgsg; // This is the GL "name" of the data object. GLuint _index; + // This is set to glgsg->_shader_storage_barrier_counter if a write was + // performed, in which case a barrier is issued before the next use. + int _shader_storage_barrier_counter = -1; + public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/glstuff/glCgShaderContext_src.cxx b/panda/src/glstuff/glCgShaderContext_src.cxx index 50e72f86a4..299ee1cab7 100644 --- a/panda/src/glstuff/glCgShaderContext_src.cxx +++ b/panda/src/glstuff/glCgShaderContext_src.cxx @@ -388,14 +388,17 @@ valid() { * all of the shader's input parameters. */ void CLP(CgShaderContext):: -bind() { +bind(GraphicsStateGuardian *gsg) { + CLP(GraphicsStateGuardian) *glgsg = (CLP(GraphicsStateGuardian) *)gsg; + _glgsg = glgsg; + if (_cg_program != 0) { // Bind the shaders. cgGLEnableProgramProfiles(_cg_program); cgGLBindProgram(_cg_program); cg_report_errors(); - _glgsg->report_my_gl_errors(); + glgsg->report_my_gl_errors(); } } diff --git a/panda/src/glstuff/glCgShaderContext_src.h b/panda/src/glstuff/glCgShaderContext_src.h index a9b4157c6e..97c11fe253 100644 --- a/panda/src/glstuff/glCgShaderContext_src.h +++ b/panda/src/glstuff/glCgShaderContext_src.h @@ -34,7 +34,7 @@ public: ALLOC_DELETED_CHAIN(CLP(CgShaderContext)); bool valid(void) override; - void bind() override; + void bind(GraphicsStateGuardian *gsg) override; void unbind() override; void set_state_and_transform(const RenderState *state, diff --git a/panda/src/glstuff/glGraphicsBuffer_src.cxx b/panda/src/glstuff/glGraphicsBuffer_src.cxx index cd87938288..9a38590b8b 100644 --- a/panda/src/glstuff/glGraphicsBuffer_src.cxx +++ b/panda/src/glstuff/glGraphicsBuffer_src.cxx @@ -281,7 +281,7 @@ begin_frame(FrameMode mode, Thread *current_thread) { CLP(GraphicsStateGuardian) *glgsg = (CLP(GraphicsStateGuardian) *)_gsg.p(); for (CLP(TextureContext) *gtc : _texture_contexts) { - if (gtc->needs_barrier(GL_FRAMEBUFFER_BARRIER_BIT)) { + if (gtc->needs_barrier(GL_FRAMEBUFFER_BARRIER_BIT, true)) { glgsg->issue_memory_barrier(GL_FRAMEBUFFER_BARRIER_BIT); // If we've done it for one, we've done it for all. break; @@ -419,6 +419,7 @@ rebuild_bitplanes() { Texture *attach[RTP_COUNT]; memset(attach, 0, sizeof(Texture *) * RTP_COUNT); _texture_contexts.clear(); + _textures.clear(); // Sort the textures list into appropriate slots. { @@ -458,15 +459,17 @@ rebuild_bitplanes() { } // If we can't bind this type of texture, punt it. - if ((tex->get_texture_type() != Texture::TT_2d_texture) && - (tex->get_texture_type() != Texture::TT_3d_texture) && - (tex->get_texture_type() != Texture::TT_2d_texture_array) && - (tex->get_texture_type() != Texture::TT_cube_map)) { + Texture::TextureType texture_type = tex->get_texture_type(); + if (texture_type != Texture::TT_2d_texture && + texture_type != Texture::TT_3d_texture && + texture_type != Texture::TT_2d_texture_array && + texture_type != Texture::TT_cube_map && + texture_type != Texture::TT_cube_map_array) { ((CData *)cdata.p())->_textures[i]._rtm_mode = RTM_copy_texture; continue; } - if (_rb_size_z > 1 && tex->get_texture_type() == Texture::TT_2d_texture) { + if (_rb_size_z > 1 && texture_type == Texture::TT_2d_texture) { // We can't bind a 2D texture to a layered FBO. If the user happened // to request RTM_bind_layered for a 2D texture, that's just silly, // and we can't render to anything but the first layer anyway. @@ -520,6 +523,18 @@ rebuild_bitplanes() { // but it's a waste. Let's not do it unless the user requested stencil. _use_depth_stencil = false; +#ifdef __APPLE__ + } else if (_fb_properties.get_depth_bits() > 0 && _requested_multisamples) { + // Apple's OpenGL driver doesn't like blitting depth-stencil targets. + // See GitHub issue #1719 + _use_depth_stencil = false; + if (_fb_properties.get_depth_bits() < 24) { + // Make sure we do get at least as many depth bits as we would have + // gotten if we did get a depth-stencil buffer. + _fb_properties.set_depth_bits(24); + } +#endif + } else if (_fb_properties.get_depth_bits() > 0) { // Let's use a depth stencil buffer by default, if a depth buffer was // requested. @@ -625,10 +640,23 @@ rebuild_bitplanes() { if (_have_any_color || have_any_depth) { // Clear if the fbo was just created, regardless of the clear settings per - // frame. + // frame. However, we don't do this for textures, which may have useful + // contents that need to be preserved. if (_initial_clear) { - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + GLbitfield mask = 0; + if (_rb[RTP_color]) { + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + mask |= GL_COLOR_BUFFER_BIT; + } + if (_rb[RTP_depth]) { + mask |= GL_DEPTH_BUFFER_BIT; + } + if (_rb[RTP_depth_stencil]) { + mask |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + } + if (mask != 0) { + glClear(mask); + } } #ifndef OPENGLES_1 } else if (glgsg->_supports_empty_framebuffer) { @@ -793,6 +821,12 @@ bind_slot(int layer, bool rb_resize, Texture **attach, RenderTexturePlane slot, _fb_properties.setup_color_texture(tex); } + if (slot == RTP_color && !tex->has_clear_color()) { + tex->set_clear_color(LColor(0, 0, 0, 1)); + } + + _textures.push_back(tex); + TextureContext *tc = tex->prepare_now(glgsg->get_prepared_objects(), glgsg); nassertv(tc != nullptr); CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc); @@ -1132,7 +1166,9 @@ bind_slot(int layer, bool rb_resize, Texture **attach, RenderTexturePlane slot, if (slot == RTP_depth_stencil) { if (GLCAT.is_debug()) { - GLCAT.debug() << "Creating depth stencil renderbuffer.\n"; + GLCAT.debug() + << "Creating depth stencil renderbuffer with format 0x" << std::hex + << gl_format << std::dec << ".\n"; } // Allocate renderbuffer storage for depth stencil. GLint depth_size = 0, stencil_size = 0; @@ -1160,7 +1196,9 @@ bind_slot(int layer, bool rb_resize, Texture **attach, RenderTexturePlane slot, } else if (slot == RTP_depth) { if (GLCAT.is_debug()) { - GLCAT.debug() << "Creating depth renderbuffer.\n"; + GLCAT.debug() + << "Creating depth renderbuffer with format 0x" << std::hex + << gl_format << std::dec << ".\n"; } // Allocate renderbuffer storage for regular depth. GLint depth_size = 0; @@ -1176,6 +1214,11 @@ bind_slot(int layer, bool rb_resize, Texture **attach, RenderTexturePlane slot, } else { gl_format = GL_DEPTH_COMPONENT32F_NV; } + if (GLCAT.is_debug()) { + GLCAT.debug() + << "GL_DEPTH_COMPONENT32 not supported, switching to format 0x" + << std::hex << gl_format << std::dec << " instead.\n"; + } glgsg->_glRenderbufferStorage(GL_RENDERBUFFER_EXT, gl_format, _rb_size_x, _rb_size_y); glgsg->_glGetRenderbufferParameteriv(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_DEPTH_SIZE_EXT, &depth_size); @@ -1200,7 +1243,9 @@ bind_slot(int layer, bool rb_resize, Texture **attach, RenderTexturePlane slot, } else { if (GLCAT.is_debug()) { - GLCAT.debug() << "Creating color renderbuffer.\n"; + GLCAT.debug() + << "Creating color renderbuffer with format 0x" << std::hex + << gl_format << std::dec << ".\n"; } glgsg->_glRenderbufferStorage(GL_RENDERBUFFER_EXT, gl_format, _rb_size_x, _rb_size_y); @@ -1248,12 +1293,33 @@ bind_slot_multisample(bool rb_resize, Texture **attach, RenderTexturePlane slot, #ifndef OPENGLES_2 if (_use_depth_stencil) { glgsg->_glBindRenderbuffer(GL_RENDERBUFFER_EXT, _rbm[slot]); + GLuint format; +#ifdef OPENGLES_1 + format = GL_DEPTH24_STENCIL8_OES; +#else + if (_fb_properties.get_depth_bits() > 24 || + _fb_properties.get_float_depth()) { + if (!glgsg->_use_remapped_depth_range) { + format = GL_DEPTH32F_STENCIL8; + } else { + format = GL_DEPTH32F_STENCIL8_NV; + } + } else { + format = GL_DEPTH24_STENCIL8; + } +#endif + if (GLCAT.is_debug()) { + GLCAT.debug() + << "Creating depth stencil renderbuffer with format 0x" << std::hex + << format << std::dec << " and " << _requested_multisamples + << " multisamples.\n"; + } if (_requested_coverage_samples) { glgsg->_glRenderbufferStorageMultisampleCoverage(GL_RENDERBUFFER_EXT, _requested_coverage_samples, - _requested_multisamples, GL_DEPTH_STENCIL_EXT, + _requested_multisamples, format, _rb_size_x, _rb_size_y); } else { - glgsg->_glRenderbufferStorageMultisample(GL_RENDERBUFFER_EXT, _requested_multisamples, GL_DEPTH_STENCIL_EXT, + glgsg->_glRenderbufferStorageMultisample(GL_RENDERBUFFER_EXT, _requested_multisamples, format, _rb_size_x, _rb_size_y); } #ifndef OPENGLES @@ -1292,6 +1358,22 @@ bind_slot_multisample(bool rb_resize, Texture **attach, RenderTexturePlane slot, default: break; } +#ifndef OPENGLES + } else if (_fb_properties.get_depth_bits() > 24) { + format = GL_DEPTH_COMPONENT32; + } else if (_fb_properties.get_depth_bits() > 16) { + format = GL_DEPTH_COMPONENT24; + } else if (_fb_properties.get_depth_bits() > 1) { + format = GL_DEPTH_COMPONENT16; + } else { + format = GL_DEPTH_COMPONENT; +#endif + } + if (GLCAT.is_debug()) { + GLCAT.debug() + << "Creating depth renderbuffer with format 0x" << std::hex + << format << std::dec << " and " << _requested_multisamples + << " multisamples.\n"; } if (_requested_coverage_samples) { glgsg->_glRenderbufferStorageMultisampleCoverage(GL_RENDERBUFFER_EXT, _requested_coverage_samples, @@ -1331,21 +1413,97 @@ bind_slot_multisample(bool rb_resize, Texture **attach, RenderTexturePlane slot, case RTP_aux_rgba_1: case RTP_aux_rgba_2: case RTP_aux_rgba_3: + gl_format = GL_RGBA; + break; default: - if (_fb_properties.get_srgb_color()) { - gl_format = GL_SRGB8_ALPHA8; - } else if (_fb_properties.get_float_color()) { - if (_fb_properties.get_color_bits() > 16 * 3) { - gl_format = GL_RGBA32F_ARB; + if (_fb_properties.get_alpha_bits() == 0) { + if (_fb_properties.get_srgb_color()) { + gl_format = GL_SRGB8; + } else if (_fb_properties.get_color_bits() > 16 * 3 || + _fb_properties.get_red_bits() > 16 || + _fb_properties.get_green_bits() > 16 || + _fb_properties.get_blue_bits() > 16) { + // 32-bit, which is always floating-point. + if (_fb_properties.get_blue_bits() > 0 || + _fb_properties.get_color_bits() == 1 || + _fb_properties.get_color_bits() > 32 * 2) { + gl_format = GL_RGB32F; + } else if (_fb_properties.get_green_bits() > 0 || + _fb_properties.get_color_bits() > 32) { + gl_format = GL_RG32F; + } else { + gl_format = GL_R32F; + } + } else if (_fb_properties.get_float_color()) { + // 16-bit floating-point. + if (_fb_properties.get_blue_bits() > 10 || + _fb_properties.get_color_bits() == 1 || + _fb_properties.get_color_bits() > 32) { + gl_format = GL_RGB16F; + } else if (_fb_properties.get_blue_bits() > 0) { + if (_fb_properties.get_red_bits() > 11 || + _fb_properties.get_green_bits() > 11) { + gl_format = GL_RGB16F; + } else { + gl_format = GL_R11F_G11F_B10F; + } + } else if (_fb_properties.get_green_bits() > 0 || + _fb_properties.get_color_bits() > 16) { + gl_format = GL_RG16F; + } else { + gl_format = GL_R16F; + } + } else if (_fb_properties.get_color_bits() > 10 * 3 || + _fb_properties.get_red_bits() > 10 || + _fb_properties.get_green_bits() > 10 || + _fb_properties.get_blue_bits() > 10) { + // 16-bit normalized. + if (_fb_properties.get_blue_bits() > 0 || + _fb_properties.get_color_bits() == 1 || + _fb_properties.get_color_bits() > 16 * 2) { + gl_format = GL_RGBA16; + } else if (_fb_properties.get_green_bits() > 0 || + _fb_properties.get_color_bits() > 16) { + gl_format = GL_RG16; + } else { + gl_format = GL_R16; + } + } else if (_fb_properties.get_color_bits() > 8 * 3 || + _fb_properties.get_red_bits() > 8 || + _fb_properties.get_green_bits() > 8 || + _fb_properties.get_blue_bits() > 8) { + gl_format = GL_RGB10_A2; } else { - gl_format = GL_RGBA16F_ARB; + gl_format = GL_RGB; } } else { - gl_format = GL_RGBA; + if (_fb_properties.get_srgb_color()) { + gl_format = GL_SRGB8_ALPHA8; + } else if (_fb_properties.get_float_color()) { + if (_fb_properties.get_color_bits() > 16 * 3) { + gl_format = GL_RGBA32F_ARB; + } else { + gl_format = GL_RGBA16F_ARB; + } + } else { + if (_fb_properties.get_color_bits() > 16 * 3) { + gl_format = GL_RGBA32F_ARB; + } else if (_fb_properties.get_color_bits() > 8 * 3) { + gl_format = GL_RGBA16; + } else { + gl_format = GL_RGBA; + } + } } break; } #endif + if (GLCAT.is_debug()) { + GLCAT.debug() + << "Creating color renderbuffer with format 0x" << std::hex + << gl_format << std::dec << " and " << _requested_multisamples + << " multisamples.\n"; + } glgsg->_glBindRenderbuffer(GL_RENDERBUFFER_EXT, _rbm[slot]); if (_requested_coverage_samples) { glgsg->_glRenderbufferStorageMultisampleCoverage(GL_RENDERBUFFER_EXT, _requested_coverage_samples, @@ -1405,6 +1563,7 @@ attach_tex(GLenum attachpoint, CLP(TextureContext) *gtc, int view, int layer) { target, index, 0, layer); break; case GL_TEXTURE_2D_ARRAY: + case GL_TEXTURE_CUBE_MAP_ARRAY: glgsg->_glFramebufferTextureLayer(GL_FRAMEBUFFER_EXT, attachpoint, index, 0, layer); break; @@ -1967,7 +2126,7 @@ resolve_multisamples() { // Issue memory barriers as necessary to make sure that the texture memory // is synchronized before we blit to it. for (CLP(TextureContext) *gtc : _texture_contexts) { - if (gtc->needs_barrier(GL_FRAMEBUFFER_BARRIER_BIT)) { + if (gtc->needs_barrier(GL_FRAMEBUFFER_BARRIER_BIT, true)) { glgsg->issue_memory_barrier(GL_FRAMEBUFFER_BARRIER_BIT); // If we've done it for one, we've done it for all. break; diff --git a/panda/src/glstuff/glGraphicsBuffer_src.h b/panda/src/glstuff/glGraphicsBuffer_src.h index ab07322293..f718e134f8 100644 --- a/panda/src/glstuff/glGraphicsBuffer_src.h +++ b/panda/src/glstuff/glGraphicsBuffer_src.h @@ -128,6 +128,10 @@ protected: typedef pvector TextureContexts; TextureContexts _texture_contexts; + // List of textures we need to keep a reference to. + typedef pvector Textures; + Textures _textures; + // The cube map face we are currently drawing to or have just finished // drawing to, or -1 if we are not drawing to a cube map. int _bound_tex_page; diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 1b3326a78c..d5b98d8418 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -68,6 +68,11 @@ #include "shaderGenerator.h" #include "samplerState.h" #include "displayInformation.h" +#include "completionCounter.h" + +#ifdef __EMSCRIPTEN__ +#include "htmlVideoTexture.h" +#endif #if defined(HAVE_CG) && !defined(OPENGLES) #include @@ -97,6 +102,10 @@ PStatCollector CLP(GraphicsStateGuardian)::_check_residency_pcollector("*:PStats PStatCollector CLP(GraphicsStateGuardian)::_wait_fence_pcollector("Wait:Fence"); PStatCollector CLP(GraphicsStateGuardian)::_copy_texture_finish_pcollector("Draw:Copy texture:Finish"); +static PStatCollector _create_texture_storage_pcollector("Draw:Transfer data:Texture:Create Storage"); +static PStatCollector _create_map_pbo_pcollector("Draw:Transfer data:Texture:Create/Map PBO"); +static PStatCollector _load_texture_copy_pcollector("Draw:Transfer data:Texture:Copy/Convert"); + #if defined(HAVE_CG) && !defined(OPENGLES) AtomicAdjust::Integer CLP(GraphicsStateGuardian)::_num_gsgs_with_cg_contexts = 0; small_vector CLP(GraphicsStateGuardian)::_destroyed_cg_contexts; @@ -326,6 +335,23 @@ uchar_l_to_rgb(unsigned char *dest, const unsigned char *source, } } +/** + * Recopies the given array of pixels, converting from luminance to RGBA + * arrangement. + */ +static void +uchar_l_to_rgba(unsigned char *dest, const unsigned char *source, + int num_pixels) { + for (int i = 0; i < num_pixels; i++) { + dest[0] = source[0]; + dest[1] = source[0]; + dest[2] = source[0]; + dest[3] = 1; + dest += 4; + source += 1; + } +} + /** * Recopies the given array of pixels, converting from BGRA to RGBA * arrangement. @@ -425,6 +451,193 @@ ushort_la_to_rgba(unsigned short *dest, const unsigned short *source, } } +/** + * Determines the number of components of the given external format. + */ +static int +get_external_format_components(GLint external_format) { + switch (external_format) { +#ifndef OPENGLES_1 + case GL_RED: + case GL_RED_INTEGER: + case GL_GREEN: + case GL_BLUE: +#endif + case GL_ALPHA: + case GL_LUMINANCE: +#ifndef OPENGLES + case GL_GREEN_INTEGER: + case GL_BLUE_INTEGER: + case GL_ALPHA_INTEGER: + case GL_STENCIL_INDEX: +#endif + case GL_DEPTH_COMPONENT: + case GL_DEPTH_STENCIL: + return 1; + +#ifndef OPENGLES_1 + case GL_RG: + case GL_RG_INTEGER: +#endif + case GL_LUMINANCE_ALPHA: + return 2; + + case GL_RGB: +#ifndef OPENGLES_1 + case GL_RGB_INTEGER: +#endif +#ifndef OPENGLES + case GL_BGR: + case GL_BGR_INTEGER: +#endif + return 3; + + case GL_RGBA: +#ifndef OPENGLES_1 + case GL_RGBA_INTEGER: +#endif + case GL_BGRA: +#ifndef OPENGLES + case GL_BGRA_INTEGER: +#endif + return 4; + + default: + GLCAT.error() + << "Unknown external format 0x" << std::hex << external_format + << std::dec << "\n"; + return 4; + } +} + +/** + * Copies the image with optional conversion. + */ +static void +copy_image(unsigned char *new_image, const unsigned char *orig_image, + size_t orig_image_size, GLint external_format, int num_components, + int component_width) { + switch (external_format) { +#ifndef OPENGLES_1 + case GL_RED: + case GL_RED_INTEGER: + case GL_GREEN: + case GL_BLUE: +#endif + case GL_ALPHA: + case GL_LUMINANCE: +#ifndef OPENGLES + case GL_GREEN_INTEGER: + case GL_BLUE_INTEGER: + case GL_ALPHA_INTEGER: + case GL_STENCIL_INDEX: +#endif + case GL_DEPTH_COMPONENT: + case GL_DEPTH_STENCIL: + if (num_components == 1) { + memcpy(new_image, orig_image, orig_image_size); + return; + } + break; + +#ifndef OPENGLES_1 + case GL_RG: + case GL_RG_INTEGER: +#endif + case GL_LUMINANCE_ALPHA: + if (num_components == 2) { + memcpy(new_image, orig_image, orig_image_size); + return; + } + break; + +#ifndef OPENGLES_1 +#ifndef OPENGLES + case GL_BGR: +#endif + case GL_RGB_INTEGER: + if (num_components == 3) { + memcpy(new_image, orig_image, orig_image_size); + return; + } + if (num_components == 1 && component_width == 1) { + uchar_l_to_rgb(new_image, orig_image, orig_image_size); + return; + } + break; +#endif + + case GL_RGB: +#ifndef OPENGLES + case GL_BGR_INTEGER: +#endif + // Need to swap order. + if (num_components == 1 && component_width == 1) { + uchar_l_to_rgb(new_image, orig_image, orig_image_size); + return; + } + if (num_components == 3 && component_width == 1) { + uchar_bgr_to_rgb(new_image, orig_image, orig_image_size / 3); + return; + } + if (num_components == 3 && component_width == 2) { + ushort_bgr_to_rgb((unsigned short *)new_image, + (const unsigned short *)orig_image, + orig_image_size / 6); + return; + } + break; + + case GL_BGRA: +#ifndef OPENGLES_1 + case GL_RGBA_INTEGER: +#endif + if (num_components == 4) { + memcpy(new_image, orig_image, orig_image_size); + return; + } + if (num_components == 1 && component_width == 1) { + uchar_l_to_rgba(new_image, orig_image, orig_image_size); + return; + } + if (num_components == 2 && component_width == 1) { + uchar_la_to_rgba(new_image, orig_image, orig_image_size / 2); + return; + } + break; + + case GL_RGBA: +#ifndef OPENGLES + case GL_BGRA_INTEGER: +#endif + // Need to swap order. + if (num_components == 1 && component_width == 1) { + uchar_l_to_rgba(new_image, orig_image, orig_image_size); + return; + } + if (num_components == 2 && component_width == 1) { + uchar_la_to_rgba(new_image, orig_image, orig_image_size / 2); + return; + } + if (num_components == 4 && component_width == 1) { + uchar_bgra_to_rgba(new_image, orig_image, orig_image_size / 4); + return; + } + if (num_components == 4 && component_width == 2) { + ushort_bgra_to_rgba((unsigned short *)new_image, + (const unsigned short *)orig_image, + orig_image_size / 8); + return; + } + break; + + default: + break; + } + + nassert_raise("Failed to convert image."); +} + /** * Reverses the order of the components within the image, to convert (for * instance) GL_BGR to GL_RGB. Returns the byte pointer representing the @@ -440,72 +653,15 @@ static const unsigned char * fix_component_ordering(PTA_uchar &new_image, const unsigned char *orig_image, size_t orig_image_size, GLenum external_format, Texture *tex) { - const unsigned char *result = orig_image; - - switch (external_format) { - case GL_RGB: - if (tex->get_num_components() == 1) { - new_image = PTA_uchar::empty_array(orig_image_size * 3); - uchar_l_to_rgb(new_image, orig_image, orig_image_size); - result = new_image; - break; - } - switch (tex->get_component_type()) { - case Texture::T_unsigned_byte: - case Texture::T_byte: - new_image = PTA_uchar::empty_array(orig_image_size); - uchar_bgr_to_rgb(new_image, orig_image, orig_image_size / 3); - result = new_image; - break; - - case Texture::T_unsigned_short: - case Texture::T_short: - new_image = PTA_uchar::empty_array(orig_image_size); - ushort_bgr_to_rgb((unsigned short *)new_image.p(), - (const unsigned short *)orig_image, - orig_image_size / 6); - result = new_image; - break; - - default: - break; - } - break; - - case GL_RGBA: - if (tex->get_num_components() == 2) { - new_image = PTA_uchar::empty_array(orig_image_size * 2); - uchar_la_to_rgba(new_image, orig_image, orig_image_size / 2); - result = new_image; - break; - } - switch (tex->get_component_type()) { - case Texture::T_unsigned_byte: - case Texture::T_byte: - new_image = PTA_uchar::empty_array(orig_image_size); - uchar_bgra_to_rgba(new_image, orig_image, orig_image_size / 4); - result = new_image; - break; - - case Texture::T_unsigned_short: - case Texture::T_short: - new_image = PTA_uchar::empty_array(orig_image_size); - ushort_bgra_to_rgba((unsigned short *)new_image.p(), - (const unsigned short *)orig_image, - orig_image_size / 8); - result = new_image; - break; - - default: - break; - } - break; - - default: - break; + if (external_format == GL_RGB || external_format == GL_RGBA) { + int num_components = tex->get_num_components(); + int component_width = tex->get_component_width(); + size_t new_image_size = (orig_image_size / num_components) * ((external_format == GL_RGBA) ? 4 : 3); + new_image = PTA_uchar::empty_array(new_image_size); + copy_image(&new_image[0], orig_image, orig_image_size, external_format, num_components, component_width); + return new_image; } - - return result; + return orig_image; } // #--- Zhao Nov2011 @@ -524,6 +680,7 @@ int CLP(GraphicsStateGuardian)::get_driver_shader_version_minor() { return _gl_s CLP(GraphicsStateGuardian):: CLP(GraphicsStateGuardian)(GraphicsEngine *engine, GraphicsPipe *pipe) : GraphicsStateGuardian(gl_coordinate_system, engine, pipe), + _job_queue_cvar(_job_queue_mutex), _renderbuffer_residency(get_prepared_objects()->get_name(), "renderbuffer"), _active_ppbuffer_memory_pcollector("Graphics memory:" + get_prepared_objects()->get_name() + ":Active:ppbuffer"), _inactive_ppbuffer_memory_pcollector("Graphics memory:" + get_prepared_objects()->get_name() + ":Inactive:ppbuffer") @@ -567,6 +724,13 @@ CLP(GraphicsStateGuardian)(GraphicsEngine *engine, GraphicsPipe *pipe) : _cg_context = 0; #endif +#ifdef HAVE_THREADS + AsyncTaskManager *task_mgr = AsyncTaskManager::get_global_ptr(); + _async_chain = task_mgr->make_task_chain("gl_texture_transfer", + gl_texture_transfer_num_threads, + gl_texture_transfer_thread_priority); +#endif + #ifdef DO_PSTATS if (gl_finish) { GLCAT.warning() @@ -585,6 +749,15 @@ CLP(GraphicsStateGuardian):: << "GLGraphicsStateGuardian " << this << " destructing\n"; } +#ifdef HAVE_THREADS + // Make sure there are no more async tasks that could reference this GSG. + _async_chain->wait_for_tasks(); +#endif + { + MutexHolder holder(_job_queue_mutex); + _job_queue.clear(); + } + close_gsg(); } @@ -767,6 +940,15 @@ reset() { // Print out a list of all extensions. report_extensions(); +#ifndef OPENGLES_1 + if (_gl_version_major >= 3) { + _glGetIntegeri_v = (PFNGLGETINTEGERI_VPROC) + get_extension_func("glGetIntegeri_v"); + } else { + _glGetIntegeri_v = nullptr; + } +#endif + // Check if we are running under a profiling tool such as apitrace. #if !defined(NDEBUG) && !defined(OPENGLES_1) if (has_extension("GL_EXT_debug_marker")) { @@ -1155,10 +1337,12 @@ reset() { if (is_at_least_gl_version(4, 3) || has_extension("GL_ARB_clear_buffer_object")) { _glClearBufferData = (PFNGLCLEARBUFFERDATAPROC) get_extension_func("glClearBufferData"); + _glClearBufferSubData = (PFNGLCLEARBUFFERSUBDATAPROC) + get_extension_func("glClearBufferSubData"); - if (_glClearBufferData == nullptr) { + if (_glClearBufferData == nullptr || _glClearBufferSubData == nullptr) { GLCAT.warning() - << "GL_ARB_clear_buffer_object advertised as supported by OpenGL runtime, but could not get pointers to extension function.\n"; + << "GL_ARB_clear_buffer_object advertised as supported by OpenGL runtime, but could not get pointers to extension functions.\n"; } else { _supports_clear_buffer = true; } @@ -1731,6 +1915,24 @@ reset() { } #endif +#ifndef OPENGLES_1 + _glCopyBufferSubData = nullptr; + if (_supports_buffers) { + if (is_at_least_gl_version(3, 1) || + is_at_least_gles_version(3, 0) || + has_extension("GL_ARB_copy_buffer")) { + _glCopyBufferSubData = (PFNGLCOPYBUFFERSUBDATAPROC) + get_extension_func("glCopyBufferSubData"); + } +#ifdef OPENGLES_2 + else if (has_extension("GL_NV_copy_buffer")) { + _glCopyBufferSubData = (PFNGLCOPYBUFFERSUBDATAPROC) + get_extension_func("glCopyBufferSubDataNV"); + } +#endif + } +#endif + #ifdef OPENGLES if (is_at_least_gles_version(3, 0)) { _glMapBufferRange = (PFNGLMAPBUFFERRANGEEXTPROC) @@ -1757,7 +1959,8 @@ reset() { _glMapBufferRange = nullptr; } - if (is_at_least_gl_version(4, 4) || has_extension("GL_ARB_buffer_storage")) { + if (_glMapBufferRange != nullptr && + (is_at_least_gl_version(4, 4) || has_extension("GL_ARB_buffer_storage"))) { _glBufferStorage = (PFNGLBUFFERSTORAGEPROC) get_extension_func("glBufferStorage"); @@ -1928,7 +2131,6 @@ reset() { } #endif // HAVE_CG - _supports_compute_shaders = false; #ifndef OPENGLES_1 #ifdef OPENGLES if (is_at_least_gles_version(3, 1)) { @@ -1939,7 +2141,26 @@ reset() { get_extension_func("glDispatchCompute"); if (_glDispatchCompute != nullptr) { - _supports_compute_shaders = true; + glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &_max_compute_work_group_invocations); + + if (_max_compute_work_group_invocations > 0) { + // Initialize to spec-mandated minima + _max_compute_work_group_count.fill(65535); +#ifdef OPENGLES + _max_compute_work_group_size.set(128, 128, 64); +#else + _max_compute_work_group_size.set(1024, 1024, 64); +#endif + + if (_glGetIntegeri_v != nullptr) { + _glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &_max_compute_work_group_count[0]); + _glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &_max_compute_work_group_count[1]); + _glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 2, &_max_compute_work_group_count[2]); + _glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 0, &_max_compute_work_group_size[0]); + _glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 1, &_max_compute_work_group_size[1]); + _glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 2, &_max_compute_work_group_size[2]); + } + } } } #endif // !OPENGLES_1 @@ -2219,7 +2440,7 @@ reset() { #ifndef OPENGLES // Check for SSBOs. - if (is_at_least_gl_version(4, 3) || has_extension("ARB_shader_storage_buffer_object")) { + if (is_at_least_gl_version(4, 3) || has_extension("GL_ARB_shader_storage_buffer_object")) { _supports_shader_buffers = true; _glGetProgramInterfaceiv = (PFNGLGETPROGRAMINTERFACEIVPROC) get_extension_func("glGetProgramInterfaceiv"); @@ -2593,7 +2814,10 @@ reset() { #endif #ifndef OPENGLES - if (is_at_least_gl_version(4, 5) || has_extension("GL_ARB_direct_state_access")) { + _glMapNamedBufferRange = nullptr; + + if (gl_support_dsa && + (is_at_least_gl_version(4, 5) || has_extension("GL_ARB_direct_state_access"))) { _glCreateTextures = (PFNGLCREATETEXTURESPROC) get_extension_func("glCreateTextures"); _glTextureStorage2D = (PFNGLTEXTURESTORAGE2DPROC) @@ -2607,12 +2831,38 @@ reset() { _glBindTextureUnit = (PFNGLBINDTEXTUREUNITPROC) get_extension_func("glBindTextureUnit"); + if (_glMapBufferRange != nullptr) { + _glMapNamedBufferRange = (PFNGLMAPNAMEDBUFFERRANGEPROC) + get_extension_func("glMapNamedBufferRange"); + } + + _glNamedBufferSubData = (PFNGLNAMEDBUFFERSUBDATAPROC) + get_extension_func("glNamedBufferSubData"); + _glClearNamedBufferSubData = (PFNGLCLEARNAMEDBUFFERSUBDATAPROC) + get_extension_func("glClearNamedBufferSubData"); + _glCopyNamedBufferSubData = (PFNGLCOPYNAMEDBUFFERSUBDATAPROC) + get_extension_func("glCopyNamedBufferSubData"); + _glGetNamedBufferSubData = (PFNGLGETNAMEDBUFFERSUBDATAPROC) + get_extension_func("glGetNamedBufferSubData"); + _supports_dsa = true; } else { _supports_dsa = false; } #endif +#ifndef OPENGLES_1 +#ifdef OPENGLES + if (is_at_least_gles_version(3, 0) || has_extension("GL_NV_pixel_buffer_object")) { +#else + if (is_at_least_gl_version(2, 1) || has_extension("GL_ARB_pixel_buffer_object")) { +#endif + _supports_pixel_buffers = true; + } else { + _supports_pixel_buffers = false; + } +#endif + #ifndef OPENGLES_1 // Do we support empty framebuffer objects? #ifdef OPENGLES @@ -3207,7 +3457,7 @@ reset() { _max_image_units = 0; #ifndef OPENGLES_1 #ifdef OPENGLES - if (is_at_least_gles_version(3, 1) && gl_immutable_texture_storage) { + if (is_at_least_gles_version(3, 1)) { #else if (is_at_least_gl_version(4, 2) || has_extension("GL_ARB_shader_image_load_store")) { #endif @@ -4279,6 +4529,8 @@ prepare_lens() { */ bool CLP(GraphicsStateGuardian):: begin_frame(Thread *current_thread) { + process_pending_jobs(false); + if (!GraphicsStateGuardian::begin_frame(current_thread)) { return false; } @@ -4292,9 +4544,11 @@ begin_frame(Thread *current_thread) { _primitive_batches_display_list_pcollector.clear_level(); #endif - if (!_async_ram_copies.empty()) { - finish_async_framebuffer_ram_copies(); +#ifndef OPENGLES_1 + if (!_fences.empty()) { + process_fences(false); } +#endif #if defined(DO_PSTATS) && !defined(OPENGLES) int frame_number = ClockObject::get_global_clock()->get_frame_count(current_thread); @@ -6252,28 +6506,32 @@ issue_memory_barrier(GLbitfield barriers) { _glMemoryBarrier(barriers); - // Indicate that barriers no longer need to be issued for the relevant lists - // of textures. + // Increment these counters to indicate that these barriers have been issued. if (barriers & GL_TEXTURE_FETCH_BARRIER_BIT) { - _textures_needing_fetch_barrier.clear(); + ++_texture_fetch_barrier_counter; GLCAT.spam(false) << " texture_fetch"; } if (barriers & GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) { - _textures_needing_image_access_barrier.clear(); + ++_shader_image_access_barrier_counter; GLCAT.spam(false) << " shader_image_access"; } if (barriers & GL_TEXTURE_UPDATE_BARRIER_BIT) { - _textures_needing_update_barrier.clear(); + ++_texture_update_barrier_counter; GLCAT.spam(false) << " texture_update"; } if (barriers & GL_FRAMEBUFFER_BARRIER_BIT) { - _textures_needing_framebuffer_barrier.clear(); + ++_framebuffer_barrier_counter; GLCAT.spam(false) << " framebuffer"; } + if (barriers & GL_SHADER_STORAGE_BARRIER_BIT) { + ++_shader_storage_barrier_counter; + GLCAT.spam(false) << " shader_storage"; + } + GLCAT.spam(false) << "\n"; report_my_gl_errors(); @@ -6365,23 +6623,16 @@ prepare_texture(Texture *tex) { * (and if get_incomplete_render() is true). */ bool CLP(GraphicsStateGuardian):: -update_texture(TextureContext *tc, bool force) { +update_texture(TextureContext *tc, bool force, CompletionToken token) { CLP(TextureContext) *gtc; DCAST_INTO_R(gtc, tc, false); - Texture *tex = tc->get_texture(); - GLenum target = get_texture_target(tex->get_texture_type()); - if (gtc->_target != target) { - // The target has changed. That means we have to re-bind a new texture - // object. - gtc->reset_data(target, tex->get_num_views()); - } - if (gtc->was_image_modified() || !gtc->_has_storage) { PStatGPUTimer timer(this, _texture_update_pcollector); // If the texture image was modified, reload the texture. - bool okflag = upload_texture(gtc, force, tex->uses_mipmaps()); + Texture *tex = tc->get_texture(); + bool okflag = upload_texture(gtc, force, tex->uses_mipmaps(), std::move(token)); if (!okflag) { GLCAT.error() << "Could not load " << *tex << "\n"; @@ -6397,6 +6648,7 @@ update_texture(TextureContext *tc, bool force) { } else if (gtc->was_properties_modified()) { PStatGPUTimer timer(this, _texture_update_pcollector); + Texture *tex = tc->get_texture(); // If only the properties have been modified, we don't necessarily need to // reload the texture. @@ -6412,7 +6664,7 @@ update_texture(TextureContext *tc, bool force) { if (needs_reload) { gtc->mark_needs_reload(); - bool okflag = upload_texture(gtc, force, tex->uses_mipmaps()); + bool okflag = upload_texture(gtc, force, tex->uses_mipmaps(), std::move(token)); if (!okflag) { GLCAT.error() << "Could not load " << *tex << "\n"; @@ -6422,8 +6674,21 @@ update_texture(TextureContext *tc, bool force) { else { // The texture didn't need reloading, but mark it fully updated now. gtc->mark_loaded(); + + if (force) { + // This update is still underway. + gtc->wait_pending_uploads(); + } + token.complete(true); } } + else { + if (force) { + // This update is still underway. + gtc->wait_pending_uploads(); + } + token.complete(true); + } gtc->enqueue_lru(&_prepared_objects->_graphics_memory_lru); @@ -6440,12 +6705,9 @@ void CLP(GraphicsStateGuardian):: release_texture(TextureContext *tc) { CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc); -#ifndef OPENGLES_1 - _textures_needing_fetch_barrier.erase(gtc); - _textures_needing_image_access_barrier.erase(gtc); - _textures_needing_update_barrier.erase(gtc); - _textures_needing_framebuffer_barrier.erase(gtc); -#endif + gtc->cancel_pending_uploads(); + gtc->wait_pending_uploads(); + gtc->delete_unused_pbos(); gtc->set_num_views(0); delete gtc; @@ -6468,13 +6730,6 @@ release_textures(const pvector &contexts) { for (TextureContext *tc : contexts) { CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc); -#ifndef OPENGLES_1 - _textures_needing_fetch_barrier.erase(gtc); - _textures_needing_image_access_barrier.erase(gtc); - _textures_needing_update_barrier.erase(gtc); - _textures_needing_framebuffer_barrier.erase(gtc); -#endif - num_indices += gtc->_num_views; if (gtc->_buffers != nullptr) { num_buffers += gtc->_num_views; @@ -7362,11 +7617,21 @@ prepare_shader_buffer(ShaderBuffer *data) { // Some drivers require the buffer to be padded to 16 byte boundary. uint64_t num_bytes = (data->get_data_size_bytes() + 15u) & ~15u; if (_supports_buffer_storage) { - _glBufferStorage(GL_SHADER_STORAGE_BUFFER, num_bytes, data->get_initial_data(), 0); + GLbitfield flags = 0; + if (data->get_usage_hint() == GeomEnums::UH_client) { + flags |= GL_CLIENT_STORAGE_BIT; + } + if (data->get_usage_hint() == GeomEnums::UH_dynamic) { + flags |= GL_DYNAMIC_STORAGE_BIT; + } + _glBufferStorage(GL_SHADER_STORAGE_BUFFER, num_bytes, data->get_initial_data(), flags); } else { _glBufferData(GL_SHADER_STORAGE_BUFFER, num_bytes, data->get_initial_data(), get_usage(data->get_usage_hint())); } + // Barrier not needed. + gbc->_shader_storage_barrier_counter = _shader_storage_barrier_counter - 1; + gbc->enqueue_lru(&_prepared_objects->_graphics_memory_lru); report_my_gl_errors(); @@ -7379,13 +7644,15 @@ prepare_shader_buffer(ShaderBuffer *data) { /** * Binds the given shader buffer to the given binding slot. */ -void CLP(GraphicsStateGuardian):: +CLP(BufferContext) *CLP(GraphicsStateGuardian):: apply_shader_buffer(GLuint base, ShaderBuffer *buffer) { + CLP(BufferContext) *gbc = nullptr; + GLuint index = 0; if (buffer != nullptr) { BufferContext *bc = buffer->prepare_now(get_prepared_objects(), this); if (bc != nullptr) { - CLP(BufferContext) *gbc = DCAST(CLP(BufferContext), bc); + gbc = DCAST(CLP(BufferContext), bc); index = gbc->_index; gbc->set_active(true); } @@ -7407,6 +7674,8 @@ apply_shader_buffer(GLuint base, ShaderBuffer *buffer) { report_my_gl_errors(); } + + return gbc; } /** @@ -7437,6 +7706,12 @@ release_shader_buffer(BufferContext *bc) { _current_sbuffer_index = 0; } + for (GLuint &index : _current_sbuffer_base) { + if (index == gbc->_index) { + index = 0; + } + } + _glDeleteBuffers(1, &gbc->_index); report_my_gl_errors(); @@ -7476,6 +7751,12 @@ release_shader_buffers(const pvector &contexts) { _current_sbuffer_index = 0; } + for (GLuint &index : _current_sbuffer_base) { + if (index == gbc->_index) { + index = 0; + } + } + if (debug) { GLCAT.debug() << "deleting shader buffer " << gbc->_index << "\n"; @@ -7490,6 +7771,164 @@ release_shader_buffers(const pvector &contexts) { _glDeleteBuffers(num_indices, indices); report_my_gl_errors(); } + +/** + * This method should only be called by the GraphicsEngine. Do not call it + * directly; call GraphicsEngine::update_shader_buffer_data() instead. + * + * This method will be called in the draw thread to upload data to (a part of) + * the shader buffer from the CPU. If data is null, clears the buffer instead. + */ +bool CLP(GraphicsStateGuardian):: +update_shader_buffer_data(ShaderBuffer *buffer, size_t start, size_t size, + const unsigned char *data) { + nassertr(buffer->get_usage_hint() == GeomEnums::UH_dynamic, false); + + BufferContext *bc = buffer->prepare_now(get_prepared_objects(), this); + if (bc == nullptr || !bc->is_of_type(CLP(BufferContext)::get_class_type())) { + return false; + } + CLP(BufferContext) *gbc = DCAST(CLP(BufferContext), bc); + + if (_glMemoryBarrier != nullptr) { + _glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); + } + +#ifndef OPENGLES + if (_supports_dsa) { + if (data != nullptr) { + _glNamedBufferSubData(gbc->_index, start, size, data); + } + else if (_supports_clear_buffer) { + _glClearNamedBufferSubData(gbc->_index, GL_R8, start, size, GL_RED, GL_UNSIGNED_BYTE, nullptr); + } + else { + void *null_data = calloc(1, size); + _glNamedBufferSubData(gbc->_index, start, size, null_data); + free(null_data); + } + } else +#endif + { + _glBindBuffer(GL_SHADER_STORAGE_BUFFER, gbc->_index); + if (data != nullptr) { + _glBufferSubData(GL_SHADER_STORAGE_BUFFER, start, size, data); + } +#ifndef OPENGLES + else if (_supports_clear_buffer) { + _glClearBufferSubData(GL_SHADER_STORAGE_BUFFER, GL_R8, start, size, GL_RED, GL_UNSIGNED_BYTE, nullptr); + } +#endif + else { + void *null_data = calloc(1, size); + _glBufferSubData(GL_SHADER_STORAGE_BUFFER, start, size, null_data); + free(null_data); + } + _glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + _current_sbuffer_index = 0; + } + report_my_gl_errors(); + + if (data != nullptr || !_supports_clear_buffer) { + _data_transferred_pcollector.add_level(size); + } + return false; +} + +/** + * This method should only be called by the GraphicsEngine. Do not call it + * directly; call GraphicsEngine::extract_texture_data() instead. + * + * This method will be called in the draw thread to download the buffer's + * current contents synchronously. + */ +bool CLP(GraphicsStateGuardian):: +extract_shader_buffer_data(ShaderBuffer *buffer, vector_uchar &data, + size_t start, size_t size) { + BufferContext *bc = buffer->prepare_now(get_prepared_objects(), this); + if (bc == nullptr || !bc->is_of_type(CLP(BufferContext)::get_class_type())) { + return false; + } + CLP(BufferContext) *gbc = DCAST(CLP(BufferContext), bc); + + size_t total_size = buffer->get_data_size_bytes(); + if (start >= total_size) { + data.clear(); + return true; + } + + data.resize(std::min(total_size - start, size)); + + if (_glMemoryBarrier != nullptr) { + _glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); + } + + if (_supports_dsa) { + _glGetNamedBufferSubData(gbc->_index, start, data.size(), &data[0]); + } else { + _glBindBuffer(GL_SHADER_STORAGE_BUFFER, gbc->_index); + _glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, start, data.size(), &data[0]); + _glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + _current_sbuffer_index = 0; + } + report_my_gl_errors(); + + return true; +} +#endif + +#ifndef OPENGLES +/** + * Asynchronous version of extract_shader_buffer_data. It is the caller's + * responsibility that the data argument outlasts the token. + */ +void CLP(GraphicsStateGuardian):: +async_extract_shader_buffer_data(ShaderBuffer *buffer, vector_uchar &data, + size_t start, size_t size, CompletionToken token) { + BufferContext *bc = buffer->prepare_now(get_prepared_objects(), this); + if (bc == nullptr || !bc->is_of_type(CLP(BufferContext)::get_class_type())) { + token.complete(false); + return; + } + + CLP(BufferContext) *gbc = DCAST(CLP(BufferContext), bc); + + size_t total_size = buffer->get_data_size_bytes(); + if (start >= total_size) { + data.clear(); + token.complete(true); + return; + } + + size = std::min(total_size - start, size); + + GLuint staging_buffer = 0; + void *mapped_ptr = nullptr; + + bind_new_client_buffer(staging_buffer, mapped_ptr, GL_COPY_WRITE_BUFFER, size); + if (_glMemoryBarrier != nullptr) { + _glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); + } + + _glBindBuffer(GL_SHADER_STORAGE_BUFFER, gbc->_index); + _glCopyBufferSubData(GL_SHADER_STORAGE_BUFFER, GL_COPY_WRITE_BUFFER, start, 0, size); + _glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + _glBindBuffer(GL_COPY_WRITE_BUFFER, 0); + _current_sbuffer_index = 0; + + if (_supports_buffer_storage && _glMemoryBarrier != nullptr) { + _glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT); + } + + insert_fence([=, &data, token = std::move(token)] (bool success) mutable { + if (success) { + data = vector_uchar((const unsigned char *)mapped_ptr, (const unsigned char *)mapped_ptr + size); + token.complete(true); + } else { + token.complete(false); + } + }); +} #endif #ifndef OPENGLES @@ -7633,9 +8072,18 @@ void CLP(GraphicsStateGuardian):: dispatch_compute(int num_groups_x, int num_groups_y, int num_groups_z) { maybe_gl_finish(); - PStatGPUTimer timer(this, _compute_dispatch_pcollector); - nassertv(_supports_compute_shaders); + nassertv(get_supports_compute_shaders()); nassertv(_current_shader_context != nullptr); + CLP(ShaderContext) *gsc; + DCAST_INTO_V(gsc, _current_shader_context); + +#ifdef DO_PSTATS + _compute_work_groups_pcollector.add_level(num_groups_x * num_groups_y * num_groups_z); + PStatGPUTimer timer(this, gsc->_compute_dispatch_pcollector); +#endif + + gsc->issue_memory_barriers(); + _glDispatchCompute(num_groups_x, num_groups_y, num_groups_z); maybe_gl_finish(); @@ -7708,6 +8156,11 @@ framebuffer_copy_to_texture(Texture *tex, int view, int z, return false; } + } else if (tex->get_texture_type() == Texture::TT_cube_map_array) { + if (!_supports_cube_map_array) { + return false; + } + } else { GLCAT.error() << "Don't know how to copy framebuffer to texture " << *tex << "\n"; @@ -7751,6 +8204,8 @@ framebuffer_copy_to_texture(Texture *tex, int view, int z, nassertr(tc != nullptr, false); CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc); + gtc->cancel_pending_uploads(); + GLenum target = get_texture_target(tex->get_texture_type()); if (gtc->_target != target) { gtc->reset_data(target, view + 1); @@ -7830,8 +8285,8 @@ framebuffer_copy_to_texture(Texture *tex, int view, int z, } #ifndef OPENGLES_1 - if (gtc->needs_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT)) { - // Make sure that any incoherent writes to this texture have been synced. + if (gtc->needs_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT, true)) { + // Make sure that any reads and writes to this texture have been synced. issue_memory_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT); } #endif @@ -8221,9 +8676,46 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z, _glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT); } #endif - GLsync fence = _glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - _async_ram_copies.push_back({request, pbo, fence, external_format, - view, mapped_ptr, image_size}); + + insert_fence([ + this, request = PT(ScreenshotRequest)(request), + mapped_ptr, size = image_size, + pbo, view, external_format + ] (bool success) { + + void *ptr = mapped_ptr; + if (ptr == nullptr) { + ptr = map_read_buffer(GL_PIXEL_PACK_BUFFER, pbo, size); + } + + // Do the memcpy in the background, since it can be slow. + auto func = [=](AsyncTask *task) { + const unsigned char *result = (unsigned char *)ptr; + PTA_uchar new_image; + if (external_format == GL_RGBA || external_format == GL_RGB) { + // We may have to reverse the byte ordering of the image if GL didn't do + // it for us. + result = fix_component_ordering(new_image, result, size, + external_format, request->get_result()); + } + request->set_view_data(view, result); + + // Finishing can take a long time, release the client buffer first so it + // can be reused for the next screenshot. + this->release_client_buffer(pbo, ptr, size); + request->finish(); + return AsyncTask::DS_done; + }; +#ifdef HAVE_THREADS + // We assign a sort value based on the originating frame number, so that + // earlier frames will be processed before subsequent frames, but we don't + // make it unique for every frame, which would kill concurrency. + int frame_number = request->get_frame_number(); + _async_chain->add(std::move(func), "screenshot", frame_number >> 3, -(frame_number & ((1 << 3) - 1))); +#else + func(nullptr); +#endif + }); } else #endif if (external_format == GL_RGBA || external_format == GL_RGB) { @@ -8248,104 +8740,6 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z, return true; } -/** - * Finishes all asynchronous framebuffer-copy-to-ram operations. - */ -void CLP(GraphicsStateGuardian):: -finish_async_framebuffer_ram_copies(bool force) { -#ifndef OPENGLES_1 - if (_async_ram_copies.empty()) { - return; - } - - //XXX having a fixed number of threads is not a great idea. We ought to have - // a common thread pool that is sized based on the available number of CPUs. -#ifdef HAVE_THREADS - AsyncTaskManager *task_mgr = AsyncTaskManager::get_global_ptr(); - static AsyncTaskChain *chain = task_mgr->make_task_chain("texture_download", 2, TP_low); -#endif - - PStatTimer timer(_copy_texture_finish_pcollector); - - if (force) { - // Just wait for the last fence, the rest must be complete too then. - PStatTimer timer(_wait_fence_pcollector); - GLsync fence = _async_ram_copies.back()._fence; - _glClientWaitSync(fence, 0, (GLuint64)-1); - } - - while (!_async_ram_copies.empty()) { - AsyncRamCopy © = _async_ram_copies.front(); - if (!force) { - GLenum result = _glClientWaitSync(copy._fence, 0, 0); - if (result != GL_ALREADY_SIGNALED && result != GL_CONDITION_SATISFIED) { - // Not yet done. The rest must not yet be done then, either. - break; - } - } - _glDeleteSync(copy._fence); - - GLuint pbo = copy._pbo; - int view = copy._view; - PT(ScreenshotRequest) request = std::move(copy._request); - GLuint external_format = copy._external_format; - void *mapped_ptr = copy._mapped_pointer; - size_t size = copy._size; - - if (mapped_ptr == nullptr) { - _glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); -#ifdef OPENGLES - // There is neither glMapBuffer nor persistent mapping in OpenGL ES - mapped_ptr = _glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, size, GL_MAP_READ_BIT); -#else - // If we get here in desktop GL, we must not have persistent mapping - nassertv(!_supports_buffer_storage); - mapped_ptr = _glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); -#endif - } - - // Do the memcpy in the background, since it can be slow. - auto func = [=](AsyncTask *task) { - const unsigned char *result = (unsigned char *)mapped_ptr; - PTA_uchar new_image; - if (external_format == GL_RGBA || external_format == GL_RGB) { - // We may have to reverse the byte ordering of the image if GL didn't do - // it for us. - result = fix_component_ordering(new_image, result, size, - external_format, request->get_result()); - } - request->set_view_data(view, result); - - // Finishing can take a long time, release the client buffer first so it - // can be reused for the next screenshot. - this->release_client_buffer(pbo, mapped_ptr, size); - request->finish(); - return AsyncTask::DS_done; - }; -#ifdef HAVE_THREADS - // We assign a sort value based on the originating frame number, so that - // earlier frames will be processed before subsequent frames, but we don't - // make it unique for every frame, which would kill concurrency. - int frame_number = request->get_frame_number(); - chain->add(std::move(func), "screenshot", frame_number >> 3, -(frame_number & ((1 << 3) - 1))); -#else - func(nullptr); -#endif - - _async_ram_copies.pop_front(); - - // If there is 1 remaining, save it for next frame. This helps prevent an - // inconsistent frame rate when the number of fetched frames alternates - // between 0 and 2, which can settle into a stable feedback loop. - if (!force && _async_ram_copies.size() == 1) { - break; - } - } - - _glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); -#endif -} - #ifdef SUPPORT_FIXED_FUNCTION /** * @@ -8475,7 +8869,7 @@ do_issue_shader() { // If it's a different type of shader, make sure to unbind the old. _current_shader_context->unbind(); } - context->bind(); + context->bind(this); _current_shader = shader; } @@ -13816,12 +14210,15 @@ apply_sampler(GLuint unit, const SamplerState &sampler, CLP(TextureContext) *gtc * image. */ bool CLP(GraphicsStateGuardian):: -upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) { +upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps, + CompletionToken token) { PStatGPUTimer timer(this, _load_texture_pcollector); Texture *tex = gtc->get_texture(); - if (_effective_incomplete_render && !force) { + //FIXME: upload simple texture for async uploaded thing + bool async_upload = true; + if (_effective_incomplete_render && !force && !async_upload) { bool has_image = _supports_compressed_texture ? tex->has_ram_image() : tex->has_uncompressed_ram_image(); if (!has_image && tex->might_have_ram_image() && tex->has_simple_ram_image() && @@ -14105,6 +14502,8 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) { int num_views = tex->get_num_views(); if (needs_reload) { + gtc->cancel_pending_uploads(); + if (gtc->_immutable) { GLCAT.info() << "Attempt to modify texture with immutable storage, recreating texture.\n"; @@ -14118,8 +14517,8 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) { #ifndef OPENGLES_1 if (needs_reload || !image.is_null()) { - // Make sure that any incoherent writes to this texture have been synced. - if (gtc->needs_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT)) { + // Make sure that any reads and writes to this texture have been synced. + if (gtc->needs_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT, true)) { issue_memory_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT); } } @@ -14136,46 +14535,73 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) { nassertr(gtc->_buffers != nullptr, false); } - bool extract_success = false; - if (tex->get_post_load_store_cache()) { - extract_success = true; + bool compressed = image_compression != Texture::CM_off; + if (compressed && !_supports_compressed_texture) { + return false; } + // Does this texture require asynchronous uploads? +#if defined(HAVE_THREADS) && !defined(OPENGLES_1) + int async_buffers = _supports_pixel_buffers ? tex->get_num_async_transfer_buffers() : 0; + if (async_buffers != 0) { + // Prefer immutable storage, if supported. + if (needs_reload && _supports_tex_storage) { + gtc->_immutable = true; + } + } + else if (async_buffers == 0 && gtc->_num_pbos > 0) { + gtc->delete_unused_pbos(); + } +#else + int async_buffers = 0; +#endif + + // Keep track of which views are uploaded. + CompletionCounter counter; bool success = true; + for (int view = 0; view < num_views; ++view) { - if (upload_texture_image(gtc, view, needs_reload || view >= old_num_views, - mipmap_bias, num_levels, - internal_format, external_format, - component_type, image_compression)) { + if (upload_texture_view(gtc, view, needs_reload || view >= old_num_views, + mipmap_bias, num_levels, + internal_format, external_format, + component_type, compressed, async_buffers, + counter.make_token())) { + // We always create storage right away even if we do the upload of the + // actual data asynchronously. gtc->_has_storage = true; gtc->_internal_format = internal_format; gtc->_width = width; gtc->_height = height; gtc->_depth = depth; gtc->_num_levels = num_levels; - - if (extract_success) { - // The next call assumes the texture is still bound. - if (!do_extract_texture_data(gtc, view)) { - extract_success = false; - } - } } else { success = false; } } - report_my_gl_errors(); + if (!success) { + report_my_gl_errors(); + return false; + } + + gtc->_uploads_pending++; + + std::move(counter).then([=, token = std::move(token)] (bool success) mutable { + --gtc->_uploads_pending; + if (!success) { + token.complete(false); + return; + } - if (success) { if (needs_reload) { gtc->update_data_size_bytes(get_texture_memory_size(gtc)); } - nassertr(gtc->_has_storage, false); + nassertv(gtc->_has_storage); - if (extract_success) { + Texture *tex = gtc->get_texture(); + if (tex->get_post_load_store_cache()) { tex->set_post_load_store_cache(false); // OK, get the RAM image, and save it in a BamCache record. if (tex->has_ram_image()) { @@ -14189,32 +14615,34 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) { } GraphicsEngine *engine = get_engine(); - nassertr(engine != nullptr, false); + nassertv(engine != nullptr); engine->texture_uploaded(tex); - gtc->mark_loaded(); - return true; - } + token.complete(true); + }); - return false; + // Update the modified counters now, even if we've only spawned an async + // upload, because we've already set things in motion to update the texture + // to this version. Otherwise, future calls to update_texture will continue + // to try to update the image over and over again. + gtc->mark_loaded(); + + report_my_gl_errors(); + return true; } /** - * Loads a texture image, or one page of a cube map image, from system RAM to - * texture memory. + * Loads a texture image, or one view of a multiview texture, from system RAM + * to texture memory. */ bool CLP(GraphicsStateGuardian):: -upload_texture_image(CLP(TextureContext) *gtc, int view, bool needs_reload, - int mipmap_bias, int num_levels, GLint internal_format, - GLint external_format, GLenum component_type, - Texture::CompressionMode image_compression) { +upload_texture_view(CLP(TextureContext) *gtc, int view, bool needs_reload, + int mipmap_bias, int num_levels, GLint internal_format, + GLint external_format, GLenum component_type, + bool compressed, int async_buffers, CompletionToken token) { // Make sure the error stack is cleared out before we begin. clear_my_gl_errors(); - if (image_compression != Texture::CM_off && !_supports_compressed_texture) { - return false; - } - GLenum target = gtc->_target; if (target == GL_NONE) { // Unsupported target (e.g. 3-d texturing on GL 1.1). @@ -14251,9 +14679,14 @@ upload_texture_image(CLP(TextureContext) *gtc, int view, bool needs_reload, int width = tex->get_expected_mipmap_x_size(mipmap_bias); int height = tex->get_expected_mipmap_y_size(mipmap_bias); int depth = tex->get_expected_mipmap_z_size(mipmap_bias); + int num_components = tex->get_num_components(); + int expected_num_components = compressed ? num_components : get_external_format_components(external_format); + int component_width = tex->get_component_width(); + GLenum usage = GL_STATIC_DRAW; #ifndef OPENGLES_1 if (target == GL_TEXTURE_BUFFER) { + usage = get_usage(tex->get_usage_hint()); GLuint buffer = gtc->get_view_buffer(view); nassertr(buffer != 0, false); _glBindBuffer(GL_TEXTURE_BUFFER, buffer); @@ -14321,6 +14754,7 @@ upload_texture_image(CLP(TextureContext) *gtc, int view, bool needs_reload, // but we are not allowed to change the texture size or number of mipmap // levels after this point. if (gtc->_immutable) { + PStatTimer timer(_create_texture_storage_pcollector); if (GLCAT.is_debug()) { GLCAT.debug() << "allocating storage for texture " << tex->get_name() << ", " @@ -14355,15 +14789,89 @@ upload_texture_image(CLP(TextureContext) *gtc, int view, bool needs_reload, // How many mipmap levels do we have available to upload? int num_ram_mipmap_levels = 0; + GLuint pbo = 0; + size_t pbo_size = 0; + void *mapped_ptr = nullptr; if (!image.is_null()) { num_ram_mipmap_levels = std::min(num_levels, tex->get_num_ram_mipmap_images() - mipmap_bias); + + // Create a PBO that can hold all the mipmap levels. +#if defined(HAVE_THREADS) && !defined(OPENGLES_1) + if (async_buffers != 0) { + pbo_size = 0; + for (int n = mipmap_bias; n < num_ram_mipmap_levels + mipmap_bias; ++n) { + size_t view_size = tex->get_ram_mipmap_view_size(n); + if (!compressed) { + view_size = expected_num_components * (view_size / num_components); + } + pbo_size += view_size; + } + + bool create_storage = false; + if (pbo_size != gtc->_pbo_size) { + // No PBOs yet, or they aren't of the right size. + if (_supports_buffer_storage) { + // If using buffer storage, need to deallocate all of them. + gtc->delete_unused_pbos(); + } + gtc->_pbo_size = pbo_size; + create_storage = true; + } + else if (async_buffers > 0) { + // Wait for a PBO to become available if we're at our limit. + gtc->wait_for_unused_pbo(async_buffers); + } + + PStatTimer timer(_create_map_pbo_pcollector); + if (gtc->_unused_pbos.empty()) { + _glGenBuffers(1, &pbo); + gtc->_num_pbos++; + create_storage = true; + } else { + // Map an existing PBO. + pbo = gtc->_unused_pbos.back(); + gtc->_unused_pbos.pop_back(); + } + + mapped_ptr = map_write_discard_buffer(GL_PIXEL_UNPACK_BUFFER, pbo, + pbo_size, create_storage); + if (mapped_ptr == nullptr) { + report_my_gl_errors(); + GLCAT.warning() + << "Failed to map pixel unpack buffer.\n"; + gtc->_unused_pbos.push_back(pbo); + } + } +#endif } - if (!needs_reload) { - // Try to subload the image over the existing GL Texture object, possibly - // saving on texture memory fragmentation. + if (needs_reload && num_ram_mipmap_levels == 0 && + external_format == GL_DEPTH_STENCIL && get_supports_depth_stencil()) { +#ifdef OPENGLES + component_type = GL_UNSIGNED_INT_24_8_OES; +#else + component_type = GL_UNSIGNED_INT_24_8_EXT; +#endif + } - if (GLCAT.is_debug()) { + int upload_count = ++gtc->_uploads_started; + + if (GLCAT.is_debug()) { + if (needs_reload) { + // Load the image up from scratch, creating a new GL Texture object. + GLCAT.debug() + << "loading new texture object for " << tex->get_name() << " view " + << view << ", " << width << " x " << height << " x " << depth + << ", mipmaps " << num_ram_mipmap_levels << " / " << num_levels; + + if (num_ram_mipmap_levels == 0 && tex->has_clear_color()) { + GLCAT.debug(false) + << ", clearing to " << tex->get_clear_color(); + } + } + else { + // Try to subload the image over the existing GL Texture object, possibly + // saving on texture memory fragmentation. SparseArray pages = gtc->get_view_modified_pages(view, 0); if (num_ram_mipmap_levels == 0) { if (tex->has_clear_color()) { @@ -14371,433 +14879,306 @@ upload_texture_image(CLP(TextureContext) *gtc, int view, bool needs_reload, << "clearing texture " << tex->get_name() << " view " << view << ", " << width << " x " << height << " x " << depth << ", pages " << pages << ", mipmaps " << num_levels << ", clear_color = " - << tex->get_clear_color() << "\n"; + << tex->get_clear_color(); } else { GLCAT.debug() << "not loading NULL image for texture " << tex->get_name() << " view " << view << ", " << width << " x " << height << " x " << depth - << ", pages " << pages << ", mipmaps = " << num_levels << "\n"; + << ", pages " << pages << ", mipmaps = " << num_levels; } } else { GLCAT.debug() << "updating image data of texture " << tex->get_name() << " view " << view << ", " << width << " x " << height << " x " << depth << ", pages " << pages << ", mipmaps " << num_ram_mipmap_levels - << " / " << num_levels << "\n"; + << " / " << num_levels; } } - - for (int n = mipmap_bias; n < num_levels + mipmap_bias; ++n) { - SparseArray pages = gtc->get_view_modified_pages(view, n); - - // we grab the mipmap pointer first, if it is NULL we grab the normal - // mipmap image pointer which is a PTA_uchar - const unsigned char *image_ptr = (unsigned char*)tex->get_ram_mipmap_pointer(n); - CPTA_uchar ptimage; - if (image_ptr == nullptr) { - ptimage = tex->get_ram_mipmap_image(n); - if (ptimage.is_null()) { - if (n - mipmap_bias < num_ram_mipmap_levels) { - // We were told we'd have this many RAM mipmap images, but we - // don't. Raise a warning. - GLCAT.warning() - << "No mipmap level " << n << " defined for " << tex->get_name() - << "\n"; - break; - } - - if (tex->has_clear_color()) { - // The texture has a clear color, so we should fill this mipmap - // level to a solid color. -#ifndef OPENGLES - if (target != GL_TEXTURE_BUFFER) { - if (_supports_clear_texture) { - // We can do that with the convenient glClearTexImage - // function. - vector_uchar clear_data = tex->get_clear_data(); - - if (pages.has_all_of(0, depth)) { - _glClearTexImage(index, n - mipmap_bias, external_format, - component_type, (void *)&clear_data[0]); - } - else for (size_t sri = 0; sri < pages.get_num_subranges(); ++sri) { - int begin = pages.get_subrange_begin(sri); - int num_pages = pages.get_subrange_end(sri) - begin; - _glClearTexSubImage(index, n - mipmap_bias, 0, 0, begin, - width, height, num_pages, external_format, - component_type, (void *)&clear_data[0]); - } - continue; - } - } else { - if (_supports_clear_buffer) { - // For buffer textures we need to clear the underlying - // storage. - vector_uchar clear_data = tex->get_clear_data(); - - _glClearBufferData(GL_TEXTURE_BUFFER, internal_format, external_format, - component_type, (const void *)&clear_data[0]); - continue; - } - } -#endif // OPENGLES - // Ask the Texture class to create the mipmap level in RAM. It'll - // fill it in with the correct clear color, which we can then - // upload. - ptimage = tex->make_ram_mipmap_image(n); - - } else { - // No clear color and no more images. - break; - } - } - image_ptr = ptimage; - } - - PTA_uchar bgr_image; - size_t page_size = tex->get_ram_mipmap_page_size(n); - if (image_ptr != nullptr) { - const unsigned char *orig_image_ptr = image_ptr; - size_t view_size = tex->get_ram_mipmap_view_size(n); - image_ptr += view_size * view; - nassertr(image_ptr >= orig_image_ptr && image_ptr + view_size <= orig_image_ptr + tex->get_ram_mipmap_image_size(n), false); - - if (image_compression == Texture::CM_off) { - // If the GL doesn't claim to support BGR, we may have to reverse - // the component ordering of the image. - image_ptr = fix_component_ordering(bgr_image, image_ptr, view_size, - external_format, tex); - } - } - - int width = tex->get_expected_mipmap_x_size(n); - int height = tex->get_expected_mipmap_y_size(n); - -#ifdef DO_PSTATS - _data_transferred_pcollector.add_level(page_size * pages.get_num_on_bits()); -#endif - switch (target) { -#ifndef OPENGLES_1 - case GL_TEXTURE_3D: - if (_supports_3d_texture) { - for (size_t sri = 0; sri < pages.get_num_subranges(); ++sri) { - int begin = pages.get_subrange_begin(sri); - int num_pages = pages.get_subrange_end(sri) - begin; - const unsigned char *page_ptr = image_ptr + page_size * begin; - - if (image_compression == Texture::CM_off) { - _glTexSubImage3D(target, n - mipmap_bias, - 0, 0, begin, width, height, num_pages, - external_format, component_type, page_ptr); - } else { - _glCompressedTexSubImage3D(target, n - mipmap_bias, - 0, 0, begin, width, height, num_pages, - external_format, - page_size * num_pages, page_ptr); - } - } - } else { - report_my_gl_errors(); - return false; - } - break; -#endif // OPENGLES_1 - -#ifndef OPENGLES - case GL_TEXTURE_1D: - if (image_compression == Texture::CM_off) { - glTexSubImage1D(target, n - mipmap_bias, 0, width, - external_format, component_type, image_ptr); - } else { - _glCompressedTexSubImage1D(target, n - mipmap_bias, 0, width, - external_format, page_size, image_ptr); - } - break; -#endif // OPENGLES - -#ifndef OPENGLES_1 - case GL_TEXTURE_2D_ARRAY: - case GL_TEXTURE_CUBE_MAP_ARRAY: - if (_supports_2d_texture_array) { - for (size_t sri = 0; sri < pages.get_num_subranges(); ++sri) { - int begin = pages.get_subrange_begin(sri); - int num_pages = pages.get_subrange_end(sri) - begin; - const unsigned char *page_ptr = image_ptr + page_size * begin; - - if (image_compression == Texture::CM_off) { - _glTexSubImage3D(target, n - mipmap_bias, - 0, 0, begin, width, height, num_pages, - external_format, component_type, page_ptr); - } else { - _glCompressedTexSubImage3D(target, n - mipmap_bias, - 0, 0, begin, width, height, num_pages, - external_format, - page_size * num_pages, page_ptr); - } - } - } else { - report_my_gl_errors(); - return false; - } - break; -#endif // OPENGLES_1 - -#ifndef OPENGLES_1 - case GL_TEXTURE_BUFFER: - if (_supports_buffer_texture) { - _glBufferSubData(GL_TEXTURE_BUFFER, 0, page_size, image_ptr); - } else { - report_my_gl_errors(); - return false; - } - break; -#endif // OPENGLES - - case GL_TEXTURE_CUBE_MAP: - if (_supports_cube_map) { - // This is the only texture type that must be specified using separate - // per-page calls. - if (n == 0) { - height = tex->get_y_size() - tex->get_pad_y_size(); - } - for (int z = 0; z < 6; ++z) { - if (pages.get_bit(z)) { - GLenum page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + z; - const unsigned char *page_ptr = image_ptr + page_size * z; - - if (image_compression == Texture::CM_off) { - glTexSubImage2D(page_target, n - mipmap_bias, 0, 0, width, height, - external_format, component_type, page_ptr); - } else { - _glCompressedTexSubImage2D(page_target, n - mipmap_bias, - 0, 0, width, height, - external_format, page_size, page_ptr); - } - } - } - } else { - report_my_gl_errors(); - return false; - } - break; - - default: - if (image_compression == Texture::CM_off) { - if (n == 0) { - // It's unfortunate that we can't adjust the width, too, but - // TexSubImage2D doesn't accept a row-stride parameter. - height = tex->get_y_size() - tex->get_pad_y_size(); - } - glTexSubImage2D(target, n - mipmap_bias, 0, 0, width, height, - external_format, component_type, image_ptr); - } else { - _glCompressedTexSubImage2D(target, n - mipmap_bias, 0, 0, width, height, - external_format, page_size, image_ptr); - } - break; - } - } - - // Did that fail? If it did, we'll immediately try again, this time - // loading the texture from scratch. - GLenum error_code = gl_get_error(); - if (error_code != GL_NO_ERROR) { - if (GLCAT.is_debug()) { - GLCAT.debug() - << "GL texture subload failed for " << tex->get_name() - << " : " << get_error_string(error_code) << "\n"; - } - needs_reload = true; + if (mapped_ptr != nullptr) { + GLCAT.debug(false) << " (async #" << upload_count << " via buffer " << pbo << ")\n"; + } else { + GLCAT.debug(false) << " (#" << upload_count << ")\n"; } } - if (needs_reload) { - // Load the image up from scratch, creating a new GL Texture object. - if (GLCAT.is_debug()) { - GLCAT.debug() - << "loading new texture object for " << tex->get_name() << " view " - << view << ", " << width << " x " << height << " x " << depth - << ", mipmaps " << num_ram_mipmap_levels << " / " << num_levels << "\n"; - } - - // If there is immutable storage, this is impossible to do, and we should - // not have gotten here at all. - nassertr(!gtc->_immutable, false); - - if (num_ram_mipmap_levels == 0) { - if (GLCAT.is_debug()) { - GLCAT.debug() - << " (initializing NULL image)\n"; - } - - if ((external_format == GL_DEPTH_STENCIL) && get_supports_depth_stencil()) { -#ifdef OPENGLES - component_type = GL_UNSIGNED_INT_24_8_OES; -#else - component_type = GL_UNSIGNED_INT_24_8_EXT; + // Keeps track of any async jobs we've spawned. +#if defined(HAVE_THREADS) && !defined(OPENGLES_1) + CompletionCounter counter; + struct AsyncLevel { + int width, height, depth; + size_t page_size; + uintptr_t pbo_offset; + SparseArray pages; + }; + AsyncLevel *async_levels = nullptr; + size_t num_async_levels = 0; + uintptr_t pbo_offset = 0u; #endif - } + bool success = true; + + for (int n = mipmap_bias; n < num_levels + mipmap_bias; ++n) { + SparseArray pages = gtc->get_view_modified_pages(view, n); + int level = n - mipmap_bias; + + int width = tex->get_expected_mipmap_x_size(n); + int height = tex->get_expected_mipmap_y_size(n); + int depth = tex->get_expected_mipmap_z_size(n); + + // we grab the mipmap pointer first, if it is NULL we grab the normal + // mipmap image pointer which is a PTA_uchar + const unsigned char *image_ptr = (unsigned char*)tex->get_ram_mipmap_pointer(n); + CPTA_uchar ptimage; + if (image_ptr == nullptr) { + ptimage = tex->get_ram_mipmap_image(n); + image_ptr = ptimage; } - - for (int n = mipmap_bias; n < num_levels + mipmap_bias; ++n) { - const unsigned char *image_ptr = (unsigned char*)tex->get_ram_mipmap_pointer(n); - CPTA_uchar ptimage; - if (image_ptr == nullptr) { - ptimage = tex->get_ram_mipmap_image(n); - if (ptimage.is_null()) { - if (n - mipmap_bias < num_ram_mipmap_levels) { - // We were told we'd have this many RAM mipmap images, but we - // don't. Raise a warning. - GLCAT.warning() - << "No mipmap level " << n << " defined for " << tex->get_name() - << "\n"; - if (_supports_texture_max_level) { - // Tell the GL we have no more mipmaps for it to use. - glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, n - mipmap_bias); - } - break; + if (image_ptr == nullptr) { +#ifdef __EMSCRIPTEN__ + if (tex->is_of_type(HTMLVideoTexture::get_class_type())) { + // Load directly from the video element into WebGL. + int result = EM_ASM_INT({ + var video = window._htmlVideoData[$7].video; + if (video.readyState < video.HAVE_CURRENT_DATA) { + return 0; } - - if (tex->has_clear_color()) { - // Ask the Texture class to create the mipmap level in RAM. It'll - // fill it in with the correct clear color, which we can then - // upload. - ptimage = tex->make_ram_mipmap_image(n); + GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, true); + if ($8) { + GLctx.texImage2D($0, $1, $2, $3, $4, 0, $5, $6, video); + } else { + GLctx.texSubImage2D($0, $1, 0, 0, $3, $4, $5, $6, video); } - else if (image_compression != Texture::CM_off) { - // We can't upload a NULL compressed texture. - if (_supports_texture_max_level) { - // Tell the GL we have no more mipmaps for it to use. - glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, n - mipmap_bias); + GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, false); + return 1; + }, target, 0, internal_format, width, height, external_format, component_type, tex, needs_reload); + + if (result) { + continue; + } + } +#endif + + if (level < num_ram_mipmap_levels) { + // We were told we'd have this many RAM mipmap images, but we + // don't. Raise a warning. + GLCAT.warning() + << "No mipmap level " << n << " defined for " << tex->get_name() + << "\n"; + + if (needs_reload && _supports_texture_max_level) { + // Tell the GL we have no more mipmaps for it to use. + glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, level - 1); + } + break; + } + + if (tex->has_clear_color()) { + // The texture has a clear color, so we should fill this mipmap + // level to a solid color. +#ifndef OPENGLES + if (target != GL_TEXTURE_BUFFER) { + if (_supports_clear_texture && !compressed) { + // If we don't have storage, we create it with a NULL image first. + if (!needs_reload || + upload_texture_level(true, false, target, level, + width, height, depth, internal_format, + external_format, component_type, + nullptr, 0, pages, usage)) { + vector_uchar clear_data = tex->get_clear_data(); + + if (needs_reload || pages.has_all_of(0, depth)) { + _glClearTexImage(index, level, external_format, + component_type, (void *)&clear_data[0]); + } + else for (size_t sri = 0; sri < pages.get_num_subranges(); ++sri) { + int begin = pages.get_subrange_begin(sri); + int num_pages = pages.get_subrange_end(sri) - begin; + _glClearTexSubImage(index, level, 0, 0, begin, + width, height, num_pages, external_format, + component_type, (void *)&clear_data[0]); + } + continue; } - break; + } + } else { + if (_supports_clear_buffer) { + // For buffer textures we need to clear the underlying + // storage. + if (needs_reload) { + _glBufferData(GL_TEXTURE_BUFFER, tex->get_expected_ram_mipmap_view_size(n), nullptr, usage); + } + vector_uchar clear_data = tex->get_clear_data(); + + _glClearBufferData(GL_TEXTURE_BUFFER, internal_format, external_format, + component_type, (const void *)&clear_data[0]); + continue; } } +#endif // OPENGLES + // Ask the Texture class to create the mipmap level in RAM. It'll + // fill it in with the correct clear color, which we can then + // upload. + ptimage = tex->make_ram_mipmap_image(n); image_ptr = ptimage; } - - PTA_uchar bgr_image; - size_t view_size = tex->get_ram_mipmap_view_size(n); - if (image_ptr != nullptr) { - const unsigned char *orig_image_ptr = image_ptr; - image_ptr += view_size * view; - nassertr(image_ptr >= orig_image_ptr && image_ptr + view_size <= orig_image_ptr + tex->get_ram_mipmap_image_size(n), false); - - if (image_compression == Texture::CM_off) { - // If the GL doesn't claim to support BGR, we may have to reverse - // the component ordering of the image. - image_ptr = fix_component_ordering(bgr_image, image_ptr, view_size, - external_format, tex); - } + else if (!needs_reload) { + // No clear color and no more images, and no storage to create. + break; } - - int width = tex->get_expected_mipmap_x_size(n); - int height = tex->get_expected_mipmap_y_size(n); -#ifndef OPENGLES_1 - int depth = tex->get_expected_mipmap_z_size(n); -#endif - -#ifdef DO_PSTATS - _data_transferred_pcollector.add_level(view_size); -#endif - switch (target) { -#ifndef OPENGLES // 1-d textures not supported by OpenGL ES. Fall through. - case GL_TEXTURE_1D: - if (image_compression == Texture::CM_off) { - glTexImage1D(target, n - mipmap_bias, internal_format, - width, 0, external_format, component_type, image_ptr); - } else { - _glCompressedTexImage1D(target, n - mipmap_bias, external_format, - width, 0, view_size, image_ptr); + else if (compressed) { + // We can't upload a NULL compressed texture. + if (_supports_texture_max_level) { + // Tell the GL we have no more mipmaps for it to use. + glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, level - 1); } break; -#endif // OPENGLES // OpenGL ES will fall through. - -#ifndef OPENGLES_1 - case GL_TEXTURE_3D: - if (_supports_3d_texture) { - if (image_compression == Texture::CM_off) { - _glTexImage3D(target, n - mipmap_bias, internal_format, - width, height, depth, 0, - external_format, component_type, image_ptr); - } else { - _glCompressedTexImage3D(target, n - mipmap_bias, external_format, - width, height, depth, 0, view_size, image_ptr); - } - } else { - report_my_gl_errors(); - return false; - } - break; -#endif // OPENGLES_1 - -#ifndef OPENGLES_1 - case GL_TEXTURE_2D_ARRAY: - case GL_TEXTURE_CUBE_MAP_ARRAY: - if (_supports_2d_texture_array) { - if (image_compression == Texture::CM_off) { - _glTexImage3D(target, n - mipmap_bias, internal_format, - width, height, depth, 0, - external_format, component_type, image_ptr); - } else { - _glCompressedTexImage3D(target, n - mipmap_bias, external_format, - width, height, depth, 0, view_size, image_ptr); - } - } else { - report_my_gl_errors(); - return false; - } - break; - - case GL_TEXTURE_BUFFER: - if (_supports_buffer_texture) { - _glBufferData(GL_TEXTURE_BUFFER, view_size, image_ptr, - get_usage(tex->get_usage_hint())); - } else { - report_my_gl_errors(); - return false; - } - break; -#endif // OPENGLES_1 - - case GL_TEXTURE_CUBE_MAP: - if (_supports_cube_map) { - // This is the only texture type that must be specified using separate - // per-page calls. - size_t page_size = tex->get_ram_mipmap_page_size(n); - for (int z = 0; z < 6; ++z) { - GLenum page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + z; - const unsigned char *page_ptr = - (image_ptr != nullptr) ? image_ptr + page_size * z : nullptr; - - if (image_compression == Texture::CM_off) { - glTexImage2D(page_target, n - mipmap_bias, internal_format, - width, height, 0, - external_format, component_type, page_ptr); - } else { - _glCompressedTexImage2D(page_target, n - mipmap_bias, external_format, - width, height, 0, page_size, page_ptr); - } - } - } else { - report_my_gl_errors(); - return false; - } - break; - - default: - if (image_compression == Texture::CM_off) { - glTexImage2D(target, n - mipmap_bias, internal_format, - width, height, 0, - external_format, component_type, image_ptr); - } else { - _glCompressedTexImage2D(target, n - mipmap_bias, external_format, - width, height, 0, view_size, image_ptr); - } } } - // Report the error message explicitly if the GL texture creation failed. + // Select the correct view. + size_t orig_view_size = 0; + size_t orig_page_size = 0; + size_t page_size = 0; + if (image_ptr != nullptr) { + orig_view_size = tex->get_ram_mipmap_view_size(n); + if (view > 0) { + const unsigned char *orig_image_ptr = image_ptr; + image_ptr += orig_view_size * view; + nassertd(image_ptr >= orig_image_ptr && image_ptr + orig_view_size <= orig_image_ptr + tex->get_ram_mipmap_image_size(n)) { + success = false; + break; + } + } + + orig_page_size = tex->get_ram_mipmap_page_size(n); + page_size = orig_page_size; + if (!compressed) { + // May need to convert. + page_size = get_external_format_components(external_format) * (page_size / num_components); + } + } +#ifndef OPENGLES_1 + else if (target == GL_TEXTURE_BUFFER) { + // page_size for buffer texture indicates the size even for a null image. + page_size = tex->get_expected_ram_mipmap_view_size(n); + } +#endif + + // Don't need to update the padded area at the bottom. + int sub_height = height; + if (n == 0 && (target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP)) { + sub_height -= tex->get_pad_y_size(); + } + +#if defined(HAVE_THREADS) && !defined(OPENGLES_1) + if (mapped_ptr != nullptr) { + // Let's make sure we have texture storage (normally this is handled by + // the glTexStorage2D calls above, if immutable texture storage is + // supported and enabled), it makes other things easier down the line. + if (needs_reload) { + PStatTimer timer(_create_texture_storage_pcollector); + if (!upload_texture_level(true, compressed, target, level, + width, height, depth, internal_format, + external_format, component_type, + nullptr, page_size, pages, usage)) { + if (level == 0) { + // If level 0 failed to create, this texture is useless. + success = false; + } + else if (_supports_texture_max_level) { + // Apparently, this is all it's going to get. + glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, level - 1); + } + break; + } + } + + // Spawn a task to do the upload asynchronously into the PBO. + if (image_ptr != nullptr) { + void *mapped_level_ptr = (char *)mapped_ptr + pbo_offset; + + if (ptimage) { + ptimage.node_ref(); + } + _async_chain->add([=, ptimage = std::move(ptimage), token = counter.make_token()](AsyncTask *task) mutable { + { + PStatTimer timer(_load_texture_copy_pcollector); + copy_image((unsigned char *)mapped_level_ptr, image_ptr, orig_view_size, + external_format, num_components, component_width); + } + if (ptimage) { + ptimage.node_unref(); + } + + token.complete(true); + return AsyncTask::DS_done; + }, "copy:" + tex->get_name()); + + if (async_levels == nullptr) { + async_levels = new AsyncLevel[num_levels + 1]; + } + async_levels[num_async_levels++] = {width, sub_height, depth, page_size, pbo_offset, std::move(pages)}; + pbo_offset += page_size * depth; + continue; + } + } +#endif + if (image_ptr != nullptr) { + if (page_size != orig_page_size || external_format == GL_RGBA || external_format == GL_RGB) { + // If the GL doesn't claim to support BGR, we may have to reverse + // the component ordering of the image. + PStatTimer timer(_load_texture_copy_pcollector); + PTA_uchar new_image = PTA_uchar::empty_array(page_size * depth); + copy_image(&new_image[0], image_ptr, orig_view_size, + external_format, num_components, component_width); + + ptimage = std::move(new_image); + image_ptr = ptimage; + } + } + + // Try updating the existing storage (sub-loading) first. + if (!needs_reload) { + if (!upload_texture_level(false, compressed, target, level, + width, sub_height, depth, internal_format, + external_format, component_type, + image_ptr, page_size, pages, usage)) { + break; + } + + // Did that fail? If it did, we'll immediately try again, this time + // loading the texture from scratch. + GLenum error_code = gl_get_error(); + if (error_code != GL_NO_ERROR) { + if (GLCAT.is_warning()) { + GLCAT.warning() + << "GL texture subload failed for " << tex->get_name() + << " level " << level << ": " << get_error_string(error_code) << "\n"; + } + needs_reload = true; + } + } + + if (needs_reload) { + if (!upload_texture_level(true, compressed, target, level, + width, height, depth, internal_format, + external_format, component_type, + image_ptr, page_size, pages, usage)) { + + if (_supports_texture_max_level) { + // Apparently, this is all it's going to get. + glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, level); + } + if (level == 0) { + success = false; + } + break; + } + } + } + + // Purely synchronous path, we can finish up the creation now. + // Report the error message explicitly if the GL texture creation failed. + if (needs_reload) { GLenum error_code = gl_get_error(); if (error_code != GL_NO_ERROR) { GLCAT.error() @@ -14805,23 +15186,349 @@ upload_texture_image(CLP(TextureContext) *gtc, int view, bool needs_reload, << " : " << get_error_string(error_code) << "\n"; gtc->_has_storage = false; - return false; + success = false; } } - if (gtc->_generate_mipmaps && _glGenerateMipmap != nullptr && !image.is_null()) { - // We uploaded an image; we may need to generate mipmaps. - if (GLCAT.is_debug()) { - GLCAT.debug() - << "generating mipmaps for texture " << tex->get_name() << " view " - << view << ", " << width << " x " << height << " x " << depth - << ", mipmaps = " << num_levels << "\n"; +#if defined(HAVE_THREADS) && !defined(OPENGLES_1) + if (async_levels != nullptr) { + // Schedule a follow-up task to finish the upload, which needs to happen + // with bound context, so we use a special mini job queue for that. + + // Storing 0 as last item saves some closure space. + async_levels[num_async_levels] = {0}; + + std::move(counter).then([ + this, token = std::move(token), + async_levels, gtc, view, pbo, pbo_size, upload_count, + external_format, component_type, compressed + ] (bool success) mutable { + call_later([=, token = std::move(token)] () mutable { + _glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); + _glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + + if (gtc->_uploads_finished - upload_count >= 0) { + // Updates arrived out of order, so we skip this one, since a newer + // update was already finished. + GLCAT.info() + << "Discarding async update #" << upload_count << " to texture " + << gtc->get_texture()->get_name() << "\n"; + success = false; + } + else if (view >= gtc->_num_views) { + // If we uploaded a view that is no longer needed, we silently + // consider it a success, even if the task failed. + success = true; + } + else if (!apply_texture(gtc, view)) { + success = false; + } + else if (success) { + PStatTimer timer(_load_texture_pcollector); + + if (gtc->_target == GL_TEXTURE_BUFFER) { + // We can use a trick for buffer textures: just swap the "PBO" with + // the existing texture storage. The existing storage becomes the + // new PBO. Note also that buffer textures have no mipmaps. + _glTexBuffer(GL_TEXTURE_BUFFER, gtc->_internal_format, pbo); + std::swap(pbo, gtc->_buffers[view]); + } + else { + for (int level = 0; async_levels[level].width != 0; ++level) { + AsyncLevel &data = async_levels[level]; + if (!upload_texture_level(false, compressed, gtc->_target, level, + data.width, data.height, data.depth, + gtc->_internal_format, external_format, + component_type, (unsigned char *)data.pbo_offset, + data.page_size, data.pages, GL_STATIC_DRAW)) { + if (_supports_texture_max_level && !gtc->_generate_mipmaps) { + // Apparently, this is all it's going to get. + glTexParameteri(gtc->_target, GL_TEXTURE_MAX_LEVEL, level - 1); + } + success = false; + break; + } + } + } + + if (success) { + gtc->_uploads_finished = upload_count; + } + + if (gtc->_generate_mipmaps && _glGenerateMipmap != nullptr) { + // We uploaded an image; we may need to generate mipmaps. + if (GLCAT.is_debug()) { + GLCAT.debug() + << "generating mipmaps for texture " << gtc->get_texture()->get_name() + << " view " << view << ", " << async_levels[0].width << " x " + << async_levels[0].height << " x " << async_levels[0].depth + << ", mipmaps = " << gtc->_num_levels + << " (async update #" << upload_count << ")\n"; + } + _glGenerateMipmap(gtc->_target); + } + + if (success && gtc->get_texture()->get_post_load_store_cache()) { + if (!do_extract_texture_data(gtc, view)) { + success = false; + } + } + } + _glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + // This goes back into the pool. + gtc->return_pbo(pbo, pbo_size); + + token.complete(success); + + delete[] async_levels; + }); + }); + } else +#endif + { + // This ensures any pending async op will not overwrite what we just did. + gtc->_uploads_finished = upload_count; + + if (pbo != 0) { + // For whatever reason, we haven't used the PBO, so return it. + gtc->return_pbo(pbo, pbo_size); } - _glGenerateMipmap(target); + + if (gtc->_generate_mipmaps && _glGenerateMipmap != nullptr && !image.is_null()) { + // We uploaded an image; we may need to generate mipmaps. + if (GLCAT.is_debug()) { + GLCAT.debug() + << "generating mipmaps for texture " << gtc->get_texture()->get_name() << " view " + << view << ", " << width << " x " << height << " x " << depth + << ", mipmaps = " << num_levels << "\n"; + } + _glGenerateMipmap(gtc->_target); + } + + if (gtc->get_texture()->get_post_load_store_cache()) { + if (!do_extract_texture_data(gtc, view)) { + success = false; + } + } + + token.complete(success); } report_my_gl_errors(); + return success; +} + +/** + * Performs the actual OpenGL call to update the texture data for the given + * mipmap level (be sure to subtract the mipmap_bias before passing it in). + * + * If full_reload is true, recreates the texture storage, otherwise subloads + * into the existing texture storage. A texture storage with undefined + * contents can be created by setting image_ptr to nullptr, in which case + * compressed must be false. + * + * Returns true if this texture format was supported, false otherwise. + */ +bool CLP(GraphicsStateGuardian):: +upload_texture_level(bool full_reload, bool compressed, GLenum target, + int level, int width, int height, int depth, + GLint internal_format, GLint external_format, + GLenum component_type, const unsigned char *image_ptr, + size_t page_size, SparseArray pages, + GLenum usage_hint) { + + switch (target) { +#ifndef OPENGLES_1 + case GL_TEXTURE_3D: + if (!_supports_3d_texture) { + return false; + } + + if (full_reload) { + if (!compressed) { + _glTexImage3D(target, level, internal_format, + width, height, depth, 0, + external_format, component_type, image_ptr); + } else { + _glCompressedTexImage3D(target, level, external_format, + width, height, depth, 0, page_size * depth, image_ptr); + } + } else { + for (size_t sri = 0; sri < pages.get_num_subranges(); ++sri) { + int begin = pages.get_subrange_begin(sri); + int num_pages = pages.get_subrange_end(sri) - begin; + const unsigned char *page_ptr = image_ptr + page_size * begin; + + if (!compressed) { + _glTexSubImage3D(target, level, + 0, 0, begin, width, height, num_pages, + external_format, component_type, page_ptr); + } else { + _glCompressedTexSubImage3D(target, level, + 0, 0, begin, width, height, num_pages, + external_format, + page_size * num_pages, page_ptr); + } + } + } + break; +#endif // OPENGLES_1 + +#ifndef OPENGLES_1 + case GL_TEXTURE_2D_ARRAY: + case GL_TEXTURE_CUBE_MAP_ARRAY: + if (!_supports_2d_texture_array) { + return false; + } + + if (full_reload) { + if (!compressed) { + _glTexImage3D(target, level, internal_format, width, height, depth, 0, + external_format, component_type, image_ptr); + } else { + _glCompressedTexImage3D(target, level, external_format, + width, height, depth, 0, + page_size * depth, image_ptr); + } + } else { + for (size_t sri = 0; sri < pages.get_num_subranges(); ++sri) { + int begin = pages.get_subrange_begin(sri); + int num_pages = pages.get_subrange_end(sri) - begin; + const unsigned char *page_ptr = image_ptr + page_size * begin; + + if (!compressed) { + _glTexSubImage3D(target, level, + 0, 0, begin, width, height, num_pages, + external_format, component_type, page_ptr); + } else { + _glCompressedTexSubImage3D(target, level, + 0, 0, begin, width, height, num_pages, + external_format, + page_size * num_pages, page_ptr); + } + } + } + break; +#endif // !OPENGLES_1 + +#ifndef OPENGLES_1 + case GL_TEXTURE_BUFFER: + if (!_supports_buffer_texture) { + return false; + } + + if (full_reload) { + _glBufferData(GL_TEXTURE_BUFFER, page_size, image_ptr, usage_hint); + } else { + _glBufferSubData(GL_TEXTURE_BUFFER, 0, page_size, image_ptr); + } + break; +#endif // !OPENGLES_1 + + case GL_TEXTURE_CUBE_MAP: + if (!_supports_cube_map) { + return false; + } + + // This is the only texture type that must be specified using separate + // per-page calls. + if (full_reload) { + for (int z = 0; z < 6; ++z) { + GLenum page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + z; + const unsigned char *page_ptr = + (image_ptr != nullptr) ? image_ptr + page_size * z : nullptr; + + if (!compressed) { + glTexImage2D(page_target, level, internal_format, width, height, 0, + external_format, component_type, page_ptr); + } else { + _glCompressedTexImage2D(page_target, level, external_format, + width, height, 0, page_size, page_ptr); + } + } + } else { + for (int z = 0; z < 6; ++z) { + if (pages.get_bit(z)) { + GLenum page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + z; + const unsigned char *page_ptr = image_ptr + page_size * z; + + if (!compressed) { + glTexSubImage2D(page_target, level, 0, 0, width, height, + external_format, component_type, page_ptr); + } else { + _glCompressedTexSubImage2D(page_target, level, + 0, 0, width, height, + external_format, page_size, page_ptr); + } + } + } + } + break; + +#ifndef OPENGLES + case GL_TEXTURE_1D: + if (full_reload) { + if (!compressed) { + glTexImage1D(target, level, internal_format, + width, 0, external_format, component_type, image_ptr); + } else { + _glCompressedTexImage1D(target, level, external_format, + width, 0, page_size, image_ptr); + } + } else { + if (!compressed) { + glTexSubImage1D(target, level, 0, width, + external_format, component_type, image_ptr); + } else { + _glCompressedTexSubImage1D(target, level, 0, width, + external_format, page_size, image_ptr); + } + } + break; +#endif // !OPENGLES + + default: + if (full_reload) { + if (!compressed) { + glTexImage2D(target, level, internal_format, width, height, 0, + external_format, component_type, image_ptr); + } else { + _glCompressedTexImage2D(target, level, external_format, + width, height, 0, page_size, image_ptr); + } + } else { + if (!compressed) { + glTexSubImage2D(target, level, 0, 0, width, height, + external_format, component_type, image_ptr); + } else { + _glCompressedTexSubImage2D(target, level, 0, 0, width, height, + external_format, page_size, image_ptr); + } + } + break; + } + +#ifdef DO_PSTATS + if (full_reload) { + _data_transferred_pcollector.add_level(page_size * depth); + } else { + _data_transferred_pcollector.add_level(pages.get_num_on_bits() * depth); + } +#endif + + // Did that fail? If it did, we'll immediately try again, this time + // loading the texture from scratch. + /*GLenum error_code = gl_get_error(); + if (error_code != GL_NO_ERROR) { + if (GLCAT.is_debug()) { + GLCAT.debug() + << "GL texture subload failed for " << tex->get_name() + << " : " << get_error_string(error_code) << "\n"; + } + full_reload = true; + }*/ return true; } @@ -15065,6 +15772,7 @@ get_texture_memory_size(CLP(TextureContext) *gtc) { void CLP(GraphicsStateGuardian):: check_nonresident_texture(BufferContextChain &chain) { #if defined(SUPPORT_FIXED_FUNCTION) && !defined(OPENGLES) // Residency queries not supported by OpenGL ES. + LightMutexHolder holder(chain._lock); size_t num_textures = chain.get_count(); if (num_textures == 0) { return; @@ -15116,7 +15824,7 @@ do_extract_texture_data(CLP(TextureContext) *gtc, int view) { #ifndef OPENGLES_1 // Make sure any incoherent writes to the texture have been synced. - if (gtc->needs_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT)) { + if (gtc->needs_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT, false)) { issue_memory_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT); } #endif @@ -15151,14 +15859,14 @@ do_extract_texture_data(CLP(TextureContext) *gtc, int view) { #endif } + GLint width = gtc->_width, height = gtc->_height, depth = gtc->_depth; + +#ifndef OPENGLES GLenum page_target = target; if (target == GL_TEXTURE_CUBE_MAP) { // We need a particular page to get the level parameter from. page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X; } - - GLint width = gtc->_width, height = gtc->_height, depth = gtc->_depth; -#ifndef OPENGLES glGetTexLevelParameteriv(page_target, 0, GL_TEXTURE_WIDTH, &width); if (target != GL_TEXTURE_1D) { glGetTexLevelParameteriv(page_target, 0, GL_TEXTURE_HEIGHT, &height); @@ -15694,8 +16402,7 @@ do_extract_texture_data(CLP(TextureContext) *gtc, int view) { PTA_uchar image; size_t page_size = 0; - if (!extract_texture_image(image, page_size, tex, target, page_target, - type, compression, 0)) { + if (!extract_texture_image(image, page_size, tex, target, type, compression, 0)) { return false; } @@ -15713,8 +16420,7 @@ do_extract_texture_data(CLP(TextureContext) *gtc, int view) { // Also get the mipmap levels. for (int n = 1; n < gtc->_num_levels; ++n) { - if (!extract_texture_image(image, page_size, tex, target, page_target, - type, compression, n)) { + if (!extract_texture_image(image, page_size, tex, target, type, compression, n)) { return false; } if (num_views == 1) { @@ -15734,7 +16440,7 @@ do_extract_texture_data(CLP(TextureContext) *gtc, int view) { */ bool CLP(GraphicsStateGuardian):: extract_texture_image(PTA_uchar &image, size_t &page_size, - Texture *tex, GLenum target, GLenum page_target, + Texture *tex, GLenum target, Texture::ComponentType type, Texture::CompressionMode compression, int n) { #ifdef OPENGLES // Extracting texture data unsupported in OpenGL ES. @@ -15763,8 +16469,8 @@ extract_texture_image(PTA_uchar &image, size_t &page_size, if (compression != Texture::CM_off) { GLint image_size; - glGetTexLevelParameteriv(page_target, n, - GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &image_size); + glGetTexLevelParameteriv(GL_TEXTURE_CUBE_MAP_POSITIVE_X, n, + GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &image_size); nassertr(image_size <= (int)page_size, false); page_size = image_size; } @@ -15772,7 +16478,7 @@ extract_texture_image(PTA_uchar &image, size_t &page_size, image = PTA_uchar::empty_array(page_size * 6); for (int z = 0; z < 6; ++z) { - page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + z; + GLenum page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + z; if (compression == Texture::CM_off) { glGetTexImage(page_target, n, external_format, pixel_type, @@ -16119,3 +16825,163 @@ do_issue_scissor() { } } } + +#ifndef OPENGLES_1 +/** + * Maps a buffer for reading. May be temporarily bound to the given target. + */ +void *CLP(GraphicsStateGuardian):: +map_read_buffer(GLenum target, GLuint buffer, size_t size) { + nassertr(buffer != 0, nullptr); + +#ifndef OPENGLES + if (_glMapNamedBufferRange != nullptr) { + return _glMapNamedBufferRange(buffer, 0, size, GL_MAP_READ_BIT); + } +#endif + + void *mapped_ptr = nullptr; + + _glBindBuffer(target, buffer); +#ifdef OPENGLES + // There is neither glMapBuffer nor persistent mapping in OpenGL ES + mapped_ptr = _glMapBufferRange(target, 0, size, GL_MAP_READ_BIT); +#else + // If we get here in desktop GL, we must not have persistent mapping + mapped_ptr = _glMapBuffer(target, GL_READ_ONLY); +#endif + + _glBindBuffer(target, 0); + if (target == GL_SHADER_STORAGE_BUFFER) { + _current_sbuffer_index = 0; + } + return mapped_ptr; +} + +/** + * Maps a buffer as write-only, discarding the previous contents. If + * create_storage is true, allocates new storage for the buffer. May use the + * given target to temporarily bind the buffer, if DSA is not supported. + */ +void *CLP(GraphicsStateGuardian):: +map_write_discard_buffer(GLenum target, GLuint buffer, size_t size, + bool create_storage) { + nassertr(buffer != 0, nullptr); + +#ifndef OPENGLES + if (!create_storage && _glMapNamedBufferRange != nullptr) { + return _glMapNamedBufferRange(buffer, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); + } +#endif + + _glBindBuffer(target, buffer); + + void *mapped_ptr; + if (_glMapBufferRange != nullptr) { + if (create_storage) { + if (_supports_buffer_storage) { + _glBufferStorage(target, size, nullptr, GL_MAP_WRITE_BIT); + } else { + _glBufferData(target, size, nullptr, GL_STATIC_DRAW); + } + } + mapped_ptr = _glMapBufferRange(target, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); + } else { +#ifdef OPENGLES + mapped_ptr = nullptr; +#else + // Explicitly orphan the buffer before mapping. + _glBufferData(target, size, nullptr, GL_STATIC_DRAW); + mapped_ptr = _glMapBuffer(target, GL_WRITE_ONLY); +#endif + } + + _glBindBuffer(target, 0); + return mapped_ptr; +} +#endif // !OPENGLES_1 + +#ifndef OPENGLES_1 +/** + * Inserts a fence into the command stream. + */ +void CLP(GraphicsStateGuardian):: +insert_fence(CompletionToken &&callback) { + GLsync fence = _glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + _fences.push_back({fence, std::move(callback)}); +} + +/** + * Checks which fences are finished and processes those. + */ +void CLP(GraphicsStateGuardian):: +process_fences(bool force) { + if (_fences.empty()) { + return; + } + + PStatTimer timer(_copy_texture_finish_pcollector); + + if (force) { + // Just wait for the last fence, the rest must be complete too then. + PStatTimer timer(_wait_fence_pcollector); + GLsync fence = _fences.back()._object; + _glClientWaitSync(fence, 0, (GLuint64)-1); + } + + while (!_fences.empty()) { + Fence &fence = _fences.front(); + if (!force) { + GLenum result = _glClientWaitSync(fence._object, 0, 0); + if (result != GL_ALREADY_SIGNALED && result != GL_CONDITION_SATISFIED) { + // Not yet done. The rest must not yet be done then, either. + break; + } + } + _glDeleteSync(fence._object); + + std::move(fence._token).complete(true); + _fences.pop_front(); + + // If there is 1 remaining, save it for next frame. This helps prevent an + // inconsistent frame rate when the number of fetched frames alternates + // between 0 and 2, which can settle into a stable feedback loop. + if (!force && _fences.size() == 1) { + break; + } + } +} +#endif // !OPENGLES_1 + +/** + * Adds a job to the queue to be processed later while the context is bound, + * useful for calling from other threads. + */ +void CLP(GraphicsStateGuardian):: +call_later(Completable &&job) { + MutexHolder holder(_job_queue_mutex); + _job_queue.push_back(std::move(job)); + _job_queue_cvar.notify(); +} + +/** + * Processes any pending jobs from the queue. If wait is true, waits for at + * least one job if the queue is empty. + * + * May only be called on the draw thread. + */ +void CLP(GraphicsStateGuardian):: +process_pending_jobs(bool wait) { + JobQueue jobs; + { + MutexHolder holder(_job_queue_mutex); + if (wait && _job_queue.empty()) { + _job_queue_cvar.wait(); + } + _job_queue.swap(jobs); + } + + for (auto &job : jobs) { + std::move(job)(); + } +} diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.h b/panda/src/glstuff/glGraphicsStateGuardian_src.h index 1abbc1d2a4..af9cc85f8c 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.h +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.h @@ -39,6 +39,8 @@ #include "geomVertexArrayData.h" #include "lightMutex.h" #include "pStatGPUTimer.h" +#include "completionToken.h" +#include "asyncTaskChain.h" class PlaneNode; class Light; @@ -166,6 +168,7 @@ typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei buf typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETINTEGERI_VPROC) (GLenum target, GLuint index, GLint *data); typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); typedef void (APIENTRYP PFNGLSHADERSOURCEPROC_P) (GLuint shader, GLsizei count, const GLchar* const *string, const GLint *length); typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); @@ -230,6 +233,7 @@ typedef void (APIENTRYP PFNGLGETPROGRAMBINARYPROC) (GLuint program, GLsizei bufS typedef void (APIENTRYP PFNGLPROGRAMBINARYPROC) (GLuint program, GLenum binaryFormat, const void *binary, GLsizei length); typedef void (APIENTRYP PFNGLGETINTERNALFORMATIVPROC) (GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint *params); typedef void (APIENTRYP PFNGLBUFFERSTORAGEPROC) (GLenum target, GLsizeiptr size, const void *data, GLbitfield flags); +typedef void (APIENTRYP PFNGLCOPYBUFFERSUBDATAPROC) (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREPROC) (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format); typedef void (APIENTRYP PFNGLCLEARTEXIMAGEPROC) (GLuint texture, GLint level, GLenum format, GLenum type, const void *data); typedef void (APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data); @@ -345,7 +349,8 @@ public: #endif virtual TextureContext *prepare_texture(Texture *tex); - virtual bool update_texture(TextureContext *tc, bool force); + virtual bool update_texture(TextureContext *tc, bool force, + CompletionToken token = CompletionToken()); virtual void release_texture(TextureContext *tc); virtual void release_textures(const pvector &contexts); virtual bool extract_texture_data(Texture *tex); @@ -391,9 +396,16 @@ public: #ifndef OPENGLES virtual BufferContext *prepare_shader_buffer(ShaderBuffer *data); - void apply_shader_buffer(GLuint base, ShaderBuffer *buffer); + CLP(BufferContext) *apply_shader_buffer(GLuint base, ShaderBuffer *buffer); virtual void release_shader_buffer(BufferContext *bc); virtual void release_shader_buffers(const pvector &contexts); + virtual bool update_shader_buffer_data(ShaderBuffer *buffer, size_t start, + size_t size, const unsigned char *data); + virtual bool extract_shader_buffer_data(ShaderBuffer *buffer, vector_uchar &data, + size_t start, size_t size); + virtual void async_extract_shader_buffer_data(ShaderBuffer *buffer, vector_uchar &data, + size_t start = 0, size_t size = (size_t)-1, + CompletionToken token = CompletionToken()); #endif #ifndef OPENGLES @@ -418,7 +430,6 @@ public: virtual bool framebuffer_copy_to_ram (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb, ScreenshotRequest *request); - void finish_async_framebuffer_ram_copies(bool force = false); #ifdef SUPPORT_FIXED_FUNCTION void apply_fog(Fog *fog); @@ -636,12 +647,21 @@ protected: bool apply_texture(CLP(TextureContext) *gtc, int view); bool apply_sampler(GLuint unit, const SamplerState &sampler, CLP(TextureContext) *gtc, int view); - bool upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps); - bool upload_texture_image(CLP(TextureContext) *gtc, int view, - bool needs_reload, int mipmap_bias, int num_levels, + bool upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps, + CompletionToken token = CompletionToken()); + bool upload_texture_view(CLP(TextureContext) *gtc, int view, + bool needs_reload, int mipmap_bias, int num_levels, + GLint internal_format, GLint external_format, + GLenum component_type, bool compressed, + int async_buffers, CompletionToken token); + bool upload_texture_level(bool full_reload, bool compressed, + GLenum target, int level, + int width, int height, int depth, GLint internal_format, GLint external_format, GLenum component_type, - Texture::CompressionMode image_compression); + const unsigned char *image_ptr, + size_t page_size, SparseArray pages, + GLenum usage_hint); void generate_mipmaps(CLP(TextureContext) *gtc); bool upload_simple_texture(CLP(TextureContext) *gtc); @@ -649,14 +669,28 @@ protected: void check_nonresident_texture(BufferContextChain &chain); bool do_extract_texture_data(CLP(TextureContext) *gtc, int view); bool extract_texture_image(PTA_uchar &image, size_t &page_size, - Texture *tex, GLenum target, GLenum page_target, - Texture::ComponentType type, - Texture::CompressionMode compression, int n); + Texture *tex, GLenum target, + Texture::ComponentType type, + Texture::CompressionMode compression, int n); #ifdef SUPPORT_FIXED_FUNCTION void do_point_size(); #endif +#ifndef OPENGLES_1 + void *map_read_buffer(GLenum target, GLuint buffer, size_t size); + void *map_write_discard_buffer(GLenum target, GLuint buffer, size_t size, + bool create_storage); +#endif + +#ifndef OPENGLES_1 + void insert_fence(CompletionToken &&callback); + void process_fences(bool force); +#endif + + void call_later(Completable &&job); + void process_pending_jobs(bool wait); + enum AutoAntialiasMode { AA_poly, AA_line, @@ -796,6 +830,10 @@ protected: #endif public: +#ifndef OPENGLES_1 + PFNGLGETINTEGERI_VPROC _glGetIntegeri_v; +#endif + #ifndef OPENGLES_1 bool _use_depth_zero_to_one; bool _use_remapped_depth_range; @@ -850,6 +888,7 @@ public: bool _supports_clear_buffer; #ifndef OPENGLES PFNGLCLEARBUFFERDATAPROC _glClearBufferData; + PFNGLCLEARBUFFERSUBDATAPROC _glClearBufferSubData; #endif PFNGLCOMPRESSEDTEXIMAGE1DPROC _glCompressedTexImage1D; @@ -903,6 +942,10 @@ public: PFNGLGETBUFFERSUBDATAPROC _glGetBufferSubData; #endif +#ifndef OPENGLES_1 + PFNGLCOPYBUFFERSUBDATAPROC _glCopyBufferSubData; +#endif + #ifdef OPENGLES PFNGLMAPBUFFERRANGEEXTPROC _glMapBufferRange; PFNGLUNMAPBUFFEROESPROC _glUnmapBuffer; @@ -910,6 +953,10 @@ public: PFNGLMAPBUFFERRANGEPROC _glMapBufferRange; #endif +#ifndef OPENGLES_1 + bool _supports_pixel_buffers; +#endif + #ifndef OPENGLES_1 bool _supports_uniform_buffers; bool _supports_shader_buffers; @@ -977,6 +1024,13 @@ public: PFNGLTEXTUREPARAMETERIPROC _glTextureParameteri; PFNGLGENERATETEXTUREMIPMAPPROC _glGenerateTextureMipmap; PFNGLBINDTEXTUREUNITPROC _glBindTextureUnit; + PFNGLMAPNAMEDBUFFERRANGEPROC _glMapNamedBufferRange; + PFNGLNAMEDBUFFERSUBDATAPROC _glNamedBufferSubData; + PFNGLCLEARNAMEDBUFFERSUBDATAPROC _glClearNamedBufferSubData; + PFNGLCOPYNAMEDBUFFERSUBDATAPROC _glCopyNamedBufferSubData; + PFNGLGETNAMEDBUFFERSUBDATAPROC _glGetNamedBufferSubData; +#else + static const bool _supports_dsa = false; #endif #ifndef OPENGLES_1 @@ -1161,12 +1215,15 @@ public: #endif #ifndef OPENGLES_1 - // Stores textures for which memory bariers should be issued. - typedef pset TextureSet; - TextureSet _textures_needing_fetch_barrier; - TextureSet _textures_needing_image_access_barrier; - TextureSet _textures_needing_update_barrier; - TextureSet _textures_needing_framebuffer_barrier; + // This count increments every time the corresponding barrier is issued. + // GLTextureContext et al store copies of this counter, when a write is + // performed on a texture, it will set its counter to match the value on the + // GSG to indicate that it is out of sync and the barrier needs to be issued. + int _texture_fetch_barrier_counter = 0; + int _shader_image_access_barrier_counter = 0; + int _texture_update_barrier_counter = 0; + int _framebuffer_barrier_counter = 0; + int _shader_storage_barrier_counter = 0; #endif // RenderState::SlotMask _inv_state_mask; @@ -1216,16 +1273,21 @@ public: FrameTiming *_current_frame_timing = nullptr; #endif - struct AsyncRamCopy { - PT(ScreenshotRequest) _request; - GLuint _pbo; - GLsync _fence; - GLuint _external_format; - int _view; - void *_mapped_pointer; - size_t _size; + struct Fence { + GLsync _object; + CompletionToken _token; }; - pdeque _async_ram_copies; + pdeque _fences; + +#ifdef HAVE_THREADS + PT(AsyncTaskChain) _async_chain; +#endif + + // Min job system pending a real job system + typedef pvector JobQueue; + Mutex _job_queue_mutex; + ConditionVar _job_queue_cvar; + JobQueue _job_queue; BufferResidencyTracker _renderbuffer_residency; @@ -1270,6 +1332,7 @@ private: friend class CLP(BufferContext); friend class CLP(ShaderContext); friend class CLP(CgShaderContext); + friend class CLP(TextureContext); friend class CLP(GraphicsBuffer); friend class CLP(OcclusionQueryContext); }; diff --git a/panda/src/glstuff/glIndexBufferContext_src.cxx b/panda/src/glstuff/glIndexBufferContext_src.cxx index 8b3e81b7ed..6971c1a4be 100644 --- a/panda/src/glstuff/glIndexBufferContext_src.cxx +++ b/panda/src/glstuff/glIndexBufferContext_src.cxx @@ -47,3 +47,13 @@ evict_lru() { update_data_size_bytes(0); mark_unloaded(); } + +/** + * Returns an implementation-defined handle or pointer that can be used + * to interface directly with the underlying API. + * Returns 0 if the underlying implementation does not support this. + */ +uint64_t CLP(IndexBufferContext):: +get_native_id() const { + return _index; +} diff --git a/panda/src/glstuff/glIndexBufferContext_src.h b/panda/src/glstuff/glIndexBufferContext_src.h index dfe011c4d0..5c1053c954 100644 --- a/panda/src/glstuff/glIndexBufferContext_src.h +++ b/panda/src/glstuff/glIndexBufferContext_src.h @@ -27,6 +27,8 @@ public: virtual void evict_lru(); + virtual uint64_t get_native_id() const; + CLP(GraphicsStateGuardian) *_glgsg; // This is the GL "name" of the data object. diff --git a/panda/src/glstuff/glSamplerContext_src.cxx b/panda/src/glstuff/glSamplerContext_src.cxx index 486ebb0cc5..84c50fb94a 100644 --- a/panda/src/glstuff/glSamplerContext_src.cxx +++ b/panda/src/glstuff/glSamplerContext_src.cxx @@ -20,7 +20,7 @@ TypeHandle CLP(SamplerContext)::_type_handle; /** * */ -INLINE CLP(SamplerContext):: +CLP(SamplerContext):: CLP(SamplerContext)(CLP(GraphicsStateGuardian) *glgsg, const SamplerState &sampler) : SamplerContext(sampler) @@ -68,4 +68,14 @@ reset_data() { // the sampler later. glGenSamplers(1, &_index); } +/** + * Returns an implementation-defined handle or pointer that can be used + * to interface directly with the underlying API. + * Returns 0 if the underlying implementation does not support this. + */ +uint64_t CLP(SamplerContext):: +get_native_id() const { + return _index; +} + #endif // OPENGLES_1 diff --git a/panda/src/glstuff/glSamplerContext_src.h b/panda/src/glstuff/glSamplerContext_src.h index 2a07bdd4a1..9085cc3d1e 100644 --- a/panda/src/glstuff/glSamplerContext_src.h +++ b/panda/src/glstuff/glSamplerContext_src.h @@ -25,14 +25,16 @@ class CLP(GraphicsStateGuardian); */ class EXPCL_GL CLP(SamplerContext) : public SamplerContext { public: - INLINE CLP(SamplerContext)(CLP(GraphicsStateGuardian) *glgsg, - const SamplerState &sampler); + CLP(SamplerContext)(CLP(GraphicsStateGuardian) *glgsg, + const SamplerState &sampler); ALLOC_DELETED_CHAIN(CLP(SamplerContext)); virtual ~CLP(SamplerContext)(); virtual void evict_lru(); void reset_data(); + virtual uint64_t get_native_id() const; + // This is the GL "name" of the sampler object. GLuint _index; diff --git a/panda/src/glstuff/glShaderContext_src.cxx b/panda/src/glstuff/glShaderContext_src.cxx index f411bb9fcc..37140ce9f0 100644 --- a/panda/src/glstuff/glShaderContext_src.cxx +++ b/panda/src/glstuff/glShaderContext_src.cxx @@ -416,11 +416,15 @@ CLP(ShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderContext if (_glgsg->_current_shader_context == nullptr) { _glgsg->_glUseProgram(0); } else { - _glgsg->_current_shader_context->bind(); + _glgsg->_current_shader_context->bind(_glgsg); } _mat_part_cache = new LVecBase4f[_shader->cp_get_mat_cache_size()]; _mat_scratch_space = new LVecBase4f[_shader->cp_get_mat_scratch_size()]; + +#ifdef DO_PSTATS + _compute_dispatch_pcollector = PStatCollector(glgsg->_compute_dispatch_pcollector, s->get_debug_name()); +#endif } /** @@ -1039,8 +1043,9 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) { else if (noprefix.compare(7, string::npos, "Height") == 0) { bind._part = Shader::STO_stage_height_i; } - else if (noprefix.compare(7, string::npos, "Selector") == 0) { - bind._part = Shader::STO_stage_selector_i; + else if (noprefix.compare(7, string::npos, "MetallicRoughness") == 0 || + noprefix.compare(7, string::npos, "Selector") == 0) { + bind._part = Shader::STO_stage_metallic_roughness_i; } else if (noprefix.compare(7, string::npos, "Gloss") == 0) { bind._part = Shader::STO_stage_gloss_i; @@ -1048,6 +1053,9 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) { else if (noprefix.compare(7, string::npos, "Emission") == 0) { bind._part = Shader::STO_stage_emission_i; } + else if (noprefix.compare(7, string::npos, "Occlusion") == 0) { + bind._part = Shader::STO_stage_occlusion_i; + } else { GLCAT.error() << "Unrecognized shader input name: p3d_" << noprefix << "\n"; @@ -2188,15 +2196,18 @@ valid() { * all of the shader's input parameters. */ void CLP(ShaderContext):: -bind() { +bind(GraphicsStateGuardian *gsg) { + CLP(GraphicsStateGuardian) *glgsg = (CLP(GraphicsStateGuardian) *)gsg; + _glgsg = glgsg; + if (!_validated) { - _glgsg->_glValidateProgram(_glsl_program); + glgsg->_glValidateProgram(_glsl_program); glsl_report_program_errors(_glsl_program, false); _validated = true; } if (!_shader->get_error_flag()) { - _glgsg->_glUseProgram(_glsl_program); + glgsg->_glUseProgram(_glsl_program); } if (GLCAT.is_spam()) { @@ -2204,7 +2215,7 @@ bind() { << _shader->get_filename() << "\n"; } - _glgsg->report_my_gl_errors(); + glgsg->report_my_gl_errors(); } /** @@ -2672,6 +2683,10 @@ update_shader_vertex_arrays(ShaderContext *prev, bool force) { update_slider_table(table); } + // This ought to be moved elsewhere, but it's convenient to do this here for + // now since it's called before every Geom is drawn. + issue_memory_barriers(); + _glgsg->report_my_gl_errors(); return true; @@ -2809,12 +2824,6 @@ update_shader_texture_bindings(ShaderContext *prev) { int view = _glgsg->get_current_tex_view_offset(); gl_tex = gtc->get_view_index(view); - -#ifndef OPENGLES - if (gtc->needs_barrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT)) { - barriers |= GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; - } -#endif } } input._writable = false; @@ -2875,7 +2884,17 @@ update_shader_texture_bindings(ShaderContext *prev) { access = GL_READ_ONLY; gl_tex = 0; } + } else { + // If no parameters were specified, we have to assume writable access. + input._writable = true; } + +#ifndef OPENGLES + if (gtc->needs_barrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT, input._writable)) { + barriers |= GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; + } +#endif + _glgsg->_glBindImageTexture(i, gl_tex, bind_level, layered, bind_layer, access, gtc->_internal_format); } @@ -2965,7 +2984,7 @@ update_shader_texture_bindings(ShaderContext *prev) { #ifndef OPENGLES // If it was recently written to, we will have to issue a memory barrier // soon. - if (gtc->needs_barrier(GL_TEXTURE_FETCH_BARRIER_BIT)) { + if (gtc->needs_barrier(GL_TEXTURE_FETCH_BARRIER_BIT, false)) { barriers |= GL_TEXTURE_FETCH_BARRIER_BIT; } #endif @@ -3039,7 +3058,35 @@ update_shader_buffer_bindings(ShaderContext *prev) { " (expected at least " << block._min_size << " bytes)\n"; } #endif - _glgsg->apply_shader_buffer(block._binding_index, buffer); + block._gbc = _glgsg->apply_shader_buffer(block._binding_index, buffer); + } +#endif +} + +/** + * Issues memory barriers for shader buffers, should be called before a draw. + */ +void CLP(ShaderContext):: +issue_memory_barriers() { +#ifndef OPENGLES + bool barrier_needed = false; + for (StorageBlock &block : _storage_blocks) { + if (block._gbc != nullptr && + block._gbc->_shader_storage_barrier_counter == _glgsg->_shader_storage_barrier_counter) { + barrier_needed = true; + break; + } + } + + if (barrier_needed) { + _glgsg->issue_memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT); + } + + // We assume that all SSBOs will be written to, for now. + for (StorageBlock &block : _storage_blocks) { + if (block._gbc != nullptr) { + block._gbc->_shader_storage_barrier_counter = _glgsg->_shader_storage_barrier_counter; + } } #endif } diff --git a/panda/src/glstuff/glShaderContext_src.h b/panda/src/glstuff/glShaderContext_src.h index 5da63ed21a..e65355e3a0 100644 --- a/panda/src/glstuff/glShaderContext_src.h +++ b/panda/src/glstuff/glShaderContext_src.h @@ -42,7 +42,7 @@ public: bool get_sampler_texture_type(int &out, GLenum param_type); bool valid(void) override; - void bind() override; + void bind(GraphicsStateGuardian *gsg) override; void unbind() override; void set_state_and_transform(const RenderState *state, @@ -58,6 +58,7 @@ public: void disable_shader_texture_bindings() override; void update_shader_texture_bindings(ShaderContext *prev) override; void update_shader_buffer_bindings(ShaderContext *prev) override; + void issue_memory_barriers(); bool uses_standard_vertex_arrays(void) override { return _uses_standard_vertex_arrays; @@ -96,6 +97,7 @@ private: #ifndef OPENGLES struct StorageBlock { CPT(InternalName) _name; + CLP(BufferContext) *_gbc = nullptr; GLuint _binding_index; GLuint _min_size; }; @@ -118,6 +120,10 @@ private: bool _uses_standard_vertex_arrays; +#ifdef DO_PSTATS + PStatCollector _compute_dispatch_pcollector; +#endif + void glsl_report_shader_errors(GLuint shader, Shader::ShaderType type, bool fatal); void glsl_report_program_errors(GLuint program, bool fatal); bool glsl_compile_shader(Shader::ShaderType type); diff --git a/panda/src/glstuff/glTextureContext_src.I b/panda/src/glstuff/glTextureContext_src.I index bde6a4d977..a0a986e80f 100644 --- a/panda/src/glstuff/glTextureContext_src.I +++ b/panda/src/glstuff/glTextureContext_src.I @@ -59,3 +59,43 @@ get_view_buffer(int view) const { return 0; } } + +/** + * Returns true if an async upload is pending. + */ +INLINE bool CLP(TextureContext):: +is_upload_pending() const { + // We can't simply compare _uploads_started to _uploads_finished, since + // they also get set to the same by cancel_pending_uploads() + return _uploads_pending > 0; +} + +/** + * Waits for all uploads to be finished. + */ +INLINE void CLP(TextureContext):: +wait_pending_uploads() const { + if (is_upload_pending()) { + do_wait_pending_uploads(); + } +} + +/** + * Cancels all asynchronous uploads. Not guaranteed to be cancelled by the + * time this returns, consider following this up with a call to + * wait_pending_uploads(). + */ +INLINE void CLP(TextureContext):: +cancel_pending_uploads() { + _uploads_finished = _uploads_started; +} + +/** + * Waits for an unused PBO unless we're not at the given limit of PBOs yet. + */ +INLINE void CLP(TextureContext):: +wait_for_unused_pbo(int limit) const { + if (_unused_pbos.empty() && _num_pbos >= limit) { + do_wait_for_unused_pbo(limit); + } +} diff --git a/panda/src/glstuff/glTextureContext_src.cxx b/panda/src/glstuff/glTextureContext_src.cxx index 7572ef8a4f..efc001df34 100644 --- a/panda/src/glstuff/glTextureContext_src.cxx +++ b/panda/src/glstuff/glTextureContext_src.cxx @@ -13,6 +13,8 @@ #include "pnotify.h" +static PStatCollector _wait_async_texture_uploads_pcollector("Wait:Async Texture Uploads"); + TypeHandle CLP(TextureContext)::_type_handle; /** @@ -48,6 +50,8 @@ evict_lru() { */ void CLP(TextureContext):: reset_data(GLenum target, int num_views) { + cancel_pending_uploads(); + // Free the texture resources. set_num_views(0); @@ -63,12 +67,13 @@ reset_data(GLenum target, int num_views) { #ifndef OPENGLES_1 // Mark the texture as coherent. - if (gl_enable_memory_barriers) { - _glgsg->_textures_needing_fetch_barrier.erase(this); - _glgsg->_textures_needing_image_access_barrier.erase(this); - _glgsg->_textures_needing_update_barrier.erase(this); - _glgsg->_textures_needing_framebuffer_barrier.erase(this); - } + _texture_fetch_barrier_counter = _glgsg->_texture_fetch_barrier_counter - 1; + _shader_image_read_barrier_counter = _glgsg->_shader_image_access_barrier_counter - 1; + _shader_image_write_barrier_counter = _glgsg->_shader_image_access_barrier_counter - 1; + _texture_read_barrier_counter = _glgsg->_texture_update_barrier_counter - 1; + _texture_write_barrier_counter = _glgsg->_shader_image_access_barrier_counter - 1; + _framebuffer_read_barrier_counter = _glgsg->_framebuffer_barrier_counter - 1; + _framebuffer_write_barrier_counter = _glgsg->_framebuffer_barrier_counter - 1; #endif } @@ -168,26 +173,50 @@ set_num_views(int num_views) { #ifndef OPENGLES_1 /** - * + * Returns true if the texture needs a barrier before a read or write of the + * given kind. If writing is false, only writes are synced, otherwise both + * reads and writes are synced. */ bool CLP(TextureContext):: -needs_barrier(GLbitfield barrier) { +needs_barrier(GLbitfield barrier, bool writing) { if (!gl_enable_memory_barriers) { return false; } - return (((barrier & GL_TEXTURE_FETCH_BARRIER_BIT) && - _glgsg->_textures_needing_fetch_barrier.count(this))) - || (((barrier & GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) && - _glgsg->_textures_needing_image_access_barrier.count(this))) - || (((barrier & GL_TEXTURE_UPDATE_BARRIER_BIT) && - _glgsg->_textures_needing_update_barrier.count(this))) - || (((barrier & GL_FRAMEBUFFER_BARRIER_BIT) && - _glgsg->_textures_needing_framebuffer_barrier.count(this))); + if (barrier & GL_TEXTURE_FETCH_BARRIER_BIT) { + // This is always a read, so only sync RAW. + if (_glgsg->_texture_fetch_barrier_counter == _texture_fetch_barrier_counter) { + return true; + } + } + + if (barrier & GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) { + // Sync WAR, WAW and RAW, but not RAR. + if ((writing && _glgsg->_shader_image_access_barrier_counter == _shader_image_read_barrier_counter) || + (_glgsg->_shader_image_access_barrier_counter == _shader_image_write_barrier_counter)) { + return true; + } + } + + if (barrier & GL_TEXTURE_UPDATE_BARRIER_BIT) { + if ((writing && _glgsg->_texture_update_barrier_counter == _texture_read_barrier_counter) || + (_glgsg->_texture_update_barrier_counter == _texture_write_barrier_counter)) { + return true; + } + } + + if (barrier & GL_FRAMEBUFFER_BARRIER_BIT) { + if ((writing && _glgsg->_framebuffer_barrier_counter == _framebuffer_read_barrier_counter) || + (_glgsg->_framebuffer_barrier_counter == _framebuffer_write_barrier_counter)) { + return true; + } + } + + return false; } /** - * Mark a texture as needing a memory barrier, since a non-coherent read or + * Mark a texture as needing a memory barrier, since an unsynchronized read or * write just happened to it. If 'wrote' is true, it was written to. */ void CLP(TextureContext):: @@ -199,16 +228,73 @@ mark_incoherent(bool wrote) { // If we only read from it, the next read operation won't need another // barrier, since it'll be reading the same data. if (wrote) { - _glgsg->_textures_needing_fetch_barrier.insert(this); + _texture_fetch_barrier_counter = _glgsg->_texture_fetch_barrier_counter; + _shader_image_write_barrier_counter = _glgsg->_shader_image_access_barrier_counter; + _texture_write_barrier_counter = _glgsg->_shader_image_access_barrier_counter; + _framebuffer_write_barrier_counter = _glgsg->_framebuffer_barrier_counter; } // We could still write to it before we read from it, so we have to always - // insert these barriers. This could be slightly optimized so that we don't - // issue a barrier between consecutive image reads, but that may not be - // worth the trouble. - _glgsg->_textures_needing_image_access_barrier.insert(this); - _glgsg->_textures_needing_update_barrier.insert(this); - _glgsg->_textures_needing_framebuffer_barrier.insert(this); + // insert these barriers. + _shader_image_read_barrier_counter = _glgsg->_shader_image_access_barrier_counter; + _texture_read_barrier_counter = _glgsg->_texture_update_barrier_counter; + _framebuffer_read_barrier_counter = _glgsg->_framebuffer_barrier_counter; } #endif // !OPENGLES_1 + +/** + * Returns a PBO with the given size to the pool of unused PBOs. + */ +void CLP(TextureContext):: +return_pbo(GLuint pbo, size_t size) { + // Also triggers when the number of buffers is -1 (which effectively means + // to always delete the buffers after use). + if (_num_pbos > get_texture()->get_num_async_transfer_buffers() || + size < _pbo_size) { + // We have too many PBOs, or this PBO is no longer of the proper + // size, so delete it rather than returning it to the pool. + _num_pbos--; + _glgsg->_glDeleteBuffers(1, &pbo); + } else { + _unused_pbos.push_front(pbo); + } +} + +/** + * Deletes all unused PBOs. + */ +void CLP(TextureContext):: +delete_unused_pbos() { + if (!_unused_pbos.empty()) { + for (GLuint pbo : _unused_pbos) { + _glgsg->_glDeleteBuffers(1, &pbo); + } + _num_pbos -= (int)_unused_pbos.size(); + _unused_pbos.clear(); + } +} + +/** + * Waits for all uploads to be finished. + */ +void CLP(TextureContext):: +do_wait_pending_uploads() const { + PStatTimer timer(_wait_async_texture_uploads_pcollector); + do { + _glgsg->process_pending_jobs(true); + } + while (is_upload_pending()); +} + +/** + * + */ +void CLP(TextureContext):: +do_wait_for_unused_pbo(int limit) const { + PStatTimer timer(_wait_async_texture_uploads_pcollector); + do { + _glgsg->process_pending_jobs(true); + } + while (_unused_pbos.empty() && _num_pbos >= limit); +} diff --git a/panda/src/glstuff/glTextureContext_src.h b/panda/src/glstuff/glTextureContext_src.h index c424488471..03626a46ce 100644 --- a/panda/src/glstuff/glTextureContext_src.h +++ b/panda/src/glstuff/glTextureContext_src.h @@ -41,12 +41,24 @@ public: INLINE GLuint get_view_buffer(int view) const; #ifdef OPENGLES_1 - static constexpr bool needs_barrier(GLbitfield barrier) { return false; }; + static constexpr bool needs_barrier(GLbitfield barrier, bool writing) { return false; }; #else - bool needs_barrier(GLbitfield barrier); + bool needs_barrier(GLbitfield barrier, bool writing); void mark_incoherent(bool wrote); #endif + INLINE bool is_upload_pending() const; + INLINE void wait_pending_uploads() const; + INLINE void cancel_pending_uploads(); + + void return_pbo(GLuint pbo, size_t size); + void delete_unused_pbos(); + INLINE void wait_for_unused_pbo(int limit) const; + +private: + void do_wait_pending_uploads() const; + void do_wait_for_unused_pbo(int limit) const; + private: // This is the GL "name" of the texture object. GLuint _index; @@ -76,8 +88,25 @@ public: GLenum _target; SamplerState _active_sampler; + // These counters are used to prevent out-of-order updates. + int _uploads_started = 0; + int _uploads_finished = 0; + int _uploads_pending = 0; + pdeque _unused_pbos; + int _num_pbos = 0; + size_t _pbo_size = 0; + CLP(GraphicsStateGuardian) *_glgsg; + // These are set to the equivalent counter in glgsg when a write is performed. + int _texture_fetch_barrier_counter = -1; + int _shader_image_read_barrier_counter = -1; + int _shader_image_write_barrier_counter = -1; + int _texture_read_barrier_counter = -1; + int _texture_write_barrier_counter = -1; + int _framebuffer_read_barrier_counter = -1; + int _framebuffer_write_barrier_counter = -1; + public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/glstuff/glVertexBufferContext_src.cxx b/panda/src/glstuff/glVertexBufferContext_src.cxx index 13889e111c..178d0c2b85 100644 --- a/panda/src/glstuff/glVertexBufferContext_src.cxx +++ b/panda/src/glstuff/glVertexBufferContext_src.cxx @@ -47,3 +47,13 @@ evict_lru() { update_data_size_bytes(0); mark_unloaded(); } + +/** + * Returns an implementation-defined handle or pointer that can be used + * to interface directly with the underlying API. + * Returns 0 if the underlying implementation does not support this. + */ +uint64_t CLP(VertexBufferContext):: +get_native_id() const { + return _index; +} diff --git a/panda/src/glstuff/glVertexBufferContext_src.h b/panda/src/glstuff/glVertexBufferContext_src.h index 6833ce4eec..5d5a917c48 100644 --- a/panda/src/glstuff/glVertexBufferContext_src.h +++ b/panda/src/glstuff/glVertexBufferContext_src.h @@ -29,6 +29,8 @@ public: virtual void evict_lru(); + virtual uint64_t get_native_id() const; + CLP(GraphicsStateGuardian) *_glgsg; // This is the GL "name" of the data object. diff --git a/panda/src/glstuff/glmisc_src.cxx b/panda/src/glstuff/glmisc_src.cxx index f223ef266e..d1ae931c7c 100644 --- a/panda/src/glstuff/glmisc_src.cxx +++ b/panda/src/glstuff/glmisc_src.cxx @@ -22,6 +22,11 @@ ConfigVariableBool gl_forward_compatible PRC_DESC("Setting this to true will request a forward-compatible OpenGL " "context, which will not support the fixed-function pipeline.")); +ConfigVariableBool gl_support_dsa + ("gl-support-dsa", true, + PRC_DESC("Configure this false if you suspect your GL's implementation of " + "Direct State Access is broken.")); + ConfigVariableBool gl_support_fbo ("gl-support-fbo", true, PRC_DESC("Configure this false if your GL's implementation of " @@ -321,6 +326,19 @@ ConfigVariableBool gl_depth_zero_to_one "range from 0 to 1, matching other graphics APIs. This setting " "requires OpenGL 4.5, or NVIDIA GeForce 8+ hardware.")); +ConfigVariableInt gl_texture_transfer_num_threads + ("gl-texture-transfer-num-threads", 2, + PRC_DESC("The number of threads that will be started to upload and download " + "texture data asynchronously, either via the setup_async_transfer " + "interface on the the Texture class or via the async screenshot " + "interface.")); + +ConfigVariableEnum gl_texture_transfer_thread_priority + ("gl-texture-transfer-thread-priority", TP_normal, + PRC_DESC("The default thread priority to assign to the threads created for " + "asynchronous texture transfers. The default is 'normal'; you may " + "also specify 'low', 'high', or 'urgent'.")); + extern ConfigVariableBool gl_parallel_arrays; void CLP(init_classes)() { diff --git a/panda/src/glstuff/glmisc_src.h b/panda/src/glstuff/glmisc_src.h index 1cc96726fe..deb219dc5a 100644 --- a/panda/src/glstuff/glmisc_src.h +++ b/panda/src/glstuff/glmisc_src.h @@ -17,6 +17,7 @@ #include "configVariableEnum.h" #include "geomEnums.h" #include "coordinateSystem.h" +#include "threadPriority.h" // Define some macros to transparently map to the double or float versions of // the OpenGL function names. @@ -35,6 +36,7 @@ extern EXPCL_GL ConfigVariableInt gl_version; extern EXPCL_GL ConfigVariableBool gl_forward_compatible; extern EXPCL_GL ConfigVariableBool gl_support_fbo; +extern ConfigVariableBool gl_support_dsa; extern ConfigVariableBool gl_cheap_textures; extern ConfigVariableBool gl_ignore_clamp; extern ConfigVariableBool gl_support_clamp_to_border; @@ -75,6 +77,8 @@ extern ConfigVariableBool gl_support_shadow_filter; extern ConfigVariableBool gl_support_vertex_array_bgra; extern ConfigVariableBool gl_force_image_bindings_writeonly; extern ConfigVariableEnum gl_coordinate_system; +extern ConfigVariableInt gl_texture_transfer_num_threads; +extern ConfigVariableEnum gl_texture_transfer_thread_priority; extern EXPCL_GL void CLP(init_classes)(); diff --git a/panda/src/glxdisplay/glxGraphicsBuffer.cxx b/panda/src/glxdisplay/glxGraphicsBuffer.cxx index 6ede9a3e36..7a29be94f6 100644 --- a/panda/src/glxdisplay/glxGraphicsBuffer.cxx +++ b/panda/src/glxdisplay/glxGraphicsBuffer.cxx @@ -166,7 +166,8 @@ open_buffer() { // with the old gsg. DCAST_INTO_R(glxgsg, _gsg, false); - if (!glxgsg->_context_has_pbuffer || + if (glxgsg->get_engine() != _engine || + !glxgsg->_context_has_pbuffer || !glxgsg->get_fb_properties().subsumes(_fb_properties)) { // We need a new pixel format, and hence a new GSG. glxgsg = new glxGraphicsStateGuardian(_engine, _pipe, glxgsg); diff --git a/panda/src/glxdisplay/glxGraphicsPipe.cxx b/panda/src/glxdisplay/glxGraphicsPipe.cxx index e4ec8380fb..e4ed9ca2f6 100644 --- a/panda/src/glxdisplay/glxGraphicsPipe.cxx +++ b/panda/src/glxdisplay/glxGraphicsPipe.cxx @@ -130,6 +130,9 @@ make_output(const string &name, (flags & (BF_require_parasite | BF_require_window)) != 0) { return nullptr; } + if (host->get_engine() != engine) { + return nullptr; + } // Early failure - if we are sure that this buffer WONT meet specs, we can // bail out early. if ((flags & BF_fb_props_optional) == 0) { diff --git a/panda/src/glxdisplay/glxGraphicsStateGuardian.cxx b/panda/src/glxdisplay/glxGraphicsStateGuardian.cxx index 83ea5f2b2f..be8c5f7f10 100644 --- a/panda/src/glxdisplay/glxGraphicsStateGuardian.cxx +++ b/panda/src/glxdisplay/glxGraphicsStateGuardian.cxx @@ -402,6 +402,7 @@ choose_pixel_format(const FrameBufferProperties &properties, << ", context_has_pixmap = " << _context_has_pixmap << "\n"; } + XFree(configs); return; } } @@ -414,6 +415,10 @@ choose_pixel_format(const FrameBufferProperties &properties, _visuals = nullptr; } + if (configs != nullptr) { + XFree(configs); + } + glxdisplay_cat.warning() << "No suitable FBConfig contexts available; using XVisual only.\n" << _fbprops << "\n"; diff --git a/panda/src/glxdisplay/glxGraphicsWindow.cxx b/panda/src/glxdisplay/glxGraphicsWindow.cxx index 449d1f66a0..e882bc98fb 100644 --- a/panda/src/glxdisplay/glxGraphicsWindow.cxx +++ b/panda/src/glxdisplay/glxGraphicsWindow.cxx @@ -184,7 +184,8 @@ open_window() { // If the old gsg has the wrong pixel format, create a new one that shares // with the old gsg. DCAST_INTO_R(glxgsg, _gsg, false); - if (!glxgsg->get_fb_properties().subsumes(_fb_properties)) { + if (glxgsg->get_engine() != _engine || + !glxgsg->get_fb_properties().subsumes(_fb_properties)) { glxgsg = new glxGraphicsStateGuardian(_engine, _pipe, glxgsg); glxgsg->choose_pixel_format(_fb_properties, glx_pipe->get_display(), glx_pipe->get_screen(), false, false); _gsg = glxgsg; diff --git a/panda/src/gobj/adaptiveLru.I b/panda/src/gobj/adaptiveLru.I index 41acf47c38..ae3a62c3f2 100644 --- a/panda/src/gobj/adaptiveLru.I +++ b/panda/src/gobj/adaptiveLru.I @@ -134,7 +134,10 @@ get_lru() const { */ INLINE void AdaptiveLruPage:: dequeue_lru() { - enqueue_lru(nullptr); + if (_lru != nullptr) { + _lru->do_remove_page(this); + _lru = nullptr; + } } /** diff --git a/panda/src/gobj/bufferContext.cxx b/panda/src/gobj/bufferContext.cxx index b268400d35..4ee4f1833d 100644 --- a/panda/src/gobj/bufferContext.cxx +++ b/panda/src/gobj/bufferContext.cxx @@ -12,6 +12,7 @@ */ #include "bufferContext.h" +#include "lightMutexHolder.h" TypeHandle BufferContext::_type_handle; @@ -43,7 +44,8 @@ BufferContext:: void BufferContext:: set_owning_chain(BufferContextChain *chain) { if (chain != _owning_chain) { - if (_owning_chain != nullptr){ + if (_owning_chain != nullptr) { + LightMutexHolder holder(_owning_chain->_lock); --(_owning_chain->_count); _owning_chain->adjust_bytes(-(int)_data_size_bytes); remove_from_list(); @@ -52,6 +54,7 @@ set_owning_chain(BufferContextChain *chain) { _owning_chain = chain; if (_owning_chain != nullptr) { + LightMutexHolder holder(_owning_chain->_lock); ++(_owning_chain->_count); _owning_chain->adjust_bytes((int)_data_size_bytes); insert_before(_owning_chain); diff --git a/panda/src/gobj/bufferContext.h b/panda/src/gobj/bufferContext.h index 60c9ab3333..0d6d49b5e4 100644 --- a/panda/src/gobj/bufferContext.h +++ b/panda/src/gobj/bufferContext.h @@ -73,7 +73,7 @@ protected: TypedWritableReferenceCount *_object; private: - BufferResidencyTracker *_residency; + BufferResidencyTracker *const _residency; int _residency_state; size_t _data_size_bytes; diff --git a/panda/src/gobj/bufferContextChain.cxx b/panda/src/gobj/bufferContextChain.cxx index 4f27c90c88..780ab23350 100644 --- a/panda/src/gobj/bufferContextChain.cxx +++ b/panda/src/gobj/bufferContextChain.cxx @@ -14,11 +14,15 @@ #include "bufferContextChain.h" #include "bufferContext.h" #include "indent.h" +#include "lightMutexHolder.h" /** * Returns the first BufferContext object stored in the tracker. You can walk * through the entire list of objects stored on the tracker by calling * get_next() on each returned object, until the return value is NULL. + * + * This does not grab the lock; make sure you are holding the lock while + * iterating over the chain. */ BufferContext *BufferContextChain:: get_first() { @@ -32,9 +36,11 @@ get_first() { /** * Moves all of the BufferContexts from the other tracker onto this one. + * The other chain must be locked. */ void BufferContextChain:: take_from(BufferContextChain &other) { + LightMutexHolder holder(_lock); _total_size += other._total_size; _count += other._count; other._total_size = 0; @@ -55,6 +61,7 @@ take_from(BufferContextChain &other) { */ void BufferContextChain:: write(std::ostream &out, int indent_level) const { + LightMutexHolder holder(_lock); indent(out, indent_level) << _count << " objects, consuming " << _total_size << " bytes:\n"; diff --git a/panda/src/gobj/bufferContextChain.h b/panda/src/gobj/bufferContextChain.h index b995c2e583..153401b264 100644 --- a/panda/src/gobj/bufferContextChain.h +++ b/panda/src/gobj/bufferContextChain.h @@ -16,6 +16,7 @@ #include "pandabase.h" #include "linkedListNode.h" +#include "lightMutex.h" class BufferContext; @@ -47,6 +48,9 @@ private: size_t _total_size; int _count; +public: + LightMutex _lock; + friend class BufferContext; }; diff --git a/panda/src/gobj/bufferResidencyTracker.cxx b/panda/src/gobj/bufferResidencyTracker.cxx index 9ac4fef735..3d05fbee83 100644 --- a/panda/src/gobj/bufferResidencyTracker.cxx +++ b/panda/src/gobj/bufferResidencyTracker.cxx @@ -117,6 +117,7 @@ write(std::ostream &out, int indent_level) const { */ void BufferResidencyTracker:: move_inactive(BufferContextChain &inactive, BufferContextChain &active) { + LightMutexHolder active_holder(active._lock); BufferContext *node = active.get_first(); while (node != nullptr) { nassertv((node->_residency_state & S_active) != 0); diff --git a/panda/src/gobj/geom.cxx b/panda/src/gobj/geom.cxx index 8a9ae9c35e..447abf93ee 100644 --- a/panda/src/gobj/geom.cxx +++ b/panda/src/gobj/geom.cxx @@ -1373,7 +1373,7 @@ compute_internal_bounds(Geom::CData *cdata, Thread *current_thread) const { nassertv(!pmax.is_nan()); // Then we put the bounding volume around both of those points. - PN_stdfloat avg_box_area; + PN_stdfloat avg_box_area {}; switch (btype) { case BoundingVolume::BT_best: case BoundingVolume::BT_fastest: diff --git a/panda/src/gobj/preparedGraphicsObjects.cxx b/panda/src/gobj/preparedGraphicsObjects.cxx index b76b052476..927c5f2111 100644 --- a/panda/src/gobj/preparedGraphicsObjects.cxx +++ b/panda/src/gobj/preparedGraphicsObjects.cxx @@ -1380,6 +1380,15 @@ prepare_shader_buffer_now(ShaderBuffer *data, GraphicsStateGuardianBase *gsg) { return bc; } +/** + * Schedules the given function to be called during the next begin_frame(). + */ +void PreparedGraphicsObjects:: +enqueue_call(EnqueuedCall &&call) { + ReMutexHolder holder(_lock); + _enqueued_calls.push_back(std::move(call)); +} + /** * Creates a new future for the given object. */ @@ -1515,9 +1524,24 @@ begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) { Texture *tex = qti->first; TextureContext *tc = tex->prepare_now(this, gsg); if (tc != nullptr) { - gsg->update_texture(tc, true); - if (qti->second != nullptr) { - qti->second->set_result(tc); + if (tex->get_num_async_transfer_buffers() == 0) { + gsg->update_texture(tc, true); + if (qti->second != nullptr) { + qti->second->set_result(tc); + } + } else { + // Async update + CompletionToken token; + if (qti->second != nullptr) { + token = [tc, fut = std::move(qti->second)] (bool success) { + if (success) { + fut->set_result(tc); + } else { + fut->notify_removed(); + } + }; + } + gsg->update_texture(tc, false, std::move(token)); } } } @@ -1586,6 +1610,12 @@ begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) { } _enqueued_shader_buffers.clear(); + + for (EnqueuedCall &call : _enqueued_calls) { + std::move(call)(gsg); + } + + _enqueued_calls.clear(); } /** diff --git a/panda/src/gobj/preparedGraphicsObjects.h b/panda/src/gobj/preparedGraphicsObjects.h index fac1ab630d..f47d3a9275 100644 --- a/panda/src/gobj/preparedGraphicsObjects.h +++ b/panda/src/gobj/preparedGraphicsObjects.h @@ -31,6 +31,10 @@ #include "adaptiveLru.h" #include "asyncFuture.h" +#ifndef CPPPARSER +#include +#endif + class TextureContext; class SamplerContext; class GeomContext; @@ -160,6 +164,12 @@ PUBLISHED: GraphicsStateGuardianBase *gsg); public: +#ifndef CPPPARSER + typedef std::function EnqueuedCall; + + void enqueue_call(EnqueuedCall &&call); +#endif + /** * This is a handle to an enqueued object, from which the result can be * obtained upon completion. @@ -281,6 +291,7 @@ private: Buffers _prepared_shader_buffers; pvector _released_shader_buffers; EnqueuedShaderBuffers _enqueued_shader_buffers; + pvector _enqueued_calls; BufferCache _vertex_buffer_cache; BufferCacheLRU _vertex_buffer_cache_lru; diff --git a/panda/src/gobj/pythonTexturePoolFilter.cxx b/panda/src/gobj/pythonTexturePoolFilter.cxx index 0fc11b181d..f20cc094ce 100644 --- a/panda/src/gobj/pythonTexturePoolFilter.cxx +++ b/panda/src/gobj/pythonTexturePoolFilter.cxx @@ -101,17 +101,19 @@ pre_load(const Filename &orig_filename, const Filename &orig_alpha_filename, #endif // Wrap the arguments. - PyObject *args = Py_BuildValue("(OOiiNO)", - DTool_CreatePyInstance((void *)&orig_filename, Dtool_Filename, false, true), - DTool_CreatePyInstance((void *)&orig_alpha_filename, Dtool_Filename, false, true), - primary_file_num_channels, - alpha_file_channel, - PyBool_FromLong(read_mipmaps), - DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true) - ); + PyObject *args[7]; + args[1] = DTool_CreatePyInstance((void *)&orig_filename, Dtool_Filename, false, true); + args[2] = DTool_CreatePyInstance((void *)&orig_alpha_filename, Dtool_Filename, false, true); + args[3] = PyLong_FromLong(primary_file_num_channels); + args[4] = PyLong_FromLong(alpha_file_channel); + args[5] = PyBool_FromLong(read_mipmaps); + args[6] = DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true); - PyObject *result = PythonThread::call_python_func(_pre_load_func, args); - Py_DECREF(args); + PyObject *result = PythonThread::call_python_func(_pre_load_func, args + 1, 6 | PY_VECTORCALL_ARGUMENTS_OFFSET); + + for (size_t i = 1; i < 7; ++i) { + Py_DECREF(args[i]); + } PT(Texture) tex; if (result != nullptr) { @@ -162,11 +164,11 @@ post_load(Texture *tex) { #endif // Wrap the arguments. - PyObject *args = PyTuple_Pack(1, - DTool_CreatePyInstance(tex, Dtool_Texture, true, false) - ); - PyObject *result = PythonThread::call_python_func(_post_load_func, args); - Py_DECREF(args); + PyObject *args[2]; + args[1] = DTool_CreatePyInstance(tex, Dtool_Texture, true, false); + + PyObject *result = PythonThread::call_python_func(_post_load_func, args + 1, 1 | PY_VECTORCALL_ARGUMENTS_OFFSET); + Py_DECREF(args[1]); PT(Texture) result_tex; if (result != nullptr) { diff --git a/panda/src/gobj/savedContext.cxx b/panda/src/gobj/savedContext.cxx index 92a1af46ca..41224846bb 100644 --- a/panda/src/gobj/savedContext.cxx +++ b/panda/src/gobj/savedContext.cxx @@ -31,3 +31,13 @@ void SavedContext:: write(std::ostream &out, int indent_level) const { indent(out, indent_level) << *this << "\n"; } + +/** + * Returns an implementation-defined handle or pointer that can be used + * to interface directly with the underlying API. + * Returns 0 if the underlying implementation does not support this. + */ +uint64_t SavedContext:: +get_native_id() const { + return 0; +} diff --git a/panda/src/gobj/savedContext.h b/panda/src/gobj/savedContext.h index 6c2db6aec8..ee4cc9f170 100644 --- a/panda/src/gobj/savedContext.h +++ b/panda/src/gobj/savedContext.h @@ -31,10 +31,12 @@ public: virtual void write(std::ostream &out, int indent_level) const; PUBLISHED: + virtual uint64_t get_native_id() const; + +public: static TypeHandle get_class_type() { return _type_handle; } -public: static void init_type() { TypedObject::init_type(); register_type(_type_handle, "SavedContext", diff --git a/panda/src/gobj/shader.cxx b/panda/src/gobj/shader.cxx index fc4ac4e7ad..8cea76df6c 100644 --- a/panda/src/gobj/shader.cxx +++ b/panda/src/gobj/shader.cxx @@ -742,7 +742,7 @@ cp_add_mat_spec(ShaderMatSpec &spec) { spec._cache_offset[p] = offset + begin[p] * size; } if (spec._func == SMF_shader_input_ptr) { - _mat_scratch_size = std::max(_mat_scratch_size, spec._array_count); + _mat_scratch_size = std::max(_mat_scratch_size, spec._array_count * ((spec._num_components + 3) / 4)); // We specify SSD_frame because a PTA may be modified by the app from // frame to frame, and we have no way to know. So, we must respecify a diff --git a/panda/src/gobj/shader.h b/panda/src/gobj/shader.h index 4bd548d331..88021d2403 100644 --- a/panda/src/gobj/shader.h +++ b/panda/src/gobj/shader.h @@ -232,9 +232,10 @@ public: STO_stage_add_i, STO_stage_normal_i, STO_stage_height_i, - STO_stage_selector_i, + STO_stage_metallic_roughness_i, STO_stage_gloss_i, STO_stage_emission_i, + STO_stage_occlusion_i, }; enum ShaderArgClass { diff --git a/panda/src/gobj/shaderBuffer.cxx b/panda/src/gobj/shaderBuffer.cxx index 9164a870ae..8872b64287 100644 --- a/panda/src/gobj/shaderBuffer.cxx +++ b/panda/src/gobj/shaderBuffer.cxx @@ -32,6 +32,26 @@ output(std::ostream &out) const { out << "buffer " << get_name() << ", " << _data_size_bytes << "B, " << _usage_hint; } +/** + * Asynchronously requests to read the given subset of the data on the CPU. + */ +PT(AsyncFuture) ShaderBuffer:: +extract_data(PreparedGraphicsObjects *prepared_objects, size_t start, size_t size) { + PT(AsyncFuture) future = new AsyncFuture; + PT(ParamBytes) bytes = new ParamBytes(vector_uchar()); + + prepared_objects->enqueue_call([=, bytes = std::move(bytes)] (GraphicsStateGuardianBase *gsg) { + gsg->async_extract_shader_buffer_data(this, (vector_uchar &)bytes->get_value(), start, size, [future = std::move(future), bytes = std::move(bytes)] (bool success) { + if (success) { + future->set_result(bytes); + } else { + future->set_result(nullptr); + } + }); + }); + return future; +} + /** * Indicates that the data should be enqueued to be prepared in the indicated * prepared_objects at the beginning of the next frame. This will ensure the diff --git a/panda/src/gobj/shaderBuffer.h b/panda/src/gobj/shaderBuffer.h index d523e13256..8cc9eedf90 100644 --- a/panda/src/gobj/shaderBuffer.h +++ b/panda/src/gobj/shaderBuffer.h @@ -21,6 +21,7 @@ #include "graphicsStateGuardianBase.h" #include "factoryParams.h" #include "vector_uchar.h" +#include "asyncFuture.h" class BufferContext; class PreparedGraphicsObjects; @@ -51,6 +52,9 @@ PUBLISHED: MAKE_PROPERTY(data_size_bytes, get_data_size_bytes); MAKE_PROPERTY(usage_hint, get_usage_hint); + PT(AsyncFuture) extract_data(PreparedGraphicsObjects *prepared_objects, + size_t start = 0, size_t size = (size_t)-1); + void prepare(PreparedGraphicsObjects *prepared_objects); bool is_prepared(PreparedGraphicsObjects *prepared_objects) const; diff --git a/panda/src/gobj/shaderContext.h b/panda/src/gobj/shaderContext.h index a8d8add8ed..53fb69bce6 100644 --- a/panda/src/gobj/shaderContext.h +++ b/panda/src/gobj/shaderContext.h @@ -19,6 +19,8 @@ #include "savedContext.h" #include "shader.h" +class GraphicsStateGuardian; + /** * The ShaderContext is meant to contain the compiled version of a shader * string. ShaderContext is an abstract base class, there will be a subclass @@ -38,7 +40,7 @@ public: const TransformState *) {}; INLINE virtual bool valid() { return false; } - INLINE virtual void bind() {}; + INLINE virtual void bind(GraphicsStateGuardian *gsg) {}; INLINE virtual void unbind() {}; INLINE virtual void issue_parameters(int altered) {}; INLINE virtual void disable_shader_vertex_arrays() {}; diff --git a/panda/src/gobj/texture.I b/panda/src/gobj/texture.I index 0d349f6653..de82e7580c 100644 --- a/panda/src/gobj/texture.I +++ b/panda/src/gobj/texture.I @@ -278,7 +278,7 @@ set_clear_color(const LColor &color) { INLINE void Texture:: clear_clear_color() { CDWriter cdata(_cycler, true); - cdata->_has_clear_color = true; + cdata->_has_clear_color = false; } /** @@ -2139,6 +2139,14 @@ rescale_texture() { return do_rescale_texture(cdata); } +/** + * Returns the number previously passed to setup_async_transfer(). + */ +INLINE int Texture:: +get_num_async_transfer_buffers() const { + return _num_async_transfer_buffers.load(std::memory_order_relaxed); +} + /** * Works like adjust_size, but also considers the texture class. Movie * textures, for instance, always pad outwards, regardless of textures- diff --git a/panda/src/gobj/texture.cxx b/panda/src/gobj/texture.cxx index 2afe0f0320..abbe304b73 100644 --- a/panda/src/gobj/texture.cxx +++ b/panda/src/gobj/texture.cxx @@ -1570,6 +1570,27 @@ get_view_modified_pages(UpdateSeq since, int view, int n) const { return result; } +/** + * Sets the number of buffers for asynchronous upload of texture data. If this + * number is higher than 0, future texture uploads will occur in the background, + * up to the provided amount at a time. The asynchronous upload will be + * triggered by calls to prepare() or when the texture comes into view and + * allow-incomplete-render is true. + * + * Each buffer is only large enough to contain a single view, so you may wish + * to create twice as many buffers if you want to update twice as many views. + * + * You can also pass the special value -1, which means to create as many + * buffers as is necessary for all asynchronous uploads to take place, and they + * will be deleted afterwards automatically. + * + * This setting will take effect immediately. + */ +void Texture:: +setup_async_transfer(int num_buffers) { + _num_async_transfer_buffers.store(num_buffers); +} + /** * Indicates that the texture should be enqueued to be prepared in the * indicated prepared_objects at the beginning of the next frame. This will @@ -5704,7 +5725,14 @@ do_modify_ram_image(CData *cdata) { } else { do_clear_ram_mipmap_images(cdata); } - return cdata->_ram_images[0]._image; + PTA_uchar data = cdata->_ram_images[0]._image; + if (data.get_node_ref_count() > 0) { + // Copy on write, if an upload thread is reading this now. + PTA_uchar new_data = PTA_uchar::empty_array(0); + new_data.v() = data.v(); + data.swap(new_data); + } + return data; } /** @@ -5779,7 +5807,15 @@ do_modify_ram_mipmap_image(CData *cdata, int n) { cdata->_ram_images[n]._image.empty()) { do_make_ram_mipmap_image(cdata, n); } - return cdata->_ram_images[n]._image; + + PTA_uchar data = cdata->_ram_images[n]._image; + if (data.get_node_ref_count() > 0) { + // Copy on write, if an upload thread is reading this now. + PTA_uchar new_data = PTA_uchar::empty_array(0); + new_data.v() = data.v(); + data.swap(new_data); + } + return data; } /** @@ -7577,7 +7613,7 @@ get_ram_image_as(const string &requested_format) { // If the image size wasn't a multiple of 4, we may have a handful of // pixels left over. Convert those the slower way. uint8_t *tail = (uint8_t *)dst; - for (int i = (imgsize & ~0x3); i < imgsize; ++i) { + for (size_t i = (imgsize & ~0x3); i < imgsize; ++i) { uint32_t v = *src++; *tail++ = (v & 0x00ff0000u) >> 16; *tail++ = (v & 0x0000ff00u) >> 8; @@ -7591,8 +7627,8 @@ get_ram_image_as(const string &requested_format) { // Convert blocks of 4 pixels at a time, so that we can treat both the // source and destination as 32-bit integers. - int blocks = imgsize >> 2; - for (int i = 0; i < blocks; ++i) { + size_t blocks = imgsize >> 2; + for (size_t i = 0; i < blocks; ++i) { uint32_t v0 = *src++; uint32_t v1 = *src++; uint32_t v2 = *src++; @@ -7605,7 +7641,7 @@ get_ram_image_as(const string &requested_format) { // If the image size wasn't a multiple of 4, we may have a handful of // pixels left over. Convert those the slower way. uint8_t *tail = (uint8_t *)dst; - for (int i = (imgsize & ~0x3); i < imgsize; ++i) { + for (size_t i = (imgsize & ~0x3); i < imgsize; ++i) { uint32_t v = *src++; *tail++ = (v & 0x000000ffu); *tail++ = (v & 0x0000ff00u) >> 8; @@ -7617,7 +7653,7 @@ get_ram_image_as(const string &requested_format) { uint8_t *dst = (uint8_t *)newdata.p(); if (format == "RGB" && cdata->_num_components == 3) { - for (int i = 0; i < imgsize; ++i) { + for (size_t i = 0; i < imgsize; ++i) { *dst++ = src[2]; *dst++ = src[1]; *dst++ = src[0]; diff --git a/panda/src/gobj/texture.h b/panda/src/gobj/texture.h index f6ec5eb1f2..4ba2b58169 100644 --- a/panda/src/gobj/texture.h +++ b/panda/src/gobj/texture.h @@ -47,6 +47,7 @@ #include "pfmFile.h" #include "asyncTask.h" #include "extension.h" +#include "patomic.h" class TextureContext; class FactoryParams; @@ -536,6 +537,8 @@ PUBLISHED: MAKE_PROPERTY(auto_texture_scale, get_auto_texture_scale, set_auto_texture_scale); + void setup_async_transfer(int num_buffers); + PT(AsyncFuture) prepare(PreparedGraphicsObjects *prepared_objects); bool is_prepared(PreparedGraphicsObjects *prepared_objects) const; bool was_image_modified(PreparedGraphicsObjects *prepared_objects) const; @@ -628,6 +631,7 @@ PUBLISHED: public: void texture_uploaded(); + INLINE int get_num_async_transfer_buffers() const; virtual bool has_cull_callback() const; virtual bool cull_callback(CullTraverser *trav, const CullTraverserData &data) const; @@ -1072,6 +1076,8 @@ protected: typedef pmap Contexts; Contexts _contexts; + patomic_signed_lock_free _num_async_transfer_buffers { 0 }; + // It is common, when using normal maps, specular maps, gloss maps, and // such, to use a file naming convention where the filenames of the special // maps are derived by concatenating a suffix to the name of the diffuse diff --git a/panda/src/gobj/textureContext.cxx b/panda/src/gobj/textureContext.cxx index 7a83a17a01..767dbbe294 100644 --- a/panda/src/gobj/textureContext.cxx +++ b/panda/src/gobj/textureContext.cxx @@ -15,16 +15,6 @@ TypeHandle TextureContext::_type_handle; -/** - * Returns an implementation-defined handle or pointer that can be used - * to interface directly with the underlying API. - * Returns 0 if the underlying implementation does not support this. - */ -uint64_t TextureContext:: -get_native_id() const { - return 0; -} - /** * Similar to get_native_id, but some implementations use a separate * identifier for the buffer object associated with buffer textures. diff --git a/panda/src/gobj/textureContext.h b/panda/src/gobj/textureContext.h index 765509ec65..006f0a607c 100644 --- a/panda/src/gobj/textureContext.h +++ b/panda/src/gobj/textureContext.h @@ -37,7 +37,8 @@ public: PUBLISHED: INLINE Texture *get_texture() const; INLINE int get_view() const; - virtual uint64_t get_native_id() const; + + //virtual uint64_t get_native_id() const; virtual uint64_t get_native_buffer_id() const; INLINE bool was_modified() const; diff --git a/panda/src/gobj/textureStage.cxx b/panda/src/gobj/textureStage.cxx index 3540deb92d..8292083773 100644 --- a/panda/src/gobj/textureStage.cxx +++ b/panda/src/gobj/textureStage.cxx @@ -519,14 +519,20 @@ operator << (ostream &out, TextureStage::Mode mode) { case TextureStage::M_height: return out << "height"; - case TextureStage::M_selector: - return out << "selector"; + case TextureStage::M_metallic_roughness: + return out << "metallic_roughness"; case TextureStage::M_normal_gloss: return out << "normal_gloss"; case TextureStage::M_emission: return out << "emission"; + + case TextureStage::M_occlusion: + return out << "occlusion"; + + case TextureStage::M_occlusion_metallic_roughness: + return out << "occlusion_metallic_roughness"; } return out << "**invalid Mode(" << (int)mode << ")**"; diff --git a/panda/src/gobj/textureStage.h b/panda/src/gobj/textureStage.h index 10a9bc5251..ff5bcb82be 100644 --- a/panda/src/gobj/textureStage.h +++ b/panda/src/gobj/textureStage.h @@ -61,10 +61,14 @@ PUBLISHED: M_glow, // Rarely used: modulate_glow is more efficient. M_gloss, // Rarely used: modulate_gloss is more efficient. M_height, // Rarely used: normal_height is more efficient. - M_selector, + M_metallic_roughness, // metalness in B, roughness in G M_normal_gloss, M_emission, + M_occlusion, // In red channel + M_occlusion_metallic_roughness, + + M_selector = M_metallic_roughness, }; enum CombineMode { diff --git a/panda/src/grutil/config_grutil.cxx b/panda/src/grutil/config_grutil.cxx index 6f74fd5e1b..fea92c6673 100644 --- a/panda/src/grutil/config_grutil.cxx +++ b/panda/src/grutil/config_grutil.cxx @@ -17,6 +17,7 @@ #include "meshDrawer.h" #include "meshDrawer2D.h" #include "geoMipTerrain.h" +#include "htmlVideoTexture.h" #include "movieTexture.h" #include "pandaSystem.h" #include "texturePool.h" @@ -137,4 +138,10 @@ init_libgrutil() { TexturePool *ts = TexturePool::get_global_ptr(); ts->register_texture_type(MovieTexture::make_texture, "avi m2v mov mpg mpeg mp4 wmv asf flv nut ogm mkv ogv webm"); #endif + +#ifdef __EMSCRIPTEN__ + HTMLVideoTexture::init_type(); + + ts->register_texture_type(HTMLVideoTexture::make_texture, "avi m2v mov mpg mpeg mp4 wmv asf flv nut ogm mkv ogv webm"); +#endif } diff --git a/panda/src/grutil/htmlVideoTexture.I b/panda/src/grutil/htmlVideoTexture.I new file mode 100644 index 0000000000..6feecd77ab --- /dev/null +++ b/panda/src/grutil/htmlVideoTexture.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file htmlVideoTexture.I + * @author rdb + * @date 2025-02-24 + */ diff --git a/panda/src/grutil/htmlVideoTexture.cxx b/panda/src/grutil/htmlVideoTexture.cxx new file mode 100644 index 0000000000..c5182f141d --- /dev/null +++ b/panda/src/grutil/htmlVideoTexture.cxx @@ -0,0 +1,530 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file htmlVideoTexture.cxx + * @author rdb + * @date 2025-02-24 + */ + +#include "htmlVideoTexture.h" + +#ifdef __EMSCRIPTEN__ + +#include "config_grutil.h" +#include "virtualFileSystem.h" +#include "virtualFileHTTP.h" + +#include + +#ifndef CPPPARSER +// If the browser supports video frame callback, we use that instead of relying +// on cull callbacks. +extern "C" void EMSCRIPTEN_KEEPALIVE +_video_frame_callback(HTMLVideoTexture *texture, int width, int height, double time) { + texture->video_frame_callback(width, height, time); +} + +static bool get_supports_video_frame_callback() { + static bool supported = EM_ASM_INT({ + return 'requestVideoFrameCallback' in HTMLVideoElement.prototype; + }); + return supported; +} +#endif + +TypeHandle HTMLVideoTexture::_type_handle; + +/** + * Creates a blank movie texture. Movies must be added using do_read_one. + */ +HTMLVideoTexture:: +HTMLVideoTexture(const std::string &name) : + Texture(name) +{ + EM_ASM_INT({ + if (!window._htmlVideoData) { + window._htmlVideoData = {}; + } + var video = document.createElement("video"); + video.style.display = 'none'; + video.defaultMuted = true; + document.body.appendChild(video); + window._htmlVideoData[$0] = {video: video}; + }, this); +} + +/** + * xxx + */ +HTMLVideoTexture:: +~HTMLVideoTexture() { + clear(); + + EM_ASM_INT({ + var data = window._htmlVideoData[$0]; + if (data) { + if (data.video) { + if (data.callback) { + video.cancelVideoFrameCallback(data.callback); + delete data.callback; + } + data.video.remove(); + delete data.video; + } + if (data.objectURL) { + URL.revokeObjectURL(data.objectURL); + delete data.objectURL; + } + delete window._htmlVideoData[$0]; + } + }, this); +} + +/** + * Returns the length of the video. + */ +double HTMLVideoTexture:: +get_video_length() const { + return EM_ASM_DOUBLE({ + return window._htmlVideoData[$0].video.duration; + }, this); +} + +/** + * Returns the width in texels of the source video stream. This is not + * necessarily the width of the actual texture, since the texture may have + * been expanded to raise it to a power of 2. + */ +int HTMLVideoTexture:: +get_video_width() const { + return EM_ASM_DOUBLE({ + return window._htmlVideoData[$0].video.videoWidth; + }, this); +} + +/** + * Returns the height in texels of the source video stream. This is not + * necessarily the height of the actual texture, since the texture may have + * been expanded to raise it to a power of 2. + */ +int HTMLVideoTexture:: +get_video_height() const { + return EM_ASM_DOUBLE({ + return window._htmlVideoData[$0].video.videoHeight; + }, this); +} + +/** + * Start playing the video from where it was last paused. Has no effect if + * the video is not paused, or if the video's cursor is already at the end. + */ +void HTMLVideoTexture:: +restart() { + EM_ASM({ + window._htmlVideoData[$0].video.play(); + }, this); +} + +/** + * Stops a currently playing or looping movie right where it is. the video's + * cursor remains frozen at the point where it was stopped. + */ +void HTMLVideoTexture:: +stop() { + EM_ASM({ + window._htmlVideoData[$0].video.pause(); + }, this); +} + +/** + * Plays the video from the beginning. + */ +void HTMLVideoTexture:: +play() { + EM_ASM({ + var video = window._htmlVideoData[$0].video; + if (video.playing) { + video.pause(); + } + video.currentTime = 0.0; + video.play(); + }, this); +} + +/** + * Sets the video's cursor. + */ +void HTMLVideoTexture:: +set_time(double t) { + EM_ASM({ + window._htmlVideoData[$0].video.currentTime = $1; + }, this, t); +} + +/** + * Returns the current value of the video's cursor. + */ +double HTMLVideoTexture:: +get_time() const { + return EM_ASM_DOUBLE({ + return window._htmlVideoData[$0].video.currentTime; + }, this); +} + +/** + * If true, sets the video's loop count to 1 billion. If false, sets the + * movie's loop count to one. + */ +void HTMLVideoTexture:: +set_loop(bool loop) { + EM_ASM({ + window._htmlVideoData[$0].video.loop = $1; + }, this, loop); +} + +/** + * Returns true if the video's is looping. + */ +bool HTMLVideoTexture:: +get_loop() const { + return EM_ASM_INT({ + return window._htmlVideoData[$0].video.loop; + }, this); +} + +/** + * Sets the video's play-rate. This is the speed at which the video's cursor + * advances. The default is to advance 1.0 movie-seconds per real-time + * second. + */ +void HTMLVideoTexture:: +set_play_rate(double rate) { + EM_ASM({ + window._htmlVideoData[$0].video.playbackRate = $1; + }, this, rate); +} + +/** + * Gets the video's play-rate. + */ +double HTMLVideoTexture:: +get_play_rate() const { + return EM_ASM_DOUBLE({ + return window._htmlVideoData[$0].video.playbackRate; + }, this); +} + +/** + * Returns true if the video's cursor is advancing. + */ +bool HTMLVideoTexture:: +is_playing() const { + return EM_ASM_INT({ + return !window._htmlVideoData[$0].video.paused; + }, this); +} + +/** + * Returns true if the video own audio is muted. True by default. + */ +bool HTMLVideoTexture:: +is_muted() const { + return EM_ASM_INT({ + return window._htmlVideoData[$0].video.muted; + }, this); +} + +/** + * Sets whether the video's own audio is muted. + */ +void HTMLVideoTexture:: +set_muted(bool muted) { + EM_ASM({ + window._htmlVideoData[$0].video.muted = ($1 !== 0); + }, this, muted); +} + +/** + * Returns the audio volume of the video element, in the range 0.0 to 1.0. + */ +double HTMLVideoTexture:: +get_volume() const { + return EM_ASM_DOUBLE({ + return window._htmlVideoData[$0].video.volume; + }, this); +} + +/** + * Sets the audio volume in the range 0.0 to 1.0. Note that you must also + * unmute the audio using the muted property. + */ +void HTMLVideoTexture:: +set_volume(double volume) { + EM_ASM({ + window._htmlVideoData[$0].video.volume = $1; + }, this, volume); +} + +/** + * A factory function to make a new HTMLVideoTexture, used to pass to the + * TexturePool. + */ +PT(Texture) HTMLVideoTexture:: +make_texture() { + return new HTMLVideoTexture(""); +} + +/** + * Should be overridden by derived classes to return true if cull_callback() + * has been defined. Otherwise, returns false to indicate cull_callback() + * does not need to be called for this node during the cull traversal. + */ +bool HTMLVideoTexture:: +has_cull_callback() const { + return !get_supports_video_frame_callback(); +} + +/** + * This function will be called during the cull traversal to update the + * HTMLVideoTexture. This will just check whether the video time changed since + * the last time it was checked. + */ +bool HTMLVideoTexture:: +cull_callback(CullTraverser *, const CullTraverserData &) const { + ((HTMLVideoTexture *)this)->check_update(); + return true; +} + +/** + * + */ +void HTMLVideoTexture:: +video_frame_callback(int width, int height, double time) { + CDWriter cdata(_cycler, true); + if (width != cdata->_x_size || height != cdata->_y_size) { + cdata->_x_size = width; + cdata->_y_size = height; + cdata->inc_properties_modified(); + } + cdata->inc_image_modified(); + _last_time = time; +} + +/** + * + */ +bool HTMLVideoTexture:: +check_update() { + double current_time = get_time(); + if (current_time != _last_time) { + int width = get_video_width(); + int height = get_video_height(); + if (width == 0 && height == 0) { + return false; + } + + video_frame_callback(width, height, current_time); + return true; + } + return false; +} + +/** + * The protected implementation of clear(). Assumes the lock is already held. + */ +void HTMLVideoTexture:: +do_clear(Texture::CData *cdata) { + EM_ASM_INT({ + var data = window._htmlVideoData[$0]; + data.video.pause(); + data.video.src = ""; + if (data.callback) { + data.video.cancelVideoFrameCallback(data.callback); + delete data.callback; + } + if (data.objectURL) { + URL.revokeObjectURL(data.objectURL); + delete data.objectURL; + } + }, this); + + Texture::do_clear(cdata); +} + +/** + * Returns true if we can safely call do_unlock_and_reload_ram_image() in + * order to make the image available, or false if we shouldn't do this + * (because we know from a priori knowledge that it wouldn't work anyway). + */ +bool HTMLVideoTexture:: +do_can_reload(const Texture::CData *cdata) const { + return false; +} + +/** + * Works like adjust_size, but also considers the texture class. Movie + * textures, for instance, always pad outwards, never scale down. + */ +bool HTMLVideoTexture:: +do_adjust_this_size(const Texture::CData *cdata_tex, + int &x_size, int &y_size, const std::string &name, + bool for_padding) const { + // We always scale, for now. May change in the future. + AutoTextureScale ats = do_get_auto_texture_scale(cdata_tex); + if (ats == ATS_pad) { + ats = ATS_up; + } + + return adjust_size(x_size, y_size, name, for_padding, ats); +} + +/** + * Combines a color and alpha video image from the two indicated filenames. + * Both must be the same kind of video with similar properties. + */ +bool HTMLVideoTexture:: +do_read_one(Texture::CData *cdata_tex, + const Filename &fullpath, const Filename &alpha_fullpath, + int z, int n, int primary_file_num_channels, int alpha_file_channel, + const LoaderOptions &options, + bool header_only, BamCacheRecord *record) { + nassertr(n == 0, false); + if (!do_reconsider_z_size(cdata_tex, z, options)) { + return false; + } + nassertr(z >= 0 && z < cdata_tex->_z_size * cdata_tex->_num_views, false); + + if (!alpha_fullpath.empty()) { + grutil_cat.error() + << "HTMLVideoTexture does not support loading separate alpha video.\n"; + return false; + } + + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + PT(VirtualFile) vfile = vfs->get_file(fullpath, true); + if (vfile == nullptr) { + return false; + } + + clear(); + + if (record != nullptr) { + record->add_dependent_file(vfile); + } + + // Pass in the URL directly if it's an HTTP file. + if (vfile->is_of_type(VirtualFileHTTP::get_class_type())) { + URLSpec url = ((VirtualFileHTTP *)vfile.p())->get_url(); + + _last_time = EM_ASM_DOUBLE({ + var data = window._htmlVideoData[$0]; + var video = data.video; + video.src = UTF8ToString($1); + video.loop = true; + video.muted = true; + + video.load(); + return video.currentTime; + }, this, url.c_str()); + } + else { + vector_uchar data; + if (!vfile->read_file(data, true)) { + return false; + } + + _last_time = EM_ASM_DOUBLE({ + var data = window._htmlVideoData[$0]; + + var blob = new Blob([HEAPU8.subarray($1, $1 + $2)]); + data.objectURL = URL.createObjectURL(blob); + + var video = data.video; + video.src = data.objectURL; + video.loop = true; + video.muted = true; + + video.load(); + return video.currentTime; + }, this, (void *)data.data(), data.size()); + } + + // If the browser supports requestVideoframeCallback, we set up a callback to + // know when the next video frame is available instead of having to poll in + // cull_callback(). + if (get_supports_video_frame_callback()) { + EM_ASM({ + var data = window._htmlVideoData[$0]; + + function callback(now, metadata) { + __video_frame_callback($0, metadata.width, metadata.height, metadata.mediaTime); + data.callback = data.video.requestVideoFrameCallback(callback); + } + data.callback = data.video.requestVideoFrameCallback(callback); + }, this); + } + + int width = get_video_width(); + int height = get_video_height(); + if (width == 0 || height == 0) { + // Not yet known. Don't set the size to 0 since that will cause + // CardMaker::set_uv_range() to generate invalid coordinates. + width = 1; + height = 1; + } + + if (!do_reconsider_image_properties(cdata_tex, width, height, 3, T_unsigned_byte, z, options)) { + return false; + } + + if (z == 0) { + if (!has_name()) { + set_name(fullpath.get_basename_wo_extension()); + } + // Don't use has_filename() here, it will cause a deadlock + if (cdata_tex->_filename.empty()) { + cdata_tex->_filename = fullpath; + cdata_tex->_alpha_filename = alpha_fullpath; + } + + cdata_tex->_fullpath = fullpath; + cdata_tex->_alpha_fullpath = alpha_fullpath; + } + + cdata_tex->_primary_file_num_channels = primary_file_num_channels; + cdata_tex->_alpha_file_channel = alpha_file_channel; + + cdata_tex->_loaded_from_image = true; + play(); + check_update(); + return true; +} + +/** + * Loading a static image into an HTMLVideoTexture is an error. + */ +bool HTMLVideoTexture:: +do_load_one(Texture::CData *cdata_tex, + const PNMImage &pnmimage, const std::string &name, int z, int n, + const LoaderOptions &options) { + grutil_cat.error() << "You cannot load a static image into an HTMLVideoTexture\n"; + return false; +} + +/** + * Loading a static image into an HTMLVideoTexture is an error. + */ +bool HTMLVideoTexture:: +do_load_one(Texture::CData *cdata_tex, + const PfmFile &pfm, const std::string &name, int z, int n, + const LoaderOptions &options) { + grutil_cat.error() << "You cannot load a static image into an HTMLVideoTexture\n"; + return false; +} + +#endif // __EMSCRIPTEN__ diff --git a/panda/src/grutil/htmlVideoTexture.h b/panda/src/grutil/htmlVideoTexture.h new file mode 100644 index 0000000000..e8a1f6a023 --- /dev/null +++ b/panda/src/grutil/htmlVideoTexture.h @@ -0,0 +1,128 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file htmlVideoTexture.h + * @author rdb + * @date 2025-02-24 + */ + +#ifndef HTMLVIDEOTEXTURE_H +#define HTMLVIDEOTEXTURE_H + +#include "pandabase.h" + +#ifdef __EMSCRIPTEN__ + +/** + * A texture that renders video frames from an HTMLVideoElement. + * Interface aims to be (nearly) identical to MovieTexture, with some + * limitations: + * - No RAM access of video data is possible + * - No explicit synchronization with audio + * - No loop count + * + * Unlike MovieTexture, this has an additional "muted" attribute which is true + * by default but can be set to false in order to play the video's audio via + * the browser interface. The "volume" property can configure the volume. + */ +class EXPCL_PANDA_GRUTIL HTMLVideoTexture : public Texture { +PUBLISHED: + explicit HTMLVideoTexture(const std::string &name); + HTMLVideoTexture(const HTMLVideoTexture ©) = delete; + virtual ~HTMLVideoTexture(); + + double get_video_length() const; + int get_video_width() const; + int get_video_height() const; + + void restart(); + void stop(); + void play(); + void set_time(double t); + double get_time() const; + void set_loop(bool enable); + bool get_loop() const; + void set_play_rate(double play_rate); + double get_play_rate() const; + bool is_playing() const; + +public: + bool is_muted() const; + void set_muted(bool muted); + double get_volume() const; + void set_volume(double volume); + +PUBLISHED: + MAKE_PROPERTY(video_length, get_video_length); + MAKE_PROPERTY(video_width, get_video_width); + MAKE_PROPERTY(video_height, get_video_height); + + MAKE_PROPERTY(time, get_time, set_time); + MAKE_PROPERTY(loop, get_loop, set_loop); + MAKE_PROPERTY(play_rate, get_play_rate, set_play_rate); + MAKE_PROPERTY(playing, is_playing); + + MAKE_PROPERTY(muted, is_muted, set_muted); + MAKE_PROPERTY(volume, get_volume, set_volume); + +public: + static PT(Texture) make_texture(); + + virtual bool has_cull_callback() const; + virtual bool cull_callback(CullTraverser *trav, const CullTraverserData &data) const; + + void video_frame_callback(int width, int height, double time); + +protected: + bool check_update(); + + virtual void do_clear(Texture::CData *cdata); + virtual bool do_can_reload(const Texture::CData *cdata) const; + + virtual bool do_adjust_this_size(const Texture::CData *cdata, + int &x_size, int &y_size, const std::string &name, + bool for_padding) const; + + virtual bool do_read_one(Texture::CData *cdata, + const Filename &fullpath, const Filename &alpha_fullpath, + int z, int n, int primary_file_num_channels, int alpha_file_channel, + const LoaderOptions &options, + bool header_only, BamCacheRecord *record); + virtual bool do_load_one(Texture::CData *cdata, + const PNMImage &pnmimage, const std::string &name, + int z, int n, const LoaderOptions &options); + virtual bool do_load_one(Texture::CData *cdata, + const PfmFile &pfm, const std::string &name, + int z, int n, const LoaderOptions &options); + +private: + double _last_time = 0.0; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + Texture::init_type(); + register_type(_type_handle, "HTMLVideoTexture", + Texture::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "htmlVideoTexture.I" + +#endif // __EMSCRIPTEN__ + +#endif diff --git a/panda/src/grutil/multitexReducer.cxx b/panda/src/grutil/multitexReducer.cxx index fd94e1d173..f2b3107051 100644 --- a/panda/src/grutil/multitexReducer.cxx +++ b/panda/src/grutil/multitexReducer.cxx @@ -712,9 +712,11 @@ make_texture_layer(const NodePath &render, case TextureStage::M_glow: case TextureStage::M_gloss: case TextureStage::M_height: - case TextureStage::M_selector: + case TextureStage::M_metallic_roughness: case TextureStage::M_normal_gloss: case TextureStage::M_emission: + case TextureStage::M_occlusion: + case TextureStage::M_occlusion_metallic_roughness: // Don't know what to do with these funny modes. We should probably raise // an exception or something. Fall through for now. diff --git a/panda/src/grutil/p3grutil_composite1.cxx b/panda/src/grutil/p3grutil_composite1.cxx index 85d1c61e50..ec5ed74be9 100644 --- a/panda/src/grutil/p3grutil_composite1.cxx +++ b/panda/src/grutil/p3grutil_composite1.cxx @@ -1,5 +1,6 @@ #include "cardMaker.cxx" #include "heightfieldTesselator.cxx" +#include "htmlVideoTexture.cxx" #include "geoMipTerrain.cxx" #include "shaderTerrainMesh.cxx" #include "config_grutil.cxx" diff --git a/panda/src/gsgbase/graphicsStateGuardianBase.h b/panda/src/gsgbase/graphicsStateGuardianBase.h index 80d31f5c3c..c3fac9a003 100644 --- a/panda/src/gsgbase/graphicsStateGuardianBase.h +++ b/panda/src/gsgbase/graphicsStateGuardianBase.h @@ -22,6 +22,8 @@ #include "lightMutex.h" #include "patomic.h" #include "small_vector.h" +#include "completionToken.h" +#include "vector_uchar.h" // A handful of forward references. @@ -31,6 +33,7 @@ class GraphicsWindow; class NodePath; class GraphicsOutputBase; class ScreenshotRequest; +class AsyncFuture; class VertexBufferContext; class IndexBufferContext; @@ -149,6 +152,7 @@ public: virtual TextureContext *prepare_texture(Texture *tex)=0; virtual bool update_texture(TextureContext *tc, bool force)=0; + virtual bool update_texture(TextureContext *tc, bool force, CompletionToken token)=0; virtual void release_texture(TextureContext *tc)=0; virtual void release_textures(const pvector &contexts)=0; virtual bool extract_texture_data(Texture *tex)=0; @@ -173,6 +177,9 @@ public: virtual BufferContext *prepare_shader_buffer(ShaderBuffer *data)=0; virtual void release_shader_buffer(BufferContext *ibc)=0; virtual void release_shader_buffers(const pvector &contexts)=0; + virtual void async_extract_shader_buffer_data(ShaderBuffer *buffer, vector_uchar &data, + size_t start = 0, size_t size = (size_t)-1, + CompletionToken token = CompletionToken())=0; virtual void dispatch_compute(int size_x, int size_y, int size_z)=0; diff --git a/panda/src/linmath/compose_matrix_src.cxx b/panda/src/linmath/compose_matrix_src.cxx index c3bb401c89..e5694b7119 100644 --- a/panda/src/linmath/compose_matrix_src.cxx +++ b/panda/src/linmath/compose_matrix_src.cxx @@ -323,79 +323,82 @@ unwind_yup_rotation(FLOATNAME(LMatrix3) &mat, FLOATNAME(LVecBase3) &hpr) { mat.get_row(z,2); // Project Z into the XZ plane. + FLOATTYPE heading = 0; FLOATNAME(LVector2) xz(z[0], z[2]); - xz = normalize(xz); + if (xz.normalize()) { + // Compute the rotation about the +Y (up) axis. This is yaw, or "heading". + heading = catan2(xz[0], xz[1]); - // Compute the rotation about the +Y (up) axis. This is yaw, or "heading". - FLOATTYPE heading = catan2(xz[0], xz[1]); + // Unwind the heading, and continue. + FLOATNAME(LMatrix3) rot_y; + rot_y._m(0, 0) = xz[1]; + rot_y._m(0, 1) = 0; + rot_y._m(0, 2) = xz[0]; - // Unwind the heading, and continue. - FLOATNAME(LMatrix3) rot_y; - rot_y._m(0, 0) = xz[1]; - rot_y._m(0, 1) = 0; - rot_y._m(0, 2) = xz[0]; + rot_y._m(1, 0) = 0; + rot_y._m(1, 1) = 1; + rot_y._m(1, 2) = 0; - rot_y._m(1, 0) = 0; - rot_y._m(1, 1) = 1; - rot_y._m(1, 2) = 0; + rot_y._m(2, 0) = -xz[0]; + rot_y._m(2, 1) = 0; + rot_y._m(2, 2) = xz[1]; - rot_y._m(2, 0) = -xz[0]; - rot_y._m(2, 1) = 0; - rot_y._m(2, 2) = xz[1]; - - x = x * rot_y; - y = y * rot_y; - z = z * rot_y; + x = x * rot_y; + y = y * rot_y; + z = z * rot_y; + } // Project the rotated Z into the YZ plane. + FLOATTYPE pitch = 0; FLOATNAME(LVector2) yz(z[1], z[2]); - yz = normalize(yz); + if (yz.normalize()) { + // Compute the rotation about the +X (right) axis. This is pitch. + pitch = -catan2(yz[0], yz[1]); - // Compute the rotation about the +X (right) axis. This is pitch. - FLOATTYPE pitch = -catan2(yz[0], yz[1]); + // Unwind the pitch. + FLOATNAME(LMatrix3) rot_x; + rot_x._m(0, 0) = 1; + rot_x._m(0, 1) = 0; + rot_x._m(0, 2) = 0; - // Unwind the pitch. - FLOATNAME(LMatrix3) rot_x; - rot_x._m(0, 0) = 1; - rot_x._m(0, 1) = 0; - rot_x._m(0, 2) = 0; + rot_x._m(1, 0) = 0; + rot_x._m(1, 1) = yz[1]; + rot_x._m(1, 2) = yz[0]; - rot_x._m(1, 0) = 0; - rot_x._m(1, 1) = yz[1]; - rot_x._m(1, 2) = yz[0]; + rot_x._m(2, 0) = 0; + rot_x._m(2, 1) = -yz[0]; + rot_x._m(2, 2) = yz[1]; - rot_x._m(2, 0) = 0; - rot_x._m(2, 1) = -yz[0]; - rot_x._m(2, 2) = yz[1]; - - x = x * rot_x; - y = y * rot_x; - z = z * rot_x; + x = x * rot_x; + y = y * rot_x; + z = z * rot_x; + } // Project the rotated X onto the XY plane. + FLOATTYPE roll = 0; FLOATNAME(LVector2) xy(x[0], x[1]); - xy = normalize(xy); + if (xy.normalize()) { + // Compute the rotation about the +Z (back) axis. This is roll. + roll = -catan2(xy[1], xy[0]); - // Compute the rotation about the +Z (back) axis. This is roll. - FLOATTYPE roll = -catan2(xy[1], xy[0]); + // Unwind the roll from the axes, and continue. + FLOATNAME(LMatrix3) rot_z; + rot_z._m(0, 0) = xy[0]; + rot_z._m(0, 1) = -xy[1]; + rot_z._m(0, 2) = 0; - // Unwind the roll from the axes, and continue. - FLOATNAME(LMatrix3) rot_z; - rot_z._m(0, 0) = xy[0]; - rot_z._m(0, 1) = -xy[1]; - rot_z._m(0, 2) = 0; + rot_z._m(1, 0) = xy[1]; + rot_z._m(1, 1) = xy[0]; + rot_z._m(1, 2) = 0; - rot_z._m(1, 0) = xy[1]; - rot_z._m(1, 1) = xy[0]; - rot_z._m(1, 2) = 0; + rot_z._m(2, 0) = 0; + rot_z._m(2, 1) = 0; + rot_z._m(2, 2) = 1; - rot_z._m(2, 0) = 0; - rot_z._m(2, 1) = 0; - rot_z._m(2, 2) = 1; - - x = x * rot_z; - y = y * rot_z; - z = z * rot_z; + x = x * rot_z; + y = y * rot_z; + z = z * rot_z; + } // Reset the matrix to reflect the unwinding. mat.set_row(0, x); @@ -425,79 +428,82 @@ unwind_zup_rotation(FLOATNAME(LMatrix3) &mat, FLOATNAME(LVecBase3) &hpr) { mat.get_row(z,2); // Project Y into the XY plane. + FLOATTYPE heading = 0; FLOATNAME(LVector2) xy(y[0], y[1]); - xy = normalize(xy); + if (xy.normalize()) { + // Compute the rotation about the +Z (up) axis. This is yaw, or "heading". + heading = -catan2(xy[0], xy[1]); - // Compute the rotation about the +Z (up) axis. This is yaw, or "heading". - FLOATTYPE heading = -catan2(xy[0], xy[1]); + // Unwind the heading, and continue. + FLOATNAME(LMatrix3) rot_z; + rot_z._m(0, 0) = xy[1]; + rot_z._m(0, 1) = xy[0]; + rot_z._m(0, 2) = 0; - // Unwind the heading, and continue. - FLOATNAME(LMatrix3) rot_z; - rot_z._m(0, 0) = xy[1]; - rot_z._m(0, 1) = xy[0]; - rot_z._m(0, 2) = 0; + rot_z._m(1, 0) = -xy[0]; + rot_z._m(1, 1) = xy[1]; + rot_z._m(1, 2) = 0; - rot_z._m(1, 0) = -xy[0]; - rot_z._m(1, 1) = xy[1]; - rot_z._m(1, 2) = 0; + rot_z._m(2, 0) = 0; + rot_z._m(2, 1) = 0; + rot_z._m(2, 2) = 1; - rot_z._m(2, 0) = 0; - rot_z._m(2, 1) = 0; - rot_z._m(2, 2) = 1; - - x = x * rot_z; - y = y * rot_z; - z = z * rot_z; + x = x * rot_z; + y = y * rot_z; + z = z * rot_z; + } // Project the rotated Y into the YZ plane. + FLOATTYPE pitch = 0; FLOATNAME(LVector2) yz(y[1], y[2]); - yz = normalize(yz); + if (yz.normalize()) { + // Compute the rotation about the +X (right) axis. This is pitch. + pitch = catan2(yz[1], yz[0]); - // Compute the rotation about the +X (right) axis. This is pitch. - FLOATTYPE pitch = catan2(yz[1], yz[0]); + // Unwind the pitch. + FLOATNAME(LMatrix3) rot_x; + rot_x._m(0, 0) = 1; + rot_x._m(0, 1) = 0; + rot_x._m(0, 2) = 0; - // Unwind the pitch. - FLOATNAME(LMatrix3) rot_x; - rot_x._m(0, 0) = 1; - rot_x._m(0, 1) = 0; - rot_x._m(0, 2) = 0; + rot_x._m(1, 0) = 0; + rot_x._m(1, 1) = yz[0]; + rot_x._m(1, 2) = -yz[1]; - rot_x._m(1, 0) = 0; - rot_x._m(1, 1) = yz[0]; - rot_x._m(1, 2) = -yz[1]; + rot_x._m(2, 0) = 0; + rot_x._m(2, 1) = yz[1]; + rot_x._m(2, 2) = yz[0]; - rot_x._m(2, 0) = 0; - rot_x._m(2, 1) = yz[1]; - rot_x._m(2, 2) = yz[0]; - - x = x * rot_x; - y = y * rot_x; - z = z * rot_x; + x = x * rot_x; + y = y * rot_x; + z = z * rot_x; + } // Project X into the XZ plane. + FLOATTYPE roll = 0; FLOATNAME(LVector2) xz(x[0], x[2]); - xz = normalize(xz); - + if (xz.normalize()) { // Compute the rotation about the -Y (back) axis. This is roll. - FLOATTYPE roll = -catan2(xz[1], xz[0]); + roll = -catan2(xz[1], xz[0]); - // Unwind the roll from the axes, and continue. - FLOATNAME(LMatrix3) rot_y; - rot_y._m(0, 0) = xz[0]; - rot_y._m(0, 1) = 0; - rot_y._m(0, 2) = -xz[1]; + // Unwind the roll from the axes, and continue. + FLOATNAME(LMatrix3) rot_y; + rot_y._m(0, 0) = xz[0]; + rot_y._m(0, 1) = 0; + rot_y._m(0, 2) = -xz[1]; - rot_y._m(1, 0) = 0; - rot_y._m(1, 1) = 1; - rot_y._m(1, 2) = 0; + rot_y._m(1, 0) = 0; + rot_y._m(1, 1) = 1; + rot_y._m(1, 2) = 0; - rot_y._m(2, 0) = xz[1]; - rot_y._m(2, 1) = 0; - rot_y._m(2, 2) = xz[0]; + rot_y._m(2, 0) = xz[1]; + rot_y._m(2, 1) = 0; + rot_y._m(2, 2) = xz[0]; - x = x * rot_y; - y = y * rot_y; - z = z * rot_y; + x = x * rot_y; + y = y * rot_y; + z = z * rot_y; + } // Reset the matrix to reflect the unwinding. mat.set_row(0, x); diff --git a/panda/src/linmath/lmatrix3_ext_src.I b/panda/src/linmath/lmatrix3_ext_src.I index f5759e3821..638f010d67 100644 --- a/panda/src/linmath/lmatrix3_ext_src.I +++ b/panda/src/linmath/lmatrix3_ext_src.I @@ -47,7 +47,7 @@ __rmul__(FLOATTYPE scalar) const { */ INLINE_LINMATH std::string Extension:: __repr__() const { - char buf[32 * 17] = "LMatrix4"; + char buf[32 * 17] = "LMatrix3"; char *p = buf + strlen(buf); *(p++) = FLOATTOKEN; *(p++) = '('; diff --git a/panda/src/movies/dr_flac.h b/panda/src/movies/dr_flac.h index 3d0b1cdd91..accc15b302 100644 --- a/panda/src/movies/dr_flac.h +++ b/panda/src/movies/dr_flac.h @@ -1,349 +1,1623 @@ -// Public domain. See "unlicense" statement at the end of this file. -//NB: modified by rdb to use 16-bit instead of 32-bit samples. +/* +FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. +dr_flac - v0.13.2 - 2025-12-02 -// ABOUT -// -// This is a simple library for decoding FLAC files. -// -// -// -// USAGE -// -// This is a single-file library. To use it, do something like the following in one .c file. -// #define DR_FLAC_IMPLEMENTATION -// #include "dr_flac.h" -// -// You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, -// do something like the following: -// -// drflac* pFlac = drflac_open_file("MySong.flac"); -// if (pFlac == NULL) { -// ... Failed to open FLAC file ... -// } -// -// int16_t* pSamples = malloc(pFlac->totalSampleCount * sizeof(int16_t)); -// uint64_t numberOfSamplesActuallyRead = drflac_read_s16(pFlac, pFlac->totalSampleCount, pSamples); -// -// ... pSamples now contains the decoded samples as interleaved signed 16-bit PCM ... -// -// The drflac object represents the decoder. It is a transparent type so all the information you need, such as the number of -// channels and the bits per sample, should be directly accessible - just make sure you don't change their values. -// -// You do not need to decode the entire stream in one go - you just specify how many samples you'd like at any given time and -// the decoder will give you as many samples as it can, up to the amount requested. Later on when you need the next batch of -// samples, just call it again. Example: -// -// while (drflac_read_s16(pFlac, chunkSize, pChunkSamples) > 0) { -// do_something(); -// } -// -// You can seek to a specific sample with drflac_seek_to_sample(). The given sample is based on interleaving. So for example, -// if you were to seek to the sample at index 0 in a stereo stream, you'll be seeking to the first sample of the left channel. -// The sample at index 1 will be the first sample of the right channel. The sample at index 2 will be the second sample of the -// left channel, etc. -// -// -// -// OPTIONS -// #define these options before including this file. -// -// #define DR_FLAC_NO_STDIO -// Disable drflac_open_file(). -// -// #define DR_FLAC_NO_WIN32_IO -// Don't use the Win32 API internally for drflac_open_file(). Setting this will force stdio FILE APIs instead. This is -// mainly for testing, but it's left here in case somebody might find use for it. dr_flac will use the Win32 API by -// default. Ignored when DR_FLAC_NO_STDIO is #defined. -// -// #define DR_FLAC_BUFFER_SIZE -// Defines the size of the internal buffer to store data from onRead(). This buffer is used to reduce the number of calls -// back to the client for more data. Larger values means more memory, but better performance. My tests show diminishing -// returns after about 4KB (which is the default). Consider reducing this if you have a very efficient implementation of -// onRead(), or increase it if it's very inefficient. -// -// -// -// QUICK NOTES -// -// - Based on my own tests, the 32-bit build is about about 1.1x-1.25x slower than the reference implementation. The 64-bit -// build is at about parity. -// - This should work fine with valid native FLAC files, but it won't work very well when the STREAMINFO block is unavailable -// and when a stream starts in the middle of a frame. This is something I plan on addressing. -// - Audio data is retrieved as signed 16-bit PCM, regardless of the bits per sample the FLAC stream is encoded as. -// - This has not been tested on big-endian architectures. -// - Rice codes in unencoded binary form (see https://xiph.org/flac/format.html#rice_partition) has not been tested. If anybody -// knows where I can find some test files for this, let me know. -// - Perverse and erroneous files have not been tested. Again, if you know where I can get some test files let me know. -// - dr_flac is not thread-safe, but it's APIs can be called from any thread so long as you do your own synchronization. -// - dr_flac does not currently do any CRC checks. -// - Ogg encapsulation is not supported, but I want to add it at some point. -// -// -// -// TODO -// - Implement a proper test suite. -// - Add support for initializing the decoder without a STREAMINFO block. Build a synthethic test to get support working at at least -// a basic level. -// - Add support for retrieving metadata blocks so applications can retrieve the album art or whatnot. -// - Add support for Ogg encapsulation. +David Reid - mackron@gmail.com + +GitHub: https://github.com/mackron/dr_libs +*/ + +/* +Introduction +============ +dr_flac is a single file library. To use it, do something like the following in one .c file. + + ```c + #define DR_FLAC_IMPLEMENTATION + #include "dr_flac.h" + ``` + +You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, do something like the following: + + ```c + drflac* pFlac = drflac_open_file("MySong.flac", NULL); + if (pFlac == NULL) { + // Failed to open FLAC file + } + + drflac_int32* pSamples = malloc(pFlac->totalPCMFrameCount * pFlac->channels * sizeof(drflac_int32)); + drflac_uint64 numberOfInterleavedSamplesActuallyRead = drflac_read_pcm_frames_s32(pFlac, pFlac->totalPCMFrameCount, pSamples); + ``` + +The drflac object represents the decoder. It is a transparent type so all the information you need, such as the number of channels and the bits per sample, +should be directly accessible - just make sure you don't change their values. Samples are always output as interleaved signed 32-bit PCM. In the example above +a native FLAC stream was opened, however dr_flac has seamless support for Ogg encapsulated FLAC streams as well. + +You do not need to decode the entire stream in one go - you just specify how many samples you'd like at any given time and the decoder will give you as many +samples as it can, up to the amount requested. Later on when you need the next batch of samples, just call it again. Example: + + ```c + while (drflac_read_pcm_frames_s32(pFlac, chunkSizeInPCMFrames, pChunkSamples) > 0) { + do_something(); + } + ``` + +You can seek to a specific PCM frame with `drflac_seek_to_pcm_frame()`. + +If you just want to quickly decode an entire FLAC file in one go you can do something like this: + + ```c + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + drflac_int32* pSampleData = drflac_open_file_and_read_pcm_frames_s32("MySong.flac", &channels, &sampleRate, &totalPCMFrameCount, NULL); + if (pSampleData == NULL) { + // Failed to open and decode FLAC file. + } + + ... + + drflac_free(pSampleData, NULL); + ``` + +You can read samples as signed 16-bit integer and 32-bit floating-point PCM with the *_s16() and *_f32() family of APIs respectively, but note that these +should be considered lossy. + + +If you need access to metadata (album art, etc.), use `drflac_open_with_metadata()`, `drflac_open_file_with_metdata()` or `drflac_open_memory_with_metadata()`. +The rationale for keeping these APIs separate is that they're slightly slower than the normal versions and also just a little bit harder to use. dr_flac +reports metadata to the application through the use of a callback, and every metadata block is reported before `drflac_open_with_metdata()` returns. + +The main opening APIs (`drflac_open()`, etc.) will fail if the header is not present. The presents a problem in certain scenarios such as broadcast style +streams or internet radio where the header may not be present because the user has started playback mid-stream. To handle this, use the relaxed APIs: + + `drflac_open_relaxed()` + `drflac_open_with_metadata_relaxed()` + +It is not recommended to use these APIs for file based streams because a missing header would usually indicate a corrupt or perverse file. In addition, these +APIs can take a long time to initialize because they may need to spend a lot of time finding the first frame. + + + +Build Options +============= +#define these options before including this file. + +#define DR_FLAC_NO_STDIO + Disable `drflac_open_file()` and family. + +#define DR_FLAC_NO_OGG + Disables support for Ogg/FLAC streams. + +#define DR_FLAC_BUFFER_SIZE + Defines the size of the internal buffer to store data from onRead(). This buffer is used to reduce the number of calls back to the client for more data. + Larger values means more memory, but better performance. My tests show diminishing returns after about 4KB (which is the default). Consider reducing this if + you have a very efficient implementation of onRead(), or increase it if it's very inefficient. Must be a multiple of 8. + +#define DR_FLAC_NO_CRC + Disables CRC checks. This will offer a performance boost when CRC is unnecessary. This will disable binary search seeking. When seeking, the seek table will + be used if available. Otherwise the seek will be performed using brute force. + +#define DR_FLAC_NO_SIMD + Disables SIMD optimizations (SSE on x86/x64 architectures, NEON on ARM architectures). Use this if you are having compatibility issues with your compiler. + +#define DR_FLAC_NO_WCHAR + Disables all functions ending with `_w`. Use this if your compiler does not provide wchar.h. Not required if DR_FLAC_NO_STDIO is also defined. + + + +Notes +===== +- dr_flac does not support changing the sample rate nor channel count mid stream. +- dr_flac is not thread-safe, but its APIs can be called from any thread so long as you do your own synchronization. +- When using Ogg encapsulation, a corrupted metadata block will result in `drflac_open_with_metadata()` and `drflac_open()` returning inconsistent samples due + to differences in corrupted stream recorvery logic between the two APIs. +*/ #ifndef dr_flac_h #define dr_flac_h -#include -#include -//#include +#if defined(CPPPARSER) + #define DR_FLAC_NO_SIMD +#endif -// As data is read from the client it is placed into an internal buffer for fast access. This controls the -// size of that buffer. Larger values means more speed, but also more memory. In my testing there is diminishing -// returns after about 4KB, but you can fiddle with this to suit your own needs. Must be a multiple of 8. +#ifdef __cplusplus +extern "C" { +#endif + +#define DRFLAC_STRINGIFY(x) #x +#define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) + +#define DRFLAC_VERSION_MAJOR 0 +#define DRFLAC_VERSION_MINOR 13 +#define DRFLAC_VERSION_REVISION 2 +#define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) + +#include /* For size_t. */ + +/* Sized Types */ +typedef signed char drflac_int8; +typedef unsigned char drflac_uint8; +typedef signed short drflac_int16; +typedef unsigned short drflac_uint16; +typedef signed int drflac_int32; +typedef unsigned int drflac_uint32; +/* Panda3D: Added !defined(CPPPARSER) so that Interrogate can parse these types correctly. */ +#if defined(_MSC_VER) && !defined(__clang__) && !defined(CPPPARSER) + typedef signed __int64 drflac_int64; + typedef unsigned __int64 drflac_uint64; +#else + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + #endif + typedef signed long long drflac_int64; + typedef unsigned long long drflac_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop + #endif +#endif +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) + typedef drflac_uint64 drflac_uintptr; +#else + typedef drflac_uint32 drflac_uintptr; +#endif +typedef drflac_uint8 drflac_bool8; +typedef drflac_uint32 drflac_bool32; +#define DRFLAC_TRUE 1 +#define DRFLAC_FALSE 0 +/* End Sized Types */ + +/* Decorations */ +#if !defined(DRFLAC_API) + #if defined(DRFLAC_DLL) + #if defined(_WIN32) + #define DRFLAC_DLL_IMPORT __declspec(dllimport) + #define DRFLAC_DLL_EXPORT __declspec(dllexport) + #define DRFLAC_DLL_PRIVATE static + #else + #if defined(__GNUC__) && __GNUC__ >= 4 + #define DRFLAC_DLL_IMPORT __attribute__((visibility("default"))) + #define DRFLAC_DLL_EXPORT __attribute__((visibility("default"))) + #define DRFLAC_DLL_PRIVATE __attribute__((visibility("hidden"))) + #else + #define DRFLAC_DLL_IMPORT + #define DRFLAC_DLL_EXPORT + #define DRFLAC_DLL_PRIVATE static + #endif + #endif + + #if defined(DR_FLAC_IMPLEMENTATION) || defined(DRFLAC_IMPLEMENTATION) + #define DRFLAC_API DRFLAC_DLL_EXPORT + #else + #define DRFLAC_API DRFLAC_DLL_IMPORT + #endif + #define DRFLAC_PRIVATE DRFLAC_DLL_PRIVATE + #else + #define DRFLAC_API extern + #define DRFLAC_PRIVATE static + #endif +#endif +/* End Decorations */ + +#if defined(_MSC_VER) && _MSC_VER >= 1700 /* Visual Studio 2012 */ + #define DRFLAC_DEPRECATED __declspec(deprecated) +#elif (defined(__GNUC__) && __GNUC__ >= 4) /* GCC 4 */ + #define DRFLAC_DEPRECATED __attribute__((deprecated)) +#elif defined(__has_feature) /* Clang */ + #if __has_feature(attribute_deprecated) + #define DRFLAC_DEPRECATED __attribute__((deprecated)) + #else + #define DRFLAC_DEPRECATED + #endif +#else + #define DRFLAC_DEPRECATED +#endif + +DRFLAC_API void drflac_version(drflac_uint32* pMajor, drflac_uint32* pMinor, drflac_uint32* pRevision); +DRFLAC_API const char* drflac_version_string(void); + +/* Allocation Callbacks */ +typedef struct +{ + void* pUserData; + void* (* onMalloc)(size_t sz, void* pUserData); + void* (* onRealloc)(void* p, size_t sz, void* pUserData); + void (* onFree)(void* p, void* pUserData); +} drflac_allocation_callbacks; +/* End Allocation Callbacks */ + +/* +As data is read from the client it is placed into an internal buffer for fast access. This controls the size of that buffer. Larger values means more speed, +but also more memory. In my testing there is diminishing returns after about 4KB, but you can fiddle with this to suit your own needs. Must be a multiple of 8. +*/ #ifndef DR_FLAC_BUFFER_SIZE #define DR_FLAC_BUFFER_SIZE 4096 #endif -// Check if we can enable 64-bit optimizations. -#if defined(_WIN64) + +/* Architecture Detection */ +#if defined(_WIN64) || defined(_LP64) || defined(__LP64__) #define DRFLAC_64BIT #endif -#if defined(__GNUC__) -#if defined(__x86_64__) || defined(__ppc64__) -#define DRFLAC_64BIT -#endif +#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)) + #define DRFLAC_X64 +#elif defined(__i386) || defined(_M_IX86) + #define DRFLAC_X86 +#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) + #define DRFLAC_ARM #endif +/* End Architecture Detection */ + #ifdef DRFLAC_64BIT -typedef uint64_t drflac_cache_t; +typedef drflac_uint64 drflac_cache_t; #else -typedef uint32_t drflac_cache_t; +typedef drflac_uint32 drflac_cache_t; #endif +/* The various metadata block types. */ +#define DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO 0 +#define DRFLAC_METADATA_BLOCK_TYPE_PADDING 1 +#define DRFLAC_METADATA_BLOCK_TYPE_APPLICATION 2 +#define DRFLAC_METADATA_BLOCK_TYPE_SEEKTABLE 3 +#define DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT 4 +#define DRFLAC_METADATA_BLOCK_TYPE_CUESHEET 5 +#define DRFLAC_METADATA_BLOCK_TYPE_PICTURE 6 +#define DRFLAC_METADATA_BLOCK_TYPE_INVALID 127 +/* The various picture types specified in the PICTURE block. */ +#define DRFLAC_PICTURE_TYPE_OTHER 0 +#define DRFLAC_PICTURE_TYPE_FILE_ICON 1 +#define DRFLAC_PICTURE_TYPE_OTHER_FILE_ICON 2 +#define DRFLAC_PICTURE_TYPE_COVER_FRONT 3 +#define DRFLAC_PICTURE_TYPE_COVER_BACK 4 +#define DRFLAC_PICTURE_TYPE_LEAFLET_PAGE 5 +#define DRFLAC_PICTURE_TYPE_MEDIA 6 +#define DRFLAC_PICTURE_TYPE_LEAD_ARTIST 7 +#define DRFLAC_PICTURE_TYPE_ARTIST 8 +#define DRFLAC_PICTURE_TYPE_CONDUCTOR 9 +#define DRFLAC_PICTURE_TYPE_BAND 10 +#define DRFLAC_PICTURE_TYPE_COMPOSER 11 +#define DRFLAC_PICTURE_TYPE_LYRICIST 12 +#define DRFLAC_PICTURE_TYPE_RECORDING_LOCATION 13 +#define DRFLAC_PICTURE_TYPE_DURING_RECORDING 14 +#define DRFLAC_PICTURE_TYPE_DURING_PERFORMANCE 15 +#define DRFLAC_PICTURE_TYPE_SCREEN_CAPTURE 16 +#define DRFLAC_PICTURE_TYPE_BRIGHT_COLORED_FISH 17 +#define DRFLAC_PICTURE_TYPE_ILLUSTRATION 18 +#define DRFLAC_PICTURE_TYPE_BAND_LOGOTYPE 19 +#define DRFLAC_PICTURE_TYPE_PUBLISHER_LOGOTYPE 20 -// Callback for when data is read. Return value is the number of bytes actually read. -typedef size_t (* drflac_read_proc)(void* userData, void* bufferOut, size_t bytesToRead); +typedef enum +{ + drflac_container_native, + drflac_container_ogg, + drflac_container_unknown +} drflac_container; -// Callback for when data needs to be seeked. Offset is always relative to the current position. Return value is false on failure, true success. -typedef bool (* drflac_seek_proc)(void* userData, int offset); +typedef enum +{ + DRFLAC_SEEK_SET, + DRFLAC_SEEK_CUR, + DRFLAC_SEEK_END +} drflac_seek_origin; +/* The order of members in this structure is important because we map this directly to the raw data within the SEEKTABLE metadata block. */ +typedef struct +{ + drflac_uint64 firstPCMFrame; + drflac_uint64 flacFrameOffset; /* The offset from the first byte of the header of the first frame. */ + drflac_uint16 pcmFrameCount; +} drflac_seekpoint; typedef struct { - // The absolute position of the first byte of the data of the block. This is just past the block's header. - long long pos; - - // The size in bytes of the block's data. - unsigned int sizeInBytes; - -} drflac_block; + drflac_uint16 minBlockSizeInPCMFrames; + drflac_uint16 maxBlockSizeInPCMFrames; + drflac_uint32 minFrameSizeInPCMFrames; + drflac_uint32 maxFrameSizeInPCMFrames; + drflac_uint32 sampleRate; + drflac_uint8 channels; + drflac_uint8 bitsPerSample; + drflac_uint64 totalPCMFrameCount; + drflac_uint8 md5[16]; +} drflac_streaminfo; typedef struct { - // The type of the subframe: SUBFRAME_CONSTANT, SUBFRAME_VERBATIM, SUBFRAME_FIXED or SUBFRAME_LPC. - unsigned char subframeType; + /* + The metadata type. Use this to know how to interpret the data below. Will be set to one of the + DRFLAC_METADATA_BLOCK_TYPE_* tokens. + */ + drflac_uint32 type; - // The number of wasted bits per sample as specified by the sub-frame header. - unsigned char wastedBitsPerSample; + /* The size in bytes of the block and the buffer pointed to by pRawData if it's non-NULL. */ + drflac_uint32 rawDataSize; - // The order to use for the prediction stage for SUBFRAME_FIXED and SUBFRAME_LPC. - unsigned char lpcOrder; + /* The offset in the stream of the raw data. */ + drflac_uint64 rawDataOffset; - // The number of bits per sample for this subframe. This is not always equal to the current frame's bit per sample because - // an extra bit is required for side channels when interchannel decorrelation is being used. - int bitsPerSample; + /* + A pointer to the raw data. This points to a temporary buffer so don't hold on to it. It's best to + not modify the contents of this buffer. Use the structures below for more meaningful and structured + information about the metadata. It's possible for this to be null. + */ + const void* pRawData; - // A pointer to the buffer containing the decoded samples in the subframe. This pointer is an offset from drflac::pHeap, or - // NULL if the heap is not being used. Note that it's a signed 32-bit integer for each value. - int32_t* pDecodedSamples; + union + { + drflac_streaminfo streaminfo; + struct + { + int unused; + } padding; + + struct + { + drflac_uint32 id; + const void* pData; + drflac_uint32 dataSize; + } application; + + struct + { + drflac_uint32 seekpointCount; + const drflac_seekpoint* pSeekpoints; + } seektable; + + struct + { + drflac_uint32 vendorLength; + const char* vendor; + drflac_uint32 commentCount; + const void* pComments; + } vorbis_comment; + + struct + { + char catalog[128]; + drflac_uint64 leadInSampleCount; + drflac_bool32 isCD; + drflac_uint8 trackCount; + const void* pTrackData; + } cuesheet; + + struct + { + drflac_uint32 type; + drflac_uint32 mimeLength; + const char* mime; + drflac_uint32 descriptionLength; + const char* description; + drflac_uint32 width; + drflac_uint32 height; + drflac_uint32 colorDepth; + drflac_uint32 indexColorCount; + drflac_uint32 pictureDataSize; + drflac_uint64 pictureDataOffset; /* Offset from the start of the stream. */ + const drflac_uint8* pPictureData; + } picture; + } data; +} drflac_metadata; + + +/* +Callback for when data needs to be read from the client. + + +Parameters +---------- +pUserData (in) + The user data that was passed to drflac_open() and family. + +pBufferOut (out) + The output buffer. + +bytesToRead (in) + The number of bytes to read. + + +Return Value +------------ +The number of bytes actually read. + + +Remarks +------- +A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until either the entire bytesToRead is filled or +you have reached the end of the stream. +*/ +typedef size_t (* drflac_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); + +/* +Callback for when data needs to be seeked. + + +Parameters +---------- +pUserData (in) + The user data that was passed to drflac_open() and family. + +offset (in) + The number of bytes to move, relative to the origin. Will never be negative. + +origin (in) + The origin of the seek - the current position, the start of the stream, or the end of the stream. + + +Return Value +------------ +Whether or not the seek was successful. + + +Remarks +------- +Seeking relative to the start and the current position must always be supported. If seeking from the end of the stream is not supported, return DRFLAC_FALSE. + +When seeking to a PCM frame using drflac_seek_to_pcm_frame(), dr_flac may call this with an offset beyond the end of the FLAC stream. This needs to be detected +and handled by returning DRFLAC_FALSE. +*/ +typedef drflac_bool32 (* drflac_seek_proc)(void* pUserData, int offset, drflac_seek_origin origin); + +/* +Callback for when the current position in the stream needs to be retrieved. + + +Parameters +---------- +pUserData (in) + The user data that was passed to drflac_open() and family. + +pCursor (out) + A pointer to a variable to receive the current position in the stream. + + +Return Value +------------ +Whether or not the operation was successful. +*/ +typedef drflac_bool32 (* drflac_tell_proc)(void* pUserData, drflac_int64* pCursor); + +/* +Callback for when a metadata block is read. + + +Parameters +---------- +pUserData (in) + The user data that was passed to drflac_open() and family. + +pMetadata (in) + A pointer to a structure containing the data of the metadata block. + + +Remarks +------- +Use pMetadata->type to determine which metadata block is being handled and how to read the data. This +will be set to one of the DRFLAC_METADATA_BLOCK_TYPE_* tokens. +*/ +typedef void (* drflac_meta_proc)(void* pUserData, drflac_metadata* pMetadata); + + +/* Structure for internal use. Only used for decoders opened with drflac_open_memory. */ +typedef struct +{ + const drflac_uint8* data; + size_t dataSize; + size_t currentReadPos; +} drflac__memory_stream; + +/* Structure for internal use. Used for bit streaming. */ +typedef struct +{ + /* The function to call when more data needs to be read. */ + drflac_read_proc onRead; + + /* The function to call when the current read position needs to be moved. */ + drflac_seek_proc onSeek; + + /* The function to call when the current read position needs to be retrieved. */ + drflac_tell_proc onTell; + + /* The user data to pass around to onRead and onSeek. */ + void* pUserData; + + + /* + The number of unaligned bytes in the L2 cache. This will always be 0 until the end of the stream is hit. At the end of the + stream there will be a number of bytes that don't cleanly fit in an L1 cache line, so we use this variable to know whether + or not the bistreamer needs to run on a slower path to read those last bytes. This will never be more than sizeof(drflac_cache_t). + */ + size_t unalignedByteCount; + + /* The content of the unaligned bytes. */ + drflac_cache_t unalignedCache; + + /* The index of the next valid cache line in the "L2" cache. */ + drflac_uint32 nextL2Line; + + /* The number of bits that have been consumed by the cache. This is used to determine how many valid bits are remaining. */ + drflac_uint32 consumedBits; + + /* + The cached data which was most recently read from the client. There are two levels of cache. Data flows as such: + Client -> L2 -> L1. The L2 -> L1 movement is aligned and runs on a fast path in just a few instructions. + */ + drflac_cache_t cacheL2[DR_FLAC_BUFFER_SIZE/sizeof(drflac_cache_t)]; + drflac_cache_t cache; + + /* + CRC-16. This is updated whenever bits are read from the bit stream. Manually set this to 0 to reset the CRC. For FLAC, this + is reset to 0 at the beginning of each frame. + */ + drflac_uint16 crc16; + drflac_cache_t crc16Cache; /* A cache for optimizing CRC calculations. This is filled when when the L1 cache is reloaded. */ + drflac_uint32 crc16CacheIgnoredBytes; /* The number of bytes to ignore when updating the CRC-16 from the CRC-16 cache. */ +} drflac_bs; + +typedef struct +{ + /* The type of the subframe: SUBFRAME_CONSTANT, SUBFRAME_VERBATIM, SUBFRAME_FIXED or SUBFRAME_LPC. */ + drflac_uint8 subframeType; + + /* The number of wasted bits per sample as specified by the sub-frame header. */ + drflac_uint8 wastedBitsPerSample; + + /* The order to use for the prediction stage for SUBFRAME_FIXED and SUBFRAME_LPC. */ + drflac_uint8 lpcOrder; + + /* A pointer to the buffer containing the decoded samples in the subframe. This pointer is an offset from drflac::pExtraData. */ + drflac_int32* pSamplesS32; } drflac_subframe; typedef struct { - // If the stream uses variable block sizes, this will be set to the index of the first sample. If fixed block sizes are used, this will - // always be set to 0. - unsigned long long sampleNumber; + /* + If the stream uses variable block sizes, this will be set to the index of the first PCM frame. If fixed block sizes are used, this will + always be set to 0. This is 64-bit because the decoded PCM frame number will be 36 bits. + */ + drflac_uint64 pcmFrameNumber; - // If the stream uses fixed block sizes, this will be set to the frame number. If variable block sizes are used, this will always be 0. - unsigned int frameNumber; + /* + If the stream uses fixed block sizes, this will be set to the frame number. If variable block sizes are used, this will always be 0. This + is 32-bit because in fixed block sizes, the maximum frame number will be 31 bits. + */ + drflac_uint32 flacFrameNumber; - // The sample rate of this frame. - unsigned int sampleRate; + /* The sample rate of this frame. */ + drflac_uint32 sampleRate; - // The number of samples in each sub-frame within this frame. - unsigned short blockSize; + /* The number of PCM frames in each sub-frame within this frame. */ + drflac_uint16 blockSizeInPCMFrames; - // The channel assignment of this frame. This is not always set to the channel count. If interchannel decorrelation is being used this - // will be set to DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE, DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE or DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE. - unsigned char channelAssignment; + /* + The channel assignment of this frame. This is not always set to the channel count. If interchannel decorrelation is being used this + will be set to DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE, DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE or DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE. + */ + drflac_uint8 channelAssignment; - // The number of bits per sample within this frame. - unsigned char bitsPerSample; + /* The number of bits per sample within this frame. */ + drflac_uint8 bitsPerSample; - // The frame's CRC. This is set, but unused at the moment. - unsigned char crc8; + /* The frame's CRC. */ + drflac_uint8 crc8; +} drflac_frame_header; - // The number of samples left to be read in this frame. This is initially set to the block size multiplied by the channel count. As samples - // are read, this will be decremented. When it reaches 0, the decoder will see this frame as fully consumed and load the next frame. - unsigned int samplesRemaining; +typedef struct +{ + /* The header. */ + drflac_frame_header header; - // The list of sub-frames within the frame. There is one sub-frame for each channel, and there's a maximum of 8 channels. + /* + The number of PCM frames left to be read in this FLAC frame. This is initially set to the block size. As PCM frames are read, + this will be decremented. When it reaches 0, the decoder will see this frame as fully consumed and load the next frame. + */ + drflac_uint32 pcmFramesRemaining; + + /* The list of sub-frames within the frame. There is one sub-frame for each channel, and there's a maximum of 8 channels. */ drflac_subframe subframes[8]; - } drflac_frame; typedef struct { - // The function to call when more data needs to be read. This is set by drflac_open(). - drflac_read_proc onRead; + /* The function to call when a metadata block is read. */ + drflac_meta_proc onMeta; - // The function to call when the current read position needs to be moved. - drflac_seek_proc onSeek; + /* The user data posted to the metadata callback function. */ + void* pUserDataMD; - // The user data to pass around to onRead and onSeek. - void* pUserData; + /* Memory allocation callbacks. */ + drflac_allocation_callbacks allocationCallbacks; - // The sample rate. Will be set to something like 44100. - unsigned int sampleRate; + /* The sample rate. Will be set to something like 44100. */ + drflac_uint32 sampleRate; - // The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. Maximum 8. This is set based on the - // value specified in the STREAMINFO block. - unsigned char channels; + /* + The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. Maximum 8. This is set based on the + value specified in the STREAMINFO block. + */ + drflac_uint8 channels; - // The bits per sample. Will be set to somthing like 16, 24, etc. - unsigned char bitsPerSample; + /* The bits per sample. Will be set to something like 16, 24, etc. */ + drflac_uint8 bitsPerSample; - // The maximum block size, in samples. This number represents the number of samples in each channel (not combined). - unsigned short maxBlockSize; + /* The maximum block size, in samples. This number represents the number of samples in each channel (not combined). */ + drflac_uint16 maxBlockSizeInPCMFrames; - // The total number of samples making up the stream. This includes every channel. For example, if the stream has 2 channels, - // with each channel having a total of 4096, this value will be set to 2*4096 = 8192. - uint64_t totalSampleCount; + /* + The total number of PCM Frames making up the stream. Can be 0 in which case it's still a valid stream, but just means + the total PCM frame count is unknown. Likely the case with streams like internet radio. + */ + drflac_uint64 totalPCMFrameCount; - // The location and size of the APPLICATION block. - drflac_block applicationBlock; + /* The container type. This is set based on whether or not the decoder was opened from a native or Ogg stream. */ + drflac_container container; - // The location and size of the SEEKTABLE block. - drflac_block seektableBlock; - - // The location and size of the VORBIS_COMMENT block. - drflac_block vorbisCommentBlock; - - // The location and size of the CUESHEET block. - drflac_block cuesheetBlock; - - // The location and size of the PICTURE block. - drflac_block pictureBlock; + /* The number of seekpoints in the seektable. */ + drflac_uint32 seekpointCount; - // Information about the frame the decoder is currently sitting on. - drflac_frame currentFrame; - - // The position of the first frame in the stream. This is only ever used for seeking. - unsigned long long firstFramePos; + /* Information about the frame the decoder is currently sitting on. */ + drflac_frame currentFLACFrame; + /* The index of the PCM frame the decoder is currently sitting on. This is only used for seeking. */ + drflac_uint64 currentPCMFrame; - // The current byte position in the client's data stream. - uint64_t currentBytePos; - - // The index of the next valid cache line in the "L2" cache. - size_t nextL2Line; - - // The number of bits that have been consumed by the cache. This is used to determine how many valid bits are remaining. - size_t consumedBits; - - // Unused L2 lines. This will always be 0 until the end of the stream is hit. Used for correctly calculating the current byte - // position of the read pointer in the stream. - size_t unusedL2Lines; - - // The cached data which was most recently read from the client. When data is read from the client, it is placed within this - // variable. As data is read, it's bit-shifted such that the next valid bit is sitting on the most significant bit. - drflac_cache_t cache; - drflac_cache_t cacheL2[DR_FLAC_BUFFER_SIZE/sizeof(drflac_cache_t)]; + /* The position of the first FLAC frame in the stream. This is only ever used for seeking. */ + drflac_uint64 firstFLACFramePosInBytes; - // A pointer to the decoded sample data. This is an offset of pExtraData. - int32_t* pDecodedSamples; + /* A hack to avoid a malloc() when opening a decoder with drflac_open_memory(). */ + drflac__memory_stream memoryStream; - // Variable length extra data. We attach this to the end of the object so we avoid unnecessary mallocs. - char pExtraData[1]; + /* A pointer to the decoded sample data. This is an offset of pExtraData. */ + drflac_int32* pDecodedSamples; + + /* A pointer to the seek table. This is an offset of pExtraData, or NULL if there is no seek table. */ + drflac_seekpoint* pSeekpoints; + + /* Internal use only. Only used with Ogg containers. Points to a drflac_oggbs object. This is an offset of pExtraData. */ + void* _oggbs; + + /* Internal use only. Used for profiling and testing different seeking modes. */ + drflac_bool32 _noSeekTableSeek : 1; + drflac_bool32 _noBinarySearchSeek : 1; + drflac_bool32 _noBruteForceSeek : 1; + + /* The bit streamer. The raw FLAC data is fed through this object. */ + drflac_bs bs; + + /* Variable length extra data. We attach this to the end of the object so we can avoid unnecessary mallocs. */ + drflac_uint8 pExtraData[1]; } drflac; +/* +Opens a FLAC decoder. -// Opens a FLAC decoder. -// -// This is the lowest level function for opening a FLAC stream. You can also use drflac_open_file() and drflac_open_memory() -// to open the stream from a file or from a block of memory respectively. -// -// At the moment the STREAMINFO block must be present for this to succeed. -// -// The onRead and onSeek callbacks are used to read and seek data provided by the client. -static drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData); +Parameters +---------- +onRead (in) + The function to call when data needs to be read from the client. -// Closes the given FLAC decoder. -static void drflac_close(drflac* pFlac); +onSeek (in) + The function to call when the read position of the client data needs to move. -// Reads sample data from the given FLAC decoder, output as interleaved signed 16-bit PCM. -// -// Returns the number of samples actually read. -static uint64_t drflac_read_s16(drflac* pFlac, uint64_t samplesToRead, int16_t* pBufferOut); +pUserData (in, optional) + A pointer to application defined data that will be passed to onRead and onSeek. -// Seeks to the sample at the given index. -static bool drflac_seek_to_sample(drflac* pFlac, uint64_t sampleIndex); +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +Returns a pointer to an object representing the decoder. + + +Remarks +------- +Close the decoder with `drflac_close()`. + +`pAllocationCallbacks` can be NULL in which case it will use `DRFLAC_MALLOC`, `DRFLAC_REALLOC` and `DRFLAC_FREE`. + +This function will automatically detect whether or not you are attempting to open a native or Ogg encapsulated FLAC, both of which should work seamlessly +without any manual intervention. Ogg encapsulation also works with multiplexed streams which basically means it can play FLAC encoded audio tracks in videos. + +This is the lowest level function for opening a FLAC stream. You can also use `drflac_open_file()` and `drflac_open_memory()` to open the stream from a file or +from a block of memory respectively. + +The STREAMINFO block must be present for this to succeed. Use `drflac_open_relaxed()` to open a FLAC stream where the header may not be present. + +Use `drflac_open_with_metadata()` if you need access to metadata. + + +Seek Also +--------- +drflac_open_file() +drflac_open_memory() +drflac_open_with_metadata() +drflac_close() +*/ +DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +Opens a FLAC stream with relaxed validation of the header block. + + +Parameters +---------- +onRead (in) + The function to call when data needs to be read from the client. + +onSeek (in) + The function to call when the read position of the client data needs to move. + +container (in) + Whether or not the FLAC stream is encapsulated using standard FLAC encapsulation or Ogg encapsulation. + +pUserData (in, optional) + A pointer to application defined data that will be passed to onRead and onSeek. + +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +A pointer to an object representing the decoder. + + +Remarks +------- +The same as drflac_open(), except attempts to open the stream even when a header block is not present. + +Because the header is not necessarily available, the caller must explicitly define the container (Native or Ogg). Do not set this to `drflac_container_unknown` +as that is for internal use only. + +Opening in relaxed mode will continue reading data from onRead until it finds a valid frame. If a frame is never found it will continue forever. To abort, +force your `onRead` callback to return 0, which dr_flac will use as an indicator that the end of the stream was found. + +Use `drflac_open_with_metadata_relaxed()` if you need access to metadata. +*/ +DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +Opens a FLAC decoder and notifies the caller of the metadata chunks (album art, etc.). + + +Parameters +---------- +onRead (in) + The function to call when data needs to be read from the client. + +onSeek (in) + The function to call when the read position of the client data needs to move. + +onMeta (in) + The function to call for every metadata block. + +pUserData (in, optional) + A pointer to application defined data that will be passed to onRead, onSeek and onMeta. + +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +A pointer to an object representing the decoder. + + +Remarks +------- +Close the decoder with `drflac_close()`. + +`pAllocationCallbacks` can be NULL in which case it will use `DRFLAC_MALLOC`, `DRFLAC_REALLOC` and `DRFLAC_FREE`. + +This is slower than `drflac_open()`, so avoid this one if you don't need metadata. Internally, this will allocate and free memory on the heap for every +metadata block except for STREAMINFO and PADDING blocks. + +The caller is notified of the metadata via the `onMeta` callback. All metadata blocks will be handled before the function returns. This callback takes a +pointer to a `drflac_metadata` object which is a union containing the data of all relevant metadata blocks. Use the `type` member to discriminate against +the different metadata types. + +The STREAMINFO block must be present for this to succeed. Use `drflac_open_with_metadata_relaxed()` to open a FLAC stream where the header may not be present. + +Note that this will behave inconsistently with `drflac_open()` if the stream is an Ogg encapsulated stream and a metadata block is corrupted. This is due to +the way the Ogg stream recovers from corrupted pages. When `drflac_open_with_metadata()` is being used, the open routine will try to read the contents of the +metadata block, whereas `drflac_open()` will simply seek past it (for the sake of efficiency). This inconsistency can result in different samples being +returned depending on whether or not the stream is being opened with metadata. + + +Seek Also +--------- +drflac_open_file_with_metadata() +drflac_open_memory_with_metadata() +drflac_open() +drflac_close() +*/ +DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +The same as drflac_open_with_metadata(), except attempts to open the stream even when a header block is not present. + +See Also +-------- +drflac_open_with_metadata() +drflac_open_relaxed() +*/ +DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +Closes the given FLAC decoder. + + +Parameters +---------- +pFlac (in) + The decoder to close. + + +Remarks +------- +This will destroy the decoder object. + + +See Also +-------- +drflac_open() +drflac_open_with_metadata() +drflac_open_file() +drflac_open_file_w() +drflac_open_file_with_metadata() +drflac_open_file_with_metadata_w() +drflac_open_memory() +drflac_open_memory_with_metadata() +*/ +DRFLAC_API void drflac_close(drflac* pFlac); + + +/* +Reads sample data from the given FLAC decoder, output as interleaved signed 32-bit PCM. + + +Parameters +---------- +pFlac (in) + The decoder. + +framesToRead (in) + The number of PCM frames to read. + +pBufferOut (out, optional) + A pointer to the buffer that will receive the decoded samples. + + +Return Value +------------ +Returns the number of PCM frames actually read. If the return value is less than `framesToRead` it has reached the end. + + +Remarks +------- +pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames seeked. +*/ +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s32(drflac* pFlac, drflac_uint64 framesToRead, drflac_int32* pBufferOut); + + +/* +Reads sample data from the given FLAC decoder, output as interleaved signed 16-bit PCM. + + +Parameters +---------- +pFlac (in) + The decoder. + +framesToRead (in) + The number of PCM frames to read. + +pBufferOut (out, optional) + A pointer to the buffer that will receive the decoded samples. + + +Return Value +------------ +Returns the number of PCM frames actually read. If the return value is less than `framesToRead` it has reached the end. + + +Remarks +------- +pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames seeked. + +Note that this is lossy for streams where the bits per sample is larger than 16. +*/ +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s16(drflac* pFlac, drflac_uint64 framesToRead, drflac_int16* pBufferOut); + +/* +Reads sample data from the given FLAC decoder, output as interleaved 32-bit floating point PCM. + + +Parameters +---------- +pFlac (in) + The decoder. + +framesToRead (in) + The number of PCM frames to read. + +pBufferOut (out, optional) + A pointer to the buffer that will receive the decoded samples. + + +Return Value +------------ +Returns the number of PCM frames actually read. If the return value is less than `framesToRead` it has reached the end. + + +Remarks +------- +pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames seeked. + +Note that this should be considered lossy due to the nature of floating point numbers not being able to exactly represent every possible number. +*/ +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut); + +/* +Seeks to the PCM frame at the given index. + + +Parameters +---------- +pFlac (in) + The decoder. + +pcmFrameIndex (in) + The index of the PCM frame to seek to. See notes below. + + +Return Value +------------- +`DRFLAC_TRUE` if successful; `DRFLAC_FALSE` otherwise. +*/ +DRFLAC_API drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex); #ifndef DR_FLAC_NO_STDIO -// Opens a flac decoder from the file at the given path. -static drflac* drflac_open_file(const char* pFile); +/* +Opens a FLAC decoder from the file at the given path. + + +Parameters +---------- +pFileName (in) + The path of the file to open, either absolute or relative to the current directory. + +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +A pointer to an object representing the decoder. + + +Remarks +------- +Close the decoder with drflac_close(). + + +Remarks +------- +This will hold a handle to the file until the decoder is closed with drflac_close(). Some platforms will restrict the number of files a process can have open +at any given time, so keep this mind if you have many decoders open at the same time. + + +See Also +-------- +drflac_open_file_with_metadata() +drflac_open() +drflac_close() +*/ +DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +Opens a FLAC decoder from the file at the given path and notifies the caller of the metadata chunks (album art, etc.) + + +Parameters +---------- +pFileName (in) + The path of the file to open, either absolute or relative to the current directory. + +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + +onMeta (in) + The callback to fire for each metadata block. + +pUserData (in) + A pointer to the user data to pass to the metadata callback. + +pAllocationCallbacks (in) + A pointer to application defined callbacks for managing memory allocations. + + +Remarks +------- +Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. + + +See Also +-------- +drflac_open_with_metadata() +drflac_open() +drflac_close() +*/ +DRFLAC_API drflac* drflac_open_file_with_metadata(const char* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); #endif -// Helper for opening a file from a pre-allocated memory buffer. -// -// This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for -// the lifetime of the decoder. -static drflac* drflac_open_memory(const void* data, size_t dataSize); - -#endif //dr_flac_h +/* +Opens a FLAC decoder from a pre-allocated block of memory -/////////////////////////////////////////////////////////////////////////////// -// -// IMPLEMENTATION -// -/////////////////////////////////////////////////////////////////////////////// -#ifdef DR_FLAC_IMPLEMENTATION -#include -#include -#include +Parameters +---------- +pData (in) + A pointer to the raw encoded FLAC data. -#ifdef _MSC_VER -#include // For _byteswap_ulong and _byteswap_uint64 +dataSize (in) + The size in bytes of `data`. + +pAllocationCallbacks (in) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +A pointer to an object representing the decoder. + + +Remarks +------- +This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for the lifetime of the decoder. + + +See Also +-------- +drflac_open() +drflac_close() +*/ +DRFLAC_API drflac* drflac_open_memory(const void* pData, size_t dataSize, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +Opens a FLAC decoder from a pre-allocated block of memory and notifies the caller of the metadata chunks (album art, etc.) + + +Parameters +---------- +pData (in) + A pointer to the raw encoded FLAC data. + +dataSize (in) + The size in bytes of `data`. + +onMeta (in) + The callback to fire for each metadata block. + +pUserData (in) + A pointer to the user data to pass to the metadata callback. + +pAllocationCallbacks (in) + A pointer to application defined callbacks for managing memory allocations. + + +Remarks +------- +Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. + + +See Also +------- +drflac_open_with_metadata() +drflac_open() +drflac_close() +*/ +DRFLAC_API drflac* drflac_open_memory_with_metadata(const void* pData, size_t dataSize, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); + + + +/* High Level APIs */ + +/* +Opens a FLAC stream from the given callbacks and fully decodes it in a single operation. The return value is a +pointer to the sample data as interleaved signed 32-bit PCM. The returned data must be freed with drflac_free(). + +You can pass in custom memory allocation callbacks via the pAllocationCallbacks parameter. This can be NULL in which +case it will use DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE. + +Sometimes a FLAC file won't keep track of the total sample count. In this situation the function will continuously +read samples into a dynamically sized buffer on the heap until no samples are left. + +Do not call this function on a broadcast type of stream (like internet radio streams and whatnot). +*/ +DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* Same as drflac_open_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ +DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* Same as drflac_open_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ +DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +#ifndef DR_FLAC_NO_STDIO +/* Same as drflac_open_and_read_pcm_frames_s32() except opens the decoder from a file. */ +DRFLAC_API drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* Same as drflac_open_file_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ +DRFLAC_API drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* Same as drflac_open_file_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ +DRFLAC_API float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); +#endif + +/* Same as drflac_open_and_read_pcm_frames_s32() except opens the decoder from a block of memory. */ +DRFLAC_API drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* Same as drflac_open_memory_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ +DRFLAC_API drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* Same as drflac_open_memory_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ +DRFLAC_API float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +Frees memory that was allocated internally by dr_flac. + +Set pAllocationCallbacks to the same object that was passed to drflac_open_*_and_read_pcm_frames_*(). If you originally passed in NULL, pass in NULL for this. +*/ +DRFLAC_API void drflac_free(void* p, const drflac_allocation_callbacks* pAllocationCallbacks); + + +/* Structure representing an iterator for vorbis comments in a VORBIS_COMMENT metadata block. */ +typedef struct +{ + drflac_uint32 countRemaining; + const char* pRunningData; +} drflac_vorbis_comment_iterator; + +/* +Initializes a vorbis comment iterator. This can be used for iterating over the vorbis comments in a VORBIS_COMMENT +metadata block. +*/ +DRFLAC_API void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments); + +/* +Goes to the next vorbis comment in the given iterator. If null is returned it means there are no more comments. The +returned string is NOT null terminated. +*/ +DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut); + + +/* Structure representing an iterator for cuesheet tracks in a CUESHEET metadata block. */ +typedef struct +{ + drflac_uint32 countRemaining; + const char* pRunningData; +} drflac_cuesheet_track_iterator; + +/* The order of members here is important because we map this directly to the raw data within the CUESHEET metadata block. */ +typedef struct +{ + drflac_uint64 offset; + drflac_uint8 index; + drflac_uint8 reserved[3]; +} drflac_cuesheet_track_index; + +typedef struct +{ + drflac_uint64 offset; + drflac_uint8 trackNumber; + char ISRC[12]; + drflac_bool8 isAudio; + drflac_bool8 preEmphasis; + drflac_uint8 indexCount; + const drflac_cuesheet_track_index* pIndexPoints; +} drflac_cuesheet_track; + +/* +Initializes a cuesheet track iterator. This can be used for iterating over the cuesheet tracks in a CUESHEET metadata +block. +*/ +DRFLAC_API void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData); + +/* Goes to the next cuesheet track in the given iterator. If DRFLAC_FALSE is returned it means there are no more comments. */ +DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack); + + +#ifdef __cplusplus +} +#endif +#endif /* dr_flac_h */ + + +/************************************************************************************************************************************************************ + ************************************************************************************************************************************************************ + + IMPLEMENTATION + + ************************************************************************************************************************************************************ + ************************************************************************************************************************************************************/ +#if defined(DR_FLAC_IMPLEMENTATION) || defined(DRFLAC_IMPLEMENTATION) +#ifndef dr_flac_c +#define dr_flac_c + +/* Disable some annoying warnings. */ +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #if __GNUC__ >= 7 + #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" + #endif #endif #ifdef __linux__ -#ifndef _BSD_SOURCE -#define _BSD_SOURCE -#endif -#include + #ifndef _BSD_SOURCE + #define _BSD_SOURCE + #endif + #ifndef _DEFAULT_SOURCE + #define _DEFAULT_SOURCE + #endif + #ifndef __USE_BSD + #define __USE_BSD + #endif + #include #endif -#define DRFLAC_INLINE ALWAYS_INLINE +#include +#include + +/* Inline */ +#ifdef _MSC_VER + #if defined(CPPPARSER) + #define DRFLAC_INLINE inline + #else + #define DRFLAC_INLINE __forceinline + #endif +#elif defined(__GNUC__) + /* + I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when + the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some + case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the + command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue + I am using "__inline__" only when we're compiling in strict ANSI mode. + */ + #if defined(__STRICT_ANSI__) + #define DRFLAC_GNUC_INLINE_HINT __inline__ + #else + #define DRFLAC_GNUC_INLINE_HINT inline + #endif + + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT + #endif +#elif defined(__WATCOMC__) + #define DRFLAC_INLINE __inline +#else + #define DRFLAC_INLINE +#endif +/* End Inline */ + +/* +Intrinsics Support + +There's a bug in GCC 4.2.x which results in an incorrect compilation error when using _mm_slli_epi32() where it complains with + + "error: shift must be an immediate" + +Unfortuantely dr_flac depends on this for a few things so we're just going to disable SSE on GCC 4.2 and below. +*/ +#if !defined(DR_FLAC_NO_SIMD) + #if defined(DRFLAC_X64) || defined(DRFLAC_X86) + #if defined(_MSC_VER) && !defined(__clang__) + /* MSVC. */ + #if _MSC_VER >= 1400 && !defined(DRFLAC_NO_SSE2) /* 2005 */ + #define DRFLAC_SUPPORT_SSE2 + #endif + #if _MSC_VER >= 1600 && !defined(DRFLAC_NO_SSE41) /* 2010 */ + #define DRFLAC_SUPPORT_SSE41 + #endif + #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))) + /* Assume GNUC-style. */ + #if defined(__SSE2__) && !defined(DRFLAC_NO_SSE2) + #define DRFLAC_SUPPORT_SSE2 + #endif + #if defined(__SSE4_1__) && !defined(DRFLAC_NO_SSE41) + #define DRFLAC_SUPPORT_SSE41 + #endif + #endif + + /* If at this point we still haven't determined compiler support for the intrinsics just fall back to __has_include. */ + #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) + #if !defined(DRFLAC_SUPPORT_SSE2) && !defined(DRFLAC_NO_SSE2) && __has_include() + #define DRFLAC_SUPPORT_SSE2 + #endif + #if !defined(DRFLAC_SUPPORT_SSE41) && !defined(DRFLAC_NO_SSE41) && __has_include() + #define DRFLAC_SUPPORT_SSE41 + #endif + #endif + + #if defined(DRFLAC_SUPPORT_SSE41) + #include + #elif defined(DRFLAC_SUPPORT_SSE2) + #include + #endif + #endif + + #if defined(DRFLAC_ARM) + #if !defined(DRFLAC_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + #define DRFLAC_SUPPORT_NEON + #include + #endif + #endif +#endif + +/* Compile-time CPU feature support. */ +#if !defined(DR_FLAC_NO_SIMD) && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) + #if defined(_MSC_VER) && !defined(__clang__) + #if _MSC_VER >= 1400 + #include + static void drflac__cpuid(int info[4], int fid) + { + __cpuid(info, fid); + } + #else + #define DRFLAC_NO_CPUID + #endif + #else + #if defined(__GNUC__) || defined(__clang__) + static void drflac__cpuid(int info[4], int fid) + { + /* + It looks like the -fPIC option uses the ebx register which GCC complains about. We can work around this by just using a different register, the + specific register of which I'm letting the compiler decide on. The "k" prefix is used to specify a 32-bit register. The {...} syntax is for + supporting different assembly dialects. + + What's basically happening is that we're saving and restoring the ebx register manually. + */ + #if defined(DRFLAC_X86) && defined(__PIC__) + __asm__ __volatile__ ( + "xchg{l} {%%}ebx, %k1;" + "cpuid;" + "xchg{l} {%%}ebx, %k1;" + : "=a"(info[0]), "=&r"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #else + __asm__ __volatile__ ( + "cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #endif + } + #else + #define DRFLAC_NO_CPUID + #endif + #endif +#else + #define DRFLAC_NO_CPUID +#endif + +static DRFLAC_INLINE drflac_bool32 drflac_has_sse2(void) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE2) + #if defined(DRFLAC_X64) + return DRFLAC_TRUE; /* 64-bit targets always support SSE2. */ + #elif (defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__) + return DRFLAC_TRUE; /* If the compiler is allowed to freely generate SSE2 code we can assume support. */ + #else + #if defined(DRFLAC_NO_CPUID) + return DRFLAC_FALSE; + #else + int info[4]; + drflac__cpuid(info, 1); + return (info[3] & (1 << 26)) != 0; + #endif + #endif + #else + return DRFLAC_FALSE; /* SSE2 is only supported on x86 and x64 architectures. */ + #endif +#else + return DRFLAC_FALSE; /* No compiler support. */ +#endif +} + +static DRFLAC_INLINE drflac_bool32 drflac_has_sse41(void) +{ +#if defined(DRFLAC_SUPPORT_SSE41) + #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE41) + #if defined(__SSE4_1__) || defined(__AVX__) + return DRFLAC_TRUE; /* If the compiler is allowed to freely generate SSE41 code we can assume support. */ + #else + #if defined(DRFLAC_NO_CPUID) + return DRFLAC_FALSE; + #else + int info[4]; + drflac__cpuid(info, 1); + return (info[2] & (1 << 19)) != 0; + #endif + #endif + #else + return DRFLAC_FALSE; /* SSE41 is only supported on x86 and x64 architectures. */ + #endif +#else + return DRFLAC_FALSE; /* No compiler support. */ +#endif +} + + +#if defined(_MSC_VER) && _MSC_VER >= 1500 && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) && !defined(__clang__) + #define DRFLAC_HAS_LZCNT_INTRINSIC +#elif (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) + #define DRFLAC_HAS_LZCNT_INTRINSIC +#elif defined(__clang__) + #if defined(__has_builtin) + #if __has_builtin(__builtin_clzll) || __has_builtin(__builtin_clzl) + #define DRFLAC_HAS_LZCNT_INTRINSIC + #endif + #endif +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1400 && !defined(__clang__) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC +#elif defined(__clang__) + #if defined(__has_builtin) + #if __has_builtin(__builtin_bswap16) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap32) + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap64) + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + #endif + #endif +#elif defined(__GNUC__) + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + #endif + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #endif +#elif defined(__WATCOMC__) && defined(__386__) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + extern __inline drflac_uint16 _watcom_bswap16(drflac_uint16); + extern __inline drflac_uint32 _watcom_bswap32(drflac_uint32); + extern __inline drflac_uint64 _watcom_bswap64(drflac_uint64); +#pragma aux _watcom_bswap16 = \ + "xchg al, ah" \ + parm [ax] \ + value [ax] \ + modify nomemory; +#pragma aux _watcom_bswap32 = \ + "bswap eax" \ + parm [eax] \ + value [eax] \ + modify nomemory; +#pragma aux _watcom_bswap64 = \ + "bswap eax" \ + "bswap edx" \ + "xchg eax,edx" \ + parm [eax edx] \ + value [eax edx] \ + modify nomemory; +#endif + + +/* Standard library stuff. */ +#ifndef DRFLAC_ASSERT +#include +#define DRFLAC_ASSERT(expression) assert(expression) +#endif +#ifndef DRFLAC_MALLOC +#define DRFLAC_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRFLAC_REALLOC +#define DRFLAC_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRFLAC_FREE +#define DRFLAC_FREE(p) free((p)) +#endif +#ifndef DRFLAC_COPY_MEMORY +#define DRFLAC_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRFLAC_ZERO_MEMORY +#define DRFLAC_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif +#ifndef DRFLAC_ZERO_OBJECT +#define DRFLAC_ZERO_OBJECT(p) DRFLAC_ZERO_MEMORY((p), sizeof(*(p))) +#endif + +#define DRFLAC_MAX_SIMD_VECTOR_SIZE 64 /* 64 for AVX-512 in the future. */ + +/* Result Codes */ +typedef drflac_int32 drflac_result; +#define DRFLAC_SUCCESS 0 +#define DRFLAC_ERROR -1 /* A generic error. */ +#define DRFLAC_INVALID_ARGS -2 +#define DRFLAC_INVALID_OPERATION -3 +#define DRFLAC_OUT_OF_MEMORY -4 +#define DRFLAC_OUT_OF_RANGE -5 +#define DRFLAC_ACCESS_DENIED -6 +#define DRFLAC_DOES_NOT_EXIST -7 +#define DRFLAC_ALREADY_EXISTS -8 +#define DRFLAC_TOO_MANY_OPEN_FILES -9 +#define DRFLAC_INVALID_FILE -10 +#define DRFLAC_TOO_BIG -11 +#define DRFLAC_PATH_TOO_LONG -12 +#define DRFLAC_NAME_TOO_LONG -13 +#define DRFLAC_NOT_DIRECTORY -14 +#define DRFLAC_IS_DIRECTORY -15 +#define DRFLAC_DIRECTORY_NOT_EMPTY -16 +#define DRFLAC_END_OF_FILE -17 +#define DRFLAC_NO_SPACE -18 +#define DRFLAC_BUSY -19 +#define DRFLAC_IO_ERROR -20 +#define DRFLAC_INTERRUPT -21 +#define DRFLAC_UNAVAILABLE -22 +#define DRFLAC_ALREADY_IN_USE -23 +#define DRFLAC_BAD_ADDRESS -24 +#define DRFLAC_BAD_SEEK -25 +#define DRFLAC_BAD_PIPE -26 +#define DRFLAC_DEADLOCK -27 +#define DRFLAC_TOO_MANY_LINKS -28 +#define DRFLAC_NOT_IMPLEMENTED -29 +#define DRFLAC_NO_MESSAGE -30 +#define DRFLAC_BAD_MESSAGE -31 +#define DRFLAC_NO_DATA_AVAILABLE -32 +#define DRFLAC_INVALID_DATA -33 +#define DRFLAC_TIMEOUT -34 +#define DRFLAC_NO_NETWORK -35 +#define DRFLAC_NOT_UNIQUE -36 +#define DRFLAC_NOT_SOCKET -37 +#define DRFLAC_NO_ADDRESS -38 +#define DRFLAC_BAD_PROTOCOL -39 +#define DRFLAC_PROTOCOL_UNAVAILABLE -40 +#define DRFLAC_PROTOCOL_NOT_SUPPORTED -41 +#define DRFLAC_PROTOCOL_FAMILY_NOT_SUPPORTED -42 +#define DRFLAC_ADDRESS_FAMILY_NOT_SUPPORTED -43 +#define DRFLAC_SOCKET_NOT_SUPPORTED -44 +#define DRFLAC_CONNECTION_RESET -45 +#define DRFLAC_ALREADY_CONNECTED -46 +#define DRFLAC_NOT_CONNECTED -47 +#define DRFLAC_CONNECTION_REFUSED -48 +#define DRFLAC_NO_HOST -49 +#define DRFLAC_IN_PROGRESS -50 +#define DRFLAC_CANCELLED -51 +#define DRFLAC_MEMORY_ALREADY_MAPPED -52 +#define DRFLAC_AT_END -53 + +#define DRFLAC_CRC_MISMATCH -100 +/* End Result Codes */ -#define DRFLAC_BLOCK_TYPE_STREAMINFO 0 -#define DRFLAC_BLOCK_TYPE_PADDING 1 -#define DRFLAC_BLOCK_TYPE_APPLICATION 2 -#define DRFLAC_BLOCK_TYPE_SEEKTABLE 3 -#define DRFLAC_BLOCK_TYPE_VORBIS_COMMENT 4 -#define DRFLAC_BLOCK_TYPE_CUESHEET 5 -#define DRFLAC_BLOCK_TYPE_PICTURE 6 -#define DRFLAC_BLOCK_TYPE_INVALID 127 #define DRFLAC_SUBFRAME_CONSTANT 0 #define DRFLAC_SUBFRAME_VERBATIM 1 @@ -359,154 +1633,169 @@ static drflac* drflac_open_memory(const void* data, size_t dataSize); #define DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE 9 #define DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE 10 -typedef struct -{ - uint64_t firstSample; - uint64_t frameOffset; // The offset from the first byte of the header of the first frame. - uint16_t sampleCount; -} drflac_seekpoint; +#define DRFLAC_SEEKPOINT_SIZE_IN_BYTES 18 +#define DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES 36 +#define DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES 12 -#ifndef DR_FLAC_NO_STDIO -#if defined(DR_FLAC_NO_WIN32_IO) || !defined(_WIN32) -#include +#define drflac_align(x, a) ((((x) + (a) - 1) / (a)) * (a)) -static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead) -{ - return fread(bufferOut, 1, bytesToRead, (FILE*)pUserData); -} -static bool drflac__on_seek_stdio(void* pUserData, int offset) +DRFLAC_API void drflac_version(drflac_uint32* pMajor, drflac_uint32* pMinor, drflac_uint32* pRevision) { - return fseek((FILE*)pUserData, offset, SEEK_CUR) == 0; -} - -drflac* drflac_open_file(const char* filename) -{ - FILE* pFile; -#ifdef _MSC_VER - if (fopen_s(&pFile, filename, "rb") != 0) { - return NULL; + if (pMajor) { + *pMajor = DRFLAC_VERSION_MAJOR; } + + if (pMinor) { + *pMinor = DRFLAC_VERSION_MINOR; + } + + if (pRevision) { + *pRevision = DRFLAC_VERSION_REVISION; + } +} + +DRFLAC_API const char* drflac_version_string(void) +{ + return DRFLAC_VERSION_STRING; +} + + +/* CPU caps. */ +#if defined(__has_feature) + #if __has_feature(thread_sanitizer) + #define DRFLAC_NO_THREAD_SANITIZE __attribute__((no_sanitize("thread"))) + #else + #define DRFLAC_NO_THREAD_SANITIZE + #endif #else - pFile = fopen(filename, "rb"); - if (pFile == NULL) { - return NULL; - } + #define DRFLAC_NO_THREAD_SANITIZE #endif - return drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, pFile); +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) +static drflac_bool32 drflac__gIsLZCNTSupported = DRFLAC_FALSE; +#endif + +#ifndef DRFLAC_NO_CPUID +static drflac_bool32 drflac__gIsSSE2Supported = DRFLAC_FALSE; +static drflac_bool32 drflac__gIsSSE41Supported = DRFLAC_FALSE; + +/* +I've had a bug report that Clang's ThreadSanitizer presents a warning in this function. Having reviewed this, this does +actually make sense. However, since CPU caps should never differ for a running process, I don't think the trade off of +complicating internal API's by passing around CPU caps versus just disabling the warnings is worthwhile. I'm therefore +just going to disable these warnings. This is disabled via the DRFLAC_NO_THREAD_SANITIZE attribute. +*/ +DRFLAC_NO_THREAD_SANITIZE static void drflac__init_cpu_caps(void) +{ + static drflac_bool32 isCPUCapsInitialized = DRFLAC_FALSE; + + if (!isCPUCapsInitialized) { + /* LZCNT */ +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) + int info[4] = {0}; + drflac__cpuid(info, 0x80000001); + drflac__gIsLZCNTSupported = (info[2] & (1 << 5)) != 0; +#endif + + /* SSE2 */ + drflac__gIsSSE2Supported = drflac_has_sse2(); + + /* SSE4.1 */ + drflac__gIsSSE41Supported = drflac_has_sse41(); + + /* Initialized. */ + isCPUCapsInitialized = DRFLAC_TRUE; + } } #else -#include +static drflac_bool32 drflac__gIsNEONSupported = DRFLAC_FALSE; -static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead) +static DRFLAC_INLINE drflac_bool32 drflac__has_neon(void) { - assert(bytesToRead < 0xFFFFFFFF); // dr_flac will never request huge amounts of data at a time. This is a safe assertion. - - DWORD bytesRead; - ReadFile((HANDLE)pUserData, bufferOut, (DWORD)bytesToRead, &bytesRead, NULL); - - return (size_t)bytesRead; +#if defined(DRFLAC_SUPPORT_NEON) + #if defined(DRFLAC_ARM) && !defined(DRFLAC_NO_NEON) + #if (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + return DRFLAC_TRUE; /* If the compiler is allowed to freely generate NEON code we can assume support. */ + #else + /* TODO: Runtime check. */ + return DRFLAC_FALSE; + #endif + #else + return DRFLAC_FALSE; /* NEON is only supported on ARM architectures. */ + #endif +#else + return DRFLAC_FALSE; /* No compiler support. */ +#endif } -static bool drflac__on_seek_stdio(void* pUserData, int offset) +DRFLAC_NO_THREAD_SANITIZE static void drflac__init_cpu_caps(void) { - return SetFilePointer((HANDLE)pUserData, offset, NULL, FILE_CURRENT) != INVALID_SET_FILE_POINTER; -} + drflac__gIsNEONSupported = drflac__has_neon(); -static drflac* drflac_open_file(const char* filename) -{ - HANDLE hFile = CreateFileA(filename, FILE_GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile == INVALID_HANDLE_VALUE) { - return NULL; - } - - return drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)hFile); +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) && defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) + drflac__gIsLZCNTSupported = DRFLAC_TRUE; +#endif } #endif -#endif //DR_FLAC_NO_STDIO -typedef struct -{ - /// A pointer to the beginning of the data. We use a char as the type here for easy offsetting. - const unsigned char* data; - - /// The size of the data. - size_t dataSize; - - /// The position we're currently sitting at. - size_t currentReadPos; - -} drflac_memory; - -static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t bytesToRead) -{ - drflac_memory* memory = (drflac_memory*)pUserData; - assert(memory != NULL); - assert(memory->dataSize >= memory->currentReadPos); - - size_t bytesRemaining = memory->dataSize - memory->currentReadPos; - if (bytesToRead > bytesRemaining) { - bytesToRead = bytesRemaining; - } - - if (bytesToRead > 0) { - memcpy(bufferOut, memory->data + memory->currentReadPos, bytesToRead); - memory->currentReadPos += bytesToRead; - } - - return bytesToRead; -} - -static bool drflac__on_seek_memory(void* pUserData, int offset) -{ - drflac_memory* memory = (drflac_memory*)pUserData; - assert(memory != NULL); - - if (offset > 0) { - if (memory->currentReadPos + offset > memory->dataSize) { - offset = (int)(memory->dataSize - memory->currentReadPos); // Trying to seek too far forward. - } - } else { - if (memory->currentReadPos < (size_t)-offset) { - offset = -(int)memory->currentReadPos; // Trying to seek too far backwards. - } - } - - // This will never underflow thanks to the clamps above. - memory->currentReadPos += offset; - - return 1; -} - -static drflac* drflac_open_memory(const void* data, size_t dataSize) -{ - drflac_memory* pUserData = (drflac_memory*)malloc(sizeof(*pUserData)); - if (pUserData == NULL) { - return NULL; - } - - pUserData->data = (const unsigned char*)data; - pUserData->dataSize = dataSize; - pUserData->currentReadPos = 0; - return drflac_open(drflac__on_read_memory, drflac__on_seek_memory, pUserData); -} - - -//// Endian Management //// -static DRFLAC_INLINE bool drflac__is_little_endian() +/* Endian Management */ +static DRFLAC_INLINE drflac_bool32 drflac__is_little_endian(void) { +#if defined(DRFLAC_X86) || defined(DRFLAC_X64) + return DRFLAC_TRUE; +#elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN + return DRFLAC_TRUE; +#else int n = 1; return (*(char*)&n) == 1; +#endif } -static DRFLAC_INLINE uint32_t drflac__swap_endian_uint32(uint32_t n) +static DRFLAC_INLINE drflac_uint16 drflac__swap_endian_uint16(drflac_uint16 n) { -#ifdef _MSC_VER - return _byteswap_ulong(n); -#elif defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) - return __builtin_bswap32(n); +#ifdef DRFLAC_HAS_BYTESWAP16_INTRINSIC + #if defined(_MSC_VER) && !defined(__clang__) + return _byteswap_ushort(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap16(n); + #elif defined(__WATCOMC__) && defined(__386__) + return _watcom_bswap16(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF00) >> 8) | + ((n & 0x00FF) << 8); +#endif +} + +static DRFLAC_INLINE drflac_uint32 drflac__swap_endian_uint32(drflac_uint32 n) +{ +#ifdef DRFLAC_HAS_BYTESWAP32_INTRINSIC + #if defined(_MSC_VER) && !defined(__clang__) + return _byteswap_ulong(n); + #elif defined(__GNUC__) || defined(__clang__) + #if defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(__ARM_ARCH_6M__) && !defined(DRFLAC_64BIT) /* <-- 64-bit inline assembly has not been tested, so disabling for now. */ + /* Inline assembly optimized implementation for ARM. In my testing, GCC does not generate optimized code with __builtin_bswap32(). */ + drflac_uint32 r; + __asm__ __volatile__ ( + #if defined(DRFLAC_64BIT) + "rev %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(n) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ + #else + "rev %[out], %[in]" : [out]"=r"(r) : [in]"r"(n) + #endif + ); + return r; + #else + return __builtin_bswap32(n); + #endif + #elif defined(__WATCOMC__) && defined(__386__) + return _watcom_bswap32(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif #else return ((n & 0xFF000000) >> 24) | ((n & 0x00FF0000) >> 8) | @@ -515,399 +1804,850 @@ static DRFLAC_INLINE uint32_t drflac__swap_endian_uint32(uint32_t n) #endif } -static DRFLAC_INLINE uint64_t drflac__swap_endian_uint64(uint64_t n) +static DRFLAC_INLINE drflac_uint64 drflac__swap_endian_uint64(drflac_uint64 n) { -#ifdef _MSC_VER - return _byteswap_uint64(n); -#elif defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) - return __builtin_bswap64(n); +#ifdef DRFLAC_HAS_BYTESWAP64_INTRINSIC + #if defined(_MSC_VER) && !defined(__clang__) + return _byteswap_uint64(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(n); + #elif defined(__WATCOMC__) && defined(__386__) + return _watcom_bswap64(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif #else - return ((n & 0xFF00000000000000ULL) >> 56) | - ((n & 0x00FF000000000000ULL) >> 40) | - ((n & 0x0000FF0000000000ULL) >> 24) | - ((n & 0x000000FF00000000ULL) >> 8) | - ((n & 0x00000000FF000000ULL) << 8) | - ((n & 0x0000000000FF0000ULL) << 24) | - ((n & 0x000000000000FF00ULL) << 40) | - ((n & 0x00000000000000FFULL) << 56); + /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ + return ((n & ((drflac_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drflac_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drflac_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drflac_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drflac_uint64)0xFF000000 )) << 8) | + ((n & ((drflac_uint64)0x00FF0000 )) << 24) | + ((n & ((drflac_uint64)0x0000FF00 )) << 40) | + ((n & ((drflac_uint64)0x000000FF )) << 56); #endif } -static DRFLAC_INLINE uint32_t drflac__be2host_32(uint32_t n) +static DRFLAC_INLINE drflac_uint16 drflac__be2host_16(drflac_uint16 n) +{ + if (drflac__is_little_endian()) { + return drflac__swap_endian_uint16(n); + } + + return n; +} + +static DRFLAC_INLINE drflac_uint32 drflac__be2host_32(drflac_uint32 n) { -#if defined(__BYTE_ORDER__) && (__BYTE_ORDER == __ORDER_LITTLE_ENDIAN__) - return drflac__swap_endian_uint32(n); -#elif defined(__linux__) - return be32toh(n); -#else if (drflac__is_little_endian()) { return drflac__swap_endian_uint32(n); } return n; -#endif } -static DRFLAC_INLINE uint64_t drflac__be2host_64(uint64_t n) +static DRFLAC_INLINE drflac_uint32 drflac__be2host_32_ptr_unaligned(const void* pData) +{ + const drflac_uint8* pNum = (drflac_uint8*)pData; + return *(pNum) << 24 | *(pNum+1) << 16 | *(pNum+2) << 8 | *(pNum+3); +} + +static DRFLAC_INLINE drflac_uint64 drflac__be2host_64(drflac_uint64 n) { -#if defined(__BYTE_ORDER__) && (__BYTE_ORDER == __ORDER_LITTLE_ENDIAN__) - return drflac__swap_endian_uint64(n); -#elif defined(__linux__) - return be64toh(n); -#else if (drflac__is_little_endian()) { return drflac__swap_endian_uint64(n); } return n; +} + + +static DRFLAC_INLINE drflac_uint32 drflac__le2host_32(drflac_uint32 n) +{ + if (!drflac__is_little_endian()) { + return drflac__swap_endian_uint32(n); + } + + return n; +} + +static DRFLAC_INLINE drflac_uint32 drflac__le2host_32_ptr_unaligned(const void* pData) +{ + const drflac_uint8* pNum = (drflac_uint8*)pData; + return *pNum | *(pNum+1) << 8 | *(pNum+2) << 16 | *(pNum+3) << 24; +} + + +static DRFLAC_INLINE drflac_uint32 drflac__unsynchsafe_32(drflac_uint32 n) +{ + drflac_uint32 result = 0; + result |= (n & 0x7F000000) >> 3; + result |= (n & 0x007F0000) >> 2; + result |= (n & 0x00007F00) >> 1; + result |= (n & 0x0000007F) >> 0; + + return result; +} + + + +/* The CRC code below is based on this document: http://zlib.net/crc_v3.txt */ +static drflac_uint8 drflac__crc8_table[] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 +}; + +static drflac_uint16 drflac__crc16_table[] = { + 0x0000, 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011, + 0x8033, 0x0036, 0x003C, 0x8039, 0x0028, 0x802D, 0x8027, 0x0022, + 0x8063, 0x0066, 0x006C, 0x8069, 0x0078, 0x807D, 0x8077, 0x0072, + 0x0050, 0x8055, 0x805F, 0x005A, 0x804B, 0x004E, 0x0044, 0x8041, + 0x80C3, 0x00C6, 0x00CC, 0x80C9, 0x00D8, 0x80DD, 0x80D7, 0x00D2, + 0x00F0, 0x80F5, 0x80FF, 0x00FA, 0x80EB, 0x00EE, 0x00E4, 0x80E1, + 0x00A0, 0x80A5, 0x80AF, 0x00AA, 0x80BB, 0x00BE, 0x00B4, 0x80B1, + 0x8093, 0x0096, 0x009C, 0x8099, 0x0088, 0x808D, 0x8087, 0x0082, + 0x8183, 0x0186, 0x018C, 0x8189, 0x0198, 0x819D, 0x8197, 0x0192, + 0x01B0, 0x81B5, 0x81BF, 0x01BA, 0x81AB, 0x01AE, 0x01A4, 0x81A1, + 0x01E0, 0x81E5, 0x81EF, 0x01EA, 0x81FB, 0x01FE, 0x01F4, 0x81F1, + 0x81D3, 0x01D6, 0x01DC, 0x81D9, 0x01C8, 0x81CD, 0x81C7, 0x01C2, + 0x0140, 0x8145, 0x814F, 0x014A, 0x815B, 0x015E, 0x0154, 0x8151, + 0x8173, 0x0176, 0x017C, 0x8179, 0x0168, 0x816D, 0x8167, 0x0162, + 0x8123, 0x0126, 0x012C, 0x8129, 0x0138, 0x813D, 0x8137, 0x0132, + 0x0110, 0x8115, 0x811F, 0x011A, 0x810B, 0x010E, 0x0104, 0x8101, + 0x8303, 0x0306, 0x030C, 0x8309, 0x0318, 0x831D, 0x8317, 0x0312, + 0x0330, 0x8335, 0x833F, 0x033A, 0x832B, 0x032E, 0x0324, 0x8321, + 0x0360, 0x8365, 0x836F, 0x036A, 0x837B, 0x037E, 0x0374, 0x8371, + 0x8353, 0x0356, 0x035C, 0x8359, 0x0348, 0x834D, 0x8347, 0x0342, + 0x03C0, 0x83C5, 0x83CF, 0x03CA, 0x83DB, 0x03DE, 0x03D4, 0x83D1, + 0x83F3, 0x03F6, 0x03FC, 0x83F9, 0x03E8, 0x83ED, 0x83E7, 0x03E2, + 0x83A3, 0x03A6, 0x03AC, 0x83A9, 0x03B8, 0x83BD, 0x83B7, 0x03B2, + 0x0390, 0x8395, 0x839F, 0x039A, 0x838B, 0x038E, 0x0384, 0x8381, + 0x0280, 0x8285, 0x828F, 0x028A, 0x829B, 0x029E, 0x0294, 0x8291, + 0x82B3, 0x02B6, 0x02BC, 0x82B9, 0x02A8, 0x82AD, 0x82A7, 0x02A2, + 0x82E3, 0x02E6, 0x02EC, 0x82E9, 0x02F8, 0x82FD, 0x82F7, 0x02F2, + 0x02D0, 0x82D5, 0x82DF, 0x02DA, 0x82CB, 0x02CE, 0x02C4, 0x82C1, + 0x8243, 0x0246, 0x024C, 0x8249, 0x0258, 0x825D, 0x8257, 0x0252, + 0x0270, 0x8275, 0x827F, 0x027A, 0x826B, 0x026E, 0x0264, 0x8261, + 0x0220, 0x8225, 0x822F, 0x022A, 0x823B, 0x023E, 0x0234, 0x8231, + 0x8213, 0x0216, 0x021C, 0x8219, 0x0208, 0x820D, 0x8207, 0x0202 +}; + +static DRFLAC_INLINE drflac_uint8 drflac_crc8_byte(drflac_uint8 crc, drflac_uint8 data) +{ + return drflac__crc8_table[crc ^ data]; +} + +static DRFLAC_INLINE drflac_uint8 drflac_crc8(drflac_uint8 crc, drflac_uint32 data, drflac_uint32 count) +{ +#ifdef DR_FLAC_NO_CRC + (void)crc; + (void)data; + (void)count; + return 0; +#else +#if 0 + /* REFERENCE (use of this implementation requires an explicit flush by doing "drflac_crc8(crc, 0, 8);") */ + drflac_uint8 p = 0x07; + for (int i = count-1; i >= 0; --i) { + drflac_uint8 bit = (data & (1 << i)) >> i; + if (crc & 0x80) { + crc = ((crc << 1) | bit) ^ p; + } else { + crc = ((crc << 1) | bit); + } + } + return crc; +#else + drflac_uint32 wholeBytes; + drflac_uint32 leftoverBits; + drflac_uint64 leftoverDataMask; + + static drflac_uint64 leftoverDataMaskTable[8] = { + 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F + }; + + DRFLAC_ASSERT(count <= 32); + + wholeBytes = count >> 3; + leftoverBits = count - (wholeBytes*8); + leftoverDataMask = leftoverDataMaskTable[leftoverBits]; + + switch (wholeBytes) { + case 4: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0xFF000000UL << leftoverBits)) >> (24 + leftoverBits))); + case 3: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x00FF0000UL << leftoverBits)) >> (16 + leftoverBits))); + case 2: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x0000FF00UL << leftoverBits)) >> ( 8 + leftoverBits))); + case 1: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x000000FFUL << leftoverBits)) >> ( 0 + leftoverBits))); + case 0: if (leftoverBits > 0) crc = (drflac_uint8)((crc << leftoverBits) ^ drflac__crc8_table[(crc >> (8 - leftoverBits)) ^ (data & leftoverDataMask)]); + } + return crc; +#endif #endif } +static DRFLAC_INLINE drflac_uint16 drflac_crc16_byte(drflac_uint16 crc, drflac_uint8 data) +{ + return (crc << 8) ^ drflac__crc16_table[(drflac_uint8)(crc >> 8) ^ data]; +} + +static DRFLAC_INLINE drflac_uint16 drflac_crc16_cache(drflac_uint16 crc, drflac_cache_t data) +{ +#ifdef DRFLAC_64BIT + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 56) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 48) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 40) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 32) & 0xFF)); +#endif + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 24) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 16) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 8) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 0) & 0xFF)); + + return crc; +} + +static DRFLAC_INLINE drflac_uint16 drflac_crc16_bytes(drflac_uint16 crc, drflac_cache_t data, drflac_uint32 byteCount) +{ + switch (byteCount) + { +#ifdef DRFLAC_64BIT + case 8: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 56) & 0xFF)); + case 7: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 48) & 0xFF)); + case 6: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 40) & 0xFF)); + case 5: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 32) & 0xFF)); +#endif + case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 24) & 0xFF)); + case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 16) & 0xFF)); + case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 8) & 0xFF)); + case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 0) & 0xFF)); + } + + return crc; +} + +#if 0 +static DRFLAC_INLINE drflac_uint16 drflac_crc16__32bit(drflac_uint16 crc, drflac_uint32 data, drflac_uint32 count) +{ +#ifdef DR_FLAC_NO_CRC + (void)crc; + (void)data; + (void)count; + return 0; +#else +#if 0 + /* REFERENCE (use of this implementation requires an explicit flush by doing "drflac_crc16(crc, 0, 16);") */ + drflac_uint16 p = 0x8005; + for (int i = count-1; i >= 0; --i) { + drflac_uint16 bit = (data & (1ULL << i)) >> i; + if (r & 0x8000) { + r = ((r << 1) | bit) ^ p; + } else { + r = ((r << 1) | bit); + } + } + + return crc; +#else + drflac_uint32 wholeBytes; + drflac_uint32 leftoverBits; + drflac_uint64 leftoverDataMask; + + static drflac_uint64 leftoverDataMaskTable[8] = { + 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F + }; + + DRFLAC_ASSERT(count <= 64); + + wholeBytes = count >> 3; + leftoverBits = count & 7; + leftoverDataMask = leftoverDataMaskTable[leftoverBits]; + + switch (wholeBytes) { + default: + case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0xFF000000UL << leftoverBits)) >> (24 + leftoverBits))); + case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0x00FF0000UL << leftoverBits)) >> (16 + leftoverBits))); + case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0x0000FF00UL << leftoverBits)) >> ( 8 + leftoverBits))); + case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0x000000FFUL << leftoverBits)) >> ( 0 + leftoverBits))); + case 0: if (leftoverBits > 0) crc = (crc << leftoverBits) ^ drflac__crc16_table[(crc >> (16 - leftoverBits)) ^ (data & leftoverDataMask)]; + } + return crc; +#endif +#endif +} + +static DRFLAC_INLINE drflac_uint16 drflac_crc16__64bit(drflac_uint16 crc, drflac_uint64 data, drflac_uint32 count) +{ +#ifdef DR_FLAC_NO_CRC + (void)crc; + (void)data; + (void)count; + return 0; +#else + drflac_uint32 wholeBytes; + drflac_uint32 leftoverBits; + drflac_uint64 leftoverDataMask; + + static drflac_uint64 leftoverDataMaskTable[8] = { + 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F + }; + + DRFLAC_ASSERT(count <= 64); + + wholeBytes = count >> 3; + leftoverBits = count & 7; + leftoverDataMask = leftoverDataMaskTable[leftoverBits]; + + switch (wholeBytes) { + default: + case 8: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0xFF000000 << 32) << leftoverBits)) >> (56 + leftoverBits))); /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ + case 7: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x00FF0000 << 32) << leftoverBits)) >> (48 + leftoverBits))); + case 6: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x0000FF00 << 32) << leftoverBits)) >> (40 + leftoverBits))); + case 5: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x000000FF << 32) << leftoverBits)) >> (32 + leftoverBits))); + case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0xFF000000 ) << leftoverBits)) >> (24 + leftoverBits))); + case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x00FF0000 ) << leftoverBits)) >> (16 + leftoverBits))); + case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x0000FF00 ) << leftoverBits)) >> ( 8 + leftoverBits))); + case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x000000FF ) << leftoverBits)) >> ( 0 + leftoverBits))); + case 0: if (leftoverBits > 0) crc = (crc << leftoverBits) ^ drflac__crc16_table[(crc >> (16 - leftoverBits)) ^ (data & leftoverDataMask)]; + } + return crc; +#endif +} + + +static DRFLAC_INLINE drflac_uint16 drflac_crc16(drflac_uint16 crc, drflac_cache_t data, drflac_uint32 count) +{ +#ifdef DRFLAC_64BIT + return drflac_crc16__64bit(crc, data, count); +#else + return drflac_crc16__32bit(crc, data, count); +#endif +} +#endif + + #ifdef DRFLAC_64BIT #define drflac__be2host__cache_line drflac__be2host_64 #else #define drflac__be2host__cache_line drflac__be2host_32 #endif +/* +BIT READING ATTEMPT #2 -// BIT READING ATTEMPT #2 -// -// This uses a 32- or 64-bit bit-shifted cache - as bits are read, the cache is shifted such that the first valid bit is sitting -// on the most significant bit. It uses the notion of an L1 and L2 cache (borrowed from CPU architecture), where the L1 cache -// is a 32- or 64-bit unsigned integer (depending on whether or not a 32- or 64-bit build is being compiled) and the L2 is an -// array of "cache lines", with each cache line being the same size as the L1. The L2 is a buffer of about 4KB and is where data -// from onRead() is read into. -#define DRFLAC_CACHE_L1_SIZE_BYTES (sizeof(pFlac->cache)) -#define DRFLAC_CACHE_L1_SIZE_BITS (sizeof(pFlac->cache)*8) -#define DRFLAC_CACHE_L1_BITS_REMAINING (DRFLAC_CACHE_L1_SIZE_BITS - (pFlac->consumedBits)) -#ifdef DRFLAC_64BIT -#define DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount) (~(((uint64_t)-1LL) >> (_bitCount))) -#else -#define DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount) (~(((uint32_t)-1) >> (_bitCount))) +This uses a 32- or 64-bit bit-shifted cache - as bits are read, the cache is shifted such that the first valid bit is sitting +on the most significant bit. It uses the notion of an L1 and L2 cache (borrowed from CPU architecture), where the L1 cache +is a 32- or 64-bit unsigned integer (depending on whether or not a 32- or 64-bit build is being compiled) and the L2 is an +array of "cache lines", with each cache line being the same size as the L1. The L2 is a buffer of about 4KB and is where data +from onRead() is read into. +*/ +#define DRFLAC_CACHE_L1_SIZE_BYTES(bs) (sizeof((bs)->cache)) +#define DRFLAC_CACHE_L1_SIZE_BITS(bs) (sizeof((bs)->cache)*8) +#define DRFLAC_CACHE_L1_BITS_REMAINING(bs) (DRFLAC_CACHE_L1_SIZE_BITS(bs) - (bs)->consumedBits) +#define DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount) (~((~(drflac_cache_t)0) >> (_bitCount))) +#define DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, _bitCount) (DRFLAC_CACHE_L1_SIZE_BITS(bs) - (_bitCount)) +#define DRFLAC_CACHE_L1_SELECT(bs, _bitCount) (((bs)->cache) & DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount)) +#define DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, _bitCount) (DRFLAC_CACHE_L1_SELECT((bs), (_bitCount)) >> DRFLAC_CACHE_L1_SELECTION_SHIFT((bs), (_bitCount))) +#define DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, _bitCount)(DRFLAC_CACHE_L1_SELECT((bs), (_bitCount)) >> (DRFLAC_CACHE_L1_SELECTION_SHIFT((bs), (_bitCount)) & (DRFLAC_CACHE_L1_SIZE_BITS(bs)-1))) +#define DRFLAC_CACHE_L2_SIZE_BYTES(bs) (sizeof((bs)->cacheL2)) +#define DRFLAC_CACHE_L2_LINE_COUNT(bs) (DRFLAC_CACHE_L2_SIZE_BYTES(bs) / sizeof((bs)->cacheL2[0])) +#define DRFLAC_CACHE_L2_LINES_REMAINING(bs) (DRFLAC_CACHE_L2_LINE_COUNT(bs) - (bs)->nextL2Line) + + +#ifndef DR_FLAC_NO_CRC +static DRFLAC_INLINE void drflac__reset_crc16(drflac_bs* bs) +{ + bs->crc16 = 0; + bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; +} + +static DRFLAC_INLINE void drflac__update_crc16(drflac_bs* bs) +{ + if (bs->crc16CacheIgnoredBytes == 0) { + bs->crc16 = drflac_crc16_cache(bs->crc16, bs->crc16Cache); + } else { + bs->crc16 = drflac_crc16_bytes(bs->crc16, bs->crc16Cache, DRFLAC_CACHE_L1_SIZE_BYTES(bs) - bs->crc16CacheIgnoredBytes); + bs->crc16CacheIgnoredBytes = 0; + } +} + +static DRFLAC_INLINE drflac_uint16 drflac__flush_crc16(drflac_bs* bs) +{ + /* We should never be flushing in a situation where we are not aligned on a byte boundary. */ + DRFLAC_ASSERT((DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7) == 0); + + /* + The bits that were read from the L1 cache need to be accumulated. The number of bytes needing to be accumulated is determined + by the number of bits that have been consumed. + */ + if (DRFLAC_CACHE_L1_BITS_REMAINING(bs) == 0) { + drflac__update_crc16(bs); + } else { + /* We only accumulate the consumed bits. */ + bs->crc16 = drflac_crc16_bytes(bs->crc16, bs->crc16Cache >> DRFLAC_CACHE_L1_BITS_REMAINING(bs), (bs->consumedBits >> 3) - bs->crc16CacheIgnoredBytes); + + /* + The bits that we just accumulated should never be accumulated again. We need to keep track of how many bytes were accumulated + so we can handle that later. + */ + bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; + } + + return bs->crc16; +} #endif -#define DRFLAC_CACHE_L1_SELECTION_SHIFT(_bitCount) (DRFLAC_CACHE_L1_SIZE_BITS - (_bitCount)) -#define DRFLAC_CACHE_L1_SELECT(_bitCount) ((pFlac->cache) & DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount)) -#define DRFLAC_CACHE_L1_SELECT_AND_SHIFT(_bitCount) (DRFLAC_CACHE_L1_SELECT(_bitCount) >> DRFLAC_CACHE_L1_SELECTION_SHIFT(_bitCount)) -#define DRFLAC_CACHE_L2_SIZE_BYTES (sizeof(pFlac->cacheL2)) -#define DRFLAC_CACHE_L2_LINE_COUNT (DRFLAC_CACHE_L2_SIZE_BYTES / sizeof(pFlac->cacheL2[0])) -#define DRFLAC_CACHE_L2_LINES_REMAINING (DRFLAC_CACHE_L2_LINE_COUNT - pFlac->nextL2Line) -static DRFLAC_INLINE bool drflac__reload_l1_cache_from_l2(drflac* pFlac) +static DRFLAC_INLINE drflac_bool32 drflac__reload_l1_cache_from_l2(drflac_bs* bs) { - // Fast path. Try loading straight from L2. - if (pFlac->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT) { - pFlac->cache = pFlac->cacheL2[pFlac->nextL2Line++]; - return true; + size_t bytesRead; + size_t alignedL1LineCount; + + /* Fast path. Try loading straight from L2. */ + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + bs->cache = bs->cacheL2[bs->nextL2Line++]; + return DRFLAC_TRUE; } - // If we get here it means we've run out of data in the L2 cache. We'll need to fetch more from the client. - size_t bytesRead = pFlac->onRead(pFlac->pUserData, pFlac->cacheL2, DRFLAC_CACHE_L2_SIZE_BYTES); - pFlac->currentBytePos += bytesRead; + /* + If we get here it means we've run out of data in the L2 cache. We'll need to fetch more from the client, if there's + any left. + */ + if (bs->unalignedByteCount > 0) { + return DRFLAC_FALSE; /* If we have any unaligned bytes it means there's no more aligned bytes left in the client. */ + } - pFlac->nextL2Line = 0; - if (bytesRead == DRFLAC_CACHE_L2_SIZE_BYTES) { - pFlac->cache = pFlac->cacheL2[pFlac->nextL2Line++]; - return true; + bytesRead = bs->onRead(bs->pUserData, bs->cacheL2, DRFLAC_CACHE_L2_SIZE_BYTES(bs)); + + bs->nextL2Line = 0; + if (bytesRead == DRFLAC_CACHE_L2_SIZE_BYTES(bs)) { + bs->cache = bs->cacheL2[bs->nextL2Line++]; + return DRFLAC_TRUE; } - // If we get here it means we were unable to retrieve enough data to fill the entire L2 cache. It probably - // means we've just reached the end of the file. We need to move the valid data down to the end of the buffer - // and adjust the index of the next line accordingly. Also keep in mind that the L2 cache must be aligned to - // the size of the L1 so we'll need to seek backwards by any misaligned bytes. - size_t alignedL1LineCount = bytesRead / DRFLAC_CACHE_L1_SIZE_BYTES; - if (alignedL1LineCount > 0) - { - size_t offset = DRFLAC_CACHE_L2_LINE_COUNT - alignedL1LineCount; - for (size_t i = alignedL1LineCount; i > 0; --i) { - pFlac->cacheL2[i-1 + offset] = pFlac->cacheL2[i-1]; - } + /* + If we get here it means we were unable to retrieve enough data to fill the entire L2 cache. It probably + means we've just reached the end of the file. We need to move the valid data down to the end of the buffer + and adjust the index of the next line accordingly. Also keep in mind that the L2 cache must be aligned to + the size of the L1 so we'll need to seek backwards by any misaligned bytes. + */ + alignedL1LineCount = bytesRead / DRFLAC_CACHE_L1_SIZE_BYTES(bs); - pFlac->nextL2Line = offset; - pFlac->unusedL2Lines = offset; - - // At this point there may be some leftover unaligned bytes. We need to seek backwards so we don't lose - // those bytes. - size_t unalignedBytes = bytesRead - (alignedL1LineCount * DRFLAC_CACHE_L1_SIZE_BYTES); - if (unalignedBytes > 0) { - pFlac->onSeek(pFlac->pUserData, -(int)unalignedBytes); - pFlac->currentBytePos -= unalignedBytes; - } - - pFlac->cache = pFlac->cacheL2[pFlac->nextL2Line++]; - return true; + /* We need to keep track of any unaligned bytes for later use. */ + bs->unalignedByteCount = bytesRead - (alignedL1LineCount * DRFLAC_CACHE_L1_SIZE_BYTES(bs)); + if (bs->unalignedByteCount > 0) { + bs->unalignedCache = bs->cacheL2[alignedL1LineCount]; } - else - { - // If we get into this branch it means we weren't able to load any L1-aligned data. We just need to seek - // backwards by the leftover bytes and return false. - if (bytesRead > 0) { - pFlac->onSeek(pFlac->pUserData, -(int)bytesRead); - pFlac->currentBytePos -= bytesRead; + + if (alignedL1LineCount > 0) { + size_t offset = DRFLAC_CACHE_L2_LINE_COUNT(bs) - alignedL1LineCount; + size_t i; + for (i = alignedL1LineCount; i > 0; --i) { + bs->cacheL2[i-1 + offset] = bs->cacheL2[i-1]; } - pFlac->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT; - return false; + bs->nextL2Line = (drflac_uint32)offset; + bs->cache = bs->cacheL2[bs->nextL2Line++]; + return DRFLAC_TRUE; + } else { + /* If we get into this branch it means we weren't able to load any L1-aligned data. */ + bs->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT(bs); + return DRFLAC_FALSE; } } -static bool drflac__reload_cache(drflac* pFlac) +static drflac_bool32 drflac__reload_cache(drflac_bs* bs) { - // Fast path. Try just moving the next value in the L2 cache to the L1 cache. - if (drflac__reload_l1_cache_from_l2(pFlac)) { - pFlac->cache = drflac__be2host__cache_line(pFlac->cache); - pFlac->consumedBits = 0; - return true; + size_t bytesRead; + +#ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); +#endif + + /* Fast path. Try just moving the next value in the L2 cache to the L1 cache. */ + if (drflac__reload_l1_cache_from_l2(bs)) { + bs->cache = drflac__be2host__cache_line(bs->cache); + bs->consumedBits = 0; +#ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs->cache; +#endif + return DRFLAC_TRUE; } - // Slow path. + /* Slow path. */ - // If we get here it means we have failed to load the L1 cache from the L2. Likely we've just reached the end of the stream and the last - // few bytes did not meet the alignment requirements for the L2 cache. In this case we need to fall back to a slower path and read the - // data straight from the client into the L1 cache. This should only really happen once per stream so efficiency is not important. - size_t bytesRead = pFlac->onRead(pFlac->pUserData, &pFlac->cache, DRFLAC_CACHE_L1_SIZE_BYTES); + /* + If we get here it means we have failed to load the L1 cache from the L2. Likely we've just reached the end of the stream and the last + few bytes did not meet the alignment requirements for the L2 cache. In this case we need to fall back to a slower path and read the + data from the unaligned cache. + */ + bytesRead = bs->unalignedByteCount; if (bytesRead == 0) { - return false; + bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); /* <-- The stream has been exhausted, so marked the bits as consumed. */ + return DRFLAC_FALSE; } - pFlac->currentBytePos += bytesRead; + DRFLAC_ASSERT(bytesRead < DRFLAC_CACHE_L1_SIZE_BYTES(bs)); + bs->consumedBits = (drflac_uint32)(DRFLAC_CACHE_L1_SIZE_BYTES(bs) - bytesRead) * 8; - assert(bytesRead < DRFLAC_CACHE_L1_SIZE_BYTES); - pFlac->consumedBits = (DRFLAC_CACHE_L1_SIZE_BYTES - bytesRead) * 8; + bs->cache = drflac__be2host__cache_line(bs->unalignedCache); + bs->cache &= DRFLAC_CACHE_L1_SELECTION_MASK(DRFLAC_CACHE_L1_BITS_REMAINING(bs)); /* <-- Make sure the consumed bits are always set to zero. Other parts of the library depend on this property. */ + bs->unalignedByteCount = 0; /* <-- At this point the unaligned bytes have been moved into the cache and we thus have no more unaligned bytes. */ - pFlac->cache = drflac__be2host__cache_line(pFlac->cache); - pFlac->cache &= DRFLAC_CACHE_L1_SELECTION_MASK(DRFLAC_CACHE_L1_SIZE_BITS - pFlac->consumedBits); // <-- Make sure the consumed bits are always set to zero. Other parts of the library depend on this property. - return true; +#ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs->cache >> bs->consumedBits; + bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; +#endif + return DRFLAC_TRUE; } -static bool drflac__seek_bits(drflac* pFlac, size_t bitsToSeek) +static void drflac__reset_cache(drflac_bs* bs) { - if (bitsToSeek <= DRFLAC_CACHE_L1_BITS_REMAINING) { - pFlac->consumedBits += bitsToSeek; - pFlac->cache <<= bitsToSeek; - return true; - } else { - // It straddles the cached data. This function isn't called too frequently so I'm favouring simplicity here. - bitsToSeek -= DRFLAC_CACHE_L1_BITS_REMAINING; - pFlac->consumedBits += DRFLAC_CACHE_L1_BITS_REMAINING; - pFlac->cache = 0; + bs->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT(bs); /* <-- This clears the L2 cache. */ + bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); /* <-- This clears the L1 cache. */ + bs->cache = 0; + bs->unalignedByteCount = 0; /* <-- This clears the trailing unaligned bytes. */ + bs->unalignedCache = 0; - size_t wholeBytesRemaining = bitsToSeek/8; - if (wholeBytesRemaining > 0) - { - // The next bytes to seek will be located in the L2 cache. The problem is that the L2 cache is not byte aligned, - // but rather DRFLAC_CACHE_L1_SIZE_BYTES aligned (usually 4 or 8). If, for example, the number of bytes to seek is - // 3, we'll need to handle it in a special way. - size_t wholeCacheLinesRemaining = wholeBytesRemaining / DRFLAC_CACHE_L1_SIZE_BYTES; - if (wholeCacheLinesRemaining < DRFLAC_CACHE_L2_LINES_REMAINING) - { - wholeBytesRemaining -= wholeCacheLinesRemaining * DRFLAC_CACHE_L1_SIZE_BYTES; - bitsToSeek -= wholeCacheLinesRemaining * DRFLAC_CACHE_L1_SIZE_BITS; - pFlac->nextL2Line += wholeCacheLinesRemaining; - } - else - { - wholeBytesRemaining -= DRFLAC_CACHE_L2_LINES_REMAINING * DRFLAC_CACHE_L1_SIZE_BYTES; - bitsToSeek -= DRFLAC_CACHE_L2_LINES_REMAINING * DRFLAC_CACHE_L1_SIZE_BITS; - pFlac->nextL2Line += DRFLAC_CACHE_L2_LINES_REMAINING; - - pFlac->onSeek(pFlac->pUserData, (int)wholeBytesRemaining); - pFlac->currentBytePos += wholeBytesRemaining; - bitsToSeek -= wholeBytesRemaining*8; - } - } - - - if (bitsToSeek > 0) { - if (!drflac__reload_cache(pFlac)) { - return false; - } - - return drflac__seek_bits(pFlac, bitsToSeek); - } - - return true; - } +#ifndef DR_FLAC_NO_CRC + bs->crc16Cache = 0; + bs->crc16CacheIgnoredBytes = 0; +#endif } -static bool drflac__read_uint32(drflac* pFlac, unsigned int bitCount, uint32_t* pResultOut) -{ - assert(pFlac != NULL); - assert(pResultOut != NULL); - assert(bitCount > 0); - assert(bitCount <= 32); - if (pFlac->consumedBits == DRFLAC_CACHE_L1_SIZE_BITS) { - if (!drflac__reload_cache(pFlac)) { - return false; +static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned int bitCount, drflac_uint32* pResultOut) +{ + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResultOut != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 32); + + if (bs->consumedBits == DRFLAC_CACHE_L1_SIZE_BITS(bs)) { + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; } } - if (bitCount <= DRFLAC_CACHE_L1_BITS_REMAINING) { - if (bitCount < DRFLAC_CACHE_L1_SIZE_BITS) { - *pResultOut = DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bitCount); - pFlac->consumedBits += bitCount; - pFlac->cache <<= bitCount; + if (bitCount <= DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* + If we want to load all 32-bits from a 32-bit cache we need to do it slightly differently because we can't do + a 32-bit shift on a 32-bit integer. This will never be the case on 64-bit caches, so we can have a slightly + more optimal solution for this. + */ +#ifdef DRFLAC_64BIT + *pResultOut = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCount); + bs->consumedBits += bitCount; + bs->cache <<= bitCount; +#else + if (bitCount < DRFLAC_CACHE_L1_SIZE_BITS(bs)) { + *pResultOut = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCount); + bs->consumedBits += bitCount; + bs->cache <<= bitCount; } else { - *pResultOut = (uint32_t)pFlac->cache; - pFlac->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS; - pFlac->cache = 0; + /* Cannot shift by 32-bits, so need to do it differently. */ + *pResultOut = (drflac_uint32)bs->cache; + bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); + bs->cache = 0; } - return true; +#endif + + return DRFLAC_TRUE; } else { - // It straddles the cached data. It will never cover more than the next chunk. We just read the number in two parts and combine them. - size_t bitCountHi = DRFLAC_CACHE_L1_BITS_REMAINING; - size_t bitCountLo = bitCount - bitCountHi; - uint32_t resultHi = DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bitCountHi); + /* It straddles the cached data. It will never cover more than the next chunk. We just read the number in two parts and combine them. */ + drflac_uint32 bitCountHi = DRFLAC_CACHE_L1_BITS_REMAINING(bs); + drflac_uint32 bitCountLo = bitCount - bitCountHi; + drflac_uint32 resultHi; - if (!drflac__reload_cache(pFlac)) { - return false; + DRFLAC_ASSERT(bitCountHi > 0); + DRFLAC_ASSERT(bitCountHi < 32); + resultHi = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountHi); + + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; } - *pResultOut = (resultHi << bitCountLo) | DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bitCountLo); - pFlac->consumedBits += bitCountLo; - pFlac->cache <<= bitCountLo; - return true; + *pResultOut = (resultHi << bitCountLo) | (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountLo); + bs->consumedBits += bitCountLo; + bs->cache <<= bitCountLo; + return DRFLAC_TRUE; } } -static bool drflac__read_int32(drflac* pFlac, unsigned int bitCount, int32_t* pResult) +static drflac_bool32 drflac__read_int32(drflac_bs* bs, unsigned int bitCount, drflac_int32* pResult) { - assert(pFlac != NULL); - assert(pResult != NULL); - assert(bitCount > 0); - assert(bitCount <= 32); + drflac_uint32 result; - uint32_t result; - if (!drflac__read_uint32(pFlac, bitCount, &result)) { - return false; + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 32); + + if (!drflac__read_uint32(bs, bitCount, &result)) { + return DRFLAC_FALSE; } - if ((result & (1 << (bitCount - 1)))) { // TODO: See if we can get rid of this branch. - result |= (-1 << bitCount); + /* Do not attempt to shift by 32 as it's undefined. */ + if (bitCount < 32) { + drflac_uint32 signbit; + signbit = ((result >> (bitCount-1)) & 0x01); + result |= (~signbit + 1) << bitCount; } - *pResult = (int32_t)result; - return true; + *pResult = (drflac_int32)result; + return DRFLAC_TRUE; } -static bool drflac__read_uint64(drflac* pFlac, unsigned int bitCount, uint64_t* pResultOut) +#ifdef DRFLAC_64BIT +static drflac_bool32 drflac__read_uint64(drflac_bs* bs, unsigned int bitCount, drflac_uint64* pResultOut) { - assert(bitCount <= 64); - assert(bitCount > 32); + drflac_uint32 resultHi; + drflac_uint32 resultLo; - uint32_t resultHi; - if (!drflac__read_uint32(pFlac, bitCount - 32, &resultHi)) { - return false; + DRFLAC_ASSERT(bitCount <= 64); + DRFLAC_ASSERT(bitCount > 32); + + if (!drflac__read_uint32(bs, bitCount - 32, &resultHi)) { + return DRFLAC_FALSE; } - uint32_t resultLo; - if (!drflac__read_uint32(pFlac, 32, &resultLo)) { - return false; + if (!drflac__read_uint32(bs, 32, &resultLo)) { + return DRFLAC_FALSE; } - *pResultOut = (((uint64_t)resultHi) << 32) | ((uint64_t)resultLo); - return true; + *pResultOut = (((drflac_uint64)resultHi) << 32) | ((drflac_uint64)resultLo); + return DRFLAC_TRUE; +} +#endif + +/* Function below is unused, but leaving it here in case I need to quickly add it again. */ +#if 0 +static drflac_bool32 drflac__read_int64(drflac_bs* bs, unsigned int bitCount, drflac_int64* pResultOut) +{ + drflac_uint64 result; + drflac_uint64 signbit; + + DRFLAC_ASSERT(bitCount <= 64); + + if (!drflac__read_uint64(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + signbit = ((result >> (bitCount-1)) & 0x01); + result |= (~signbit + 1) << bitCount; + + *pResultOut = (drflac_int64)result; + return DRFLAC_TRUE; +} +#endif + +static drflac_bool32 drflac__read_uint16(drflac_bs* bs, unsigned int bitCount, drflac_uint16* pResult) +{ + drflac_uint32 result; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 16); + + if (!drflac__read_uint32(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + *pResult = (drflac_uint16)result; + return DRFLAC_TRUE; } -static bool drflac__read_int64(drflac* pFlac, unsigned int bitCount, int64_t* pResultOut) +#if 0 +static drflac_bool32 drflac__read_int16(drflac_bs* bs, unsigned int bitCount, drflac_int16* pResult) { - assert(bitCount <= 64); + drflac_int32 result; - uint64_t result; - if (!drflac__read_uint64(pFlac, bitCount, &result)) { - return false; + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 16); + + if (!drflac__read_int32(bs, bitCount, &result)) { + return DRFLAC_FALSE; } - if ((result & (1ULL << (bitCount - 1)))) { // TODO: See if we can get rid of this branch. - result |= (-1LL << bitCount); + *pResult = (drflac_int16)result; + return DRFLAC_TRUE; +} +#endif + +static drflac_bool32 drflac__read_uint8(drflac_bs* bs, unsigned int bitCount, drflac_uint8* pResult) +{ + drflac_uint32 result; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 8); + + if (!drflac__read_uint32(bs, bitCount, &result)) { + return DRFLAC_FALSE; } - *pResultOut = (int64_t)result; - return true; + *pResult = (drflac_uint8)result; + return DRFLAC_TRUE; } -static bool drflac__read_uint16(drflac* pFlac, unsigned int bitCount, uint16_t* pResult) +static drflac_bool32 drflac__read_int8(drflac_bs* bs, unsigned int bitCount, drflac_int8* pResult) { - assert(pFlac != NULL); - assert(pResult != NULL); - assert(bitCount > 0); - assert(bitCount <= 16); + drflac_int32 result; - uint32_t result; - if (!drflac__read_uint32(pFlac, bitCount, &result)) { - return false; + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 8); + + if (!drflac__read_int32(bs, bitCount, &result)) { + return DRFLAC_FALSE; } - *pResult = (uint16_t)result; - return true; -} - -static bool drflac__read_int16(drflac* pFlac, unsigned int bitCount, int16_t* pResult) -{ - assert(pFlac != NULL); - assert(pResult != NULL); - assert(bitCount > 0); - assert(bitCount <= 16); - - int32_t result; - if (!drflac__read_int32(pFlac, bitCount, &result)) { - return false; - } - - *pResult = (int16_t)result; - return true; -} - -static bool drflac__read_uint8(drflac* pFlac, unsigned int bitCount, uint8_t* pResult) -{ - assert(pFlac != NULL); - assert(pResult != NULL); - assert(bitCount > 0); - assert(bitCount <= 8); - - uint32_t result; - if (!drflac__read_uint32(pFlac, bitCount, &result)) { - return false; - } - - *pResult = (uint8_t)result; - return true; -} - -static bool drflac__read_int8(drflac* pFlac, unsigned int bitCount, int8_t* pResult) -{ - assert(pFlac != NULL); - assert(pResult != NULL); - assert(bitCount > 0); - assert(bitCount <= 8); - - int32_t result; - if (!drflac__read_int32(pFlac, bitCount, &result)) { - return false; - } - - *pResult = (int8_t)result; - return true; + *pResult = (drflac_int8)result; + return DRFLAC_TRUE; } -static inline bool drflac__seek_past_next_set_bit(drflac* pFlac, unsigned int* pOffsetOut) +static drflac_bool32 drflac__seek_bits(drflac_bs* bs, size_t bitsToSeek) { - unsigned int zeroCounter = 0; - while (pFlac->cache == 0) { - zeroCounter += (unsigned int)DRFLAC_CACHE_L1_BITS_REMAINING; - if (!drflac__reload_cache(pFlac)) { - return false; + if (bitsToSeek <= DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + bs->consumedBits += (drflac_uint32)bitsToSeek; + bs->cache <<= bitsToSeek; + return DRFLAC_TRUE; + } else { + /* It straddles the cached data. This function isn't called too frequently so I'm favouring simplicity here. */ + bitsToSeek -= DRFLAC_CACHE_L1_BITS_REMAINING(bs); + bs->consumedBits += DRFLAC_CACHE_L1_BITS_REMAINING(bs); + bs->cache = 0; + + /* Simple case. Seek in groups of the same number as bits that fit within a cache line. */ +#ifdef DRFLAC_64BIT + while (bitsToSeek >= DRFLAC_CACHE_L1_SIZE_BITS(bs)) { + drflac_uint64 bin; + if (!drflac__read_uint64(bs, DRFLAC_CACHE_L1_SIZE_BITS(bs), &bin)) { + return DRFLAC_FALSE; + } + bitsToSeek -= DRFLAC_CACHE_L1_SIZE_BITS(bs); + } +#else + while (bitsToSeek >= DRFLAC_CACHE_L1_SIZE_BITS(bs)) { + drflac_uint32 bin; + if (!drflac__read_uint32(bs, DRFLAC_CACHE_L1_SIZE_BITS(bs), &bin)) { + return DRFLAC_FALSE; + } + bitsToSeek -= DRFLAC_CACHE_L1_SIZE_BITS(bs); + } +#endif + + /* Whole leftover bytes. */ + while (bitsToSeek >= 8) { + drflac_uint8 bin; + if (!drflac__read_uint8(bs, 8, &bin)) { + return DRFLAC_FALSE; + } + bitsToSeek -= 8; + } + + /* Leftover bits. */ + if (bitsToSeek > 0) { + drflac_uint8 bin; + if (!drflac__read_uint8(bs, (drflac_uint32)bitsToSeek, &bin)) { + return DRFLAC_FALSE; + } + bitsToSeek = 0; /* <-- Necessary for the assert below. */ + } + + DRFLAC_ASSERT(bitsToSeek == 0); + return DRFLAC_TRUE; + } +} + + +/* This function moves the bit streamer to the first bit after the sync code (bit 15 of the of the frame header). It will also update the CRC-16. */ +static drflac_bool32 drflac__find_and_seek_to_next_sync_code(drflac_bs* bs) +{ + DRFLAC_ASSERT(bs != NULL); + + /* + The sync code is always aligned to 8 bits. This is convenient for us because it means we can do byte-aligned movements. The first + thing to do is align to the next byte. + */ + if (!drflac__seek_bits(bs, DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7)) { + return DRFLAC_FALSE; + } + + for (;;) { + drflac_uint8 hi; + +#ifndef DR_FLAC_NO_CRC + drflac__reset_crc16(bs); +#endif + + if (!drflac__read_uint8(bs, 8, &hi)) { + return DRFLAC_FALSE; + } + + if (hi == 0xFF) { + drflac_uint8 lo; + if (!drflac__read_uint8(bs, 6, &lo)) { + return DRFLAC_FALSE; + } + + if (lo == 0x3E) { + return DRFLAC_TRUE; + } else { + if (!drflac__seek_bits(bs, DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7)) { + return DRFLAC_FALSE; + } + } } } - // At this point the cache should not be zero, in which case we know the first set bit should be somewhere in here. There is - // no need for us to perform any cache reloading logic here which should make things much faster. - assert(pFlac->cache != 0); + /* Should never get here. */ + /*return DRFLAC_FALSE;*/ +} - unsigned int bitOffsetTable[] = { + +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) +#define DRFLAC_IMPLEMENT_CLZ_LZCNT +#endif +#if defined(_MSC_VER) && _MSC_VER >= 1400 && (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(__clang__) +#define DRFLAC_IMPLEMENT_CLZ_MSVC +#endif +#if defined(__WATCOMC__) && defined(__386__) +#define DRFLAC_IMPLEMENT_CLZ_WATCOM +#endif +#ifdef __MRC__ +#include +#define DRFLAC_IMPLEMENT_CLZ_MRC +#endif + +static DRFLAC_INLINE drflac_uint32 drflac__clz_software(drflac_cache_t x) +{ + drflac_uint32 n; + static drflac_uint32 clz_table_4[] = { 0, 4, 3, 3, @@ -915,106 +2655,293 @@ static inline bool drflac__seek_past_next_set_bit(drflac* pFlac, unsigned int* p 1, 1, 1, 1, 1, 1, 1, 1 }; - unsigned int setBitOffsetPlus1 = bitOffsetTable[DRFLAC_CACHE_L1_SELECT_AND_SHIFT(4)]; - if (setBitOffsetPlus1 == 0) { - if (pFlac->cache == 1) { - setBitOffsetPlus1 = DRFLAC_CACHE_L1_SIZE_BITS; - } else { - setBitOffsetPlus1 = 5; - for (;;) - { - if ((pFlac->cache & DRFLAC_CACHE_L1_SELECT(setBitOffsetPlus1))) { - break; - } + if (x == 0) { + return sizeof(x)*8; + } - setBitOffsetPlus1 += 1; + n = clz_table_4[x >> (sizeof(x)*8 - 4)]; + if (n == 0) { +#ifdef DRFLAC_64BIT + if ((x & ((drflac_uint64)0xFFFFFFFF << 32)) == 0) { n = 32; x <<= 32; } + if ((x & ((drflac_uint64)0xFFFF0000 << 32)) == 0) { n += 16; x <<= 16; } + if ((x & ((drflac_uint64)0xFF000000 << 32)) == 0) { n += 8; x <<= 8; } + if ((x & ((drflac_uint64)0xF0000000 << 32)) == 0) { n += 4; x <<= 4; } +#else + if ((x & 0xFFFF0000) == 0) { n = 16; x <<= 16; } + if ((x & 0xFF000000) == 0) { n += 8; x <<= 8; } + if ((x & 0xF0000000) == 0) { n += 4; x <<= 4; } +#endif + n += clz_table_4[x >> (sizeof(x)*8 - 4)]; + } + + return n - 1; +} + +#ifdef DRFLAC_IMPLEMENT_CLZ_LZCNT +static DRFLAC_INLINE drflac_bool32 drflac__is_lzcnt_supported(void) +{ + /* Fast compile time check for ARM. */ +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) && defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) + return DRFLAC_TRUE; +#elif defined(__MRC__) + return DRFLAC_TRUE; +#else + /* If the compiler itself does not support the intrinsic then we'll need to return false. */ + #ifdef DRFLAC_HAS_LZCNT_INTRINSIC + return drflac__gIsLZCNTSupported; + #else + return DRFLAC_FALSE; + #endif +#endif +} + +static DRFLAC_INLINE drflac_uint32 drflac__clz_lzcnt(drflac_cache_t x) +{ + /* + It's critical for competitive decoding performance that this function be highly optimal. With MSVC we can use the __lzcnt64() and __lzcnt() intrinsics + to achieve good performance, however on GCC and Clang it's a little bit more annoying. The __builtin_clzl() and __builtin_clzll() intrinsics leave + it undefined as to the return value when `x` is 0. We need this to be well defined as returning 32 or 64, depending on whether or not it's a 32- or + 64-bit build. To work around this we would need to add a conditional to check for the x = 0 case, but this creates unnecessary inefficiency. To work + around this problem I have written some inline assembly to emit the LZCNT (x86) or CLZ (ARM) instruction directly which removes the need to include + the conditional. This has worked well in the past, but for some reason Clang's MSVC compatible driver, clang-cl, does not seem to be handling this + in the same way as the normal Clang driver. It seems that `clang-cl` is just outputting the wrong results sometimes, maybe due to some register + getting clobbered? + + I'm not sure if this is a bug with dr_flac's inlined assembly (most likely), a bug in `clang-cl` or just a misunderstanding on my part with inline + assembly rules for `clang-cl`. If somebody can identify an error in dr_flac's inlined assembly I'm happy to get that fixed. + + Fortunately there is an easy workaround for this. Clang implements MSVC-specific intrinsics for compatibility. It also defines _MSC_VER for extra + compatibility. We can therefore just check for _MSC_VER and use the MSVC intrinsic which, fortunately for us, Clang supports. It would still be nice + to know how to fix the inlined assembly for correctness sake, however. + */ + +#if defined(_MSC_VER) /*&& !defined(__clang__)*/ /* <-- Intentionally wanting Clang to use the MSVC __lzcnt64/__lzcnt intrinsics due to above ^. */ + #ifdef DRFLAC_64BIT + return (drflac_uint32)__lzcnt64(x); + #else + return (drflac_uint32)__lzcnt(x); + #endif +#else + #if defined(__GNUC__) || defined(__clang__) + #if defined(DRFLAC_X64) + { + drflac_uint64 r; + __asm__ __volatile__ ( + "lzcnt{ %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" + ); + + return (drflac_uint32)r; } + #elif defined(DRFLAC_X86) + { + drflac_uint32 r; + __asm__ __volatile__ ( + "lzcnt{l %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" + ); + + return r; + } + #elif defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(__ARM_ARCH_6M__) && !(defined(__thumb__) && !defined(__thumb2__)) && !defined(DRFLAC_64BIT) /* <-- I haven't tested 64-bit inline assembly, so only enabling this for the 32-bit build for now. */ + { + unsigned int r; + __asm__ __volatile__ ( + #if defined(DRFLAC_64BIT) + "clz %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(x) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ + #else + "clz %[out], %[in]" : [out]"=r"(r) : [in]"r"(x) + #endif + ); + + return r; + } + #else + if (x == 0) { + return sizeof(x)*8; + } + #ifdef DRFLAC_64BIT + return (drflac_uint32)__builtin_clzll((drflac_uint64)x); + #else + return (drflac_uint32)__builtin_clzl((drflac_uint32)x); + #endif + #endif + #else + /* Unsupported compiler. */ + #error "This compiler does not support the lzcnt intrinsic." + #endif +#endif +} +#endif + +#ifdef DRFLAC_IMPLEMENT_CLZ_MSVC +#include /* For BitScanReverse(). */ + +static DRFLAC_INLINE drflac_uint32 drflac__clz_msvc(drflac_cache_t x) +{ + drflac_uint32 n; + + if (x == 0) { + return sizeof(x)*8; + } + +#ifdef DRFLAC_64BIT + _BitScanReverse64((unsigned long*)&n, x); +#else + _BitScanReverse((unsigned long*)&n, x); +#endif + return sizeof(x)*8 - n - 1; +} +#endif + +#ifdef DRFLAC_IMPLEMENT_CLZ_WATCOM +static __inline drflac_uint32 drflac__clz_watcom (drflac_uint32); +#ifdef DRFLAC_IMPLEMENT_CLZ_WATCOM_LZCNT +/* Use the LZCNT instruction (only available on some processors since the 2010s). */ +#pragma aux drflac__clz_watcom_lzcnt = \ + "db 0F3h, 0Fh, 0BDh, 0C0h" /* lzcnt eax, eax */ \ + parm [eax] \ + value [eax] \ + modify nomemory; +#else +/* Use the 386+-compatible implementation. */ +#pragma aux drflac__clz_watcom = \ + "bsr eax, eax" \ + "xor eax, 31" \ + parm [eax] nomemory \ + value [eax] \ + modify exact [eax] nomemory; +#endif +#endif + +static DRFLAC_INLINE drflac_uint32 drflac__clz(drflac_cache_t x) +{ +#ifdef DRFLAC_IMPLEMENT_CLZ_LZCNT + if (drflac__is_lzcnt_supported()) { + return drflac__clz_lzcnt(x); + } else +#endif + { +#ifdef DRFLAC_IMPLEMENT_CLZ_MSVC + return drflac__clz_msvc(x); +#elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM_LZCNT) + return drflac__clz_watcom_lzcnt(x); +#elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM) + return (x == 0) ? sizeof(x)*8 : drflac__clz_watcom(x); +#elif defined(__MRC__) + return __cntlzw(x); +#else + return drflac__clz_software(x); +#endif + } +} + + +static DRFLAC_INLINE drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs, unsigned int* pOffsetOut) +{ + drflac_uint32 zeroCounter = 0; + drflac_uint32 setBitOffsetPlus1; + + while (bs->cache == 0) { + zeroCounter += (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs); + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; } } - pFlac->consumedBits += setBitOffsetPlus1; - pFlac->cache <<= setBitOffsetPlus1; + if (bs->cache == 1) { + /* Not catching this would lead to undefined behaviour: a shift of a 32-bit number by 32 or more is undefined */ + *pOffsetOut = zeroCounter + (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs) - 1; + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + return DRFLAC_TRUE; + } + + setBitOffsetPlus1 = drflac__clz(bs->cache); + setBitOffsetPlus1 += 1; + + if (setBitOffsetPlus1 > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } + + bs->consumedBits += setBitOffsetPlus1; + bs->cache <<= setBitOffsetPlus1; *pOffsetOut = zeroCounter + setBitOffsetPlus1 - 1; - return true; + return DRFLAC_TRUE; } -static bool drflac__seek_to_byte(drflac* pFlac, long long offsetFromStart) +static drflac_bool32 drflac__seek_to_byte(drflac_bs* bs, drflac_uint64 offsetFromStart) { - assert(pFlac != NULL); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(offsetFromStart > 0); - long long bytesToMove = offsetFromStart - pFlac->currentBytePos; - if (bytesToMove == 0) { - return 1; - } + /* + Seeking from the start is not quite as trivial as it sounds because the onSeek callback takes a signed 32-bit integer (which + is intentional because it simplifies the implementation of the onSeek callbacks), however offsetFromStart is unsigned 64-bit. + To resolve we just need to do an initial seek from the start, and then a series of offset seeks to make up the remainder. + */ + if (offsetFromStart > 0x7FFFFFFF) { + drflac_uint64 bytesRemaining = offsetFromStart; + if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, DRFLAC_SEEK_SET)) { + return DRFLAC_FALSE; + } + bytesRemaining -= 0x7FFFFFFF; - if (bytesToMove > 0x7FFFFFFF) { - while (bytesToMove > 0x7FFFFFFF) { - if (!pFlac->onSeek(pFlac->pUserData, 0x7FFFFFFF)) { - return 0; + while (bytesRemaining > 0x7FFFFFFF) { + if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, DRFLAC_SEEK_CUR)) { + return DRFLAC_FALSE; } + bytesRemaining -= 0x7FFFFFFF; + } - pFlac->currentBytePos += 0x7FFFFFFF; - bytesToMove -= 0x7FFFFFFF; + if (bytesRemaining > 0) { + if (!bs->onSeek(bs->pUserData, (int)bytesRemaining, DRFLAC_SEEK_CUR)) { + return DRFLAC_FALSE; + } } } else { - while (bytesToMove < (int)0x80000000) { - if (!pFlac->onSeek(pFlac->pUserData, (int)0x80000000)) { - return 0; - } - - pFlac->currentBytePos += (int)0x80000000; - bytesToMove -= (int)0x80000000; + if (!bs->onSeek(bs->pUserData, (int)offsetFromStart, DRFLAC_SEEK_SET)) { + return DRFLAC_FALSE; } } - assert(bytesToMove <= 0x7FFFFFFF && bytesToMove >= (int)0x80000000); - - bool result = pFlac->onSeek(pFlac->pUserData, (int)bytesToMove); // <-- Safe cast as per the assert above. - pFlac->currentBytePos += (int)bytesToMove; - - pFlac->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS; - pFlac->cache = 0; - pFlac->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT; // <-- This clears the L2 cache. - - return result; -} - -static long long drflac__tell(drflac* pFlac) -{ - assert(pFlac != NULL); - - size_t unreadBytesFromL1 = (DRFLAC_CACHE_L1_SIZE_BYTES - (pFlac->consumedBits/8)); - size_t unreadBytesFromL2 = (DRFLAC_CACHE_L2_SIZE_BYTES - ((pFlac->nextL2Line - pFlac->unusedL2Lines)*DRFLAC_CACHE_L1_SIZE_BYTES)); - - return pFlac->currentBytePos - unreadBytesFromL1 - unreadBytesFromL2; + /* The cache should be reset to force a reload of fresh data from the client. */ + drflac__reset_cache(bs); + return DRFLAC_TRUE; } - -static bool drflac__read_utf8_coded_number(drflac* pFlac, unsigned long long* pNumberOut) +static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64* pNumberOut, drflac_uint8* pCRCOut) { - assert(pFlac != NULL); - assert(pNumberOut != NULL); + drflac_uint8 crc; + drflac_uint64 result; + drflac_uint8 utf8[7] = {0}; + int byteCount; + int i; - // We should never need to read UTF-8 data while not being aligned to a byte boundary. Therefore we can grab the data - // directly from the input stream rather than using drflac__read_uint8(). - assert((pFlac->consumedBits & 7) == 0); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pNumberOut != NULL); + DRFLAC_ASSERT(pCRCOut != NULL); - unsigned char utf8[7] = {0}; - if (!drflac__read_uint8(pFlac, 8, utf8)) { + crc = *pCRCOut; + + if (!drflac__read_uint8(bs, 8, utf8)) { *pNumberOut = 0; - return false; + return DRFLAC_AT_END; } + crc = drflac_crc8(crc, utf8[0], 8); if ((utf8[0] & 0x80) == 0) { *pNumberOut = utf8[0]; - return true; + *pCRCOut = crc; + return DRFLAC_SUCCESS; } - int byteCount = 1; + /*byteCount = 1;*/ if ((utf8[0] & 0xE0) == 0xC0) { byteCount = 2; } else if ((utf8[0] & 0xF0) == 0xE0) { @@ -1029,212 +2956,67 @@ static bool drflac__read_utf8_coded_number(drflac* pFlac, unsigned long long* pN byteCount = 7; } else { *pNumberOut = 0; - return false; // Bad UTF-8 encoding. + return DRFLAC_CRC_MISMATCH; /* Bad UTF-8 encoding. */ } - // Read extra bytes. - assert(byteCount > 1); + /* Read extra bytes. */ + DRFLAC_ASSERT(byteCount > 1); - unsigned long long result = ((long long)(utf8[0] & (0xFF >> (byteCount + 1)))); - for (int i = 1; i < byteCount; ++i) { - if (!drflac__read_uint8(pFlac, 8, utf8 + i)) { + result = (drflac_uint64)(utf8[0] & (0xFF >> (byteCount + 1))); + for (i = 1; i < byteCount; ++i) { + if (!drflac__read_uint8(bs, 8, utf8 + i)) { *pNumberOut = 0; - return false; + return DRFLAC_AT_END; } + crc = drflac_crc8(crc, utf8[i], 8); result = (result << 6) | (utf8[i] & 0x3F); } *pNumberOut = result; - return true; + *pCRCOut = crc; + return DRFLAC_SUCCESS; } - -static DRFLAC_INLINE bool drflac__read_and_seek_rice(drflac* pFlac, unsigned char m) +static DRFLAC_INLINE drflac_uint32 drflac__ilog2_u32(drflac_uint32 x) { - unsigned int unused; - if (!drflac__seek_past_next_set_bit(pFlac, &unused)) { - return false; +#if 1 /* Needs optimizing. */ + drflac_uint32 result = 0; + while (x > 0) { + result += 1; + x >>= 1; } - if (m > 0) { - if (!drflac__seek_bits(pFlac, m)) { - return false; - } - } - - return true; -} - - -// The next two functions are responsible for calculating the prediction. -// -// When the bits per sample is >16 we need to use 64-bit integer arithmetic because otherwise we'll run out of precision. It's -// safe to assume this will be slower on 32-bit platforms so we use a more optimal solution when the bits per sample is <=16. -// -// -// Optimization Experiment #1 -// -// The first optimization experiment I'm trying here is a loop unroll for the most common LPC orders. I've done a little test -// and the results are as follows, in order of most common: -// 1) order = 8 : 93.1M -// 2) order = 7 : 36.6M -// 3) order = 3 : 33.2M -// 4) order = 6 : 20.9M -// 5) order = 5 : 18.1M -// 6) order = 4 : 15.8M -// 7) order = 12 : 10.8M -// 8) order = 2 : 9.8M -// 9) order = 1 : 1.6M -// 10) order = 10 : 1.0M -// 11) order = 9 : 0.8M -// 12) order = 11 : 0.8M -// -// We'll experiment with unrolling the top 8 most common ones. We'll ignore the least common ones since there seems to be a -// large drop off there. -// -// Result: There's a tiny improvement in some cases, but it could just be within margin of error so unsure if it's worthwhile -// just yet. -static DRFLAC_INLINE int32_t drflac__calculate_prediction_32(unsigned int order, int shift, const short* coefficients, int32_t* pDecodedSamples) -{ - assert(order <= 32); - - // 32-bit version. - - // This method is slower on both 32- and 64-bit builds with VC++. Leaving this here for now just in case we need it later - // for whatever reason. -#if 0 - int prediction; - if (order == 8) - { - prediction = coefficients[0] * pDecodedSamples[-1]; - prediction += coefficients[1] * pDecodedSamples[-2]; - prediction += coefficients[2] * pDecodedSamples[-3]; - prediction += coefficients[3] * pDecodedSamples[-4]; - prediction += coefficients[4] * pDecodedSamples[-5]; - prediction += coefficients[5] * pDecodedSamples[-6]; - prediction += coefficients[6] * pDecodedSamples[-7]; - prediction += coefficients[7] * pDecodedSamples[-8]; - } - else if (order == 7) - { - prediction = coefficients[0] * pDecodedSamples[-1]; - prediction += coefficients[1] * pDecodedSamples[-2]; - prediction += coefficients[2] * pDecodedSamples[-3]; - prediction += coefficients[3] * pDecodedSamples[-4]; - prediction += coefficients[4] * pDecodedSamples[-5]; - prediction += coefficients[5] * pDecodedSamples[-6]; - prediction += coefficients[6] * pDecodedSamples[-7]; - } - else if (order == 3) - { - prediction = coefficients[0] * pDecodedSamples[-1]; - prediction += coefficients[1] * pDecodedSamples[-2]; - prediction += coefficients[2] * pDecodedSamples[-3]; - } - else if (order == 6) - { - prediction = coefficients[0] * pDecodedSamples[-1]; - prediction += coefficients[1] * pDecodedSamples[-2]; - prediction += coefficients[2] * pDecodedSamples[-3]; - prediction += coefficients[3] * pDecodedSamples[-4]; - prediction += coefficients[4] * pDecodedSamples[-5]; - prediction += coefficients[5] * pDecodedSamples[-6]; - } - else if (order == 5) - { - prediction = coefficients[0] * pDecodedSamples[-1]; - prediction += coefficients[1] * pDecodedSamples[-2]; - prediction += coefficients[2] * pDecodedSamples[-3]; - prediction += coefficients[3] * pDecodedSamples[-4]; - prediction += coefficients[4] * pDecodedSamples[-5]; - } - else if (order == 4) - { - prediction = coefficients[0] * pDecodedSamples[-1]; - prediction += coefficients[1] * pDecodedSamples[-2]; - prediction += coefficients[2] * pDecodedSamples[-3]; - prediction += coefficients[3] * pDecodedSamples[-4]; - } - else if (order == 12) - { - prediction = coefficients[0] * pDecodedSamples[-1]; - prediction += coefficients[1] * pDecodedSamples[-2]; - prediction += coefficients[2] * pDecodedSamples[-3]; - prediction += coefficients[3] * pDecodedSamples[-4]; - prediction += coefficients[4] * pDecodedSamples[-5]; - prediction += coefficients[5] * pDecodedSamples[-6]; - prediction += coefficients[6] * pDecodedSamples[-7]; - prediction += coefficients[7] * pDecodedSamples[-8]; - prediction += coefficients[8] * pDecodedSamples[-9]; - prediction += coefficients[9] * pDecodedSamples[-10]; - prediction += coefficients[10] * pDecodedSamples[-11]; - prediction += coefficients[11] * pDecodedSamples[-12]; - } - else if (order == 2) - { - prediction = coefficients[0] * pDecodedSamples[-1]; - prediction += coefficients[1] * pDecodedSamples[-2]; - } - else if (order == 1) - { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; - } - else if (order == 10) - { - prediction = coefficients[0] * pDecodedSamples[-1]; - prediction += coefficients[1] * pDecodedSamples[-2]; - prediction += coefficients[2] * pDecodedSamples[-3]; - prediction += coefficients[3] * pDecodedSamples[-4]; - prediction += coefficients[4] * pDecodedSamples[-5]; - prediction += coefficients[5] * pDecodedSamples[-6]; - prediction += coefficients[6] * pDecodedSamples[-7]; - prediction += coefficients[7] * pDecodedSamples[-8]; - prediction += coefficients[8] * pDecodedSamples[-9]; - prediction += coefficients[9] * pDecodedSamples[-10]; - } - else if (order == 9) - { - prediction = coefficients[0] * pDecodedSamples[-1]; - prediction += coefficients[1] * pDecodedSamples[-2]; - prediction += coefficients[2] * pDecodedSamples[-3]; - prediction += coefficients[3] * pDecodedSamples[-4]; - prediction += coefficients[4] * pDecodedSamples[-5]; - prediction += coefficients[5] * pDecodedSamples[-6]; - prediction += coefficients[6] * pDecodedSamples[-7]; - prediction += coefficients[7] * pDecodedSamples[-8]; - prediction += coefficients[8] * pDecodedSamples[-9]; - } - else if (order == 11) - { - prediction = coefficients[0] * pDecodedSamples[-1]; - prediction += coefficients[1] * pDecodedSamples[-2]; - prediction += coefficients[2] * pDecodedSamples[-3]; - prediction += coefficients[3] * pDecodedSamples[-4]; - prediction += coefficients[4] * pDecodedSamples[-5]; - prediction += coefficients[5] * pDecodedSamples[-6]; - prediction += coefficients[6] * pDecodedSamples[-7]; - prediction += coefficients[7] * pDecodedSamples[-8]; - prediction += coefficients[8] * pDecodedSamples[-9]; - prediction += coefficients[9] * pDecodedSamples[-10]; - prediction += coefficients[10] * pDecodedSamples[-11]; - } - else - { - prediction = 0; - for (int j = 0; j < (int)order; ++j) { - prediction += coefficients[j] * pDecodedSamples[-j-1]; - } - } + return result; #endif +} - // Experiment #2. See if we can use a switch and let the compiler optimize it to a jump table. - // Result: VC++ definitely optimizes this to a single jmp as expected. I expect other compilers should do the same, but I've - // not verified yet. -#if 1 - int prediction = 0; +static DRFLAC_INLINE drflac_bool32 drflac__use_64_bit_prediction(drflac_uint32 bitsPerSample, drflac_uint32 order, drflac_uint32 precision) +{ + /* https://web.archive.org/web/20220205005724/https://github.com/ietf-wg-cellar/flac-specification/blob/37a49aa48ba4ba12e8757badfc59c0df35435fec/rfc_backmatter.md */ + return bitsPerSample + precision + drflac__ilog2_u32(order) > 32; +} + +/* +The next two functions are responsible for calculating the prediction. + +When the bits per sample is >16 we need to use 64-bit integer arithmetic because otherwise we'll run out of precision. It's +safe to assume this will be slower on 32-bit platforms so we use a more optimal solution when the bits per sample is <=16. +*/ +#if defined(__clang__) +__attribute__((no_sanitize("signed-integer-overflow"))) +#endif +static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) +{ + drflac_int32 prediction = 0; + + DRFLAC_ASSERT(order <= 32); + + /* 32-bit version. */ + + /* VC++ optimizes this to a single jmp. I've not yet verified this for other compilers. */ switch (order) { case 32: prediction += coefficients[31] * pDecodedSamples[-32]; @@ -1270,472 +3052,1942 @@ static DRFLAC_INLINE int32_t drflac__calculate_prediction_32(unsigned int order, case 2: prediction += coefficients[ 1] * pDecodedSamples[- 2]; case 1: prediction += coefficients[ 0] * pDecodedSamples[- 1]; } -#endif - return (int32_t)(prediction >> shift); + return (drflac_int32)(prediction >> shift); } -static DRFLAC_INLINE int32_t drflac__calculate_prediction(unsigned int order, int shift, const short* coefficients, int32_t* pDecodedSamples) +static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { - assert(order <= 32); + drflac_int64 prediction; - // 64-bit version. + DRFLAC_ASSERT(order <= 32); - // This method is faster on the 32-bit build when compiling with VC++. See note below. + /* 64-bit version. */ + + /* This method is faster on the 32-bit build when compiling with VC++. See note below. */ #ifndef DRFLAC_64BIT - long long prediction; if (order == 8) { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; - prediction += (long long)coefficients[1] * (long long)pDecodedSamples[-2]; - prediction += (long long)coefficients[2] * (long long)pDecodedSamples[-3]; - prediction += (long long)coefficients[3] * (long long)pDecodedSamples[-4]; - prediction += (long long)coefficients[4] * (long long)pDecodedSamples[-5]; - prediction += (long long)coefficients[5] * (long long)pDecodedSamples[-6]; - prediction += (long long)coefficients[6] * (long long)pDecodedSamples[-7]; - prediction += (long long)coefficients[7] * (long long)pDecodedSamples[-8]; + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; } else if (order == 7) { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; - prediction += (long long)coefficients[1] * (long long)pDecodedSamples[-2]; - prediction += (long long)coefficients[2] * (long long)pDecodedSamples[-3]; - prediction += (long long)coefficients[3] * (long long)pDecodedSamples[-4]; - prediction += (long long)coefficients[4] * (long long)pDecodedSamples[-5]; - prediction += (long long)coefficients[5] * (long long)pDecodedSamples[-6]; - prediction += (long long)coefficients[6] * (long long)pDecodedSamples[-7]; + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; } else if (order == 3) { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; - prediction += (long long)coefficients[1] * (long long)pDecodedSamples[-2]; - prediction += (long long)coefficients[2] * (long long)pDecodedSamples[-3]; + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; } else if (order == 6) { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; - prediction += (long long)coefficients[1] * (long long)pDecodedSamples[-2]; - prediction += (long long)coefficients[2] * (long long)pDecodedSamples[-3]; - prediction += (long long)coefficients[3] * (long long)pDecodedSamples[-4]; - prediction += (long long)coefficients[4] * (long long)pDecodedSamples[-5]; - prediction += (long long)coefficients[5] * (long long)pDecodedSamples[-6]; + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; } else if (order == 5) { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; - prediction += (long long)coefficients[1] * (long long)pDecodedSamples[-2]; - prediction += (long long)coefficients[2] * (long long)pDecodedSamples[-3]; - prediction += (long long)coefficients[3] * (long long)pDecodedSamples[-4]; - prediction += (long long)coefficients[4] * (long long)pDecodedSamples[-5]; + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; } else if (order == 4) { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; - prediction += (long long)coefficients[1] * (long long)pDecodedSamples[-2]; - prediction += (long long)coefficients[2] * (long long)pDecodedSamples[-3]; - prediction += (long long)coefficients[3] * (long long)pDecodedSamples[-4]; + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; } else if (order == 12) { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; - prediction += (long long)coefficients[1] * (long long)pDecodedSamples[-2]; - prediction += (long long)coefficients[2] * (long long)pDecodedSamples[-3]; - prediction += (long long)coefficients[3] * (long long)pDecodedSamples[-4]; - prediction += (long long)coefficients[4] * (long long)pDecodedSamples[-5]; - prediction += (long long)coefficients[5] * (long long)pDecodedSamples[-6]; - prediction += (long long)coefficients[6] * (long long)pDecodedSamples[-7]; - prediction += (long long)coefficients[7] * (long long)pDecodedSamples[-8]; - prediction += (long long)coefficients[8] * (long long)pDecodedSamples[-9]; - prediction += (long long)coefficients[9] * (long long)pDecodedSamples[-10]; - prediction += (long long)coefficients[10] * (long long)pDecodedSamples[-11]; - prediction += (long long)coefficients[11] * (long long)pDecodedSamples[-12]; + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; + prediction += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; + prediction += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; + prediction += coefficients[11] * (drflac_int64)pDecodedSamples[-12]; } else if (order == 2) { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; - prediction += (long long)coefficients[1] * (long long)pDecodedSamples[-2]; + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; } else if (order == 1) { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; } else if (order == 10) { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; - prediction += (long long)coefficients[1] * (long long)pDecodedSamples[-2]; - prediction += (long long)coefficients[2] * (long long)pDecodedSamples[-3]; - prediction += (long long)coefficients[3] * (long long)pDecodedSamples[-4]; - prediction += (long long)coefficients[4] * (long long)pDecodedSamples[-5]; - prediction += (long long)coefficients[5] * (long long)pDecodedSamples[-6]; - prediction += (long long)coefficients[6] * (long long)pDecodedSamples[-7]; - prediction += (long long)coefficients[7] * (long long)pDecodedSamples[-8]; - prediction += (long long)coefficients[8] * (long long)pDecodedSamples[-9]; - prediction += (long long)coefficients[9] * (long long)pDecodedSamples[-10]; + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; + prediction += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; } else if (order == 9) { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; - prediction += (long long)coefficients[1] * (long long)pDecodedSamples[-2]; - prediction += (long long)coefficients[2] * (long long)pDecodedSamples[-3]; - prediction += (long long)coefficients[3] * (long long)pDecodedSamples[-4]; - prediction += (long long)coefficients[4] * (long long)pDecodedSamples[-5]; - prediction += (long long)coefficients[5] * (long long)pDecodedSamples[-6]; - prediction += (long long)coefficients[6] * (long long)pDecodedSamples[-7]; - prediction += (long long)coefficients[7] * (long long)pDecodedSamples[-8]; - prediction += (long long)coefficients[8] * (long long)pDecodedSamples[-9]; + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; } else if (order == 11) { - prediction = (long long)coefficients[0] * (long long)pDecodedSamples[-1]; - prediction += (long long)coefficients[1] * (long long)pDecodedSamples[-2]; - prediction += (long long)coefficients[2] * (long long)pDecodedSamples[-3]; - prediction += (long long)coefficients[3] * (long long)pDecodedSamples[-4]; - prediction += (long long)coefficients[4] * (long long)pDecodedSamples[-5]; - prediction += (long long)coefficients[5] * (long long)pDecodedSamples[-6]; - prediction += (long long)coefficients[6] * (long long)pDecodedSamples[-7]; - prediction += (long long)coefficients[7] * (long long)pDecodedSamples[-8]; - prediction += (long long)coefficients[8] * (long long)pDecodedSamples[-9]; - prediction += (long long)coefficients[9] * (long long)pDecodedSamples[-10]; - prediction += (long long)coefficients[10] * (long long)pDecodedSamples[-11]; + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; + prediction += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; + prediction += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; } else { + int j; + prediction = 0; - for (int j = 0; j < (int)order; ++j) { - prediction += (long long)coefficients[j] * (long long)pDecodedSamples[-j-1]; + for (j = 0; j < (int)order; ++j) { + prediction += coefficients[j] * (drflac_int64)pDecodedSamples[-j-1]; } } #endif - // Experiment #2. See if we can use a switch and let the compiler optimize it to a single jmp instruction. - // Result: VC++ optimizes this to a single jmp on the 64-bit build, but for some reason the 32-bit version compiles to less efficient - // code. Thus, we use this version on the 64-bit build and the uglier version above for the 32-bit build. If anyone has an idea on how - // I can get VC++ to generate an efficient jump table for the 32-bit build let me know. + /* + VC++ optimizes this to a single jmp instruction, but only the 64-bit build. The 32-bit build generates less efficient code for some + reason. The ugly version above is faster so we'll just switch between the two depending on the target platform. + */ #ifdef DRFLAC_64BIT - long long prediction = 0; - + prediction = 0; switch (order) { - case 32: prediction += (long long)coefficients[31] * (long long)pDecodedSamples[-32]; - case 31: prediction += (long long)coefficients[30] * (long long)pDecodedSamples[-31]; - case 30: prediction += (long long)coefficients[29] * (long long)pDecodedSamples[-30]; - case 29: prediction += (long long)coefficients[28] * (long long)pDecodedSamples[-29]; - case 28: prediction += (long long)coefficients[27] * (long long)pDecodedSamples[-28]; - case 27: prediction += (long long)coefficients[26] * (long long)pDecodedSamples[-27]; - case 26: prediction += (long long)coefficients[25] * (long long)pDecodedSamples[-26]; - case 25: prediction += (long long)coefficients[24] * (long long)pDecodedSamples[-25]; - case 24: prediction += (long long)coefficients[23] * (long long)pDecodedSamples[-24]; - case 23: prediction += (long long)coefficients[22] * (long long)pDecodedSamples[-23]; - case 22: prediction += (long long)coefficients[21] * (long long)pDecodedSamples[-22]; - case 21: prediction += (long long)coefficients[20] * (long long)pDecodedSamples[-21]; - case 20: prediction += (long long)coefficients[19] * (long long)pDecodedSamples[-20]; - case 19: prediction += (long long)coefficients[18] * (long long)pDecodedSamples[-19]; - case 18: prediction += (long long)coefficients[17] * (long long)pDecodedSamples[-18]; - case 17: prediction += (long long)coefficients[16] * (long long)pDecodedSamples[-17]; - case 16: prediction += (long long)coefficients[15] * (long long)pDecodedSamples[-16]; - case 15: prediction += (long long)coefficients[14] * (long long)pDecodedSamples[-15]; - case 14: prediction += (long long)coefficients[13] * (long long)pDecodedSamples[-14]; - case 13: prediction += (long long)coefficients[12] * (long long)pDecodedSamples[-13]; - case 12: prediction += (long long)coefficients[11] * (long long)pDecodedSamples[-12]; - case 11: prediction += (long long)coefficients[10] * (long long)pDecodedSamples[-11]; - case 10: prediction += (long long)coefficients[ 9] * (long long)pDecodedSamples[-10]; - case 9: prediction += (long long)coefficients[ 8] * (long long)pDecodedSamples[- 9]; - case 8: prediction += (long long)coefficients[ 7] * (long long)pDecodedSamples[- 8]; - case 7: prediction += (long long)coefficients[ 6] * (long long)pDecodedSamples[- 7]; - case 6: prediction += (long long)coefficients[ 5] * (long long)pDecodedSamples[- 6]; - case 5: prediction += (long long)coefficients[ 4] * (long long)pDecodedSamples[- 5]; - case 4: prediction += (long long)coefficients[ 3] * (long long)pDecodedSamples[- 4]; - case 3: prediction += (long long)coefficients[ 2] * (long long)pDecodedSamples[- 3]; - case 2: prediction += (long long)coefficients[ 1] * (long long)pDecodedSamples[- 2]; - case 1: prediction += (long long)coefficients[ 0] * (long long)pDecodedSamples[- 1]; + case 32: prediction += coefficients[31] * (drflac_int64)pDecodedSamples[-32]; + case 31: prediction += coefficients[30] * (drflac_int64)pDecodedSamples[-31]; + case 30: prediction += coefficients[29] * (drflac_int64)pDecodedSamples[-30]; + case 29: prediction += coefficients[28] * (drflac_int64)pDecodedSamples[-29]; + case 28: prediction += coefficients[27] * (drflac_int64)pDecodedSamples[-28]; + case 27: prediction += coefficients[26] * (drflac_int64)pDecodedSamples[-27]; + case 26: prediction += coefficients[25] * (drflac_int64)pDecodedSamples[-26]; + case 25: prediction += coefficients[24] * (drflac_int64)pDecodedSamples[-25]; + case 24: prediction += coefficients[23] * (drflac_int64)pDecodedSamples[-24]; + case 23: prediction += coefficients[22] * (drflac_int64)pDecodedSamples[-23]; + case 22: prediction += coefficients[21] * (drflac_int64)pDecodedSamples[-22]; + case 21: prediction += coefficients[20] * (drflac_int64)pDecodedSamples[-21]; + case 20: prediction += coefficients[19] * (drflac_int64)pDecodedSamples[-20]; + case 19: prediction += coefficients[18] * (drflac_int64)pDecodedSamples[-19]; + case 18: prediction += coefficients[17] * (drflac_int64)pDecodedSamples[-18]; + case 17: prediction += coefficients[16] * (drflac_int64)pDecodedSamples[-17]; + case 16: prediction += coefficients[15] * (drflac_int64)pDecodedSamples[-16]; + case 15: prediction += coefficients[14] * (drflac_int64)pDecodedSamples[-15]; + case 14: prediction += coefficients[13] * (drflac_int64)pDecodedSamples[-14]; + case 13: prediction += coefficients[12] * (drflac_int64)pDecodedSamples[-13]; + case 12: prediction += coefficients[11] * (drflac_int64)pDecodedSamples[-12]; + case 11: prediction += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; + case 10: prediction += coefficients[ 9] * (drflac_int64)pDecodedSamples[-10]; + case 9: prediction += coefficients[ 8] * (drflac_int64)pDecodedSamples[- 9]; + case 8: prediction += coefficients[ 7] * (drflac_int64)pDecodedSamples[- 8]; + case 7: prediction += coefficients[ 6] * (drflac_int64)pDecodedSamples[- 7]; + case 6: prediction += coefficients[ 5] * (drflac_int64)pDecodedSamples[- 6]; + case 5: prediction += coefficients[ 4] * (drflac_int64)pDecodedSamples[- 5]; + case 4: prediction += coefficients[ 3] * (drflac_int64)pDecodedSamples[- 4]; + case 3: prediction += coefficients[ 2] * (drflac_int64)pDecodedSamples[- 3]; + case 2: prediction += coefficients[ 1] * (drflac_int64)pDecodedSamples[- 2]; + case 1: prediction += coefficients[ 0] * (drflac_int64)pDecodedSamples[- 1]; } #endif - return (int32_t)(prediction >> shift); + return (drflac_int32)(prediction >> shift); } -// Reads and decodes a string of residual values as Rice codes. The decoder should be sitting on the first bit of the Rice codes. -// -// This is the most frequently called function in the library. It does both the Rice decoding and the prediction in a single loop -// iteration. -static bool drflac__decode_samples_with_residual__rice(drflac* pFlac, unsigned int count, unsigned char riceParam, unsigned int order, int shift, const short* coefficients, int* pSamplesOut) +#if 0 +/* +Reference implementation for reading and decoding samples with residual. This is intentionally left unoptimized for the +sake of readability and should only be used as a reference. +*/ +static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { - assert(pFlac != NULL); - assert(count > 0); - assert(pSamplesOut != NULL); + drflac_uint32 i; - static unsigned int bitOffsetTable[] = { - 0, - 4, - 3, 3, - 2, 2, 2, 2, - 1, 1, 1, 1, 1, 1, 1, 1 - }; + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); - drflac_cache_t riceParamMask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParam); - drflac_cache_t resultHiShift = DRFLAC_CACHE_L1_SIZE_BITS - riceParam; + for (i = 0; i < count; ++i) { + drflac_uint32 zeroCounter = 0; + for (;;) { + drflac_uint8 bit; + if (!drflac__read_uint8(bs, 1, &bit)) { + return DRFLAC_FALSE; + } - for (int i = 0; i < (int)count; ++i) - { - unsigned int zeroCounter = 0; - while (pFlac->cache == 0) { - zeroCounter += (unsigned int)DRFLAC_CACHE_L1_BITS_REMAINING; - if (!drflac__reload_cache(pFlac)) { - return false; + if (bit == 0) { + zeroCounter += 1; + } else { + break; } } - // At this point the cache should not be zero, in which case we know the first set bit should be somewhere in here. There is - // no need for us to perform any cache reloading logic here which should make things much faster. - assert(pFlac->cache != 0); - unsigned int decodedRice; - - unsigned int setBitOffsetPlus1 = bitOffsetTable[DRFLAC_CACHE_L1_SELECT_AND_SHIFT(4)]; - if (setBitOffsetPlus1 > 0) { - decodedRice = (zeroCounter + (setBitOffsetPlus1-1)) << riceParam; + drflac_uint32 decodedRice; + if (riceParam > 0) { + if (!drflac__read_uint32(bs, riceParam, &decodedRice)) { + return DRFLAC_FALSE; + } } else { - if (pFlac->cache == 1) { - setBitOffsetPlus1 = DRFLAC_CACHE_L1_SIZE_BITS; - decodedRice = (zeroCounter + (DRFLAC_CACHE_L1_SIZE_BITS-1)) << riceParam; - } else { - setBitOffsetPlus1 = 5; - for (;;) - { - if ((pFlac->cache & DRFLAC_CACHE_L1_SELECT(setBitOffsetPlus1))) { - decodedRice = (zeroCounter + (setBitOffsetPlus1-1)) << riceParam; - break; - } - - setBitOffsetPlus1 += 1; - } - } + decodedRice = 0; } - - unsigned int bitsLo = 0; - unsigned int riceLength = setBitOffsetPlus1 + riceParam; - if (riceLength < DRFLAC_CACHE_L1_BITS_REMAINING) - { - bitsLo = (unsigned int)((pFlac->cache & (riceParamMask >> setBitOffsetPlus1)) >> (DRFLAC_CACHE_L1_SIZE_BITS - riceLength)); - - pFlac->consumedBits += riceLength; - pFlac->cache <<= riceLength; - } - else - { - pFlac->consumedBits += riceLength; - pFlac->cache <<= setBitOffsetPlus1; - - // It straddles the cached data. It will never cover more than the next chunk. We just read the number in two parts and combine them. - size_t bitCountLo = pFlac->consumedBits - DRFLAC_CACHE_L1_SIZE_BITS; - drflac_cache_t resultHi = pFlac->cache & riceParamMask; // <-- This mask is OK because all bits after the first bits are always zero. - - - if (pFlac->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT) { - pFlac->cache = drflac__be2host__cache_line(pFlac->cacheL2[pFlac->nextL2Line++]); - } else { - // Slow path. We need to fetch more data from the client. - if (!drflac__reload_cache(pFlac)) { - return false; - } - } - - bitsLo = (unsigned int)((resultHi >> resultHiShift) | DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bitCountLo)); - pFlac->consumedBits = bitCountLo; - pFlac->cache <<= bitCountLo; - } - - - decodedRice |= bitsLo; + decodedRice |= (zeroCounter << riceParam); if ((decodedRice & 0x01)) { decodedRice = ~(decodedRice >> 1); } else { - decodedRice = (decodedRice >> 1); + decodedRice = (decodedRice >> 1); } - // In order to properly calculate the prediction when the bits per sample is >16 we need to do it using 64-bit arithmetic. We can assume this - // is probably going to be slower on 32-bit systems so we'll do a more optimized 32-bit version when the bits per sample is low enough. - if (pFlac->currentFrame.bitsPerSample > 16) { - pSamplesOut[i] = ((int)decodedRice + drflac__calculate_prediction(order, shift, coefficients, pSamplesOut + i)); + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } else { - pSamplesOut[i] = ((int)decodedRice + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i)); + pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } } - return true; + return DRFLAC_TRUE; } +#endif - -// Reads and seeks past a string of residual values as Rice codes. The decoder should be sitting on the first bit of the Rice codes. -static bool drflac__read_and_seek_residual__rice(drflac* pFlac, unsigned int count, unsigned char riceParam) +#if 0 +static drflac_bool32 drflac__read_rice_parts__reference(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) { - assert(pFlac != NULL); - assert(count > 0); + drflac_uint32 zeroCounter = 0; + drflac_uint32 decodedRice; - for (unsigned int i = 0; i < count; ++i) { - if (!drflac__read_and_seek_rice(pFlac, riceParam)) { - return false; + for (;;) { + drflac_uint8 bit; + if (!drflac__read_uint8(bs, 1, &bit)) { + return DRFLAC_FALSE; + } + + if (bit == 0) { + zeroCounter += 1; + } else { + break; } } - return true; + if (riceParam > 0) { + if (!drflac__read_uint32(bs, riceParam, &decodedRice)) { + return DRFLAC_FALSE; + } + } else { + decodedRice = 0; + } + + *pZeroCounterOut = zeroCounter; + *pRiceParamPartOut = decodedRice; + return DRFLAC_TRUE; +} +#endif + +#if 0 +static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) +{ + drflac_cache_t riceParamMask; + drflac_uint32 zeroCounter; + drflac_uint32 setBitOffsetPlus1; + drflac_uint32 riceParamPart; + drflac_uint32 riceLength; + + DRFLAC_ASSERT(riceParam > 0); /* <-- riceParam should never be 0. drflac__read_rice_parts__param_equals_zero() should be used instead for this case. */ + + riceParamMask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParam); + + zeroCounter = 0; + while (bs->cache == 0) { + zeroCounter += (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs); + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + } + + setBitOffsetPlus1 = drflac__clz(bs->cache); + zeroCounter += setBitOffsetPlus1; + setBitOffsetPlus1 += 1; + + riceLength = setBitOffsetPlus1 + riceParam; + if (riceLength < DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + riceParamPart = (drflac_uint32)((bs->cache & (riceParamMask >> setBitOffsetPlus1)) >> DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceLength)); + + bs->consumedBits += riceLength; + bs->cache <<= riceLength; + } else { + drflac_uint32 bitCountLo; + drflac_cache_t resultHi; + + bs->consumedBits += riceLength; + bs->cache <<= setBitOffsetPlus1 & (DRFLAC_CACHE_L1_SIZE_BITS(bs)-1); /* <-- Equivalent to "if (setBitOffsetPlus1 < DRFLAC_CACHE_L1_SIZE_BITS(bs)) { bs->cache <<= setBitOffsetPlus1; }" */ + + /* It straddles the cached data. It will never cover more than the next chunk. We just read the number in two parts and combine them. */ + bitCountLo = bs->consumedBits - DRFLAC_CACHE_L1_SIZE_BITS(bs); + resultHi = DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, riceParam); /* <-- Use DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE() if ever this function allows riceParam=0. */ + + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { +#ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); +#endif + bs->cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs->consumedBits = 0; +#ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs->cache; +#endif + } else { + /* Slow path. We need to fetch more data from the client. */ + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } + } + + riceParamPart = (drflac_uint32)(resultHi | DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, bitCountLo)); + + bs->consumedBits += bitCountLo; + bs->cache <<= bitCountLo; + } + + pZeroCounterOut[0] = zeroCounter; + pRiceParamPartOut[0] = riceParamPart; + + return DRFLAC_TRUE; +} +#endif + +static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x1(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) +{ + drflac_uint32 riceParamPlus1 = riceParam + 1; + /*drflac_cache_t riceParamPlus1Mask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParamPlus1);*/ + drflac_uint32 riceParamPlus1Shift = DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceParamPlus1); + drflac_uint32 riceParamPlus1MaxConsumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs) - riceParamPlus1; + + /* + The idea here is to use local variables for the cache in an attempt to encourage the compiler to store them in registers. I have + no idea how this will work in practice... + */ + drflac_cache_t bs_cache = bs->cache; + drflac_uint32 bs_consumedBits = bs->consumedBits; + + /* The first thing to do is find the first unset bit. Most likely a bit will be set in the current cache line. */ + drflac_uint32 lzcount = drflac__clz(bs_cache); + if (lzcount < sizeof(bs_cache)*8) { + pZeroCounterOut[0] = lzcount; + + /* + It is most likely that the riceParam part (which comes after the zero counter) is also on this cache line. When extracting + this, we include the set bit from the unary coded part because it simplifies cache management. This bit will be handled + outside of this function at a higher level. + */ + extract_rice_param_part: + bs_cache <<= lzcount; + bs_consumedBits += lzcount; + + if (bs_consumedBits <= riceParamPlus1MaxConsumedBits) { + /* Getting here means the rice parameter part is wholly contained within the current cache line. */ + pRiceParamPartOut[0] = (drflac_uint32)(bs_cache >> riceParamPlus1Shift); + bs_cache <<= riceParamPlus1; + bs_consumedBits += riceParamPlus1; + } else { + drflac_uint32 riceParamPartHi; + drflac_uint32 riceParamPartLo; + drflac_uint32 riceParamPartLoBitCount; + + /* + Getting here means the rice parameter part straddles the cache line. We need to read from the tail of the current cache + line, reload the cache, and then combine it with the head of the next cache line. + */ + + /* Grab the high part of the rice parameter part. */ + riceParamPartHi = (drflac_uint32)(bs_cache >> riceParamPlus1Shift); + + /* Before reloading the cache we need to grab the size in bits of the low part. */ + riceParamPartLoBitCount = bs_consumedBits - riceParamPlus1MaxConsumedBits; + DRFLAC_ASSERT(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); + + /* Now reload the cache. */ + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = riceParamPartLoBitCount; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + /* Slow path. We need to fetch more data from the client. */ + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; + } + + /* We should now have enough information to construct the rice parameter part. */ + riceParamPartLo = (drflac_uint32)(bs_cache >> (DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceParamPartLoBitCount))); + pRiceParamPartOut[0] = riceParamPartHi | riceParamPartLo; + + bs_cache <<= riceParamPartLoBitCount; + } + } else { + /* + Getting here means there are no bits set on the cache line. This is a less optimal case because we just wasted a call + to drflac__clz() and we need to reload the cache. + */ + drflac_uint32 zeroCounter = (drflac_uint32)(DRFLAC_CACHE_L1_SIZE_BITS(bs) - bs_consumedBits); + for (;;) { + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = 0; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + /* Slow path. We need to fetch more data from the client. */ + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits; + } + + lzcount = drflac__clz(bs_cache); + zeroCounter += lzcount; + + if (lzcount < sizeof(bs_cache)*8) { + break; + } + } + + pZeroCounterOut[0] = zeroCounter; + goto extract_rice_param_part; + } + + /* Make sure the cache is restored at the end of it all. */ + bs->cache = bs_cache; + bs->consumedBits = bs_consumedBits; + + return DRFLAC_TRUE; } -static bool drflac__decode_samples_with_residual__unencoded(drflac* pFlac, unsigned int count, unsigned char unencodedBitsPerSample, unsigned int order, int shift, const short* coefficients, int* pSamplesOut) +static DRFLAC_INLINE drflac_bool32 drflac__seek_rice_parts(drflac_bs* bs, drflac_uint8 riceParam) { - assert(pFlac != NULL); - assert(count > 0); - assert(unencodedBitsPerSample > 0 && unencodedBitsPerSample <= 32); - assert(pSamplesOut != NULL); + drflac_uint32 riceParamPlus1 = riceParam + 1; + drflac_uint32 riceParamPlus1MaxConsumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs) - riceParamPlus1; - for (unsigned int i = 0; i < count; ++i) + /* + The idea here is to use local variables for the cache in an attempt to encourage the compiler to store them in registers. I have + no idea how this will work in practice... + */ + drflac_cache_t bs_cache = bs->cache; + drflac_uint32 bs_consumedBits = bs->consumedBits; + + /* The first thing to do is find the first unset bit. Most likely a bit will be set in the current cache line. */ + drflac_uint32 lzcount = drflac__clz(bs_cache); + if (lzcount < sizeof(bs_cache)*8) { + /* + It is most likely that the riceParam part (which comes after the zero counter) is also on this cache line. When extracting + this, we include the set bit from the unary coded part because it simplifies cache management. This bit will be handled + outside of this function at a higher level. + */ + extract_rice_param_part: + bs_cache <<= lzcount; + bs_consumedBits += lzcount; + + if (bs_consumedBits <= riceParamPlus1MaxConsumedBits) { + /* Getting here means the rice parameter part is wholly contained within the current cache line. */ + bs_cache <<= riceParamPlus1; + bs_consumedBits += riceParamPlus1; + } else { + /* + Getting here means the rice parameter part straddles the cache line. We need to read from the tail of the current cache + line, reload the cache, and then combine it with the head of the next cache line. + */ + + /* Before reloading the cache we need to grab the size in bits of the low part. */ + drflac_uint32 riceParamPartLoBitCount = bs_consumedBits - riceParamPlus1MaxConsumedBits; + DRFLAC_ASSERT(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); + + /* Now reload the cache. */ + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = riceParamPartLoBitCount; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + /* Slow path. We need to fetch more data from the client. */ + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; + } + + bs_cache <<= riceParamPartLoBitCount; + } + } else { + /* + Getting here means there are no bits set on the cache line. This is a less optimal case because we just wasted a call + to drflac__clz() and we need to reload the cache. + */ + for (;;) { + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = 0; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + /* Slow path. We need to fetch more data from the client. */ + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits; + } + + lzcount = drflac__clz(bs_cache); + if (lzcount < sizeof(bs_cache)*8) { + break; + } + } + + goto extract_rice_param_part; + } + + /* Make sure the cache is restored at the end of it all. */ + bs->cache = bs_cache; + bs->consumedBits = bs_consumedBits; + + return DRFLAC_TRUE; +} + + +static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar_zeroorder(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + drflac_uint32 zeroCountPart0; + drflac_uint32 riceParamPart0; + drflac_uint32 riceParamMask; + drflac_uint32 i; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); + + (void)bitsPerSample; + (void)order; + (void)shift; + (void)coefficients; + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + + i = 0; + while (i < count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0)) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamPart0 &= riceParamMask; + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + + pSamplesOut[i] = riceParamPart0; + + i += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + drflac_uint32 zeroCountPart0 = 0; + drflac_uint32 zeroCountPart1 = 0; + drflac_uint32 zeroCountPart2 = 0; + drflac_uint32 zeroCountPart3 = 0; + drflac_uint32 riceParamPart0 = 0; + drflac_uint32 riceParamPart1 = 0; + drflac_uint32 riceParamPart2 = 0; + drflac_uint32 riceParamPart3 = 0; + drflac_uint32 riceParamMask; + const drflac_int32* pSamplesOutEnd; + drflac_uint32 i; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); + + if (lpcOrder == 0) { + return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); + } + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + pSamplesOutEnd = pSamplesOut + (count & ~3); + + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + while (pSamplesOut < pSamplesOutEnd) { + /* + Rice extraction. It's faster to do this one at a time against local variables than it is to use the x4 version + against an array. Not sure why, but perhaps it's making more efficient use of registers? + */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart1, &riceParamPart1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart2, &riceParamPart2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart3, &riceParamPart3)) { + return DRFLAC_FALSE; + } + + riceParamPart0 &= riceParamMask; + riceParamPart1 &= riceParamMask; + riceParamPart2 &= riceParamMask; + riceParamPart3 &= riceParamMask; + + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart1 |= (zeroCountPart1 << riceParam); + riceParamPart2 |= (zeroCountPart2 << riceParam); + riceParamPart3 |= (zeroCountPart3 << riceParam); + + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; + riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; + riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; + + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 3); + + pSamplesOut += 4; + } + } else { + while (pSamplesOut < pSamplesOutEnd) { + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart1, &riceParamPart1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart2, &riceParamPart2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart3, &riceParamPart3)) { + return DRFLAC_FALSE; + } + + riceParamPart0 &= riceParamMask; + riceParamPart1 &= riceParamMask; + riceParamPart2 &= riceParamMask; + riceParamPart3 &= riceParamMask; + + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart1 |= (zeroCountPart1 << riceParam); + riceParamPart2 |= (zeroCountPart2 << riceParam); + riceParamPart3 |= (zeroCountPart3 << riceParam); + + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; + riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; + riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; + + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 3); + + pSamplesOut += 4; + } + } + + i = (count & ~3); + while (i < count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0)) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamPart0 &= riceParamMask; + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + /*riceParamPart0 = (riceParamPart0 >> 1) ^ (~(riceParamPart0 & 0x01) + 1);*/ + + /* Sample reconstruction. */ + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + } else { + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + } + + i += 1; + pSamplesOut += 1; + } + + return DRFLAC_TRUE; +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE __m128i drflac__mm_packs_interleaved_epi32(__m128i a, __m128i b) +{ + __m128i r; + + /* Pack. */ + r = _mm_packs_epi32(a, b); + + /* a3a2 a1a0 b3b2 b1b0 -> a3a2 b3b2 a1a0 b1b0 */ + r = _mm_shuffle_epi32(r, _MM_SHUFFLE(3, 1, 2, 0)); + + /* a3a2 b3b2 a1a0 b1b0 -> a3b3 a2b2 a1b1 a0b0 */ + r = _mm_shufflehi_epi16(r, _MM_SHUFFLE(3, 1, 2, 0)); + r = _mm_shufflelo_epi16(r, _MM_SHUFFLE(3, 1, 2, 0)); + + return r; +} +#endif + +#if defined(DRFLAC_SUPPORT_SSE41) +static DRFLAC_INLINE __m128i drflac__mm_not_si128(__m128i a) +{ + return _mm_xor_si128(a, _mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128())); +} + +static DRFLAC_INLINE __m128i drflac__mm_hadd_epi32(__m128i x) +{ + __m128i x64 = _mm_add_epi32(x, _mm_shuffle_epi32(x, _MM_SHUFFLE(1, 0, 3, 2))); + __m128i x32 = _mm_shufflelo_epi16(x64, _MM_SHUFFLE(1, 0, 3, 2)); + return _mm_add_epi32(x64, x32); +} + +static DRFLAC_INLINE __m128i drflac__mm_hadd_epi64(__m128i x) +{ + return _mm_add_epi64(x, _mm_shuffle_epi32(x, _MM_SHUFFLE(1, 0, 3, 2))); +} + +static DRFLAC_INLINE __m128i drflac__mm_srai_epi64(__m128i x, int count) +{ + /* + To simplify this we are assuming count < 32. This restriction allows us to work on a low side and a high side. The low side + is shifted with zero bits, whereas the right side is shifted with sign bits. + */ + __m128i lo = _mm_srli_epi64(x, count); + __m128i hi = _mm_srai_epi32(x, count); + + hi = _mm_and_si128(hi, _mm_set_epi32(0xFFFFFFFF, 0, 0xFFFFFFFF, 0)); /* The high part needs to have the low part cleared. */ + + return _mm_or_si128(lo, hi); +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_32(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + int i; + drflac_uint32 riceParamMask; + drflac_int32* pDecodedSamples = pSamplesOut; + drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); + drflac_uint32 zeroCountParts0 = 0; + drflac_uint32 zeroCountParts1 = 0; + drflac_uint32 zeroCountParts2 = 0; + drflac_uint32 zeroCountParts3 = 0; + drflac_uint32 riceParamParts0 = 0; + drflac_uint32 riceParamParts1 = 0; + drflac_uint32 riceParamParts2 = 0; + drflac_uint32 riceParamParts3 = 0; + __m128i coefficients128_0; + __m128i coefficients128_4; + __m128i coefficients128_8; + __m128i samples128_0; + __m128i samples128_4; + __m128i samples128_8; + __m128i riceParamMask128; + + const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + riceParamMask128 = _mm_set1_epi32(riceParamMask); + + /* Pre-load. */ + coefficients128_0 = _mm_setzero_si128(); + coefficients128_4 = _mm_setzero_si128(); + coefficients128_8 = _mm_setzero_si128(); + + samples128_0 = _mm_setzero_si128(); + samples128_4 = _mm_setzero_si128(); + samples128_8 = _mm_setzero_si128(); + + /* + Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than + what's available in the input buffers. It would be convenient to use a fall-through switch to do this, but this results + in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted + so I think there's opportunity for this to be simplified. + */ +#if 1 { - if (!drflac__read_int32(pFlac, unencodedBitsPerSample, pSamplesOut + i)) { - return false; + int runningOrder = order; + + /* 0 - 3. */ + if (runningOrder >= 4) { + coefficients128_0 = _mm_loadu_si128((const __m128i*)(coefficients + 0)); + samples128_0 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 4)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_0 = _mm_set_epi32(0, coefficients[2], coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], pSamplesOut[-3], 0); break; + case 2: coefficients128_0 = _mm_set_epi32(0, 0, coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], 0, 0); break; + case 1: coefficients128_0 = _mm_set_epi32(0, 0, 0, coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], 0, 0, 0); break; + } + runningOrder = 0; } - pSamplesOut[i] += drflac__calculate_prediction(order, shift, coefficients, pSamplesOut + i); + /* 4 - 7 */ + if (runningOrder >= 4) { + coefficients128_4 = _mm_loadu_si128((const __m128i*)(coefficients + 4)); + samples128_4 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 8)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_4 = _mm_set_epi32(0, coefficients[6], coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], pSamplesOut[-7], 0); break; + case 2: coefficients128_4 = _mm_set_epi32(0, 0, coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], 0, 0); break; + case 1: coefficients128_4 = _mm_set_epi32(0, 0, 0, coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* 8 - 11 */ + if (runningOrder == 4) { + coefficients128_8 = _mm_loadu_si128((const __m128i*)(coefficients + 8)); + samples128_8 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 12)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_8 = _mm_set_epi32(0, coefficients[10], coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], pSamplesOut[-11], 0); break; + case 2: coefficients128_8 = _mm_set_epi32(0, 0, coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], 0, 0); break; + case 1: coefficients128_8 = _mm_set_epi32(0, 0, 0, coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ + coefficients128_0 = _mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(0, 1, 2, 3)); + coefficients128_4 = _mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(0, 1, 2, 3)); + coefficients128_8 = _mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(0, 1, 2, 3)); + } +#else + /* This causes strict-aliasing warnings with GCC. */ + switch (order) + { + case 12: ((drflac_int32*)&coefficients128_8)[0] = coefficients[11]; ((drflac_int32*)&samples128_8)[0] = pDecodedSamples[-12]; + case 11: ((drflac_int32*)&coefficients128_8)[1] = coefficients[10]; ((drflac_int32*)&samples128_8)[1] = pDecodedSamples[-11]; + case 10: ((drflac_int32*)&coefficients128_8)[2] = coefficients[ 9]; ((drflac_int32*)&samples128_8)[2] = pDecodedSamples[-10]; + case 9: ((drflac_int32*)&coefficients128_8)[3] = coefficients[ 8]; ((drflac_int32*)&samples128_8)[3] = pDecodedSamples[- 9]; + case 8: ((drflac_int32*)&coefficients128_4)[0] = coefficients[ 7]; ((drflac_int32*)&samples128_4)[0] = pDecodedSamples[- 8]; + case 7: ((drflac_int32*)&coefficients128_4)[1] = coefficients[ 6]; ((drflac_int32*)&samples128_4)[1] = pDecodedSamples[- 7]; + case 6: ((drflac_int32*)&coefficients128_4)[2] = coefficients[ 5]; ((drflac_int32*)&samples128_4)[2] = pDecodedSamples[- 6]; + case 5: ((drflac_int32*)&coefficients128_4)[3] = coefficients[ 4]; ((drflac_int32*)&samples128_4)[3] = pDecodedSamples[- 5]; + case 4: ((drflac_int32*)&coefficients128_0)[0] = coefficients[ 3]; ((drflac_int32*)&samples128_0)[0] = pDecodedSamples[- 4]; + case 3: ((drflac_int32*)&coefficients128_0)[1] = coefficients[ 2]; ((drflac_int32*)&samples128_0)[1] = pDecodedSamples[- 3]; + case 2: ((drflac_int32*)&coefficients128_0)[2] = coefficients[ 1]; ((drflac_int32*)&samples128_0)[2] = pDecodedSamples[- 2]; + case 1: ((drflac_int32*)&coefficients128_0)[3] = coefficients[ 0]; ((drflac_int32*)&samples128_0)[3] = pDecodedSamples[- 1]; + } +#endif + + /* For this version we are doing one sample at a time. */ + while (pDecodedSamples < pDecodedSamplesEnd) { + __m128i prediction128; + __m128i zeroCountPart128; + __m128i riceParamPart128; + + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts1, &riceParamParts1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts2, &riceParamParts2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts3, &riceParamParts3)) { + return DRFLAC_FALSE; + } + + zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); + riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); + + riceParamPart128 = _mm_and_si128(riceParamPart128, riceParamMask128); + riceParamPart128 = _mm_or_si128(riceParamPart128, _mm_slli_epi32(zeroCountPart128, riceParam)); + riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_add_epi32(drflac__mm_not_si128(_mm_and_si128(riceParamPart128, _mm_set1_epi32(0x01))), _mm_set1_epi32(0x01))); /* <-- SSE2 compatible */ + /*riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_mullo_epi32(_mm_and_si128(riceParamPart128, _mm_set1_epi32(0x01)), _mm_set1_epi32(0xFFFFFFFF)));*/ /* <-- Only supported from SSE4.1 and is slower in my testing... */ + + if (order <= 4) { + for (i = 0; i < 4; i += 1) { + prediction128 = _mm_mullo_epi32(coefficients128_0, samples128_0); + + /* Horizontal add and shift. */ + prediction128 = drflac__mm_hadd_epi32(prediction128); + prediction128 = _mm_srai_epi32(prediction128, shift); + prediction128 = _mm_add_epi32(riceParamPart128, prediction128); + + samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); + riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); + } + } else if (order <= 8) { + for (i = 0; i < 4; i += 1) { + prediction128 = _mm_mullo_epi32(coefficients128_4, samples128_4); + prediction128 = _mm_add_epi32(prediction128, _mm_mullo_epi32(coefficients128_0, samples128_0)); + + /* Horizontal add and shift. */ + prediction128 = drflac__mm_hadd_epi32(prediction128); + prediction128 = _mm_srai_epi32(prediction128, shift); + prediction128 = _mm_add_epi32(riceParamPart128, prediction128); + + samples128_4 = _mm_alignr_epi8(samples128_0, samples128_4, 4); + samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); + riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); + } + } else { + for (i = 0; i < 4; i += 1) { + prediction128 = _mm_mullo_epi32(coefficients128_8, samples128_8); + prediction128 = _mm_add_epi32(prediction128, _mm_mullo_epi32(coefficients128_4, samples128_4)); + prediction128 = _mm_add_epi32(prediction128, _mm_mullo_epi32(coefficients128_0, samples128_0)); + + /* Horizontal add and shift. */ + prediction128 = drflac__mm_hadd_epi32(prediction128); + prediction128 = _mm_srai_epi32(prediction128, shift); + prediction128 = _mm_add_epi32(riceParamPart128, prediction128); + + samples128_8 = _mm_alignr_epi8(samples128_4, samples128_8, 4); + samples128_4 = _mm_alignr_epi8(samples128_0, samples128_4, 4); + samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); + riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); + } + } + + /* We store samples in groups of 4. */ + _mm_storeu_si128((__m128i*)pDecodedSamples, samples128_0); + pDecodedSamples += 4; } - return true; + /* Make sure we process the last few samples. */ + i = (count & ~3); + while (i < (int)count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0)) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamParts0 &= riceParamMask; + riceParamParts0 |= (zeroCountParts0 << riceParam); + riceParamParts0 = (riceParamParts0 >> 1) ^ t[riceParamParts0 & 0x01]; + + /* Sample reconstruction. */ + pDecodedSamples[0] = riceParamParts0 + drflac__calculate_prediction_32(order, shift, coefficients, pDecodedSamples); + + i += 1; + pDecodedSamples += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_64(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + int i; + drflac_uint32 riceParamMask; + drflac_int32* pDecodedSamples = pSamplesOut; + drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); + drflac_uint32 zeroCountParts0 = 0; + drflac_uint32 zeroCountParts1 = 0; + drflac_uint32 zeroCountParts2 = 0; + drflac_uint32 zeroCountParts3 = 0; + drflac_uint32 riceParamParts0 = 0; + drflac_uint32 riceParamParts1 = 0; + drflac_uint32 riceParamParts2 = 0; + drflac_uint32 riceParamParts3 = 0; + __m128i coefficients128_0; + __m128i coefficients128_4; + __m128i coefficients128_8; + __m128i samples128_0; + __m128i samples128_4; + __m128i samples128_8; + __m128i prediction128; + __m128i riceParamMask128; + + const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + DRFLAC_ASSERT(order <= 12); + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + riceParamMask128 = _mm_set1_epi32(riceParamMask); + + prediction128 = _mm_setzero_si128(); + + /* Pre-load. */ + coefficients128_0 = _mm_setzero_si128(); + coefficients128_4 = _mm_setzero_si128(); + coefficients128_8 = _mm_setzero_si128(); + + samples128_0 = _mm_setzero_si128(); + samples128_4 = _mm_setzero_si128(); + samples128_8 = _mm_setzero_si128(); + +#if 1 + { + int runningOrder = order; + + /* 0 - 3. */ + if (runningOrder >= 4) { + coefficients128_0 = _mm_loadu_si128((const __m128i*)(coefficients + 0)); + samples128_0 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 4)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_0 = _mm_set_epi32(0, coefficients[2], coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], pSamplesOut[-3], 0); break; + case 2: coefficients128_0 = _mm_set_epi32(0, 0, coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], 0, 0); break; + case 1: coefficients128_0 = _mm_set_epi32(0, 0, 0, coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* 4 - 7 */ + if (runningOrder >= 4) { + coefficients128_4 = _mm_loadu_si128((const __m128i*)(coefficients + 4)); + samples128_4 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 8)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_4 = _mm_set_epi32(0, coefficients[6], coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], pSamplesOut[-7], 0); break; + case 2: coefficients128_4 = _mm_set_epi32(0, 0, coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], 0, 0); break; + case 1: coefficients128_4 = _mm_set_epi32(0, 0, 0, coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* 8 - 11 */ + if (runningOrder == 4) { + coefficients128_8 = _mm_loadu_si128((const __m128i*)(coefficients + 8)); + samples128_8 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 12)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_8 = _mm_set_epi32(0, coefficients[10], coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], pSamplesOut[-11], 0); break; + case 2: coefficients128_8 = _mm_set_epi32(0, 0, coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], 0, 0); break; + case 1: coefficients128_8 = _mm_set_epi32(0, 0, 0, coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ + coefficients128_0 = _mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(0, 1, 2, 3)); + coefficients128_4 = _mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(0, 1, 2, 3)); + coefficients128_8 = _mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(0, 1, 2, 3)); + } +#else + switch (order) + { + case 12: ((drflac_int32*)&coefficients128_8)[0] = coefficients[11]; ((drflac_int32*)&samples128_8)[0] = pDecodedSamples[-12]; + case 11: ((drflac_int32*)&coefficients128_8)[1] = coefficients[10]; ((drflac_int32*)&samples128_8)[1] = pDecodedSamples[-11]; + case 10: ((drflac_int32*)&coefficients128_8)[2] = coefficients[ 9]; ((drflac_int32*)&samples128_8)[2] = pDecodedSamples[-10]; + case 9: ((drflac_int32*)&coefficients128_8)[3] = coefficients[ 8]; ((drflac_int32*)&samples128_8)[3] = pDecodedSamples[- 9]; + case 8: ((drflac_int32*)&coefficients128_4)[0] = coefficients[ 7]; ((drflac_int32*)&samples128_4)[0] = pDecodedSamples[- 8]; + case 7: ((drflac_int32*)&coefficients128_4)[1] = coefficients[ 6]; ((drflac_int32*)&samples128_4)[1] = pDecodedSamples[- 7]; + case 6: ((drflac_int32*)&coefficients128_4)[2] = coefficients[ 5]; ((drflac_int32*)&samples128_4)[2] = pDecodedSamples[- 6]; + case 5: ((drflac_int32*)&coefficients128_4)[3] = coefficients[ 4]; ((drflac_int32*)&samples128_4)[3] = pDecodedSamples[- 5]; + case 4: ((drflac_int32*)&coefficients128_0)[0] = coefficients[ 3]; ((drflac_int32*)&samples128_0)[0] = pDecodedSamples[- 4]; + case 3: ((drflac_int32*)&coefficients128_0)[1] = coefficients[ 2]; ((drflac_int32*)&samples128_0)[1] = pDecodedSamples[- 3]; + case 2: ((drflac_int32*)&coefficients128_0)[2] = coefficients[ 1]; ((drflac_int32*)&samples128_0)[2] = pDecodedSamples[- 2]; + case 1: ((drflac_int32*)&coefficients128_0)[3] = coefficients[ 0]; ((drflac_int32*)&samples128_0)[3] = pDecodedSamples[- 1]; + } +#endif + + /* For this version we are doing one sample at a time. */ + while (pDecodedSamples < pDecodedSamplesEnd) { + __m128i zeroCountPart128; + __m128i riceParamPart128; + + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts1, &riceParamParts1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts2, &riceParamParts2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts3, &riceParamParts3)) { + return DRFLAC_FALSE; + } + + zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); + riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); + + riceParamPart128 = _mm_and_si128(riceParamPart128, riceParamMask128); + riceParamPart128 = _mm_or_si128(riceParamPart128, _mm_slli_epi32(zeroCountPart128, riceParam)); + riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_add_epi32(drflac__mm_not_si128(_mm_and_si128(riceParamPart128, _mm_set1_epi32(1))), _mm_set1_epi32(1))); + + for (i = 0; i < 4; i += 1) { + prediction128 = _mm_xor_si128(prediction128, prediction128); /* Reset to 0. */ + + switch (order) + { + case 12: + case 11: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(1, 1, 0, 0)), _mm_shuffle_epi32(samples128_8, _MM_SHUFFLE(1, 1, 0, 0)))); + case 10: + case 9: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(3, 3, 2, 2)), _mm_shuffle_epi32(samples128_8, _MM_SHUFFLE(3, 3, 2, 2)))); + case 8: + case 7: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(1, 1, 0, 0)), _mm_shuffle_epi32(samples128_4, _MM_SHUFFLE(1, 1, 0, 0)))); + case 6: + case 5: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(3, 3, 2, 2)), _mm_shuffle_epi32(samples128_4, _MM_SHUFFLE(3, 3, 2, 2)))); + case 4: + case 3: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(1, 1, 0, 0)), _mm_shuffle_epi32(samples128_0, _MM_SHUFFLE(1, 1, 0, 0)))); + case 2: + case 1: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(3, 3, 2, 2)), _mm_shuffle_epi32(samples128_0, _MM_SHUFFLE(3, 3, 2, 2)))); + } + + /* Horizontal add and shift. */ + prediction128 = drflac__mm_hadd_epi64(prediction128); + prediction128 = drflac__mm_srai_epi64(prediction128, shift); + prediction128 = _mm_add_epi32(riceParamPart128, prediction128); + + /* Our value should be sitting in prediction128[0]. We need to combine this with our SSE samples. */ + samples128_8 = _mm_alignr_epi8(samples128_4, samples128_8, 4); + samples128_4 = _mm_alignr_epi8(samples128_0, samples128_4, 4); + samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); + + /* Slide our rice parameter down so that the value in position 0 contains the next one to process. */ + riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); + } + + /* We store samples in groups of 4. */ + _mm_storeu_si128((__m128i*)pDecodedSamples, samples128_0); + pDecodedSamples += 4; + } + + /* Make sure we process the last few samples. */ + i = (count & ~3); + while (i < (int)count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0)) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamParts0 &= riceParamMask; + riceParamParts0 |= (zeroCountParts0 << riceParam); + riceParamParts0 = (riceParamParts0 >> 1) ^ t[riceParamParts0 & 0x01]; + + /* Sample reconstruction. */ + pDecodedSamples[0] = riceParamParts0 + drflac__calculate_prediction_64(order, shift, coefficients, pDecodedSamples); + + i += 1; + pDecodedSamples += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); + + /* In my testing the order is rarely > 12, so in this case I'm going to simplify the SSE implementation by only handling order <= 12. */ + if (lpcOrder > 0 && lpcOrder <= 12) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); + } else { + return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); + } + } else { + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac__vst2q_s32(drflac_int32* p, int32x4x2_t x) +{ + vst1q_s32(p+0, x.val[0]); + vst1q_s32(p+4, x.val[1]); +} + +static DRFLAC_INLINE void drflac__vst2q_u32(drflac_uint32* p, uint32x4x2_t x) +{ + vst1q_u32(p+0, x.val[0]); + vst1q_u32(p+4, x.val[1]); +} + +static DRFLAC_INLINE void drflac__vst2q_f32(float* p, float32x4x2_t x) +{ + vst1q_f32(p+0, x.val[0]); + vst1q_f32(p+4, x.val[1]); +} + +static DRFLAC_INLINE void drflac__vst2q_s16(drflac_int16* p, int16x4x2_t x) +{ + vst1q_s16(p, vcombine_s16(x.val[0], x.val[1])); +} + +static DRFLAC_INLINE void drflac__vst2q_u16(drflac_uint16* p, uint16x4x2_t x) +{ + vst1q_u16(p, vcombine_u16(x.val[0], x.val[1])); +} + +static DRFLAC_INLINE int32x4_t drflac__vdupq_n_s32x4(drflac_int32 x3, drflac_int32 x2, drflac_int32 x1, drflac_int32 x0) +{ + drflac_int32 x[4]; + x[3] = x3; + x[2] = x2; + x[1] = x1; + x[0] = x0; + return vld1q_s32(x); +} + +static DRFLAC_INLINE int32x4_t drflac__valignrq_s32_1(int32x4_t a, int32x4_t b) +{ + /* Equivalent to SSE's _mm_alignr_epi8(a, b, 4) */ + + /* Reference */ + /*return drflac__vdupq_n_s32x4( + vgetq_lane_s32(a, 0), + vgetq_lane_s32(b, 3), + vgetq_lane_s32(b, 2), + vgetq_lane_s32(b, 1) + );*/ + + return vextq_s32(b, a, 1); +} + +static DRFLAC_INLINE uint32x4_t drflac__valignrq_u32_1(uint32x4_t a, uint32x4_t b) +{ + /* Equivalent to SSE's _mm_alignr_epi8(a, b, 4) */ + + /* Reference */ + /*return drflac__vdupq_n_s32x4( + vgetq_lane_s32(a, 0), + vgetq_lane_s32(b, 3), + vgetq_lane_s32(b, 2), + vgetq_lane_s32(b, 1) + );*/ + + return vextq_u32(b, a, 1); +} + +static DRFLAC_INLINE int32x2_t drflac__vhaddq_s32(int32x4_t x) +{ + /* The sum must end up in position 0. */ + + /* Reference */ + /*return vdupq_n_s32( + vgetq_lane_s32(x, 3) + + vgetq_lane_s32(x, 2) + + vgetq_lane_s32(x, 1) + + vgetq_lane_s32(x, 0) + );*/ + + int32x2_t r = vadd_s32(vget_high_s32(x), vget_low_s32(x)); + return vpadd_s32(r, r); +} + +static DRFLAC_INLINE int64x1_t drflac__vhaddq_s64(int64x2_t x) +{ + return vadd_s64(vget_high_s64(x), vget_low_s64(x)); +} + +static DRFLAC_INLINE int32x4_t drflac__vrevq_s32(int32x4_t x) +{ + /* Reference */ + /*return drflac__vdupq_n_s32x4( + vgetq_lane_s32(x, 0), + vgetq_lane_s32(x, 1), + vgetq_lane_s32(x, 2), + vgetq_lane_s32(x, 3) + );*/ + + return vrev64q_s32(vcombine_s32(vget_high_s32(x), vget_low_s32(x))); +} + +static DRFLAC_INLINE int32x4_t drflac__vnotq_s32(int32x4_t x) +{ + return veorq_s32(x, vdupq_n_s32(0xFFFFFFFF)); +} + +static DRFLAC_INLINE uint32x4_t drflac__vnotq_u32(uint32x4_t x) +{ + return veorq_u32(x, vdupq_n_u32(0xFFFFFFFF)); +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_32(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + int i; + drflac_uint32 riceParamMask; + drflac_int32* pDecodedSamples = pSamplesOut; + drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); + drflac_uint32 zeroCountParts[4]; + drflac_uint32 riceParamParts[4]; + int32x4_t coefficients128_0; + int32x4_t coefficients128_4; + int32x4_t coefficients128_8; + int32x4_t samples128_0; + int32x4_t samples128_4; + int32x4_t samples128_8; + uint32x4_t riceParamMask128; + int32x4_t riceParam128; + int32x2_t shift64; + uint32x4_t one128; + + const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + riceParamMask128 = vdupq_n_u32(riceParamMask); + + riceParam128 = vdupq_n_s32(riceParam); + shift64 = vdup_n_s32(-shift); /* Negate the shift because we'll be doing a variable shift using vshlq_s32(). */ + one128 = vdupq_n_u32(1); + + /* + Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than + what's available in the input buffers. It would be conenient to use a fall-through switch to do this, but this results + in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted + so I think there's opportunity for this to be simplified. + */ + { + int runningOrder = order; + drflac_int32 tempC[4] = {0, 0, 0, 0}; + drflac_int32 tempS[4] = {0, 0, 0, 0}; + + /* 0 - 3. */ + if (runningOrder >= 4) { + coefficients128_0 = vld1q_s32(coefficients + 0); + samples128_0 = vld1q_s32(pSamplesOut - 4); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[2]; tempS[1] = pSamplesOut[-3]; /* fallthrough */ + case 2: tempC[1] = coefficients[1]; tempS[2] = pSamplesOut[-2]; /* fallthrough */ + case 1: tempC[0] = coefficients[0]; tempS[3] = pSamplesOut[-1]; /* fallthrough */ + } + + coefficients128_0 = vld1q_s32(tempC); + samples128_0 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* 4 - 7 */ + if (runningOrder >= 4) { + coefficients128_4 = vld1q_s32(coefficients + 4); + samples128_4 = vld1q_s32(pSamplesOut - 8); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[6]; tempS[1] = pSamplesOut[-7]; /* fallthrough */ + case 2: tempC[1] = coefficients[5]; tempS[2] = pSamplesOut[-6]; /* fallthrough */ + case 1: tempC[0] = coefficients[4]; tempS[3] = pSamplesOut[-5]; /* fallthrough */ + } + + coefficients128_4 = vld1q_s32(tempC); + samples128_4 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* 8 - 11 */ + if (runningOrder == 4) { + coefficients128_8 = vld1q_s32(coefficients + 8); + samples128_8 = vld1q_s32(pSamplesOut - 12); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[10]; tempS[1] = pSamplesOut[-11]; /* fallthrough */ + case 2: tempC[1] = coefficients[ 9]; tempS[2] = pSamplesOut[-10]; /* fallthrough */ + case 1: tempC[0] = coefficients[ 8]; tempS[3] = pSamplesOut[- 9]; /* fallthrough */ + } + + coefficients128_8 = vld1q_s32(tempC); + samples128_8 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ + coefficients128_0 = drflac__vrevq_s32(coefficients128_0); + coefficients128_4 = drflac__vrevq_s32(coefficients128_4); + coefficients128_8 = drflac__vrevq_s32(coefficients128_8); + } + + /* For this version we are doing one sample at a time. */ + while (pDecodedSamples < pDecodedSamplesEnd) { + int32x4_t prediction128; + int32x2_t prediction64; + uint32x4_t zeroCountPart128; + uint32x4_t riceParamPart128; + + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[1], &riceParamParts[1]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[2], &riceParamParts[2]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[3], &riceParamParts[3])) { + return DRFLAC_FALSE; + } + + zeroCountPart128 = vld1q_u32(zeroCountParts); + riceParamPart128 = vld1q_u32(riceParamParts); + + riceParamPart128 = vandq_u32(riceParamPart128, riceParamMask128); + riceParamPart128 = vorrq_u32(riceParamPart128, vshlq_u32(zeroCountPart128, riceParam128)); + riceParamPart128 = veorq_u32(vshrq_n_u32(riceParamPart128, 1), vaddq_u32(drflac__vnotq_u32(vandq_u32(riceParamPart128, one128)), one128)); + + if (order <= 4) { + for (i = 0; i < 4; i += 1) { + prediction128 = vmulq_s32(coefficients128_0, samples128_0); + + /* Horizontal add and shift. */ + prediction64 = drflac__vhaddq_s32(prediction128); + prediction64 = vshl_s32(prediction64, shift64); + prediction64 = vadd_s32(prediction64, vget_low_s32(vreinterpretq_s32_u32(riceParamPart128))); + + samples128_0 = drflac__valignrq_s32_1(vcombine_s32(prediction64, vdup_n_s32(0)), samples128_0); + riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); + } + } else if (order <= 8) { + for (i = 0; i < 4; i += 1) { + prediction128 = vmulq_s32(coefficients128_4, samples128_4); + prediction128 = vmlaq_s32(prediction128, coefficients128_0, samples128_0); + + /* Horizontal add and shift. */ + prediction64 = drflac__vhaddq_s32(prediction128); + prediction64 = vshl_s32(prediction64, shift64); + prediction64 = vadd_s32(prediction64, vget_low_s32(vreinterpretq_s32_u32(riceParamPart128))); + + samples128_4 = drflac__valignrq_s32_1(samples128_0, samples128_4); + samples128_0 = drflac__valignrq_s32_1(vcombine_s32(prediction64, vdup_n_s32(0)), samples128_0); + riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); + } + } else { + for (i = 0; i < 4; i += 1) { + prediction128 = vmulq_s32(coefficients128_8, samples128_8); + prediction128 = vmlaq_s32(prediction128, coefficients128_4, samples128_4); + prediction128 = vmlaq_s32(prediction128, coefficients128_0, samples128_0); + + /* Horizontal add and shift. */ + prediction64 = drflac__vhaddq_s32(prediction128); + prediction64 = vshl_s32(prediction64, shift64); + prediction64 = vadd_s32(prediction64, vget_low_s32(vreinterpretq_s32_u32(riceParamPart128))); + + samples128_8 = drflac__valignrq_s32_1(samples128_4, samples128_8); + samples128_4 = drflac__valignrq_s32_1(samples128_0, samples128_4); + samples128_0 = drflac__valignrq_s32_1(vcombine_s32(prediction64, vdup_n_s32(0)), samples128_0); + riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); + } + } + + /* We store samples in groups of 4. */ + vst1q_s32(pDecodedSamples, samples128_0); + pDecodedSamples += 4; + } + + /* Make sure we process the last few samples. */ + i = (count & ~3); + while (i < (int)count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0])) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamParts[0] &= riceParamMask; + riceParamParts[0] |= (zeroCountParts[0] << riceParam); + riceParamParts[0] = (riceParamParts[0] >> 1) ^ t[riceParamParts[0] & 0x01]; + + /* Sample reconstruction. */ + pDecodedSamples[0] = riceParamParts[0] + drflac__calculate_prediction_32(order, shift, coefficients, pDecodedSamples); + + i += 1; + pDecodedSamples += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + int i; + drflac_uint32 riceParamMask; + drflac_int32* pDecodedSamples = pSamplesOut; + drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); + drflac_uint32 zeroCountParts[4]; + drflac_uint32 riceParamParts[4]; + int32x4_t coefficients128_0; + int32x4_t coefficients128_4; + int32x4_t coefficients128_8; + int32x4_t samples128_0; + int32x4_t samples128_4; + int32x4_t samples128_8; + uint32x4_t riceParamMask128; + int32x4_t riceParam128; + int64x1_t shift64; + uint32x4_t one128; + int64x2_t prediction128 = { 0 }; + uint32x4_t zeroCountPart128; + uint32x4_t riceParamPart128; + + const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + riceParamMask128 = vdupq_n_u32(riceParamMask); + + riceParam128 = vdupq_n_s32(riceParam); + shift64 = vdup_n_s64(-shift); /* Negate the shift because we'll be doing a variable shift using vshlq_s32(). */ + one128 = vdupq_n_u32(1); + + /* + Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than + what's available in the input buffers. It would be convenient to use a fall-through switch to do this, but this results + in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted + so I think there's opportunity for this to be simplified. + */ + { + int runningOrder = order; + drflac_int32 tempC[4] = {0, 0, 0, 0}; + drflac_int32 tempS[4] = {0, 0, 0, 0}; + + /* 0 - 3. */ + if (runningOrder >= 4) { + coefficients128_0 = vld1q_s32(coefficients + 0); + samples128_0 = vld1q_s32(pSamplesOut - 4); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[2]; tempS[1] = pSamplesOut[-3]; /* fallthrough */ + case 2: tempC[1] = coefficients[1]; tempS[2] = pSamplesOut[-2]; /* fallthrough */ + case 1: tempC[0] = coefficients[0]; tempS[3] = pSamplesOut[-1]; /* fallthrough */ + } + + coefficients128_0 = vld1q_s32(tempC); + samples128_0 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* 4 - 7 */ + if (runningOrder >= 4) { + coefficients128_4 = vld1q_s32(coefficients + 4); + samples128_4 = vld1q_s32(pSamplesOut - 8); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[6]; tempS[1] = pSamplesOut[-7]; /* fallthrough */ + case 2: tempC[1] = coefficients[5]; tempS[2] = pSamplesOut[-6]; /* fallthrough */ + case 1: tempC[0] = coefficients[4]; tempS[3] = pSamplesOut[-5]; /* fallthrough */ + } + + coefficients128_4 = vld1q_s32(tempC); + samples128_4 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* 8 - 11 */ + if (runningOrder == 4) { + coefficients128_8 = vld1q_s32(coefficients + 8); + samples128_8 = vld1q_s32(pSamplesOut - 12); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[10]; tempS[1] = pSamplesOut[-11]; /* fallthrough */ + case 2: tempC[1] = coefficients[ 9]; tempS[2] = pSamplesOut[-10]; /* fallthrough */ + case 1: tempC[0] = coefficients[ 8]; tempS[3] = pSamplesOut[- 9]; /* fallthrough */ + } + + coefficients128_8 = vld1q_s32(tempC); + samples128_8 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ + coefficients128_0 = drflac__vrevq_s32(coefficients128_0); + coefficients128_4 = drflac__vrevq_s32(coefficients128_4); + coefficients128_8 = drflac__vrevq_s32(coefficients128_8); + } + + /* For this version we are doing one sample at a time. */ + while (pDecodedSamples < pDecodedSamplesEnd) { + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[1], &riceParamParts[1]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[2], &riceParamParts[2]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[3], &riceParamParts[3])) { + return DRFLAC_FALSE; + } + + zeroCountPart128 = vld1q_u32(zeroCountParts); + riceParamPart128 = vld1q_u32(riceParamParts); + + riceParamPart128 = vandq_u32(riceParamPart128, riceParamMask128); + riceParamPart128 = vorrq_u32(riceParamPart128, vshlq_u32(zeroCountPart128, riceParam128)); + riceParamPart128 = veorq_u32(vshrq_n_u32(riceParamPart128, 1), vaddq_u32(drflac__vnotq_u32(vandq_u32(riceParamPart128, one128)), one128)); + + for (i = 0; i < 4; i += 1) { + int64x1_t prediction64; + + prediction128 = veorq_s64(prediction128, prediction128); /* Reset to 0. */ + switch (order) + { + case 12: + case 11: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_low_s32(coefficients128_8), vget_low_s32(samples128_8))); + case 10: + case 9: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_high_s32(coefficients128_8), vget_high_s32(samples128_8))); + case 8: + case 7: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_low_s32(coefficients128_4), vget_low_s32(samples128_4))); + case 6: + case 5: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_high_s32(coefficients128_4), vget_high_s32(samples128_4))); + case 4: + case 3: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_low_s32(coefficients128_0), vget_low_s32(samples128_0))); + case 2: + case 1: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_high_s32(coefficients128_0), vget_high_s32(samples128_0))); + } + + /* Horizontal add and shift. */ + prediction64 = drflac__vhaddq_s64(prediction128); + prediction64 = vshl_s64(prediction64, shift64); + prediction64 = vadd_s64(prediction64, vdup_n_s64(vgetq_lane_u32(riceParamPart128, 0))); + + /* Our value should be sitting in prediction64[0]. We need to combine this with our SSE samples. */ + samples128_8 = drflac__valignrq_s32_1(samples128_4, samples128_8); + samples128_4 = drflac__valignrq_s32_1(samples128_0, samples128_4); + samples128_0 = drflac__valignrq_s32_1(vcombine_s32(vreinterpret_s32_s64(prediction64), vdup_n_s32(0)), samples128_0); + + /* Slide our rice parameter down so that the value in position 0 contains the next one to process. */ + riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); + } + + /* We store samples in groups of 4. */ + vst1q_s32(pDecodedSamples, samples128_0); + pDecodedSamples += 4; + } + + /* Make sure we process the last few samples. */ + i = (count & ~3); + while (i < (int)count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0])) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamParts[0] &= riceParamMask; + riceParamParts[0] |= (zeroCountParts[0] << riceParam); + riceParamParts[0] = (riceParamParts[0] >> 1) ^ t[riceParamParts[0] & 0x01]; + + /* Sample reconstruction. */ + pDecodedSamples[0] = riceParamParts[0] + drflac__calculate_prediction_64(order, shift, coefficients, pDecodedSamples); + + i += 1; + pDecodedSamples += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); + + /* In my testing the order is rarely > 12, so in this case I'm going to simplify the NEON implementation by only handling order <= 12. */ + if (lpcOrder > 0 && lpcOrder <= 12) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); + } else { + return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); + } + } else { + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); + } +} +#endif + +static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ +#if defined(DRFLAC_SUPPORT_SSE41) + if (drflac__gIsSSE41Supported) { + return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported) { + return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); + } else +#endif + { + /* Scalar fallback. */ + #if 0 + return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); + #else + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); + #endif + } +} + +/* Reads and seeks past a string of residual values as Rice codes. The decoder should be sitting on the first bit of the Rice codes. */ +static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam) +{ + drflac_uint32 i; + + DRFLAC_ASSERT(bs != NULL); + + for (i = 0; i < count; ++i) { + if (!drflac__seek_rice_parts(bs, riceParam)) { + return DRFLAC_FALSE; + } + } + + return DRFLAC_TRUE; +} + +#if defined(__clang__) +__attribute__((no_sanitize("signed-integer-overflow"))) +#endif +static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + drflac_uint32 i; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(unencodedBitsPerSample <= 31); /* <-- unencodedBitsPerSample is a 5 bit number, so cannot exceed 31. */ + DRFLAC_ASSERT(pSamplesOut != NULL); + + for (i = 0; i < count; ++i) { + if (unencodedBitsPerSample > 0) { + if (!drflac__read_int32(bs, unencodedBitsPerSample, pSamplesOut + i)) { + return DRFLAC_FALSE; + } + } else { + pSamplesOut[i] = 0; + } + + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[i] += drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i); + } else { + pSamplesOut[i] += drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i); + } + } + + return DRFLAC_TRUE; } -// Reads and decodes the residual for the sub-frame the decoder is currently sitting on. This function should be called -// when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be ignored. The -// and parameters are used to determine how many residual values need to be decoded. -static bool drflac__decode_samples_with_residual(drflac* pFlac, unsigned int blockSize, unsigned int order, int shift, const short* coefficients, int* pDecodedSamples) +/* +Reads and decodes the residual for the sub-frame the decoder is currently sitting on. This function should be called +when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be ignored. The + and parameters are used to determine how many residual values need to be decoded. +*/ +static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { - assert(pFlac != NULL); - assert(blockSize != 0); - assert(pDecodedSamples != NULL); // <-- Should we allow NULL, in which case we just seek past the residual rather than do a full decode? + drflac_uint8 residualMethod; + drflac_uint8 partitionOrder; + drflac_uint32 samplesInPartition; + drflac_uint32 partitionsRemaining; - unsigned char residualMethod; - if (!drflac__read_uint8(pFlac, 2, &residualMethod)) { - return false; + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(blockSize != 0); + DRFLAC_ASSERT(pDecodedSamples != NULL); /* <-- Should we allow NULL, in which case we just seek past the residual rather than do a full decode? */ + + if (!drflac__read_uint8(bs, 2, &residualMethod)) { + return DRFLAC_FALSE; } if (residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE && residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { - return false; // Unknown or unsupported residual coding method. + return DRFLAC_FALSE; /* Unknown or unsupported residual coding method. */ } - // Ignore the first values. - pDecodedSamples += order; + /* Ignore the first values. */ + pDecodedSamples += lpcOrder; - - unsigned char partitionOrder; - if (!drflac__read_uint8(pFlac, 4, &partitionOrder)) { - return false; + if (!drflac__read_uint8(bs, 4, &partitionOrder)) { + return DRFLAC_FALSE; } + /* + From the FLAC spec: + The Rice partition order in a Rice-coded residual section must be less than or equal to 8. + */ + if (partitionOrder > 8) { + return DRFLAC_FALSE; + } - unsigned int samplesInPartition = (blockSize / (1 << partitionOrder)) - order; - unsigned int partitionsRemaining = (1 << partitionOrder); - for (;;) - { - unsigned char riceParam = 0; + /* Validation check. */ + if ((blockSize / (1 << partitionOrder)) < lpcOrder) { + return DRFLAC_FALSE; + } + + samplesInPartition = (blockSize / (1 << partitionOrder)) - lpcOrder; + partitionsRemaining = (1 << partitionOrder); + for (;;) { + drflac_uint8 riceParam = 0; if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE) { - if (!drflac__read_uint8(pFlac, 4, &riceParam)) { - return false; + if (!drflac__read_uint8(bs, 4, &riceParam)) { + return DRFLAC_FALSE; } - if (riceParam == 16) { + if (riceParam == 15) { riceParam = 0xFF; } } else if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { - if (!drflac__read_uint8(pFlac, 5, &riceParam)) { - return false; + if (!drflac__read_uint8(bs, 5, &riceParam)) { + return DRFLAC_FALSE; } - if (riceParam == 32) { + if (riceParam == 31) { riceParam = 0xFF; } } if (riceParam != 0xFF) { - if (!drflac__decode_samples_with_residual__rice(pFlac, samplesInPartition, riceParam, order, shift, coefficients, pDecodedSamples)) { - return false; + if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { + return DRFLAC_FALSE; } } else { - unsigned char unencodedBitsPerSample = 0; - if (!drflac__read_uint8(pFlac, 5, &unencodedBitsPerSample)) { - return false; + drflac_uint8 unencodedBitsPerSample = 0; + if (!drflac__read_uint8(bs, 5, &unencodedBitsPerSample)) { + return DRFLAC_FALSE; } - if (!drflac__decode_samples_with_residual__unencoded(pFlac, samplesInPartition, unencodedBitsPerSample, order, shift, coefficients, pDecodedSamples)) { - return false; + if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { + return DRFLAC_FALSE; } } pDecodedSamples += samplesInPartition; - if (partitionsRemaining == 1) { break; } partitionsRemaining -= 1; - samplesInPartition = blockSize / (1 << partitionOrder); + + if (partitionOrder != 0) { + samplesInPartition = blockSize / (1 << partitionOrder); + } } - return true; + return DRFLAC_TRUE; } -// Reads and seeks past the residual for the sub-frame the decoder is currently sitting on. This function should be called -// when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be set to 0. The -// and parameters are used to determine how many residual values need to be decoded. -static bool drflac__read_and_seek_residual(drflac* pFlac, unsigned int blockSize, unsigned int order) +/* +Reads and seeks past the residual for the sub-frame the decoder is currently sitting on. This function should be called +when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be set to 0. The + and parameters are used to determine how many residual values need to be decoded. +*/ +static drflac_bool32 drflac__read_and_seek_residual(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 order) { - assert(pFlac != NULL); - assert(blockSize != 0); + drflac_uint8 residualMethod; + drflac_uint8 partitionOrder; + drflac_uint32 samplesInPartition; + drflac_uint32 partitionsRemaining; - unsigned char residualMethod; - if (!drflac__read_uint8(pFlac, 2, &residualMethod)) { - return false; + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(blockSize != 0); + + if (!drflac__read_uint8(bs, 2, &residualMethod)) { + return DRFLAC_FALSE; } if (residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE && residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { - return false; // Unknown or unsupported residual coding method. + return DRFLAC_FALSE; /* Unknown or unsupported residual coding method. */ } - unsigned char partitionOrder; - if (!drflac__read_uint8(pFlac, 4, &partitionOrder)) { - return false; + if (!drflac__read_uint8(bs, 4, &partitionOrder)) { + return DRFLAC_FALSE; } - unsigned int samplesInPartition = (blockSize / (1 << partitionOrder)) - order; - unsigned int partitionsRemaining = (1 << partitionOrder); + /* + From the FLAC spec: + The Rice partition order in a Rice-coded residual section must be less than or equal to 8. + */ + if (partitionOrder > 8) { + return DRFLAC_FALSE; + } + + /* Validation check. */ + if ((blockSize / (1 << partitionOrder)) <= order) { + return DRFLAC_FALSE; + } + + samplesInPartition = (blockSize / (1 << partitionOrder)) - order; + partitionsRemaining = (1 << partitionOrder); for (;;) { - unsigned char riceParam = 0; + drflac_uint8 riceParam = 0; if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE) { - if (!drflac__read_uint8(pFlac, 4, &riceParam)) { - return false; + if (!drflac__read_uint8(bs, 4, &riceParam)) { + return DRFLAC_FALSE; } - if (riceParam == 16) { + if (riceParam == 15) { riceParam = 0xFF; } } else if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { - if (!drflac__read_uint8(pFlac, 5, &riceParam)) { - return false; + if (!drflac__read_uint8(bs, 5, &riceParam)) { + return DRFLAC_FALSE; } - if (riceParam == 32) { + if (riceParam == 31) { riceParam = 0xFF; } } if (riceParam != 0xFF) { - if (!drflac__read_and_seek_residual__rice(pFlac, samplesInPartition, riceParam)) { - return false; + if (!drflac__read_and_seek_residual__rice(bs, samplesInPartition, riceParam)) { + return DRFLAC_FALSE; } } else { - unsigned char unencodedBitsPerSample = 0; - if (!drflac__read_uint8(pFlac, 5, &unencodedBitsPerSample)) { - return false; + drflac_uint8 unencodedBitsPerSample = 0; + if (!drflac__read_uint8(bs, 5, &unencodedBitsPerSample)) { + return DRFLAC_FALSE; } - if (!drflac__seek_bits(pFlac, unencodedBitsPerSample * samplesInPartition)) { - return false; + if (!drflac__seek_bits(bs, unencodedBitsPerSample * samplesInPartition)) { + return DRFLAC_FALSE; } } @@ -1748,44 +5000,52 @@ static bool drflac__read_and_seek_residual(drflac* pFlac, unsigned int blockSize samplesInPartition = blockSize / (1 << partitionOrder); } - return true; + return DRFLAC_TRUE; } -static bool drflac__decode_samples__constant(drflac* pFlac, drflac_subframe* pSubframe) +static drflac_bool32 drflac__decode_samples__constant(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 subframeBitsPerSample, drflac_int32* pDecodedSamples) { - // Only a single sample needs to be decoded here. - int sample; - if (!drflac__read_int32(pFlac, pSubframe->bitsPerSample, &sample)) { - return false; + drflac_uint32 i; + + /* Only a single sample needs to be decoded here. */ + drflac_int32 sample; + if (!drflac__read_int32(bs, subframeBitsPerSample, &sample)) { + return DRFLAC_FALSE; } - // We don't really need to expand this, but it does simplify the process of reading samples. If this becomes a performance issue (unlikely) - // we'll want to look at a more efficient way. - for (unsigned int i = 0; i < pFlac->currentFrame.blockSize; ++i) { - pSubframe->pDecodedSamples[i] = sample; + /* + We don't really need to expand this, but it does simplify the process of reading samples. If this becomes a performance issue (unlikely) + we'll want to look at a more efficient way. + */ + for (i = 0; i < blockSize; ++i) { + pDecodedSamples[i] = sample; } - return true; + return DRFLAC_TRUE; } -static bool drflac__decode_samples__verbatim(drflac* pFlac, drflac_subframe* pSubframe) +static drflac_bool32 drflac__decode_samples__verbatim(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 subframeBitsPerSample, drflac_int32* pDecodedSamples) { - for (unsigned int i = 0; i < pFlac->currentFrame.blockSize; ++i) { - int sample; - if (!drflac__read_int32(pFlac, pSubframe->bitsPerSample, &sample)) { - return false; + drflac_uint32 i; + + for (i = 0; i < blockSize; ++i) { + drflac_int32 sample; + if (!drflac__read_int32(bs, subframeBitsPerSample, &sample)) { + return DRFLAC_FALSE; } - pSubframe->pDecodedSamples[i] = sample; + pDecodedSamples[i] = sample; } - return true; + return DRFLAC_TRUE; } -static bool drflac__decode_samples__fixed(drflac* pFlac, drflac_subframe* pSubframe) +static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 subframeBitsPerSample, drflac_uint8 lpcOrder, drflac_int32* pDecodedSamples) { - short lpcCoefficientsTable[5][4] = { + drflac_uint32 i; + + static drflac_int32 lpcCoefficientsTable[5][4] = { {0, 0, 0, 0}, {1, 0, 0, 0}, {2, -1, 0, 0}, @@ -1793,210 +5053,279 @@ static bool drflac__decode_samples__fixed(drflac* pFlac, drflac_subframe* pSubfr {4, -6, 4, -1} }; - // Warm up samples and coefficients. - for (unsigned int i = 0; i < pSubframe->lpcOrder; ++i) { - int sample; - if (!drflac__read_int32(pFlac, pSubframe->bitsPerSample, &sample)) { - return false; + /* Warm up samples and coefficients. */ + for (i = 0; i < lpcOrder; ++i) { + drflac_int32 sample; + if (!drflac__read_int32(bs, subframeBitsPerSample, &sample)) { + return DRFLAC_FALSE; } - pSubframe->pDecodedSamples[i] = sample; + pDecodedSamples[i] = sample; } - - if (!drflac__decode_samples_with_residual(pFlac, pFlac->currentFrame.blockSize, pSubframe->lpcOrder, 0, lpcCoefficientsTable[pSubframe->lpcOrder], pSubframe->pDecodedSamples)) { - return false; + if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, 4, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { + return DRFLAC_FALSE; } - return true; + return DRFLAC_TRUE; } -static bool drflac__decode_samples__lpc(drflac* pFlac, drflac_subframe* pSubframe) +static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_uint8 lpcOrder, drflac_int32* pDecodedSamples) { - // Warm up samples. - for (unsigned int i = 0; i < pSubframe->lpcOrder; ++i) { - int sample; - if (!drflac__read_int32(pFlac, pSubframe->bitsPerSample, &sample)) { - return false; + drflac_uint8 i; + drflac_uint8 lpcPrecision; + drflac_int8 lpcShift; + drflac_int32 coefficients[32]; + + /* Warm up samples. */ + for (i = 0; i < lpcOrder; ++i) { + drflac_int32 sample; + if (!drflac__read_int32(bs, bitsPerSample, &sample)) { + return DRFLAC_FALSE; } - pSubframe->pDecodedSamples[i] = sample; + pDecodedSamples[i] = sample; } - unsigned char lpcPrecision; - if (!drflac__read_uint8(pFlac, 4, &lpcPrecision)) { - return false; + if (!drflac__read_uint8(bs, 4, &lpcPrecision)) { + return DRFLAC_FALSE; } if (lpcPrecision == 15) { - return false; // Invalid. + return DRFLAC_FALSE; /* Invalid. */ } lpcPrecision += 1; - - signed char lpcShift; - if (!drflac__read_int8(pFlac, 5, &lpcShift)) { - return false; + if (!drflac__read_int8(bs, 5, &lpcShift)) { + return DRFLAC_FALSE; } + /* + From the FLAC specification: - short coefficients[32]; - for (unsigned int i = 0; i < pSubframe->lpcOrder; ++i) { - if (!drflac__read_int16(pFlac, lpcPrecision, coefficients + i)) { - return false; + Quantized linear predictor coefficient shift needed in bits (NOTE: this number is signed two's-complement) + + Emphasis on the "signed two's-complement". In practice there does not seem to be any encoders nor decoders supporting negative shifts. For now dr_flac is + not going to support negative shifts as I don't have any reference files. However, when a reference file comes through I will consider adding support. + */ + if (lpcShift < 0) { + return DRFLAC_FALSE; + } + + DRFLAC_ZERO_MEMORY(coefficients, sizeof(coefficients)); + for (i = 0; i < lpcOrder; ++i) { + if (!drflac__read_int32(bs, lpcPrecision, coefficients + i)) { + return DRFLAC_FALSE; } } - if (!drflac__decode_samples_with_residual(pFlac, pFlac->currentFrame.blockSize, pSubframe->lpcOrder, lpcShift, coefficients, pSubframe->pDecodedSamples)) { - return false; + if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { + return DRFLAC_FALSE; } - return true; + return DRFLAC_TRUE; } -static bool drflac__read_next_frame_header(drflac* pFlac) +static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_uint8 streaminfoBitsPerSample, drflac_frame_header* header) { - assert(pFlac != NULL); - assert(pFlac->onRead != NULL); + const drflac_uint32 sampleRateTable[12] = {0, 88200, 176400, 192000, 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000}; + const drflac_uint8 bitsPerSampleTable[8] = {0, 8, 12, (drflac_uint8)-1, 16, 20, 24, (drflac_uint8)-1}; /* -1 = reserved. */ - // At the moment the sync code is as a form of basic validation. The CRC is stored, but is unused at the moment. This - // should probably be handled better in the future. + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(header != NULL); - const int sampleRateTable[12] = {0, 88200, 176400, 192000, 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000}; - const uint8_t bitsPerSampleTable[8] = {0, 8, 12, (uint8_t)-1, 16, 20, 24, (uint8_t)-1}; // -1 = reserved. + /* Keep looping until we find a valid sync code. */ + for (;;) { + drflac_uint8 crc8 = 0xCE; /* 0xCE = drflac_crc8(0, 0x3FFE, 14); */ + drflac_uint8 reserved = 0; + drflac_uint8 blockingStrategy = 0; + drflac_uint8 blockSize = 0; + drflac_uint8 sampleRate = 0; + drflac_uint8 channelAssignment = 0; + drflac_uint8 bitsPerSample = 0; + drflac_bool32 isVariableBlockSize; - unsigned short syncCode = 0; - if (!drflac__read_uint16(pFlac, 14, &syncCode)) { - return false; - } - - if (syncCode != 0x3FFE) { - // TODO: Try and recover by attempting to seek to and read the next frame? - return false; - } - - unsigned char reserved; - if (!drflac__read_uint8(pFlac, 1, &reserved)) { - return false; - } - - unsigned char blockingStrategy = 0; - if (!drflac__read_uint8(pFlac, 1, &blockingStrategy)) { - return false; - } - - - - unsigned char blockSize = 0; - if (!drflac__read_uint8(pFlac, 4, &blockSize)) { - return false; - } - - unsigned char sampleRate = 0; - if (!drflac__read_uint8(pFlac, 4, &sampleRate)) { - return false; - } - - unsigned char channelAssignment = 0; - if (!drflac__read_uint8(pFlac, 4, &channelAssignment)) { - return false; - } - - unsigned char bitsPerSample = 0; - if (!drflac__read_uint8(pFlac, 3, &bitsPerSample)) { - return false; - } - - if (!drflac__read_uint8(pFlac, 1, &reserved)) { - return false; - } - - - unsigned char isVariableBlockSize = blockingStrategy == 1; - if (isVariableBlockSize) { - pFlac->currentFrame.frameNumber = 0; - if (!drflac__read_utf8_coded_number(pFlac, &pFlac->currentFrame.sampleNumber)) { - return false; + if (!drflac__find_and_seek_to_next_sync_code(bs)) { + return DRFLAC_FALSE; } - } else { - unsigned long long frameNumber = 0; - if (!drflac__read_utf8_coded_number(pFlac, &frameNumber)) { - return false; + + if (!drflac__read_uint8(bs, 1, &reserved)) { + return DRFLAC_FALSE; } - pFlac->currentFrame.frameNumber = (unsigned int)frameNumber; // <-- Safe cast. - pFlac->currentFrame.sampleNumber = 0; + if (reserved == 1) { + continue; + } + crc8 = drflac_crc8(crc8, reserved, 1); + + if (!drflac__read_uint8(bs, 1, &blockingStrategy)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, blockingStrategy, 1); + + if (!drflac__read_uint8(bs, 4, &blockSize)) { + return DRFLAC_FALSE; + } + if (blockSize == 0) { + continue; + } + crc8 = drflac_crc8(crc8, blockSize, 4); + + if (!drflac__read_uint8(bs, 4, &sampleRate)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, sampleRate, 4); + + if (!drflac__read_uint8(bs, 4, &channelAssignment)) { + return DRFLAC_FALSE; + } + if (channelAssignment > 10) { + continue; + } + crc8 = drflac_crc8(crc8, channelAssignment, 4); + + if (!drflac__read_uint8(bs, 3, &bitsPerSample)) { + return DRFLAC_FALSE; + } + if (bitsPerSample == 3 || bitsPerSample == 7) { + continue; + } + crc8 = drflac_crc8(crc8, bitsPerSample, 3); + + + if (!drflac__read_uint8(bs, 1, &reserved)) { + return DRFLAC_FALSE; + } + if (reserved == 1) { + continue; + } + crc8 = drflac_crc8(crc8, reserved, 1); + + + isVariableBlockSize = blockingStrategy == 1; + if (isVariableBlockSize) { + drflac_uint64 pcmFrameNumber; + drflac_result result = drflac__read_utf8_coded_number(bs, &pcmFrameNumber, &crc8); + if (result != DRFLAC_SUCCESS) { + if (result == DRFLAC_AT_END) { + return DRFLAC_FALSE; + } else { + continue; + } + } + header->flacFrameNumber = 0; + header->pcmFrameNumber = pcmFrameNumber; + } else { + drflac_uint64 flacFrameNumber = 0; + drflac_result result = drflac__read_utf8_coded_number(bs, &flacFrameNumber, &crc8); + if (result != DRFLAC_SUCCESS) { + if (result == DRFLAC_AT_END) { + return DRFLAC_FALSE; + } else { + continue; + } + } + header->flacFrameNumber = (drflac_uint32)flacFrameNumber; /* <-- Safe cast. */ + header->pcmFrameNumber = 0; + } + + + DRFLAC_ASSERT(blockSize > 0); + if (blockSize == 1) { + header->blockSizeInPCMFrames = 192; + } else if (blockSize <= 5) { + DRFLAC_ASSERT(blockSize >= 2); + header->blockSizeInPCMFrames = 576 * (1 << (blockSize - 2)); + } else if (blockSize == 6) { + if (!drflac__read_uint16(bs, 8, &header->blockSizeInPCMFrames)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 8); + header->blockSizeInPCMFrames += 1; + } else if (blockSize == 7) { + if (!drflac__read_uint16(bs, 16, &header->blockSizeInPCMFrames)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 16); + if (header->blockSizeInPCMFrames == 0xFFFF) { + return DRFLAC_FALSE; /* Frame is too big. This is the size of the frame minus 1. The STREAMINFO block defines the max block size which is 16-bits. Adding one will make it 17 bits and therefore too big. */ + } + header->blockSizeInPCMFrames += 1; + } else { + DRFLAC_ASSERT(blockSize >= 8); + header->blockSizeInPCMFrames = 256 * (1 << (blockSize - 8)); + } + + + if (sampleRate <= 11) { + header->sampleRate = sampleRateTable[sampleRate]; + } else if (sampleRate == 12) { + if (!drflac__read_uint32(bs, 8, &header->sampleRate)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->sampleRate, 8); + header->sampleRate *= 1000; + } else if (sampleRate == 13) { + if (!drflac__read_uint32(bs, 16, &header->sampleRate)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->sampleRate, 16); + } else if (sampleRate == 14) { + if (!drflac__read_uint32(bs, 16, &header->sampleRate)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->sampleRate, 16); + header->sampleRate *= 10; + } else { + continue; /* Invalid. Assume an invalid block. */ + } + + + header->channelAssignment = channelAssignment; + + header->bitsPerSample = bitsPerSampleTable[bitsPerSample]; + if (header->bitsPerSample == 0) { + header->bitsPerSample = streaminfoBitsPerSample; + } + + if (header->bitsPerSample != streaminfoBitsPerSample) { + /* If this subframe has a different bitsPerSample then streaminfo or the first frame, reject it */ + return DRFLAC_FALSE; + } + + if (!drflac__read_uint8(bs, 8, &header->crc8)) { + return DRFLAC_FALSE; + } + +#ifndef DR_FLAC_NO_CRC + if (header->crc8 != crc8) { + continue; /* CRC mismatch. Loop back to the top and find the next sync code. */ + } +#endif + return DRFLAC_TRUE; } - - - if (blockSize == 1) { - pFlac->currentFrame.blockSize = 192; - } else if (blockSize >= 2 && blockSize <= 5) { - pFlac->currentFrame.blockSize = 576 * (1 << (blockSize - 2)); - } else if (blockSize == 6) { - if (!drflac__read_uint16(pFlac, 8, &pFlac->currentFrame.blockSize)) { - return false; - } - pFlac->currentFrame.blockSize += 1; - } else if (blockSize == 7) { - if (!drflac__read_uint16(pFlac, 16, &pFlac->currentFrame.blockSize)) { - return false; - } - pFlac->currentFrame.blockSize += 1; - } else { - pFlac->currentFrame.blockSize = 256 * (1 << (blockSize - 8)); - } - - - if (sampleRate <= 11) { - pFlac->currentFrame.sampleRate = sampleRateTable[sampleRate]; - } else if (sampleRate == 12) { - if (!drflac__read_uint32(pFlac, 8, &pFlac->currentFrame.sampleRate)) { - return false; - } - pFlac->currentFrame.sampleRate *= 1000; - } else if (sampleRate == 13) { - if (!drflac__read_uint32(pFlac, 16, &pFlac->currentFrame.sampleRate)) { - return false; - } - } else if (sampleRate == 14) { - if (!drflac__read_uint32(pFlac, 16, &pFlac->currentFrame.sampleRate)) { - return false; - } - pFlac->currentFrame.sampleRate *= 10; - } else { - return false; // Invalid. - } - - - pFlac->currentFrame.channelAssignment = channelAssignment; - - pFlac->currentFrame.bitsPerSample = bitsPerSampleTable[bitsPerSample]; - if (pFlac->currentFrame.bitsPerSample == 0) { - pFlac->currentFrame.bitsPerSample = pFlac->bitsPerSample; - } - - if (drflac__read_uint8(pFlac, 8, &pFlac->currentFrame.crc8) != 1) { - return false; - } - - memset(pFlac->currentFrame.subframes, 0, sizeof(pFlac->currentFrame.subframes)); - - return true; } -static bool drflac__read_subframe_header(drflac* pFlac, drflac_subframe* pSubframe) +static drflac_bool32 drflac__read_subframe_header(drflac_bs* bs, drflac_subframe* pSubframe) { - unsigned char header; - if (!drflac__read_uint8(pFlac, 8, &header)) { - return false; + drflac_uint8 header; + int type; + + if (!drflac__read_uint8(bs, 8, &header)) { + return DRFLAC_FALSE; } - // First bit should always be 0. + /* First bit should always be 0. */ if ((header & 0x80) != 0) { - return false; + return DRFLAC_FALSE; } - int type = (header & 0x7E) >> 1; + /* + Default to 0 for the LPC order. It's important that we always set this to 0 for non LPC + and FIXED subframes because we'll be using it in a generic validation check later. + */ + pSubframe->lpcOrder = 0; + + type = (header & 0x7E) >> 1; if (type == 0) { pSubframe->subframeType = DRFLAC_SUBFRAME_CONSTANT; } else if (type == 1) { @@ -2004,10 +5333,10 @@ static bool drflac__read_subframe_header(drflac* pFlac, drflac_subframe* pSubfra } else { if ((type & 0x20) != 0) { pSubframe->subframeType = DRFLAC_SUBFRAME_LPC; - pSubframe->lpcOrder = (type & 0x1F) + 1; + pSubframe->lpcOrder = (drflac_uint8)(type & 0x1F) + 1; } else if ((type & 0x08) != 0) { pSubframe->subframeType = DRFLAC_SUBFRAME_FIXED; - pSubframe->lpcOrder = (type & 0x07); + pSubframe->lpcOrder = (drflac_uint8)(type & 0x07); if (pSubframe->lpcOrder > 4) { pSubframe->subframeType = DRFLAC_SUBFRAME_RESERVED; pSubframe->lpcOrder = 0; @@ -2018,961 +5347,7308 @@ static bool drflac__read_subframe_header(drflac* pFlac, drflac_subframe* pSubfra } if (pSubframe->subframeType == DRFLAC_SUBFRAME_RESERVED) { - return false; + return DRFLAC_FALSE; } - // Wasted bits per sample. + /* Wasted bits per sample. */ pSubframe->wastedBitsPerSample = 0; if ((header & 0x01) == 1) { unsigned int wastedBitsPerSample; - if (!drflac__seek_past_next_set_bit(pFlac, &wastedBitsPerSample)) { - return false; + if (!drflac__seek_past_next_set_bit(bs, &wastedBitsPerSample)) { + return DRFLAC_FALSE; } - pSubframe->wastedBitsPerSample = (unsigned char)wastedBitsPerSample + 1; + pSubframe->wastedBitsPerSample = (drflac_uint8)wastedBitsPerSample + 1; } - return true; + return DRFLAC_TRUE; } -static bool drflac__decode_subframe(drflac* pFlac, int subframeIndex) +static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame, int subframeIndex, drflac_int32* pDecodedSamplesOut) { - assert(pFlac != NULL); + drflac_subframe* pSubframe; + drflac_uint32 subframeBitsPerSample; - drflac_subframe* pSubframe = pFlac->currentFrame.subframes + subframeIndex; - if (!drflac__read_subframe_header(pFlac, pSubframe)) { - return false; + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(frame != NULL); + + pSubframe = frame->subframes + subframeIndex; + if (!drflac__read_subframe_header(bs, pSubframe)) { + return DRFLAC_FALSE; } - // Side channels require an extra bit per sample. Took a while to figure that one out... - pSubframe->bitsPerSample = pFlac->currentFrame.bitsPerSample; - if ((pFlac->currentFrame.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE || pFlac->currentFrame.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE) && subframeIndex == 1) { - pSubframe->bitsPerSample += 1; - } else if (pFlac->currentFrame.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { - pSubframe->bitsPerSample += 1; + /* Side channels require an extra bit per sample. Took a while to figure that one out... */ + subframeBitsPerSample = frame->header.bitsPerSample; + if ((frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE || frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE) && subframeIndex == 1) { + subframeBitsPerSample += 1; + } else if (frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { + subframeBitsPerSample += 1; } - // Need to handle wasted bits per sample. - pSubframe->bitsPerSample -= pSubframe->wastedBitsPerSample; - pSubframe->pDecodedSamples = pFlac->pDecodedSamples + (pFlac->currentFrame.blockSize * subframeIndex); + if (subframeBitsPerSample > 32) { + /* libFLAC and ffmpeg reject 33-bit subframes as well */ + return DRFLAC_FALSE; + } + + /* Need to handle wasted bits per sample. */ + if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) { + return DRFLAC_FALSE; + } + subframeBitsPerSample -= pSubframe->wastedBitsPerSample; + + pSubframe->pSamplesS32 = pDecodedSamplesOut; + + /* + pDecodedSamplesOut will be pointing to a buffer that was allocated with enough memory to store + maxBlockSizeInPCMFrames samples (as specified in the FLAC header). We need to guard against an + overflow here. At a higher level we are checking maxBlockSizeInPCMFrames from the header, but + here we need to do an additional check to ensure this frame's block size fully encompasses any + warmup samples which is determined by the LPC order. For non LPC and FIXED subframes, the LPC + order will be have been set to 0 in drflac__read_subframe_header(). + */ + if (frame->header.blockSizeInPCMFrames < pSubframe->lpcOrder) { + return DRFLAC_FALSE; + } switch (pSubframe->subframeType) { case DRFLAC_SUBFRAME_CONSTANT: { - drflac__decode_samples__constant(pFlac, pSubframe); + drflac__decode_samples__constant(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->pSamplesS32); } break; case DRFLAC_SUBFRAME_VERBATIM: { - drflac__decode_samples__verbatim(pFlac, pSubframe); + drflac__decode_samples__verbatim(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->pSamplesS32); } break; case DRFLAC_SUBFRAME_FIXED: { - drflac__decode_samples__fixed(pFlac, pSubframe); + drflac__decode_samples__fixed(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->lpcOrder, pSubframe->pSamplesS32); } break; case DRFLAC_SUBFRAME_LPC: { - drflac__decode_samples__lpc(pFlac, pSubframe); + drflac__decode_samples__lpc(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->lpcOrder, pSubframe->pSamplesS32); } break; - default: return false; + default: return DRFLAC_FALSE; } - return true; + return DRFLAC_TRUE; } -static bool drflac__seek_subframe(drflac* pFlac, int subframeIndex) +static drflac_bool32 drflac__seek_subframe(drflac_bs* bs, drflac_frame* frame, int subframeIndex) { - assert(pFlac != NULL); + drflac_subframe* pSubframe; + drflac_uint32 subframeBitsPerSample; - drflac_subframe* pSubframe = pFlac->currentFrame.subframes + subframeIndex; - if (!drflac__read_subframe_header(pFlac, pSubframe)) { - return false; + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(frame != NULL); + + pSubframe = frame->subframes + subframeIndex; + if (!drflac__read_subframe_header(bs, pSubframe)) { + return DRFLAC_FALSE; } - // Side channels require an extra bit per sample. Took a while to figure that one out... - pSubframe->bitsPerSample = pFlac->currentFrame.bitsPerSample; - if ((pFlac->currentFrame.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE || pFlac->currentFrame.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE) && subframeIndex == 1) { - pSubframe->bitsPerSample += 1; - } else if (pFlac->currentFrame.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { - pSubframe->bitsPerSample += 1; + /* Side channels require an extra bit per sample. Took a while to figure that one out... */ + subframeBitsPerSample = frame->header.bitsPerSample; + if ((frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE || frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE) && subframeIndex == 1) { + subframeBitsPerSample += 1; + } else if (frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { + subframeBitsPerSample += 1; } - // Need to handle wasted bits per sample. - pSubframe->bitsPerSample -= pSubframe->wastedBitsPerSample; - pSubframe->pDecodedSamples = pFlac->pDecodedSamples + (pFlac->currentFrame.blockSize * subframeIndex); + /* Need to handle wasted bits per sample. */ + if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) { + return DRFLAC_FALSE; + } + subframeBitsPerSample -= pSubframe->wastedBitsPerSample; + + pSubframe->pSamplesS32 = NULL; switch (pSubframe->subframeType) { case DRFLAC_SUBFRAME_CONSTANT: { - if (!drflac__seek_bits(pFlac, pSubframe->bitsPerSample)) { - return false; + if (!drflac__seek_bits(bs, subframeBitsPerSample)) { + return DRFLAC_FALSE; } } break; case DRFLAC_SUBFRAME_VERBATIM: { - unsigned int bitsToSeek = pFlac->currentFrame.blockSize * pSubframe->bitsPerSample; - if (!drflac__seek_bits(pFlac, bitsToSeek)) { - return false; + unsigned int bitsToSeek = frame->header.blockSizeInPCMFrames * subframeBitsPerSample; + if (!drflac__seek_bits(bs, bitsToSeek)) { + return DRFLAC_FALSE; } } break; case DRFLAC_SUBFRAME_FIXED: { - unsigned int bitsToSeek = pSubframe->lpcOrder * pSubframe->bitsPerSample; - if (!drflac__seek_bits(pFlac, bitsToSeek)) { - return false; + unsigned int bitsToSeek = pSubframe->lpcOrder * subframeBitsPerSample; + if (!drflac__seek_bits(bs, bitsToSeek)) { + return DRFLAC_FALSE; } - if (!drflac__read_and_seek_residual(pFlac, pFlac->currentFrame.blockSize, pSubframe->lpcOrder)) { - return false; + if (!drflac__read_and_seek_residual(bs, frame->header.blockSizeInPCMFrames, pSubframe->lpcOrder)) { + return DRFLAC_FALSE; } } break; case DRFLAC_SUBFRAME_LPC: { - unsigned int bitsToSeek = pSubframe->lpcOrder * pSubframe->bitsPerSample; - if (!drflac__seek_bits(pFlac, bitsToSeek)) { - return false; + drflac_uint8 lpcPrecision; + + unsigned int bitsToSeek = pSubframe->lpcOrder * subframeBitsPerSample; + if (!drflac__seek_bits(bs, bitsToSeek)) { + return DRFLAC_FALSE; } - unsigned char lpcPrecision; - if (!drflac__read_uint8(pFlac, 4, &lpcPrecision)) { - return false; + if (!drflac__read_uint8(bs, 4, &lpcPrecision)) { + return DRFLAC_FALSE; } if (lpcPrecision == 15) { - return false; // Invalid. + return DRFLAC_FALSE; /* Invalid. */ } lpcPrecision += 1; - bitsToSeek = (pSubframe->lpcOrder * lpcPrecision) + 5; // +5 for shift. - if (!drflac__seek_bits(pFlac, bitsToSeek)) { - return false; + bitsToSeek = (pSubframe->lpcOrder * lpcPrecision) + 5; /* +5 for shift. */ + if (!drflac__seek_bits(bs, bitsToSeek)) { + return DRFLAC_FALSE; } - if (!drflac__read_and_seek_residual(pFlac, pFlac->currentFrame.blockSize, pSubframe->lpcOrder)) { - return false; + if (!drflac__read_and_seek_residual(bs, frame->header.blockSizeInPCMFrames, pSubframe->lpcOrder)) { + return DRFLAC_FALSE; } } break; - default: return false; + default: return DRFLAC_FALSE; } - return true; + return DRFLAC_TRUE; } -static DRFLAC_INLINE int drflac__get_channel_count_from_channel_assignment(int channelAssignment) +static DRFLAC_INLINE drflac_uint8 drflac__get_channel_count_from_channel_assignment(drflac_int8 channelAssignment) { - assert(channelAssignment <= 10); + drflac_uint8 lookup[] = {1, 2, 3, 4, 5, 6, 7, 8, 2, 2, 2}; - int lookup[] = {1, 2, 3, 4, 5, 6, 7, 8, 2, 2, 2}; + DRFLAC_ASSERT(channelAssignment <= 10); return lookup[channelAssignment]; } -static bool drflac__decode_frame(drflac* pFlac) +static drflac_result drflac__decode_flac_frame(drflac* pFlac) { - // This function should be called while the stream is sitting on the first byte after the frame header. + int channelCount; + int i; + drflac_uint8 paddingSizeInBits; + drflac_uint16 desiredCRC16; +#ifndef DR_FLAC_NO_CRC + drflac_uint16 actualCRC16; +#endif - int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.channelAssignment); - for (int i = 0; i < channelCount; ++i) - { - if (!drflac__decode_subframe(pFlac, i)) { - return false; + /* This function should be called while the stream is sitting on the first byte after the frame header. */ + DRFLAC_ZERO_MEMORY(pFlac->currentFLACFrame.subframes, sizeof(pFlac->currentFLACFrame.subframes)); + + /* The frame block size must never be larger than the maximum block size defined by the FLAC stream. */ + if (pFlac->currentFLACFrame.header.blockSizeInPCMFrames > pFlac->maxBlockSizeInPCMFrames) { + return DRFLAC_ERROR; + } + + /* The number of channels in the frame must match the channel count from the STREAMINFO block. */ + channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + if (channelCount != (int)pFlac->channels) { + return DRFLAC_ERROR; + } + + for (i = 0; i < channelCount; ++i) { + if (!drflac__decode_subframe(&pFlac->bs, &pFlac->currentFLACFrame, i, pFlac->pDecodedSamples + (pFlac->currentFLACFrame.header.blockSizeInPCMFrames * i))) { + return DRFLAC_ERROR; } } - // At the end of the frame sits the padding and CRC. We don't use these so we can just seek past. - if (!drflac__seek_bits(pFlac, (DRFLAC_CACHE_L1_BITS_REMAINING & 7) + 16)) { - return false; - } - - - pFlac->currentFrame.samplesRemaining = pFlac->currentFrame.blockSize * channelCount; - - return true; -} - -static bool drflac__seek_frame(drflac* pFlac) -{ - int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.channelAssignment); - for (int i = 0; i < channelCount; ++i) - { - if (!drflac__seek_subframe(pFlac, i)) { - return false; + paddingSizeInBits = (drflac_uint8)(DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7); + if (paddingSizeInBits > 0) { + drflac_uint8 padding = 0; + if (!drflac__read_uint8(&pFlac->bs, paddingSizeInBits, &padding)) { + return DRFLAC_AT_END; } } - // Padding and CRC. - return drflac__seek_bits(pFlac, (DRFLAC_CACHE_L1_BITS_REMAINING & 7) + 16); +#ifndef DR_FLAC_NO_CRC + actualCRC16 = drflac__flush_crc16(&pFlac->bs); +#endif + if (!drflac__read_uint16(&pFlac->bs, 16, &desiredCRC16)) { + return DRFLAC_AT_END; + } + +#ifndef DR_FLAC_NO_CRC + if (actualCRC16 != desiredCRC16) { + return DRFLAC_CRC_MISMATCH; /* CRC mismatch. */ + } +#endif + + pFlac->currentFLACFrame.pcmFramesRemaining = pFlac->currentFLACFrame.header.blockSizeInPCMFrames; + + return DRFLAC_SUCCESS; } -static bool drflac__read_and_decode_next_frame(drflac* pFlac) +static drflac_result drflac__seek_flac_frame(drflac* pFlac) { - assert(pFlac != NULL); + int channelCount; + int i; + drflac_uint16 desiredCRC16; +#ifndef DR_FLAC_NO_CRC + drflac_uint16 actualCRC16; +#endif - if (!drflac__read_next_frame_header(pFlac)) { - return false; + channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + for (i = 0; i < channelCount; ++i) { + if (!drflac__seek_subframe(&pFlac->bs, &pFlac->currentFLACFrame, i)) { + return DRFLAC_ERROR; + } } - return drflac__decode_frame(pFlac); + /* Padding. */ + if (!drflac__seek_bits(&pFlac->bs, DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7)) { + return DRFLAC_ERROR; + } + + /* CRC. */ +#ifndef DR_FLAC_NO_CRC + actualCRC16 = drflac__flush_crc16(&pFlac->bs); +#endif + if (!drflac__read_uint16(&pFlac->bs, 16, &desiredCRC16)) { + return DRFLAC_AT_END; + } + +#ifndef DR_FLAC_NO_CRC + if (actualCRC16 != desiredCRC16) { + return DRFLAC_CRC_MISMATCH; /* CRC mismatch. */ + } +#endif + + return DRFLAC_SUCCESS; } -static unsigned int drflac__read_block_header(drflac* pFlac, unsigned int* pBlockSizeOut, bool* pIsLastBlockOut) // Returns the block type. +static drflac_bool32 drflac__read_and_decode_next_flac_frame(drflac* pFlac) { - assert(pFlac != NULL); + DRFLAC_ASSERT(pFlac != NULL); - unsigned char isLastBlock = 1; - unsigned char blockType = DRFLAC_BLOCK_TYPE_INVALID; - unsigned int blockSize = 0; + for (;;) { + drflac_result result; - if (!drflac__read_uint8(pFlac, 1, &isLastBlock)) { - goto done_reading_block_header; - } + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } - if (!drflac__read_uint8(pFlac, 7, &blockType)) { - goto done_reading_block_header; - } + result = drflac__decode_flac_frame(pFlac); + if (result != DRFLAC_SUCCESS) { + if (result == DRFLAC_CRC_MISMATCH) { + continue; /* CRC mismatch. Skip to the next frame. */ + } else { + return DRFLAC_FALSE; + } + } - if (!drflac__read_uint32(pFlac, 24, &blockSize)) { - goto done_reading_block_header; - } - - -done_reading_block_header: - if (pBlockSizeOut) { - *pBlockSizeOut = blockSize; - } - - if (pIsLastBlockOut) { - *pIsLastBlockOut = (isLastBlock != 0); - } - - return blockType; -} - - -static void drflac__get_current_frame_sample_range(drflac* pFlac, uint64_t* pFirstSampleInFrameOut, uint64_t* pLastSampleInFrameOut) -{ - assert(pFlac != NULL); - - unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.channelAssignment); - - uint64_t firstSampleInFrame = pFlac->currentFrame.sampleNumber; - if (firstSampleInFrame == 0) { - firstSampleInFrame = pFlac->currentFrame.frameNumber * pFlac->maxBlockSize*channelCount; - } - - uint64_t lastSampleInFrame = firstSampleInFrame + (pFlac->currentFrame.blockSize*channelCount); - if (lastSampleInFrame > 0) { - lastSampleInFrame -= 1; // Needs to be zero based. - } - - - if (pFirstSampleInFrameOut) { - *pFirstSampleInFrameOut = firstSampleInFrame; - } - if (pLastSampleInFrameOut) { - *pLastSampleInFrameOut = lastSampleInFrame; + return DRFLAC_TRUE; } } -static bool drflac__seek_to_first_frame(drflac* pFlac) +static void drflac__get_pcm_frame_range_of_current_flac_frame(drflac* pFlac, drflac_uint64* pFirstPCMFrame, drflac_uint64* pLastPCMFrame) { - assert(pFlac != NULL); + drflac_uint64 firstPCMFrame; + drflac_uint64 lastPCMFrame; - bool result = drflac__seek_to_byte(pFlac, (long long)pFlac->firstFramePos); - pFlac->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS; - pFlac->cache = 0; + DRFLAC_ASSERT(pFlac != NULL); - memset(&pFlac->currentFrame, 0, sizeof(pFlac->currentFrame)); + firstPCMFrame = pFlac->currentFLACFrame.header.pcmFrameNumber; + if (firstPCMFrame == 0) { + firstPCMFrame = ((drflac_uint64)pFlac->currentFLACFrame.header.flacFrameNumber) * pFlac->maxBlockSizeInPCMFrames; + } + lastPCMFrame = firstPCMFrame + pFlac->currentFLACFrame.header.blockSizeInPCMFrames; + if (lastPCMFrame > 0) { + lastPCMFrame -= 1; /* Needs to be zero based. */ + } + + if (pFirstPCMFrame) { + *pFirstPCMFrame = firstPCMFrame; + } + if (pLastPCMFrame) { + *pLastPCMFrame = lastPCMFrame; + } +} + +static drflac_bool32 drflac__seek_to_first_frame(drflac* pFlac) +{ + drflac_bool32 result; + + DRFLAC_ASSERT(pFlac != NULL); + + result = drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes); + + DRFLAC_ZERO_MEMORY(&pFlac->currentFLACFrame, sizeof(pFlac->currentFLACFrame)); + pFlac->currentPCMFrame = 0; return result; } -static DRFLAC_INLINE bool drflac__seek_to_next_frame(drflac* pFlac) +static DRFLAC_INLINE drflac_result drflac__seek_to_next_flac_frame(drflac* pFlac) { - // This function should only ever be called while the decoder is sitting on the first byte past the FRAME_HEADER section. - assert(pFlac != NULL); - return drflac__seek_frame(pFlac); -} - -static bool drflac__seek_to_frame_containing_sample(drflac* pFlac, uint64_t sampleIndex) -{ - assert(pFlac != NULL); - - if (!drflac__seek_to_first_frame(pFlac)) { - return false; - } - - uint64_t firstSampleInFrame = 0; - uint64_t lastSampleInFrame = 0; - for (;;) - { - // We need to read the frame's header in order to determine the range of samples it contains. - if (!drflac__read_next_frame_header(pFlac)) { - return false; - } - - drflac__get_current_frame_sample_range(pFlac, &firstSampleInFrame, &lastSampleInFrame); - if (sampleIndex >= firstSampleInFrame && sampleIndex <= lastSampleInFrame) { - break; // The sample is in this frame. - } - - if (!drflac__seek_to_next_frame(pFlac)) { - return false; - } - } - - // If we get here we should be right at the start of the frame containing the sample. - return true; -} - -static bool drflac__seek_to_sample__brute_force(drflac* pFlac, uint64_t sampleIndex) -{ - if (!drflac__seek_to_frame_containing_sample(pFlac, sampleIndex)) { - return false; - } - - // At this point we should be sitting on the first byte of the frame containing the sample. We need to decode every sample up to (but - // not including) the sample we're seeking to. - uint64_t firstSampleInFrame = 0; - drflac__get_current_frame_sample_range(pFlac, &firstSampleInFrame, NULL); - - assert(firstSampleInFrame <= sampleIndex); - size_t samplesToDecode = (size_t)(sampleIndex - firstSampleInFrame); // <-- Safe cast because the maximum number of samples in a frame is 65535. - if (samplesToDecode == 0) { - return true; - } - - // At this point we are just sitting on the byte after the frame header. We need to decode the frame before reading anything from it. - if (!drflac__decode_frame(pFlac)) { - return false; - } - - return (drflac_read_s16(pFlac, samplesToDecode, NULL) != 0); -} - -static bool drflac__seek_to_sample__seek_table(drflac* pFlac, uint64_t sampleIndex) -{ - assert(pFlac != NULL); - - if (pFlac->seektableBlock.pos == 0) { - return false; - } - - if (!drflac__seek_to_byte(pFlac, pFlac->seektableBlock.pos)) { - return false; - } - - // The number of seek points is derived from the size of the SEEKTABLE block. - unsigned int seekpointCount = pFlac->seektableBlock.sizeInBytes / 18; // 18 = the size of each seek point. - if (seekpointCount == 0) { - return false; // Would this ever happen? - } - - - drflac_seekpoint closestSeekpoint = {0}; - - unsigned int seekpointsRemaining = seekpointCount; - while (seekpointsRemaining > 0) - { - drflac_seekpoint seekpoint; - if (!drflac__read_uint64(pFlac, 64, &seekpoint.firstSample)) { - break; - } - if (!drflac__read_uint64(pFlac, 64, &seekpoint.frameOffset)) { - break; - } - if (!drflac__read_uint16(pFlac, 16, &seekpoint.sampleCount)) { - break; - } - - if (seekpoint.firstSample * pFlac->channels > sampleIndex) { - break; - } - - closestSeekpoint = seekpoint; - seekpointsRemaining -= 1; - } - - // At this point we should have found the seekpoint closest to our sample. We need to seek to it using basically the same - // technique as we use with the brute force method. - drflac__seek_to_byte(pFlac, pFlac->firstFramePos + closestSeekpoint.frameOffset); - - uint64_t firstSampleInFrame = 0; - uint64_t lastSampleInFrame = 0; - for (;;) - { - // We need to read the frame's header in order to determine the range of samples it contains. - if (!drflac__read_next_frame_header(pFlac)) { - return false; - } - - drflac__get_current_frame_sample_range(pFlac, &firstSampleInFrame, &lastSampleInFrame); - if (sampleIndex >= firstSampleInFrame && sampleIndex <= lastSampleInFrame) { - break; // The sample is in this frame. - } - - if (!drflac__seek_to_next_frame(pFlac)) { - return false; - } - } - - assert(firstSampleInFrame <= sampleIndex); - - // At this point we are just sitting on the byte after the frame header. We need to decode the frame before reading anything from it. - if (!drflac__decode_frame(pFlac)) { - return false; - } - - size_t samplesToDecode = (size_t)(sampleIndex - firstSampleInFrame); // <-- Safe cast because the maximum number of samples in a frame is 65535. - return drflac_read_s16(pFlac, samplesToDecode, NULL) == samplesToDecode; + /* This function should only ever be called while the decoder is sitting on the first byte past the FRAME_HEADER section. */ + DRFLAC_ASSERT(pFlac != NULL); + return drflac__seek_flac_frame(pFlac); } -static drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData) +static drflac_uint64 drflac__seek_forward_by_pcm_frames(drflac* pFlac, drflac_uint64 pcmFramesToSeek) { - if (onRead == NULL || onSeek == NULL) { + drflac_uint64 pcmFramesRead = 0; + while (pcmFramesToSeek > 0) { + if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + break; /* Couldn't read the next frame, so just break from the loop and return. */ + } + } else { + if (pFlac->currentFLACFrame.pcmFramesRemaining > pcmFramesToSeek) { + pcmFramesRead += pcmFramesToSeek; + pFlac->currentFLACFrame.pcmFramesRemaining -= (drflac_uint32)pcmFramesToSeek; /* <-- Safe cast. Will always be < currentFrame.pcmFramesRemaining < 65536. */ + pcmFramesToSeek = 0; + } else { + pcmFramesRead += pFlac->currentFLACFrame.pcmFramesRemaining; + pcmFramesToSeek -= pFlac->currentFLACFrame.pcmFramesRemaining; + pFlac->currentFLACFrame.pcmFramesRemaining = 0; + } + } + } + + pFlac->currentPCMFrame += pcmFramesRead; + return pcmFramesRead; +} + + +static drflac_bool32 drflac__seek_to_pcm_frame__brute_force(drflac* pFlac, drflac_uint64 pcmFrameIndex) +{ + drflac_bool32 isMidFrame = DRFLAC_FALSE; + drflac_uint64 runningPCMFrameCount; + + DRFLAC_ASSERT(pFlac != NULL); + + /* If we are seeking forward we start from the current position. Otherwise we need to start all the way from the start of the file. */ + if (pcmFrameIndex >= pFlac->currentPCMFrame) { + /* Seeking forward. Need to seek from the current position. */ + runningPCMFrameCount = pFlac->currentPCMFrame; + + /* The frame header for the first frame may not yet have been read. We need to do that if necessary. */ + if (pFlac->currentPCMFrame == 0 && pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + } else { + isMidFrame = DRFLAC_TRUE; + } + } else { + /* Seeking backwards. Need to seek from the start of the file. */ + runningPCMFrameCount = 0; + + /* Move back to the start. */ + if (!drflac__seek_to_first_frame(pFlac)) { + return DRFLAC_FALSE; + } + + /* Decode the first frame in preparation for sample-exact seeking below. */ + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + } + + /* + We need to as quickly as possible find the frame that contains the target sample. To do this, we iterate over each frame and inspect its + header. If based on the header we can determine that the frame contains the sample, we do a full decode of that frame. + */ + for (;;) { + drflac_uint64 pcmFrameCountInThisFLACFrame; + drflac_uint64 firstPCMFrameInFLACFrame = 0; + drflac_uint64 lastPCMFrameInFLACFrame = 0; + + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &firstPCMFrameInFLACFrame, &lastPCMFrameInFLACFrame); + + pcmFrameCountInThisFLACFrame = (lastPCMFrameInFLACFrame - firstPCMFrameInFLACFrame) + 1; + if (pcmFrameIndex < (runningPCMFrameCount + pcmFrameCountInThisFLACFrame)) { + /* + The sample should be in this frame. We need to fully decode it, however if it's an invalid frame (a CRC mismatch), we need to pretend + it never existed and keep iterating. + */ + drflac_uint64 pcmFramesToDecode = pcmFrameIndex - runningPCMFrameCount; + + if (!isMidFrame) { + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + /* The frame is valid. We just need to skip over some samples to ensure it's sample-exact. */ + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ + } else { + if (result == DRFLAC_CRC_MISMATCH) { + goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ + } else { + return DRFLAC_FALSE; + } + } + } else { + /* We started seeking mid-frame which means we need to skip the frame decoding part. */ + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; + } + } else { + /* + It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this + frame never existed and leave the running sample count untouched. + */ + if (!isMidFrame) { + drflac_result result = drflac__seek_to_next_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + runningPCMFrameCount += pcmFrameCountInThisFLACFrame; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ + } else { + return DRFLAC_FALSE; + } + } + } else { + /* + We started seeking mid-frame which means we need to seek by reading to the end of the frame instead of with + drflac__seek_to_next_flac_frame() which only works if the decoder is sitting on the byte just after the frame header. + */ + runningPCMFrameCount += pFlac->currentFLACFrame.pcmFramesRemaining; + pFlac->currentFLACFrame.pcmFramesRemaining = 0; + isMidFrame = DRFLAC_FALSE; + } + + /* If we are seeking to the end of the file and we've just hit it, we're done. */ + if (pcmFrameIndex == pFlac->totalPCMFrameCount && runningPCMFrameCount == pFlac->totalPCMFrameCount) { + return DRFLAC_TRUE; + } + } + + next_iteration: + /* Grab the next frame in preparation for the next iteration. */ + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + } +} + + +#if !defined(DR_FLAC_NO_CRC) +/* +We use an average compression ratio to determine our approximate start location. FLAC files are generally about 50%-70% the size of their +uncompressed counterparts so we'll use this as a basis. I'm going to split the middle and use a factor of 0.6 to determine the starting +location. +*/ +#define DRFLAC_BINARY_SEARCH_APPROX_COMPRESSION_RATIO 0.6f + +static drflac_bool32 drflac__seek_to_approximate_flac_frame_to_byte(drflac* pFlac, drflac_uint64 targetByte, drflac_uint64 rangeLo, drflac_uint64 rangeHi, drflac_uint64* pLastSuccessfulSeekOffset) +{ + DRFLAC_ASSERT(pFlac != NULL); + DRFLAC_ASSERT(pLastSuccessfulSeekOffset != NULL); + DRFLAC_ASSERT(targetByte >= rangeLo); + DRFLAC_ASSERT(targetByte <= rangeHi); + + *pLastSuccessfulSeekOffset = pFlac->firstFLACFramePosInBytes; + + for (;;) { + /* After rangeLo == rangeHi == targetByte fails, we need to break out. */ + drflac_uint64 lastTargetByte = targetByte; + + /* When seeking to a byte, failure probably means we've attempted to seek beyond the end of the stream. To counter this we just halve it each attempt. */ + if (!drflac__seek_to_byte(&pFlac->bs, targetByte)) { + /* If we couldn't even seek to the first byte in the stream we have a problem. Just abandon the whole thing. */ + if (targetByte == 0) { + drflac__seek_to_first_frame(pFlac); /* Try to recover. */ + return DRFLAC_FALSE; + } + + /* Halve the byte location and continue. */ + targetByte = rangeLo + ((rangeHi - rangeLo)/2); + rangeHi = targetByte; + } else { + /* Getting here should mean that we have seeked to an appropriate byte. */ + + /* Clear the details of the FLAC frame so we don't misreport data. */ + DRFLAC_ZERO_MEMORY(&pFlac->currentFLACFrame, sizeof(pFlac->currentFLACFrame)); + + /* + Now seek to the next FLAC frame. We need to decode the entire frame (not just the header) because it's possible for the header to incorrectly pass the + CRC check and return bad data. We need to decode the entire frame to be more certain. Although this seems unlikely, this has happened to me in testing + so it needs to stay this way for now. + */ +#if 1 + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + /* Halve the byte location and continue. */ + targetByte = rangeLo + ((rangeHi - rangeLo)/2); + rangeHi = targetByte; + } else { + break; + } +#else + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + /* Halve the byte location and continue. */ + targetByte = rangeLo + ((rangeHi - rangeLo)/2); + rangeHi = targetByte; + } else { + break; + } +#endif + } + + /* We already tried this byte and there are no more to try, break out. */ + if(targetByte == lastTargetByte) { + return DRFLAC_FALSE; + } + } + + /* The current PCM frame needs to be updated based on the frame we just seeked to. */ + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &pFlac->currentPCMFrame, NULL); + + DRFLAC_ASSERT(targetByte <= rangeHi); + + *pLastSuccessfulSeekOffset = targetByte; + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(drflac* pFlac, drflac_uint64 offset) +{ + /* This section of code would be used if we were only decoding the FLAC frame header when calling drflac__seek_to_approximate_flac_frame_to_byte(). */ +#if 0 + if (drflac__decode_flac_frame(pFlac) != DRFLAC_SUCCESS) { + /* We failed to decode this frame which may be due to it being corrupt. We'll just use the next valid FLAC frame. */ + if (drflac__read_and_decode_next_flac_frame(pFlac) == DRFLAC_FALSE) { + return DRFLAC_FALSE; + } + } +#endif + + return drflac__seek_forward_by_pcm_frames(pFlac, offset) == offset; +} + + +static drflac_bool32 drflac__seek_to_pcm_frame__binary_search_internal(drflac* pFlac, drflac_uint64 pcmFrameIndex, drflac_uint64 byteRangeLo, drflac_uint64 byteRangeHi) +{ + /* This assumes pFlac->currentPCMFrame is sitting on byteRangeLo upon entry. */ + + drflac_uint64 targetByte; + drflac_uint64 pcmRangeLo = pFlac->totalPCMFrameCount; + drflac_uint64 pcmRangeHi = 0; + drflac_uint64 lastSuccessfulSeekOffset = (drflac_uint64)-1; + drflac_uint64 closestSeekOffsetBeforeTargetPCMFrame = byteRangeLo; + drflac_uint32 seekForwardThreshold = (pFlac->maxBlockSizeInPCMFrames != 0) ? pFlac->maxBlockSizeInPCMFrames*2 : 4096; + + targetByte = byteRangeLo + (drflac_uint64)(((drflac_int64)((pcmFrameIndex - pFlac->currentPCMFrame) * pFlac->channels * pFlac->bitsPerSample)/8.0f) * DRFLAC_BINARY_SEARCH_APPROX_COMPRESSION_RATIO); + if (targetByte > byteRangeHi) { + targetByte = byteRangeHi; + } + + for (;;) { + if (drflac__seek_to_approximate_flac_frame_to_byte(pFlac, targetByte, byteRangeLo, byteRangeHi, &lastSuccessfulSeekOffset)) { + /* We found a FLAC frame. We need to check if it contains the sample we're looking for. */ + drflac_uint64 newPCMRangeLo; + drflac_uint64 newPCMRangeHi; + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &newPCMRangeLo, &newPCMRangeHi); + + /* If we selected the same frame, it means we should be pretty close. Just decode the rest. */ + if (pcmRangeLo == newPCMRangeLo) { + if (!drflac__seek_to_approximate_flac_frame_to_byte(pFlac, closestSeekOffsetBeforeTargetPCMFrame, closestSeekOffsetBeforeTargetPCMFrame, byteRangeHi, &lastSuccessfulSeekOffset)) { + break; /* Failed to seek to closest frame. */ + } + + if (drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(pFlac, pcmFrameIndex - pFlac->currentPCMFrame)) { + return DRFLAC_TRUE; + } else { + break; /* Failed to seek forward. */ + } + } + + pcmRangeLo = newPCMRangeLo; + pcmRangeHi = newPCMRangeHi; + + if (pcmRangeLo <= pcmFrameIndex && pcmRangeHi >= pcmFrameIndex) { + /* The target PCM frame is in this FLAC frame. */ + if (drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(pFlac, pcmFrameIndex - pFlac->currentPCMFrame) ) { + return DRFLAC_TRUE; + } else { + break; /* Failed to seek to FLAC frame. */ + } + } else { + const float approxCompressionRatio = (drflac_int64)(lastSuccessfulSeekOffset - pFlac->firstFLACFramePosInBytes) / ((drflac_int64)(pcmRangeLo * pFlac->channels * pFlac->bitsPerSample)/8.0f); + + if (pcmRangeLo > pcmFrameIndex) { + /* We seeked too far forward. We need to move our target byte backward and try again. */ + byteRangeHi = lastSuccessfulSeekOffset; + if (byteRangeLo > byteRangeHi) { + byteRangeLo = byteRangeHi; + } + + targetByte = byteRangeLo + ((byteRangeHi - byteRangeLo) / 2); + if (targetByte < byteRangeLo) { + targetByte = byteRangeLo; + } + } else /*if (pcmRangeHi < pcmFrameIndex)*/ { + /* We didn't seek far enough. We need to move our target byte forward and try again. */ + + /* If we're close enough we can just seek forward. */ + if ((pcmFrameIndex - pcmRangeLo) < seekForwardThreshold) { + if (drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(pFlac, pcmFrameIndex - pFlac->currentPCMFrame)) { + return DRFLAC_TRUE; + } else { + break; /* Failed to seek to FLAC frame. */ + } + } else { + byteRangeLo = lastSuccessfulSeekOffset; + if (byteRangeHi < byteRangeLo) { + byteRangeHi = byteRangeLo; + } + + targetByte = lastSuccessfulSeekOffset + (drflac_uint64)(((drflac_int64)((pcmFrameIndex-pcmRangeLo) * pFlac->channels * pFlac->bitsPerSample)/8.0f) * approxCompressionRatio); + if (targetByte > byteRangeHi) { + targetByte = byteRangeHi; + } + + if (closestSeekOffsetBeforeTargetPCMFrame < lastSuccessfulSeekOffset) { + closestSeekOffsetBeforeTargetPCMFrame = lastSuccessfulSeekOffset; + } + } + } + } + } else { + /* Getting here is really bad. We just recover as best we can, but moving to the first frame in the stream, and then abort. */ + break; + } + } + + drflac__seek_to_first_frame(pFlac); /* <-- Try to recover. */ + return DRFLAC_FALSE; +} + +static drflac_bool32 drflac__seek_to_pcm_frame__binary_search(drflac* pFlac, drflac_uint64 pcmFrameIndex) +{ + drflac_uint64 byteRangeLo; + drflac_uint64 byteRangeHi; + drflac_uint32 seekForwardThreshold = (pFlac->maxBlockSizeInPCMFrames != 0) ? pFlac->maxBlockSizeInPCMFrames*2 : 4096; + + /* Our algorithm currently assumes the FLAC stream is currently sitting at the start. */ + if (drflac__seek_to_first_frame(pFlac) == DRFLAC_FALSE) { + return DRFLAC_FALSE; + } + + /* If we're close enough to the start, just move to the start and seek forward. */ + if (pcmFrameIndex < seekForwardThreshold) { + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFrameIndex) == pcmFrameIndex; + } + + /* + Our starting byte range is the byte position of the first FLAC frame and the approximate end of the file as if it were completely uncompressed. This ensures + the entire file is included, even though most of the time it'll exceed the end of the actual stream. This is OK as the frame searching logic will handle it. + */ + byteRangeLo = pFlac->firstFLACFramePosInBytes; + byteRangeHi = pFlac->firstFLACFramePosInBytes + (drflac_uint64)((drflac_int64)(pFlac->totalPCMFrameCount * pFlac->channels * pFlac->bitsPerSample)/8.0f); + + return drflac__seek_to_pcm_frame__binary_search_internal(pFlac, pcmFrameIndex, byteRangeLo, byteRangeHi); +} +#endif /* !DR_FLAC_NO_CRC */ + +static drflac_bool32 drflac__seek_to_pcm_frame__seek_table(drflac* pFlac, drflac_uint64 pcmFrameIndex) +{ + drflac_uint32 iClosestSeekpoint = 0; + drflac_bool32 isMidFrame = DRFLAC_FALSE; + drflac_uint64 runningPCMFrameCount; + drflac_uint32 iSeekpoint; + + + DRFLAC_ASSERT(pFlac != NULL); + + if (pFlac->pSeekpoints == NULL || pFlac->seekpointCount == 0) { + return DRFLAC_FALSE; + } + + /* Do not use the seektable if pcmFramIndex is not coverd by it. */ + if (pFlac->pSeekpoints[0].firstPCMFrame > pcmFrameIndex) { + return DRFLAC_FALSE; + } + + for (iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { + if (pFlac->pSeekpoints[iSeekpoint].firstPCMFrame >= pcmFrameIndex) { + break; + } + + iClosestSeekpoint = iSeekpoint; + } + + /* There's been cases where the seek table contains only zeros. We need to do some basic validation on the closest seekpoint. */ + if (pFlac->pSeekpoints[iClosestSeekpoint].pcmFrameCount == 0 || pFlac->pSeekpoints[iClosestSeekpoint].pcmFrameCount > pFlac->maxBlockSizeInPCMFrames) { + return DRFLAC_FALSE; + } + if (pFlac->pSeekpoints[iClosestSeekpoint].firstPCMFrame > pFlac->totalPCMFrameCount && pFlac->totalPCMFrameCount > 0) { + return DRFLAC_FALSE; + } + +#if !defined(DR_FLAC_NO_CRC) + /* At this point we should know the closest seek point. We can use a binary search for this. We need to know the total sample count for this. */ + if (pFlac->totalPCMFrameCount > 0) { + drflac_uint64 byteRangeLo; + drflac_uint64 byteRangeHi; + + byteRangeHi = pFlac->firstFLACFramePosInBytes + (drflac_uint64)((drflac_int64)(pFlac->totalPCMFrameCount * pFlac->channels * pFlac->bitsPerSample)/8.0f); + byteRangeLo = pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset; + + /* + If our closest seek point is not the last one, we only need to search between it and the next one. The section below calculates an appropriate starting + value for byteRangeHi which will clamp it appropriately. + + Note that the next seekpoint must have an offset greater than the closest seekpoint because otherwise our binary search algorithm will break down. There + have been cases where a seektable consists of seek points where every byte offset is set to 0 which causes problems. If this happens we need to abort. + */ + if (iClosestSeekpoint < pFlac->seekpointCount-1) { + drflac_uint32 iNextSeekpoint = iClosestSeekpoint + 1; + + /* Basic validation on the seekpoints to ensure they're usable. */ + if (pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset >= pFlac->pSeekpoints[iNextSeekpoint].flacFrameOffset || pFlac->pSeekpoints[iNextSeekpoint].pcmFrameCount == 0) { + return DRFLAC_FALSE; /* The next seekpoint doesn't look right. The seek table cannot be trusted from here. Abort. */ + } + + if (pFlac->pSeekpoints[iNextSeekpoint].firstPCMFrame != (((drflac_uint64)0xFFFFFFFF << 32) | 0xFFFFFFFF)) { /* Make sure it's not a placeholder seekpoint. */ + byteRangeHi = pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iNextSeekpoint].flacFrameOffset - 1; /* byteRangeHi must be zero based. */ + } + } + + if (drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset)) { + if (drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &pFlac->currentPCMFrame, NULL); + + if (drflac__seek_to_pcm_frame__binary_search_internal(pFlac, pcmFrameIndex, byteRangeLo, byteRangeHi)) { + return DRFLAC_TRUE; + } + } + } + } +#endif /* !DR_FLAC_NO_CRC */ + + /* Getting here means we need to use a slower algorithm because the binary search method failed or cannot be used. */ + + /* + If we are seeking forward and the closest seekpoint is _before_ the current sample, we just seek forward from where we are. Otherwise we start seeking + from the seekpoint's first sample. + */ + if (pcmFrameIndex >= pFlac->currentPCMFrame && pFlac->pSeekpoints[iClosestSeekpoint].firstPCMFrame <= pFlac->currentPCMFrame) { + /* Optimized case. Just seek forward from where we are. */ + runningPCMFrameCount = pFlac->currentPCMFrame; + + /* The frame header for the first frame may not yet have been read. We need to do that if necessary. */ + if (pFlac->currentPCMFrame == 0 && pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + } else { + isMidFrame = DRFLAC_TRUE; + } + } else { + /* Slower case. Seek to the start of the seekpoint and then seek forward from there. */ + runningPCMFrameCount = pFlac->pSeekpoints[iClosestSeekpoint].firstPCMFrame; + + if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset)) { + return DRFLAC_FALSE; + } + + /* Grab the frame the seekpoint is sitting on in preparation for the sample-exact seeking below. */ + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + } + + for (;;) { + drflac_uint64 pcmFrameCountInThisFLACFrame; + drflac_uint64 firstPCMFrameInFLACFrame = 0; + drflac_uint64 lastPCMFrameInFLACFrame = 0; + + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &firstPCMFrameInFLACFrame, &lastPCMFrameInFLACFrame); + + pcmFrameCountInThisFLACFrame = (lastPCMFrameInFLACFrame - firstPCMFrameInFLACFrame) + 1; + if (pcmFrameIndex < (runningPCMFrameCount + pcmFrameCountInThisFLACFrame)) { + /* + The sample should be in this frame. We need to fully decode it, but if it's an invalid frame (a CRC mismatch) we need to pretend + it never existed and keep iterating. + */ + drflac_uint64 pcmFramesToDecode = pcmFrameIndex - runningPCMFrameCount; + + if (!isMidFrame) { + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + /* The frame is valid. We just need to skip over some samples to ensure it's sample-exact. */ + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ + } else { + if (result == DRFLAC_CRC_MISMATCH) { + goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ + } else { + return DRFLAC_FALSE; + } + } + } else { + /* We started seeking mid-frame which means we need to skip the frame decoding part. */ + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; + } + } else { + /* + It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this + frame never existed and leave the running sample count untouched. + */ + if (!isMidFrame) { + drflac_result result = drflac__seek_to_next_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + runningPCMFrameCount += pcmFrameCountInThisFLACFrame; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ + } else { + return DRFLAC_FALSE; + } + } + } else { + /* + We started seeking mid-frame which means we need to seek by reading to the end of the frame instead of with + drflac__seek_to_next_flac_frame() which only works if the decoder is sitting on the byte just after the frame header. + */ + runningPCMFrameCount += pFlac->currentFLACFrame.pcmFramesRemaining; + pFlac->currentFLACFrame.pcmFramesRemaining = 0; + isMidFrame = DRFLAC_FALSE; + } + + /* If we are seeking to the end of the file and we've just hit it, we're done. */ + if (pcmFrameIndex == pFlac->totalPCMFrameCount && runningPCMFrameCount == pFlac->totalPCMFrameCount) { + return DRFLAC_TRUE; + } + } + + next_iteration: + /* Grab the next frame in preparation for the next iteration. */ + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + } +} + + +#ifndef DR_FLAC_NO_OGG +typedef struct +{ + drflac_uint8 capturePattern[4]; /* Should be "OggS" */ + drflac_uint8 structureVersion; /* Always 0. */ + drflac_uint8 headerType; + drflac_uint64 granulePosition; + drflac_uint32 serialNumber; + drflac_uint32 sequenceNumber; + drflac_uint32 checksum; + drflac_uint8 segmentCount; + drflac_uint8 segmentTable[255]; +} drflac_ogg_page_header; +#endif + +typedef struct +{ + drflac_read_proc onRead; + drflac_seek_proc onSeek; + drflac_tell_proc onTell; + drflac_meta_proc onMeta; + drflac_container container; + void* pUserData; + void* pUserDataMD; + drflac_uint32 sampleRate; + drflac_uint8 channels; + drflac_uint8 bitsPerSample; + drflac_uint64 totalPCMFrameCount; + drflac_uint16 maxBlockSizeInPCMFrames; + drflac_uint64 runningFilePos; + drflac_bool32 hasStreamInfoBlock; + drflac_bool32 hasMetadataBlocks; + drflac_bs bs; /* <-- A bit streamer is required for loading data during initialization. */ + drflac_frame_header firstFrameHeader; /* <-- The header of the first frame that was read during relaxed initalization. Only set if there is no STREAMINFO block. */ + +#ifndef DR_FLAC_NO_OGG + drflac_uint32 oggSerial; + drflac_uint64 oggFirstBytePos; + drflac_ogg_page_header oggBosHeader; +#endif +} drflac_init_info; + +static DRFLAC_INLINE void drflac__decode_block_header(drflac_uint32 blockHeader, drflac_uint8* isLastBlock, drflac_uint8* blockType, drflac_uint32* blockSize) +{ + blockHeader = drflac__be2host_32(blockHeader); + *isLastBlock = (drflac_uint8)((blockHeader & 0x80000000UL) >> 31); + *blockType = (drflac_uint8)((blockHeader & 0x7F000000UL) >> 24); + *blockSize = (blockHeader & 0x00FFFFFFUL); +} + +static DRFLAC_INLINE drflac_bool32 drflac__read_and_decode_block_header(drflac_read_proc onRead, void* pUserData, drflac_uint8* isLastBlock, drflac_uint8* blockType, drflac_uint32* blockSize) +{ + drflac_uint32 blockHeader; + + *blockSize = 0; + if (onRead(pUserData, &blockHeader, 4) != 4) { + return DRFLAC_FALSE; + } + + drflac__decode_block_header(blockHeader, isLastBlock, blockType, blockSize); + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__read_streaminfo(drflac_read_proc onRead, void* pUserData, drflac_streaminfo* pStreamInfo) +{ + drflac_uint32 blockSizes; + drflac_uint64 frameSizes = 0; + drflac_uint64 importantProps; + drflac_uint8 md5[16]; + + /* min/max block size. */ + if (onRead(pUserData, &blockSizes, 4) != 4) { + return DRFLAC_FALSE; + } + + /* min/max frame size. */ + if (onRead(pUserData, &frameSizes, 6) != 6) { + return DRFLAC_FALSE; + } + + /* Sample rate, channels, bits per sample and total sample count. */ + if (onRead(pUserData, &importantProps, 8) != 8) { + return DRFLAC_FALSE; + } + + /* MD5 */ + if (onRead(pUserData, md5, sizeof(md5)) != sizeof(md5)) { + return DRFLAC_FALSE; + } + + blockSizes = drflac__be2host_32(blockSizes); + frameSizes = drflac__be2host_64(frameSizes); + importantProps = drflac__be2host_64(importantProps); + + pStreamInfo->minBlockSizeInPCMFrames = (drflac_uint16)((blockSizes & 0xFFFF0000) >> 16); + pStreamInfo->maxBlockSizeInPCMFrames = (drflac_uint16) (blockSizes & 0x0000FFFF); + pStreamInfo->minFrameSizeInPCMFrames = (drflac_uint32)((frameSizes & (((drflac_uint64)0x00FFFFFF << 16) << 24)) >> 40); + pStreamInfo->maxFrameSizeInPCMFrames = (drflac_uint32)((frameSizes & (((drflac_uint64)0x00FFFFFF << 16) << 0)) >> 16); + pStreamInfo->sampleRate = (drflac_uint32)((importantProps & (((drflac_uint64)0x000FFFFF << 16) << 28)) >> 44); + pStreamInfo->channels = (drflac_uint8 )((importantProps & (((drflac_uint64)0x0000000E << 16) << 24)) >> 41) + 1; + pStreamInfo->bitsPerSample = (drflac_uint8 )((importantProps & (((drflac_uint64)0x0000001F << 16) << 20)) >> 36) + 1; + pStreamInfo->totalPCMFrameCount = ((importantProps & ((((drflac_uint64)0x0000000F << 16) << 16) | 0xFFFFFFFF))); + DRFLAC_COPY_MEMORY(pStreamInfo->md5, md5, sizeof(md5)); + + return DRFLAC_TRUE; +} + + +static void* drflac__malloc_default(size_t sz, void* pUserData) +{ + (void)pUserData; + return DRFLAC_MALLOC(sz); +} + +static void* drflac__realloc_default(void* p, size_t sz, void* pUserData) +{ + (void)pUserData; + return DRFLAC_REALLOC(p, sz); +} + +static void drflac__free_default(void* p, void* pUserData) +{ + (void)pUserData; + DRFLAC_FREE(p); +} + + +static void* drflac__malloc_from_callbacks(size_t sz, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { return NULL; } - unsigned char id[4]; - if (onRead(pUserData, id, 4) != 4 || id[0] != 'f' || id[1] != 'L' || id[2] != 'a' || id[3] != 'C') { - return NULL; // Not a FLAC stream. + if (pAllocationCallbacks->onMalloc != NULL) { + return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); } - drflac tempFlac; - memset(&tempFlac, 0, sizeof(tempFlac)); - tempFlac.onRead = onRead; - tempFlac.onSeek = onSeek; - tempFlac.pUserData = pUserData; - tempFlac.currentBytePos = 4; - tempFlac.nextL2Line = sizeof(tempFlac.cacheL2) / sizeof(tempFlac.cacheL2[0]); // <-- Initialize to this to force a client-side data retrieval right from the start. - tempFlac.consumedBits = sizeof(tempFlac.cache)*8; + /* Try using realloc(). */ + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); + } - // The first metadata block should be the STREAMINFO block. We don't care about everything in here. - unsigned int blockSize; - bool isLastBlock; - int blockType = drflac__read_block_header(&tempFlac, &blockSize, &isLastBlock); - if (blockType != DRFLAC_BLOCK_TYPE_STREAMINFO && blockSize != 34) { + return NULL; +} + +static void* drflac__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { return NULL; } - if (!drflac__seek_bits(&tempFlac, 16)) { // minBlockSize - return NULL; - } - if (!drflac__read_uint16(&tempFlac, 16, &tempFlac.maxBlockSize)) { - return NULL; - } - if (!drflac__seek_bits(&tempFlac, 48)) { // minFrameSize + maxFrameSize - return NULL; - } - if (!drflac__read_uint32(&tempFlac, 20, &tempFlac.sampleRate)) { - return NULL; - } - if (!drflac__read_uint8(&tempFlac, 3, &tempFlac.channels)) { - return NULL; - } - if (!drflac__read_uint8(&tempFlac, 5, &tempFlac.bitsPerSample)) { - return NULL; - } - if (!drflac__read_uint64(&tempFlac, 36, &tempFlac.totalSampleCount)) { - return NULL; - } - if (!drflac__seek_bits(&tempFlac, 128)) { // MD5 - return NULL; + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); } - tempFlac.channels += 1; - tempFlac.bitsPerSample += 1; - tempFlac.totalSampleCount *= tempFlac.channels; + /* Try emulating realloc() in terms of malloc()/free(). */ + if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { + void* p2; - while (!isLastBlock) - { - blockType = drflac__read_block_header(&tempFlac, &blockSize, &isLastBlock); + p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); + if (p2 == NULL) { + return NULL; + } + + if (p != NULL) { + DRFLAC_COPY_MEMORY(p2, p, szOld); + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } + + return p2; + } + + return NULL; +} + +static void drflac__free_from_callbacks(void* p, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (p == NULL || pAllocationCallbacks == NULL) { + return; + } + + if (pAllocationCallbacks->onFree != NULL) { + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } +} + + +static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_uint64* pFirstFramePos, drflac_uint64* pSeektablePos, drflac_uint32* pSeekpointCount, drflac_allocation_callbacks* pAllocationCallbacks) +{ + /* + We want to keep track of the byte position in the stream of the seektable. At the time of calling this function we know that + we'll be sitting on byte 42. + */ + drflac_uint64 runningFilePos = 42; + drflac_uint64 seektablePos = 0; + drflac_uint32 seektableSize = 0; + + (void)onTell; + + for (;;) { + drflac_metadata metadata; + drflac_uint8 isLastBlock = 0; + drflac_uint8 blockType = 0; + drflac_uint32 blockSize; + if (drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize) == DRFLAC_FALSE) { + return DRFLAC_FALSE; + } + runningFilePos += 4; + + metadata.type = blockType; + metadata.rawDataSize = 0; + metadata.rawDataOffset = runningFilePos; + metadata.pRawData = NULL; switch (blockType) { - case DRFLAC_BLOCK_TYPE_APPLICATION: + case DRFLAC_METADATA_BLOCK_TYPE_APPLICATION: { - tempFlac.applicationBlock.pos = drflac__tell(&tempFlac); - tempFlac.applicationBlock.sizeInBytes = blockSize; + if (blockSize < 4) { + return DRFLAC_FALSE; + } + + if (onMeta) { + void* pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + metadata.data.application.id = drflac__be2host_32(*(drflac_uint32*)pRawData); + metadata.data.application.pData = (const void*)((drflac_uint8*)pRawData + sizeof(drflac_uint32)); + metadata.data.application.dataSize = blockSize - sizeof(drflac_uint32); + onMeta(pUserDataMD, &metadata); + + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + } } break; - case DRFLAC_BLOCK_TYPE_SEEKTABLE: + case DRFLAC_METADATA_BLOCK_TYPE_SEEKTABLE: { - tempFlac.seektableBlock.pos = drflac__tell(&tempFlac); - tempFlac.seektableBlock.sizeInBytes = blockSize; + seektablePos = runningFilePos; + seektableSize = blockSize; + + if (onMeta) { + drflac_uint32 seekpointCount; + drflac_uint32 iSeekpoint; + void* pRawData; + + seekpointCount = blockSize/DRFLAC_SEEKPOINT_SIZE_IN_BYTES; + + pRawData = drflac__malloc_from_callbacks(seekpointCount * sizeof(drflac_seekpoint), pAllocationCallbacks); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + /* We need to read seekpoint by seekpoint and do some processing. */ + for (iSeekpoint = 0; iSeekpoint < seekpointCount; ++iSeekpoint) { + drflac_seekpoint* pSeekpoint = (drflac_seekpoint*)pRawData + iSeekpoint; + + if (onRead(pUserData, pSeekpoint, DRFLAC_SEEKPOINT_SIZE_IN_BYTES) != DRFLAC_SEEKPOINT_SIZE_IN_BYTES) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + /* Endian swap. */ + pSeekpoint->firstPCMFrame = drflac__be2host_64(pSeekpoint->firstPCMFrame); + pSeekpoint->flacFrameOffset = drflac__be2host_64(pSeekpoint->flacFrameOffset); + pSeekpoint->pcmFrameCount = drflac__be2host_16(pSeekpoint->pcmFrameCount); + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + metadata.data.seektable.seekpointCount = seekpointCount; + metadata.data.seektable.pSeekpoints = (const drflac_seekpoint*)pRawData; + + onMeta(pUserDataMD, &metadata); + + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + } } break; - case DRFLAC_BLOCK_TYPE_VORBIS_COMMENT: + case DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT: { - tempFlac.vorbisCommentBlock.pos = drflac__tell(&tempFlac); - tempFlac.vorbisCommentBlock.sizeInBytes = blockSize; + if (blockSize < 8) { + return DRFLAC_FALSE; + } + + if (onMeta) { + void* pRawData; + const char* pRunningData; + const char* pRunningDataEnd; + drflac_uint32 i; + + pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + + pRunningData = (const char*)pRawData; + pRunningDataEnd = (const char*)pRawData + blockSize; + + metadata.data.vorbis_comment.vendorLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + + /* Need space for the rest of the block */ + if ((pRunningDataEnd - pRunningData) - 4 < (drflac_int64)metadata.data.vorbis_comment.vendorLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength; + metadata.data.vorbis_comment.commentCount = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + + /* Need space for 'commentCount' comments after the block, which at minimum is a drflac_uint32 per comment */ + if ((pRunningDataEnd - pRunningData) / sizeof(drflac_uint32) < metadata.data.vorbis_comment.commentCount) { /* <-- Note the order of operations to avoid overflow to a valid value */ + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + metadata.data.vorbis_comment.pComments = pRunningData; + + /* Check that the comments section is valid before passing it to the callback */ + for (i = 0; i < metadata.data.vorbis_comment.commentCount; ++i) { + drflac_uint32 commentLength; + + if (pRunningDataEnd - pRunningData < 4) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + commentLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + if (pRunningDataEnd - pRunningData < (drflac_int64)commentLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + pRunningData += commentLength; + } + + onMeta(pUserDataMD, &metadata); + + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + } } break; - case DRFLAC_BLOCK_TYPE_CUESHEET: + case DRFLAC_METADATA_BLOCK_TYPE_CUESHEET: { - tempFlac.cuesheetBlock.pos = drflac__tell(&tempFlac); - tempFlac.cuesheetBlock.sizeInBytes = blockSize; + if (blockSize < 396) { + return DRFLAC_FALSE; + } + + if (onMeta) { + void* pRawData; + const char* pRunningData; + const char* pRunningDataEnd; + size_t bufferSize; + drflac_uint8 iTrack; + drflac_uint8 iIndex; + void* pTrackData; + + /* + This needs to be loaded in two passes. The first pass is used to calculate the size of the memory allocation + we need for storing the necessary data. The second pass will fill that buffer with usable data. + */ + pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + + pRunningData = (const char*)pRawData; + pRunningDataEnd = (const char*)pRawData + blockSize; + + DRFLAC_COPY_MEMORY(metadata.data.cuesheet.catalog, pRunningData, 128); pRunningData += 128; + metadata.data.cuesheet.leadInSampleCount = drflac__be2host_64(*(const drflac_uint64*)pRunningData); pRunningData += 8; + metadata.data.cuesheet.isCD = (pRunningData[0] & 0x80) != 0; pRunningData += 259; + metadata.data.cuesheet.trackCount = pRunningData[0]; pRunningData += 1; + metadata.data.cuesheet.pTrackData = NULL; /* Will be filled later. */ + + /* Pass 1: Calculate the size of the buffer for the track data. */ + { + const char* pRunningDataSaved = pRunningData; /* Will be restored at the end in preparation for the second pass. */ + + bufferSize = metadata.data.cuesheet.trackCount * DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES; + + for (iTrack = 0; iTrack < metadata.data.cuesheet.trackCount; ++iTrack) { + drflac_uint8 indexCount; + drflac_uint32 indexPointSize; + + if (pRunningDataEnd - pRunningData < DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + /* Skip to the index point count */ + pRunningData += 35; + + indexCount = pRunningData[0]; + pRunningData += 1; + + bufferSize += indexCount * sizeof(drflac_cuesheet_track_index); + + /* Quick validation check. */ + indexPointSize = indexCount * DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES; + if (pRunningDataEnd - pRunningData < (drflac_int64)indexPointSize) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + pRunningData += indexPointSize; + } + + pRunningData = pRunningDataSaved; + } + + /* Pass 2: Allocate a buffer and fill the data. Validation was done in the step above so can be skipped. */ + { + char* pRunningTrackData; + + pTrackData = drflac__malloc_from_callbacks(bufferSize, pAllocationCallbacks); + if (pTrackData == NULL) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + pRunningTrackData = (char*)pTrackData; + + for (iTrack = 0; iTrack < metadata.data.cuesheet.trackCount; ++iTrack) { + drflac_uint8 indexCount; + + DRFLAC_COPY_MEMORY(pRunningTrackData, pRunningData, DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES); + pRunningData += DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES-1; /* Skip forward, but not beyond the last byte in the CUESHEET_TRACK block which is the index count. */ + pRunningTrackData += DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES-1; + + /* Grab the index count for the next part. */ + indexCount = pRunningData[0]; + pRunningData += 1; + pRunningTrackData += 1; + + /* Extract each track index. */ + for (iIndex = 0; iIndex < indexCount; ++iIndex) { + drflac_cuesheet_track_index* pTrackIndex = (drflac_cuesheet_track_index*)pRunningTrackData; + + DRFLAC_COPY_MEMORY(pRunningTrackData, pRunningData, DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES); + pRunningData += DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES; + pRunningTrackData += sizeof(drflac_cuesheet_track_index); + + pTrackIndex->offset = drflac__be2host_64(pTrackIndex->offset); + } + } + + metadata.data.cuesheet.pTrackData = pTrackData; + } + + /* The original data is no longer needed. */ + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + pRawData = NULL; + + onMeta(pUserDataMD, &metadata); + + drflac__free_from_callbacks(pTrackData, pAllocationCallbacks); + pTrackData = NULL; + } } break; - case DRFLAC_BLOCK_TYPE_PICTURE: + case DRFLAC_METADATA_BLOCK_TYPE_PICTURE: { - tempFlac.pictureBlock.pos = drflac__tell(&tempFlac); - tempFlac.pictureBlock.sizeInBytes = blockSize; + if (blockSize < 32) { + return DRFLAC_FALSE; + } + + if (onMeta) { + drflac_bool32 result = DRFLAC_TRUE; + drflac_uint32 blockSizeRemaining = blockSize; + char* pMime = NULL; + char* pDescription = NULL; + void* pPictureData = NULL; + + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.type, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.type = drflac__be2host_32(metadata.data.picture.type); + + + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.mimeLength, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.mimeLength = drflac__be2host_32(metadata.data.picture.mimeLength); + + pMime = (char*)drflac__malloc_from_callbacks(metadata.data.picture.mimeLength + 1, pAllocationCallbacks); /* +1 for null terminator. */ + if (pMime == NULL) { + result = DRFLAC_FALSE; + goto done_flac; + } + + if (blockSizeRemaining < metadata.data.picture.mimeLength || onRead(pUserData, pMime, metadata.data.picture.mimeLength) != metadata.data.picture.mimeLength) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= metadata.data.picture.mimeLength; + pMime[metadata.data.picture.mimeLength] = '\0'; /* Null terminate for safety. */ + metadata.data.picture.mime = (const char*)pMime; + + + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.descriptionLength, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.descriptionLength = drflac__be2host_32(metadata.data.picture.descriptionLength); + + pDescription = (char*)drflac__malloc_from_callbacks(metadata.data.picture.descriptionLength + 1, pAllocationCallbacks); /* +1 for null terminator. */ + if (pDescription == NULL) { + result = DRFLAC_FALSE; + goto done_flac; + } + + if (blockSizeRemaining < metadata.data.picture.descriptionLength || onRead(pUserData, pDescription, metadata.data.picture.descriptionLength) != metadata.data.picture.descriptionLength) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= metadata.data.picture.descriptionLength; + pDescription[metadata.data.picture.descriptionLength] = '\0'; /* Null terminate for safety. */ + metadata.data.picture.description = (const char*)pDescription; + + + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.width, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.width = drflac__be2host_32(metadata.data.picture.width); + + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.height, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.height = drflac__be2host_32(metadata.data.picture.height); + + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.colorDepth, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.colorDepth = drflac__be2host_32(metadata.data.picture.colorDepth); + + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.indexColorCount, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.indexColorCount = drflac__be2host_32(metadata.data.picture.indexColorCount); + + + /* Picture data. */ + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.pictureDataSize, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.pictureDataSize = drflac__be2host_32(metadata.data.picture.pictureDataSize); + + if (blockSizeRemaining < metadata.data.picture.pictureDataSize) { + result = DRFLAC_FALSE; + goto done_flac; + } + + /* For the actual image data we want to store the offset to the start of the stream. */ + metadata.data.picture.pictureDataOffset = runningFilePos + (blockSize - blockSizeRemaining); + + /* + For the allocation of image data, we can allow memory allocation to fail, in which case we just leave + the pointer as null. If it fails, we need to fall back to seeking past the image data. + */ + #ifndef DR_FLAC_NO_PICTURE_METADATA_MALLOC + pPictureData = drflac__malloc_from_callbacks(metadata.data.picture.pictureDataSize, pAllocationCallbacks); + if (pPictureData != NULL) { + if (onRead(pUserData, pPictureData, metadata.data.picture.pictureDataSize) != metadata.data.picture.pictureDataSize) { + result = DRFLAC_FALSE; + goto done_flac; + } + } else + #endif + { + /* Allocation failed. We need to seek past the picture data. */ + if (!onSeek(pUserData, metadata.data.picture.pictureDataSize, DRFLAC_SEEK_CUR)) { + result = DRFLAC_FALSE; + goto done_flac; + } + } + + blockSizeRemaining -= metadata.data.picture.pictureDataSize; + metadata.data.picture.pPictureData = (const drflac_uint8*)pPictureData; + + + /* Only fire the callback if we actually have a way to read the image data. We must have either a valid offset, or a valid data pointer. */ + if (metadata.data.picture.pictureDataOffset != 0 || metadata.data.picture.pPictureData != NULL) { + onMeta(pUserDataMD, &metadata); + } else { + /* Don't have a valid offset or data pointer, so just pretend we don't have a picture metadata. */ + } + + done_flac: + drflac__free_from_callbacks(pMime, pAllocationCallbacks); + drflac__free_from_callbacks(pDescription, pAllocationCallbacks); + drflac__free_from_callbacks(pPictureData, pAllocationCallbacks); + + if (result != DRFLAC_TRUE) { + return DRFLAC_FALSE; + } + } } break; + case DRFLAC_METADATA_BLOCK_TYPE_PADDING: + { + if (onMeta) { + metadata.data.padding.unused = 0; - // These blocks we either don't care about or aren't supporting. - case DRFLAC_BLOCK_TYPE_PADDING: - case DRFLAC_BLOCK_TYPE_INVALID: - default: break; + /* Padding doesn't have anything meaningful in it, so just skip over it, but make sure the caller is aware of it by firing the callback. */ + if (!onSeek(pUserData, blockSize, DRFLAC_SEEK_CUR)) { + isLastBlock = DRFLAC_TRUE; /* An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. */ + } else { + onMeta(pUserDataMD, &metadata); + } + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_INVALID: + { + /* Invalid chunk. Just skip over this one. */ + if (onMeta) { + if (!onSeek(pUserData, blockSize, DRFLAC_SEEK_CUR)) { + isLastBlock = DRFLAC_TRUE; /* An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. */ + } + } + } break; + + default: + { + /* + It's an unknown chunk, but not necessarily invalid. There's a chance more metadata blocks might be defined later on, so we + can at the very least report the chunk to the application and let it look at the raw data. + */ + if (onMeta) { + void* pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); + if (pRawData != NULL) { + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + } else { + /* Allocation failed. We need to seek past the block. */ + if (!onSeek(pUserData, blockSize, DRFLAC_SEEK_CUR)) { + return DRFLAC_FALSE; + } + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + onMeta(pUserDataMD, &metadata); + + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + } + } break; } - if (!drflac__seek_bits(&tempFlac, blockSize*8)) { + /* If we're not handling metadata, just skip over the block. If we are, it will have been handled earlier in the switch statement above. */ + if (onMeta == NULL && blockSize > 0) { + if (!onSeek(pUserData, blockSize, DRFLAC_SEEK_CUR)) { + isLastBlock = DRFLAC_TRUE; + } + } + + runningFilePos += blockSize; + if (isLastBlock) { + break; + } + } + + *pSeektablePos = seektablePos; + *pSeekpointCount = seektableSize / DRFLAC_SEEKPOINT_SIZE_IN_BYTES; + *pFirstFramePos = runningFilePos; + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__init_private__native(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_bool32 relaxed) +{ + /* Pre Condition: The bit stream should be sitting just past the 4-byte id header. */ + + drflac_uint8 isLastBlock; + drflac_uint8 blockType; + drflac_uint32 blockSize; + + (void)onSeek; + + pInit->container = drflac_container_native; + + /* The first metadata block should be the STREAMINFO block. */ + if (!drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize)) { + return DRFLAC_FALSE; + } + + if (blockType != DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO || blockSize != 34) { + if (!relaxed) { + /* We're opening in strict mode and the first block is not the STREAMINFO block. Error. */ + return DRFLAC_FALSE; + } else { + /* + Relaxed mode. To open from here we need to just find the first frame and set the sample rate, etc. to whatever is defined + for that frame. + */ + pInit->hasStreamInfoBlock = DRFLAC_FALSE; + pInit->hasMetadataBlocks = DRFLAC_FALSE; + + if (!drflac__read_next_flac_frame_header(&pInit->bs, 0, &pInit->firstFrameHeader)) { + return DRFLAC_FALSE; /* Couldn't find a frame. */ + } + + if (pInit->firstFrameHeader.bitsPerSample == 0) { + return DRFLAC_FALSE; /* Failed to initialize because the first frame depends on the STREAMINFO block, which does not exist. */ + } + + pInit->sampleRate = pInit->firstFrameHeader.sampleRate; + pInit->channels = drflac__get_channel_count_from_channel_assignment(pInit->firstFrameHeader.channelAssignment); + pInit->bitsPerSample = pInit->firstFrameHeader.bitsPerSample; + pInit->maxBlockSizeInPCMFrames = 65535; /* <-- See notes here: https://xiph.org/flac/format.html#metadata_block_streaminfo */ + return DRFLAC_TRUE; + } + } else { + drflac_streaminfo streaminfo; + if (!drflac__read_streaminfo(onRead, pUserData, &streaminfo)) { + return DRFLAC_FALSE; + } + + pInit->hasStreamInfoBlock = DRFLAC_TRUE; + pInit->sampleRate = streaminfo.sampleRate; + pInit->channels = streaminfo.channels; + pInit->bitsPerSample = streaminfo.bitsPerSample; + pInit->totalPCMFrameCount = streaminfo.totalPCMFrameCount; + pInit->maxBlockSizeInPCMFrames = streaminfo.maxBlockSizeInPCMFrames; /* Don't care about the min block size - only the max (used for determining the size of the memory allocation). */ + pInit->hasMetadataBlocks = !isLastBlock; + + if (onMeta) { + drflac_metadata metadata; + metadata.type = DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO; + metadata.pRawData = NULL; + metadata.rawDataSize = 0; + metadata.data.streaminfo = streaminfo; + onMeta(pUserDataMD, &metadata); + } + + return DRFLAC_TRUE; + } +} + +#ifndef DR_FLAC_NO_OGG +#define DRFLAC_OGG_MAX_PAGE_SIZE 65307 +#define DRFLAC_OGG_CAPTURE_PATTERN_CRC32 1605413199 /* CRC-32 of "OggS". */ + +typedef enum +{ + drflac_ogg_recover_on_crc_mismatch, + drflac_ogg_fail_on_crc_mismatch +} drflac_ogg_crc_mismatch_recovery; + +#ifndef DR_FLAC_NO_CRC +static drflac_uint32 drflac__crc32_table[] = { + 0x00000000L, 0x04C11DB7L, 0x09823B6EL, 0x0D4326D9L, + 0x130476DCL, 0x17C56B6BL, 0x1A864DB2L, 0x1E475005L, + 0x2608EDB8L, 0x22C9F00FL, 0x2F8AD6D6L, 0x2B4BCB61L, + 0x350C9B64L, 0x31CD86D3L, 0x3C8EA00AL, 0x384FBDBDL, + 0x4C11DB70L, 0x48D0C6C7L, 0x4593E01EL, 0x4152FDA9L, + 0x5F15ADACL, 0x5BD4B01BL, 0x569796C2L, 0x52568B75L, + 0x6A1936C8L, 0x6ED82B7FL, 0x639B0DA6L, 0x675A1011L, + 0x791D4014L, 0x7DDC5DA3L, 0x709F7B7AL, 0x745E66CDL, + 0x9823B6E0L, 0x9CE2AB57L, 0x91A18D8EL, 0x95609039L, + 0x8B27C03CL, 0x8FE6DD8BL, 0x82A5FB52L, 0x8664E6E5L, + 0xBE2B5B58L, 0xBAEA46EFL, 0xB7A96036L, 0xB3687D81L, + 0xAD2F2D84L, 0xA9EE3033L, 0xA4AD16EAL, 0xA06C0B5DL, + 0xD4326D90L, 0xD0F37027L, 0xDDB056FEL, 0xD9714B49L, + 0xC7361B4CL, 0xC3F706FBL, 0xCEB42022L, 0xCA753D95L, + 0xF23A8028L, 0xF6FB9D9FL, 0xFBB8BB46L, 0xFF79A6F1L, + 0xE13EF6F4L, 0xE5FFEB43L, 0xE8BCCD9AL, 0xEC7DD02DL, + 0x34867077L, 0x30476DC0L, 0x3D044B19L, 0x39C556AEL, + 0x278206ABL, 0x23431B1CL, 0x2E003DC5L, 0x2AC12072L, + 0x128E9DCFL, 0x164F8078L, 0x1B0CA6A1L, 0x1FCDBB16L, + 0x018AEB13L, 0x054BF6A4L, 0x0808D07DL, 0x0CC9CDCAL, + 0x7897AB07L, 0x7C56B6B0L, 0x71159069L, 0x75D48DDEL, + 0x6B93DDDBL, 0x6F52C06CL, 0x6211E6B5L, 0x66D0FB02L, + 0x5E9F46BFL, 0x5A5E5B08L, 0x571D7DD1L, 0x53DC6066L, + 0x4D9B3063L, 0x495A2DD4L, 0x44190B0DL, 0x40D816BAL, + 0xACA5C697L, 0xA864DB20L, 0xA527FDF9L, 0xA1E6E04EL, + 0xBFA1B04BL, 0xBB60ADFCL, 0xB6238B25L, 0xB2E29692L, + 0x8AAD2B2FL, 0x8E6C3698L, 0x832F1041L, 0x87EE0DF6L, + 0x99A95DF3L, 0x9D684044L, 0x902B669DL, 0x94EA7B2AL, + 0xE0B41DE7L, 0xE4750050L, 0xE9362689L, 0xEDF73B3EL, + 0xF3B06B3BL, 0xF771768CL, 0xFA325055L, 0xFEF34DE2L, + 0xC6BCF05FL, 0xC27DEDE8L, 0xCF3ECB31L, 0xCBFFD686L, + 0xD5B88683L, 0xD1799B34L, 0xDC3ABDEDL, 0xD8FBA05AL, + 0x690CE0EEL, 0x6DCDFD59L, 0x608EDB80L, 0x644FC637L, + 0x7A089632L, 0x7EC98B85L, 0x738AAD5CL, 0x774BB0EBL, + 0x4F040D56L, 0x4BC510E1L, 0x46863638L, 0x42472B8FL, + 0x5C007B8AL, 0x58C1663DL, 0x558240E4L, 0x51435D53L, + 0x251D3B9EL, 0x21DC2629L, 0x2C9F00F0L, 0x285E1D47L, + 0x36194D42L, 0x32D850F5L, 0x3F9B762CL, 0x3B5A6B9BL, + 0x0315D626L, 0x07D4CB91L, 0x0A97ED48L, 0x0E56F0FFL, + 0x1011A0FAL, 0x14D0BD4DL, 0x19939B94L, 0x1D528623L, + 0xF12F560EL, 0xF5EE4BB9L, 0xF8AD6D60L, 0xFC6C70D7L, + 0xE22B20D2L, 0xE6EA3D65L, 0xEBA91BBCL, 0xEF68060BL, + 0xD727BBB6L, 0xD3E6A601L, 0xDEA580D8L, 0xDA649D6FL, + 0xC423CD6AL, 0xC0E2D0DDL, 0xCDA1F604L, 0xC960EBB3L, + 0xBD3E8D7EL, 0xB9FF90C9L, 0xB4BCB610L, 0xB07DABA7L, + 0xAE3AFBA2L, 0xAAFBE615L, 0xA7B8C0CCL, 0xA379DD7BL, + 0x9B3660C6L, 0x9FF77D71L, 0x92B45BA8L, 0x9675461FL, + 0x8832161AL, 0x8CF30BADL, 0x81B02D74L, 0x857130C3L, + 0x5D8A9099L, 0x594B8D2EL, 0x5408ABF7L, 0x50C9B640L, + 0x4E8EE645L, 0x4A4FFBF2L, 0x470CDD2BL, 0x43CDC09CL, + 0x7B827D21L, 0x7F436096L, 0x7200464FL, 0x76C15BF8L, + 0x68860BFDL, 0x6C47164AL, 0x61043093L, 0x65C52D24L, + 0x119B4BE9L, 0x155A565EL, 0x18197087L, 0x1CD86D30L, + 0x029F3D35L, 0x065E2082L, 0x0B1D065BL, 0x0FDC1BECL, + 0x3793A651L, 0x3352BBE6L, 0x3E119D3FL, 0x3AD08088L, + 0x2497D08DL, 0x2056CD3AL, 0x2D15EBE3L, 0x29D4F654L, + 0xC5A92679L, 0xC1683BCEL, 0xCC2B1D17L, 0xC8EA00A0L, + 0xD6AD50A5L, 0xD26C4D12L, 0xDF2F6BCBL, 0xDBEE767CL, + 0xE3A1CBC1L, 0xE760D676L, 0xEA23F0AFL, 0xEEE2ED18L, + 0xF0A5BD1DL, 0xF464A0AAL, 0xF9278673L, 0xFDE69BC4L, + 0x89B8FD09L, 0x8D79E0BEL, 0x803AC667L, 0x84FBDBD0L, + 0x9ABC8BD5L, 0x9E7D9662L, 0x933EB0BBL, 0x97FFAD0CL, + 0xAFB010B1L, 0xAB710D06L, 0xA6322BDFL, 0xA2F33668L, + 0xBCB4666DL, 0xB8757BDAL, 0xB5365D03L, 0xB1F740B4L +}; +#endif + +static DRFLAC_INLINE drflac_uint32 drflac_crc32_byte(drflac_uint32 crc32, drflac_uint8 data) +{ +#ifndef DR_FLAC_NO_CRC + return (crc32 << 8) ^ drflac__crc32_table[(drflac_uint8)((crc32 >> 24) & 0xFF) ^ data]; +#else + (void)data; + return crc32; +#endif +} + +#if 0 +static DRFLAC_INLINE drflac_uint32 drflac_crc32_uint32(drflac_uint32 crc32, drflac_uint32 data) +{ + crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 24) & 0xFF)); + crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 16) & 0xFF)); + crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 8) & 0xFF)); + crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 0) & 0xFF)); + return crc32; +} + +static DRFLAC_INLINE drflac_uint32 drflac_crc32_uint64(drflac_uint32 crc32, drflac_uint64 data) +{ + crc32 = drflac_crc32_uint32(crc32, (drflac_uint32)((data >> 32) & 0xFFFFFFFF)); + crc32 = drflac_crc32_uint32(crc32, (drflac_uint32)((data >> 0) & 0xFFFFFFFF)); + return crc32; +} +#endif + +static DRFLAC_INLINE drflac_uint32 drflac_crc32_buffer(drflac_uint32 crc32, drflac_uint8* pData, drflac_uint32 dataSize) +{ + /* This can be optimized. */ + drflac_uint32 i; + for (i = 0; i < dataSize; ++i) { + crc32 = drflac_crc32_byte(crc32, pData[i]); + } + return crc32; +} + + +static DRFLAC_INLINE drflac_bool32 drflac_ogg__is_capture_pattern(drflac_uint8 pattern[4]) +{ + return pattern[0] == 'O' && pattern[1] == 'g' && pattern[2] == 'g' && pattern[3] == 'S'; +} + +static DRFLAC_INLINE drflac_uint32 drflac_ogg__get_page_header_size(drflac_ogg_page_header* pHeader) +{ + return 27 + pHeader->segmentCount; +} + +static DRFLAC_INLINE drflac_uint32 drflac_ogg__get_page_body_size(drflac_ogg_page_header* pHeader) +{ + drflac_uint32 pageBodySize = 0; + int i; + + for (i = 0; i < pHeader->segmentCount; ++i) { + pageBodySize += pHeader->segmentTable[i]; + } + + return pageBodySize; +} + +static drflac_result drflac_ogg__read_page_header_after_capture_pattern(drflac_read_proc onRead, void* pUserData, drflac_ogg_page_header* pHeader, drflac_uint32* pBytesRead, drflac_uint32* pCRC32) +{ + drflac_uint8 data[23]; + drflac_uint32 i; + + DRFLAC_ASSERT(*pCRC32 == DRFLAC_OGG_CAPTURE_PATTERN_CRC32); + + if (onRead(pUserData, data, 23) != 23) { + return DRFLAC_AT_END; + } + *pBytesRead += 23; + + /* + It's not actually used, but set the capture pattern to 'OggS' for completeness. Not doing this will cause static analysers to complain about + us trying to access uninitialized data. We could alternatively just comment out this member of the drflac_ogg_page_header structure, but I + like to have it map to the structure of the underlying data. + */ + pHeader->capturePattern[0] = 'O'; + pHeader->capturePattern[1] = 'g'; + pHeader->capturePattern[2] = 'g'; + pHeader->capturePattern[3] = 'S'; + + pHeader->structureVersion = data[0]; + pHeader->headerType = data[1]; + DRFLAC_COPY_MEMORY(&pHeader->granulePosition, &data[ 2], 8); + DRFLAC_COPY_MEMORY(&pHeader->serialNumber, &data[10], 4); + DRFLAC_COPY_MEMORY(&pHeader->sequenceNumber, &data[14], 4); + DRFLAC_COPY_MEMORY(&pHeader->checksum, &data[18], 4); + pHeader->segmentCount = data[22]; + + /* Calculate the CRC. Note that for the calculation the checksum part of the page needs to be set to 0. */ + data[18] = 0; + data[19] = 0; + data[20] = 0; + data[21] = 0; + + for (i = 0; i < 23; ++i) { + *pCRC32 = drflac_crc32_byte(*pCRC32, data[i]); + } + + + if (onRead(pUserData, pHeader->segmentTable, pHeader->segmentCount) != pHeader->segmentCount) { + return DRFLAC_AT_END; + } + *pBytesRead += pHeader->segmentCount; + + for (i = 0; i < pHeader->segmentCount; ++i) { + *pCRC32 = drflac_crc32_byte(*pCRC32, pHeader->segmentTable[i]); + } + + return DRFLAC_SUCCESS; +} + +static drflac_result drflac_ogg__read_page_header(drflac_read_proc onRead, void* pUserData, drflac_ogg_page_header* pHeader, drflac_uint32* pBytesRead, drflac_uint32* pCRC32) +{ + drflac_uint8 id[4]; + + *pBytesRead = 0; + + if (onRead(pUserData, id, 4) != 4) { + return DRFLAC_AT_END; + } + *pBytesRead += 4; + + /* We need to read byte-by-byte until we find the OggS capture pattern. */ + for (;;) { + if (drflac_ogg__is_capture_pattern(id)) { + drflac_result result; + + *pCRC32 = DRFLAC_OGG_CAPTURE_PATTERN_CRC32; + + result = drflac_ogg__read_page_header_after_capture_pattern(onRead, pUserData, pHeader, pBytesRead, pCRC32); + if (result == DRFLAC_SUCCESS) { + return DRFLAC_SUCCESS; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + continue; + } else { + return result; + } + } + } else { + /* The first 4 bytes did not equal the capture pattern. Read the next byte and try again. */ + id[0] = id[1]; + id[1] = id[2]; + id[2] = id[3]; + if (onRead(pUserData, &id[3], 1) != 1) { + return DRFLAC_AT_END; + } + *pBytesRead += 1; + } + } +} + + +/* +The main part of the Ogg encapsulation is the conversion from the physical Ogg bitstream to the native FLAC bitstream. It works +in three general stages: Ogg Physical Bitstream -> Ogg/FLAC Logical Bitstream -> FLAC Native Bitstream. dr_flac is designed +in such a way that the core sections assume everything is delivered in native format. Therefore, for each encapsulation type +dr_flac is supporting there needs to be a layer sitting on top of the onRead and onSeek callbacks that ensures the bits read from +the physical Ogg bitstream are converted and delivered in native FLAC format. +*/ +typedef struct +{ + drflac_read_proc onRead; /* The original onRead callback from drflac_open() and family. */ + drflac_seek_proc onSeek; /* The original onSeek callback from drflac_open() and family. */ + drflac_tell_proc onTell; /* The original onTell callback from drflac_open() and family. */ + void* pUserData; /* The user data passed on onRead and onSeek. This is the user data that was passed on drflac_open() and family. */ + drflac_uint64 currentBytePos; /* The position of the byte we are sitting on in the physical byte stream. Used for efficient seeking. */ + drflac_uint64 firstBytePos; /* The position of the first byte in the physical bitstream. Points to the start of the "OggS" identifier of the FLAC bos page. */ + drflac_uint32 serialNumber; /* The serial number of the FLAC audio pages. This is determined by the initial header page that was read during initialization. */ + drflac_ogg_page_header bosPageHeader; /* Used for seeking. */ + drflac_ogg_page_header currentPageHeader; + drflac_uint32 bytesRemainingInPage; + drflac_uint32 pageDataSize; + drflac_uint8 pageData[DRFLAC_OGG_MAX_PAGE_SIZE]; +} drflac_oggbs; /* oggbs = Ogg Bitstream */ + +static size_t drflac_oggbs__read_physical(drflac_oggbs* oggbs, void* bufferOut, size_t bytesToRead) +{ + size_t bytesActuallyRead = oggbs->onRead(oggbs->pUserData, bufferOut, bytesToRead); + oggbs->currentBytePos += bytesActuallyRead; + + return bytesActuallyRead; +} + +static drflac_bool32 drflac_oggbs__seek_physical(drflac_oggbs* oggbs, drflac_uint64 offset, drflac_seek_origin origin) +{ + if (origin == DRFLAC_SEEK_SET) { + if (offset <= 0x7FFFFFFF) { + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, DRFLAC_SEEK_SET)) { + return DRFLAC_FALSE; + } + oggbs->currentBytePos = offset; + + return DRFLAC_TRUE; + } else { + if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, DRFLAC_SEEK_SET)) { + return DRFLAC_FALSE; + } + oggbs->currentBytePos = offset; + + return drflac_oggbs__seek_physical(oggbs, offset - 0x7FFFFFFF, DRFLAC_SEEK_CUR); + } + } else { + while (offset > 0x7FFFFFFF) { + if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, DRFLAC_SEEK_CUR)) { + return DRFLAC_FALSE; + } + oggbs->currentBytePos += 0x7FFFFFFF; + offset -= 0x7FFFFFFF; + } + + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, DRFLAC_SEEK_CUR)) { /* <-- Safe cast thanks to the loop above. */ + return DRFLAC_FALSE; + } + oggbs->currentBytePos += offset; + + return DRFLAC_TRUE; + } +} + +static drflac_bool32 drflac_oggbs__goto_next_page(drflac_oggbs* oggbs, drflac_ogg_crc_mismatch_recovery recoveryMethod) +{ + drflac_ogg_page_header header; + for (;;) { + drflac_uint32 crc32 = 0; + drflac_uint32 bytesRead; + drflac_uint32 pageBodySize; +#ifndef DR_FLAC_NO_CRC + drflac_uint32 actualCRC32; +#endif + + if (drflac_ogg__read_page_header(oggbs->onRead, oggbs->pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { + return DRFLAC_FALSE; + } + oggbs->currentBytePos += bytesRead; + + pageBodySize = drflac_ogg__get_page_body_size(&header); + if (pageBodySize > DRFLAC_OGG_MAX_PAGE_SIZE) { + continue; /* Invalid page size. Assume it's corrupted and just move to the next page. */ + } + + if (header.serialNumber != oggbs->serialNumber) { + /* It's not a FLAC page. Skip it. */ + if (pageBodySize > 0 && !drflac_oggbs__seek_physical(oggbs, pageBodySize, DRFLAC_SEEK_CUR)) { + return DRFLAC_FALSE; + } + continue; + } + + + /* We need to read the entire page and then do a CRC check on it. If there's a CRC mismatch we need to skip this page. */ + if (drflac_oggbs__read_physical(oggbs, oggbs->pageData, pageBodySize) != pageBodySize) { + return DRFLAC_FALSE; + } + oggbs->pageDataSize = pageBodySize; + +#ifndef DR_FLAC_NO_CRC + actualCRC32 = drflac_crc32_buffer(crc32, oggbs->pageData, oggbs->pageDataSize); + if (actualCRC32 != header.checksum) { + if (recoveryMethod == drflac_ogg_recover_on_crc_mismatch) { + continue; /* CRC mismatch. Skip this page. */ + } else { + /* + Even though we are failing on a CRC mismatch, we still want our stream to be in a good state. Therefore we + go to the next valid page to ensure we're in a good state, but return false to let the caller know that the + seek did not fully complete. + */ + drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch); + return DRFLAC_FALSE; + } + } +#else + (void)recoveryMethod; /* <-- Silence a warning. */ +#endif + + oggbs->currentPageHeader = header; + oggbs->bytesRemainingInPage = pageBodySize; + return DRFLAC_TRUE; + } +} + +/* Function below is unused at the moment, but I might be re-adding it later. */ +#if 0 +static drflac_uint8 drflac_oggbs__get_current_segment_index(drflac_oggbs* oggbs, drflac_uint8* pBytesRemainingInSeg) +{ + drflac_uint32 bytesConsumedInPage = drflac_ogg__get_page_body_size(&oggbs->currentPageHeader) - oggbs->bytesRemainingInPage; + drflac_uint8 iSeg = 0; + drflac_uint32 iByte = 0; + while (iByte < bytesConsumedInPage) { + drflac_uint8 segmentSize = oggbs->currentPageHeader.segmentTable[iSeg]; + if (iByte + segmentSize > bytesConsumedInPage) { + break; + } else { + iSeg += 1; + iByte += segmentSize; + } + } + + *pBytesRemainingInSeg = oggbs->currentPageHeader.segmentTable[iSeg] - (drflac_uint8)(bytesConsumedInPage - iByte); + return iSeg; +} + +static drflac_bool32 drflac_oggbs__seek_to_next_packet(drflac_oggbs* oggbs) +{ + /* The current packet ends when we get to the segment with a lacing value of < 255 which is not at the end of a page. */ + for (;;) { + drflac_bool32 atEndOfPage = DRFLAC_FALSE; + + drflac_uint8 bytesRemainingInSeg; + drflac_uint8 iFirstSeg = drflac_oggbs__get_current_segment_index(oggbs, &bytesRemainingInSeg); + + drflac_uint32 bytesToEndOfPacketOrPage = bytesRemainingInSeg; + for (drflac_uint8 iSeg = iFirstSeg; iSeg < oggbs->currentPageHeader.segmentCount; ++iSeg) { + drflac_uint8 segmentSize = oggbs->currentPageHeader.segmentTable[iSeg]; + if (segmentSize < 255) { + if (iSeg == oggbs->currentPageHeader.segmentCount-1) { + atEndOfPage = DRFLAC_TRUE; + } + + break; + } + + bytesToEndOfPacketOrPage += segmentSize; + } + + /* + At this point we will have found either the packet or the end of the page. If were at the end of the page we'll + want to load the next page and keep searching for the end of the packet. + */ + drflac_oggbs__seek_physical(oggbs, bytesToEndOfPacketOrPage, DRFLAC_SEEK_CUR); + oggbs->bytesRemainingInPage -= bytesToEndOfPacketOrPage; + + if (atEndOfPage) { + /* + We're potentially at the next packet, but we need to check the next page first to be sure because the packet may + straddle pages. + */ + if (!drflac_oggbs__goto_next_page(oggbs)) { + return DRFLAC_FALSE; + } + + /* If it's a fresh packet it most likely means we're at the next packet. */ + if ((oggbs->currentPageHeader.headerType & 0x01) == 0) { + return DRFLAC_TRUE; + } + } else { + /* We're at the next packet. */ + return DRFLAC_TRUE; + } + } +} + +static drflac_bool32 drflac_oggbs__seek_to_next_frame(drflac_oggbs* oggbs) +{ + /* The bitstream should be sitting on the first byte just after the header of the frame. */ + + /* What we're actually doing here is seeking to the start of the next packet. */ + return drflac_oggbs__seek_to_next_packet(oggbs); +} +#endif + +static size_t drflac__on_read_ogg(void* pUserData, void* bufferOut, size_t bytesToRead) +{ + drflac_oggbs* oggbs = (drflac_oggbs*)pUserData; + drflac_uint8* pRunningBufferOut = (drflac_uint8*)bufferOut; + size_t bytesRead = 0; + + DRFLAC_ASSERT(oggbs != NULL); + DRFLAC_ASSERT(pRunningBufferOut != NULL); + + /* Reading is done page-by-page. If we've run out of bytes in the page we need to move to the next one. */ + while (bytesRead < bytesToRead) { + size_t bytesRemainingToRead = bytesToRead - bytesRead; + + if (oggbs->bytesRemainingInPage >= bytesRemainingToRead) { + DRFLAC_COPY_MEMORY(pRunningBufferOut, oggbs->pageData + (oggbs->pageDataSize - oggbs->bytesRemainingInPage), bytesRemainingToRead); + bytesRead += bytesRemainingToRead; + oggbs->bytesRemainingInPage -= (drflac_uint32)bytesRemainingToRead; + break; + } + + /* If we get here it means some of the requested data is contained in the next pages. */ + if (oggbs->bytesRemainingInPage > 0) { + DRFLAC_COPY_MEMORY(pRunningBufferOut, oggbs->pageData + (oggbs->pageDataSize - oggbs->bytesRemainingInPage), oggbs->bytesRemainingInPage); + bytesRead += oggbs->bytesRemainingInPage; + pRunningBufferOut += oggbs->bytesRemainingInPage; + oggbs->bytesRemainingInPage = 0; + } + + DRFLAC_ASSERT(bytesRemainingToRead > 0); + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { + break; /* Failed to go to the next page. Might have simply hit the end of the stream. */ + } + } + + return bytesRead; +} + +static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_seek_origin origin) +{ + drflac_oggbs* oggbs = (drflac_oggbs*)pUserData; + int bytesSeeked = 0; + + DRFLAC_ASSERT(oggbs != NULL); + DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ + + /* Seeking is always forward which makes things a lot simpler. */ + if (origin == DRFLAC_SEEK_SET) { + if (!drflac_oggbs__seek_physical(oggbs, (int)oggbs->firstBytePos, DRFLAC_SEEK_SET)) { + return DRFLAC_FALSE; + } + + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { + return DRFLAC_FALSE; + } + + return drflac__on_seek_ogg(pUserData, offset, DRFLAC_SEEK_CUR); + } else if (origin == DRFLAC_SEEK_CUR) { + while (bytesSeeked < offset) { + int bytesRemainingToSeek = offset - bytesSeeked; + DRFLAC_ASSERT(bytesRemainingToSeek >= 0); + + if (oggbs->bytesRemainingInPage >= (size_t)bytesRemainingToSeek) { + bytesSeeked += bytesRemainingToSeek; + (void)bytesSeeked; /* <-- Silence a dead store warning emitted by Clang Static Analyzer. */ + oggbs->bytesRemainingInPage -= bytesRemainingToSeek; + break; + } + + /* If we get here it means some of the requested data is contained in the next pages. */ + if (oggbs->bytesRemainingInPage > 0) { + bytesSeeked += (int)oggbs->bytesRemainingInPage; + oggbs->bytesRemainingInPage = 0; + } + + DRFLAC_ASSERT(bytesRemainingToSeek > 0); + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { + /* Failed to go to the next page. We either hit the end of the stream or had a CRC mismatch. */ + return DRFLAC_FALSE; + } + } + } else if (origin == DRFLAC_SEEK_END) { + /* Seeking to the end is not supported. */ + return DRFLAC_FALSE; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__on_tell_ogg(void* pUserData, drflac_int64* pCursor) +{ + /* + Not implemented for Ogg containers because we don't currently track the byte position of the logical bitstream. To support this, we'll need + to track the position in drflac__on_read_ogg and drflac__on_seek_ogg. + */ + (void)pUserData; + (void)pCursor; + return DRFLAC_FALSE; +} + + +static drflac_bool32 drflac_ogg__seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex) +{ + drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + drflac_uint64 originalBytePos; + drflac_uint64 runningGranulePosition; + drflac_uint64 runningFrameBytePos; + drflac_uint64 runningPCMFrameCount; + + DRFLAC_ASSERT(oggbs != NULL); + + originalBytePos = oggbs->currentBytePos; /* For recovery. Points to the OggS identifier. */ + + /* First seek to the first frame. */ + if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes)) { + return DRFLAC_FALSE; + } + oggbs->bytesRemainingInPage = 0; + + runningGranulePosition = 0; + for (;;) { + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { + drflac_oggbs__seek_physical(oggbs, originalBytePos, DRFLAC_SEEK_SET); + return DRFLAC_FALSE; /* Never did find that sample... */ + } + + runningFrameBytePos = oggbs->currentBytePos - drflac_ogg__get_page_header_size(&oggbs->currentPageHeader) - oggbs->pageDataSize; + if (oggbs->currentPageHeader.granulePosition >= pcmFrameIndex) { + break; /* The sample is somewhere in the previous page. */ + } + + /* + At this point we know the sample is not in the previous page. It could possibly be in this page. For simplicity we + disregard any pages that do not begin a fresh packet. + */ + if ((oggbs->currentPageHeader.headerType & 0x01) == 0) { /* <-- Is it a fresh page? */ + if (oggbs->currentPageHeader.segmentTable[0] >= 2) { + drflac_uint8 firstBytesInPage[2]; + firstBytesInPage[0] = oggbs->pageData[0]; + firstBytesInPage[1] = oggbs->pageData[1]; + + if ((firstBytesInPage[0] == 0xFF) && (firstBytesInPage[1] & 0xFC) == 0xF8) { /* <-- Does the page begin with a frame's sync code? */ + runningGranulePosition = oggbs->currentPageHeader.granulePosition; + } + + continue; + } + } + } + + /* + We found the page that that is closest to the sample, so now we need to find it. The first thing to do is seek to the + start of that page. In the loop above we checked that it was a fresh page which means this page is also the start of + a new frame. This property means that after we've seeked to the page we can immediately start looping over frames until + we find the one containing the target sample. + */ + if (!drflac_oggbs__seek_physical(oggbs, runningFrameBytePos, DRFLAC_SEEK_SET)) { + return DRFLAC_FALSE; + } + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { + return DRFLAC_FALSE; + } + + /* + At this point we'll be sitting on the first byte of the frame header of the first frame in the page. We just keep + looping over these frames until we find the one containing the sample we're after. + */ + runningPCMFrameCount = runningGranulePosition; + for (;;) { + /* + There are two ways to find the sample and seek past irrelevant frames: + 1) Use the native FLAC decoder. + 2) Use Ogg's framing system. + + Both of these options have their own pros and cons. Using the native FLAC decoder is slower because it needs to + do a full decode of the frame. Using Ogg's framing system is faster, but more complicated and involves some code + duplication for the decoding of frame headers. + + Another thing to consider is that using the Ogg framing system will perform direct seeking of the physical Ogg + bitstream. This is important to consider because it means we cannot read data from the drflac_bs object using the + standard drflac__*() APIs because that will read in extra data for its own internal caching which in turn breaks + the positioning of the read pointer of the physical Ogg bitstream. Therefore, anything that would normally be read + using the native FLAC decoding APIs, such as drflac__read_next_flac_frame_header(), need to be re-implemented so as to + avoid the use of the drflac_bs object. + + Considering these issues, I have decided to use the slower native FLAC decoding method for the following reasons: + 1) Seeking is already partially accelerated using Ogg's paging system in the code block above. + 2) Seeking in an Ogg encapsulated FLAC stream is probably quite uncommon. + 3) Simplicity. + */ + drflac_uint64 firstPCMFrameInFLACFrame = 0; + drflac_uint64 lastPCMFrameInFLACFrame = 0; + drflac_uint64 pcmFrameCountInThisFrame; + + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &firstPCMFrameInFLACFrame, &lastPCMFrameInFLACFrame); + + pcmFrameCountInThisFrame = (lastPCMFrameInFLACFrame - firstPCMFrameInFLACFrame) + 1; + + /* If we are seeking to the end of the file and we've just hit it, we're done. */ + if (pcmFrameIndex == pFlac->totalPCMFrameCount && (runningPCMFrameCount + pcmFrameCountInThisFrame) == pFlac->totalPCMFrameCount) { + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + pFlac->currentPCMFrame = pcmFrameIndex; + pFlac->currentFLACFrame.pcmFramesRemaining = 0; + return DRFLAC_TRUE; + } else { + return DRFLAC_FALSE; + } + } + + if (pcmFrameIndex < (runningPCMFrameCount + pcmFrameCountInThisFrame)) { + /* + The sample should be in this FLAC frame. We need to fully decode it, however if it's an invalid frame (a CRC mismatch), we need to pretend + it never existed and keep iterating. + */ + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + /* The frame is valid. We just need to skip over some samples to ensure it's sample-exact. */ + drflac_uint64 pcmFramesToDecode = (size_t)(pcmFrameIndex - runningPCMFrameCount); /* <-- Safe cast because the maximum number of samples in a frame is 65535. */ + if (pcmFramesToDecode == 0) { + return DRFLAC_TRUE; + } + + pFlac->currentPCMFrame = runningPCMFrameCount; + + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ + } else { + if (result == DRFLAC_CRC_MISMATCH) { + continue; /* CRC mismatch. Pretend this frame never existed. */ + } else { + return DRFLAC_FALSE; + } + } + } else { + /* + It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this + frame never existed and leave the running sample count untouched. + */ + drflac_result result = drflac__seek_to_next_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + runningPCMFrameCount += pcmFrameCountInThisFrame; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + continue; /* CRC mismatch. Pretend this frame never existed. */ + } else { + return DRFLAC_FALSE; + } + } + } + } +} + + + +static drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_bool32 relaxed) +{ + drflac_ogg_page_header header; + drflac_uint32 crc32 = DRFLAC_OGG_CAPTURE_PATTERN_CRC32; + drflac_uint32 bytesRead = 0; + + /* Pre Condition: The bit stream should be sitting just past the 4-byte OggS capture pattern. */ + (void)relaxed; + + pInit->container = drflac_container_ogg; + pInit->oggFirstBytePos = 0; + + /* + We'll get here if the first 4 bytes of the stream were the OggS capture pattern, however it doesn't necessarily mean the + stream includes FLAC encoded audio. To check for this we need to scan the beginning-of-stream page markers and check if + any match the FLAC specification. Important to keep in mind that the stream may be multiplexed. + */ + if (drflac_ogg__read_page_header_after_capture_pattern(onRead, pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { + return DRFLAC_FALSE; + } + pInit->runningFilePos += bytesRead; + + for (;;) { + int pageBodySize; + + /* Break if we're past the beginning of stream page. */ + if ((header.headerType & 0x02) == 0) { + return DRFLAC_FALSE; + } + + /* Check if it's a FLAC header. */ + pageBodySize = drflac_ogg__get_page_body_size(&header); + if (pageBodySize == 51) { /* 51 = the lacing value of the FLAC header packet. */ + /* It could be a FLAC page... */ + drflac_uint32 bytesRemainingInPage = pageBodySize; + drflac_uint8 packetType; + + if (onRead(pUserData, &packetType, 1) != 1) { + return DRFLAC_FALSE; + } + + bytesRemainingInPage -= 1; + if (packetType == 0x7F) { + /* Increasingly more likely to be a FLAC page... */ + drflac_uint8 sig[4]; + if (onRead(pUserData, sig, 4) != 4) { + return DRFLAC_FALSE; + } + + bytesRemainingInPage -= 4; + if (sig[0] == 'F' && sig[1] == 'L' && sig[2] == 'A' && sig[3] == 'C') { + /* Almost certainly a FLAC page... */ + drflac_uint8 mappingVersion[2]; + if (onRead(pUserData, mappingVersion, 2) != 2) { + return DRFLAC_FALSE; + } + + if (mappingVersion[0] != 1) { + return DRFLAC_FALSE; /* Only supporting version 1.x of the Ogg mapping. */ + } + + /* + The next 2 bytes are the non-audio packets, not including this one. We don't care about this because we're going to + be handling it in a generic way based on the serial number and packet types. + */ + if (!onSeek(pUserData, 2, DRFLAC_SEEK_CUR)) { + return DRFLAC_FALSE; + } + + /* Expecting the native FLAC signature "fLaC". */ + if (onRead(pUserData, sig, 4) != 4) { + return DRFLAC_FALSE; + } + + if (sig[0] == 'f' && sig[1] == 'L' && sig[2] == 'a' && sig[3] == 'C') { + /* The remaining data in the page should be the STREAMINFO block. */ + drflac_streaminfo streaminfo; + drflac_uint8 isLastBlock; + drflac_uint8 blockType; + drflac_uint32 blockSize; + if (!drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize)) { + return DRFLAC_FALSE; + } + + if (blockType != DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO || blockSize != 34) { + return DRFLAC_FALSE; /* Invalid block type. First block must be the STREAMINFO block. */ + } + + if (drflac__read_streaminfo(onRead, pUserData, &streaminfo)) { + /* Success! */ + pInit->hasStreamInfoBlock = DRFLAC_TRUE; + pInit->sampleRate = streaminfo.sampleRate; + pInit->channels = streaminfo.channels; + pInit->bitsPerSample = streaminfo.bitsPerSample; + pInit->totalPCMFrameCount = streaminfo.totalPCMFrameCount; + pInit->maxBlockSizeInPCMFrames = streaminfo.maxBlockSizeInPCMFrames; + pInit->hasMetadataBlocks = !isLastBlock; + + if (onMeta) { + drflac_metadata metadata; + metadata.type = DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO; + metadata.pRawData = NULL; + metadata.rawDataSize = 0; + metadata.data.streaminfo = streaminfo; + onMeta(pUserDataMD, &metadata); + } + + pInit->runningFilePos += pageBodySize; + pInit->oggFirstBytePos = pInit->runningFilePos - 79; /* Subtracting 79 will place us right on top of the "OggS" identifier of the FLAC bos page. */ + pInit->oggSerial = header.serialNumber; + pInit->oggBosHeader = header; + break; + } else { + /* Failed to read STREAMINFO block. Aww, so close... */ + return DRFLAC_FALSE; + } + } else { + /* Invalid file. */ + return DRFLAC_FALSE; + } + } else { + /* Not a FLAC header. Skip it. */ + if (!onSeek(pUserData, bytesRemainingInPage, DRFLAC_SEEK_CUR)) { + return DRFLAC_FALSE; + } + } + } else { + /* Not a FLAC header. Seek past the entire page and move on to the next. */ + if (!onSeek(pUserData, bytesRemainingInPage, DRFLAC_SEEK_CUR)) { + return DRFLAC_FALSE; + } + } + } else { + if (!onSeek(pUserData, pageBodySize, DRFLAC_SEEK_CUR)) { + return DRFLAC_FALSE; + } + } + + pInit->runningFilePos += pageBodySize; + + + /* Read the header of the next page. */ + if (drflac_ogg__read_page_header(onRead, pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { + return DRFLAC_FALSE; + } + pInit->runningFilePos += bytesRead; + } + + /* + If we get here it means we found a FLAC audio stream. We should be sitting on the first byte of the header of the next page. The next + packets in the FLAC logical stream contain the metadata. The only thing left to do in the initialization phase for Ogg is to create the + Ogg bistream object. + */ + pInit->hasMetadataBlocks = DRFLAC_TRUE; /* <-- Always have at least VORBIS_COMMENT metadata block. */ + return DRFLAC_TRUE; +} +#endif + +static drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) +{ + drflac_bool32 relaxed; + drflac_uint8 id[4]; + + if (pInit == NULL || onRead == NULL || onSeek == NULL) { /* <-- onTell is optional. */ + return DRFLAC_FALSE; + } + + DRFLAC_ZERO_MEMORY(pInit, sizeof(*pInit)); + pInit->onRead = onRead; + pInit->onSeek = onSeek; + pInit->onTell = onTell; + pInit->onMeta = onMeta; + pInit->container = container; + pInit->pUserData = pUserData; + pInit->pUserDataMD = pUserDataMD; + + pInit->bs.onRead = onRead; + pInit->bs.onSeek = onSeek; + pInit->bs.onTell = onTell; + pInit->bs.pUserData = pUserData; + drflac__reset_cache(&pInit->bs); + + + /* If the container is explicitly defined then we can try opening in relaxed mode. */ + relaxed = container != drflac_container_unknown; + + /* Skip over any ID3 tags. */ + for (;;) { + if (onRead(pUserData, id, 4) != 4) { + return DRFLAC_FALSE; /* Ran out of data. */ + } + pInit->runningFilePos += 4; + + if (id[0] == 'I' && id[1] == 'D' && id[2] == '3') { + drflac_uint8 header[6]; + drflac_uint8 flags; + drflac_uint32 headerSize; + + if (onRead(pUserData, header, 6) != 6) { + return DRFLAC_FALSE; /* Ran out of data. */ + } + pInit->runningFilePos += 6; + + flags = header[1]; + + DRFLAC_COPY_MEMORY(&headerSize, header+2, 4); + headerSize = drflac__unsynchsafe_32(drflac__be2host_32(headerSize)); + if (flags & 0x10) { + headerSize += 10; + } + + if (!onSeek(pUserData, headerSize, DRFLAC_SEEK_CUR)) { + return DRFLAC_FALSE; /* Failed to seek past the tag. */ + } + pInit->runningFilePos += headerSize; + } else { + break; + } + } + + if (id[0] == 'f' && id[1] == 'L' && id[2] == 'a' && id[3] == 'C') { + return drflac__init_private__native(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); + } +#ifndef DR_FLAC_NO_OGG + if (id[0] == 'O' && id[1] == 'g' && id[2] == 'g' && id[3] == 'S') { + return drflac__init_private__ogg(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); + } +#endif + + /* If we get here it means we likely don't have a header. Try opening in relaxed mode, if applicable. */ + if (relaxed) { + if (container == drflac_container_native) { + return drflac__init_private__native(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); + } +#ifndef DR_FLAC_NO_OGG + if (container == drflac_container_ogg) { + return drflac__init_private__ogg(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); + } +#endif + } + + /* Unsupported container. */ + return DRFLAC_FALSE; +} + +static void drflac__init_from_info(drflac* pFlac, const drflac_init_info* pInit) +{ + DRFLAC_ASSERT(pFlac != NULL); + DRFLAC_ASSERT(pInit != NULL); + + DRFLAC_ZERO_MEMORY(pFlac, sizeof(*pFlac)); + pFlac->bs = pInit->bs; + pFlac->onMeta = pInit->onMeta; + pFlac->pUserDataMD = pInit->pUserDataMD; + pFlac->maxBlockSizeInPCMFrames = pInit->maxBlockSizeInPCMFrames; + pFlac->sampleRate = pInit->sampleRate; + pFlac->channels = (drflac_uint8)pInit->channels; + pFlac->bitsPerSample = (drflac_uint8)pInit->bitsPerSample; + pFlac->totalPCMFrameCount = pInit->totalPCMFrameCount; + pFlac->container = pInit->container; +} + + +static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac_init_info init; + drflac_uint32 allocationSize; + drflac_uint32 wholeSIMDVectorCountPerChannel; + drflac_uint32 decodedSamplesAllocationSize; +#ifndef DR_FLAC_NO_OGG + drflac_oggbs* pOggbs = NULL; +#endif + drflac_uint64 firstFramePos; + drflac_uint64 seektablePos; + drflac_uint32 seekpointCount; + drflac_allocation_callbacks allocationCallbacks; + drflac* pFlac; + + /* CPU support first. */ + drflac__init_cpu_caps(); + + if (!drflac__init_private(&init, onRead, onSeek, onTell, onMeta, container, pUserData, pUserDataMD)) { + return NULL; + } + + if (pAllocationCallbacks != NULL) { + allocationCallbacks = *pAllocationCallbacks; + if (allocationCallbacks.onFree == NULL || (allocationCallbacks.onMalloc == NULL && allocationCallbacks.onRealloc == NULL)) { + return NULL; /* Invalid allocation callbacks. */ + } + } else { + allocationCallbacks.pUserData = NULL; + allocationCallbacks.onMalloc = drflac__malloc_default; + allocationCallbacks.onRealloc = drflac__realloc_default; + allocationCallbacks.onFree = drflac__free_default; + } + + + /* + The size of the allocation for the drflac object needs to be large enough to fit the following: + 1) The main members of the drflac structure + 2) A block of memory large enough to store the decoded samples of the largest frame in the stream + 3) If the container is Ogg, a drflac_oggbs object + + The complicated part of the allocation is making sure there's enough room the decoded samples, taking into consideration + the different SIMD instruction sets. + */ + allocationSize = sizeof(drflac); + + /* + The allocation size for decoded frames depends on the number of 32-bit integers that fit inside the largest SIMD vector + we are supporting. + */ + if ((init.maxBlockSizeInPCMFrames % (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) == 0) { + wholeSIMDVectorCountPerChannel = (init.maxBlockSizeInPCMFrames / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))); + } else { + wholeSIMDVectorCountPerChannel = (init.maxBlockSizeInPCMFrames / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) + 1; + } + + decodedSamplesAllocationSize = wholeSIMDVectorCountPerChannel * DRFLAC_MAX_SIMD_VECTOR_SIZE * init.channels; + + allocationSize += decodedSamplesAllocationSize; + allocationSize += DRFLAC_MAX_SIMD_VECTOR_SIZE; /* Allocate extra bytes to ensure we have enough for alignment. */ + +#ifndef DR_FLAC_NO_OGG + /* There's additional data required for Ogg streams. */ + if (init.container == drflac_container_ogg) { + allocationSize += sizeof(drflac_oggbs); + + pOggbs = (drflac_oggbs*)drflac__malloc_from_callbacks(sizeof(*pOggbs), &allocationCallbacks); + if (pOggbs == NULL) { + return NULL; /*DRFLAC_OUT_OF_MEMORY;*/ + } + + DRFLAC_ZERO_MEMORY(pOggbs, sizeof(*pOggbs)); + pOggbs->onRead = onRead; + pOggbs->onSeek = onSeek; + pOggbs->onTell = onTell; + pOggbs->pUserData = pUserData; + pOggbs->currentBytePos = init.oggFirstBytePos; + pOggbs->firstBytePos = init.oggFirstBytePos; + pOggbs->serialNumber = init.oggSerial; + pOggbs->bosPageHeader = init.oggBosHeader; + pOggbs->bytesRemainingInPage = 0; + } +#endif + + /* + This part is a bit awkward. We need to load the seektable so that it can be referenced in-memory, but I want the drflac object to + consist of only a single heap allocation. To this, the size of the seek table needs to be known, which we determine when reading + and decoding the metadata. + */ + firstFramePos = 42; /* <-- We know we are at byte 42 at this point. */ + seektablePos = 0; + seekpointCount = 0; + if (init.hasMetadataBlocks) { + drflac_read_proc onReadOverride = onRead; + drflac_seek_proc onSeekOverride = onSeek; + drflac_tell_proc onTellOverride = onTell; + void* pUserDataOverride = pUserData; + +#ifndef DR_FLAC_NO_OGG + if (init.container == drflac_container_ogg) { + onReadOverride = drflac__on_read_ogg; + onSeekOverride = drflac__on_seek_ogg; + onTellOverride = drflac__on_tell_ogg; + pUserDataOverride = (void*)pOggbs; + } +#endif + + if (!drflac__read_and_decode_metadata(onReadOverride, onSeekOverride, onTellOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seekpointCount, &allocationCallbacks)) { + #ifndef DR_FLAC_NO_OGG + drflac__free_from_callbacks(pOggbs, &allocationCallbacks); + #endif return NULL; } + + allocationSize += seekpointCount * sizeof(drflac_seekpoint); + } + + + pFlac = (drflac*)drflac__malloc_from_callbacks(allocationSize, &allocationCallbacks); + if (pFlac == NULL) { + #ifndef DR_FLAC_NO_OGG + drflac__free_from_callbacks(pOggbs, &allocationCallbacks); + #endif + return NULL; + } + + drflac__init_from_info(pFlac, &init); + pFlac->allocationCallbacks = allocationCallbacks; + pFlac->pDecodedSamples = (drflac_int32*)drflac_align((size_t)pFlac->pExtraData, DRFLAC_MAX_SIMD_VECTOR_SIZE); + +#ifndef DR_FLAC_NO_OGG + if (init.container == drflac_container_ogg) { + drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + (seekpointCount * sizeof(drflac_seekpoint))); + DRFLAC_COPY_MEMORY(pInternalOggbs, pOggbs, sizeof(*pOggbs)); + + /* At this point the pOggbs object has been handed over to pInternalOggbs and can be freed. */ + drflac__free_from_callbacks(pOggbs, &allocationCallbacks); + pOggbs = NULL; + + /* The Ogg bistream needs to be layered on top of the original bitstream. */ + pFlac->bs.onRead = drflac__on_read_ogg; + pFlac->bs.onSeek = drflac__on_seek_ogg; + pFlac->bs.onTell = drflac__on_tell_ogg; + pFlac->bs.pUserData = (void*)pInternalOggbs; + pFlac->_oggbs = (void*)pInternalOggbs; + } +#endif + + pFlac->firstFLACFramePosInBytes = firstFramePos; + + /* NOTE: Seektables are not currently compatible with Ogg encapsulation (Ogg has its own accelerated seeking system). I may change this later, so I'm leaving this here for now. */ +#ifndef DR_FLAC_NO_OGG + if (init.container == drflac_container_ogg) + { + pFlac->pSeekpoints = NULL; + pFlac->seekpointCount = 0; + } + else +#endif + { + /* If we have a seektable we need to load it now, making sure we move back to where we were previously. */ + if (seektablePos != 0) { + pFlac->seekpointCount = seekpointCount; + pFlac->pSeekpoints = (drflac_seekpoint*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize); + + DRFLAC_ASSERT(pFlac->bs.onSeek != NULL); + DRFLAC_ASSERT(pFlac->bs.onRead != NULL); + + /* Seek to the seektable, then just read directly into our seektable buffer. */ + if (pFlac->bs.onSeek(pFlac->bs.pUserData, (int)seektablePos, DRFLAC_SEEK_SET)) { + drflac_uint32 iSeekpoint; + + for (iSeekpoint = 0; iSeekpoint < seekpointCount; iSeekpoint += 1) { + if (pFlac->bs.onRead(pFlac->bs.pUserData, pFlac->pSeekpoints + iSeekpoint, DRFLAC_SEEKPOINT_SIZE_IN_BYTES) == DRFLAC_SEEKPOINT_SIZE_IN_BYTES) { + /* Endian swap. */ + pFlac->pSeekpoints[iSeekpoint].firstPCMFrame = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].firstPCMFrame); + pFlac->pSeekpoints[iSeekpoint].flacFrameOffset = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].flacFrameOffset); + pFlac->pSeekpoints[iSeekpoint].pcmFrameCount = drflac__be2host_16(pFlac->pSeekpoints[iSeekpoint].pcmFrameCount); + } else { + /* Failed to read the seektable. Pretend we don't have one. */ + pFlac->pSeekpoints = NULL; + pFlac->seekpointCount = 0; + break; + } + } + + /* We need to seek back to where we were. If this fails it's a critical error. */ + if (!pFlac->bs.onSeek(pFlac->bs.pUserData, (int)pFlac->firstFLACFramePosInBytes, DRFLAC_SEEK_SET)) { + drflac__free_from_callbacks(pFlac, &allocationCallbacks); + return NULL; + } + } else { + /* Failed to seek to the seektable. Ominous sign, but for now we can just pretend we don't have one. */ + pFlac->pSeekpoints = NULL; + pFlac->seekpointCount = 0; + } + } } - // At this point we should be sitting right at the start of the very first frame. - tempFlac.firstFramePos = drflac__tell(&tempFlac); - - drflac* pFlac = (drflac*)malloc(sizeof(*pFlac) - sizeof(pFlac->pExtraData) + (tempFlac.maxBlockSize * tempFlac.channels * sizeof(int32_t))); - memcpy(pFlac, &tempFlac, sizeof(tempFlac) - sizeof(pFlac->pExtraData)); - pFlac->pDecodedSamples = (int32_t*)pFlac->pExtraData; + /* + If we get here, but don't have a STREAMINFO block, it means we've opened the stream in relaxed mode and need to decode + the first frame. + */ + if (!init.hasStreamInfoBlock) { + pFlac->currentFLACFrame.header = init.firstFrameHeader; + for (;;) { + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + break; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + drflac__free_from_callbacks(pFlac, &allocationCallbacks); + return NULL; + } + continue; + } else { + drflac__free_from_callbacks(pFlac, &allocationCallbacks); + return NULL; + } + } + } + } return pFlac; } -static void drflac_close(drflac* pFlac) + + +#ifndef DR_FLAC_NO_STDIO +#include +#ifndef DR_FLAC_NO_WCHAR +#include /* For wcslen(), wcsrtombs() */ +#endif + +/* Errno */ +/* drflac_result_from_errno() is only used for fopen() and wfopen() so putting it inside DR_WAV_NO_STDIO for now. If something else needs this later we can move it out. */ +#include +static drflac_result drflac_result_from_errno(int e) +{ + switch (e) + { + case 0: return DRFLAC_SUCCESS; + #ifdef EPERM + case EPERM: return DRFLAC_INVALID_OPERATION; + #endif + #ifdef ENOENT + case ENOENT: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef ESRCH + case ESRCH: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef EINTR + case EINTR: return DRFLAC_INTERRUPT; + #endif + #ifdef EIO + case EIO: return DRFLAC_IO_ERROR; + #endif + #ifdef ENXIO + case ENXIO: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef E2BIG + case E2BIG: return DRFLAC_INVALID_ARGS; + #endif + #ifdef ENOEXEC + case ENOEXEC: return DRFLAC_INVALID_FILE; + #endif + #ifdef EBADF + case EBADF: return DRFLAC_INVALID_FILE; + #endif + #ifdef ECHILD + case ECHILD: return DRFLAC_ERROR; + #endif + #ifdef EAGAIN + case EAGAIN: return DRFLAC_UNAVAILABLE; + #endif + #ifdef ENOMEM + case ENOMEM: return DRFLAC_OUT_OF_MEMORY; + #endif + #ifdef EACCES + case EACCES: return DRFLAC_ACCESS_DENIED; + #endif + #ifdef EFAULT + case EFAULT: return DRFLAC_BAD_ADDRESS; + #endif + #ifdef ENOTBLK + case ENOTBLK: return DRFLAC_ERROR; + #endif + #ifdef EBUSY + case EBUSY: return DRFLAC_BUSY; + #endif + #ifdef EEXIST + case EEXIST: return DRFLAC_ALREADY_EXISTS; + #endif + #ifdef EXDEV + case EXDEV: return DRFLAC_ERROR; + #endif + #ifdef ENODEV + case ENODEV: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef ENOTDIR + case ENOTDIR: return DRFLAC_NOT_DIRECTORY; + #endif + #ifdef EISDIR + case EISDIR: return DRFLAC_IS_DIRECTORY; + #endif + #ifdef EINVAL + case EINVAL: return DRFLAC_INVALID_ARGS; + #endif + #ifdef ENFILE + case ENFILE: return DRFLAC_TOO_MANY_OPEN_FILES; + #endif + #ifdef EMFILE + case EMFILE: return DRFLAC_TOO_MANY_OPEN_FILES; + #endif + #ifdef ENOTTY + case ENOTTY: return DRFLAC_INVALID_OPERATION; + #endif + #ifdef ETXTBSY + case ETXTBSY: return DRFLAC_BUSY; + #endif + #ifdef EFBIG + case EFBIG: return DRFLAC_TOO_BIG; + #endif + #ifdef ENOSPC + case ENOSPC: return DRFLAC_NO_SPACE; + #endif + #ifdef ESPIPE + case ESPIPE: return DRFLAC_BAD_SEEK; + #endif + #ifdef EROFS + case EROFS: return DRFLAC_ACCESS_DENIED; + #endif + #ifdef EMLINK + case EMLINK: return DRFLAC_TOO_MANY_LINKS; + #endif + #ifdef EPIPE + case EPIPE: return DRFLAC_BAD_PIPE; + #endif + #ifdef EDOM + case EDOM: return DRFLAC_OUT_OF_RANGE; + #endif + #ifdef ERANGE + case ERANGE: return DRFLAC_OUT_OF_RANGE; + #endif + #ifdef EDEADLK + case EDEADLK: return DRFLAC_DEADLOCK; + #endif + #ifdef ENAMETOOLONG + case ENAMETOOLONG: return DRFLAC_PATH_TOO_LONG; + #endif + #ifdef ENOLCK + case ENOLCK: return DRFLAC_ERROR; + #endif + #ifdef ENOSYS + case ENOSYS: return DRFLAC_NOT_IMPLEMENTED; + #endif + #if defined(ENOTEMPTY) && ENOTEMPTY != EEXIST /* In AIX, ENOTEMPTY and EEXIST use the same value. */ + case ENOTEMPTY: return DRFLAC_DIRECTORY_NOT_EMPTY; + #endif + #ifdef ELOOP + case ELOOP: return DRFLAC_TOO_MANY_LINKS; + #endif + #ifdef ENOMSG + case ENOMSG: return DRFLAC_NO_MESSAGE; + #endif + #ifdef EIDRM + case EIDRM: return DRFLAC_ERROR; + #endif + #ifdef ECHRNG + case ECHRNG: return DRFLAC_ERROR; + #endif + #ifdef EL2NSYNC + case EL2NSYNC: return DRFLAC_ERROR; + #endif + #ifdef EL3HLT + case EL3HLT: return DRFLAC_ERROR; + #endif + #ifdef EL3RST + case EL3RST: return DRFLAC_ERROR; + #endif + #ifdef ELNRNG + case ELNRNG: return DRFLAC_OUT_OF_RANGE; + #endif + #ifdef EUNATCH + case EUNATCH: return DRFLAC_ERROR; + #endif + #ifdef ENOCSI + case ENOCSI: return DRFLAC_ERROR; + #endif + #ifdef EL2HLT + case EL2HLT: return DRFLAC_ERROR; + #endif + #ifdef EBADE + case EBADE: return DRFLAC_ERROR; + #endif + #ifdef EBADR + case EBADR: return DRFLAC_ERROR; + #endif + #ifdef EXFULL + case EXFULL: return DRFLAC_ERROR; + #endif + #ifdef ENOANO + case ENOANO: return DRFLAC_ERROR; + #endif + #ifdef EBADRQC + case EBADRQC: return DRFLAC_ERROR; + #endif + #ifdef EBADSLT + case EBADSLT: return DRFLAC_ERROR; + #endif + #ifdef EBFONT + case EBFONT: return DRFLAC_INVALID_FILE; + #endif + #ifdef ENOSTR + case ENOSTR: return DRFLAC_ERROR; + #endif + #ifdef ENODATA + case ENODATA: return DRFLAC_NO_DATA_AVAILABLE; + #endif + #ifdef ETIME + case ETIME: return DRFLAC_TIMEOUT; + #endif + #ifdef ENOSR + case ENOSR: return DRFLAC_NO_DATA_AVAILABLE; + #endif + #ifdef ENONET + case ENONET: return DRFLAC_NO_NETWORK; + #endif + #ifdef ENOPKG + case ENOPKG: return DRFLAC_ERROR; + #endif + #ifdef EREMOTE + case EREMOTE: return DRFLAC_ERROR; + #endif + #ifdef ENOLINK + case ENOLINK: return DRFLAC_ERROR; + #endif + #ifdef EADV + case EADV: return DRFLAC_ERROR; + #endif + #ifdef ESRMNT + case ESRMNT: return DRFLAC_ERROR; + #endif + #ifdef ECOMM + case ECOMM: return DRFLAC_ERROR; + #endif + #ifdef EPROTO + case EPROTO: return DRFLAC_ERROR; + #endif + #ifdef EMULTIHOP + case EMULTIHOP: return DRFLAC_ERROR; + #endif + #ifdef EDOTDOT + case EDOTDOT: return DRFLAC_ERROR; + #endif + #ifdef EBADMSG + case EBADMSG: return DRFLAC_BAD_MESSAGE; + #endif + #ifdef EOVERFLOW + case EOVERFLOW: return DRFLAC_TOO_BIG; + #endif + #ifdef ENOTUNIQ + case ENOTUNIQ: return DRFLAC_NOT_UNIQUE; + #endif + #ifdef EBADFD + case EBADFD: return DRFLAC_ERROR; + #endif + #ifdef EREMCHG + case EREMCHG: return DRFLAC_ERROR; + #endif + #ifdef ELIBACC + case ELIBACC: return DRFLAC_ACCESS_DENIED; + #endif + #ifdef ELIBBAD + case ELIBBAD: return DRFLAC_INVALID_FILE; + #endif + #ifdef ELIBSCN + case ELIBSCN: return DRFLAC_INVALID_FILE; + #endif + #ifdef ELIBMAX + case ELIBMAX: return DRFLAC_ERROR; + #endif + #ifdef ELIBEXEC + case ELIBEXEC: return DRFLAC_ERROR; + #endif + #ifdef EILSEQ + case EILSEQ: return DRFLAC_INVALID_DATA; + #endif + #ifdef ERESTART + case ERESTART: return DRFLAC_ERROR; + #endif + #ifdef ESTRPIPE + case ESTRPIPE: return DRFLAC_ERROR; + #endif + #ifdef EUSERS + case EUSERS: return DRFLAC_ERROR; + #endif + #ifdef ENOTSOCK + case ENOTSOCK: return DRFLAC_NOT_SOCKET; + #endif + #ifdef EDESTADDRREQ + case EDESTADDRREQ: return DRFLAC_NO_ADDRESS; + #endif + #ifdef EMSGSIZE + case EMSGSIZE: return DRFLAC_TOO_BIG; + #endif + #ifdef EPROTOTYPE + case EPROTOTYPE: return DRFLAC_BAD_PROTOCOL; + #endif + #ifdef ENOPROTOOPT + case ENOPROTOOPT: return DRFLAC_PROTOCOL_UNAVAILABLE; + #endif + #ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: return DRFLAC_PROTOCOL_NOT_SUPPORTED; + #endif + #ifdef ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: return DRFLAC_SOCKET_NOT_SUPPORTED; + #endif + #ifdef EOPNOTSUPP + case EOPNOTSUPP: return DRFLAC_INVALID_OPERATION; + #endif + #ifdef EPFNOSUPPORT + case EPFNOSUPPORT: return DRFLAC_PROTOCOL_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EAFNOSUPPORT + case EAFNOSUPPORT: return DRFLAC_ADDRESS_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EADDRINUSE + case EADDRINUSE: return DRFLAC_ALREADY_IN_USE; + #endif + #ifdef EADDRNOTAVAIL + case EADDRNOTAVAIL: return DRFLAC_ERROR; + #endif + #ifdef ENETDOWN + case ENETDOWN: return DRFLAC_NO_NETWORK; + #endif + #ifdef ENETUNREACH + case ENETUNREACH: return DRFLAC_NO_NETWORK; + #endif + #ifdef ENETRESET + case ENETRESET: return DRFLAC_NO_NETWORK; + #endif + #ifdef ECONNABORTED + case ECONNABORTED: return DRFLAC_NO_NETWORK; + #endif + #ifdef ECONNRESET + case ECONNRESET: return DRFLAC_CONNECTION_RESET; + #endif + #ifdef ENOBUFS + case ENOBUFS: return DRFLAC_NO_SPACE; + #endif + #ifdef EISCONN + case EISCONN: return DRFLAC_ALREADY_CONNECTED; + #endif + #ifdef ENOTCONN + case ENOTCONN: return DRFLAC_NOT_CONNECTED; + #endif + #ifdef ESHUTDOWN + case ESHUTDOWN: return DRFLAC_ERROR; + #endif + #ifdef ETOOMANYREFS + case ETOOMANYREFS: return DRFLAC_ERROR; + #endif + #ifdef ETIMEDOUT + case ETIMEDOUT: return DRFLAC_TIMEOUT; + #endif + #ifdef ECONNREFUSED + case ECONNREFUSED: return DRFLAC_CONNECTION_REFUSED; + #endif + #ifdef EHOSTDOWN + case EHOSTDOWN: return DRFLAC_NO_HOST; + #endif + #ifdef EHOSTUNREACH + case EHOSTUNREACH: return DRFLAC_NO_HOST; + #endif + #ifdef EALREADY + case EALREADY: return DRFLAC_IN_PROGRESS; + #endif + #ifdef EINPROGRESS + case EINPROGRESS: return DRFLAC_IN_PROGRESS; + #endif + #ifdef ESTALE + case ESTALE: return DRFLAC_INVALID_FILE; + #endif + #ifdef EUCLEAN + case EUCLEAN: return DRFLAC_ERROR; + #endif + #ifdef ENOTNAM + case ENOTNAM: return DRFLAC_ERROR; + #endif + #ifdef ENAVAIL + case ENAVAIL: return DRFLAC_ERROR; + #endif + #ifdef EISNAM + case EISNAM: return DRFLAC_ERROR; + #endif + #ifdef EREMOTEIO + case EREMOTEIO: return DRFLAC_IO_ERROR; + #endif + #ifdef EDQUOT + case EDQUOT: return DRFLAC_NO_SPACE; + #endif + #ifdef ENOMEDIUM + case ENOMEDIUM: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef EMEDIUMTYPE + case EMEDIUMTYPE: return DRFLAC_ERROR; + #endif + #ifdef ECANCELED + case ECANCELED: return DRFLAC_CANCELLED; + #endif + #ifdef ENOKEY + case ENOKEY: return DRFLAC_ERROR; + #endif + #ifdef EKEYEXPIRED + case EKEYEXPIRED: return DRFLAC_ERROR; + #endif + #ifdef EKEYREVOKED + case EKEYREVOKED: return DRFLAC_ERROR; + #endif + #ifdef EKEYREJECTED + case EKEYREJECTED: return DRFLAC_ERROR; + #endif + #ifdef EOWNERDEAD + case EOWNERDEAD: return DRFLAC_ERROR; + #endif + #ifdef ENOTRECOVERABLE + case ENOTRECOVERABLE: return DRFLAC_ERROR; + #endif + #ifdef ERFKILL + case ERFKILL: return DRFLAC_ERROR; + #endif + #ifdef EHWPOISON + case EHWPOISON: return DRFLAC_ERROR; + #endif + default: return DRFLAC_ERROR; + } +} +/* End Errno */ + +/* fopen */ +static drflac_result drflac_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err; +#endif + + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRFLAC_INVALID_ARGS; + } + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + err = fopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drflac_result_from_errno(err); + } +#else +#if defined(_WIN32) || defined(__APPLE__) + *ppFile = fopen(pFilePath, pOpenMode); +#else + #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) + *ppFile = fopen64(pFilePath, pOpenMode); + #else + *ppFile = fopen(pFilePath, pOpenMode); + #endif +#endif + if (*ppFile == NULL) { + drflac_result result = drflac_result_from_errno(errno); + if (result == DRFLAC_SUCCESS) { + result = DRFLAC_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ + } + + return result; + } +#endif + + return DRFLAC_SUCCESS; +} + +/* +_wfopen() isn't always available in all compilation environments. + + * Windows only. + * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). + * MinGW-64 (both 32- and 64-bit) seems to support it. + * MinGW wraps it in !defined(__STRICT_ANSI__). + * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). + +This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() +fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. +*/ +#if defined(_WIN32) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) + #define DRFLAC_HAS_WFOPEN + #endif +#endif + +#ifndef DR_FLAC_NO_WCHAR +static drflac_result drflac_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRFLAC_INVALID_ARGS; + } + +#if defined(DRFLAC_HAS_WFOPEN) + { + /* Use _wfopen() on Windows. */ + #if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drflac_result_from_errno(err); + } + #else + *ppFile = _wfopen(pFilePath, pOpenMode); + if (*ppFile == NULL) { + return drflac_result_from_errno(errno); + } + #endif + (void)pAllocationCallbacks; + } +#else + /* + Use fopen() on anything other than Windows. Requires a conversion. This is annoying because + fopen() is locale specific. The only real way I can think of to do this is with wcsrtombs(). Note + that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for + maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler + error I'll look into improving compatibility. + */ + + /* + Some compilers don't support wchar_t or wcsrtombs() which we're using below. In this case we just + need to abort with an error. If you encounter a compiler lacking such support, add it to this list + and submit a bug report and it'll be added to the library upstream. + */ + #if defined(__DJGPP__) + { + /* Nothing to do here. This will fall through to the error check below. */ + } + #else + { + mbstate_t mbs; + size_t lenMB; + const wchar_t* pFilePathTemp = pFilePath; + char* pFilePathMB = NULL; + char pOpenModeMB[32] = {0}; + + /* Get the length first. */ + DRFLAC_ZERO_OBJECT(&mbs); + lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); + if (lenMB == (size_t)-1) { + return drflac_result_from_errno(errno); + } + + pFilePathMB = (char*)drflac__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); + if (pFilePathMB == NULL) { + return DRFLAC_OUT_OF_MEMORY; + } + + pFilePathTemp = pFilePath; + DRFLAC_ZERO_OBJECT(&mbs); + wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); + + /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ + { + size_t i = 0; + for (;;) { + if (pOpenMode[i] == 0) { + pOpenModeMB[i] = '\0'; + break; + } + + pOpenModeMB[i] = (char)pOpenMode[i]; + i += 1; + } + } + + *ppFile = fopen(pFilePathMB, pOpenModeMB); + + drflac__free_from_callbacks(pFilePathMB, pAllocationCallbacks); + } + #endif + + if (*ppFile == NULL) { + return DRFLAC_ERROR; + } +#endif + + return DRFLAC_SUCCESS; +} +#endif +/* End fopen */ + +static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead) +{ + return fread(bufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +static drflac_bool32 drflac__on_seek_stdio(void* pUserData, int offset, drflac_seek_origin origin) +{ + int whence = SEEK_SET; + if (origin == DRFLAC_SEEK_CUR) { + whence = SEEK_CUR; + } else if (origin == DRFLAC_SEEK_END) { + whence = SEEK_END; + } + + return fseek((FILE*)pUserData, offset, whence) == 0; +} + +static drflac_bool32 drflac__on_tell_stdio(void* pUserData, drflac_int64* pCursor) +{ + FILE* pFileStdio = (FILE*)pUserData; + drflac_int64 result; + + /* These were all validated at a higher level. */ + DRFLAC_ASSERT(pFileStdio != NULL); + DRFLAC_ASSERT(pCursor != NULL); + +#if defined(_WIN32) && !defined(NXDK) + #if defined(_MSC_VER) && _MSC_VER > 1200 + result = _ftelli64(pFileStdio); + #else + result = ftell(pFileStdio); + #endif +#else + result = ftell(pFileStdio); +#endif + + *pCursor = result; + + return DRFLAC_TRUE; +} + + + +DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + FILE* pFile; + + if (drflac_fopen(&pFile, pFileName, "rb") != DRFLAC_SUCCESS) { + return NULL; + } + + pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, drflac__on_tell_stdio, (void*)pFile, pAllocationCallbacks); + if (pFlac == NULL) { + fclose(pFile); + return NULL; + } + + return pFlac; +} + +#ifndef DR_FLAC_NO_WCHAR +DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + FILE* pFile; + + if (drflac_wfopen(&pFile, pFileName, L"rb", pAllocationCallbacks) != DRFLAC_SUCCESS) { + return NULL; + } + + pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, drflac__on_tell_stdio, (void*)pFile, pAllocationCallbacks); + if (pFlac == NULL) { + fclose(pFile); + return NULL; + } + + return pFlac; +} +#endif + +DRFLAC_API drflac* drflac_open_file_with_metadata(const char* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + FILE* pFile; + + if (drflac_fopen(&pFile, pFileName, "rb") != DRFLAC_SUCCESS) { + return NULL; + } + + pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, drflac__on_tell_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + fclose(pFile); + return pFlac; + } + + return pFlac; +} + +#ifndef DR_FLAC_NO_WCHAR +DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + FILE* pFile; + + if (drflac_wfopen(&pFile, pFileName, L"rb", pAllocationCallbacks) != DRFLAC_SUCCESS) { + return NULL; + } + + pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, drflac__on_tell_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + fclose(pFile); + return pFlac; + } + + return pFlac; +} +#endif +#endif /* DR_FLAC_NO_STDIO */ + +static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t bytesToRead) +{ + drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + size_t bytesRemaining; + + DRFLAC_ASSERT(memoryStream != NULL); + DRFLAC_ASSERT(memoryStream->dataSize >= memoryStream->currentReadPos); + + bytesRemaining = memoryStream->dataSize - memoryStream->currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + DRFLAC_COPY_MEMORY(bufferOut, memoryStream->data + memoryStream->currentReadPos, bytesToRead); + memoryStream->currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +static drflac_bool32 drflac__on_seek_memory(void* pUserData, int offset, drflac_seek_origin origin) +{ + drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + drflac_int64 newCursor; + + DRFLAC_ASSERT(memoryStream != NULL); + + if (origin == DRFLAC_SEEK_SET) { + newCursor = 0; + } else if (origin == DRFLAC_SEEK_CUR) { + newCursor = (drflac_int64)memoryStream->currentReadPos; + } else if (origin == DRFLAC_SEEK_END) { + newCursor = (drflac_int64)memoryStream->dataSize; + } else { + DRFLAC_ASSERT(!"Invalid seek origin"); + return DRFLAC_FALSE; + } + + newCursor += offset; + + if (newCursor < 0) { + return DRFLAC_FALSE; /* Trying to seek prior to the start of the buffer. */ + } + if ((size_t)newCursor > memoryStream->dataSize) { + return DRFLAC_FALSE; /* Trying to seek beyond the end of the buffer. */ + } + + memoryStream->currentReadPos = (size_t)newCursor; + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__on_tell_memory(void* pUserData, drflac_int64* pCursor) +{ + drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + + DRFLAC_ASSERT(memoryStream != NULL); + DRFLAC_ASSERT(pCursor != NULL); + + *pCursor = (drflac_int64)memoryStream->currentReadPos; + return DRFLAC_TRUE; +} + +DRFLAC_API drflac* drflac_open_memory(const void* pData, size_t dataSize, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac__memory_stream memoryStream; + drflac* pFlac; + + memoryStream.data = (const drflac_uint8*)pData; + memoryStream.dataSize = dataSize; + memoryStream.currentReadPos = 0; + pFlac = drflac_open(drflac__on_read_memory, drflac__on_seek_memory, drflac__on_tell_memory, &memoryStream, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + pFlac->memoryStream = memoryStream; + + /* This is an awful hack... */ +#ifndef DR_FLAC_NO_OGG + if (pFlac->container == drflac_container_ogg) + { + drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + oggbs->pUserData = &pFlac->memoryStream; + } + else +#endif + { + pFlac->bs.pUserData = &pFlac->memoryStream; + } + + return pFlac; +} + +DRFLAC_API drflac* drflac_open_memory_with_metadata(const void* pData, size_t dataSize, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac__memory_stream memoryStream; + drflac* pFlac; + + memoryStream.data = (const drflac_uint8*)pData; + memoryStream.dataSize = dataSize; + memoryStream.currentReadPos = 0; + pFlac = drflac_open_with_metadata_private(drflac__on_read_memory, drflac__on_seek_memory, drflac__on_tell_memory, onMeta, drflac_container_unknown, &memoryStream, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + pFlac->memoryStream = memoryStream; + + /* This is an awful hack... */ +#ifndef DR_FLAC_NO_OGG + if (pFlac->container == drflac_container_ogg) + { + drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + oggbs->pUserData = &pFlac->memoryStream; + } + else +#endif + { + pFlac->bs.pUserData = &pFlac->memoryStream; + } + + return pFlac; +} + + + +DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + return drflac_open_with_metadata_private(onRead, onSeek, onTell, NULL, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); +} +DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + return drflac_open_with_metadata_private(onRead, onSeek, onTell, NULL, container, pUserData, pUserData, pAllocationCallbacks); +} + +DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + return drflac_open_with_metadata_private(onRead, onSeek, onTell, onMeta, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); +} +DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + return drflac_open_with_metadata_private(onRead, onSeek, onTell, onMeta, container, pUserData, pUserData, pAllocationCallbacks); +} + +DRFLAC_API void drflac_close(drflac* pFlac) { if (pFlac == NULL) { return; } #ifndef DR_FLAC_NO_STDIO - // If we opened the file with drflac_open_file() we will want to close the file handle. We can know whether or not drflac_open_file() - // was used by looking at the callbacks. - if (pFlac->onRead == drflac__on_read_stdio) { -#if defined(DR_OPUS_NO_WIN32_IO) || !defined(_WIN32) - fclose((FILE*)pFlac->pUserData); + /* + If we opened the file with drflac_open_file() we will want to close the file handle. We can know whether or not drflac_open_file() + was used by looking at the callbacks. + */ + if (pFlac->bs.onRead == drflac__on_read_stdio) { + fclose((FILE*)pFlac->bs.pUserData); + } + +#ifndef DR_FLAC_NO_OGG + /* Need to clean up Ogg streams a bit differently due to the way the bit streaming is chained. */ + if (pFlac->container == drflac_container_ogg) { + drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + DRFLAC_ASSERT(pFlac->bs.onRead == drflac__on_read_ogg); + + if (oggbs->onRead == drflac__on_read_stdio) { + fclose((FILE*)oggbs->pUserData); + } + } +#endif +#endif + + drflac__free_from_callbacks(pFlac, &pFlac->allocationCallbacks); +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 left = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 left0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 left1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 left2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 left3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 right0 = left0 - side0; + drflac_uint32 right1 = left1 - side1; + drflac_uint32 right2 = left2 - side2; + drflac_uint32 right3 = left3 - side3; + + pOutputSamples[i*8+0] = (drflac_int32)left0; + pOutputSamples[i*8+1] = (drflac_int32)right0; + pOutputSamples[i*8+2] = (drflac_int32)left1; + pOutputSamples[i*8+3] = (drflac_int32)right1; + pOutputSamples[i*8+4] = (drflac_int32)left2; + pOutputSamples[i*8+5] = (drflac_int32)right2; + pOutputSamples[i*8+6] = (drflac_int32)left3; + pOutputSamples[i*8+7] = (drflac_int32)right3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i right = _mm_sub_epi32(left, side); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t left; + uint32x4_t side; + uint32x4_t right; + + left = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + right = vsubq_u32(left, side); + + drflac__vst2q_u32((drflac_uint32*)pOutputSamples + i*8, vzipq_u32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_left_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s32__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else - CloseHandle((HANDLE)pFlac->pUserData); + drflac_read_pcm_frames_s32__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 side = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 right = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} #endif - // If we opened the file with drflac_open_memory() we will want to free() the user data. - if (pFlac->onRead == drflac__on_read_memory) { - free(pFlac->pUserData); +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 side0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 side1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 side2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 side3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 right0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 right1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 right2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 right3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 left0 = right0 + side0; + drflac_uint32 left1 = right1 + side1; + drflac_uint32 left2 = right2 + side2; + drflac_uint32 left3 = right3 + side3; + + pOutputSamples[i*8+0] = (drflac_int32)left0; + pOutputSamples[i*8+1] = (drflac_int32)right0; + pOutputSamples[i*8+2] = (drflac_int32)left1; + pOutputSamples[i*8+3] = (drflac_int32)right1; + pOutputSamples[i*8+4] = (drflac_int32)left2; + pOutputSamples[i*8+5] = (drflac_int32)right2; + pOutputSamples[i*8+6] = (drflac_int32)left3; + pOutputSamples[i*8+7] = (drflac_int32)right3; } - free(pFlac); + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } } -static uint64_t drflac__read_s16__misaligned(drflac* pFlac, uint64_t samplesToRead, int16_t* bufferOut) +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { - unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.channelAssignment); + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; - // We should never be calling this when the number of samples to read is >= the sample count. - assert(samplesToRead < channelCount); - assert(pFlac->currentFrame.samplesRemaining > 0 && samplesToRead <= pFlac->currentFrame.samplesRemaining); + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + for (i = 0; i < frameCount4; ++i) { + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i left = _mm_add_epi32(right, side); - uint64_t samplesRead = 0; - while (samplesToRead > 0) + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t side; + uint32x4_t right; + uint32x4_t left; + + side = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + left = vaddq_u32(right, side); + + drflac__vst2q_u32((drflac_uint32*)pOutputSamples + i*8, vzipq_u32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_right_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif { - uint64_t totalSamplesInFrame = pFlac->currentFrame.blockSize * channelCount; - uint64_t samplesReadFromFrameSoFar = totalSamplesInFrame - pFlac->currentFrame.samplesRemaining; - unsigned int channelIndex = samplesReadFromFrameSoFar % channelCount; - - unsigned long long nextSampleInFrame = samplesReadFromFrameSoFar / channelCount; - - int decodedSample = 0; - switch (pFlac->currentFrame.channelAssignment) - { - case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: - { - if (channelIndex == 0) { - decodedSample = pFlac->currentFrame.subframes[channelIndex].pDecodedSamples[nextSampleInFrame]; - } else { - int side = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame]; - int left = pFlac->currentFrame.subframes[channelIndex - 1].pDecodedSamples[nextSampleInFrame]; - decodedSample = left - side; - } - - } break; - - case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: - { - if (channelIndex == 0) { - int side = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame]; - int right = pFlac->currentFrame.subframes[channelIndex + 1].pDecodedSamples[nextSampleInFrame]; - decodedSample = side + right; - } else { - decodedSample = pFlac->currentFrame.subframes[channelIndex].pDecodedSamples[nextSampleInFrame]; - } - - } break; - - case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: - { - int mid; - int side; - if (channelIndex == 0) { - mid = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame]; - side = pFlac->currentFrame.subframes[channelIndex + 1].pDecodedSamples[nextSampleInFrame]; - - mid = (((unsigned int)mid) << 1) | (side & 0x01); - decodedSample = (mid + side) >> 1; - } else { - mid = pFlac->currentFrame.subframes[channelIndex - 1].pDecodedSamples[nextSampleInFrame]; - side = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame]; - - mid = (((unsigned int)mid) << 1) | (side & 0x01); - decodedSample = (mid - side) >> 1; - } - - } break; - - case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: - default: - { - decodedSample = pFlac->currentFrame.subframes[channelIndex].pDecodedSamples[nextSampleInFrame]; - } break; - } - - int shift = (16 - pFlac->bitsPerSample) + pFlac->currentFrame.subframes[channelIndex].wastedBitsPerSample; - if (shift >= 0) { - decodedSample <<= shift; - } else { - decodedSample >>= -shift; - } - - if (bufferOut) { - *bufferOut++ = decodedSample; - } - - samplesRead += 1; - pFlac->currentFrame.samplesRemaining -= 1; - samplesToRead -= 1; + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s32__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s32__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif } - - return samplesRead; } -static uint64_t drflac__seek_forward_by_samples(drflac* pFlac, uint64_t samplesToRead) + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { - uint64_t samplesRead = 0; - while (samplesToRead > 0) + for (drflac_uint64 i = 0; i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample); + pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_int32 shift = unusedBitsPerSample; + + if (shift > 0) { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (mid0 + side0) << shift; + temp1L = (mid1 + side1) << shift; + temp2L = (mid2 + side2) << shift; + temp3L = (mid3 + side3) << shift; + + temp0R = (mid0 - side0) << shift; + temp1R = (mid1 - side1) << shift; + temp2R = (mid2 - side2) << shift; + temp3R = (mid3 - side3) << shift; + + pOutputSamples[i*8+0] = (drflac_int32)temp0L; + pOutputSamples[i*8+1] = (drflac_int32)temp0R; + pOutputSamples[i*8+2] = (drflac_int32)temp1L; + pOutputSamples[i*8+3] = (drflac_int32)temp1R; + pOutputSamples[i*8+4] = (drflac_int32)temp2L; + pOutputSamples[i*8+5] = (drflac_int32)temp2R; + pOutputSamples[i*8+6] = (drflac_int32)temp3L; + pOutputSamples[i*8+7] = (drflac_int32)temp3R; + } + } else { + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (drflac_uint32)((drflac_int32)(mid0 + side0) >> 1); + temp1L = (drflac_uint32)((drflac_int32)(mid1 + side1) >> 1); + temp2L = (drflac_uint32)((drflac_int32)(mid2 + side2) >> 1); + temp3L = (drflac_uint32)((drflac_int32)(mid3 + side3) >> 1); + + temp0R = (drflac_uint32)((drflac_int32)(mid0 - side0) >> 1); + temp1R = (drflac_uint32)((drflac_int32)(mid1 - side1) >> 1); + temp2R = (drflac_uint32)((drflac_int32)(mid2 - side2) >> 1); + temp3R = (drflac_uint32)((drflac_int32)(mid3 - side3) >> 1); + + pOutputSamples[i*8+0] = (drflac_int32)temp0L; + pOutputSamples[i*8+1] = (drflac_int32)temp0R; + pOutputSamples[i*8+2] = (drflac_int32)temp1L; + pOutputSamples[i*8+3] = (drflac_int32)temp1R; + pOutputSamples[i*8+4] = (drflac_int32)temp2L; + pOutputSamples[i*8+5] = (drflac_int32)temp2R; + pOutputSamples[i*8+6] = (drflac_int32)temp3L; + pOutputSamples[i*8+7] = (drflac_int32)temp3R; + } + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample); + pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_int32 shift = unusedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i left; + __m128i right; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + left = _mm_srai_epi32(_mm_add_epi32(mid, side), 1); + right = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)(mid + side) >> 1; + pOutputSamples[i*2+1] = (drflac_int32)(mid - side) >> 1; + } + } else { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i left; + __m128i right; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + left = _mm_slli_epi32(_mm_add_epi32(mid, side), shift); + right = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift); + pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift); + } + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_int32 shift = unusedBitsPerSample; + int32x4_t wbpsShift0_4; /* wbps = Wasted Bits Per Sample */ + int32x4_t wbpsShift1_4; /* wbps = Wasted Bits Per Sample */ + uint32x4_t one4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + wbpsShift0_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + wbpsShift1_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + one4 = vdupq_n_u32(1); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t left; + int32x4_t right; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, one4)); + + left = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1); + right = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); + + drflac__vst2q_s32(pOutputSamples + i*8, vzipq_s32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)(mid + side) >> 1; + pOutputSamples[i*2+1] = (drflac_int32)(mid - side) >> 1; + } + } else { + int32x4_t shift4; + + shift -= 1; + shift4 = vdupq_n_s32(shift); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t left; + int32x4_t right; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, one4)); + + left = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4)); + right = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); + + drflac__vst2q_s32(pOutputSamples + i*8, vzipq_s32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift); + pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift); + } + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_mid_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif { - if (pFlac->currentFrame.samplesRemaining == 0) - { - if (!drflac__read_and_decode_next_frame(pFlac)) { - break; // Couldn't read the next frame, so just break from the loop and return. - } - } - else - { - samplesRead += 1; - pFlac->currentFrame.samplesRemaining -= 1; - samplesToRead -= 1; - } + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s32__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s32__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif } - - return samplesRead; } -static uint64_t drflac_read_s16(drflac* pFlac, uint64_t samplesToRead, int16_t* bufferOut) + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { - // Note that is allowed to be null, in which case this will be treated as something like a seek. - if (pFlac == NULL || samplesToRead == 0) { + for (drflac_uint64 i = 0; i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample)); + pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample)); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 tempL0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 tempL1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 tempL2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 tempL3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 tempR0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 tempR1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 tempR2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 tempR3 = pInputSamples1U32[i*4+3] << shift1; + + pOutputSamples[i*8+0] = (drflac_int32)tempL0; + pOutputSamples[i*8+1] = (drflac_int32)tempR0; + pOutputSamples[i*8+2] = (drflac_int32)tempL1; + pOutputSamples[i*8+3] = (drflac_int32)tempR1; + pOutputSamples[i*8+4] = (drflac_int32)tempL2; + pOutputSamples[i*8+5] = (drflac_int32)tempR2; + pOutputSamples[i*8+6] = (drflac_int32)tempL3; + pOutputSamples[i*8+7] = (drflac_int32)tempR3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0); + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0); + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1); + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + int32x4_t shift4_0 = vdupq_n_s32(shift0); + int32x4_t shift4_1 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + int32x4_t left; + int32x4_t right; + + left = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift4_0)); + right = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift4_1)); + + drflac__vst2q_s32(pOutputSamples + i*8, vzipq_s32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0); + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_independent_stereo__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s32__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s32__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s32(drflac* pFlac, drflac_uint64 framesToRead, drflac_int32* pBufferOut) +{ + drflac_uint64 framesRead; + drflac_uint32 unusedBitsPerSample; + + if (pFlac == NULL || framesToRead == 0) { return 0; } - if (bufferOut == NULL) { - return drflac__seek_forward_by_samples(pFlac, samplesToRead); + if (pBufferOut == NULL) { + return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); } + DRFLAC_ASSERT(pFlac->bitsPerSample <= 32); + unusedBitsPerSample = 32 - pFlac->bitsPerSample; - uint64_t samplesRead = 0; - while (samplesToRead > 0) - { - // If we've run out of samples in this frame, go to the next. - if (pFlac->currentFrame.samplesRemaining == 0) - { - if (!drflac__read_and_decode_next_frame(pFlac)) { - break; // Couldn't read the next frame, so just break from the loop and return. + framesRead = 0; + while (framesToRead > 0) { + /* If we've run out of samples in this frame, go to the next. */ + if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + break; /* Couldn't read the next frame, so just break from the loop and return. */ } - } - else - { - // Here is where we grab the samples and interleave them. + } else { + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + drflac_uint64 iFirstPCMFrame = pFlac->currentFLACFrame.header.blockSizeInPCMFrames - pFlac->currentFLACFrame.pcmFramesRemaining; + drflac_uint64 frameCountThisIteration = framesToRead; - unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.channelAssignment); - uint64_t totalSamplesInFrame = pFlac->currentFrame.blockSize * channelCount; - uint64_t samplesReadFromFrameSoFar = totalSamplesInFrame - pFlac->currentFrame.samplesRemaining; - - int misalignedSampleCount = samplesReadFromFrameSoFar % channelCount; - if (misalignedSampleCount > 0) { - uint64_t misalignedSamplesRead = drflac__read_s16__misaligned(pFlac, misalignedSampleCount, bufferOut); - samplesRead += misalignedSamplesRead; - samplesReadFromFrameSoFar += misalignedSamplesRead; - bufferOut += misalignedSamplesRead; - samplesToRead -= misalignedSamplesRead; + if (frameCountThisIteration > pFlac->currentFLACFrame.pcmFramesRemaining) { + frameCountThisIteration = pFlac->currentFLACFrame.pcmFramesRemaining; } + if (channelCount == 2) { + const drflac_int32* pDecodedSamples0 = pFlac->currentFLACFrame.subframes[0].pSamplesS32 + iFirstPCMFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFLACFrame.subframes[1].pSamplesS32 + iFirstPCMFrame; - uint64_t alignedSampleCountPerChannel = samplesToRead / channelCount; - if (alignedSampleCountPerChannel > pFlac->currentFrame.samplesRemaining / channelCount) { - alignedSampleCountPerChannel = pFlac->currentFrame.samplesRemaining / channelCount; - } - - uint64_t firstAlignedSampleInFrame = samplesReadFromFrameSoFar / channelCount; - int unusedBitsPerSample = 16 - pFlac->bitsPerSample; - - if (unusedBitsPerSample >= 0) { - int lshift0 = unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample; - int lshift1 = unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample; - - switch (pFlac->currentFrame.channelAssignment) + switch (pFlac->currentFLACFrame.header.channelAssignment) { case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: { - const int* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; - const int* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; - - for (uint64_t i = 0; i < alignedSampleCountPerChannel; ++i) { - int left = pDecodedSamples0[i]; - int side = pDecodedSamples1[i]; - int right = left - side; - - bufferOut[i*2+0] = left << lshift0; - bufferOut[i*2+1] = right << lshift1; - } + drflac_read_pcm_frames_s32__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: { - const int* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; - const int* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; - - for (uint64_t i = 0; i < alignedSampleCountPerChannel; ++i) { - int side = pDecodedSamples0[i]; - int right = pDecodedSamples1[i]; - int left = right + side; - - bufferOut[i*2+0] = left << lshift0; - bufferOut[i*2+1] = right << lshift1; - } + drflac_read_pcm_frames_s32__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: { - const int* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; - const int* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; - - for (uint64_t i = 0; i < alignedSampleCountPerChannel; ++i) { - int side = pDecodedSamples1[i]; - int mid = (((uint32_t)pDecodedSamples0[i]) << 1) | (side & 0x01); - - bufferOut[i*2+0] = ((mid + side) >> 1) << lshift0; - bufferOut[i*2+1] = ((mid - side) >> 1) << lshift1; - } + drflac_read_pcm_frames_s32__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: default: { - if (pFlac->currentFrame.channelAssignment == 1) // 1 = Stereo - { - // Stereo optimized inner loop unroll. - const int* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; - const int* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; - - for (uint64_t i = 0; i < alignedSampleCountPerChannel; ++i) { - bufferOut[i*2+0] = pDecodedSamples0[i] << lshift0; - bufferOut[i*2+1] = pDecodedSamples1[i] << lshift1; - } - } - else - { - // Generic interleaving. - for (uint64_t i = 0; i < alignedSampleCountPerChannel; ++i) { - for (unsigned int j = 0; j < channelCount; ++j) { - bufferOut[(i*channelCount)+j] = (pFlac->currentFrame.subframes[j].pDecodedSamples[firstAlignedSampleInFrame + i]) << (unusedBitsPerSample + pFlac->currentFrame.subframes[j].wastedBitsPerSample); - } - } - } + drflac_read_pcm_frames_s32__decode_independent_stereo(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; } } else { - int rshift0 = -unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample; - int rshift1 = -unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample; + /* Generic interleaving. */ + drflac_uint64 i; + for (i = 0; i < frameCountThisIteration; ++i) { + unsigned int j; + for (j = 0; j < channelCount; ++j) { + pBufferOut[(i*channelCount)+j] = (drflac_int32)((drflac_uint32)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample)); + } + } + } - switch (pFlac->currentFrame.channelAssignment) + framesRead += frameCountThisIteration; + pBufferOut += frameCountThisIteration * channelCount; + framesToRead -= frameCountThisIteration; + pFlac->currentPCMFrame += frameCountThisIteration; + pFlac->currentFLACFrame.pcmFramesRemaining -= (drflac_uint32)frameCountThisIteration; + } + } + + return framesRead; +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 left = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 right = left - side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 left0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 left1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 left2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 left3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 right0 = left0 - side0; + drflac_uint32 right1 = left1 - side1; + drflac_uint32 right2 = left2 - side2; + drflac_uint32 right3 = left3 - side3; + + left0 >>= 16; + left1 >>= 16; + left2 >>= 16; + left3 >>= 16; + + right0 >>= 16; + right1 >>= 16; + right2 >>= 16; + right3 >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)left0; + pOutputSamples[i*8+1] = (drflac_int16)right0; + pOutputSamples[i*8+2] = (drflac_int16)left1; + pOutputSamples[i*8+3] = (drflac_int16)right1; + pOutputSamples[i*8+4] = (drflac_int16)left2; + pOutputSamples[i*8+5] = (drflac_int16)right2; + pOutputSamples[i*8+6] = (drflac_int16)left3; + pOutputSamples[i*8+7] = (drflac_int16)right3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i right = _mm_sub_epi32(left, side); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t left; + uint32x4_t side; + uint32x4_t right; + + left = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + right = vsubq_u32(left, side); + + left = vshrq_n_u32(left, 16); + right = vshrq_n_u32(right, 16); + + drflac__vst2q_u16((drflac_uint16*)pOutputSamples + i*8, vzip_u16(vmovn_u32(left), vmovn_u32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_left_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s16__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s16__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 side = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 right = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 left = right + side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 side0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 side1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 side2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 side3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 right0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 right1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 right2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 right3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 left0 = right0 + side0; + drflac_uint32 left1 = right1 + side1; + drflac_uint32 left2 = right2 + side2; + drflac_uint32 left3 = right3 + side3; + + left0 >>= 16; + left1 >>= 16; + left2 >>= 16; + left3 >>= 16; + + right0 >>= 16; + right1 >>= 16; + right2 >>= 16; + right3 >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)left0; + pOutputSamples[i*8+1] = (drflac_int16)right0; + pOutputSamples[i*8+2] = (drflac_int16)left1; + pOutputSamples[i*8+3] = (drflac_int16)right1; + pOutputSamples[i*8+4] = (drflac_int16)left2; + pOutputSamples[i*8+5] = (drflac_int16)right2; + pOutputSamples[i*8+6] = (drflac_int16)left3; + pOutputSamples[i*8+7] = (drflac_int16)right3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + for (i = 0; i < frameCount4; ++i) { + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i left = _mm_add_epi32(right, side); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t side; + uint32x4_t right; + uint32x4_t left; + + side = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + left = vaddq_u32(right, side); + + left = vshrq_n_u32(left, 16); + right = vshrq_n_u32(right, 16); + + drflac__vst2q_u16((drflac_uint16*)pOutputSamples + i*8, vzip_u16(vmovn_u32(left), vmovn_u32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_right_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s16__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s16__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + drflac_uint32 mid = (drflac_uint32)pInputSamples0[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample) >> 16); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample; + + if (shift > 0) { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (mid0 + side0) << shift; + temp1L = (mid1 + side1) << shift; + temp2L = (mid2 + side2) << shift; + temp3L = (mid3 + side3) << shift; + + temp0R = (mid0 - side0) << shift; + temp1R = (mid1 - side1) << shift; + temp2R = (mid2 - side2) << shift; + temp3R = (mid3 - side3) << shift; + + temp0L >>= 16; + temp1L >>= 16; + temp2L >>= 16; + temp3L >>= 16; + + temp0R >>= 16; + temp1R >>= 16; + temp2R >>= 16; + temp3R >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)temp0L; + pOutputSamples[i*8+1] = (drflac_int16)temp0R; + pOutputSamples[i*8+2] = (drflac_int16)temp1L; + pOutputSamples[i*8+3] = (drflac_int16)temp1R; + pOutputSamples[i*8+4] = (drflac_int16)temp2L; + pOutputSamples[i*8+5] = (drflac_int16)temp2R; + pOutputSamples[i*8+6] = (drflac_int16)temp3L; + pOutputSamples[i*8+7] = (drflac_int16)temp3R; + } + } else { + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = ((drflac_int32)(mid0 + side0) >> 1); + temp1L = ((drflac_int32)(mid1 + side1) >> 1); + temp2L = ((drflac_int32)(mid2 + side2) >> 1); + temp3L = ((drflac_int32)(mid3 + side3) >> 1); + + temp0R = ((drflac_int32)(mid0 - side0) >> 1); + temp1R = ((drflac_int32)(mid1 - side1) >> 1); + temp2R = ((drflac_int32)(mid2 - side2) >> 1); + temp3R = ((drflac_int32)(mid3 - side3) >> 1); + + temp0L >>= 16; + temp1L >>= 16; + temp2L >>= 16; + temp3L >>= 16; + + temp0R >>= 16; + temp1R >>= 16; + temp2R >>= 16; + temp3R >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)temp0L; + pOutputSamples[i*8+1] = (drflac_int16)temp0R; + pOutputSamples[i*8+2] = (drflac_int16)temp1L; + pOutputSamples[i*8+3] = (drflac_int16)temp1R; + pOutputSamples[i*8+4] = (drflac_int16)temp2L; + pOutputSamples[i*8+5] = (drflac_int16)temp2R; + pOutputSamples[i*8+6] = (drflac_int16)temp3L; + pOutputSamples[i*8+7] = (drflac_int16)temp3R; + } + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample) >> 16); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i left; + __m128i right; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + left = _mm_srai_epi32(_mm_add_epi32(mid, side), 1); + right = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((drflac_int32)(mid + side) >> 1) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((drflac_int32)(mid - side) >> 1) >> 16); + } + } else { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i left; + __m128i right; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + left = _mm_slli_epi32(_mm_add_epi32(mid, side), shift); + right = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((mid + side) << shift) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((mid - side) << shift) >> 16); + } + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample; + int32x4_t wbpsShift0_4; /* wbps = Wasted Bits Per Sample */ + int32x4_t wbpsShift1_4; /* wbps = Wasted Bits Per Sample */ + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + wbpsShift0_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + wbpsShift1_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t left; + int32x4_t right; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); + + left = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1); + right = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); + + left = vshrq_n_s32(left, 16); + right = vshrq_n_s32(right, 16); + + drflac__vst2q_s16(pOutputSamples + i*8, vzip_s16(vmovn_s32(left), vmovn_s32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((drflac_int32)(mid + side) >> 1) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((drflac_int32)(mid - side) >> 1) >> 16); + } + } else { + int32x4_t shift4; + + shift -= 1; + shift4 = vdupq_n_s32(shift); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t left; + int32x4_t right; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); + + left = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4)); + right = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); + + left = vshrq_n_s32(left, 16); + right = vshrq_n_s32(right, 16); + + drflac__vst2q_s16(pOutputSamples + i*8, vzip_s16(vmovn_s32(left), vmovn_s32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((mid + side) << shift) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((mid - side) << shift) >> 16); + } + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_mid_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s16__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s16__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int16)((drflac_int32)((drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample)) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)((drflac_int32)((drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample)) >> 16); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 tempL0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 tempL1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 tempL2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 tempL3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 tempR0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 tempR1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 tempR2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 tempR3 = pInputSamples1U32[i*4+3] << shift1; + + tempL0 >>= 16; + tempL1 >>= 16; + tempL2 >>= 16; + tempL3 >>= 16; + + tempR0 >>= 16; + tempR1 >>= 16; + tempR2 >>= 16; + tempR3 >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)tempL0; + pOutputSamples[i*8+1] = (drflac_int16)tempR0; + pOutputSamples[i*8+2] = (drflac_int16)tempL1; + pOutputSamples[i*8+3] = (drflac_int16)tempR1; + pOutputSamples[i*8+4] = (drflac_int16)tempL2; + pOutputSamples[i*8+5] = (drflac_int16)tempR2; + pOutputSamples[i*8+6] = (drflac_int16)tempL3; + pOutputSamples[i*8+7] = (drflac_int16)tempR3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int16)((pInputSamples0U32[i] << shift0) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)((pInputSamples1U32[i] << shift1) >> 16); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + /* At this point we have results. We can now pack and interleave these into a single __m128i object and then store the in the output buffer. */ + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int16)((pInputSamples0U32[i] << shift0) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)((pInputSamples1U32[i] << shift1) >> 16); + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + int32x4_t shift0_4 = vdupq_n_s32(shift0); + int32x4_t shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + int32x4_t left; + int32x4_t right; + + left = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4)); + right = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4)); + + left = vshrq_n_s32(left, 16); + right = vshrq_n_s32(right, 16); + + drflac__vst2q_s16(pOutputSamples + i*8, vzip_s16(vmovn_s32(left), vmovn_s32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int16)((pInputSamples0U32[i] << shift0) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)((pInputSamples1U32[i] << shift1) >> 16); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_independent_stereo__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s16__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s16__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s16(drflac* pFlac, drflac_uint64 framesToRead, drflac_int16* pBufferOut) +{ + drflac_uint64 framesRead; + drflac_uint32 unusedBitsPerSample; + + if (pFlac == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); + } + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 32); + unusedBitsPerSample = 32 - pFlac->bitsPerSample; + + framesRead = 0; + while (framesToRead > 0) { + /* If we've run out of samples in this frame, go to the next. */ + if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + break; /* Couldn't read the next frame, so just break from the loop and return. */ + } + } else { + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + drflac_uint64 iFirstPCMFrame = pFlac->currentFLACFrame.header.blockSizeInPCMFrames - pFlac->currentFLACFrame.pcmFramesRemaining; + drflac_uint64 frameCountThisIteration = framesToRead; + + if (frameCountThisIteration > pFlac->currentFLACFrame.pcmFramesRemaining) { + frameCountThisIteration = pFlac->currentFLACFrame.pcmFramesRemaining; + } + + if (channelCount == 2) { + const drflac_int32* pDecodedSamples0 = pFlac->currentFLACFrame.subframes[0].pSamplesS32 + iFirstPCMFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFLACFrame.subframes[1].pSamplesS32 + iFirstPCMFrame; + + switch (pFlac->currentFLACFrame.header.channelAssignment) { case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: { - const int* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; - const int* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; - - for (uint64_t i = 0; i < alignedSampleCountPerChannel; ++i) { - int left = pDecodedSamples0[i]; - int side = pDecodedSamples1[i]; - int right = left - side; - - bufferOut[i*2+0] = left >> rshift0; - bufferOut[i*2+1] = right >> rshift1; - } + drflac_read_pcm_frames_s16__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: { - const int* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; - const int* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; - - for (uint64_t i = 0; i < alignedSampleCountPerChannel; ++i) { - int side = pDecodedSamples0[i]; - int right = pDecodedSamples1[i]; - int left = right + side; - - bufferOut[i*2+0] = left >> rshift0; - bufferOut[i*2+1] = right >> rshift1; - } + drflac_read_pcm_frames_s16__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: { - const int* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; - const int* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; - - for (uint64_t i = 0; i < alignedSampleCountPerChannel; ++i) { - int side = pDecodedSamples1[i]; - int mid = (((uint32_t)pDecodedSamples0[i]) << 1) | (side & 0x01); - - bufferOut[i*2+0] = ((mid + side) >> 1) >> rshift0; - bufferOut[i*2+1] = ((mid - side) >> 1) >> rshift1; - } + drflac_read_pcm_frames_s16__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: default: { - if (pFlac->currentFrame.channelAssignment == 1) // 1 = Stereo - { - // Stereo optimized inner loop unroll. - const int* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; - const int* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; - - for (uint64_t i = 0; i < alignedSampleCountPerChannel; ++i) { - bufferOut[i*2+0] = pDecodedSamples0[i] >> rshift0; - bufferOut[i*2+1] = pDecodedSamples1[i] >> rshift1; - } - } - else - { - // Generic interleaving. - for (uint64_t i = 0; i < alignedSampleCountPerChannel; ++i) { - for (unsigned int j = 0; j < channelCount; ++j) { - bufferOut[(i*channelCount)+j] = (pFlac->currentFrame.subframes[j].pDecodedSamples[firstAlignedSampleInFrame + i]) >> (pFlac->currentFrame.subframes[j].wastedBitsPerSample - unusedBitsPerSample); - } - } - } + drflac_read_pcm_frames_s16__decode_independent_stereo(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; } - } - - uint64_t alignedSamplesRead = alignedSampleCountPerChannel * channelCount; - samplesRead += alignedSamplesRead; - samplesReadFromFrameSoFar += alignedSamplesRead; - bufferOut += alignedSamplesRead; - samplesToRead -= alignedSamplesRead; - pFlac->currentFrame.samplesRemaining -= (unsigned int)alignedSamplesRead; - - - - // At this point we may still have some excess samples left to read. - if (samplesToRead > 0 && pFlac->currentFrame.samplesRemaining > 0) - { - uint64_t excessSamplesRead = 0; - if (samplesToRead < pFlac->currentFrame.samplesRemaining) { - excessSamplesRead = drflac__read_s16__misaligned(pFlac, samplesToRead, bufferOut); - } else { - excessSamplesRead = drflac__read_s16__misaligned(pFlac, pFlac->currentFrame.samplesRemaining, bufferOut); + } else { + /* Generic interleaving. */ + drflac_uint64 i; + for (i = 0; i < frameCountThisIteration; ++i) { + unsigned int j; + for (j = 0; j < channelCount; ++j) { + drflac_int32 sampleS32 = (drflac_int32)((drflac_uint32)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample)); + pBufferOut[(i*channelCount)+j] = (drflac_int16)(sampleS32 >> 16); + } } - - samplesRead += excessSamplesRead; - samplesReadFromFrameSoFar += excessSamplesRead; - bufferOut += excessSamplesRead; - samplesToRead -= excessSamplesRead; } + + framesRead += frameCountThisIteration; + pBufferOut += frameCountThisIteration * channelCount; + framesToRead -= frameCountThisIteration; + pFlac->currentPCMFrame += frameCountThisIteration; + pFlac->currentFLACFrame.pcmFramesRemaining -= (drflac_uint32)frameCountThisIteration; } } - return samplesRead; + return framesRead; } -static bool drflac_seek_to_sample(drflac* pFlac, uint64_t sampleIndex) + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 left = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (float)((drflac_int32)left / 2147483648.0); + pOutputSamples[i*2+1] = (float)((drflac_int32)right / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + float factor = 1 / 2147483648.0; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 left0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 left1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 left2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 left3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 right0 = left0 - side0; + drflac_uint32 right1 = left1 - side1; + drflac_uint32 right2 = left2 - side2; + drflac_uint32 right3 = left3 - side3; + + pOutputSamples[i*8+0] = (drflac_int32)left0 * factor; + pOutputSamples[i*8+1] = (drflac_int32)right0 * factor; + pOutputSamples[i*8+2] = (drflac_int32)left1 * factor; + pOutputSamples[i*8+3] = (drflac_int32)right1 * factor; + pOutputSamples[i*8+4] = (drflac_int32)left2 * factor; + pOutputSamples[i*8+5] = (drflac_int32)right2 * factor; + pOutputSamples[i*8+6] = (drflac_int32)left3 * factor; + pOutputSamples[i*8+7] = (drflac_int32)right3 * factor; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left * factor; + pOutputSamples[i*2+1] = (drflac_int32)right * factor; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + __m128 factor; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor = _mm_set1_ps(1.0f / 8388608.0f); + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i right = _mm_sub_epi32(left, side); + __m128 leftf = _mm_mul_ps(_mm_cvtepi32_ps(left), factor); + __m128 rightf = _mm_mul_ps(_mm_cvtepi32_ps(right), factor); + + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; + pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + float32x4_t factor4; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor4 = vdupq_n_f32(1.0f / 8388608.0f); + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t left; + uint32x4_t side; + uint32x4_t right; + float32x4_t leftf; + float32x4_t rightf; + + left = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + right = vsubq_u32(left, side); + leftf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(left)), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(right)), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; + pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_left_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_f32__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 side = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 right = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (float)((drflac_int32)left / 2147483648.0); + pOutputSamples[i*2+1] = (float)((drflac_int32)right / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + float factor = 1 / 2147483648.0; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 side0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 side1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 side2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 side3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 right0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 right1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 right2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 right3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 left0 = right0 + side0; + drflac_uint32 left1 = right1 + side1; + drflac_uint32 left2 = right2 + side2; + drflac_uint32 left3 = right3 + side3; + + pOutputSamples[i*8+0] = (drflac_int32)left0 * factor; + pOutputSamples[i*8+1] = (drflac_int32)right0 * factor; + pOutputSamples[i*8+2] = (drflac_int32)left1 * factor; + pOutputSamples[i*8+3] = (drflac_int32)right1 * factor; + pOutputSamples[i*8+4] = (drflac_int32)left2 * factor; + pOutputSamples[i*8+5] = (drflac_int32)right2 * factor; + pOutputSamples[i*8+6] = (drflac_int32)left3 * factor; + pOutputSamples[i*8+7] = (drflac_int32)right3 * factor; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left * factor; + pOutputSamples[i*2+1] = (drflac_int32)right * factor; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + __m128 factor; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor = _mm_set1_ps(1.0f / 8388608.0f); + + for (i = 0; i < frameCount4; ++i) { + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i left = _mm_add_epi32(right, side); + __m128 leftf = _mm_mul_ps(_mm_cvtepi32_ps(left), factor); + __m128 rightf = _mm_mul_ps(_mm_cvtepi32_ps(right), factor); + + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; + pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + float32x4_t factor4; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor4 = vdupq_n_f32(1.0f / 8388608.0f); + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t side; + uint32x4_t right; + uint32x4_t left; + float32x4_t leftf; + float32x4_t rightf; + + side = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + left = vaddq_u32(right, side); + leftf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(left)), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(right)), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; + pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_right_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_f32__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + drflac_uint32 mid = (drflac_uint32)pInputSamples0[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (float)((((drflac_int32)(mid + side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); + pOutputSamples[i*2+1] = (float)((((drflac_int32)(mid - side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample; + float factor = 1 / 2147483648.0; + + if (shift > 0) { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (mid0 + side0) << shift; + temp1L = (mid1 + side1) << shift; + temp2L = (mid2 + side2) << shift; + temp3L = (mid3 + side3) << shift; + + temp0R = (mid0 - side0) << shift; + temp1R = (mid1 - side1) << shift; + temp2R = (mid2 - side2) << shift; + temp3R = (mid3 - side3) << shift; + + pOutputSamples[i*8+0] = (drflac_int32)temp0L * factor; + pOutputSamples[i*8+1] = (drflac_int32)temp0R * factor; + pOutputSamples[i*8+2] = (drflac_int32)temp1L * factor; + pOutputSamples[i*8+3] = (drflac_int32)temp1R * factor; + pOutputSamples[i*8+4] = (drflac_int32)temp2L * factor; + pOutputSamples[i*8+5] = (drflac_int32)temp2R * factor; + pOutputSamples[i*8+6] = (drflac_int32)temp3L * factor; + pOutputSamples[i*8+7] = (drflac_int32)temp3R * factor; + } + } else { + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (drflac_uint32)((drflac_int32)(mid0 + side0) >> 1); + temp1L = (drflac_uint32)((drflac_int32)(mid1 + side1) >> 1); + temp2L = (drflac_uint32)((drflac_int32)(mid2 + side2) >> 1); + temp3L = (drflac_uint32)((drflac_int32)(mid3 + side3) >> 1); + + temp0R = (drflac_uint32)((drflac_int32)(mid0 - side0) >> 1); + temp1R = (drflac_uint32)((drflac_int32)(mid1 - side1) >> 1); + temp2R = (drflac_uint32)((drflac_int32)(mid2 - side2) >> 1); + temp3R = (drflac_uint32)((drflac_int32)(mid3 - side3) >> 1); + + pOutputSamples[i*8+0] = (drflac_int32)temp0L * factor; + pOutputSamples[i*8+1] = (drflac_int32)temp0R * factor; + pOutputSamples[i*8+2] = (drflac_int32)temp1L * factor; + pOutputSamples[i*8+3] = (drflac_int32)temp1R * factor; + pOutputSamples[i*8+4] = (drflac_int32)temp2L * factor; + pOutputSamples[i*8+5] = (drflac_int32)temp2R * factor; + pOutputSamples[i*8+6] = (drflac_int32)temp3L * factor; + pOutputSamples[i*8+7] = (drflac_int32)temp3R * factor; + } + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample) * factor; + pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample) * factor; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample - 8; + float factor; + __m128 factor128; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor = 1.0f / 8388608.0f; + factor128 = _mm_set1_ps(factor); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i tempL; + __m128i tempR; + __m128 leftf; + __m128 rightf; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + tempL = _mm_srai_epi32(_mm_add_epi32(mid, side), 1); + tempR = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); + + leftf = _mm_mul_ps(_mm_cvtepi32_ps(tempL), factor128); + rightf = _mm_mul_ps(_mm_cvtepi32_ps(tempR), factor128); + + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = ((drflac_int32)(mid + side) >> 1) * factor; + pOutputSamples[i*2+1] = ((drflac_int32)(mid - side) >> 1) * factor; + } + } else { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i tempL; + __m128i tempR; + __m128 leftf; + __m128 rightf; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + tempL = _mm_slli_epi32(_mm_add_epi32(mid, side), shift); + tempR = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); + + leftf = _mm_mul_ps(_mm_cvtepi32_ps(tempL), factor128); + rightf = _mm_mul_ps(_mm_cvtepi32_ps(tempR), factor128); + + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift) * factor; + pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift) * factor; + } + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample - 8; + float factor; + float32x4_t factor4; + int32x4_t shift4; + int32x4_t wbps0_4; /* Wasted Bits Per Sample */ + int32x4_t wbps1_4; /* Wasted Bits Per Sample */ + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor = 1.0f / 8388608.0f; + factor4 = vdupq_n_f32(factor); + wbps0_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + wbps1_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + int32x4_t lefti; + int32x4_t righti; + float32x4_t leftf; + float32x4_t rightf; + + uint32x4_t mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbps0_4); + uint32x4_t side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbps1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); + + lefti = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1); + righti = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); + + leftf = vmulq_f32(vcvtq_f32_s32(lefti), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(righti), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = ((drflac_int32)(mid + side) >> 1) * factor; + pOutputSamples[i*2+1] = ((drflac_int32)(mid - side) >> 1) * factor; + } + } else { + shift -= 1; + shift4 = vdupq_n_s32(shift); + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t lefti; + int32x4_t righti; + float32x4_t leftf; + float32x4_t rightf; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbps0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbps1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); + + lefti = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4)); + righti = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); + + leftf = vmulq_f32(vcvtq_f32_s32(lefti), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(righti), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift) * factor; + pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift) * factor; + } + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_mid_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_f32__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + pOutputSamples[i*2+0] = (float)((drflac_int32)((drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample)) / 2147483648.0); + pOutputSamples[i*2+1] = (float)((drflac_int32)((drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample)) / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + float factor = 1 / 2147483648.0; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 tempL0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 tempL1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 tempL2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 tempL3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 tempR0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 tempR1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 tempR2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 tempR3 = pInputSamples1U32[i*4+3] << shift1; + + pOutputSamples[i*8+0] = (drflac_int32)tempL0 * factor; + pOutputSamples[i*8+1] = (drflac_int32)tempR0 * factor; + pOutputSamples[i*8+2] = (drflac_int32)tempL1 * factor; + pOutputSamples[i*8+3] = (drflac_int32)tempR1 * factor; + pOutputSamples[i*8+4] = (drflac_int32)tempL2 * factor; + pOutputSamples[i*8+5] = (drflac_int32)tempR2 * factor; + pOutputSamples[i*8+6] = (drflac_int32)tempL3 * factor; + pOutputSamples[i*8+7] = (drflac_int32)tempR3 * factor; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0) * factor; + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1) * factor; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + + float factor = 1.0f / 8388608.0f; + __m128 factor128 = _mm_set1_ps(factor); + + for (i = 0; i < frameCount4; ++i) { + __m128i lefti; + __m128i righti; + __m128 leftf; + __m128 rightf; + + lefti = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + righti = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + + leftf = _mm_mul_ps(_mm_cvtepi32_ps(lefti), factor128); + rightf = _mm_mul_ps(_mm_cvtepi32_ps(righti), factor128); + + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0) * factor; + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1) * factor; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + + float factor = 1.0f / 8388608.0f; + float32x4_t factor4 = vdupq_n_f32(factor); + int32x4_t shift0_4 = vdupq_n_s32(shift0); + int32x4_t shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + int32x4_t lefti; + int32x4_t righti; + float32x4_t leftf; + float32x4_t rightf; + + lefti = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4)); + righti = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4)); + + leftf = vmulq_f32(vcvtq_f32_s32(lefti), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(righti), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0) * factor; + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1) * factor; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_independent_stereo__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_f32__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut) +{ + drflac_uint64 framesRead; + drflac_uint32 unusedBitsPerSample; + + if (pFlac == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); + } + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 32); + unusedBitsPerSample = 32 - pFlac->bitsPerSample; + + framesRead = 0; + while (framesToRead > 0) { + /* If we've run out of samples in this frame, go to the next. */ + if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + break; /* Couldn't read the next frame, so just break from the loop and return. */ + } + } else { + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + drflac_uint64 iFirstPCMFrame = pFlac->currentFLACFrame.header.blockSizeInPCMFrames - pFlac->currentFLACFrame.pcmFramesRemaining; + drflac_uint64 frameCountThisIteration = framesToRead; + + if (frameCountThisIteration > pFlac->currentFLACFrame.pcmFramesRemaining) { + frameCountThisIteration = pFlac->currentFLACFrame.pcmFramesRemaining; + } + + if (channelCount == 2) { + const drflac_int32* pDecodedSamples0 = pFlac->currentFLACFrame.subframes[0].pSamplesS32 + iFirstPCMFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFLACFrame.subframes[1].pSamplesS32 + iFirstPCMFrame; + + switch (pFlac->currentFLACFrame.header.channelAssignment) + { + case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: + { + drflac_read_pcm_frames_f32__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: + { + drflac_read_pcm_frames_f32__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: + { + drflac_read_pcm_frames_f32__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: + default: + { + drflac_read_pcm_frames_f32__decode_independent_stereo(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + } + } else { + /* Generic interleaving. */ + drflac_uint64 i; + for (i = 0; i < frameCountThisIteration; ++i) { + unsigned int j; + for (j = 0; j < channelCount; ++j) { + drflac_int32 sampleS32 = (drflac_int32)((drflac_uint32)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample)); + pBufferOut[(i*channelCount)+j] = (float)(sampleS32 / 2147483648.0); + } + } + } + + framesRead += frameCountThisIteration; + pBufferOut += frameCountThisIteration * channelCount; + framesToRead -= frameCountThisIteration; + pFlac->currentPCMFrame += frameCountThisIteration; + pFlac->currentFLACFrame.pcmFramesRemaining -= (unsigned int)frameCountThisIteration; + } + } + + return framesRead; +} + + +DRFLAC_API drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex) { if (pFlac == NULL) { - return false; + return DRFLAC_FALSE; } - if (sampleIndex == 0) { + /* Don't do anything if we're already on the seek point. */ + if (pFlac->currentPCMFrame == pcmFrameIndex) { + return DRFLAC_TRUE; + } + + /* + If we don't know where the first frame begins then we can't seek. This will happen when the STREAMINFO block was not present + when the decoder was opened. + */ + if (pFlac->firstFLACFramePosInBytes == 0) { + return DRFLAC_FALSE; + } + + if (pcmFrameIndex == 0) { + pFlac->currentPCMFrame = 0; return drflac__seek_to_first_frame(pFlac); + } else { + drflac_bool32 wasSuccessful = DRFLAC_FALSE; + drflac_uint64 originalPCMFrame = pFlac->currentPCMFrame; + + /* Clamp the sample to the end. */ + if (pcmFrameIndex > pFlac->totalPCMFrameCount) { + pcmFrameIndex = pFlac->totalPCMFrameCount; + } + + /* If the target sample and the current sample are in the same frame we just move the position forward. */ + if (pcmFrameIndex > pFlac->currentPCMFrame) { + /* Forward. */ + drflac_uint32 offset = (drflac_uint32)(pcmFrameIndex - pFlac->currentPCMFrame); + if (pFlac->currentFLACFrame.pcmFramesRemaining > offset) { + pFlac->currentFLACFrame.pcmFramesRemaining -= offset; + pFlac->currentPCMFrame = pcmFrameIndex; + return DRFLAC_TRUE; + } + } else { + /* Backward. */ + drflac_uint32 offsetAbs = (drflac_uint32)(pFlac->currentPCMFrame - pcmFrameIndex); + drflac_uint32 currentFLACFramePCMFrameCount = pFlac->currentFLACFrame.header.blockSizeInPCMFrames; + drflac_uint32 currentFLACFramePCMFramesConsumed = currentFLACFramePCMFrameCount - pFlac->currentFLACFrame.pcmFramesRemaining; + if (currentFLACFramePCMFramesConsumed > offsetAbs) { + pFlac->currentFLACFrame.pcmFramesRemaining += offsetAbs; + pFlac->currentPCMFrame = pcmFrameIndex; + return DRFLAC_TRUE; + } + } + + /* + Different techniques depending on encapsulation. Using the native FLAC seektable with Ogg encapsulation is a bit awkward so + we'll instead use Ogg's natural seeking facility. + */ +#ifndef DR_FLAC_NO_OGG + if (pFlac->container == drflac_container_ogg) + { + wasSuccessful = drflac_ogg__seek_to_pcm_frame(pFlac, pcmFrameIndex); + } + else +#endif + { + /* First try seeking via the seek table. If this fails, fall back to a brute force seek which is much slower. */ + if (/*!wasSuccessful && */!pFlac->_noSeekTableSeek) { + wasSuccessful = drflac__seek_to_pcm_frame__seek_table(pFlac, pcmFrameIndex); + } + +#if !defined(DR_FLAC_NO_CRC) + /* Fall back to binary search if seek table seeking fails. This requires the length of the stream to be known. */ + if (!wasSuccessful && !pFlac->_noBinarySearchSeek && pFlac->totalPCMFrameCount > 0) { + wasSuccessful = drflac__seek_to_pcm_frame__binary_search(pFlac, pcmFrameIndex); + } +#endif + + /* Fall back to brute force if all else fails. */ + if (!wasSuccessful && !pFlac->_noBruteForceSeek) { + wasSuccessful = drflac__seek_to_pcm_frame__brute_force(pFlac, pcmFrameIndex); + } + } + + if (wasSuccessful) { + pFlac->currentPCMFrame = pcmFrameIndex; + } else { + /* Seek failed. Try putting the decoder back to it's original state. */ + if (drflac_seek_to_pcm_frame(pFlac, originalPCMFrame) == DRFLAC_FALSE) { + /* Failed to seek back to the original PCM frame. Fall back to 0. */ + drflac_seek_to_pcm_frame(pFlac, 0); + } + } + + return wasSuccessful; } - - // Clamp the sample to the end. - if (sampleIndex >= pFlac->totalSampleCount) { - sampleIndex = pFlac->totalSampleCount - 1; - } - - - // First try seeking via the seek table. If this fails, fall back to a brute force seek which is much slower. - if (!drflac__seek_to_sample__seek_table(pFlac, sampleIndex)) { - return drflac__seek_to_sample__brute_force(pFlac, sampleIndex); - } - - return true; } -#endif //DR_FLAC_IMPLEMENTATION + +/* High Level APIs */ + +/* SIZE_MAX */ +#if defined(SIZE_MAX) + #define DRFLAC_SIZE_MAX SIZE_MAX +#else + #if defined(DRFLAC_64BIT) + #define DRFLAC_SIZE_MAX ((drflac_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRFLAC_SIZE_MAX 0xFFFFFFFF + #endif +#endif +/* End SIZE_MAX */ + + +/* Using a macro as the definition of the drflac__full_decode_and_close_*() API family. Sue me. */ +#define DRFLAC_DEFINE_FULL_READ_AND_CLOSE(extension, type) \ +static type* drflac__full_read_and_close_ ## extension (drflac* pFlac, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut)\ +{ \ + type* pSampleData = NULL; \ + drflac_uint64 totalPCMFrameCount; \ + type buffer[4096]; \ + drflac_uint64 pcmFramesRead; \ + size_t sampleDataBufferSize = sizeof(buffer); \ + \ + DRFLAC_ASSERT(pFlac != NULL); \ + \ + totalPCMFrameCount = 0; \ + \ + pSampleData = (type*)drflac__malloc_from_callbacks(sampleDataBufferSize, &pFlac->allocationCallbacks); \ + if (pSampleData == NULL) { \ + goto on_error; \ + } \ + \ + while ((pcmFramesRead = (drflac_uint64)drflac_read_pcm_frames_##extension(pFlac, sizeof(buffer)/sizeof(buffer[0])/pFlac->channels, buffer)) > 0) { \ + if (((totalPCMFrameCount + pcmFramesRead) * pFlac->channels * sizeof(type)) > sampleDataBufferSize) { \ + type* pNewSampleData; \ + size_t newSampleDataBufferSize; \ + \ + newSampleDataBufferSize = sampleDataBufferSize * 2; \ + pNewSampleData = (type*)drflac__realloc_from_callbacks(pSampleData, newSampleDataBufferSize, sampleDataBufferSize, &pFlac->allocationCallbacks); \ + if (pNewSampleData == NULL) { \ + drflac__free_from_callbacks(pSampleData, &pFlac->allocationCallbacks); \ + goto on_error; \ + } \ + \ + sampleDataBufferSize = newSampleDataBufferSize; \ + pSampleData = pNewSampleData; \ + } \ + \ + DRFLAC_COPY_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), buffer, (size_t)(pcmFramesRead*pFlac->channels*sizeof(type))); \ + totalPCMFrameCount += pcmFramesRead; \ + } \ + \ + /* At this point everything should be decoded, but we just want to fill the unused part buffer with silence - need to \ + protect those ears from random noise! */ \ + DRFLAC_ZERO_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), (size_t)(sampleDataBufferSize - totalPCMFrameCount*pFlac->channels*sizeof(type))); \ + \ + if (sampleRateOut) *sampleRateOut = pFlac->sampleRate; \ + if (channelsOut) *channelsOut = pFlac->channels; \ + if (totalPCMFrameCountOut) *totalPCMFrameCountOut = totalPCMFrameCount; \ + \ + drflac_close(pFlac); \ + return pSampleData; \ + \ +on_error: \ + drflac_close(pFlac); \ + return NULL; \ +} + +DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s32, drflac_int32) +DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s16, drflac_int16) +DRFLAC_DEFINE_FULL_READ_AND_CLOSE(f32, float) + +DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalPCMFrameCountOut) { + *totalPCMFrameCountOut = 0; + } + + pFlac = drflac_open(onRead, onSeek, onTell, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); +} + +DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalPCMFrameCountOut) { + *totalPCMFrameCountOut = 0; + } + + pFlac = drflac_open(onRead, onSeek, onTell, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s16(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); +} + +DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalPCMFrameCountOut) { + *totalPCMFrameCountOut = 0; + } + + pFlac = drflac_open(onRead, onSeek, onTell, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_f32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); +} + +#ifndef DR_FLAC_NO_STDIO +DRFLAC_API drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_file(filename, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s32(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +DRFLAC_API drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_file(filename, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s16(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +DRFLAC_API float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_file(filename, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_f32(pFlac, channels, sampleRate, totalPCMFrameCount); +} +#endif + +DRFLAC_API drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_memory(data, dataSize, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s32(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +DRFLAC_API drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_memory(data, dataSize, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s16(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +DRFLAC_API float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_memory(data, dataSize, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_f32(pFlac, channels, sampleRate, totalPCMFrameCount); +} + + +DRFLAC_API void drflac_free(void* p, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + drflac__free_from_callbacks(p, pAllocationCallbacks); + } else { + drflac__free_default(p, NULL); + } +} + + + + +DRFLAC_API void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments) +{ + if (pIter == NULL) { + return; + } + + pIter->countRemaining = commentCount; + pIter->pRunningData = (const char*)pComments; +} + +DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut) +{ + drflac_int32 length; + const char* pComment; + + /* Safety. */ + if (pCommentLengthOut) { + *pCommentLengthOut = 0; + } + + if (pIter == NULL || pIter->countRemaining == 0 || pIter->pRunningData == NULL) { + return NULL; + } + + length = drflac__le2host_32_ptr_unaligned(pIter->pRunningData); + pIter->pRunningData += 4; + + pComment = pIter->pRunningData; + pIter->pRunningData += length; + pIter->countRemaining -= 1; + + if (pCommentLengthOut) { + *pCommentLengthOut = length; + } + + return pComment; +} + + + + +DRFLAC_API void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData) +{ + if (pIter == NULL) { + return; + } + + pIter->countRemaining = trackCount; + pIter->pRunningData = (const char*)pTrackData; +} + +DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack) +{ + drflac_cuesheet_track cuesheetTrack; + const char* pRunningData; + drflac_uint64 offsetHi; + drflac_uint64 offsetLo; + + if (pIter == NULL || pIter->countRemaining == 0 || pIter->pRunningData == NULL) { + return DRFLAC_FALSE; + } + + pRunningData = pIter->pRunningData; + + offsetHi = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + offsetLo = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + cuesheetTrack.offset = offsetLo | (offsetHi << 32); + cuesheetTrack.trackNumber = pRunningData[0]; pRunningData += 1; + DRFLAC_COPY_MEMORY(cuesheetTrack.ISRC, pRunningData, sizeof(cuesheetTrack.ISRC)); pRunningData += 12; + cuesheetTrack.isAudio = (pRunningData[0] & 0x80) != 0; + cuesheetTrack.preEmphasis = (pRunningData[0] & 0x40) != 0; pRunningData += 14; + cuesheetTrack.indexCount = pRunningData[0]; pRunningData += 1; + cuesheetTrack.pIndexPoints = (const drflac_cuesheet_track_index*)pRunningData; pRunningData += cuesheetTrack.indexCount * sizeof(drflac_cuesheet_track_index); + + pIter->pRunningData = pRunningData; + pIter->countRemaining -= 1; + + if (pCuesheetTrack) { + *pCuesheetTrack = cuesheetTrack; + } + + return DRFLAC_TRUE; +} + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop +#endif +#endif /* dr_flac_c */ +#endif /* DR_FLAC_IMPLEMENTATION */ /* +REVISION HISTORY +================ +v0.13.2 - 2025-12-02 + - Improve robustness of the parsing of picture metadata to improve support for memory constrained embedded devices. + - Fix a warning about an assigned by unused variable. + - Improvements to drflac_open_and_read_pcm_frames_*() and family to avoid excessively large memory allocations from malformed files. + +v0.13.1 - 2025-09-10 + - Fix an error with the NXDK build. + +v0.13.0 - 2025-07-23 + - API CHANGE: Seek origin enums have been renamed to match the naming convention used by other dr_libs libraries: + - drflac_seek_origin_start -> DRFLAC_SEEK_SET + - drflac_seek_origin_current -> DRFLAC_SEEK_CUR + - DRFLAC_SEEK_END (new) + - API CHANGE: A new seek origin has been added to allow seeking from the end of the file. If you implement your own `onSeek` callback, you should now detect and handle `DRFLAC_SEEK_END`. If seeking to the end is not supported, return `DRFLAC_FALSE`. If you only use `*_open_file()` or `*_open_memory()`, you need not change anything. + - API CHANGE: An `onTell` callback has been added to the following functions: + - drflac_open() + - drflac_open_relaxed() + - drflac_open_with_metadata() + - drflac_open_with_metadata_relaxed() + - drflac_open_and_read_pcm_frames_s32() + - drflac_open_and_read_pcm_frames_s16() + - drflac_open_and_read_pcm_frames_f32() + - Fix compilation for AIX OS. + +v0.12.43 - 2024-12-17 + - Fix a possible buffer overflow during decoding. + - Improve detection of ARM64EC + +v0.12.42 - 2023-11-02 + - Fix build for ARMv6-M. + - Fix a compilation warning with GCC. + +v0.12.41 - 2023-06-17 + - Fix an incorrect date in revision history. No functional change. + +v0.12.40 - 2023-05-22 + - Minor code restructure. No functional change. + +v0.12.39 - 2022-09-17 + - Fix compilation with DJGPP. + - Fix compilation error with Visual Studio 2019 and the ARM build. + - Fix an error with SSE 4.1 detection. + - Add support for disabling wchar_t with DR_WAV_NO_WCHAR. + - Improve compatibility with compilers which lack support for explicit struct packing. + - Improve compatibility with low-end and embedded hardware by reducing the amount of stack + allocation when loading an Ogg encapsulated file. + +v0.12.38 - 2022-04-10 + - Fix compilation error on older versions of GCC. + +v0.12.37 - 2022-02-12 + - Improve ARM detection. + +v0.12.36 - 2022-02-07 + - Fix a compilation error with the ARM build. + +v0.12.35 - 2022-02-06 + - Fix a bug due to underestimating the amount of precision required for the prediction stage. + - Fix some bugs found from fuzz testing. + +v0.12.34 - 2022-01-07 + - Fix some misalignment bugs when reading metadata. + +v0.12.33 - 2021-12-22 + - Fix a bug with seeking when the seek table does not start at PCM frame 0. + +v0.12.32 - 2021-12-11 + - Fix a warning with Clang. + +v0.12.31 - 2021-08-16 + - Silence some warnings. + +v0.12.30 - 2021-07-31 + - Fix platform detection for ARM64. + +v0.12.29 - 2021-04-02 + - Fix a bug where the running PCM frame index is set to an invalid value when over-seeking. + - Fix a decoding error due to an incorrect validation check. + +v0.12.28 - 2021-02-21 + - Fix a warning due to referencing _MSC_VER when it is undefined. + +v0.12.27 - 2021-01-31 + - Fix a static analysis warning. + +v0.12.26 - 2021-01-17 + - Fix a compilation warning due to _BSD_SOURCE being deprecated. + +v0.12.25 - 2020-12-26 + - Update documentation. + +v0.12.24 - 2020-11-29 + - Fix ARM64/NEON detection when compiling with MSVC. + +v0.12.23 - 2020-11-21 + - Fix compilation with OpenWatcom. + +v0.12.22 - 2020-11-01 + - Fix an error with the previous release. + +v0.12.21 - 2020-11-01 + - Fix a possible deadlock when seeking. + - Improve compiler support for older versions of GCC. + +v0.12.20 - 2020-09-08 + - Fix a compilation error on older compilers. + +v0.12.19 - 2020-08-30 + - Fix a bug due to an undefined 32-bit shift. + +v0.12.18 - 2020-08-14 + - Fix a crash when compiling with clang-cl. + +v0.12.17 - 2020-08-02 + - Simplify sized types. + +v0.12.16 - 2020-07-25 + - Fix a compilation warning. + +v0.12.15 - 2020-07-06 + - Check for negative LPC shifts and return an error. + +v0.12.14 - 2020-06-23 + - Add include guard for the implementation section. + +v0.12.13 - 2020-05-16 + - Add compile-time and run-time version querying. + - DRFLAC_VERSION_MINOR + - DRFLAC_VERSION_MAJOR + - DRFLAC_VERSION_REVISION + - DRFLAC_VERSION_STRING + - drflac_version() + - drflac_version_string() + +v0.12.12 - 2020-04-30 + - Fix compilation errors with VC6. + +v0.12.11 - 2020-04-19 + - Fix some pedantic warnings. + - Fix some undefined behaviour warnings. + +v0.12.10 - 2020-04-10 + - Fix some bugs when trying to seek with an invalid seek table. + +v0.12.9 - 2020-04-05 + - Fix warnings. + +v0.12.8 - 2020-04-04 + - Add drflac_open_file_w() and drflac_open_file_with_metadata_w(). + - Fix some static analysis warnings. + - Minor documentation updates. + +v0.12.7 - 2020-03-14 + - Fix compilation errors with VC6. + +v0.12.6 - 2020-03-07 + - Fix compilation error with Visual Studio .NET 2003. + +v0.12.5 - 2020-01-30 + - Silence some static analysis warnings. + +v0.12.4 - 2020-01-29 + - Silence some static analysis warnings. + +v0.12.3 - 2019-12-02 + - Fix some warnings when compiling with GCC and the -Og flag. + - Fix a crash in out-of-memory situations. + - Fix potential integer overflow bug. + - Fix some static analysis warnings. + - Fix a possible crash when using custom memory allocators without a custom realloc() implementation. + - Fix a bug with binary search seeking where the bits per sample is not a multiple of 8. + +v0.12.2 - 2019-10-07 + - Internal code clean up. + +v0.12.1 - 2019-09-29 + - Fix some Clang Static Analyzer warnings. + - Fix an unused variable warning. + +v0.12.0 - 2019-09-23 + - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation + routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs: + - drflac_open() + - drflac_open_relaxed() + - drflac_open_with_metadata() + - drflac_open_with_metadata_relaxed() + - drflac_open_file() + - drflac_open_file_with_metadata() + - drflac_open_memory() + - drflac_open_memory_with_metadata() + - drflac_open_and_read_pcm_frames_s32() + - drflac_open_and_read_pcm_frames_s16() + - drflac_open_and_read_pcm_frames_f32() + - drflac_open_file_and_read_pcm_frames_s32() + - drflac_open_file_and_read_pcm_frames_s16() + - drflac_open_file_and_read_pcm_frames_f32() + - drflac_open_memory_and_read_pcm_frames_s32() + - drflac_open_memory_and_read_pcm_frames_s16() + - drflac_open_memory_and_read_pcm_frames_f32() + Set this extra parameter to NULL to use defaults which is the same as the previous behaviour. Setting this NULL will use + DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE. + - Remove deprecated APIs: + - drflac_read_s32() + - drflac_read_s16() + - drflac_read_f32() + - drflac_seek_to_sample() + - drflac_open_and_decode_s32() + - drflac_open_and_decode_s16() + - drflac_open_and_decode_f32() + - drflac_open_and_decode_file_s32() + - drflac_open_and_decode_file_s16() + - drflac_open_and_decode_file_f32() + - drflac_open_and_decode_memory_s32() + - drflac_open_and_decode_memory_s16() + - drflac_open_and_decode_memory_f32() + - Remove drflac.totalSampleCount which is now replaced with drflac.totalPCMFrameCount. You can emulate drflac.totalSampleCount + by doing pFlac->totalPCMFrameCount*pFlac->channels. + - Rename drflac.currentFrame to drflac.currentFLACFrame to remove ambiguity with PCM frames. + - Fix errors when seeking to the end of a stream. + - Optimizations to seeking. + - SSE improvements and optimizations. + - ARM NEON optimizations. + - Optimizations to drflac_read_pcm_frames_s16(). + - Optimizations to drflac_read_pcm_frames_s32(). + +v0.11.10 - 2019-06-26 + - Fix a compiler error. + +v0.11.9 - 2019-06-16 + - Silence some ThreadSanitizer warnings. + +v0.11.8 - 2019-05-21 + - Fix warnings. + +v0.11.7 - 2019-05-06 + - C89 fixes. + +v0.11.6 - 2019-05-05 + - Add support for C89. + - Fix a compiler warning when CRC is disabled. + - Change license to choice of public domain or MIT-0. + +v0.11.5 - 2019-04-19 + - Fix a compiler error with GCC. + +v0.11.4 - 2019-04-17 + - Fix some warnings with GCC when compiling with -std=c99. + +v0.11.3 - 2019-04-07 + - Silence warnings with GCC. + +v0.11.2 - 2019-03-10 + - Fix a warning. + +v0.11.1 - 2019-02-17 + - Fix a potential bug with seeking. + +v0.11.0 - 2018-12-16 + - API CHANGE: Deprecated drflac_read_s32(), drflac_read_s16() and drflac_read_f32() and replaced them with + drflac_read_pcm_frames_s32(), drflac_read_pcm_frames_s16() and drflac_read_pcm_frames_f32(). The new APIs take + and return PCM frame counts instead of sample counts. To upgrade you will need to change the input count by + dividing it by the channel count, and then do the same with the return value. + - API_CHANGE: Deprecated drflac_seek_to_sample() and replaced with drflac_seek_to_pcm_frame(). Same rules as + the changes to drflac_read_*() apply. + - API CHANGE: Deprecated drflac_open_and_decode_*() and replaced with drflac_open_*_and_read_*(). Same rules as + the changes to drflac_read_*() apply. + - Optimizations. + +v0.10.0 - 2018-09-11 + - Remove the DR_FLAC_NO_WIN32_IO option and the Win32 file IO functionality. If you need to use Win32 file IO you + need to do it yourself via the callback API. + - Fix the clang build. + - Fix undefined behavior. + - Fix errors with CUESHEET metdata blocks. + - Add an API for iterating over each cuesheet track in the CUESHEET metadata block. This works the same way as the + Vorbis comment API. + - Other miscellaneous bug fixes, mostly relating to invalid FLAC streams. + - Minor optimizations. + +v0.9.11 - 2018-08-29 + - Fix a bug with sample reconstruction. + +v0.9.10 - 2018-08-07 + - Improve 64-bit detection. + +v0.9.9 - 2018-08-05 + - Fix C++ build on older versions of GCC. + +v0.9.8 - 2018-07-24 + - Fix compilation errors. + +v0.9.7 - 2018-07-05 + - Fix a warning. + +v0.9.6 - 2018-06-29 + - Fix some typos. + +v0.9.5 - 2018-06-23 + - Fix some warnings. + +v0.9.4 - 2018-06-14 + - Optimizations to seeking. + - Clean up. + +v0.9.3 - 2018-05-22 + - Bug fix. + +v0.9.2 - 2018-05-12 + - Fix a compilation error due to a missing break statement. + +v0.9.1 - 2018-04-29 + - Fix compilation error with Clang. + +v0.9 - 2018-04-24 + - Fix Clang build. + - Start using major.minor.revision versioning. + +v0.8g - 2018-04-19 + - Fix build on non-x86/x64 architectures. + +v0.8f - 2018-02-02 + - Stop pretending to support changing rate/channels mid stream. + +v0.8e - 2018-02-01 + - Fix a crash when the block size of a frame is larger than the maximum block size defined by the FLAC stream. + - Fix a crash the the Rice partition order is invalid. + +v0.8d - 2017-09-22 + - Add support for decoding streams with ID3 tags. ID3 tags are just skipped. + +v0.8c - 2017-09-07 + - Fix warning on non-x86/x64 architectures. + +v0.8b - 2017-08-19 + - Fix build on non-x86/x64 architectures. + +v0.8a - 2017-08-13 + - A small optimization for the Clang build. + +v0.8 - 2017-08-12 + - API CHANGE: Rename dr_* types to drflac_*. + - Optimizations. This brings dr_flac back to about the same class of efficiency as the reference implementation. + - Add support for custom implementations of malloc(), realloc(), etc. + - Add CRC checking to Ogg encapsulated streams. + - Fix VC++ 6 build. This is only for the C++ compiler. The C compiler is not currently supported. + - Bug fixes. + +v0.7 - 2017-07-23 + - Add support for opening a stream without a header block. To do this, use drflac_open_relaxed() / drflac_open_with_metadata_relaxed(). + +v0.6 - 2017-07-22 + - Add support for recovering from invalid frames. With this change, dr_flac will simply skip over invalid frames as if they + never existed. Frames are checked against their sync code, the CRC-8 of the frame header and the CRC-16 of the whole frame. + +v0.5 - 2017-07-16 + - Fix typos. + - Change drflac_bool* types to unsigned. + - Add CRC checking. This makes dr_flac slower, but can be disabled with #define DR_FLAC_NO_CRC. + +v0.4f - 2017-03-10 + - Fix a couple of bugs with the bitstreaming code. + +v0.4e - 2017-02-17 + - Fix some warnings. + +v0.4d - 2016-12-26 + - Add support for 32-bit floating-point PCM decoding. + - Use drflac_int* and drflac_uint* sized types to improve compiler support. + - Minor improvements to documentation. + +v0.4c - 2016-12-26 + - Add support for signed 16-bit integer PCM decoding. + +v0.4b - 2016-10-23 + - A minor change to drflac_bool8 and drflac_bool32 types. + +v0.4a - 2016-10-11 + - Rename drBool32 to drflac_bool32 for styling consistency. + +v0.4 - 2016-09-29 + - API/ABI CHANGE: Use fixed size 32-bit booleans instead of the built-in bool type. + - API CHANGE: Rename drflac_open_and_decode*() to drflac_open_and_decode*_s32(). + - API CHANGE: Swap the order of "channels" and "sampleRate" parameters in drflac_open_and_decode*(). Rationale for this is to + keep it consistent with drflac_audio. + +v0.3f - 2016-09-21 + - Fix a warning with GCC. + +v0.3e - 2016-09-18 + - Fixed a bug where GCC 4.3+ was not getting properly identified. + - Fixed a few typos. + - Changed date formats to ISO 8601 (YYYY-MM-DD). + +v0.3d - 2016-06-11 + - Minor clean up. + +v0.3c - 2016-05-28 + - Fixed compilation error. + +v0.3b - 2016-05-16 + - Fixed Linux/GCC build. + - Updated documentation. + +v0.3a - 2016-05-15 + - Minor fixes to documentation. + +v0.3 - 2016-05-11 + - Optimizations. Now at about parity with the reference implementation on 32-bit builds. + - Lots of clean up. + +v0.2b - 2016-05-10 + - Bug fixes. + +v0.2a - 2016-05-10 + - Made drflac_open_and_decode() more robust. + - Removed an unused debugging variable + +v0.2 - 2016-05-09 + - Added support for Ogg encapsulation. + - API CHANGE. Have the onSeek callback take a third argument which specifies whether or not the seek + should be relative to the start or the current position. Also changes the seeking rules such that + seeking offsets will never be negative. + - Have drflac_open_and_decode() fail gracefully if the stream has an unknown total sample count. + +v0.1b - 2016-05-07 + - Properly close the file handle in drflac_open_file() and family when the decoder fails to initialize. + - Removed a stale comment. + +v0.1a - 2016-05-05 + - Minor formatting changes. + - Fixed a warning on the GCC build. + +v0.1 - 2016-05-03 + - Initial versioned release. +*/ + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - Public Domain (www.unlicense.org) +=============================================================================== This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. -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 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. +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 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. For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2023 David Reid + +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. + +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. */ diff --git a/panda/src/movies/flacAudio.h b/panda/src/movies/flacAudio.h index 5309e95eae..b999ebe004 100644 --- a/panda/src/movies/flacAudio.h +++ b/panda/src/movies/flacAudio.h @@ -20,7 +20,7 @@ class FlacAudioCursor; /** - * Reads FLAC audio files. Ogg-encapsulated FLAC files are not supported. + * Reads FLAC audio files. Ogg-encapsulated FLAC files are now supported. * * @since 1.10.0 */ diff --git a/panda/src/movies/flacAudioCursor.cxx b/panda/src/movies/flacAudioCursor.cxx index 1c9d06984b..1af4121146 100644 --- a/panda/src/movies/flacAudioCursor.cxx +++ b/panda/src/movies/flacAudioCursor.cxx @@ -24,9 +24,9 @@ extern "C" { /** * Callback passed to dr_flac to implement file I/O via the VirtualFileSystem. */ -static size_t cb_read_proc(void *user, void *buffer, size_t size) { - std::istream *stream = (std::istream *)user; - nassertr(stream != nullptr, false); +size_t cb_read_proc(void *user, void *buffer, size_t size) { + std::istream *stream = static_cast(user)->_stream; + nassertr(stream != nullptr, 0); stream->read((char *)buffer, size); @@ -39,14 +39,53 @@ static size_t cb_read_proc(void *user, void *buffer, size_t size) { } /** - * Callback passed to dr_flac to implement file I/O via the VirtualFileSystem. + * Callback passed to dr_flac to implement file seeking via the VirtualFileSystem. */ -static bool cb_seek_proc(void *user, int offset) { - std::istream *stream = (std::istream *)user; - nassertr(stream != nullptr, false); +drflac_bool32 cb_seek_proc(void* user, int offset, drflac_seek_origin origin) { + std::istream* stream = static_cast(user)->_stream; + nassertr(stream != nullptr, DRFLAC_FALSE); - stream->seekg(offset, std::ios::cur); - return !stream->fail(); + std::ios_base::seekdir dir; + switch (origin) { + case DRFLAC_SEEK_SET: + dir = std::ios_base::beg; + break; + case DRFLAC_SEEK_CUR: + dir = std::ios_base::cur; + break; + default: + return DRFLAC_FALSE; + } + + stream->seekg(offset, dir); + return !stream->fail() ? DRFLAC_TRUE : DRFLAC_FALSE; +} + +/** + * Callback passed to dr_flac to report the current stream position. + */ +drflac_bool32 cb_tell_proc(void *user, drflac_int64 *pCursor) { + std::istream *stream = static_cast(user)->_stream; + nassertr(stream != nullptr, DRFLAC_FALSE); + + *pCursor = (drflac_int64)stream->tellg(); + return !stream->fail() ? DRFLAC_TRUE : DRFLAC_FALSE; +} + +/** + * Callback passed to dr_flac to process metadata found inside flac files. + */ +void cb_meta_proc(void* pUserData, drflac_metadata* pMetadata) { + FlacAudioCursor* cursor = static_cast(pUserData); + if (pMetadata->type == DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT) { + drflac_vorbis_comment_iterator iter; + drflac_init_vorbis_comment_iterator(&iter, pMetadata->data.vorbis_comment.commentCount, pMetadata->data.vorbis_comment.pComments); + const char* next_comment; + drflac_uint32 comment_length; + while ((next_comment = drflac_next_vorbis_comment(&iter, &comment_length)) != nullptr) { + cursor->raw_comment.push_back({ next_comment, comment_length }); + } + } } TypeHandle FlacAudioCursor::_type_handle; @@ -60,12 +99,13 @@ FlacAudioCursor(FlacAudio *src, std::istream *stream) : MovieAudioCursor(src), _is_valid(false), _drflac(nullptr), - _stream(stream) + _stream(stream), + raw_comment() { nassertv(stream != nullptr); nassertv(stream->good()); - _drflac = drflac_open(&cb_read_proc, &cb_seek_proc, (void *)stream); + _drflac = drflac_open_with_metadata(&cb_read_proc, &cb_seek_proc, &cb_tell_proc, &cb_meta_proc, (void *)this, nullptr); if (_drflac == nullptr) { movies_cat.error() @@ -73,7 +113,7 @@ FlacAudioCursor(FlacAudio *src, std::istream *stream) : _is_valid = false; } - _length = (_drflac->totalSampleCount / _drflac->channels) / (double)_drflac->sampleRate; + _length = _drflac->totalPCMFrameCount / (double)_drflac->sampleRate; _audio_channels = _drflac->channels; _audio_rate = _drflac->sampleRate; @@ -107,7 +147,7 @@ seek(double t) { uint64_t sample = t * _drflac->sampleRate; - if (drflac_seek_to_sample(_drflac, sample * _drflac->channels)) { + if (drflac_seek_to_pcm_frame(_drflac, sample)) { _last_seek = sample / (double)_drflac->sampleRate; _samples_read = 0; } @@ -120,8 +160,15 @@ seek(double t) { */ int FlacAudioCursor:: read_samples(int n, int16_t *data) { - int desired = n * _audio_channels; - n = drflac_read_s16(_drflac, desired, data) / _audio_channels; + n = (int)drflac_read_pcm_frames_s16(_drflac, n, data); _samples_read += n; return n; } + +/** + * + */ +vector_string FlacAudioCursor:: +get_raw_comment() const { + return raw_comment; +} diff --git a/panda/src/movies/flacAudioCursor.h b/panda/src/movies/flacAudioCursor.h index 93a48e5f32..349b004724 100644 --- a/panda/src/movies/flacAudioCursor.h +++ b/panda/src/movies/flacAudioCursor.h @@ -16,6 +16,7 @@ #include "pandabase.h" #include "movieAudioCursor.h" +#include "vector_string.h" #define DR_FLAC_NO_STDIO extern "C" { @@ -31,6 +32,11 @@ class FlacAudio; * @since 1.10.0 */ class EXPCL_PANDA_MOVIES FlacAudioCursor : public MovieAudioCursor { + friend drflac_bool32 cb_tell_proc(void* pUserData, drflac_int64 *pCursor); + friend drflac_bool32 cb_seek_proc(void* pUserData, int offset, drflac_seek_origin origin); + friend size_t cb_read_proc(void* pUserData, void *buffer, size_t size); + friend void cb_meta_proc(void* pUserData, drflac_metadata* pMetadata); + PUBLISHED: explicit FlacAudioCursor(FlacAudio *src, std::istream *stream); virtual ~FlacAudioCursor(); @@ -58,9 +64,11 @@ public: return get_class_type(); } virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + vector_string get_raw_comment() const; private: static TypeHandle _type_handle; + vector_string raw_comment; }; #include "flacAudioCursor.I" diff --git a/panda/src/movies/movieAudioCursor.cxx b/panda/src/movies/movieAudioCursor.cxx index a836937455..374b7a9f93 100644 --- a/panda/src/movies/movieAudioCursor.cxx +++ b/panda/src/movies/movieAudioCursor.cxx @@ -12,6 +12,7 @@ */ #include "movieAudioCursor.h" +#include "vector_string.h" TypeHandle MovieAudioCursor::_type_handle; @@ -167,3 +168,11 @@ int MovieAudioCursor:: ready() const { return 0x40000000; } + +/** + * + */ +vector_string MovieAudioCursor:: +get_raw_comment() const { + return vector_string(); +} diff --git a/panda/src/movies/movieAudioCursor.h b/panda/src/movies/movieAudioCursor.h index e7c9668713..b810b04a59 100644 --- a/panda/src/movies/movieAudioCursor.h +++ b/panda/src/movies/movieAudioCursor.h @@ -18,6 +18,7 @@ #include "namable.h" #include "texture.h" #include "pointerTo.h" +#include "vector_string.h" class MovieAudio; /** @@ -49,6 +50,7 @@ PUBLISHED: virtual void seek(double offset); void read_samples(int n, Datagram *dg); vector_uchar read_samples(int n); + virtual vector_string get_raw_comment() const; public: virtual int read_samples(int n, int16_t *data); diff --git a/panda/src/movies/opusAudioCursor.cxx b/panda/src/movies/opusAudioCursor.cxx index ab81068742..afdf738f26 100644 --- a/panda/src/movies/opusAudioCursor.cxx +++ b/panda/src/movies/opusAudioCursor.cxx @@ -17,6 +17,7 @@ #include "opusAudio.h" #include "virtualFileSystem.h" +#include "vector_string.h" #ifdef HAVE_OPUS @@ -121,6 +122,20 @@ static const OpusFileCallbacks callbacks = {cb_read, cb_seek, cb_tell, nullptr}; TypeHandle OpusAudioCursor::_type_handle; +/** + * + */ +vector_string +parse_opus_comments(const OpusTags *com) { + // Blatant copy from vorbis code because they're the same struct + vector_string comments; + for (int cnum = 0; cnum < com->comments; ++cnum) { + std::string tag(com->user_comments[cnum], com->comment_lengths[cnum]); + comments.push_back(std::move(tag)); + } + return comments; +} + /** * Reads the .wav header from the indicated stream. This leaves the read * pointer positioned at the start of the data. @@ -156,6 +171,9 @@ OpusAudioCursor(OpusAudio *src, istream *stream) : _can_seek_fast = _can_seek; _is_valid = true; + + const OpusTags *tag = op_tags(_op, -1); + _comment = parse_opus_comments(tag); } /** @@ -270,4 +288,12 @@ read_samples(int n, int16_t *data) { return n; } +/** + * + */ +vector_string OpusAudioCursor:: +get_raw_comment() const { + return _comment; +} + #endif // HAVE_OPUS diff --git a/panda/src/movies/opusAudioCursor.h b/panda/src/movies/opusAudioCursor.h index 3e2137de5e..26dea22a63 100644 --- a/panda/src/movies/opusAudioCursor.h +++ b/panda/src/movies/opusAudioCursor.h @@ -16,6 +16,7 @@ #include "pandabase.h" #include "movieAudioCursor.h" +#include "vector_string.h" #ifdef HAVE_OPUS @@ -37,6 +38,7 @@ PUBLISHED: explicit OpusAudioCursor(OpusAudio *src, std::istream *stream); virtual ~OpusAudioCursor(); virtual void seek(double offset); + vector_string get_raw_comment() const; public: virtual int read_samples(int n, int16_t *data); @@ -47,6 +49,7 @@ protected: OggOpusFile *_op; std::istream *_stream; int _link; + vector_string _comment; public: static TypeHandle get_class_type() { diff --git a/panda/src/movies/vorbisAudioCursor.cxx b/panda/src/movies/vorbisAudioCursor.cxx index 181b9454c4..e7954d2bc5 100644 --- a/panda/src/movies/vorbisAudioCursor.cxx +++ b/panda/src/movies/vorbisAudioCursor.cxx @@ -17,6 +17,7 @@ #include "vorbisAudio.h" #include "virtualFileSystem.h" +#include "vector_string.h" #ifdef HAVE_VORBIS @@ -24,6 +25,19 @@ using std::istream; TypeHandle VorbisAudioCursor::_type_handle; +/** + * + */ +vector_string +parse_vorbis_comments(vorbis_comment *com) { + vector_string comments; + for (int cnum = 0; cnum < com->comments; ++cnum) { + std::string tag(com->user_comments[cnum], com->comment_lengths[cnum]); + comments.push_back(std::move(tag)); + } + return comments; +} + /** * Reads the .wav header from the indicated stream. This leaves the read * pointer positioned at the start of the data. @@ -67,6 +81,9 @@ VorbisAudioCursor(VorbisAudio *src, istream *stream) : _can_seek = vorbis_enable_seek && (ov_seekable(&_ov) != 0); _can_seek_fast = _can_seek; + vorbis_comment *com = ov_comment(&_ov, -1); + _comment = parse_vorbis_comments(com); + _is_valid = true; } @@ -313,4 +330,12 @@ cb_tell_func(void *datasource) { return stream->tellg(); } +/** + * + */ +vector_string VorbisAudioCursor:: +get_raw_comment() const { + return _comment; +} + #endif // HAVE_VORBIS diff --git a/panda/src/movies/vorbisAudioCursor.h b/panda/src/movies/vorbisAudioCursor.h index cd74acc750..2bb48dfa3c 100644 --- a/panda/src/movies/vorbisAudioCursor.h +++ b/panda/src/movies/vorbisAudioCursor.h @@ -16,6 +16,7 @@ #include "pandabase.h" #include "movieAudioCursor.h" +#include "vector_string.h" #ifdef HAVE_VORBIS @@ -33,6 +34,7 @@ PUBLISHED: explicit VorbisAudioCursor(VorbisAudio *src, std::istream *stream); virtual ~VorbisAudioCursor(); virtual void seek(double offset); + vector_string get_raw_comment() const; public: virtual int read_samples(int n, int16_t *data); @@ -51,6 +53,7 @@ protected: OggVorbis_File _ov; #endif int _bitstream; + vector_string _comment; public: static TypeHandle get_class_type() { diff --git a/panda/src/nativenet/socket_address.cxx b/panda/src/nativenet/socket_address.cxx index e92310ce7f..96a9ece619 100644 --- a/panda/src/nativenet/socket_address.cxx +++ b/panda/src/nativenet/socket_address.cxx @@ -125,13 +125,15 @@ get_ip_port() const { if (_storage.ss_family == AF_INET) { getnameinfo(&_addr, sizeof(sockaddr_in), buf, sizeof(buf), nullptr, 0, NI_NUMERICHOST); - sprintf(buf + strlen(buf), ":%hu", get_port()); + size_t len = strlen(buf); + snprintf(buf + len, sizeof(buf) - len, ":%hu", get_port()); } else if (_storage.ss_family == AF_INET6) { // Protect the IPv6 address within square brackets. buf[0] = '['; getnameinfo(&_addr, sizeof(sockaddr_in6), buf + 1, sizeof(buf) - 1, nullptr, 0, NI_NUMERICHOST); - sprintf(buf + strlen(buf), "]:%hu", get_port()); + size_t len = strlen(buf); + snprintf(buf + len, sizeof(buf) - len, "]:%hu", get_port()); } else { nassert_raise("unsupported address family"); diff --git a/panda/src/ode/odeRayGeom.I b/panda/src/ode/odeRayGeom.I index 94fdf2af15..235213be1c 100644 --- a/panda/src/ode/odeRayGeom.I +++ b/panda/src/ode/odeRayGeom.I @@ -63,6 +63,11 @@ get_params(int &first_contact, int &backface_cull) const { dGeomRayGetParams(_id, &first_contact, &backface_cull); } +INLINE void OdeRayGeom:: +set_first_contact(int first_contact) { + dGeomRaySetFirstContact(_id, first_contact); +} + INLINE int OdeRayGeom:: get_first_contact() const { int fc, bc; @@ -70,6 +75,11 @@ get_first_contact() const { return fc; } +INLINE void OdeRayGeom:: +set_backface_cull(int backface_cull) { + dGeomRaySetBackfaceCull(_id, backface_cull); +} + INLINE int OdeRayGeom:: get_backface_cull() const { int fc, bc; diff --git a/panda/src/ode/odeRayGeom.h b/panda/src/ode/odeRayGeom.h index 712f0e8440..9a9cfff166 100644 --- a/panda/src/ode/odeRayGeom.h +++ b/panda/src/ode/odeRayGeom.h @@ -41,9 +41,11 @@ PUBLISHED: INLINE void get(LVecBase3f &start, LVecBase3f &dir) const; INLINE LVecBase3f get_start() const; INLINE LVecBase3f get_direction() const; - INLINE void set_params(int first_contact, int backface_cull); - INLINE void get_params(int &first_contact, int &backface_cull) const; + [[deprecated]] INLINE void set_params(int first_contact, int backface_cull); + [[deprecated]] INLINE void get_params(int &first_contact, int &backface_cull) const; + INLINE void set_first_contact(int first_contact); INLINE int get_first_contact() const; + INLINE void set_backface_cull(int backface_cull); INLINE int get_backface_cull() const; INLINE void set_closest_hit(int closest_hit); INLINE int get_closest_hit(); diff --git a/panda/src/pgraph/cullResult.I b/panda/src/pgraph/cullResult.I index a0ea58eb3f..76e11fbd49 100644 --- a/panda/src/pgraph/cullResult.I +++ b/panda/src/pgraph/cullResult.I @@ -43,7 +43,11 @@ alloc_object(CullableObject &&object) { if (page->_size >= page->_capacity) { page = new_page(); } - return new (page->_memory + sizeof(CullableObject) * (page->_size++)) CullableObject(std::move(object)); + void *ptr = page->_memory + sizeof(CullableObject) * (page->_size++); +#ifdef DO_MEMORY_USAGE + //memory_hook->mark_pointer(ptr, sizeof(CullableObject), nullptr); +#endif + return new (ptr) CullableObject(std::move(object)); } /** diff --git a/panda/src/pgraph/cullResult.cxx b/panda/src/pgraph/cullResult.cxx index d4b1550935..eb8d3519d2 100644 --- a/panda/src/pgraph/cullResult.cxx +++ b/panda/src/pgraph/cullResult.cxx @@ -65,7 +65,10 @@ CullResult(GraphicsStateGuardianBase *gsg, #ifdef DO_MEMORY_USAGE MemoryUsage::update_type(this, get_class_type()); -#endif + + CullableObject::get_class_type().inc_memory_usage( + TypeHandle::MC_array, sizeof(CullableObject) * AllocationPage::_capacity); +#endif // DO_MEMORY_USAGE #ifndef NDEBUG _show_transparency = show_transparency.get_value(); @@ -372,6 +375,10 @@ make_new_bin(int bin_index) { CullResult::AllocationPage *CullResult:: new_page() { AllocationPage *page = new AllocationPage; +#ifdef DO_MEMORY_USAGE + CullableObject::get_class_type().inc_memory_usage( + TypeHandle::MC_array, sizeof(CullableObject) * AllocationPage::_capacity); +#endif // DO_MEMORY_USAGE page->_next = _page; _page = page; return page; @@ -385,7 +392,16 @@ delete_page(AllocationPage *page) { size_t size = std::exchange(page->_size, 0); for (size_t i = 0; i < size; ++i) { ((CullableObject *)page->_memory)[i].~CullableObject(); +#ifdef DO_MEMORY_USAGE + //MemoryUsage::remove_void_pointer(&((CullableObject *)page->_memory)[i]); +#endif } + +#ifdef DO_MEMORY_USAGE + CullableObject::get_class_type().dec_memory_usage( + TypeHandle::MC_array, sizeof(CullableObject) * AllocationPage::_capacity); +#endif // DO_MEMORY_USAGE + AllocationPage *next = page->_next; if (next != nullptr) { delete_page(next); diff --git a/panda/src/pgraph/cullableObject.I b/panda/src/pgraph/cullableObject.I index 3910903948..6c1906bc79 100644 --- a/panda/src/pgraph/cullableObject.I +++ b/panda/src/pgraph/cullableObject.I @@ -11,16 +11,6 @@ * @date 2002-03-04 */ -/** - * Creates an empty CullableObject whose pointers can be filled in later. - */ -INLINE CullableObject:: -CullableObject() { -#ifdef DO_MEMORY_USAGE - MemoryUsage::record_pointer(this, get_class_type()); -#endif -} - /** * Creates a CullableObject based the indicated geom, with the indicated * render state and transform. @@ -32,9 +22,6 @@ CullableObject(CPT(Geom) geom, CPT(RenderState) state, _state(std::move(state)), _internal_transform(std::move(internal_transform)) { -#ifdef DO_MEMORY_USAGE - MemoryUsage::record_pointer(this, get_class_type()); -#endif } /** @@ -47,9 +34,6 @@ CullableObject(const CullableObject ©) : _state(copy._state), _internal_transform(copy._internal_transform) { -#ifdef DO_MEMORY_USAGE - MemoryUsage::record_pointer(this, get_class_type()); -#endif } /** diff --git a/panda/src/pgraph/cullableObject.h b/panda/src/pgraph/cullableObject.h index 46fdc228c6..26f1f7674f 100644 --- a/panda/src/pgraph/cullableObject.h +++ b/panda/src/pgraph/cullableObject.h @@ -40,7 +40,7 @@ class GeomMunger; */ class EXPCL_PANDA_PGRAPH CullableObject { public: - INLINE CullableObject(); + INLINE CullableObject() = default; INLINE CullableObject(CPT(Geom) geom, CPT(RenderState) state, CPT(TransformState) internal_transform); diff --git a/panda/src/pgraph/loader.cxx b/panda/src/pgraph/loader.cxx index 15b130592d..3d9067e053 100644 --- a/panda/src/pgraph/loader.cxx +++ b/panda/src/pgraph/loader.cxx @@ -305,21 +305,44 @@ try_load_file(const Filename &pathname, const LoaderOptions &options, (options.get_flags() & LoaderOptions::LF_no_disk_cache) == 0) { // See if the model can be found in the on-disk cache, if it is active. record = cache->lookup(pathname, "bam"); - if (record != nullptr) { - if (record->has_data()) { - if (report_errors) { - loader_cat.info() - << "Model " << pathname << " found in disk cache.\n"; + if (record != nullptr && record->has_data()) { + PT(PandaNode) result = DCAST(PandaNode, record->get_data()); + + ModelRoot *model_root = nullptr; + if (result != nullptr && result->is_of_type(ModelRoot::get_class_type())) { + model_root = DCAST(ModelRoot, result.p()); + + if (requested_type != nullptr && + model_root->get_loader_type() != requested_type->get_type()) { + result.clear(); + if (report_errors) { + loader_cat.info() + << "Model " << pathname << " found in disk cache, but using " + << "unexpected loader " << model_root->get_loader_type() + << " (expected " << requested_type->get_type() << ").\n"; + } + } + } + + if (result != nullptr) { + if (report_errors) { + TypeHandle loaded_type = model_root->get_loader_type(); + if (loaded_type != TypeHandle::none()) { + loader_cat.info() + << "Model " << pathname << " found in disk cache (loaded using " + << loaded_type << ").\n"; + } else { + loader_cat.info() + << "Model " << pathname << " found in disk cache.\n"; + } } - PT(PandaNode) result = DCAST(PandaNode, record->get_data()); if (premunge_data) { SceneGraphReducer sgr; sgr.premunge(result, RenderState::make_empty()); } - if (result->is_of_type(ModelRoot::get_class_type())) { - ModelRoot *model_root = DCAST(ModelRoot, result.p()); + if (model_root != nullptr) { model_root->set_fullpath(pathname); model_root->set_timestamp(record->get_source_timestamp()); @@ -336,8 +359,7 @@ try_load_file(const Filename &pathname, const LoaderOptions &options, return result; } } - - if (loader_cat.is_debug()) { + else if (loader_cat.is_debug()) { loader_cat.debug() << "Model " << pathname << " not found in cache.\n"; } @@ -354,6 +376,10 @@ try_load_file(const Filename &pathname, const LoaderOptions &options, result = requested_type->load_file(pathname, options, record); } if (result != nullptr) { + if (result->is_of_type(ModelRoot::get_class_type())) { + ((ModelRoot *)result.p())->set_loader_type(requested_type->get_type()); + } + if (record != nullptr) { // Store the loaded model in the model cache. record->set_data(result); @@ -392,13 +418,17 @@ try_load_file(const Filename &pathname, const LoaderOptions &options, sgr.premunge(result, RenderState::make_empty()); } - if (allow_ram_cache && result->is_of_type(ModelRoot::get_class_type())) { - // Store the loaded model in the RAM cache, and make sure we return a - // copy so that this node can be modified independently from the RAM - // cached version. - ModelPool::add_model(pathname, DCAST(ModelRoot, result.p())); - if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) { - result = NodePath(result).copy_to(NodePath()).node(); + if (result->is_of_type(ModelRoot::get_class_type())) { + ((ModelRoot *)result.p())->set_fullpath(pathname); + + if (allow_ram_cache) { + // Store the loaded model in the RAM cache, and make sure we return a + // copy so that this node can be modified independently from the RAM + // cached version. + ModelPool::add_model(pathname, DCAST(ModelRoot, result.p())); + if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) { + result = NodePath(result).copy_to(NodePath()).node(); + } } } diff --git a/panda/src/pgraph/modelPool.I b/panda/src/pgraph/modelPool.I index efa8fa99f8..ff796e5953 100644 --- a/panda/src/pgraph/modelPool.I +++ b/panda/src/pgraph/modelPool.I @@ -42,7 +42,7 @@ verify_model(const Filename &filename) { * date (and hasn't been modified in the meantime), and if not, will still * return NULL. */ -INLINE ModelRoot *ModelPool:: +INLINE PT(ModelRoot) ModelPool:: get_model(const Filename &filename, bool verify) { return get_ptr()->ns_get_model(filename, verify); } @@ -54,7 +54,7 @@ get_model(const Filename &filename, bool verify) { * is true and the file has recently changed). If the model file cannot be * found, or cannot be loaded for some reason, returns NULL. */ -INLINE ModelRoot *ModelPool:: +INLINE PT(ModelRoot) ModelPool:: load_model(const Filename &filename, const LoaderOptions &options) { return get_ptr()->ns_load_model(filename, options); } diff --git a/panda/src/pgraph/modelPool.cxx b/panda/src/pgraph/modelPool.cxx index c53e2b867f..76452b4005 100644 --- a/panda/src/pgraph/modelPool.cxx +++ b/panda/src/pgraph/modelPool.cxx @@ -48,7 +48,7 @@ ns_has_model(const Filename &filename) { /** * The nonstatic implementation of get_model(). */ -ModelRoot *ModelPool:: +PT(ModelRoot) ModelPool:: ns_get_model(const Filename &filename, bool verify) { PT(ModelRoot) cached_model; @@ -116,54 +116,31 @@ ns_get_model(const Filename &filename, bool verify) { /** * The nonstatic implementation of load_model(). */ -ModelRoot *ModelPool:: +PT(ModelRoot) ModelPool:: ns_load_model(const Filename &filename, const LoaderOptions &options) { - - // First check if it has already been loaded and is still current. + // First check if it's been cached under the given filename (for backward + // compatibility reasons) PT(ModelRoot) cached_model = ns_get_model(filename, true); if (cached_model != nullptr) { return cached_model; } - // Look on disk for the current file. LoaderOptions new_options(options); - new_options.set_flags((new_options.get_flags() | LoaderOptions::LF_no_ram_cache) & - ~LoaderOptions::LF_search); + new_options.set_flags(new_options.get_flags() & ~LoaderOptions::LF_no_ram_cache); Loader *model_loader = Loader::get_global_ptr(); PT(PandaNode) panda_node = model_loader->load_sync(filename, new_options); PT(ModelRoot) node; - if (panda_node.is_null()) { - // This model was not found. - - } else { + if (!panda_node.is_null()) { if (panda_node->is_of_type(ModelRoot::get_class_type())) { node = DCAST(ModelRoot, panda_node); - } else { // We have to construct a ModelRoot node to put it under. node = new ModelRoot(filename); node->add_child(panda_node); } - node->set_fullpath(filename); } - - { - LightMutexHolder holder(_lock); - - // Look again, in case someone has just loaded the model in another - // thread. - Models::const_iterator ti; - ti = _models.find(filename); - if (ti != _models.end() && (*ti).second != cached_model) { - // This model was previously loaded. - return (*ti).second; - } - - _models[filename] = node; - } - return node; } diff --git a/panda/src/pgraph/modelPool.h b/panda/src/pgraph/modelPool.h index 8e0476c682..da9046c927 100644 --- a/panda/src/pgraph/modelPool.h +++ b/panda/src/pgraph/modelPool.h @@ -43,9 +43,9 @@ class EXPCL_PANDA_PGRAPH ModelPool { PUBLISHED: INLINE static bool has_model(const Filename &filename); INLINE static bool verify_model(const Filename &filename); - INLINE static ModelRoot *get_model(const Filename &filename, bool verify); - BLOCKING INLINE static ModelRoot *load_model(const Filename &filename, - const LoaderOptions &options = LoaderOptions()); + INLINE static PT(ModelRoot) get_model(const Filename &filename, bool verify); + BLOCKING INLINE static PT(ModelRoot) load_model(const Filename &filename, + const LoaderOptions &options = LoaderOptions()); INLINE static void add_model(const Filename &filename, ModelRoot *model); INLINE static void release_model(const Filename &filename); @@ -65,9 +65,9 @@ private: INLINE ModelPool(); bool ns_has_model(const Filename &filename); - ModelRoot *ns_get_model(const Filename &filename, bool verify); - ModelRoot *ns_load_model(const Filename &filename, - const LoaderOptions &options); + PT(ModelRoot) ns_get_model(const Filename &filename, bool verify); + PT(ModelRoot) ns_load_model(const Filename &filename, + const LoaderOptions &options); void ns_add_model(const Filename &filename, ModelRoot *model); void ns_release_model(const Filename &filename); diff --git a/panda/src/pgraph/modelRoot.I b/panda/src/pgraph/modelRoot.I index 4478702a28..7a8ea482ff 100644 --- a/panda/src/pgraph/modelRoot.I +++ b/panda/src/pgraph/modelRoot.I @@ -19,7 +19,8 @@ ModelRoot(const std::string &name) : ModelNode(name), _fullpath(name), _timestamp(0), - _reference(new ModelRoot::ModelReference) + _reference(new ModelRoot::ModelReference), + _loader_type(TypeHandle::none()) { } @@ -31,7 +32,8 @@ ModelRoot(const Filename &fullpath, time_t timestamp) : ModelNode(fullpath.get_basename()), _fullpath(fullpath), _timestamp(timestamp), - _reference(new ModelRoot::ModelReference) + _reference(new ModelRoot::ModelReference), + _loader_type(TypeHandle::none()) { } @@ -114,6 +116,23 @@ set_reference(ModelRoot::ModelReference *ref) { _reference = ref; } +/** + * Returns the type of the loader object that was used to load this model. + */ +INLINE TypeHandle ModelRoot:: +get_loader_type() const { + return _loader_type; +} + +/** + * Sets the type of the loader object used to load this model. Normally this + * is only called by Loader. + */ +INLINE void ModelRoot:: +set_loader_type(TypeHandle type) { + _loader_type = type; +} + /** * */ @@ -122,7 +141,8 @@ ModelRoot(const ModelRoot ©) : ModelNode(copy), _fullpath(copy._fullpath), _timestamp(copy._timestamp), - _reference(copy._reference) + _reference(copy._reference), + _loader_type(copy._loader_type) { } diff --git a/panda/src/pgraph/modelRoot.cxx b/panda/src/pgraph/modelRoot.cxx index 30dbf1e6a6..e39d101c2e 100644 --- a/panda/src/pgraph/modelRoot.cxx +++ b/panda/src/pgraph/modelRoot.cxx @@ -41,6 +41,10 @@ register_with_read_factory() { void ModelRoot:: write_datagram(BamWriter *manager, Datagram &dg) { ModelNode::write_datagram(manager, dg); + + if (manager->get_file_minor_ver() >= 46) { + manager->write_handle(dg, _loader_type); + } } /** @@ -67,4 +71,10 @@ make_from_bam(const FactoryParams ¶ms) { void ModelRoot:: fillin(DatagramIterator &scan, BamReader *manager) { ModelNode::fillin(scan, manager); + + if (manager->get_file_minor_ver() >= 46) { + _loader_type = manager->read_handle(scan); + } else { + _loader_type = TypeHandle::none(); + } } diff --git a/panda/src/pgraph/modelRoot.h b/panda/src/pgraph/modelRoot.h index c73363c108..d249340b11 100644 --- a/panda/src/pgraph/modelRoot.h +++ b/panda/src/pgraph/modelRoot.h @@ -50,6 +50,13 @@ PUBLISHED: void set_reference(ModelReference *ref); MAKE_PROPERTY(reference, get_reference, set_reference); +public: + INLINE TypeHandle get_loader_type() const; + INLINE void set_loader_type(TypeHandle loader_type); + +PUBLISHED: + MAKE_PROPERTY(loader_type, get_loader_type); + protected: INLINE ModelRoot(const ModelRoot ©); @@ -60,6 +67,7 @@ private: Filename _fullpath; time_t _timestamp; PT(ModelReference) _reference; + TypeHandle _loader_type; public: static void register_with_read_factory(); diff --git a/panda/src/pgraph/nodePath.h b/panda/src/pgraph/nodePath.h index b8f82f779f..21328a5888 100644 --- a/panda/src/pgraph/nodePath.h +++ b/panda/src/pgraph/nodePath.h @@ -884,6 +884,8 @@ PUBLISHED: INLINE void set_collide_mask(CollideMask new_mask, CollideMask bits_to_change = CollideMask::all_on(), TypeHandle node_type = TypeHandle::none()); + EXTENSION(void set_collide_owner(PyObject *owner)); + // Comparison methods INLINE bool operator == (const NodePath &other) const; INLINE bool operator != (const NodePath &other) const; diff --git a/panda/src/pgraph/nodePath_ext.cxx b/panda/src/pgraph/nodePath_ext.cxx index e05877be53..af3be290b7 100644 --- a/panda/src/pgraph/nodePath_ext.cxx +++ b/panda/src/pgraph/nodePath_ext.cxx @@ -15,6 +15,7 @@ #include "typedWritable_ext.h" #include "shaderInput_ext.h" #include "shaderAttrib.h" +#include "collisionNode.h" #ifdef HAVE_PYTHON @@ -55,10 +56,9 @@ PyObject *Extension:: __deepcopy__(PyObject *self, PyObject *memo) const { extern struct Dtool_PyTypedObject Dtool_NodePath; - // Borrowed reference. PyObject *dupe; - if (PyDict_GetItemRef(memo, self, &dupe) > 0) { - // Already in the memo dictionary. + if (PyDict_GetItemRef(memo, self, &dupe) != 0) { + // Already in the memo dictionary (or an error happened). return dupe; } @@ -327,4 +327,62 @@ get_tight_bounds(const NodePath &other) const { } } +/** + * Recursively assigns a weak reference to the given owner object to all + * collision nodes at this level and below. + * + * You may pass in None to clear all owners below this level. + * + * Note that there is no corresponding get_collide_owner(), since there may be + * multiple nodes below this level with different owners. + */ +void Extension:: +set_collide_owner(PyObject *owner) { + if (owner != Py_None) { + PyObject *ref = PyWeakref_NewRef(owner, nullptr); + if (ref != nullptr) { + r_set_collide_owner(_this->node(), ref); + Py_DECREF(ref); + } + } else { + r_clear_collide_owner(_this->node()); + } +} + +/** + * Recursive implementation of set_collide_owner. weakref must be a weak ref + * object. + */ +void Extension:: +r_set_collide_owner(PandaNode *node, PyObject *weakref) { + if (node->is_collision_node()) { + CollisionNode *cnode = (CollisionNode *)node; + cnode->set_owner(Py_NewRef(weakref), + [](void *obj) { Py_DECREF((PyObject *)obj); }); + } + + PandaNode::Children cr = node->get_children(); + int num_children = cr.get_num_children(); + for (int i = 0; i < num_children; i++) { + r_set_collide_owner(cr.get_child(i), weakref); + } +} + +/** + * Recursive implementation of set_collide_owner(None). + */ +void Extension:: +r_clear_collide_owner(PandaNode *node) { + if (node->is_collision_node()) { + CollisionNode *cnode = (CollisionNode *)node; + cnode->clear_owner(); + } + + PandaNode::Children cr = node->get_children(); + int num_children = cr.get_num_children(); + for (int i = 0; i < num_children; i++) { + r_clear_collide_owner(cr.get_child(i)); + } +} + #endif // HAVE_PYTHON diff --git a/panda/src/pgraph/nodePath_ext.h b/panda/src/pgraph/nodePath_ext.h index b1127f930d..b6f03b792c 100644 --- a/panda/src/pgraph/nodePath_ext.h +++ b/panda/src/pgraph/nodePath_ext.h @@ -54,6 +54,12 @@ public: void set_shader_inputs(PyObject *args, PyObject *kwargs); PyObject *get_tight_bounds(const NodePath &other = NodePath()) const; + + void set_collide_owner(PyObject *owner); + +private: + static void r_set_collide_owner(PandaNode *node, PyObject *weakref); + static void r_clear_collide_owner(PandaNode *node); }; BEGIN_PUBLISH diff --git a/panda/src/pgraph/pandaNode_ext.cxx b/panda/src/pgraph/pandaNode_ext.cxx index f1d25ed08e..34fc727832 100644 --- a/panda/src/pgraph/pandaNode_ext.cxx +++ b/panda/src/pgraph/pandaNode_ext.cxx @@ -46,10 +46,9 @@ PyObject *Extension:: __deepcopy__(PyObject *self, PyObject *memo) const { extern struct Dtool_PyTypedObject Dtool_PandaNode; - // Borrowed reference. PyObject *dupe; - if (PyDict_GetItemRef(memo, self, &dupe) > 0) { - // Already in the memo dictionary. + if (PyDict_GetItemRef(memo, self, &dupe) != 0) { + // Already in the memo dictionary (or an error happened). return dupe; } diff --git a/panda/src/pgraph/pythonLoaderFileType.cxx b/panda/src/pgraph/pythonLoaderFileType.cxx index aa4b84cceb..b33930cffe 100644 --- a/panda/src/pgraph/pythonLoaderFileType.cxx +++ b/panda/src/pgraph/pythonLoaderFileType.cxx @@ -305,19 +305,19 @@ load_file(const Filename &path, const LoaderOptions &options, #endif // Wrap the arguments. - PyObject *args = PyTuple_New(3); - PyTuple_SET_ITEM(args, 0, DTool_CreatePyInstance((void *)&path, Dtool_Filename, false, true)); - PyTuple_SET_ITEM(args, 1, DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true)); + PyObject *args[4]; + args[1] = DTool_CreatePyInstance((void *)&path, Dtool_Filename, false, true); + args[2] = DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true); if (record != nullptr) { record->ref(); - PyTuple_SET_ITEM(args, 2, DTool_CreatePyInstanceTyped((void *)record, Dtool_BamCacheRecord, true, false, record->get_type_index())); + args[3] = DTool_CreatePyInstanceTyped((void *)record, Dtool_BamCacheRecord, true, false, record->get_type_index()); } else { - PyTuple_SET_ITEM(args, 2, Py_NewRef(Py_None)); + args[3] = Py_NewRef(Py_None); } PT(PandaNode) node; - PyObject *result = PythonThread::call_python_func(_load_func, args); + PyObject *result = PythonThread::call_python_func(_load_func, args + 1, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET); if (result != nullptr) { if (DtoolInstance_Check(result)) { node = (PandaNode *)DtoolInstance_UPCAST(result, Dtool_PandaNode); @@ -325,7 +325,9 @@ load_file(const Filename &path, const LoaderOptions &options, Py_DECREF(result); } - Py_DECREF(args); + Py_DECREF(args[1]); + Py_DECREF(args[2]); + Py_DECREF(args[3]); if (node == nullptr) { PyObject *exc_type = PyErr_Occurred(); @@ -372,13 +374,16 @@ save_file(const Filename &path, const LoaderOptions &options, #endif // Wrap the arguments. - PyObject *args = PyTuple_New(3); - PyTuple_SET_ITEM(args, 0, DTool_CreatePyInstance((void *)&path, Dtool_Filename, false, true)); - PyTuple_SET_ITEM(args, 1, DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true)); - PyTuple_SET_ITEM(args, 2, DTool_CreatePyInstanceTyped((void *)node, Dtool_PandaNode, true, false, node->get_type_index())); + PyObject *args[4]; + args[1] = DTool_CreatePyInstance((void *)&path, Dtool_Filename, false, true); + args[2] = DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true); + args[3] = DTool_CreatePyInstanceTyped((void *)node, Dtool_PandaNode, true, false, node->get_type_index()); + + PyObject *result = PythonThread::call_python_func(_save_func, args + 1, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET); + Py_DECREF(args[1]); + Py_DECREF(args[2]); + Py_DECREF(args[3]); - PyObject *result = PythonThread::call_python_func(_load_func, args); - Py_DECREF(args); if (result != nullptr) { Py_DECREF(result); } else { diff --git a/panda/src/pgraph/renderAttrib.cxx b/panda/src/pgraph/renderAttrib.cxx index 0992dcb79c..4f48d5f9fd 100644 --- a/panda/src/pgraph/renderAttrib.cxx +++ b/panda/src/pgraph/renderAttrib.cxx @@ -478,7 +478,8 @@ init_attribs() { // _attribs_lock without a startup race condition. For the meantime, this // is OK because we guarantee that this method is called at static init // time, presumably when there is still only one thread in the world. - _attribs_lock = new LightReMutex("RenderAttrib::_attribs_lock"); + alignas(LightReMutex) static char storage[sizeof(LightReMutex)]; + _attribs_lock = new (storage) LightReMutex("RenderAttrib::_attribs_lock"); nassertv(Thread::get_current_thread() == Thread::get_main_thread()); } diff --git a/panda/src/pgraph/renderState.cxx b/panda/src/pgraph/renderState.cxx index 235075cb1d..423ca6434f 100644 --- a/panda/src/pgraph/renderState.cxx +++ b/panda/src/pgraph/renderState.cxx @@ -1841,13 +1841,15 @@ init_states() { // _states_lock without a startup race condition. For the meantime, this is // OK because we guarantee that this method is called at static init time, // presumably when there is still only one thread in the world. - _states_lock = new LightReMutex("RenderState::_states_lock"); + alignas(LightReMutex) static char storage[sizeof(LightReMutex)]; + _states_lock = new (storage) LightReMutex("RenderState::_states_lock"); _cache_stats.init(); nassertv(Thread::get_current_thread() == Thread::get_main_thread()); // Initialize the empty state object as well. It is used so often that it // is declared globally, and lives forever. - RenderState *state = new RenderState; + alignas(RenderState) static char state_storage[sizeof(RenderState)]; + RenderState *state = new (state_storage) RenderState; state->local_object(); state->cache_ref_only(); state->_saved_entry = _states.store(state, nullptr); diff --git a/panda/src/pgraph/transformState.cxx b/panda/src/pgraph/transformState.cxx index f02e77203a..c8e27962da 100644 --- a/panda/src/pgraph/transformState.cxx +++ b/panda/src/pgraph/transformState.cxx @@ -1387,7 +1387,8 @@ init_states() { // _states_lock without a startup race condition. For the meantime, this is // OK because we guarantee that this method is called at static init time, // presumably when there is still only one thread in the world. - _states_lock = new LightReMutex("TransformState::_states_lock"); + alignas(LightReMutex) static char storage[sizeof(LightReMutex)]; + _states_lock = new (storage) LightReMutex("TransformState::_states_lock"); _cache_stats.init(); nassertv(Thread::get_current_thread() == Thread::get_main_thread()); diff --git a/panda/src/pgraphnodes/lightLensNode.I b/panda/src/pgraphnodes/lightLensNode.I index 4fd1138b03..ccdb55ea0a 100644 --- a/panda/src/pgraphnodes/lightLensNode.I +++ b/panda/src/pgraphnodes/lightLensNode.I @@ -36,6 +36,14 @@ get_shadow_buffer_sort() const { return _sb_sort; } +/** + * Sets the sort of the shadow buffer to be created for this light source. + */ +INLINE void LightLensNode:: +set_shadow_buffer_sort(int sort) { + _sb_sort = sort; +} + /** * Returns the size of the shadow buffer to be created for this light source. */ diff --git a/panda/src/pgraphnodes/lightLensNode.h b/panda/src/pgraphnodes/lightLensNode.h index f045c539b3..1d6b7720c8 100644 --- a/panda/src/pgraphnodes/lightLensNode.h +++ b/panda/src/pgraphnodes/lightLensNode.h @@ -42,6 +42,7 @@ PUBLISHED: void set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int sort = -10); INLINE int get_shadow_buffer_sort() const; + INLINE void set_shadow_buffer_sort(int sort); INLINE LVecBase2i get_shadow_buffer_size() const; INLINE void set_shadow_buffer_size(const LVecBase2i &size); @@ -50,6 +51,7 @@ PUBLISHED: PUBLISHED: MAKE_PROPERTY(shadow_caster, is_shadow_caster); + MAKE_PROPERTY(shadow_buffer_sort, get_shadow_buffer_sort, set_shadow_buffer_sort); MAKE_PROPERTY(shadow_buffer_size, get_shadow_buffer_size, set_shadow_buffer_size); public: diff --git a/panda/src/pgraphnodes/shaderGenerator.cxx b/panda/src/pgraphnodes/shaderGenerator.cxx index 5a8a17c9df..dd5e9f4c11 100644 --- a/panda/src/pgraphnodes/shaderGenerator.cxx +++ b/panda/src/pgraphnodes/shaderGenerator.cxx @@ -491,6 +491,10 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) { case TextureStage::M_emission: info._flags = ShaderKey::TF_map_emission; break; + case TextureStage::M_occlusion: + case TextureStage::M_occlusion_metallic_roughness: + info._flags = ShaderKey::TF_map_occlusion; + break; default: break; } @@ -557,9 +561,13 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) { // Decide whether to separate ambient and diffuse calculations. if (have_ambient) { - if (key._material_flags & Material::F_ambient) { + if (key._texture_flags & ShaderKey::TF_map_occlusion) { key._have_separate_ambient = true; - } else { + } + else if (key._material_flags & Material::F_ambient) { + key._have_separate_ambient = true; + } + else { if (key._material_flags & Material::F_diffuse) { key._have_separate_ambient = true; } else { @@ -1599,6 +1607,13 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) { text << "\t result *= saturate(2 * (tex" << map_index_glow << ".a - 0.5));\n"; } if (key._have_separate_ambient) { + // Apply occlusion textures. + for (size_t i = 0; i < key._textures.size(); ++i) { + ShaderKey::TextureInfo &tex = key._textures[i]; + if (tex._flags & ShaderKey::TF_map_occlusion) { + text << "\t tot_ambient *= tex" << i << ".r;\n"; + } + } if (key._material_flags & Material::F_ambient) { text << "\t result += tot_ambient * attr_material[0];\n"; } else if (key._color_type == ColorAttrib::T_vertex) { diff --git a/panda/src/pgraphnodes/shaderGenerator.h b/panda/src/pgraphnodes/shaderGenerator.h index f23fe3b315..fe574707f1 100644 --- a/panda/src/pgraphnodes/shaderGenerator.h +++ b/panda/src/pgraphnodes/shaderGenerator.h @@ -111,6 +111,7 @@ protected: TF_map_glow = 0x080, TF_map_gloss = 0x100, TF_map_emission = 0x001000000, + TF_map_occlusion = 0x002000000, TF_uses_color = 0x200, TF_uses_primary_color = 0x400, TF_uses_last_saved_result = 0x800, diff --git a/panda/src/pgui/pgSliderBar.cxx b/panda/src/pgui/pgSliderBar.cxx index d423549ccb..7fe5bc8c73 100644 --- a/panda/src/pgui/pgSliderBar.cxx +++ b/panda/src/pgui/pgSliderBar.cxx @@ -693,6 +693,11 @@ advance_scroll() { */ void PGSliderBar:: advance_page() { + // Do not try to advance the page while dragging + if (_dragging) { + return; + } + // Is the mouse position left or right of the current thumb position? LPoint3 mouse = mouse_to_local(_mouse_pos) - _thumb_start; PN_stdfloat target_ratio = mouse.dot(_axis) / _range_x; @@ -705,7 +710,7 @@ advance_page() { t = min(_ratio + _page_ratio - _scroll_ratio, target_ratio); } internal_set_ratio(t); - if (t == target_ratio) { + if (_ratio == target_ratio) { // We made it; begin dragging from now on until the user releases the // mouse. begin_drag(); diff --git a/panda/src/physics/linearDistanceForce.h b/panda/src/physics/linearDistanceForce.h index fa245db104..6de191f052 100644 --- a/panda/src/physics/linearDistanceForce.h +++ b/panda/src/physics/linearDistanceForce.h @@ -26,7 +26,10 @@ PUBLISHED: enum FalloffType { FT_ONE_OVER_R, FT_ONE_OVER_R_SQUARED, - FT_ONE_OVER_R_CUBED + FT_ONE_OVER_R_CUBED, + FT_ONE_OVER_R_OVER_DISTANCE, + FT_ONE_OVER_R_OVER_DISTANCE_SQUARED, + FT_ONE_OVER_R_OVER_DISTANCE_CUBED }; INLINE void set_radius(PN_stdfloat r); diff --git a/panda/src/physics/linearSinkForce.cxx b/panda/src/physics/linearSinkForce.cxx index ab1268cd5c..52d5e04174 100644 --- a/panda/src/physics/linearSinkForce.cxx +++ b/panda/src/physics/linearSinkForce.cxx @@ -61,7 +61,25 @@ make_copy() { */ LVector3 LinearSinkForce:: get_child_vector(const PhysicsObject *po) { - return (get_force_center() - po->get_position()) * get_scalar_term(); + LVector3 distance_vector = get_force_center() - po->get_position(); + PN_stdfloat distance_squared = distance_vector.length_squared(); + + if (distance_squared == 0) { + return distance_vector; + } + + PN_stdfloat scalar = get_scalar_term(); + + switch (get_falloff_type()) { + case FT_ONE_OVER_R_OVER_DISTANCE: + return (distance_vector / sqrt(distance_squared)) * scalar; + case FT_ONE_OVER_R_OVER_DISTANCE_SQUARED: + return (distance_vector / distance_squared) * scalar; + case FT_ONE_OVER_R_OVER_DISTANCE_CUBED: + return (distance_vector / (distance_squared * sqrt(distance_squared))) * scalar; + default: + return distance_vector * scalar; + } } /** diff --git a/panda/src/physics/linearSourceForce.cxx b/panda/src/physics/linearSourceForce.cxx index 4bb674dbad..db21b4b1f2 100644 --- a/panda/src/physics/linearSourceForce.cxx +++ b/panda/src/physics/linearSourceForce.cxx @@ -61,7 +61,25 @@ make_copy() { */ LVector3 LinearSourceForce:: get_child_vector(const PhysicsObject *po) { - return (po->get_position() - get_force_center()) * get_scalar_term(); + LVector3 distance_vector = po->get_position() - get_force_center(); + PN_stdfloat distance_squared = distance_vector.length_squared(); + + if (distance_squared == 0) { + return distance_vector; + } + + PN_stdfloat scalar = get_scalar_term(); + + switch (get_falloff_type()) { + case FT_ONE_OVER_R_OVER_DISTANCE: + return (distance_vector / sqrt(distance_squared)) * scalar; + case FT_ONE_OVER_R_OVER_DISTANCE_SQUARED: + return (distance_vector / distance_squared) * scalar; + case FT_ONE_OVER_R_OVER_DISTANCE_CUBED: + return (distance_vector / (distance_squared * sqrt(distance_squared))) * scalar; + default: + return distance_vector * scalar; + } } /** diff --git a/panda/src/pipeline/pythonThread.cxx b/panda/src/pipeline/pythonThread.cxx index 84770c258e..eff5728aaf 100644 --- a/panda/src/pipeline/pythonThread.cxx +++ b/panda/src/pipeline/pythonThread.cxx @@ -123,7 +123,7 @@ set_args(PyObject *args) { * exception. */ PyObject *PythonThread:: -call_python_func(PyObject *function, PyObject *args) { +call_python_func(PyObject *function, PyObject **args, size_t nargsf) { Thread *current_thread = get_current_thread(); // Create a new Python thread state data structure, so Python can properly @@ -132,7 +132,7 @@ call_python_func(PyObject *function, PyObject *args) { if (current_thread == get_main_thread()) { // In the main thread, just call the function. - result = PyObject_Call(function, args, nullptr); + result = _PyObject_Vectorcall(function, args, nargsf, nullptr); if (result == nullptr) { if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) { @@ -189,7 +189,7 @@ call_python_func(PyObject *function, PyObject *args) { PyThreadState_Swap(new_thread_state); // Call the user's function. - result = PyObject_Call(function, args, nullptr); + result = _PyObject_Vectorcall(function, args, nargsf, nullptr); if (result == nullptr && PyErr_Occurred()) { // We got an exception. Move the exception from the current thread into // the main thread, so it can be handled there. @@ -230,7 +230,7 @@ call_python_func(PyObject *function, PyObject *args) { gstate = PyGILState_Ensure(); // Call the user's function. - result = PyObject_Call(function, args, nullptr); + result = _PyObject_Vectorcall(function, args, nargsf, nullptr); if (result == nullptr && PyErr_Occurred()) { // We got an exception. Move the exception from the current thread into // the main thread, so it can be handled there. @@ -275,7 +275,9 @@ call_python_func(PyObject *function, PyObject *args) { */ void PythonThread:: thread_main() { - _result = call_python_func(_function, _args); + PyObject **args = &PyTuple_GET_ITEM(_args, 0); + Py_ssize_t nargs = PyTuple_GET_SIZE(_args); + _result = call_python_func(_function, args, (size_t)nargs); } #endif // HAVE_PYTHON diff --git a/panda/src/pipeline/pythonThread.h b/panda/src/pipeline/pythonThread.h index eb57351149..f9bf6c91e9 100644 --- a/panda/src/pipeline/pythonThread.h +++ b/panda/src/pipeline/pythonThread.h @@ -36,7 +36,8 @@ public: PyObject *get_args() const; void set_args(PyObject *); - static PyObject *call_python_func(PyObject *function, PyObject *args); + static PyObject *call_python_func(PyObject *function, PyObject **args, + size_t nargsf); PUBLISHED: MAKE_PROPERTY(args, get_args, set_args); diff --git a/panda/src/pipeline/threadPosixImpl.I b/panda/src/pipeline/threadPosixImpl.I index 326868ab61..cde10d4175 100644 --- a/panda/src/pipeline/threadPosixImpl.I +++ b/panda/src/pipeline/threadPosixImpl.I @@ -46,8 +46,14 @@ prepare_for_exit() { INLINE Thread *ThreadPosixImpl:: get_current_thread() { TAU_PROFILE("Thread *ThreadPosixImpl::get_current_thread()", " ", TAU_USER); +#ifdef ANDROID + // Android doesn't correctly share TLS variables across multiple DSOs, so + // we can't inline this. + return do_get_current_thread(); +#else Thread *thread = _current_thread; return (thread != nullptr) ? thread : init_current_thread(); +#endif } /** diff --git a/panda/src/pipeline/threadPosixImpl.cxx b/panda/src/pipeline/threadPosixImpl.cxx index 9266ded933..debea8f096 100644 --- a/panda/src/pipeline/threadPosixImpl.cxx +++ b/panda/src/pipeline/threadPosixImpl.cxx @@ -33,7 +33,12 @@ static JavaVM *java_vm = nullptr; #endif +// See comment in header file. +#ifdef ANDROID +static __thread Thread *_current_thread = nullptr; +#else __thread Thread *ThreadPosixImpl::_current_thread = nullptr; +#endif static patomic_flag _main_thread_known = ATOMIC_FLAG_INIT; /** @@ -53,6 +58,10 @@ ThreadPosixImpl:: _detached = true; } + if (_current_thread == _parent_obj) { + _current_thread = nullptr; + } + _mutex.unlock(); } @@ -214,12 +223,15 @@ bind_thread(Thread *thread) { #ifdef ANDROID /** - * Attaches the thread to the Java virtual machine. If this returns true, a - * JNIEnv pointer can be acquired using get_jni_env(). + * Attaches the thread to the Java virtual machine. On success, returns a + * JNIEnv pointer; returns nullptr otherwise, in which case the application + * might not be running inside a Java VM. */ -bool ThreadPosixImpl:: +JNIEnv *ThreadPosixImpl:: attach_java_vm() { - assert(java_vm != nullptr); + if (java_vm == nullptr) { + return nullptr; + } JNIEnv *env; std::string thread_name = _parent_obj->get_name(); @@ -232,10 +244,10 @@ attach_java_vm() { << "Failed to attach Java VM to thread " << _parent_obj->get_name() << "!\n"; _jni_env = nullptr; - return false; + return nullptr; } _jni_env = env; - return true; + return env; } /** @@ -308,7 +320,7 @@ root_func(void *data) { #ifdef ANDROID // Attach the Java VM to allow calling Java functions in this thread. - self->attach_java_vm(); + JNIEnv *jni_env = self->attach_java_vm(); #endif self->_parent_obj->thread_main(); @@ -331,9 +343,11 @@ root_func(void *data) { #ifdef ANDROID // We cannot let the thread end without detaching it. - if (self->_jni_env != nullptr) { - java_vm->DetachCurrentThread(); - self->_jni_env = nullptr; + if (jni_env != nullptr) { + if (java_vm != nullptr) { + java_vm->DetachCurrentThread(); + } + jni_env = nullptr; } #endif @@ -341,6 +355,8 @@ root_func(void *data) { // might delete the parent object, and in turn, delete the ThreadPosixImpl // object. unref_delete(self->_parent_obj); + + _current_thread = nullptr; } return nullptr; @@ -360,6 +376,17 @@ init_current_thread() { return thread; } +#ifdef ANDROID +/** + * + */ +Thread *ThreadPosixImpl:: +do_get_current_thread() { + Thread *thread = _current_thread; + return (thread != nullptr) ? thread : init_current_thread(); +} +#endif // ANDROID + #ifdef ANDROID /** * Called by Java when loading this library from the Java virtual machine. diff --git a/panda/src/pipeline/threadPosixImpl.h b/panda/src/pipeline/threadPosixImpl.h index 34422506d7..7859c2897d 100644 --- a/panda/src/pipeline/threadPosixImpl.h +++ b/panda/src/pipeline/threadPosixImpl.h @@ -48,7 +48,7 @@ public: INLINE static void prepare_for_exit(); - INLINE static Thread *get_current_thread(); + static Thread *get_current_thread(); static Thread *bind_thread(Thread *thread); INLINE static bool is_threading_supported(); INLINE static bool is_true_threads(); @@ -59,7 +59,7 @@ public: #ifdef ANDROID INLINE JNIEnv *get_jni_env() const; - bool attach_java_vm(); + JNIEnv *attach_java_vm(); static void bind_java_thread(); #endif @@ -69,6 +69,10 @@ private: static void *root_func(void *data); static Thread *init_current_thread(); +#ifdef ANDROID + static Thread *do_get_current_thread(); +#endif + // There appears to be a name collision with the word "Status". enum PStatus { S_new, @@ -88,7 +92,11 @@ private: JNIEnv *_jni_env; #endif + // Android doesn't do TLS correctly across .so boundaries, so we hide the + // current thread in the .cxx file. +#ifndef ANDROID static __thread Thread *_current_thread; +#endif }; #include "threadPosixImpl.I" diff --git a/panda/src/pstatclient/config_pstatclient.cxx b/panda/src/pstatclient/config_pstatclient.cxx index 640a4777d5..54d9398dce 100644 --- a/panda/src/pstatclient/config_pstatclient.cxx +++ b/panda/src/pstatclient/config_pstatclient.cxx @@ -98,6 +98,16 @@ ConfigVariableBool pstats_python_profiler "somewhat, and requires a recent version of the PStats server, so " "it is not enabled by default.")); +ConfigVariableBool pstats_python_ref_tracer +("pstats-python-ref-tracer", false, + PRC_DESC("Set this true to integrate with the Python ref tracer to show a " + "count of all Python objects by module. This is not enabled by " + "default since it incurs a significant performance overhead, but " + "can be useful to find bottlenecks caused by a growth of objects " + "in the Python interpreter. This feature is only available as of " + "Python 3.14a6.")); + + // The rest are different in that they directly control the server, not the // client. ConfigVariableBool pstats_scroll_mode diff --git a/panda/src/pstatclient/config_pstatclient.h b/panda/src/pstatclient/config_pstatclient.h index a16967c964..fd83a22f40 100644 --- a/panda/src/pstatclient/config_pstatclient.h +++ b/panda/src/pstatclient/config_pstatclient.h @@ -40,6 +40,7 @@ extern EXPCL_PANDA_PSTATCLIENT ConfigVariableDouble pstats_target_frame_rate; extern EXPCL_PANDA_PSTATCLIENT ConfigVariableBool pstats_gpu_timing; extern EXPCL_PANDA_PSTATCLIENT ConfigVariableBool pstats_thread_profiling; extern EXPCL_PANDA_PSTATCLIENT ConfigVariableBool pstats_python_profiler; +extern EXPCL_PANDA_PSTATCLIENT ConfigVariableBool pstats_python_ref_tracer; extern EXPCL_PANDA_PSTATCLIENT ConfigVariableBool pstats_scroll_mode; extern EXPCL_PANDA_PSTATCLIENT ConfigVariableDouble pstats_history; diff --git a/panda/src/pstatclient/pStatClient.cxx b/panda/src/pstatclient/pStatClient.cxx index 3daa78d3d5..809dd0828a 100644 --- a/panda/src/pstatclient/pStatClient.cxx +++ b/panda/src/pstatclient/pStatClient.cxx @@ -1116,9 +1116,19 @@ add_collector(PStatClient::Collector *collector) { // We need to grow the array. We have to be careful here, because there // might be clients accessing the array right now who are not protected by // the lock. - size_t new_collectors_size = (_collectors_size == 0) ? 128 : _collectors_size * 2; CollectorPointer *old_collectors = _collectors.load(std::memory_order_relaxed); - CollectorPointer *new_collectors = new CollectorPointer[new_collectors_size]; + CollectorPointer *new_collectors; + + static const size_t initial_collectors_size = 256; + static CollectorPointer initial_collectors[initial_collectors_size]; + size_t new_collectors_size; + if (_collectors_size == 0) { + new_collectors_size = initial_collectors_size; + new_collectors = initial_collectors; + } else { + new_collectors_size = _collectors_size * 2; + new_collectors = new CollectorPointer[new_collectors_size]; + } if (old_collectors != nullptr) { memcpy(new_collectors, old_collectors, num_collectors * sizeof(CollectorPointer)); } diff --git a/panda/src/pstatclient/pStatClient.h b/panda/src/pstatclient/pStatClient.h index 97139e770d..4548d29d01 100644 --- a/panda/src/pstatclient/pStatClient.h +++ b/panda/src/pstatclient/pStatClient.h @@ -234,6 +234,9 @@ private: size_t _threads_size {0}; // size of the allocated array patomic _num_threads {0}; // number of in-use elements within the array + // See pStatClient_ext.cxx. + pmap _python_type_collectors; + mutable PStatClientImpl *_impl; static PStatCollector _heap_total_size_pcollector; diff --git a/panda/src/pstatclient/pStatClientImpl.cxx b/panda/src/pstatclient/pStatClientImpl.cxx index ed8a8d0e82..f6db443b55 100644 --- a/panda/src/pstatclient/pStatClientImpl.cxx +++ b/panda/src/pstatclient/pStatClientImpl.cxx @@ -155,10 +155,9 @@ client_connect(std::string hostname, int port) { int thread_index = current_thread->get_pstats_index(); if (thread_index >= 0) { PStatClient *client = PStatClient::get_global_pstats(); - double start = client->get_real_time(); + client->start(_wait_sleep_pcollector.get_index(), thread_index); ThreadImpl::sleep(seconds); - double stop = client->get_real_time(); - client->start_stop(_wait_sleep_pcollector.get_index(), thread_index, start, stop); + client->stop(_wait_sleep_pcollector.get_index(), thread_index); client->add_level(_cswitch_sleep_pcollector.get_index(), thread_index, 1); } else { @@ -171,10 +170,9 @@ client_connect(std::string hostname, int port) { int thread_index = current_thread->get_pstats_index(); if (thread_index >= 0) { PStatClient *client = PStatClient::get_global_pstats(); - double start = client->get_real_time(); + client->start(_wait_yield_pcollector.get_index(), thread_index); ThreadImpl::yield(); - double stop = client->get_real_time(); - client->start_stop(_wait_yield_pcollector.get_index(), thread_index, start, stop); + client->stop(_wait_yield_pcollector.get_index(), thread_index); client->add_level(_cswitch_yield_pcollector.get_index(), thread_index, 1); } else { @@ -190,10 +188,9 @@ client_connect(std::string hostname, int port) { BOOL result; if (thread_index >= 0) { PStatClient *client = PStatClient::get_global_pstats(); - double start = client->get_real_time(); + client->start(_wait_cvar_pcollector.get_index(), thread_index); result = SleepConditionVariableSRW(cvar, lock, time, flags); - double stop = client->get_real_time(); - client->start_stop(_wait_cvar_pcollector.get_index(), thread_index, start, stop); + client->stop(_wait_cvar_pcollector.get_index(), thread_index); client->add_level(_cswitch_cvar_pcollector.get_index(), thread_index, 1); } else { @@ -211,10 +208,9 @@ client_connect(std::string hostname, int port) { int result; if (thread_index >= 0) { PStatClient *client = PStatClient::get_global_pstats(); - double start = client->get_real_time(); + client->start(_wait_cvar_pcollector.get_index(), thread_index); result = pthread_cond_wait(cvar, lock); - double stop = client->get_real_time(); - client->start_stop(_wait_cvar_pcollector.get_index(), thread_index, start, stop); + client->stop(_wait_cvar_pcollector.get_index(), thread_index); client->add_level(_cswitch_cvar_pcollector.get_index(), thread_index, 1); } else { @@ -231,10 +227,9 @@ client_connect(std::string hostname, int port) { int result; if (thread_index >= 0) { PStatClient *client = PStatClient::get_global_pstats(); - double start = client->get_real_time(); + client->start(_wait_cvar_pcollector.get_index(), thread_index); result = pthread_cond_timedwait(cvar, lock, ts); - double stop = client->get_real_time(); - client->start_stop(_wait_cvar_pcollector.get_index(), thread_index, start, stop); + client->stop(_wait_cvar_pcollector.get_index(), thread_index); client->add_level(_cswitch_cvar_pcollector.get_index(), thread_index, 1); } else { diff --git a/panda/src/pstatclient/pStatClient_ext.cxx b/panda/src/pstatclient/pStatClient_ext.cxx index f786b07dcd..dfe94536b5 100644 --- a/panda/src/pstatclient/pStatClient_ext.cxx +++ b/panda/src/pstatclient/pStatClient_ext.cxx @@ -17,6 +17,7 @@ #include "pStatCollector.h" #include "config_pstatclient.h" +#include "reMutexHolder.h" #ifndef CPPPARSER #include "frameobject.h" @@ -33,6 +34,9 @@ static pmap _c_method_collectors; // Parent collector for all Python profiling collectors. static PStatCollector code_collector("App:Python"); +// Parent collector for all Python ref tracer collectors. +static PStatCollector refs_collector("Python objects"); + /** * Walks up the type hierarchy to find the class where the method originates. */ @@ -263,8 +267,30 @@ client_connect(std::string hostname, int port) { _extra_index = _PyEval_RequestCodeExtraIndex(nullptr); } PyEval_SetProfile(&trace_callback, arg); - _python_profiler_enabled = false; + _python_profiler_enabled = true; } + + // We require 3.13.3 or 3.14a6, since those versions fix an important bug + // with the ref tracer; prior versions did not always send destroy events. +#if PY_VERSION_HEX >= 0x030D0000 +#if PY_VERSION_HEX >= 0x030E0000 + if (Py_Version >= 0x030E00A6) +#else + if (Py_Version >= 0x030D0300) +#endif + { + if (pstats_python_ref_tracer) { + PyRefTracer_SetTracer(&ref_trace_callback, _this); + } + } + else +#endif + if (pstats_python_ref_tracer) { + pstats_cat.warning() + << "The pstats-python-ref-tracer feature requires at least " + "Python 3.13.3 or Python 3.14a6.\n"; + } + return true; } else if (_python_profiler_enabled) { @@ -284,6 +310,13 @@ client_disconnect() { PyEval_SetProfile(nullptr, nullptr); _python_profiler_enabled = false; } + +#if PY_VERSION_HEX >= 0x030D0000 // 3.13 + void *data; + if (PyRefTracer_GetTracer(&data) == &ref_trace_callback && data == _this) { + PyRefTracer_SetTracer(nullptr, nullptr); + } +#endif } /** @@ -365,4 +398,112 @@ trace_callback(PyObject *py_thread, PyFrameObject *frame, int what, PyObject *ar return 0; } +/** + * Callback passed to PyRefTracer_SetTracer. + */ +#if PY_VERSION_HEX >= 0x030D0000 // 3.13 +int Extension:: +ref_trace_callback(PyObject *obj, PyRefTracerEvent event, void *data) { + PStatClient *client = (PStatClient *)data; + if (!client->client_is_connected()) { + return 0; + } + + PyTypeObject *cls = Py_TYPE(obj); + +#ifdef Py_GIL_DISABLED + // With GIL disabled, the GIL is no longer protecting the cache, so we + // have to do that ourselves. + client->_lock.acquire(); +#endif + + int collector_index; + auto it = client->_python_type_collectors.find(cls); + if (it != client->_python_type_collectors.end()) { + collector_index = it->second; +#ifdef Py_GIL_DISABLED + client->_lock.release(); +#endif + } + else { +#ifdef Py_GIL_DISABLED + client->_lock.release(); +#endif + char buffer[1024]; + size_t len; + + if (cls == &PyDict_Type || cls == &PyUnicode_Type) { + // Prevents recursion due to PyDict_GetItemStringRef + len = snprintf(buffer, sizeof(buffer), "builtins:%s", cls->tp_name); + } + else { + const char *dot = strrchr(cls->tp_name, '.'); + if (dot != nullptr) { + // The module name is included in the type name. + len = snprintf(buffer, sizeof(buffer), "%s", cls->tp_name); + } else { + // If there's no module name, we need to get it from __module__. + PyObject *py_mod_name = nullptr; + const char *mod_name = nullptr; + if (cls->tp_dict != nullptr && + PyDict_GetItemStringRef(cls->tp_dict, "__module__", &py_mod_name) > 0) { + if (PyUnicode_Check(py_mod_name)) { + mod_name = PyUnicode_AsUTF8(py_mod_name); + } else { + // Might be a descriptor. + Py_DECREF(py_mod_name); + py_mod_name = PyObject_GetAttrString(obj, "__module__"); + if (py_mod_name != nullptr) { + if (PyUnicode_Check(py_mod_name)) { + mod_name = PyUnicode_AsUTF8(py_mod_name); + } + } + else PyErr_Clear(); + } + } + else PyErr_Clear(); + + if (mod_name == nullptr) { + // Is it a built-in, like int or dict? + PyObject *builtins = PyEval_GetBuiltins(); + if (PyDict_GetItemString(builtins, cls->tp_name) == (PyObject *)cls) { + mod_name = "builtins"; + } else { + mod_name = ""; + } + } + len = snprintf(buffer, sizeof(buffer), "%s:%s", mod_name, cls->tp_name); + Py_XDECREF(py_mod_name); + } + } + + for (size_t i = 0; i < len; ++i) { + if (buffer[i] == '.') { + buffer[i] = ':'; + } + } + + std::string collector_name(buffer, len); + +#ifdef Py_GIL_DISABLED + ReMutexHolder holder(client->_lock); +#endif + collector_index = client->make_collector_with_relname(refs_collector.get_index(), collector_name).get_index(); + client->_python_type_collectors[cls] = collector_index; + } + + switch (event) { + case PyRefTracer_CREATE: + client->add_level(collector_index, 0, 1); + break; + + case PyRefTracer_DESTROY: + client->add_level(collector_index, 0, -1); + break; + } + + return 0; +} +#endif + #endif // HAVE_PYTHON && DO_PSTATS diff --git a/panda/src/pstatclient/pStatClient_ext.h b/panda/src/pstatclient/pStatClient_ext.h index 216d7777b1..47c43b178d 100644 --- a/panda/src/pstatclient/pStatClient_ext.h +++ b/panda/src/pstatclient/pStatClient_ext.h @@ -40,6 +40,10 @@ public: private: static int trace_callback(PyObject *py_thread, PyFrameObject *frame, int what, PyObject *arg); + +#if PY_VERSION_HEX >= 0x030D0000 // 3.13 + static int ref_trace_callback(PyObject *obj, PyRefTracerEvent event, void *data); +#endif }; #include "pStatClient_ext.I" diff --git a/panda/src/pstatclient/pStatFrameData.cxx b/panda/src/pstatclient/pStatFrameData.cxx index aef68b3d9e..4397a3a5de 100644 --- a/panda/src/pstatclient/pStatFrameData.cxx +++ b/panda/src/pstatclient/pStatFrameData.cxx @@ -34,7 +34,7 @@ sort_time() { */ bool PStatFrameData:: write_datagram(Datagram &destination, PStatClient *client) const { - if (_time_data.size() >= 65536 || _level_data.size() >= 65536) { + if (_time_data.size() >= INT_MAX || _level_data.size() >= INT_MAX) { pstats_cat.info() << "Dropping frame with " << _time_data.size() << " time measurements and " << _level_data.size() @@ -63,7 +63,7 @@ write_datagram(Datagram &destination, PStatClient *client) const { ptr += 2; } - *(uint32_t *)ptr = __builtin_bswap16(_level_data.size()); + *(uint32_t *)ptr = __builtin_bswap32(_level_data.size()); ptr += 2; for (const DataPoint &dp : _level_data) { diff --git a/panda/src/putil/CMakeLists.txt b/panda/src/putil/CMakeLists.txt index ecc14b9843..c99dfac383 100644 --- a/panda/src/putil/CMakeLists.txt +++ b/panda/src/putil/CMakeLists.txt @@ -20,6 +20,9 @@ set(P3PUTIL_HEADERS clockObject.h clockObject.I collideMask.h colorSpace.h + completable.I completable.h + completionCounter.I completionCounter.h + completionToken.I completionToken.h copyOnWriteObject.h copyOnWriteObject.I copyOnWritePointer.h copyOnWritePointer.I compareTo.I compareTo.h @@ -86,6 +89,7 @@ set(P3PUTIL_SOURCES callbackObject.cxx clockObject.cxx colorSpace.cxx + completionCounter.cxx copyOnWriteObject.cxx copyOnWritePointer.cxx config_putil.cxx configurable.cxx diff --git a/panda/src/putil/bam.h b/panda/src/putil/bam.h index 33ce0b9f38..7647ab0f87 100644 --- a/panda/src/putil/bam.h +++ b/panda/src/putil/bam.h @@ -32,8 +32,8 @@ static const unsigned short _bam_major_ver = 6; // Bumped to major version 6 on 2006-02-11 to factor out PandaNode::CData. static const unsigned short _bam_first_minor_ver = 14; -static const unsigned short _bam_last_minor_ver = 45; -static const unsigned short _bam_minor_ver = 44; +static const unsigned short _bam_last_minor_ver = 46; +static const unsigned short _bam_minor_ver = 46; // Bumped to minor version 14 on 2007-12-19 to change default ColorAttrib. // Bumped to minor version 15 on 2008-04-09 to add TextureAttrib::_implicit_sort. // Bumped to minor version 16 on 2008-05-13 to add Texture::_quality_level. @@ -66,5 +66,6 @@ static const unsigned short _bam_minor_ver = 44; // Bumped to minor version 43 on 2018-12-06 to expand BillboardEffect and CompassEffect. // Bumped to minor version 44 on 2018-12-23 to rename CollisionTube to CollisionCapsule. // Bumped to minor version 45 on 2020-03-18 to add Texture::_clear_color. +// Bumped to minor version 46 on 2025-08-03 to add ModelRoot::_loader_type. #endif diff --git a/panda/src/putil/bamCache.I b/panda/src/putil/bamCache.I index 38a9e45540..5729d6d0fb 100644 --- a/panda/src/putil/bamCache.I +++ b/panda/src/putil/bamCache.I @@ -257,3 +257,4 @@ mark_index_stale() { _index_stale_since = time(nullptr); } } + diff --git a/panda/src/putil/bamCache.cxx b/panda/src/putil/bamCache.cxx index c6fd7d5e88..47ec79a9ce 100644 --- a/panda/src/putil/bamCache.cxx +++ b/panda/src/putil/bamCache.cxx @@ -959,6 +959,89 @@ do_read_record(const Filename &cache_pathname, bool read_data) { return record; } +/** + * Clear the model cache. + * + * Acquires the internal _lock for thread-safety (so callers do NOT need to + * hold the lock). If no cache root is configured (_root.empty()), returns + * immediately. If the cache is marked _read_only, resets only the in-memory + * index and emits a warning message. + * If the VirtualFileSystem global pointer is not available, logs an error, + * resets only the in-memory index, and returns immediately. Otherwise, scans + * the cache root directory and deletes cache files matching the known cache + * extensions (.bam, .txo, .sho). Removes the on-disk index file (if known) + * or the index reference file and clears the in-memory index reference + * contents. Resets the in-memory index to an empty BamCacheIndex. Marks the + * index stale and attempts to flush a new, empty on-disk index so other + * processes will observe the cleared state. + */ +void BamCache:: +clear() { + ReMutexHolder holder(_lock); + + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + + if (_root.empty()) { + return; + } + + // If the cache is read-only, only reset the in-memory index and log. + if (_read_only) { + util_cat.info() << "BamCache::clear(): cache is read-only; clearing in-memory index only.\n"; + reset_in_memory_index(); + return; + } + + if (vfs == nullptr) { + util_cat.error() << "BamCache::clear(): VFS is not available\n"; + reset_in_memory_index(); + return; + } + + // Delete cache files (.bam, .txo, .sho) in the cache root directory. + PT(VirtualFileList) contents = vfs->scan_directory(_root); + if (contents != nullptr) { + int num_files = contents->get_num_files(); + for (int ci = 0; ci < num_files; ++ci) { + VirtualFile *file = contents->get_file(ci); + Filename filename = file->get_filename(); + const std::string ext = filename.get_extension(); + + if (ext == "bam" || ext == "txo" || ext == "sho") { + Filename pathname(_root, filename); + if (!vfs->delete_file(pathname)) { + util_cat.debug() + << "Could not delete cache file " << pathname << "\n"; + } + } + } + } + + // Remove index files: the index file itself (if known) and the index ref. + Filename index_ref_pathname(_root, Filename("index_name.txt")); + if (!_index_pathname.empty()) { + vfs->delete_file(_index_pathname); + _index_pathname = Filename(); + _index_ref_contents.clear(); + } else { + vfs->delete_file(index_ref_pathname); + } + + reset_in_memory_index(); + + // Try to write an empty index back to disk: mark stale then flush. + mark_index_stale(); + flush_index(); +} + +/* Reset the in-memory cache index to an empty state (caller must hold _lock). */ +void BamCache:: +reset_in_memory_index() { + delete _index; + _index = new BamCacheIndex; + _index_stale_since = 0; +} + /** * Returns the appropriate filename to use for a cache file, given the * fullpath string to the source filename. diff --git a/panda/src/putil/bamCache.h b/panda/src/putil/bamCache.h index 28b2335ff6..28a81f8d31 100644 --- a/panda/src/putil/bamCache.h +++ b/panda/src/putil/bamCache.h @@ -80,6 +80,8 @@ PUBLISHED: void list_index(std::ostream &out, int indent_level = 0) const; + void clear(); + INLINE static BamCache *get_global_ptr(); INLINE static void consider_flush_global_index(); INLINE static void flush_global_index(); @@ -104,7 +106,7 @@ private: void merge_index(BamCacheIndex *new_index); void rebuild_index(); INLINE void mark_index_stale(); - + void reset_in_memory_index(); void add_to_index(const BamCacheRecord *record); void remove_from_index(const Filename &source_filename); diff --git a/panda/src/putil/bamCacheRecord.I b/panda/src/putil/bamCacheRecord.I index e5288c03ac..f2fb5aa0e1 100644 --- a/panda/src/putil/bamCacheRecord.I +++ b/panda/src/putil/bamCacheRecord.I @@ -30,6 +30,7 @@ operator == (const BamCacheRecord &other) const { return (_source_pathname == other._source_pathname && _cache_filename == other._cache_filename && _recorded_time == other._recorded_time && + _loader_type == other._loader_type && _record_size == other._record_size); } diff --git a/panda/src/putil/bamCacheRecord.h b/panda/src/putil/bamCacheRecord.h index 39b3728dfa..2f904f8db5 100644 --- a/panda/src/putil/bamCacheRecord.h +++ b/panda/src/putil/bamCacheRecord.h @@ -93,6 +93,7 @@ private: time_t _recorded_time; std::streamsize _record_size; // this is accurate only in the index file. time_t _source_timestamp; // Not record to the cache file. + TypeHandle _loader_type; class DependentFile { public: diff --git a/panda/src/putil/bamReader_ext.cxx b/panda/src/putil/bamReader_ext.cxx index 84c5060e1e..e85606b440 100644 --- a/panda/src/putil/bamReader_ext.cxx +++ b/panda/src/putil/bamReader_ext.cxx @@ -41,15 +41,14 @@ static TypedWritable *factory_callback(const FactoryParams ¶ms){ BamReader *manager; parse_params(params, scan, manager); - PyObject *py_scan = DTool_CreatePyInstance(&scan, Dtool_DatagramIterator, false, false); - PyObject *py_manager = DTool_CreatePyInstance(manager, Dtool_BamReader, false, false); - PyObject *args = PyTuple_Pack(2, py_scan, py_manager); + PyObject *args[3]; + args[1] = DTool_CreatePyInstance(&scan, Dtool_DatagramIterator, false, false); + args[2] = DTool_CreatePyInstance(manager, Dtool_BamReader, false, false); // Now call the Python function. - PyObject *result = PythonThread::call_python_func(func, args); - Py_DECREF(args); - Py_DECREF(py_scan); - Py_DECREF(py_manager); + PyObject *result = PythonThread::call_python_func(func, args + 1, 2 | PY_VECTORCALL_ARGUMENTS_OFFSET); + Py_DECREF(args[1]); + Py_DECREF(args[2]); if (result == nullptr) { util_cat.error() diff --git a/panda/src/putil/bitArray_ext.cxx b/panda/src/putil/bitArray_ext.cxx index b454593d1b..1391e37e92 100644 --- a/panda/src/putil/bitArray_ext.cxx +++ b/panda/src/putil/bitArray_ext.cxx @@ -20,6 +20,29 @@ */ void Extension:: __init__(PyObject *init_value) { +#if PY_VERSION_HEX >= 0x030e0000 // Python 3.14 + BitArray::WordType word = 0; + Py_ssize_t result = PyLong_AsNativeBytes(init_value, &word, sizeof(word), Py_ASNATIVEBYTES_LITTLE_ENDIAN | Py_ASNATIVEBYTES_UNSIGNED_BUFFER | Py_ASNATIVEBYTES_REJECT_NEGATIVE); + if (result < 0) { + return; + } + + if ((size_t)result <= sizeof(BitArray::WordType)) { + // It fit in a single word + _this->_array.clear(); + if (word != 0) { + _this->_array.push_back(word); + } + } else { + // Requires multiple words. + size_t num_words = ((size_t)result + sizeof(BitArray::WordType) - 1) / sizeof(BitArray::WordType); + _this->_array.resize(num_words); + + if (PyLong_AsNativeBytes(init_value, &_this->_array[0], num_words * sizeof(BitArray::WordType), Py_ASNATIVEBYTES_LITTLE_ENDIAN | Py_ASNATIVEBYTES_UNSIGNED_BUFFER | Py_ASNATIVEBYTES_REJECT_NEGATIVE) <= 0) { + return; + } + } +#else if (!PyLong_Check(init_value) || !PyLong_IsNonNegative(init_value)) { PyErr_SetString(PyExc_ValueError, "BitArray constructor requires a positive integer"); return; @@ -38,6 +61,7 @@ __init__(PyObject *init_value) { #endif ); } +#endif } /** @@ -55,19 +79,34 @@ __getstate__() const { } if (_this->_highest_bits == 0) { +#if PY_VERSION_HEX >= 0x030e0000 // Python 3.14 + return PyLong_FromUnsignedNativeBytes( + &_this->_array[0], _this->_array.size() * sizeof(BitArray::WordType), + Py_ASNATIVEBYTES_LITTLE_ENDIAN); +#else return _PyLong_FromByteArray( (const unsigned char *)&_this->_array[0], _this->_array.size() * sizeof(BitArray::WordType), 1, 0); +#endif } else { // This is an infinite array, so we invert it to make it a finite array and // store it as an inverted long. BitArray copy(*_this); copy.invert_in_place(); +#if PY_VERSION_HEX >= 0x030e0000 // Python 3.14 + PyObject *state = PyLong_FromUnsignedNativeBytes( + ©._array[0], copy._array.size() * sizeof(BitArray::WordType), + Py_ASNATIVEBYTES_LITTLE_ENDIAN); +#else PyObject *state = _PyLong_FromByteArray( (const unsigned char *)©._array[0], copy._array.size() * sizeof(BitArray::WordType), 1, 0); +#endif + if (state == nullptr) { + return nullptr; + } PyObject *inverted = PyNumber_Invert(state); Py_DECREF(state); return inverted; diff --git a/panda/src/putil/callbackObject.h b/panda/src/putil/callbackObject.h index 3e1b551eec..c51e1bae0d 100644 --- a/panda/src/putil/callbackObject.h +++ b/panda/src/putil/callbackObject.h @@ -16,6 +16,7 @@ #include "pandabase.h" #include "typedReferenceCount.h" +#include "pointerTo.h" class CallbackData; diff --git a/panda/src/putil/completable.I b/panda/src/putil/completable.I new file mode 100644 index 0000000000..96d140be33 --- /dev/null +++ b/panda/src/putil/completable.I @@ -0,0 +1,75 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file completable.I + * @author rdb + * @date 2025-01-22 + */ + +#ifndef CPPPARSER +/** + * + */ +template +INLINE Completable:: +Completable(Callable callback) : + _data(new LambdaData(std::move(callback), [](Data *data, bool do_run) { + LambdaData *self = (LambdaData *)data; + if (do_run) { + std::move(self->_lambda)(); + } + delete self; + })) { +} +#endif + +/** + * + */ +INLINE Completable:: +Completable(Completable &&from) noexcept : + _data(from._data) { + from._data = nullptr; +} + +/** + * + */ +INLINE Completable &Completable:: +operator =(Completable &&from) { + Data *data = _data; + _data = from._data; + from._data = nullptr; + if (data != nullptr) { + data->_function.load(std::memory_order_relaxed)(data, false); + } + return *this; +} + +/** + * + */ +INLINE Completable:: +~Completable() { + Data *data = _data; + if (data != nullptr) { + data->_function.load(std::memory_order_relaxed)(data, false); + } +} + +/** + * + */ +INLINE void Completable:: +operator ()() { + Data *data = _data; + _data = nullptr; + if (data != nullptr) { + data->_function.load(std::memory_order_relaxed)(data, true); + } +} diff --git a/panda/src/putil/completable.h b/panda/src/putil/completable.h new file mode 100644 index 0000000000..9b0f6fdd12 --- /dev/null +++ b/panda/src/putil/completable.h @@ -0,0 +1,82 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file completable.h + * @author rdb + * @date 2025-01-22 + */ + +#ifndef COMPLETABLE_H +#define COMPLETABLE_H + +#include "pandabase.h" +#include "patomic.h" + +/** + * Stores a type-erased callable that is move-only. May only be called once. + */ +class EXPCL_PANDA_PUTIL Completable { +public: + constexpr Completable() = default; + +#ifndef CPPPARSER + template + INLINE Completable(Callable callback); +#endif + + INLINE Completable(const Completable ©) = delete; + INLINE Completable(Completable &&from) noexcept; + + INLINE Completable &operator =(const Completable ©) = delete; + INLINE Completable &operator =(Completable &&from); + + INLINE void operator ()(); + + INLINE ~Completable(); + +protected: + // There are several design approaches here: + // 1. Optimize for no data block: do not require dynamic allocation of a data + // block in the simple case where the callback data is only the size of a + // single pointer. Store two pointers, one function pointer and a data + // pointer(-sized storage), directly on the class here. + // 2. Optimize for a data block: store the function pointer on the data block, + // always requiring dynamic allocation. + // + // Right now I have opted for 2 because it allows the function pointer to be + // dynamically swapped (used in CompletionCounter), but this decision may + // change in the future. + + struct Data; + typedef void CallbackFunction(Data *, bool); + + struct Data { + patomic _function { nullptr }; + }; + + template + struct LambdaData : public Data { + // Must unfortunately be defined inline, since this struct is protected. + LambdaData(Lambda lambda, CallbackFunction *function) : + _lambda(std::move(lambda)) { + _function = function; + } + + Lambda _lambda; + }; + + Data *_data = nullptr; + + friend class AsyncFuture; + friend class CompletionCounter; + friend class CompletionToken; +}; + +#include "completable.I" + +#endif diff --git a/panda/src/putil/completionCounter.I b/panda/src/putil/completionCounter.I new file mode 100644 index 0000000000..6d59143360 --- /dev/null +++ b/panda/src/putil/completionCounter.I @@ -0,0 +1,97 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file completionCounter.I + * @author rdb + * @date 2025-01-22 + */ + +/** + * + */ +INLINE CompletionCounter:: +~CompletionCounter() { + CounterData *data = _data; + if (data != nullptr) { + // then() is not called; we still need something that destructs the data + // when done. + auto prev_function = data->_function.exchange(&abandon_callback, std::memory_order_relaxed); + if (prev_function == nullptr) { + // Was already done. + delete data; + } + } +} + +/** + * Returns a new token. May not be called after then(). + */ +INLINE CompletionToken CompletionCounter:: +make_token() { + CompletionToken token; + if (_data == nullptr) { + _data = new CounterData; + _data->_function = &initial_callback; + } + auto old_value = _data->_counter.fetch_add(1); + nassertr(old_value >= 0, token); + token._callback._data = _data; + return token; +} + +/** + * Runs the given callback immediately upon completion. If the counter is + * already done, runs it immediately. This requires an rvalue because it + * consumes the counter, use std::move() if you don't have an rvalue. + * + * The callback will either be called immediately or directly when the last + * token calls complete(), however, it may also be called if a token is + * destroyed. This may happen at unexpected times, such as when the lambda + * holding the token is destroyed prematurely. In this case, however, the + * passed success argument will always be false. + */ +template +INLINE void CompletionCounter:: +then(Callable callable) && { + // Replace the callback pointer with something that calls the given callable + // once the count reaches 0. + CounterData *data = _data; + nassertv(data != nullptr); + _data = nullptr; + if (data->_function.load(std::memory_order_acquire) == nullptr) { + // Already done. + callable((data->_counter.load(std::memory_order_relaxed) & ~0xffff) == 0); + delete data; + return; + } + + static_assert(sizeof(Callable) <= sizeof(data->_storage), + "raise storage size in completionCounter.h or reduce lambda captures"); + + new (data->_storage) Callable(std::move(callable)); + + Completable::CallbackFunction *new_function = + [] (Completable::Data *data_ptr, bool success) { + CounterData *data = (CounterData *)data_ptr; + auto prev_count = data->_counter.fetch_add((success ? 0 : 0x10000) - 1, std::memory_order_release); + if ((short)(prev_count & 0xffff) > 1) { + return; + } + + Callable *callable = (Callable *)data->_storage; + std::move(*callable)(success && (prev_count & ~0xffff) == 0); + callable->~Callable(); + delete data; + }; + + auto prev_function = data->_function.exchange(new_function, std::memory_order_acq_rel); + if (UNLIKELY(prev_function == nullptr)) { + // Last token finished in the meantime. + new_function(data, (data->_counter.load(std::memory_order_relaxed) & ~0xffff) == 0); + } +} diff --git a/panda/src/putil/completionCounter.cxx b/panda/src/putil/completionCounter.cxx new file mode 100644 index 0000000000..2540867e61 --- /dev/null +++ b/panda/src/putil/completionCounter.cxx @@ -0,0 +1,52 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file completionCounter.cxx + * @author rdb + * @date 2025-01-24 + */ + +#include "completionCounter.h" + +/** + * Called when a token is completed before then() is called. + */ +void CompletionCounter:: +initial_callback(Completable::Data *data_ptr, bool success) { + CounterData &data = *(CounterData *)data_ptr; + auto prev_count = data._counter.fetch_add((success ? 0 : 0x10000) - 1, std::memory_order_release); + if ((prev_count & 0xffff) == 1) { + // We're done early. + auto prev_callback = data._function.exchange(nullptr, std::memory_order_acq_rel); + nassertv(prev_callback != nullptr); + + // Someone called then() in the meantime. Call the new callback. The + // refcount will drop below 0 when that's called but they are designed to + // handle that. + if (prev_callback != &initial_callback) { + prev_callback(data_ptr, success && (prev_count & ~0xffff) == 0); + } + } +} + +/** + * Called when a token is completed after this object is destroyed without + * then() being called. + */ +void CompletionCounter:: +abandon_callback(Completable::Data *data_ptr, bool success) { + CounterData &data = *(CounterData *)data_ptr; + auto prev_count = data._counter.fetch_sub(1, std::memory_order_relaxed); + if ((prev_count & 0xffff) <= 1) { + // Done. + auto prev_callback = data._function.exchange(nullptr, std::memory_order_relaxed); + nassertv(prev_callback != nullptr); + nassertv(prev_callback == &abandon_callback); + delete &data; + } +} diff --git a/panda/src/putil/completionCounter.h b/panda/src/putil/completionCounter.h new file mode 100644 index 0000000000..dbb0e2dcfb --- /dev/null +++ b/panda/src/putil/completionCounter.h @@ -0,0 +1,58 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file completionCounter.h + * @author rdb + * @date 2025-01-22 + */ + +#ifndef COMPLETIONCOUNTER_H +#define COMPLETIONCOUNTER_H + +#include "pandabase.h" +#include "completionToken.h" + +#include + +/** + * Shared counter that generates "completion tokens" incrementing a counter, + * which will decrement the counter once they are finished. After the tokens + * are handed out, a callback may be registered using then(), which will be + * called as soon as the last token is done. + */ +class EXPCL_PANDA_PUTIL CompletionCounter { +public: + constexpr CompletionCounter() = default; + CompletionCounter(const CompletionCounter ©) = delete; + + INLINE ~CompletionCounter(); + + INLINE CompletionToken make_token(); + + template + INLINE void then(Callable callable) &&; + +private: + static void initial_callback(Completable::Data *data, bool success); + static void abandon_callback(Completable::Data *data, bool success); + +protected: + struct CounterData : public Completable::Data { + // Least significant half is counter, most significant half is error count + patomic_signed_lock_free _counter { 0 }; + + // Just raise this if the static_assert fires (or limit the size of your + // lambda captures). + alignas(std::max_align_t) unsigned char _storage[64]; + }; + CounterData *_data = nullptr; +}; + +#include "completionCounter.I" + +#endif diff --git a/panda/src/putil/completionToken.I b/panda/src/putil/completionToken.I new file mode 100644 index 0000000000..aef06a7d4e --- /dev/null +++ b/panda/src/putil/completionToken.I @@ -0,0 +1,42 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file completionToken.I + * @author rdb + * @date 2025-01-22 + */ + +#ifndef CPPPARSER +/** + * Creates a token that calls the given callback when it's done, passing it + * true on success and false on failure or abandonment. + */ +template +INLINE CompletionToken:: +CompletionToken(Callable callback) { + // Main difference over a Completable is that this will always call the + // callback, even on failure, so that cleanup can be done. + _callback._data = new Completable::LambdaData(std::move(callback), [](Completable::Data *data, bool success) { + Completable::LambdaData *self = (Completable::LambdaData *)data; + std::move(self->_lambda)(success); + delete self; + }); +} +#endif + +/** + * + */ +INLINE void CompletionToken:: +complete(bool success) { + Completable::Data *data = _callback._data; + if (data != nullptr) { + _callback._data = nullptr; + data->_function.load(std::memory_order_relaxed)(data, success); + } +} diff --git a/panda/src/putil/completionToken.h b/panda/src/putil/completionToken.h new file mode 100644 index 0000000000..b73f4fe376 --- /dev/null +++ b/panda/src/putil/completionToken.h @@ -0,0 +1,56 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file completionToken.h + * @author rdb + * @date 2025-01-22 + */ + +#ifndef COMPLETIONTOKEN_H +#define COMPLETIONTOKEN_H + +#include "pandabase.h" +#include "pnotify.h" +#include "completable.h" + +/** + * A completion token can be created from a callback, future or + * CompletionCounter and can be passed into an asynchronous operation in order + * to receive a signal when it is done. + * + * The asynchronous operation should call complete() on it when it is done, + * with a boolean value indicating success or failure. If the token is + * destroyed prematurely, it is treated as if it called complete(false). + * + * This should be preferred over passing an AsyncFuture into a method since + * a CompletionToken provides both more flexibility in use (due to accepting + * an arbitrary callback) and more safety (since the RAII semantics guarantees + * that the callback is never silently dropped). + * + * The token may only be moved, not copied. + */ +class EXPCL_PANDA_PUTIL CompletionToken { +public: + constexpr CompletionToken() = default; + +#ifndef CPPPARSER + template + INLINE CompletionToken(Callable callback); +#endif + + void complete(bool success); + +protected: + Completable _callback; + + friend class CompletionCounter; +}; + +#include "completionToken.I" + +#endif diff --git a/panda/src/putil/config_putil.cxx b/panda/src/putil/config_putil.cxx index 9849cd5b84..c9e8e4b9b1 100644 --- a/panda/src/putil/config_putil.cxx +++ b/panda/src/putil/config_putil.cxx @@ -212,6 +212,7 @@ init_libputil() { FactoryParam::init_type(); Namable::init_type(); NodeCachedReferenceCount::init_type(); + ParamBytes::init_type("ParamBytes"); ParamMatrix3d::init_type("ParamMatrix3d"); ParamMatrix3f::init_type("ParamMatrix3f"); ParamMatrix4d::init_type("ParamMatrix4d"); diff --git a/panda/src/putil/p3putil_composite1.cxx b/panda/src/putil/p3putil_composite1.cxx index f78459eff7..c464d7708c 100644 --- a/panda/src/putil/p3putil_composite1.cxx +++ b/panda/src/putil/p3putil_composite1.cxx @@ -17,6 +17,7 @@ #include "callbackObject.cxx" #include "clockObject.cxx" #include "colorSpace.cxx" +#include "completionCounter.cxx" #include "config_putil.cxx" #include "configurable.cxx" #include "copyOnWriteObject.cxx" diff --git a/panda/src/putil/paramValue.cxx b/panda/src/putil/paramValue.cxx index 5ffcf2807c..88add8a9c0 100644 --- a/panda/src/putil/paramValue.cxx +++ b/panda/src/putil/paramValue.cxx @@ -16,6 +16,7 @@ template class ParamValue; template class ParamValue; +template class ParamValue; template class ParamValue; template class ParamValue; diff --git a/panda/src/putil/paramValue.h b/panda/src/putil/paramValue.h index ea3b8fcae5..e6f66ba8dd 100644 --- a/panda/src/putil/paramValue.h +++ b/panda/src/putil/paramValue.h @@ -23,6 +23,7 @@ #include "bamReader.h" #include "bamWriter.h" #include "luse.h" +#include "vector_uchar.h" /** * A non-template base class of ParamValue (below), which serves mainly to @@ -153,6 +154,7 @@ private: EXPORT_TEMPLATE_CLASS(EXPCL_PANDA_PUTIL, EXPTP_PANDA_PUTIL, ParamValue); EXPORT_TEMPLATE_CLASS(EXPCL_PANDA_PUTIL, EXPTP_PANDA_PUTIL, ParamValue); +EXPORT_TEMPLATE_CLASS(EXPCL_PANDA_PUTIL, EXPTP_PANDA_PUTIL, ParamValue); EXPORT_TEMPLATE_CLASS(EXPCL_PANDA_PUTIL, EXPTP_PANDA_PUTIL, ParamValue); EXPORT_TEMPLATE_CLASS(EXPCL_PANDA_PUTIL, EXPTP_PANDA_PUTIL, ParamValue); @@ -174,6 +176,7 @@ EXPORT_TEMPLATE_CLASS(EXPCL_PANDA_PUTIL, EXPTP_PANDA_PUTIL, ParamValue ParamString; typedef ParamValue ParamWstring; +typedef ParamValue ParamBytes; typedef ParamValue ParamVecBase2d; typedef ParamValue ParamVecBase2f; diff --git a/panda/src/putil/pythonCallbackObject.cxx b/panda/src/putil/pythonCallbackObject.cxx index bd70b13641..8607273a6a 100644 --- a/panda/src/putil/pythonCallbackObject.cxx +++ b/panda/src/putil/pythonCallbackObject.cxx @@ -58,7 +58,18 @@ PythonCallbackObject(PyObject *function) { */ PythonCallbackObject:: ~PythonCallbackObject() { + // This may be called from the cull or draw thread, so we have to be sure we + // own the GIL. +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); +#endif + Py_DECREF(_function); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyGILState_Release(gstate); +#endif } /** @@ -118,16 +129,13 @@ void PythonCallbackObject:: do_python_callback(CallbackData *cbdata) { nassertv(cbdata != nullptr); - // Wrap the cbdata up in a Python object, then put it in a tuple, for the - // argument list. - PyObject *pycbdata = - DTool_CreatePyInstanceTyped(cbdata, Dtool_TypedObject, - false, false, cbdata->get_type_index()); - PyObject *args = Py_BuildValue("(O)", pycbdata); - Py_DECREF(pycbdata); + // Wrap the cbdata up in a Python object. + PyObject *args[2]; + args[1] = DTool_CreatePyInstanceTyped(cbdata, Dtool_TypedObject, + false, false, cbdata->get_type_index()); - PyObject *result = PythonThread::call_python_func(_function, args); - Py_DECREF(args); + PyObject *result = PythonThread::call_python_func(_function, args + 1, 1 | PY_VECTORCALL_ARGUMENTS_OFFSET); + Py_DECREF(args[1]); if (result == nullptr) { if (PyErr_Occurred() != PyExc_SystemExit) { diff --git a/panda/src/putil/sparseArray.cxx b/panda/src/putil/sparseArray.cxx index fdad9246a1..e475dbd24c 100644 --- a/panda/src/putil/sparseArray.cxx +++ b/panda/src/putil/sparseArray.cxx @@ -588,6 +588,10 @@ do_intersection(const SparseArray &other) { do_remove_range(other._subranges[i]._end, other._subranges[i + 1]._begin); } + if (_subranges.empty()) { + return; + } + int my_end = (*(_subranges.begin() + _subranges.size() - 1))._end; int other_end = (*(other._subranges.begin() + other._subranges.size() - 1))._end; do_remove_range(other_end, my_end); diff --git a/panda/src/text/textProperties.cxx b/panda/src/text/textProperties.cxx index 1cbc8ddb9a..a61ba92b96 100644 --- a/panda/src/text/textProperties.cxx +++ b/panda/src/text/textProperties.cxx @@ -489,4 +489,8 @@ load_default_font() { #endif // HAVE_FREETYPE #endif // COMPILE_IN_DEFAULT_FONT + + if (_default_font != nullptr && !_default_font->has_name()) { + _default_font->set_name("default_font"); + } } diff --git a/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx b/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx index 9c221846eb..54dd73fb15 100644 --- a/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx +++ b/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx @@ -43,7 +43,10 @@ TinySDLGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, _pitch = 0; update_pixel_factor(); - add_input_device(GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse")); + PT(GraphicsWindowInputDevice) device = + GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); + add_input_device(device); + _input = device.p(); } /** @@ -166,35 +169,35 @@ process_events() { switch(evt.type) { case SDL_KEYDOWN: if (evt.key.keysym.unicode) { - _input_devices[0]->keystroke(evt.key.keysym.unicode); + _input->keystroke(evt.key.keysym.unicode); } button = get_keyboard_button(evt.key.keysym.sym); if (button != ButtonHandle::none()) { - _input_devices[0]->button_down(button); + _input->button_down(button); } break; case SDL_KEYUP: button = get_keyboard_button(evt.key.keysym.sym); if (button != ButtonHandle::none()) { - _input_devices[0]->button_up(button); + _input->button_up(button); } break; case SDL_MOUSEBUTTONDOWN: button = get_mouse_button(evt.button.button); - _input_devices[0]->set_pointer_in_window(evt.button.x, evt.button.y); - _input_devices[0]->button_down(button); + _input->set_pointer_in_window(evt.button.x, evt.button.y); + _input->button_down(button); break; case SDL_MOUSEBUTTONUP: button = get_mouse_button(evt.button.button); - _input_devices[0]->set_pointer_in_window(evt.button.x, evt.button.y); - _input_devices[0]->button_up(button); + _input->set_pointer_in_window(evt.button.x, evt.button.y); + _input->button_up(button); break; case SDL_MOUSEMOTION: - _input_devices[0]->set_pointer_in_window(evt.motion.x, evt.motion.y); + _input->set_pointer_in_window(evt.motion.x, evt.motion.y); break; case SDL_VIDEORESIZE: diff --git a/panda/src/tinydisplay/tinySDLGraphicsWindow.h b/panda/src/tinydisplay/tinySDLGraphicsWindow.h index 8073663ce5..dc3695a6c0 100644 --- a/panda/src/tinydisplay/tinySDLGraphicsWindow.h +++ b/panda/src/tinydisplay/tinySDLGraphicsWindow.h @@ -63,6 +63,8 @@ private: unsigned int _flags; unsigned int _pitch; + GraphicsWindowInputDevice *_input; + public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/webgldisplay/webGLGraphicsWindow.cxx b/panda/src/webgldisplay/webGLGraphicsWindow.cxx index 700f89c1b8..da094d1cb5 100644 --- a/panda/src/webgldisplay/webGLGraphicsWindow.cxx +++ b/panda/src/webgldisplay/webGLGraphicsWindow.cxx @@ -20,6 +20,13 @@ #include "pointerData.h" #include +#ifndef CPPPARSER +extern "C" void EMSCRIPTEN_KEEPALIVE +_canvas_resized(WebGLGraphicsWindow *window, double width, double height) { + window->on_resize(width, height); +} +#endif + TypeHandle WebGLGraphicsWindow::_type_handle; /** @@ -171,8 +178,9 @@ set_properties_now(WindowProperties &properties) { const char *target = "#canvas"; if (properties.has_size()) { - emscripten_set_canvas_element_size(target, properties.get_x_size(), properties.get_y_size()); _properties.set_size(properties.get_size()); + emscripten_set_canvas_element_size(target, properties.get_x_size(), properties.get_y_size()); + emscripten_set_element_css_size(target, properties.get_x_size(), properties.get_y_size()); properties.clear_size(); set_size_and_recalc(_properties.get_x_size(), _properties.get_y_size()); throw_event(get_window_event(), this); @@ -238,6 +246,16 @@ set_properties_now(WindowProperties &properties) { // though, we can't hide the cursor. properties.clear_cursor_hidden(); } + + if (properties.get_foreground()) { + EM_ASM_({ + var canvas = document.getElementById('canvas'); + if (canvas) { + canvas.focus(); + } + }); + properties.clear_foreground(); + } } /** @@ -250,6 +268,14 @@ close_window() { _gsg.clear(); } + EM_ASM({ + var canvas = document.getElementById('canvas'); + if (canvas && canvas._p3d_resizeObserver) { + canvas._p3d_resizeObserver.disconnect(); + delete canvas._p3d_resizeObserver; + } + }); + // Clear the assigned callbacks. const char *target = "#canvas"; emscripten_set_fullscreenchange_callback(target, nullptr, false, nullptr); @@ -307,11 +333,22 @@ open_window() { return false; } - if (_properties.has_size() && _properties.get_size() != LVecBase2i(1, 1)) { + // For now, always use the size specified in the CSS, except when fixed size + // has been specified, or the CSS has no size + double css_width, css_height; + emscripten_get_element_css_size(target, &css_width, &css_height); + + if (_properties.has_size() && (_properties.get_fixed_size() || css_width == 0.0 || css_height == 0.0)) { emscripten_set_canvas_element_size(target, _properties.get_x_size(), _properties.get_y_size()); + emscripten_set_element_css_size(target, _properties.get_x_size(), _properties.get_y_size()); } else { - int width, height; - emscripten_get_canvas_element_size(target, &width, &height); + int width = (int)css_width; + int height = (int)css_height; + if (width == 0 || height == 0) { + emscripten_get_canvas_element_size(target, &width, &height); + } else { + emscripten_set_canvas_element_size(target, width, height); + } _properties.set_size(width, height); EmscriptenFullscreenChangeEvent event; @@ -369,9 +406,63 @@ open_window() { emscripten_set_wheel_callback(target, user_data, false, &on_wheel_event); + // Emscripten has no working resize handler for the canvas element, we'll + // have to create our own + EM_ASM({ + var canvas = document.getElementById('canvas'); + if (canvas) { + var observer = new ResizeObserver(function(entries) { + var entry = entries[0]; + if (entry) { + var width = entry.contentRect.width; + var height = entry.contentRect.height; + if (width != 0 && height != 0) { + __canvas_resized($0, entry.contentRect.width, entry.contentRect.height); + } + } + }); + observer.observe(canvas); + if (canvas._p3d_resizeObserver) { + canvas._p3d_resizeObserver.disconnect(); + } + canvas._p3d_resizeObserver = observer; + } + }, this); + + if (!_properties.has_foreground() || _properties.get_foreground()) { + _properties.set_foreground(EM_ASM_INT({ + var canvas = document.getElementById('canvas'); + if (canvas) { + canvas.focus(); + + return document.activeElement === canvas; + } else { + return false; + } + })); + } + return true; } +/** + * + */ +void WebGLGraphicsWindow:: +on_resize(double width, double height) { + if (_properties.get_fixed_size()) { + return; + } + + const char *target = "#canvas"; + + LVecBase2i size((int)width, (int)height); + emscripten_set_canvas_element_size(target, size[0], size[1]); + if (_properties.get_size() != size) { + system_changed_properties(WindowProperties::size(size)); + } +} + /** * */ @@ -472,9 +563,9 @@ on_keyboard_event(int type, const EmscriptenKeyboardEvent *event, void *user_dat // it does the right thing. We grab the first unicode code point. // Unfortunately, this doesn't seem to handle dead keys on Firefox. int keycode = 0; - EM_ASM_({ - stringToUTF32(String.fromCharCode($0), $1, 4); - }, event->charCode, &keycode); + keycode = EM_ASM_INT({ + return String.fromCharCode($0).codePointAt(0); + }, event->charCode); if (keycode != 0) { device->keystroke(keycode); diff --git a/panda/src/webgldisplay/webGLGraphicsWindow.h b/panda/src/webgldisplay/webGLGraphicsWindow.h index 16535cc730..3b905f8543 100644 --- a/panda/src/webgldisplay/webGLGraphicsWindow.h +++ b/panda/src/webgldisplay/webGLGraphicsWindow.h @@ -48,6 +48,9 @@ protected: virtual void close_window(); virtual bool open_window(); +public: + void on_resize(double width, double height); + private: static EM_BOOL on_fullscreen_event(int type, const EmscriptenFullscreenChangeEvent *event, void *user_data); static EM_BOOL on_pointerlock_event(int type, const EmscriptenPointerlockChangeEvent *event, void *user_data); diff --git a/panda/src/wgldisplay/wglGraphicsBuffer.cxx b/panda/src/wgldisplay/wglGraphicsBuffer.cxx index 61edb9643c..3721dacb16 100644 --- a/panda/src/wgldisplay/wglGraphicsBuffer.cxx +++ b/panda/src/wgldisplay/wglGraphicsBuffer.cxx @@ -301,9 +301,10 @@ open_buffer() { // If the old gsg has the wrong pixel format, create a new one that shares // with the old gsg. DCAST_INTO_R(wglgsg, _gsg, false); - if ((!wglgsg->get_fb_properties().subsumes(_fb_properties))|| - (!wglgsg->get_fb_properties().is_single_buffered())|| - (!wglgsg->pfnum_supports_pbuffer())) { + if (wglgsg->get_engine() != _engine || + !wglgsg->get_fb_properties().subsumes(_fb_properties) || + !wglgsg->get_fb_properties().is_single_buffered() || + !wglgsg->pfnum_supports_pbuffer()) { wglgsg = new wglGraphicsStateGuardian(_engine, _pipe, wglgsg); wglgsg->choose_pixel_format(_fb_properties, true); _gsg = wglgsg; diff --git a/panda/src/wgldisplay/wglGraphicsPipe.cxx b/panda/src/wgldisplay/wglGraphicsPipe.cxx index 4186c35a35..2f4d1392af 100644 --- a/panda/src/wgldisplay/wglGraphicsPipe.cxx +++ b/panda/src/wgldisplay/wglGraphicsPipe.cxx @@ -148,6 +148,9 @@ make_output(const std::string &name, (flags & (BF_require_parasite | BF_require_window)) != 0) { return nullptr; } + if (host->get_engine() != engine) { + return nullptr; + } // Early failure - if we are sure that this buffer WONT meet specs, we can // bail out early. if ((flags & BF_fb_props_optional) == 0) { diff --git a/panda/src/wgldisplay/wglGraphicsWindow.cxx b/panda/src/wgldisplay/wglGraphicsWindow.cxx index c79ea4eb9f..536b5ad2f6 100644 --- a/panda/src/wgldisplay/wglGraphicsWindow.cxx +++ b/panda/src/wgldisplay/wglGraphicsWindow.cxx @@ -209,7 +209,8 @@ open_window() { // If the old gsg has the wrong pixel format, create a new one that shares // with the old gsg. DCAST_INTO_R(wglgsg, _gsg, false); - if (!wglgsg->get_fb_properties().subsumes(_fb_properties)) { + if (wglgsg->get_engine() != _engine || + !wglgsg->get_fb_properties().subsumes(_fb_properties)) { wglgsg = new wglGraphicsStateGuardian(_engine, _pipe, wglgsg); wglgsg->choose_pixel_format(_fb_properties, false); _gsg = wglgsg; diff --git a/panda/src/windisplay/config_windisplay.cxx b/panda/src/windisplay/config_windisplay.cxx index 6d655d905b..0e3090257c 100644 --- a/panda/src/windisplay/config_windisplay.cxx +++ b/panda/src/windisplay/config_windisplay.cxx @@ -27,17 +27,6 @@ ConfigureFn(config_windisplay) { init_libwindisplay(); } -ConfigVariableBool responsive_minimized_fullscreen_window -("responsive-minimized-fullscreen-window",false); - -ConfigVariableBool hold_keys_across_windows -("hold-keys-across-windows", false, - PRC_DESC("Set this true to remember the current state of the keyboard while " - "the window focus is lost, or false to pretend the user is not " - "holding down any keys while the window focus is lost. In either " - "case it should accurately restore the correct keyboard state when " - "the window focus is regained.")); - ConfigVariableBool do_vidmemsize_check ("do-vidmemsize-check", true, PRC_DESC("if true, use ddraw's GetAvailVidMem to fail if driver says " @@ -76,10 +65,6 @@ ConfigVariableBool dpi_window_resize "panel. Only available in Windows 8.1 and later, and requires " "dpi-aware to be set as well.")); -ConfigVariableBool swapbuffer_framelock -("swapbuffer-framelock", false, - PRC_DESC("Set this true to enable HW swapbuffer frame-lock on 3dlabs cards")); - ConfigVariableBool paste_emit_keystrokes ("paste-emit-keystrokes", true, PRC_DESC("Handle paste events (Ctrl-V) as separate keystroke events for each " diff --git a/panda/src/windisplay/config_windisplay.h b/panda/src/windisplay/config_windisplay.h index 377b272472..9ea1e23cc3 100644 --- a/panda/src/windisplay/config_windisplay.h +++ b/panda/src/windisplay/config_windisplay.h @@ -21,8 +21,6 @@ NotifyCategoryDecl(windisplay, EXPCL_PANDAWIN, EXPTP_PANDAWIN); -extern ConfigVariableBool responsive_minimized_fullscreen_window; -extern ConfigVariableBool hold_keys_across_windows; extern ConfigVariableBool do_vidmemsize_check; extern ConfigVariableBool auto_cpu_data; extern ConfigVariableBool ime_hide; @@ -32,8 +30,6 @@ extern ConfigVariableBool dpi_window_resize; extern ConfigVariableBool paste_emit_keystrokes; extern ConfigVariableBool disable_message_loop; -extern EXPCL_PANDAWIN ConfigVariableBool swapbuffer_framelock; - extern EXPCL_PANDAWIN void init_libwindisplay(); #endif diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index 4c111fad5c..266722aa1b 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -1515,6 +1515,26 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { WindowProperties properties; switch (msg) { + case WM_GETMINMAXINFO: + { + MINMAXINFO* minmaxinfo = (MINMAXINFO*)lparam; + + int minClientWidth = 1; // Minimum client area width + int minClientHeight = 1; // Minimum client area height + + // Adjust window for non-client area + RECT rect = { 0, 0, minClientWidth, minClientHeight }; + AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), FALSE); + + // Calculate final size + int minWidth = rect.right - rect.left; + int minHeight = rect.bottom - rect.top; + + // Set the minimum track size in MINMAXINFO + minmaxinfo->ptMinTrackSize.x = minWidth; // Minimum window width + minmaxinfo->ptMinTrackSize.y = minHeight; // Minimum window height + } + break; case WM_MOUSEMOVE: if (!_tracking_mouse_leaving) { // need to re-call TrackMouseEvent every time mouse re-enters window @@ -2044,7 +2064,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // the actual value passed to WM_CHAR seems to be poorly defined. Now we // are using RegisterClassW etc., which means WM_CHAR is absolutely // supposed to be utf-16. - if (!_ime_open) { + if (!_ime_open || !ime_aware) { _input->keystroke(wparam); } break; diff --git a/panda/src/x11display/config_x11display.cxx b/panda/src/x11display/config_x11display.cxx index 1744a8a556..36b33a4fb1 100644 --- a/panda/src/x11display/config_x11display.cxx +++ b/panda/src/x11display/config_x11display.cxx @@ -45,6 +45,27 @@ ConfigVariableBool x_init_threads PRC_DESC("Set this true to ask Panda3D to call XInitThreads() upon loading " "the display module, which may help with some threading issues.")); +ConfigVariableBool x_support_xcursor +("x-support-xcursor", true, + PRC_DESC("Set this false if you wish to disable loading of the XCursor " + "library, which is used for setting custom cursor icons.")); + +ConfigVariableBool x_support_xinput2 +("x-support-xinput2", true, + PRC_DESC("Set this false if you wish to disable loading of the XInput2 " + "library, which is used for raw mouse events as well as for relative " + "mouse events if the xf86dga extension is not supported.")); + +ConfigVariableBool x_support_xf86dga +("x-support-xf86dga", true, + PRC_DESC("Set this false if you wish to disable loading of the xf86dga " + "extension, which is used for relative mouse mode.")); + +ConfigVariableBool x_support_xrandr +("x-support-xrandr", true, + PRC_DESC("Set this false if you wish to disable loading of the Xrandr " + "extension, which is used for changing screen resolutions.")); + ConfigVariableInt x_wheel_up_button ("x-wheel-up-button", 4, PRC_DESC("This is the mouse button index of the wheel_up event: which " @@ -91,6 +112,10 @@ ConfigVariableBool x_send_startup_notification "lets the window manager know that an application has launched, so " "that it no longer needs to display a spinning mouse cursor.")); +ConfigVariableBool x_detectable_auto_repeat +("x-detectable-auto-repeat", true, + PRC_DESC("Set this true to enable detectable auto-repeat for keyboard input.")); + /** * Initializes the library. This must be called at least once before any of * the functions or classes in this library can be used. Normally it will be diff --git a/panda/src/x11display/config_x11display.h b/panda/src/x11display/config_x11display.h index d45ff1cd66..2dcc23942e 100644 --- a/panda/src/x11display/config_x11display.h +++ b/panda/src/x11display/config_x11display.h @@ -27,6 +27,10 @@ extern EXPCL_PANDAX11 void init_libx11display(); extern ConfigVariableString display_cfg; extern ConfigVariableBool x_error_abort; extern ConfigVariableBool x_init_threads; +extern ConfigVariableBool x_support_xcursor; +extern ConfigVariableBool x_support_xinput2; +extern ConfigVariableBool x_support_xf86dga; +extern ConfigVariableBool x_support_xrandr; extern ConfigVariableInt x_wheel_up_button; extern ConfigVariableInt x_wheel_down_button; @@ -37,5 +41,6 @@ extern ConfigVariableInt x_cursor_size; extern ConfigVariableString x_wm_class_name; extern ConfigVariableString x_wm_class; extern ConfigVariableBool x_send_startup_notification; +extern ConfigVariableBool x_detectable_auto_repeat; #endif diff --git a/panda/src/x11display/x11GraphicsPipe.cxx b/panda/src/x11display/x11GraphicsPipe.cxx index dda30ff5c1..136ebaee6d 100644 --- a/panda/src/x11display/x11GraphicsPipe.cxx +++ b/panda/src/x11display/x11GraphicsPipe.cxx @@ -121,15 +121,22 @@ x11GraphicsPipe(const std::string &display) : _is_valid = true; // Dynamically load the xf86dga extension. - void *xf86dga = dlopen("libXxf86dga.so.1", RTLD_NOW | RTLD_LOCAL); - if (xf86dga != nullptr) { + if (!x_support_xf86dga) { + _XF86DGADirectVideo = nullptr; + if (x11display_cat.is_debug()) { + x11display_cat.debug() + << "XFree86-DGA support is disabled" + << "; relative mouse mode may not work.\n"; + } + } else if (void *xf86dga = dlopen("libXxf86dga.so.1", RTLD_NOW | RTLD_LOCAL)) { pfn_XF86DGAQueryVersion _XF86DGAQueryVersion = (pfn_XF86DGAQueryVersion)dlsym(xf86dga, "XF86DGAQueryVersion"); _XF86DGADirectVideo = (pfn_XF86DGADirectVideo)dlsym(xf86dga, "XF86DGADirectVideo"); int major_ver, minor_ver; if (_XF86DGAQueryVersion == nullptr || _XF86DGADirectVideo == nullptr) { x11display_cat.warning() - << "libXxf86dga.so.1 does not provide required functions; relative mouse mode may not work.\n"; + << "libXxf86dga.so.1 does not provide required functions" + << "; relative mouse mode may not work.\n"; } else if (!_XF86DGAQueryVersion(_display, &major_ver, &minor_ver)) { _XF86DGADirectVideo = nullptr; @@ -138,13 +145,19 @@ x11GraphicsPipe(const std::string &display) : _XF86DGADirectVideo = nullptr; if (x11display_cat.is_debug()) { x11display_cat.debug() - << "cannot dlopen libXxf86dga.so.1; relative mouse mode may not work.\n"; + << "cannot dlopen libXxf86dga.so.1" + << "; relative mouse mode may not work.\n"; } } // Dynamically load the XCursor extension. - void *xcursor = dlopen("libXcursor.so.1", RTLD_NOW | RTLD_LOCAL); - if (xcursor != nullptr) { + if (!x_support_xcursor) { + _xcursor_size = -1; + if (x11display_cat.is_debug()) { + x11display_cat.debug() + << "XCursor support is disabled; cursor changing will not work.\n"; + } + } else if (void *xcursor = dlopen("libXcursor.so.1", RTLD_NOW | RTLD_LOCAL)) { pfn_XcursorGetDefaultSize _XcursorGetDefaultSize = (pfn_XcursorGetDefaultSize)dlsym(xcursor, "XcursorGetDefaultSize"); _XcursorXcFileLoadImages = (pfn_XcursorXcFileLoadImages)dlsym(xcursor, "XcursorXcFileLoadImages"); _XcursorImagesLoadCursor = (pfn_XcursorImagesLoadCursor)dlsym(xcursor, "XcursorImagesLoadCursor"); @@ -175,8 +188,14 @@ x11GraphicsPipe(const std::string &display) : } // Dynamically load the XRandr extension. - void *xrandr = dlopen("libXrandr.so.2", RTLD_NOW | RTLD_LOCAL); - if (xrandr != nullptr) { + if (!x_support_xrandr) { + _have_xrandr = false; + if (x11display_cat.is_debug()) { + x11display_cat.debug() + << "XRandR support is disabled; resolution setting will not work.\n"; + } + } + else if (void *xrandr = dlopen("libXrandr.so.2", RTLD_NOW | RTLD_LOCAL)) { pfn_XRRQueryExtension _XRRQueryExtension = (pfn_XRRQueryExtension)dlsym(xrandr, "XRRQueryExtension"); pfn_XRRQueryVersion _XRRQueryVersion = (pfn_XRRQueryVersion)dlsym(xrandr, "XRRQueryVersion"); @@ -238,7 +257,8 @@ x11GraphicsPipe(const std::string &display) : // Dynamically load the XInput2 extension. int ev, err; - if (XQueryExtension(_display, "XInputExtension", &_xi_opcode, &ev, &err)) { + if (x_support_xinput2 && + XQueryExtension(_display, "XInputExtension", &_xi_opcode, &ev, &err)) { void *xi = dlopen("libXi.so.6", RTLD_NOW | RTLD_LOCAL); if (xi != nullptr) { pfn_XIQueryVersion _XIQueryVersion = (pfn_XIQueryVersion)dlsym(xi, "XIQueryVersion"); @@ -272,6 +292,8 @@ x11GraphicsPipe(const std::string &display) : << "cannot dlopen libXi.so.1; relative mouse mode will not work.\n"; } } + } else { + _XISelectEvents = nullptr; } // Use Xrandr to fill in the supported resolution list. diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 49c1e19b13..b3363ff110 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -97,8 +97,7 @@ x11GraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, int flags, GraphicsStateGuardian *gsg, GraphicsOutput *host) : - GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host) -{ + GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host) { x11GraphicsPipe *x11_pipe; DCAST_INTO_V(x11_pipe, _pipe); _display = x11_pipe->get_display(); @@ -126,6 +125,8 @@ x11GraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); add_input_device(device); _input = device; + + enable_detectable_auto_repeat(); } /** @@ -633,10 +634,10 @@ process_events() { Atom type; int format; unsigned long nItem, bytesAfter; - unsigned char *new_window_properties = NULL; + unsigned char *new_window_properties = nullptr; // gather all properties from the active dispplay and window XGetWindowProperty(_display, _xwindow, wmState, 0, LONG_MAX, false, AnyPropertyType, &type, &format, &nItem, &bytesAfter, &new_window_properties); - if (nItem > 0) { + if (nItem > 0 && new_window_properties != nullptr) { x11GraphicsPipe *x11_pipe; DCAST_INTO_V(x11_pipe, _pipe); // run through all found items @@ -649,6 +650,7 @@ process_events() { is_maximized = true; } } + XFree(new_window_properties); } // Debug entry @@ -1531,6 +1533,9 @@ set_wm_properties(const WindowProperties &properties, bool already_mapped) { XSetWMProperties(_display, _xwindow, window_name_p, window_name_p, nullptr, 0, size_hints_p, wm_hints_p, class_hints_p); + if (window_name_p != nullptr) { + XFree(window_name.value); + } if (size_hints_p != nullptr) { XFree(size_hints_p); } @@ -2745,3 +2750,22 @@ xim_preedit_done(XIC ic, XPointer client_data, XPointer call_data) { x11GraphicsWindow *window = (x11GraphicsWindow *)client_data; window->handle_preedit_done(); } + +/** + * Enables detectable auto-repeat if supported by the X server. + */ +void x11GraphicsWindow:: +enable_detectable_auto_repeat() { + if (!x_detectable_auto_repeat) { + return; + } + + Bool supported; + if (XkbSetDetectableAutoRepeat(_display, True, &supported)) { + x11display_cat.info() << "Detectable auto-repeat enabled.\n"; + } else if (!supported) { + x11display_cat.warning() << "Detectable auto-repeat is not supported by the X server.\n"; + } else { + x11display_cat.error() << "Failed to set detectable auto-repeat.\n"; + } +} diff --git a/panda/src/x11display/x11GraphicsWindow.h b/panda/src/x11display/x11GraphicsWindow.h index e147f907a5..4ed95c8a11 100644 --- a/panda/src/x11display/x11GraphicsWindow.h +++ b/panda/src/x11display/x11GraphicsWindow.h @@ -46,6 +46,8 @@ public: INLINE X11_Window get_xwindow() const; + void enable_detectable_auto_repeat(); + protected: virtual void close_window(); virtual bool open_window(); diff --git a/pandatool/src/assimp/assimpLoader.cxx b/pandatool/src/assimp/assimpLoader.cxx index 80b94a7ca3..592520df0e 100644 --- a/pandatool/src/assimp/assimpLoader.cxx +++ b/pandatool/src/assimp/assimpLoader.cxx @@ -58,6 +58,10 @@ #define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR "$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0 #endif +#ifndef AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE +#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 0 +#endif + #ifndef AI_MATKEY_GLTF_ALPHAMODE #define AI_MATKEY_GLTF_ALPHAMODE "$mat.gltf.alphaMode", 0, 0 #endif @@ -325,11 +329,13 @@ load_texture(size_t index) { /** * Converts an aiMaterial into a RenderState. + * The dummy argument exists so we can pass a MATKEY macro into this function + * instead of just a texture type. */ void AssimpLoader:: -load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype, - TextureStage::Mode mode, CPT(TextureAttrib) &tattr, - CPT(TexMatrixAttrib) &tmattr) { +load_texture_stage(const aiMaterial &mat, TextureStage::Mode mode, + CPT(TextureAttrib) &tattr, CPT(TexMatrixAttrib) &tmattr, + const aiTextureType &ttype, unsigned int dummy) { aiString path; aiTextureMapping mapping; unsigned int uvindex; @@ -596,20 +602,33 @@ load_material(size_t index) { // And let's not forget the textures! CPT(TextureAttrib) tattr = DCAST(TextureAttrib, TextureAttrib::make()); CPT(TexMatrixAttrib) tmattr; - load_texture_stage(mat, aiTextureType_DIFFUSE, TextureStage::M_modulate, tattr, tmattr); + load_texture_stage(mat, TextureStage::M_modulate, tattr, tmattr, aiTextureType_DIFFUSE); - // Check for an ORM map, from the glTF/OBJ importer. glTF also puts it in the - // LIGHTMAP slot, despite only having the lightmap in the red channel, so we - // have to ignore it. - if (mat.GetTextureCount(aiTextureType_UNKNOWN) > 0) { - load_texture_stage(mat, aiTextureType_UNKNOWN, TextureStage::M_selector, tattr, tmattr); + // Check for an ORM map, from the glTF/OBJ importer. glTF also erroneously + // puts an occlusion texture in the LIGHTMAP slot. + aiString roughness_path, occlusion_path; + aiTextureMapping roughness_mapping, occlusion_mapping; + unsigned int roughness_uv, occlusion_uv; + if (AI_SUCCESS == mat.GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &roughness_path, &roughness_mapping, &roughness_uv)) { + if (AI_SUCCESS == mat.GetTexture(aiTextureType_LIGHTMAP, 0, &occlusion_path, &occlusion_mapping, &occlusion_uv)) { + if (roughness_path == occlusion_path && + roughness_mapping == occlusion_mapping && + roughness_uv == occlusion_uv) { + load_texture_stage(mat, TextureStage::M_occlusion_metallic_roughness, tattr, tmattr, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); + } else { + load_texture_stage(mat, TextureStage::M_metallic_roughness, tattr, tmattr, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); + load_texture_stage(mat, TextureStage::M_occlusion, tattr, tmattr, aiTextureType_LIGHTMAP); + } + } else { + load_texture_stage(mat, TextureStage::M_metallic_roughness, tattr, tmattr, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); + } } else { - load_texture_stage(mat, aiTextureType_LIGHTMAP, TextureStage::M_modulate, tattr, tmattr); + load_texture_stage(mat, TextureStage::M_modulate, tattr, tmattr, aiTextureType_LIGHTMAP); } - load_texture_stage(mat, aiTextureType_NORMALS, TextureStage::M_normal, tattr, tmattr); - load_texture_stage(mat, aiTextureType_EMISSIVE, TextureStage::M_emission, tattr, tmattr); - load_texture_stage(mat, aiTextureType_HEIGHT, TextureStage::M_height, tattr, tmattr); + load_texture_stage(mat, TextureStage::M_normal, tattr, tmattr, aiTextureType_NORMALS); + load_texture_stage(mat, TextureStage::M_emission, tattr, tmattr, aiTextureType_EMISSIVE); + load_texture_stage(mat, TextureStage::M_height, tattr, tmattr, aiTextureType_HEIGHT); if (tattr->get_num_on_stages() > 0) { state = state->add_attrib(tattr); } diff --git a/pandatool/src/assimp/assimpLoader.h b/pandatool/src/assimp/assimpLoader.h index e323cb1e94..e36e146a23 100644 --- a/pandatool/src/assimp/assimpLoader.h +++ b/pandatool/src/assimp/assimpLoader.h @@ -78,9 +78,9 @@ private: const aiNode *find_node(const aiNode &root, const aiString &name); void load_texture(size_t index); - void load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype, - TextureStage::Mode mode, CPT(TextureAttrib) &tattr, - CPT(TexMatrixAttrib) &tmattr); + void load_texture_stage(const aiMaterial &mat, TextureStage::Mode mode, + CPT(TextureAttrib) &tattr, CPT(TexMatrixAttrib) &tmattr, + const aiTextureType &ttype, unsigned int dummy=0); void load_material(size_t index); void create_joint(Character *character, CharacterJointBundle *bundle, PartGroup *parent, const aiNode &node); void create_anim_channel(const aiAnimation &anim, AnimBundle *bundle, AnimGroup *parent, const aiNode &node); diff --git a/pandatool/src/converter/CMakeLists.txt b/pandatool/src/converter/CMakeLists.txt index a793b718ac..eccae3a774 100644 --- a/pandatool/src/converter/CMakeLists.txt +++ b/pandatool/src/converter/CMakeLists.txt @@ -21,3 +21,7 @@ install(TARGETS p3converter INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d ARCHIVE COMPONENT ToolsDevel) install(FILES ${P3CONVERTER_HEADERS} COMPONENT ToolsDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d) + +add_executable(txo-converter txoConverter.cxx txoConverter.h) +target_link_libraries(txo-converter p3progbase panda) +install(TARGETS txo-converter EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/converter/txoConverter.cxx b/pandatool/src/converter/txoConverter.cxx new file mode 100644 index 0000000000..a85a504d77 --- /dev/null +++ b/pandatool/src/converter/txoConverter.cxx @@ -0,0 +1,148 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file txoConverter.cxx + * @author RegDogg + * @date 2025-11-10 + */ + +#include "txoConverter.h" +#include "pnmFileTypeRegistry.h" +#include "pnmFileType.h" + +/** + * + */ +TxoConverter:: +TxoConverter() : WithOutputFile(true, false, true) { + set_program_brief("convert various image formats to .txo file format"); + set_program_description + ("This program reads the image date from the input file and " + "outputs a txo files, suitable for viewing in Panda."); + + clear_runlines(); + add_runline("[opts] input output"); + add_runline("[opts] -o output input"); + + add_option + ("o", "filename", 0, + "Specify the filename to which the resulting .bam file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file.", + &TxoConverter::dispatch_filename, &_got_output_filename, &_output_filename); + + add_option + ("alpha", "filename", 0, + "Apply an RGB alpha file for image types such as JPEG.", + &TxoConverter::dispatch_filename, &_got_rgb_filename, &_rgb_filename); +} + +/** + * + */ +void TxoConverter:: +run() { + nassertv(has_output_filename()); + Filename fullpath = Filename(_image_filename.get_fullpath()); + + nout << "Reading " << fullpath << "...\n"; + + PNMFileType *type = PNMFileTypeRegistry::get_global_ptr()->get_type_from_extension(fullpath); + if (type == nullptr) { + nout << "Cannot determine type of image file " << fullpath << ".\n"; + nout << "Currently supported image types:\n"; + PNMFileTypeRegistry::get_global_ptr()->write(nout, 2); + nout << "\n"; + return; + } + + PT(Texture) tex = new Texture("original image"); + + if (_got_rgb_filename) { + PNMFileType *type = PNMFileTypeRegistry::get_global_ptr()->get_type_from_extension(_rgb_filename); + if (type == nullptr) { + nout << "Image file type '" << _rgb_filename << "' is unknown.\n"; + return; + } + tex->read(_image_filename, _rgb_filename.get_fullpath(), 0, 0, LoaderOptions()); + } + else { + tex->read(_image_filename, LoaderOptions()); + } + tex->get_ram_image(); + + convert_txo(tex); + +} + + +/** + * If the indicated Texture was not already loaded from a txo file, writes it + * to a txo file and updates the Texture object to reference the new file. + */ +void TxoConverter:: +convert_txo(Texture *tex) { + if (!tex->get_loaded_from_txo()) { + + Filename output = get_output_filename(); + output.make_dir(); + + if (tex->write(output)) { + nout << "Writing " << output << "...\n"; + tex->set_loaded_from_txo(); + tex->set_fullpath(output); + tex->clear_alpha_fullpath(); + + tex->set_filename(output); + tex->clear_alpha_filename(); + } + } +} + +/** + * + */ +bool TxoConverter:: +handle_args(ProgramBase::Args &args) { + if (!_got_output_filename && args.size() > 1) { + _got_output_filename = true; + _output_filename = Filename::from_os_specific(args.back()); + args.pop_back(); + } + + if (!_got_output_filename) { + nout << "You must specify an output path."; + return false; + } + + if ((_output_filename.get_extension() != "txo")) { + nout << "Output filename " << _output_filename << " must end in .txo\n"; + return false; + } + + if (args.empty()) { + nout << "You must specify the image file to read on the command line.\n"; + return false; + } + + if (args.size() != 1) { + nout << "Specify only one image on the command line.\n"; + return false; + } + + _image_filename = Filename::from_os_specific(args[0]); + + return true; +} + +int main(int argc, char *argv[]) { + TxoConverter prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} \ No newline at end of file diff --git a/pandatool/src/converter/txoConverter.h b/pandatool/src/converter/txoConverter.h new file mode 100644 index 0000000000..9291e2e5b2 --- /dev/null +++ b/pandatool/src/converter/txoConverter.h @@ -0,0 +1,43 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file txoConverter.h + * @author RegDogg + * @date 2025-11-10 + */ + +#ifndef TXOCONVERTER_H +#define TXOCONVERTER_H + +#include "pandatoolbase.h" +#include "programBase.h" +#include "filename.h" +#include "withOutputFile.h" +#include "textureAttrib.h" + +/** + * + */ +class TxoConverter : public ProgramBase, public WithOutputFile { +public: + TxoConverter(); + + void run(); + + protected: + virtual bool handle_args(Args &args); + + private: + void convert_txo(Texture *tex); + + Filename _image_filename; + bool _got_rgb_filename; + Filename _rgb_filename; +}; + +#endif diff --git a/pandatool/src/deploy-stub/android_main.cxx b/pandatool/src/deploy-stub/android_main.cxx index d1e140c5f3..d0b0e7d3b5 100644 --- a/pandatool/src/deploy-stub/android_main.cxx +++ b/pandatool/src/deploy-stub/android_main.cxx @@ -27,11 +27,17 @@ #include #include -#include - // Leave room for future expansion. #define MAX_NUM_POINTERS 24 +/* Stored in the flags field of the blobinfo structure below. */ +enum Flags { + F_log_append = 1, + F_log_filename_strftime = 2, + F_keep_docstrings = 4, + F_python_verbose = 8, +}; + // Define an exposed symbol where we store the offset to the module data. extern "C" { __attribute__((__visibility__("default"), used)) @@ -50,8 +56,8 @@ extern "C" { } blobinfo = {(uint64_t)-1}; } -// Defined in android_log.c -extern "C" PyObject *PyInit_android_log(); +// Defined in android_support.cxx +extern "C" PyObject *PyInit_android_support(); /** * Maps the binary blob at the given memory address to memory, and returns the @@ -61,11 +67,16 @@ static void *map_blob(const char *path, off_t offset, size_t size) { FILE *runtime = fopen(path, "rb"); assert(runtime != NULL); - void *blob = (void *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), offset); + // Align the offset down to the page size boundary. + size_t page_size = getpagesize(); + off_t aligned = offset & ~((off_t)page_size - 1); + size_t delta = (size_t)(offset - aligned); + + void *blob = (void *)mmap(0, size + delta, PROT_READ, MAP_PRIVATE, fileno(runtime), aligned); assert(blob != MAP_FAILED); fclose(runtime); - return blob; + return (void *)((uintptr_t)blob + delta); } /** @@ -118,7 +129,8 @@ void android_main(struct android_app *app) { PT(Thread) current_thread = Thread::bind_thread(thread_name, "android_app"); android_cat.info() - << "New native activity started on " << *current_thread << "\n"; + << "New native activity started on " << *current_thread + << " (" << current_thread << ")\n"; // Fetch the data directory. jmethodID get_appinfo = env->GetMethodID(activity_class, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); @@ -180,10 +192,29 @@ void android_main(struct android_app *app) { android_cat.info() << "Path to native library: " << lib_path << "\n"; ExecutionEnvironment::set_binary_name(lib_path); - // Map the blob to memory - void *blob = map_blob(lib_path, (off_t)blobinfo.blob_offset, (size_t)blobinfo.blob_size); + // Nowadays we store the blob in a raw resource. + methodID = env->GetMethodID(activity_class, "mapBlobFromResource", "(J)J"); + assert(methodID != nullptr); + void *blob = (void *)env->CallLongMethod(activity->clazz, methodID, blobinfo.blob_offset); + + if (blob == nullptr) { + // Try the old method otherwise. + blob = map_blob(lib_path, (off_t)blobinfo.blob_offset, (size_t)blobinfo.blob_size); + } env->ReleaseStringUTFChars(lib_path_jstr, lib_path); - assert(blob != NULL); + assert(blob != nullptr); + + // This should always be aligned, but just in case... + void *blob_copy = nullptr; + if ((((uintptr_t)blob) & (sizeof(void *) - 1)) != 0) { + android_cat.warning() + << "Blob with offset " << blobinfo.blob_offset + << " and size " << blobinfo.blob_size << " is not aligned!\n"; + + blob_copy = malloc(blobinfo.blob_size); + memcpy(blob_copy, blob, blobinfo.blob_size); + blob = blob_copy; + } assert(blobinfo.num_pointers <= MAX_NUM_POINTERS); for (uint32_t i = 0; i < blobinfo.num_pointers; ++i) { @@ -224,17 +255,35 @@ void android_main(struct android_app *app) { get_model_path().append_directory(asset_dir); // Offset the pointers in the module table using the base mmap address. - struct _frozen *moddef = (struct _frozen *)blobinfo.pointers[0]; - while (moddef->name) { - moddef->name = (char *)((uintptr_t)moddef->name + (uintptr_t)blob); - if (moddef->code != nullptr) { - moddef->code = (unsigned char *)((uintptr_t)moddef->code + (uintptr_t)blob); + // We did a read-only mmap, so we have to create a copy of this table. + // First count how many modules there are. + struct _frozen *src_moddef = (struct _frozen *)blobinfo.pointers[0]; + struct _frozen *dst_moddef; + struct _frozen *new_modules = nullptr; + if (blob_copy != nullptr) { + // We already made a copy, so we can just write to this. + dst_moddef = src_moddef; + } else { + size_t num_modules = 0; + while (src_moddef->name) { + num_modules++; + src_moddef++; } - //__android_log_print(ANDROID_LOG_DEBUG, "Panda3D", "MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size); - moddef++; - } - PyImport_FrozenModules = (struct _frozen *)blobinfo.pointers[0]; + new_modules = (struct _frozen *)malloc(sizeof(struct _frozen) * (num_modules + 1)); + memcpy(new_modules, blobinfo.pointers[0], sizeof(struct _frozen) * (num_modules + 1)); + dst_moddef = new_modules; + } + PyImport_FrozenModules = dst_moddef; + + while (dst_moddef->name) { + dst_moddef->name = (char *)((uintptr_t)dst_moddef->name + (uintptr_t)blob); + if (dst_moddef->code != nullptr) { + dst_moddef->code = (unsigned char *)((uintptr_t)dst_moddef->code + (uintptr_t)blob); + } + //__android_log_print(ANDROID_LOG_DEBUG, "Panda3D", "MOD: %s %p %d\n", dst_moddef->name, (void*)dst_moddef->code, dst_moddef->size); + dst_moddef++; + } PyPreConfig preconfig; PyPreConfig_InitIsolatedConfig(&preconfig); @@ -246,10 +295,10 @@ void android_main(struct android_app *app) { return; } - // Register the android_log module. - if (PyImport_AppendInittab("android_log", &PyInit_android_log) < 0) { + // Register the android_support module. + if (PyImport_AppendInittab("android_support", &PyInit_android_support) < 0) { android_cat.error() - << "Failed to register android_log module.\n"; + << "Failed to register android_support module.\n"; env->ReleaseStringUTFChars(libdir_jstr, libdir); return; } @@ -260,9 +309,14 @@ void android_main(struct android_app *app) { config.buffered_stdio = 0; config.configure_c_stdio = 0; config.write_bytecode = 0; + config.module_search_paths_set = 1; // Leave sys.path empty PyConfig_SetBytesString(&config, &config.platlibdir, libdir); env->ReleaseStringUTFChars(libdir_jstr, libdir); + if (blobinfo.flags & F_python_verbose) { + config.verbose = 1; + } + status = Py_InitializeFromConfig(&config); PyConfig_Clear(&config); if (PyStatus_Exception(status)) { @@ -299,10 +353,9 @@ void android_main(struct android_app *app) { // We still need to keep an event loop going until Android gives us leave // to end the process. while (!app->destroyRequested) { - int looper_id; - struct android_poll_source *source; - auto result = ALooper_pollOnce(-1, &looper_id, nullptr, (void **)&source); - if (looper_id == LOOPER_ID_MAIN) { + struct android_poll_source *source = nullptr; + int ident = ALooper_pollOnce(-1, nullptr, nullptr, (void **)&source); + if (ident == LOOPER_ID_MAIN) { int8_t cmd = android_app_read_cmd(app); android_app_pre_exec_cmd(app, cmd); android_app_post_exec_cmd(app, cmd); @@ -328,7 +381,14 @@ void android_main(struct android_app *app) { cp_mgr->delete_explicit_page(page); } + PyImport_FrozenModules = nullptr; + if (new_modules != nullptr) { + free(new_modules); + } unmap_blob(blob); + if (blob_copy != nullptr) { + free(blob_copy); + } // Detach the thread before exiting. activity->vm->DetachCurrentThread(); diff --git a/pandatool/src/deploy-stub/android_log.c b/pandatool/src/deploy-stub/android_support.cxx similarity index 50% rename from pandatool/src/deploy-stub/android_log.c rename to pandatool/src/deploy-stub/android_support.cxx index 83b6090111..bb13166871 100644 --- a/pandatool/src/deploy-stub/android_log.c +++ b/pandatool/src/deploy-stub/android_support.cxx @@ -6,23 +6,26 @@ * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * - * @file android_log.c + * @file android_support.cxx * @author rdb * @date 2021-12-10 */ +#include +#include "android_native_app_glue.h" +#include "config_android.h" + #undef _POSIX_C_SOURCE #undef _XOPEN_SOURCE #define PY_SSIZE_T_CLEAN 1 #include "Python.h" -#include /** * Writes a message to the Android log. */ static PyObject * -_py_write(PyObject *self, PyObject *args) { +_py_log_write(PyObject *self, PyObject *args) { int prio; char *tag; char *text; @@ -33,14 +36,32 @@ _py_write(PyObject *self, PyObject *args) { return NULL; } +/** + * Returns the path to a library, if it can be found. + */ +static PyObject * +_py_find_library(PyObject *self, PyObject *args) { + char *lib; + if (PyArg_ParseTuple(args, "s", &lib)) { + Filename result = android_find_library(panda_android_app->activity, lib); + if (!result.empty()) { + return PyUnicode_FromStringAndSize(result.c_str(), (Py_ssize_t)result.length()); + } else { + Py_RETURN_NONE; + } + } + return NULL; +} + static PyMethodDef python_simple_funcs[] = { - { "write", &_py_write, METH_VARARGS }, + { "log_write", &_py_log_write, METH_VARARGS }, + { "find_library", &_py_find_library, METH_VARARGS }, { NULL, NULL } }; -static struct PyModuleDef android_log_module = { +static struct PyModuleDef android_support_module = { PyModuleDef_HEAD_INIT, - "android_log", + "android_support", NULL, -1, python_simple_funcs, @@ -48,6 +69,6 @@ static struct PyModuleDef android_log_module = { }; __attribute__((visibility("default"))) -PyObject *PyInit_android_log() { - return PyModule_Create(&android_log_module); +extern "C" PyObject *PyInit_android_support() { + return PyModule_Create(&android_support_module); } diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 6cd2c2803c..97c3e9d672 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -35,6 +36,7 @@ enum Flags { F_log_append = 1, F_log_filename_strftime = 2, F_keep_docstrings = 4, + F_python_verbose = 8, }; /* Define an exposed symbol where we store the offset to the module data. */ @@ -416,6 +418,10 @@ int Py_FrozenMain(int argc, char **argv) } #endif + if (blobinfo.flags & F_python_verbose) { + Py_VerboseFlag = 1; + } + #ifndef NDEBUG if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0') inspect = 1; diff --git a/pandatool/src/gtk-stats/gtkStatsTimeline.cxx b/pandatool/src/gtk-stats/gtkStatsTimeline.cxx index 5010b2dabc..bffdd32e57 100644 --- a/pandatool/src/gtk-stats/gtkStatsTimeline.cxx +++ b/pandatool/src/gtk-stats/gtkStatsTimeline.cxx @@ -54,7 +54,8 @@ GtkStatsTimeline(GtkStatsMonitor *monitor) : G_CALLBACK(thread_area_draw_callback), this); // Listen for mouse wheel and keyboard events. - gtk_widget_add_events(_graph_window, GDK_SCROLL_MASK | + gtk_widget_add_events(_graph_window, GDK_SMOOTH_SCROLL_MASK | + GDK_SCROLL_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK); gtk_widget_set_can_focus(_graph_window, TRUE); @@ -65,6 +66,25 @@ GtkStatsTimeline(GtkStatsMonitor *monitor) : g_signal_connect(G_OBJECT(_graph_window), "key_release_event", G_CALLBACK(key_release_callback), this); + // Set up trackpad pinch and swipe gestures. + _zoom_gesture = gtk_gesture_zoom_new(_graph_window); + g_signal_connect(_zoom_gesture, "begin", + G_CALLBACK(+[](GtkGestureZoom *gesture, GdkEventSequence *sequence, gpointer data) { + GtkStatsTimeline *self = (GtkStatsTimeline *)data; + self->_zoom_scale = self->get_horizontal_scale(); + }), this); + + g_signal_connect(_zoom_gesture, "scale-changed", + G_CALLBACK((+[](GtkGestureZoom *gesture, gdouble scale, gpointer data) { + GtkStatsTimeline *self = (GtkStatsTimeline *)data; + gdouble x, y; + if (gtk_gesture_get_point(GTK_GESTURE(gesture), NULL, &x, &y)) { + int graph_x = (int)(x * self->_cr_scale); + self->zoom_by(log(scale) * 0.8, self->pixel_to_timestamp(graph_x)); + self->start_animation(); + } + })), this); + int min_height = 0; if (!_threads.empty()) { double height = row_to_pixel(get_num_rows()) + _pixel_scale * 2.5; @@ -100,6 +120,7 @@ GtkStatsTimeline(GtkStatsMonitor *monitor) : GtkStatsTimeline:: ~GtkStatsTimeline() { cairo_pattern_destroy(_grid_pattern); + g_object_unref(_zoom_gesture); } /** @@ -627,6 +648,16 @@ handle_scroll(int graph_x, int graph_y, double dx, double dy, bool ctrl_held) { return handled; } +/** + * + */ +gboolean GtkStatsTimeline:: +handle_zoom(int graph_x, int graph_y, double scale) { + zoom_to(get_horizontal_scale() / scale, pixel_to_timestamp(graph_x)); + start_animation(); + return TRUE; +} + /** * */ diff --git a/pandatool/src/gtk-stats/gtkStatsTimeline.h b/pandatool/src/gtk-stats/gtkStatsTimeline.h index 37f3f38167..b354712d66 100644 --- a/pandatool/src/gtk-stats/gtkStatsTimeline.h +++ b/pandatool/src/gtk-stats/gtkStatsTimeline.h @@ -63,6 +63,7 @@ protected: virtual gboolean handle_leave(); gboolean handle_scroll(int graph_x, int graph_y, double dx, double dy, bool ctrl_held); + gboolean handle_zoom(int graph_x, int graph_y, double scale); gboolean handle_key(bool pressed, guint val, guint16 hw_code); private: @@ -92,6 +93,9 @@ private: int _highlighted_x = 0; int _scroll = 0; ColorBar _popup_bar; + + double _zoom_scale = 1.0; + GtkGesture *_zoom_gesture = nullptr; }; #endif diff --git a/pandatool/src/mac-stats/macStatsStripChart.mm b/pandatool/src/mac-stats/macStatsStripChart.mm index 068c41ea37..a43d6f4d73 100644 --- a/pandatool/src/mac-stats/macStatsStripChart.mm +++ b/pandatool/src/mac-stats/macStatsStripChart.mm @@ -23,8 +23,8 @@ static const int default_strip_chart_width = 400; static const int default_strip_chart_height = 200; -static const int minimum_strip_chart_sidebar_width = 116; -static const int default_strip_chart_sidebar_width = 116; +static const int minimum_strip_chart_sidebar_width = 128; +static const int default_strip_chart_sidebar_width = 128; /** * diff --git a/pandatool/src/pstatserver/pStatTimeline.cxx b/pandatool/src/pstatserver/pStatTimeline.cxx index 01f9020f37..d8d0fdfbc2 100644 --- a/pandatool/src/pstatserver/pStatTimeline.cxx +++ b/pandatool/src/pstatserver/pStatTimeline.cxx @@ -196,6 +196,7 @@ update_bars(int thread_index, int frame_number) { // pair pvector > stack; + bool had_children = false; double frame_start = frame_data.get_start() + _clock_skew; double prev = frame_start; @@ -275,6 +276,7 @@ update_bars(int thread_index, int frame_number) { thread_row._rows.resize(stack.size()); changed_num_rows = true; } + had_children = false; } } else if (!stack.empty() && stack.back().first == collector_index) { @@ -288,6 +290,7 @@ update_bars(int thread_index, int frame_number) { while (!stack.empty() && stack.back().first < 0) { stack.pop_back(); } + had_children = true; } else { // Unlikely case: ending a collector before a "child" has ended. @@ -349,6 +352,33 @@ update_bars(int thread_index, int frame_number) { frame_start, time, collector_index, thread_index, frame_number, true, false}); } + else if (i > 0 && !had_children) { + // Figure out if the currently active collector could actually be + // slotted higher. This prevents the staircase effect where + // overlapping collectors will cause the number of rows to continue + // to grow at every overlap. We only do this if the current + // collector has not had children yet, that'd be too confusing. + int j = stack.size() - 3; + while (j > 0) { + auto &item = stack[j]; + if (item.first >= 0) { + // Nope, can't do. + break; + } + // Yes, does this row have enough space? + Row &row = thread_row._rows[j]; + if (row.empty() || row.back()._end < stack.back().second) { + // It does. + item = std::move(stack.back()); + stack.pop_back(); + while (!stack.empty() && stack.back().first < 0) { + stack.pop_back(); + } + break; + } + --j; + } + } } } diff --git a/pandatool/src/xfile/windowsGuid.I b/pandatool/src/xfile/windowsGuid.I index 5d0ce22046..434182d70a 100644 --- a/pandatool/src/xfile/windowsGuid.I +++ b/pandatool/src/xfile/windowsGuid.I @@ -11,14 +11,6 @@ * @date 2004-10-03 */ -/** - * - */ -INLINE WindowsGuid:: -WindowsGuid() { - memset(this, 0, sizeof(WindowsGuid)); -} - /** * */ @@ -42,22 +34,6 @@ WindowsGuid(unsigned long data1, { } -/** - * - */ -INLINE WindowsGuid:: -WindowsGuid(const WindowsGuid ©) { - (*this) = copy; -} - -/** - * - */ -INLINE void WindowsGuid:: -operator = (const WindowsGuid ©) { - memcpy(this, ©, sizeof(WindowsGuid)); -} - /** * */ diff --git a/pandatool/src/xfile/windowsGuid.h b/pandatool/src/xfile/windowsGuid.h index ea79d0be5a..8c0e306993 100644 --- a/pandatool/src/xfile/windowsGuid.h +++ b/pandatool/src/xfile/windowsGuid.h @@ -25,14 +25,12 @@ */ class WindowsGuid { public: - INLINE WindowsGuid(); + constexpr WindowsGuid() = default; INLINE WindowsGuid(unsigned long data1, unsigned short data2, unsigned short data3, unsigned char b1, unsigned char b2, unsigned char b3, unsigned char b4, unsigned char b5, unsigned char b6, unsigned char b7, unsigned char b8); - INLINE WindowsGuid(const WindowsGuid ©); - INLINE void operator = (const WindowsGuid ©); INLINE bool operator == (const WindowsGuid &other) const; INLINE bool operator != (const WindowsGuid &other) const; @@ -45,10 +43,10 @@ public: void output(std::ostream &out) const; private: - unsigned long _data1; - unsigned short _data2; - unsigned short _data3; - unsigned char _b1, _b2, _b3, _b4, _b5, _b6, _b7, _b8; + unsigned long _data1 = 0; + unsigned short _data2 = 0; + unsigned short _data3 = 0; + unsigned char _b1 = 0, _b2 = 0, _b3 = 0, _b4 = 0, _b5 = 0, _b6 = 0, _b7 = 0, _b8 = 0; }; INLINE std::ostream &operator << (std::ostream &out, const WindowsGuid &guid); diff --git a/samples/procedural-cube/main.py b/samples/procedural-cube/main.py index 2f21cd9a0f..266afc4205 100755 --- a/samples/procedural-cube/main.py +++ b/samples/procedural-cube/main.py @@ -17,7 +17,6 @@ from panda3d.core import GeomVertexFormat, GeomVertexData from panda3d.core import Geom, GeomTriangles, GeomVertexWriter from panda3d.core import Texture, GeomNode from panda3d.core import PerspectiveLens -from panda3d.core import CardMaker from panda3d.core import Light, Spotlight from panda3d.core import TextNode from panda3d.core import LVector3 diff --git a/setup.cfg b/setup.cfg index c6e478a40e..4fdd24c9e8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,6 +20,7 @@ classifiers = Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.13 + Programming Language :: Python :: 3.14 Programming Language :: Python :: Implementation :: CPython Topic :: Games/Entertainment Topic :: Multimedia diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000000..f3c56a38bc --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,20 @@ +if(NOT INTERROGATE_PYTHON_INTERFACE) + return() +endif() + +add_executable(run_pytest main.c) +target_link_libraries(run_pytest panda) +target_link_libraries(run_pytest Python::Python) + +# Note, we do NOT test for pytest before adding this test. If the user doesn't +# have pytest, we'd like for the tests to fail. + +# In the Coverage configuration, we also require pytest-cov + +# To prevent an empty argument from being passed in in non-Coverage builds, +# which will sometimes cause pytest to read all tests in the current directory, +# substitute a nonsensical argument that will be ignored instead +add_test(NAME pytest COMMAND run_pytest tests $,--cov=.,--ignore=nonexistent> + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}") + +set_tests_properties(pytest PROPERTIES ENVIRONMENT "PYTHONPATH=${PANDA_OUTPUT_DIR}") diff --git a/tests/audio/openclose_with_comments.flac b/tests/audio/openclose_with_comments.flac new file mode 100644 index 0000000000..a32b3836e8 Binary files /dev/null and b/tests/audio/openclose_with_comments.flac differ diff --git a/tests/audio/openclose_with_comments.mp3 b/tests/audio/openclose_with_comments.mp3 new file mode 100644 index 0000000000..f594029c9b Binary files /dev/null and b/tests/audio/openclose_with_comments.mp3 differ diff --git a/tests/audio/openclose_with_comments.ogg b/tests/audio/openclose_with_comments.ogg new file mode 100644 index 0000000000..479d94d7db Binary files /dev/null and b/tests/audio/openclose_with_comments.ogg differ diff --git a/tests/audio/openclose_with_comments.opus b/tests/audio/openclose_with_comments.opus new file mode 100644 index 0000000000..ec3bd78b48 Binary files /dev/null and b/tests/audio/openclose_with_comments.opus differ diff --git a/tests/audio/test_loading.py b/tests/audio/test_loading.py index ea3bdb2cd9..46032e7351 100644 --- a/tests/audio/test_loading.py +++ b/tests/audio/test_loading.py @@ -1,5 +1,36 @@ +import os + import pytest +from panda3d.core import Filename + def test_missing_file(audiomgr): - sound = audiomgr.get_sound('/not/a/valid/file.ogg') - assert str(sound).startswith('NullAudioSound') + sound = audiomgr.get_sound("/not/a/valid/file.ogg") + assert str(sound).startswith("NullAudioSound") + + +@pytest.mark.parametrize("extension", ["ogg", "opus", "mp3", "flac"]) +def test_comments(audiomgr, extension): + if "openal" not in str(audiomgr).lower(): + # NULL audio manager (as well as fmod) don't support comment reading + pytest.skip("Comment reading is only supported on OpenAL") + # ogg should be loaded with libvorbis, opus with libopus, mp3 with ffmpeg + # we cannot test this though because after loading the loader information is gone + sound_path = os.path.join( + os.path.dirname(__file__), f"openclose_with_comments.{extension}" + ) + sound_path = Filename.from_os_specific(sound_path) + sound = audiomgr.get_sound(sound_path) + if extension == "mp3": # FFMPEG encodes/decodes tags differently + tags = ["artist", "comment", "genre"] + else: + tags = ["ARTIST", "COMMENTS", "GENRE"] + assert sorted(sound.raw_comments) == [ + f"{tags[0]}=Example Artist", + f"{tags[1]}=This is an example OGG comment", + f"{tags[2]}=Blues", + ] + assert sound.comments[tags[-1]] == "Blues" + assert sound.get_comment(tags[0]) == "Example Artist" + assert sound.get_comment("DOES NOT EXIST") == "" + assert sound.has_comment(tags[0]) diff --git a/tests/collide/test_collision_node.py b/tests/collide/test_collision_node.py new file mode 100644 index 0000000000..20197e956f --- /dev/null +++ b/tests/collide/test_collision_node.py @@ -0,0 +1,46 @@ +from panda3d.core import * +import sys + + +class CustomObject: + pass + + +def test_collision_node_owner(): + owner = CustomObject() + initial_rc = sys.getrefcount(owner) + + node = CollisionNode("node") + assert node.owner is None + + node.owner = owner + assert sys.getrefcount(owner) == initial_rc + assert node.owner is owner + + node.owner = owner + assert sys.getrefcount(owner) == initial_rc + assert node.owner is owner + + node.owner = None + assert sys.getrefcount(owner) == initial_rc + assert node.owner is None + + del node + assert sys.getrefcount(owner) == initial_rc + + # Assign owner and then delete node + node = CollisionNode("node") + assert sys.getrefcount(owner) == initial_rc + node.owner = owner + assert sys.getrefcount(owner) == initial_rc + del node + assert sys.getrefcount(owner) == initial_rc + + # Delete owner and see what happens to the node + node = CollisionNode("node") + assert sys.getrefcount(owner) == initial_rc + node.owner = owner + assert sys.getrefcount(owner) == initial_rc + del owner + assert node.owner is None + diff --git a/tests/conftest.py b/tests/conftest.py index 4735ef99bf..8b703fe2ad 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import os import sys import pytest from panda3d import core @@ -99,3 +100,125 @@ def gsg(graphics_pipe, graphics_engine): if buffer is not None: graphics_engine.remove_window(buffer) + + +def pytest_configure(config): + """Initialize the failure collector.""" + config._github_summary_failures = [] + config._github_summary_counts = { + "passed": 0, + "failed": 0, + "skipped": 0, + "xfailed": 0, + } + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_makereport(item, call): + """Collect information about test outcomes.""" + outcome = yield + report = outcome.get_result() + + if report.when == "call": + counts = item.config._github_summary_counts + + if report.passed: + if hasattr(report, "wasxfail"): + # xpass - passed but was expected to fail (treat as passed for now) + counts["passed"] += 1 + else: + counts["passed"] += 1 + elif report.failed: + counts["failed"] += 1 + failure_info = { + "nodeid": report.nodeid, + "location": report.location, + "longrepr": str(report.longrepr) if report.longrepr else None, + "sections": report.sections, + "duration": report.duration, + } + item.config._github_summary_failures.append(failure_info) + elif report.skipped: + if hasattr(report, "wasxfail"): + counts["xfailed"] += 1 + else: + counts["skipped"] += 1 + + elif report.when == "setup" and report.skipped: + # Handle skip during setup (e.g., skipif, skip markers) + item.config._github_summary_counts["skipped"] += 1 + + +def pytest_sessionfinish(session, exitstatus): + """Write GitHub step summary if GITHUB_STEP_SUMMARY is set.""" + summary_path = os.environ.get("GITHUB_STEP_SUMMARY") + if not summary_path: + return + + failures = session.config._github_summary_failures + counts = session.config._github_summary_counts + + lines = [] + + # Build status parts for the summary line + status_parts = [] + if counts["failed"]: + status_parts.append(f"**{counts['failed']}** failed") + if counts["passed"]: + status_parts.append(f"**{counts['passed']}** passed") + if counts["skipped"]: + status_parts.append(f"**{counts['skipped']}** skipped") + if counts["xfailed"]: + status_parts.append(f"**{counts['xfailed']}** xfailed") + + total = sum(counts.values()) + status_line = ", ".join(status_parts) + f" (**{total}** total)\n" + + # Header with overall status + if counts["failed"] == 0: + lines.append(f"### :white_check_mark: All tests passed (Python {sys.version_info.major}.{sys.version_info.minor})\n\n") + else: + lines.append(f"### :x: Test failures (Python {sys.version_info.major}.{sys.version_info.minor})\n\n") + + lines.append(status_line) + lines.append("\n") + + # Each failure in a collapsible section + for failure in failures: + nodeid = failure["nodeid"] + duration = failure["duration"] + + lines.append(f"
\n{_escape_html(nodeid)} ({duration:.2f}s)\n") + + # Traceback / longrepr + if failure["longrepr"]: + lines.append("\n#### Traceback\n") + lines.append("```python\n") + lines.append(failure["longrepr"]) + if not failure["longrepr"].endswith("\n"): + lines.append("\n") + lines.append("```\n") + + # Captured output sections (stdout, stderr, log, etc.) + for section_name, section_content in failure["sections"]: + if section_content.strip(): + lines.append(f"\n#### {_escape_html(section_name)}\n") + lines.append("```\n") + lines.append(section_content) + if not section_content.endswith("\n"): + lines.append("\n") + lines.append("```\n") + + lines.append("\n
\n\n") + + with open(summary_path, "a", encoding="utf-8") as f: + f.writelines(lines) + + +def _escape_html(text: str) -> str: + """Escape HTML special characters for safe rendering in markdown.""" + return ( + text.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + ) diff --git a/tests/display/test_color_buffer.py b/tests/display/test_color_buffer.py index 1450981f37..e2a43991ad 100644 --- a/tests/display/test_color_buffer.py +++ b/tests/display/test_color_buffer.py @@ -367,3 +367,31 @@ def test_color_transparency_no_light(color_region, shader_attrib): ) result = render_color_pixel(color_region, state) assert result.x == pytest.approx(1.0, 0.1) + + +def test_texture_occlusion(color_region): + shader_attrib = core.ShaderAttrib.make_default().set_shader_auto(True) + + tex = core.Texture("occlusion") + tex.set_clear_color((0.5, 1.0, 1.0, 1.0)) + stage = core.TextureStage("occlusion") + stage.set_mode(core.TextureStage.M_occlusion) + texture_attrib = core.TextureAttrib.make().add_on_stage(stage, tex) + + mat = core.Material() + mat.diffuse = (0, 1, 0, 1) + mat.ambient = (0, 0, 1, 1) + material_attrib = core.MaterialAttrib.make(mat) + + alight = core.AmbientLight("ambient") + alight.set_color((0, 0, 1, 1)) + light_attrib = core.LightAttrib.make().add_on_light(core.NodePath(alight)) + + state = core.RenderState.make( + shader_attrib, + material_attrib, + texture_attrib, + light_attrib + ) + result = render_color_pixel(color_region, state) + assert result.z == pytest.approx(0.5, 0.05) diff --git a/tests/display/test_depth_buffer.py b/tests/display/test_depth_buffer.py index cdbaffd370..c33ddbb6f1 100644 --- a/tests/display/test_depth_buffer.py +++ b/tests/display/test_depth_buffer.py @@ -198,12 +198,12 @@ def test_depth_bias(depth_region): # With slope-scaled depth bias (our quad has no slope) state = core.RenderState.make(core.DepthBiasAttrib.make(10, 0)) z = render_depth_pixel(depth_region, 5, near=1, far=10, state=state) - assert z == z_ref + assert z == pytest.approx(z_ref) # Same, but negative state = core.RenderState.make(core.DepthBiasAttrib.make(-10, 0)) z = render_depth_pixel(depth_region, 5, near=1, far=10, state=state) - assert z == z_ref + assert z == pytest.approx(z_ref) def test_depth_offset(depth_region): diff --git a/tests/display/test_glsl_shader.py b/tests/display/test_glsl_shader.py index b2cf64fefe..941e60f061 100644 --- a/tests/display/test_glsl_shader.py +++ b/tests/display/test_glsl_shader.py @@ -299,11 +299,11 @@ def test_glsl_uimage(gsg): def test_glsl_ssbo(gsg): - from struct import pack + from struct import pack, unpack num1 = pack(' 0.2886); - assert(p3d_LightSource[0].shadowViewMatrix[1][1] < 0.2887); - assert(p3d_LightSource[0].shadowViewMatrix[1][2] == 0); - assert(p3d_LightSource[0].shadowViewMatrix[1][3] == 0); - assert(p3d_LightSource[0].shadowViewMatrix[2][0] == -0.5); - assert(p3d_LightSource[0].shadowViewMatrix[2][1] == -0.5); - assert(p3d_LightSource[0].shadowViewMatrix[2][2] > -1.00002); - assert(p3d_LightSource[0].shadowViewMatrix[2][2] < -1.0); - assert(p3d_LightSource[0].shadowViewMatrix[2][3] == -1); + assert(p3d_LightSource[0].shadowViewMatrix[1][0] == 0.5); + assert(p3d_LightSource[0].shadowViewMatrix[1][1] == 0.5); + assert(p3d_LightSource[0].shadowViewMatrix[1][2] > 1.0); + assert(p3d_LightSource[0].shadowViewMatrix[1][2] < 1.00002); + assert(p3d_LightSource[0].shadowViewMatrix[1][3] == 1); + assert(p3d_LightSource[0].shadowViewMatrix[2][0] == 0); + assert(p3d_LightSource[0].shadowViewMatrix[2][1] > 0.2886); + assert(p3d_LightSource[0].shadowViewMatrix[2][1] < 0.2887); + assert(p3d_LightSource[0].shadowViewMatrix[2][2] == 0); + assert(p3d_LightSource[0].shadowViewMatrix[2][3] == 0); assert(p3d_LightSource[0].shadowViewMatrix[3][0] > -16.2736); assert(p3d_LightSource[0].shadowViewMatrix[3][0] < -16.2734); assert(p3d_LightSource[0].shadowViewMatrix[3][1] > -16.8510); diff --git a/tests/event/test_futures.py b/tests/event/test_futures.py index c1bf870c1c..64e0b5de68 100644 --- a/tests/event/test_futures.py +++ b/tests/event/test_futures.py @@ -31,6 +31,49 @@ class MockFuture: return self._result +def check_result(fut, expected): + """Asserts the result of the future is the expected value.""" + + if fut.result() != expected: + return False + + if sys.version_info < (3, 5): + return True + + # Make sure that await also returns the values properly + with pytest.raises(StopIteration) as e: + next(fut.__await__()) + if e.value.value != expected: + return False + + return True + + +def test_future_await_send(): + fut = core.AsyncFuture() + + i = fut.__await__() + assert i.send(None) == fut + assert i.send(None) == fut + assert i.send(None) == fut + assert i.send(None) == fut + + fut.set_result(123) + + with pytest.raises(StopIteration) as e: + i.send(None) + + assert e.value.value == 123 + + +def test_future_await_throw(): + fut = core.AsyncFuture() + + i = fut.__await__() + with pytest.raises(RuntimeError): + i.throw(RuntimeError) + + def test_future_cancelled(): fut = core.AsyncFuture() @@ -133,13 +176,29 @@ def test_future_wait_cancel(): fut.result() -def test_task_cancel(): +def test_task_remove(): task_mgr = core.AsyncTaskManager.get_global_ptr() task = core.PythonTask(lambda task: task.done) task_mgr.add(task) assert not task.done() task_mgr.remove(task) + assert not task.is_alive() + assert task.done() + assert task.cancelled() + + with pytest.raises(CancelledError): + task.result() + + +def test_task_cancel(): + task_mgr = core.AsyncTaskManager.get_global_ptr() + task = core.PythonTask(lambda task: task.done) + task_mgr.add(task) + + assert not task.done() + task.cancel() + assert not task.is_alive() assert task.done() assert task.cancelled() @@ -446,15 +505,20 @@ def test_future_result(): ep = core.EventParameter(0.5) fut = core.AsyncFuture() fut.set_result(ep) - assert fut.result() is ep - assert fut.result() is ep + assert check_result(fut, ep) + assert check_result(fut, ep) # Store TypedObject dg = core.Datagram(b"test") fut = core.AsyncFuture() fut.set_result(dg) - assert fut.result() == dg - assert fut.result() == dg + assert check_result(fut, dg) + assert check_result(fut, dg) + + # Store tuple + fut = core.AsyncFuture() + fut.set_result((1, 2)) + assert check_result(fut, (1, 2)) # Store arbitrary Python object obj = object() @@ -491,7 +555,7 @@ def test_future_gather(): assert gather.done() assert not gather.cancelled() - assert tuple(gather.result()) == (1, 2) + assert check_result(gather, [1, 2]) def test_future_gather_cancel_inner(): diff --git a/tests/express/cert.pem b/tests/express/cert.pem new file mode 100644 index 0000000000..33a3a9f151 --- /dev/null +++ b/tests/express/cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZTCCAk2gAwIBAgIULuBiiqTjDq8eK6pCRMLMTtV5tRUwDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAeFw0yNTEwMTUwOTA3MDhaFw0yNjEwMTUw +OTA3MDhaMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAa +BgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCjCpKGVRGDaAkdb+Fl7NYv6/d3QEp3HDDN0I8Ns5xBPRlvsnUm +uPcv+VZRtgM8DXFyypqmbknHeayWBwcAdvEor3JsRLYpB+buZl48Sr7rOQMbP3xG +da0XYMA3JNW4MSf7VfNbq9tBcXM+JGHZgyEFkmKPxxdwjJC0g90zOBRlTmmOjI1H +Aus1hTQXeCxkbkTIjggxU5e6ptx4EOpaWotLLIWbuFx9rLTcbuQVx4Pwu+DuRcCp +eXsnWcS/wcGQSUF1586zu6oAFBgv8XPmskzOphK0Ei8eDwyfIHxqF/ej0NAJei1Q +hePOh+fX2eLh6kH4sAc2mFtLbnURxwz3b03tAgMBAAGjUzBRMB0GA1UdDgQWBBT7 +KryYClrgtH/oF518izizsMfX1DAfBgNVHSMEGDAWgBT7KryYClrgtH/oF518iziz +sMfX1DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA+JH1el4P8 +ofA5Us4LBJNS82KJY96dSITKE3W7R6IZj0TZPYgE244eeN/wdbu1QsFrXXS7jYYY +npLlVouiC26aMNIrNoRa0rHqJA7shR4+1iT3hawu8zYH55kooOgZwn8mRe1G7XD+ +TrvFYvgjNi5AgISqkmfuXyzcOfSZ7jt/mv7rVSSs9N1ZVrNOAGDnFN1YLAVXD9eh +cH2i0SrtmFYnK+xYvK/eT1EGngtTdsBwD/GGoaPuuGU9sWVswcR9Gp/FgQBB31ZK +Edeyh2IdydBlHRSzpJkhnUtyJBN29+/P3WW27XUYDyfSma+0X7G8qAWsyBG7qWXi +fIU+/gpvLoNC +-----END CERTIFICATE----- diff --git a/tests/express/private.pem b/tests/express/private.pem new file mode 100644 index 0000000000..3f6759b81c --- /dev/null +++ b/tests/express/private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCjCpKGVRGDaAkd +b+Fl7NYv6/d3QEp3HDDN0I8Ns5xBPRlvsnUmuPcv+VZRtgM8DXFyypqmbknHeayW +BwcAdvEor3JsRLYpB+buZl48Sr7rOQMbP3xGda0XYMA3JNW4MSf7VfNbq9tBcXM+ +JGHZgyEFkmKPxxdwjJC0g90zOBRlTmmOjI1HAus1hTQXeCxkbkTIjggxU5e6ptx4 +EOpaWotLLIWbuFx9rLTcbuQVx4Pwu+DuRcCpeXsnWcS/wcGQSUF1586zu6oAFBgv +8XPmskzOphK0Ei8eDwyfIHxqF/ej0NAJei1QhePOh+fX2eLh6kH4sAc2mFtLbnUR +xwz3b03tAgMBAAECggEAR8txCFxPcPkQAnlw3Mw06TdUapvR7q9oQklTpSnxZbz9 +BXWlJt8OYn6+Zw7qT7hvu6fCAAXS0VcgC5SenbLCsTLJBSoguOK060gCuTQE7FnX +p1kGZZSOGxxMqDu9LPXgcEnB9x0vWJsXr0agHAMlOGnkowF9rd8IHaVvc41/VbhD +kH9mWSlX8jSFZnN3pTJUygpA3/m2fBzs+WWacGukF0c3TNgbXpmf2ODxrqCWsf0e +kv7CW1X64g9T/UPoIXCaaAFwM6AzfykEHvty5Yac2WRavTSSxw619vtNGK6Sw7se +SJo9tykxXa1emhEl+AlHkOz4AqPJ+BXZv3IVBiswtQKBgQDlzPKdNPlIQlEsJXrE +3evmOL8ACqnSQG2kof2ouEBE6Z09e4Wm2VA6SBV4soPiVyOcKZp58ZNiU9MIHVl2 +OKvT1AhsKID2cIYjGYvmr7+IpHqqklAMy3K+EtT6oUGu/Js3wlmxG9KJnCtxGwIH +QjizqaUoAZKwj/j5ZWQPxLYdUwKBgQC1oSlTTVvd+tGtEKXyeG5/xTpQpjyRnKWp +bof2Apj6zaWKx6EWAdmLB7uIk4J1rQqCdEVSbUZ+PGcl1flKoSfSWFOtnb0pdU91 +70WS4hz2ABgMuRlOh1xCk0YaoIpR5My+KsoHQngAqChj5rlcLTS2hJTUcQjItSB3 +tuQf8Ug/vwKBgEaA8bKvzQeRaSwdN6Rs3fJdWKRfoSijocAP/y4jkXxTHG3/lsrR +A59N/GByjKoFyCQiU4W0S16wjx9/ObJewsET3Z2pc5+oeE8OiHC3XRFEPWpVx3+V +b9fHGVbn4KoaUFj0JOaEvbyAkId8HMwzUgg1NTmn+eR315BUvjVkSeeHAoGBALUV +ZNpEul7qfroJayn/oEuLG8TkuGaEwzXVmYBQvqzu8ykWNyh116qhnvtf9iXUzDGc +MrEneazKFBzI5K7fVZCFt4fVSI9ZAkwWrkrvXOh214N32B9PHVDS/IX3oXBcqTS/ +/ISYZIRjI0HW+t9HwtJmBOx5dcSFsUUp9u9R5DKJAoGBAMWEsK5lMz0FFr9g9OLD +lARC8iYtFSgL2klINxeMT3lzhB/7baIoEYbgWNcHWw/qB+li3QGpDn+tlilNHIzU +Y1LTEJFMjoHLanA7qskhkziyVIL5z4VCsguArUHamGajT3ZQ03814rG0OUk7wHfk +9ZN06G0/Ot4cPZEc5Jy6E4pS +-----END PRIVATE KEY----- diff --git a/tests/express/test_zip.py b/tests/express/test_zip.py index 89b869ff69..88985db933 100644 --- a/tests/express/test_zip.py +++ b/tests/express/test_zip.py @@ -2,6 +2,8 @@ from panda3d.core import ZipArchive, IStreamWrapper, StringStream, Filename from direct.stdpy.file import StreamIOWrapper import zipfile from io import BytesIO +import os +import pytest EMPTY_ZIP = b'PK\x05\x06' + b'\x00' * 18 @@ -181,3 +183,19 @@ def test_zip_repack(tmp_path): assert zf.read("test1.txt") == b"contents of first file" assert "test2.txt" not in zf.namelist() assert zf.read("test3.txt") == b"contents of third file" + + +@pytest.mark.skipif(not hasattr(ZipArchive, 'add_jar_signature'), reason='OpenSSL support disabled') +def test_zip_jar_signature(): + cur_dir = Filename.from_os_specific(os.path.dirname(__file__)) + + stream = StringStream() + zip = ZipArchive() + zip.open_read_write(stream) + zip.add_subfile("test.txt", StringStream(b"contents of test file"), 6) + zip.add_jar_signature(Filename(cur_dir, "cert.pem"), Filename(cur_dir, "private.pem")) + zip.close() + + with zipfile.ZipFile(StreamIOWrapper(stream), 'r') as zf: + assert zf.read("META-INF/MANIFEST.MF") == b'Manifest-Version: 1.0\r\n\r\nName: test.txt\r\nSHA-256-Digest: k5XWgStAZvRlNWIcz67qLSzso8Mc+OUG1QOlAwysyhE=\r\n\r\n' + assert zf.read("META-INF/CERT.SF") == b'Signature-Version: 1.0\r\nSHA-256-Digest-Manifest-Main-Attributes: VmrRqAIgAm0FCZViZFzpaP8OfDbN4iY0MyYFuzTMPv8=\r\nSHA-256-Digest-Manifest: 9R83KbhgHCBaYGXhJ/bV2MofgjRU254oUx+YilOvRcE=\r\n\r\nName: test.txt\r\nSHA-256-Digest: q8FmiLsrdoC5XQRaN9KmaPCcd2revsR0NzDul9cK6bk=\r\n\r\n' diff --git a/tests/linmath/test_compose_matrix.py b/tests/linmath/test_compose_matrix.py index 82497cb67d..bd52875a02 100644 --- a/tests/linmath/test_compose_matrix.py +++ b/tests/linmath/test_compose_matrix.py @@ -24,3 +24,20 @@ def test_compose_matrix(coordsys): new_quat = core.LQuaternion() new_quat.set_hpr(new_hpr, coordsys) assert quat.is_same_direction(new_quat) + + +@pytest.mark.parametrize("coordsys", (core.CS_zup_right, core.CS_yup_right, core.CS_zup_left, core.CS_yup_left)) +def test_compose_matrix2(coordsys): + mat = core.LMatrix3(1, 0, 0, 0, 0, -1, 0, 1, 0) + + new_scale = core.LVecBase3() + new_hpr = core.LVecBase3() + new_shear = core.LVecBase3() + core.decompose_matrix(mat, new_scale, new_shear, new_hpr, coordsys) + + assert new_scale.almost_equal(core.LVecBase3(1, 1, 1)) + if coordsys in (core.CS_zup_left, core.CS_yup_left): + assert new_hpr.almost_equal(core.LVecBase3(0, 90, 0)) + else: + assert new_hpr.almost_equal(core.LVecBase3(0, -90, 0)) + assert new_shear.almost_equal(core.LVecBase3(0, 0, 0)) diff --git a/tests/main.c b/tests/main.c index acf26dd10e..fe49ed14fc 100644 --- a/tests/main.c +++ b/tests/main.c @@ -19,11 +19,6 @@ #include -#ifdef __EMSCRIPTEN__ -#include -#include -#endif - #include "pandabase.h" #ifdef LINK_ALL_STATIC @@ -55,43 +50,6 @@ int main(int argc, char **argv) { PyConfig config; PyConfig_InitPythonConfig(&config); -#ifdef __EMSCRIPTEN__ - // getenv does not work with emscripten, instead read the PYTHONPATH and - // PYTHONHOME from the process.env variable if we're running in node.js. - char path[4096], home[4096]; - path[0] = 0; - home[0] = 0; - - EM_ASM({ - if (typeof process === 'object' && typeof process.env === 'object') { - var path = process.env.PYTHONPATH; - var home = process.env.PYTHONHOME; - if (path) { - if (process.platform === 'win32') { - path = path.replace(/;/g, ':'); - } - stringToUTF8(path, $0, $1); - } - if (home) { - stringToUTF8(home, $2, $3); - } - } - }, path, sizeof(path), home, sizeof(home)); - - if (path[0] != 0) { - status = PyConfig_SetBytesString(&config, &config.pythonpath_env, path); - if (PyStatus_Exception(status)) { - goto exception; - } - } - if (home[0] != 0) { - status = PyConfig_SetBytesString(&config, &config.home, home); - if (PyStatus_Exception(status)) { - goto exception; - } - } -#endif - PyConfig_SetBytesString(&config, &config.run_module, "pytest"); config.parse_argv = 0; @@ -153,6 +111,11 @@ int main(int argc, char **argv) { PyRun_SimpleString("import sys; sys.argv.insert(1, '--capture=sys')"); #endif +#ifdef ANDROID + // No caching on Android + PyRun_SimpleString("import sys; sys.argv.insert(1, '-o cache_dir=/dev/null')"); +#endif + return Py_RunMain(); exception: diff --git a/tests/pgraph/test_nodepath.py b/tests/pgraph/test_nodepath.py index ef9d44ab33..f426530429 100644 --- a/tests/pgraph/test_nodepath.py +++ b/tests/pgraph/test_nodepath.py @@ -312,3 +312,47 @@ def test_nodepath_replace_texture_none(): assert path2.get_texture() == tex1 path1.replace_texture(tex1, None) assert path2.get_texture() is None + + +def test_nodepath_set_collide_owner(): + from panda3d.core import NodePath, CollisionNode + + class CustomOwner: + pass + + owner1 = CustomOwner() + owner2 = CustomOwner() + owner3 = CustomOwner() + + root = NodePath("root") + model1 = root.attach_new_node("model1") + collider1 = model1.attach_new_node(CollisionNode("collider1")) + collider2 = model1.attach_new_node(CollisionNode("collider2")) + model2 = root.attach_new_node("model2") + collider3 = model2.attach_new_node(CollisionNode("collider3")) + + root.set_collide_owner(owner1) + assert collider1.node().owner is owner1 + assert collider2.node().owner is owner1 + assert collider3.node().owner is owner1 + + model1.set_collide_owner(None) + assert collider1.node().owner is None + assert collider2.node().owner is None + assert collider3.node().owner is owner1 + + collider2.set_collide_owner(owner2) + assert collider1.node().owner is None + assert collider2.node().owner is owner2 + assert collider3.node().owner is owner1 + + del owner1 + assert collider1.node().owner is None + assert collider2.node().owner is owner2 + assert collider3.node().owner is None + + root.set_collide_owner(owner3) + model2.set_collide_owner(owner2) + assert collider1.node().owner is owner3 + assert collider2.node().owner is owner3 + assert collider3.node().owner is owner2 diff --git a/tests/putil/test_bitarray.py b/tests/putil/test_bitarray.py index 81f058f904..15319edcb7 100644 --- a/tests/putil/test_bitarray.py +++ b/tests/putil/test_bitarray.py @@ -11,6 +11,20 @@ def test_bitarray_constructor(): assert BitArray().is_zero() assert BitArray(0).is_zero() + one = BitArray(1) + assert not one.is_zero() + assert one.get_lowest_on_bit() == 0 + assert one.get_highest_on_bit() == 0 + assert one.get_lowest_off_bit() == 1 + assert one.get_highest_off_bit() == -1 + + two = BitArray(2) + assert not two.is_zero() + assert two.get_lowest_on_bit() == 1 + assert two.get_highest_on_bit() == 1 + assert two.get_lowest_off_bit() == 0 + assert two.get_highest_off_bit() == -1 + ba = BitArray(0x10000000000000000000000000) assert ba.get_lowest_on_bit() == 100 assert ba.get_highest_on_bit() == 100 diff --git a/tests/putil/test_custom_writable.py b/tests/putil/test_custom_writable.py index 202c12320c..1ac5499d3f 100644 --- a/tests/putil/test_custom_writable.py +++ b/tests/putil/test_custom_writable.py @@ -32,6 +32,7 @@ BamReader.register_factory(CustomObject.get_class_type(), CustomObject.make_from def test_typed_writable_subclass(): obj = CustomObject() obj.field = 123 + base_rc = sys.getrefcount(obj) assert obj.get_type() == CustomObject.get_class_type() assert obj.type == CustomObject.get_class_type() @@ -45,9 +46,9 @@ def test_typed_writable_subclass(): reader = BamReader(buf) reader.init() obj = reader.read_object() - assert sys.getrefcount(obj) == 3 + assert sys.getrefcount(obj) == base_rc + 1 reader.resolve() del reader - assert sys.getrefcount(obj) == 2 + assert sys.getrefcount(obj) == base_rc assert obj.field == 123 diff --git a/tests/showbase/test_Loader.py b/tests/showbase/test_Loader.py index fe0c433326..b082ea6fe5 100644 --- a/tests/showbase/test_Loader.py +++ b/tests/showbase/test_Loader.py @@ -6,7 +6,9 @@ import sys @pytest.fixture def loader(): - return Loader(base=None) + loader = Loader(base=None) + yield loader + loader.destroy() @pytest.fixture @@ -120,9 +122,10 @@ fnrgl = fnargle:FnargleLoader sys.path = [str(tmp_path), platstdlib, stdlib] Loader._loadedPythonFileTypes = False + loader = Loader() - # base parameter is only used for audio - loader = Loader(None) + if not Loader._loadedPythonFileTypes: + Loader._loadPythonFileTypes() assert Loader._loadedPythonFileTypes # Should be registered, not yet loaded diff --git a/tests/showbase/test_VFSImporter.py b/tests/showbase/test_VFSImporter.py new file mode 100644 index 0000000000..52ce6c3206 --- /dev/null +++ b/tests/showbase/test_VFSImporter.py @@ -0,0 +1,60 @@ +from panda3d.core import VirtualFileSystem, VirtualFileMountRamdisk, Filename +import sys + + +def test_VFSImporter(): + from direct.showbase import VFSImporter + + VFSImporter.register() + + vfs = VirtualFileSystem.get_global_ptr() + mount = VirtualFileMountRamdisk() + success = vfs.mount(mount, "/ram", 0) + assert success + try: + sys.path.insert(0, "/ram") + vfs.write_file("/ram/testmod.py", b"var = 1\n", False) + + vfs.make_directory("/ram/testpkg") + vfs.write_file("/ram/testpkg/__init__.py", b"var = 2\n", False) + vfs.write_file("/ram/testpkg/test.py", b"var = 3\n", False) + + vfs.make_directory("/ram/testnspkg") + vfs.write_file("/ram/testnspkg/test.py", b"var = 4\n", False) + + import testmod + assert testmod.var == 1 + assert testmod.__spec__.name == 'testmod' + filename = Filename('/ram/testmod.py').to_os_specific() + assert testmod.__spec__.origin == filename + assert testmod.__file__ == filename + + import testpkg + assert testpkg.var == 2 + assert testpkg.__package__ == 'testpkg' + assert testpkg.__path__ == [Filename('/ram/testpkg').to_os_specific()] + assert testpkg.__spec__.name == 'testpkg' + filename = Filename('/ram/testpkg/__init__.py').to_os_specific() + assert testpkg.__spec__.origin == filename + assert testpkg.__file__ == filename + + from testpkg import test + assert test.var == 3 + assert test.__spec__.name == 'testpkg.test' + filename = Filename('/ram/testpkg/test.py').to_os_specific() + assert test.__spec__.origin == filename + assert test.__file__ == filename + + from testnspkg import test + assert test.var == 4 + assert test.__spec__.name == 'testnspkg.test' + filename = Filename('/ram/testnspkg/test.py').to_os_specific() + assert test.__spec__.origin == filename + assert test.__file__ == filename + + finally: + vfs.unmount(mount) + try: + del sys.path[sys.path.index("/ram")] + except ValueError: + pass diff --git a/tests/stdpy/test_threading.py b/tests/stdpy/test_threading.py index 3a4363e810..f5573270bc 100644 --- a/tests/stdpy/test_threading.py +++ b/tests/stdpy/test_threading.py @@ -10,6 +10,7 @@ def test_threading_error(): @pytest.mark.skipif(sys.platform == "emscripten", reason="No threading") +@pytest.mark.skipif(not core.Thread.is_threading_supported(), reason="No threading") def test_threading(): from collections import deque diff --git a/tests/stdpy/test_threading2.py b/tests/stdpy/test_threading2.py index fd7560def9..12da4108ba 100644 --- a/tests/stdpy/test_threading2.py +++ b/tests/stdpy/test_threading2.py @@ -6,6 +6,7 @@ import pytest @pytest.mark.skipif(sys.platform == "emscripten", reason="No threading") +@pytest.mark.skipif(not core.Thread.is_threading_supported(), reason="No threading") def test_threading2(): class BoundedQueue(threading2._Verbose):