diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..45995eb1b0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,138 @@ +name: Bug report +description: Report a focused, reproducible Lime bug +title: "[Bug]: " +labels: [] +body: + - type: markdown + attributes: + value: | + Please report one specific problem per issue. Reports without enough information to reproduce may be closed until the missing details are provided. + + - type: checkboxes + id: checks + attributes: + label: Preflight Checklist + options: + - label: I searched existing open and closed issues for duplicates. + required: true + - label: I tested the latest stable Lime release or the current development branch where possible. + required: true + - label: I reduced this to the smallest project or code sample that still reproduces the issue. + required: true + - label: This issue describes one specific behavior, not several unrelated observations. + required: true + + - type: textarea + id: summary + attributes: + label: Problem Description + description: Describe the specific behavior that appears to be wrong. + validations: + required: true + + - type: textarea + id: versions + attributes: + label: Version Information + description: Include exact versions. Do not write "latest" by itself. + value: | + Lime version: + OpenFL version, if applicable: + Haxe version: + Target/runtime: + validations: + required: true + + - type: textarea + id: environment + attributes: + label: Environment + description: Include relevant OS, architecture, hardware and driver details. + value: | + OS: + Architecture: + GPU/driver, if relevant: + Display refresh rate, if relevant: + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Minimal Reproduction + description: Attach or link a minimal project, or paste the smallest code sample that reproduces the problem. Include project.xml or project.hxp. + placeholder: | + Link to minimal project: + + project.xml / project.hxp: + + Relevant source: + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: List exact steps needed to reproduce the issue from a clean checkout/project. + placeholder: | + 1. + 2. + 3. + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What should happen? + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual Behavior + description: What happens instead? + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Logs and Output + description: Paste relevant console output, stack traces or build logs. + render: text + validations: + required: false + + - type: textarea + id: media + attributes: + label: Screenshots or Videos + description: Attach screenshots or videos only if they help demonstrate the reproduced behavior. They do not replace a minimal reproduction. + validations: + required: false + + - type: textarea + id: timing + attributes: + label: Performance, Timing or Rendering Details + description: If this is about frame pacing, timing or rendering, explain how you measured it and list frame rate, vsync, window/fullscreen and display settings. + placeholder: | + Frame rate: + Vsync setting: + Window/fullscreen mode: + Measurement method: + Reproduces on other targets/machines/drivers: + validations: + required: false + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Add any other focused context that may help reproduce or diagnose this issue. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..3ba13e0cec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000..143721ab44 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,87 @@ +name: Feature request +description: Propose a focused Lime feature or behavior change +title: "[Feature]: " +labels: [] +body: + - type: markdown + attributes: + value: | + Please use this form for focused feature proposals. If you are reporting broken behavior, use the bug report form instead. + + - type: checkboxes + id: checks + attributes: + label: Preflight Checklist + options: + - label: I searched existing open and closed issues for similar proposals. + required: true + - label: This request describes one focused feature or behavior change. + required: true + - label: This is not a bug report or a request for help debugging project-specific code. + required: true + + - type: textarea + id: problem + attributes: + label: Problem or Use Case + description: What problem would this feature solve? Who needs it, and when? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed Behavior + description: Describe the exact behavior, API, project setting or workflow change you are proposing. + validations: + required: true + + - type: textarea + id: targets + attributes: + label: Affected Targets or Backends + description: List the targets, runtimes or backends this proposal affects. + placeholder: | + Native SDL: + HTML5: + HashLink: + HXCPP: + Other: + validations: + required: true + + - type: textarea + id: compatibility + attributes: + label: Compatibility and Defaults + description: Explain whether this should be default behavior, opt-in behavior or a breaking change. Include any compatibility concerns. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: What existing options, workarounds or alternative designs have you considered? + validations: + required: false + + - type: textarea + id: examples + attributes: + label: Example Usage + description: Show how a project would use this feature, if applicable. + placeholder: | + project.xml / project.hxp: + + Haxe code: + validations: + required: false + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Add any focused context, links or prior discussion that may help evaluate the proposal. + validations: + required: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 857907b35e..b18bf094f7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,9 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libgl1-mesa-dev libglu1-mesa-dev g++-multilib gcc-multilib libasound2-dev libx11-dev libxext-dev libxi-dev libxrandr-dev libxinerama-dev libpulse-dev libmbedtls-dev libpng-dev libturbojpeg-dev libuv1-dev libvorbis-dev + sudo apt-get install -y libgl1-mesa-dev libglu1-mesa-dev g++-multilib gcc-multilib libasound2-dev libx11-dev libxext-dev libxi-dev libxrandr-dev libxinerama-dev libpulse-dev libmbedtls-dev libpng-dev libturbojpeg-dev libuv1-dev libvorbis-dev libvulkan-dev + # GStreamer decodes video in the unit tests + sudo apt-get install -y gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-libav xvfb - uses: krdlab/setup-haxe@v2 with: @@ -30,9 +32,9 @@ jobs: - name: Install Haxe dependencies run: | - curl --output ../hxcpp-4.3.45.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.45/hxcpp-4.3.45.zip + curl --output ../hxcpp-4.3.53.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.53/hxcpp-4.3.53.zip haxelib --global update haxelib --quiet - haxelib install ../hxcpp-4.3.45.zip --quiet + haxelib install ../hxcpp-4.3.53.zip --quiet haxelib install format --quiet haxelib install hxp --quiet haxelib install utest --quiet @@ -74,11 +76,31 @@ jobs: - name: Run unit tests working-directory: tests/unit + env: + # OpenAL's null output stands in for the sound card runners lack + ALSOFT_DRIVERS: "null" run: | lime test neko -release -verbose -nocolor lime test hl -release -verbose -nocolor lime test linux -release -verbose -nocolor + - name: Run unit tests past 24.8 days + working-directory: tests/unit + # A timer stuck at the Int limit hangs the tests rather than failing them + timeout-minutes: 10 + env: + ALSOFT_DRIVERS: "null" + run: | + # The timer starts 10 s short of 2^31 ms, where an Int millisecond count + # runs out, so the tests run on across it + lime test linux -release -verbose -nocolor -Dsimulate_uptime=2147473648 + + - name: Run video smoke test + working-directory: tests/video-smoke + run: | + lime build linux -release -verbose -nocolor + SDL_AUDIODRIVER=dummy xvfb-run -a Export/linux/bin/LimeVideoSmoke + - name: Install samples run: | haxelib git lime-samples https://github.com/openfl/lime-samples --quiet @@ -98,6 +120,11 @@ jobs: lime create SimpleAudio -verbose -nocolor lime build SimpleAudio linux -release -verbose -nocolor + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor + lime build SimpleVideo linux -release -verbose -nocolor + linux-arm64: runs-on: ubuntu-22.04-arm steps: @@ -110,7 +137,9 @@ jobs: run: | sudo add-apt-repository ppa:haxe/releases -y sudo apt-get update - sudo apt-get install -y libgl1-mesa-dev libglu1-mesa-dev libasound2-dev libx11-dev libxext-dev libxi-dev libxrandr-dev libxinerama-dev libpulse-dev libmbedtls-dev libpng-dev libturbojpeg-dev libuv1-dev libvorbis-dev + sudo apt-get install -y libgl1-mesa-dev libglu1-mesa-dev libasound2-dev libx11-dev libxext-dev libxi-dev libxrandr-dev libxinerama-dev libpulse-dev libmbedtls-dev libpng-dev libturbojpeg-dev libuv1-dev libvorbis-dev libvulkan-dev + # GStreamer decodes video in the unit tests + sudo apt-get install -y gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-libav # krdlab/setup-haxe doesn't support arm64 linux # so this is our workaround for now @@ -128,17 +157,13 @@ jobs: - name: Install Haxe dependencies run: | - curl --output ../hxcpp-4.3.45.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.45/hxcpp-4.3.45.zip + curl --output ../hxcpp-4.3.53.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.53/hxcpp-4.3.53.zip haxelib --global update haxelib --quiet - haxelib install ../hxcpp-4.3.45.zip --quiet + haxelib install ../hxcpp-4.3.53.zip --quiet haxelib install format --quiet haxelib install hxp --quiet haxelib install utest --quiet - - name: Enable HXCPP compile cache - run: | - echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV - - name: Rebuild Lime tools run: | haxelib dev lime ${{ github.workspace }} @@ -159,6 +184,9 @@ jobs: - name: Run unit tests working-directory: tests/unit + env: + # OpenAL's null output stands in for the sound card runners lack + ALSOFT_DRIVERS: "null" run: | lime test neko -release -verbose -nocolor lime test linux -release -verbose -nocolor @@ -182,14 +210,24 @@ jobs: lime create SimpleAudio -verbose -nocolor lime build SimpleAudio linux -release -verbose -nocolor - macos: - runs-on: macos-14 + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor + lime build SimpleVideo linux -release -verbose -nocolor + + vulkan-smoke: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 with: submodules: true + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgl1-mesa-dev libglu1-mesa-dev libasound2-dev libx11-dev libx11-xcb1 libxext-dev libxi-dev libxrandr-dev libxinerama-dev libpulse-dev libmbedtls-dev libpng-dev libturbojpeg-dev libuv1-dev libvorbis-dev libvulkan-dev mesa-vulkan-drivers xvfb + - uses: krdlab/setup-haxe@v2 with: haxe-version: ${{ env.HAXE_VERSION }} @@ -198,23 +236,58 @@ jobs: run: | echo "HAXEPATH=$HAXE_STD_PATH/.." >> $GITHUB_ENV - - name: Install HashLink dependencies + - name: Install Haxe dependencies + run: | + curl --output ../hxcpp-4.3.53.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.53/hxcpp-4.3.53.zip + haxelib --global update haxelib --quiet + haxelib install ../hxcpp-4.3.53.zip --quiet + haxelib install format --quiet + haxelib install hxp --quiet + haxelib install utest --quiet + + - name: Rebuild Lime tools + run: | + haxelib dev lime ${{ github.workspace }} + haxelib run lime rebuild tools -nocolor -verbose -nocffi + haxelib run lime setup -alias -y -nocffi + + - name: Rebuild Lime (Linux default Vulkan) + run: | + lime rebuild linux -64 -release -nocolor -verbose -nocffi + + - name: Build Vulkan smoke test app + working-directory: tests/vulkan-smoke + run: | + lime build linux -release -verbose -nocolor + + - name: Run Vulkan smoke test app + working-directory: tests/vulkan-smoke + run: | + if [ -f /usr/share/vulkan/icd.d/lvp_icd.x86_64.json ]; then + export VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json + fi + SDL_AUDIODRIVER=dummy xvfb-run -a Export/linux/bin/LimeVulkanSmoke + + macos: + runs-on: macos-14 + steps: + + - uses: actions/checkout@v6 + with: + submodules: true + + - uses: krdlab/setup-haxe@v2 + with: + haxe-version: 4.3.7 + + - name: Set HAXEPATH run: | - arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" - pushd project/lib/hashlink - arch -x86_64 /usr/local/bin/brew update - rm /usr/local/bin/2to3* - rm /usr/local/bin/idle3* - rm /usr/local/bin/pydoc3* - rm /usr/local/bin/python3* - rm /usr/local/bin/pip3* - arch -x86_64 /usr/local/bin/brew bundle - popd + echo "HAXEPATH=$HAXE_STD_PATH/.." >> $GITHUB_ENV - name: Install Haxe dependencies run: | - curl --output ../hxcpp-4.3.45.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.45/hxcpp-4.3.45.zip - haxelib install ../hxcpp-4.3.45.zip --quiet + curl --output ../hxcpp-4.3.53.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.53/hxcpp-4.3.53.zip + haxelib install ../hxcpp-4.3.53.zip --quiet haxelib install format --quiet haxelib install hxp --quiet haxelib install utest --quiet @@ -227,23 +300,125 @@ jobs: - name: Rebuild Lime (macOS) run: | - lime rebuild macos -clean -release -64 -nocolor -verbose -nocffi lime rebuild macos -clean -release -arm64 -nocolor -verbose -nocffi - lime rebuild hl -clean -release -nocolor -verbose -nocffi - uses: actions/upload-artifact@v7 with: - name: Mac64-NDLL + name: MacArm64-NDLL path: | - ndll/Mac64/ + ndll/MacArm64/ !**/.gitignore if-no-files-found: error + - name: Run unit tests + working-directory: tests/unit + env: + # OpenAL's null output stands in for the sound card runners lack + ALSOFT_DRIVERS: "null" + run: | + lime test neko -release -verbose -nocolor + lime test mac -arm64 -release -verbose -nocolor + + - name: Run video smoke test + working-directory: tests/video-smoke + run: | + lime test mac -arm64 -release -verbose -nocolor + + - name: Install samples + run: | + haxelib git lime-samples https://github.com/openfl/lime-samples --quiet + + # - name: Build HelloWorld sample + # run: | + # lime create HelloWorld -verbose -nocolor + # lime build HelloWorld macos -release -verbose -nocolor + + - name: Build SimpleImage sample + run: | + lime create SimpleImage -verbose -nocolor + lime build SimpleImage macos -arm64 -release -verbose -nocolor + + - name: Build SimpleAudio sample + run: | + lime create SimpleAudio -verbose -nocolor + lime build SimpleAudio macos -arm64 -release -verbose -nocolor + + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor + lime build SimpleVideo macos -arm64 -release -verbose -nocolor + + macos-intel: + runs-on: macos-15-intel + steps: + + - uses: actions/checkout@v6 + with: + submodules: true + + - uses: krdlab/setup-haxe@v2 + with: + haxe-version: ${{ env.HAXE_VERSION }} + + - name: Set HAXEPATH + run: | + echo "HAXEPATH=$HAXE_STD_PATH/.." >> $GITHUB_ENV + + - name: Install HashLink dependencies + env: + # Homebrew no longer builds bottles for Intel macOS, so updating it and + # upgrading what the runner already has rebuilds cmake, OpenSSL and + # Python from source, which takes an hour. Only what is missing is + # installed, from the formulae the runner image came with. + HOMEBREW_NO_AUTO_UPDATE: 1 + HOMEBREW_NO_INSTALL_UPGRADE: 1 + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 + run: | + set -euxo pipefail + + rm -f /usr/local/bin/2to3* + rm -f /usr/local/bin/idle3* + rm -f /usr/local/bin/pydoc3* + rm -f /usr/local/bin/python3* + rm -f /usr/local/bin/pip3* + + for attempt in 1 2 3; do + if /usr/local/bin/brew install jpeg-turbo libpng libuv libvorbis; then + break + fi + if [ "$attempt" -eq 3 ]; then + exit 1 + fi + sleep $((attempt * 30)) + done + + - name: Install Haxe dependencies + run: | + curl --output ../hxcpp-4.3.53.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.53/hxcpp-4.3.53.zip + haxelib install ../hxcpp-4.3.53.zip --quiet + haxelib install format --quiet + haxelib install hxp --quiet + haxelib install utest --quiet + + - name: Rebuild Lime tools + run: | + haxelib dev lime ${{ github.workspace }} + haxelib run lime rebuild tools -nocolor -verbose -nocffi + haxelib run lime setup -alias -y -nocffi + + - name: Rebuild Lime (macOS) + run: | + lime rebuild macos -clean -release -x86_64 -nocolor -verbose -nocffi + + - name: Rebuild Lime (HashLink macOS) + run: | + lime rebuild hl -clean -release -nocolor -verbose -nocffi + - uses: actions/upload-artifact@v7 with: - name: MacArm64-NDLL + name: Mac64-NDLL path: | - ndll/MacArm64/ + ndll/Mac64/ !**/.gitignore if-no-files-found: error @@ -256,10 +431,13 @@ jobs: - name: Run unit tests working-directory: tests/unit + env: + # OpenAL's null output stands in for the sound card runners lack + ALSOFT_DRIVERS: "null" run: | lime test neko -release -verbose -nocolor lime test hl -release -verbose -nocolor - lime test mac -release -verbose -nocolor + lime test mac -x86_64 -release -verbose -nocolor - name: Install samples run: | @@ -273,12 +451,17 @@ jobs: - name: Build SimpleImage sample run: | lime create SimpleImage -verbose -nocolor - lime build SimpleImage macos -release -verbose -nocolor + lime build SimpleImage macos -x86_64 -release -verbose -nocolor - name: Build SimpleAudio sample run: | lime create SimpleAudio -verbose -nocolor - lime build SimpleAudio macos -release -verbose -nocolor + lime build SimpleAudio macos -x86_64 -release -verbose -nocolor + + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor + lime build SimpleVideo macos -x86_64 -release -verbose -nocolor windows: runs-on: windows-latest @@ -303,8 +486,8 @@ jobs: - name: Install Haxe dependencies run: | - curl --output ../hxcpp-4.3.45.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.45/hxcpp-4.3.45.zip - haxelib install ../hxcpp-4.3.45.zip --quiet + curl --output ../hxcpp-4.3.53.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.53/hxcpp-4.3.53.zip + haxelib install ../hxcpp-4.3.53.zip --quiet haxelib install format --quiet haxelib install hxp --quiet haxelib install utest --quiet @@ -353,12 +536,26 @@ jobs: - name: Run unit tests working-directory: tests/unit + env: + # OpenAL's null output stands in for the sound card runners lack + ALSOFT_DRIVERS: "null" run: | lime test neko -release -verbose -nocolor lime test hl -release -verbose -nocolor lime test air -release -verbose -nocolor lime test windows -release -verbose -nocolor + - name: Run unit tests past 24.8 days + working-directory: tests/unit + # A timer stuck at the Int limit hangs the tests rather than failing them + timeout-minutes: 10 + env: + ALSOFT_DRIVERS: "null" + run: | + # The timer starts 10 s short of 2^31 ms, where an Int millisecond count + # runs out, so the tests run on across it + lime test windows -release -verbose -nocolor -Dsimulate_uptime=2147473648 + - name: Install samples run: | haxelib git lime-samples https://github.com/openfl/lime-samples --quiet @@ -378,6 +575,11 @@ jobs: lime create SimpleAudio -verbose -nocolor lime build SimpleAudio windows -release -verbose -nocolor + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor + lime build SimpleVideo windows -release -verbose -nocolor + - name: Rebuild Lime (Windows, Mingw) run: | rm ndll/Windows64/* @@ -391,6 +593,73 @@ jobs: !**/.gitignore if-no-files-found: error + webassembly: + runs-on: ubuntu-22.04 + steps: + + - uses: actions/checkout@v6 + with: + submodules: true + + - name: Install Emscripten NDK + uses: emscripten-core/setup-emsdk@v16 + + - uses: krdlab/setup-haxe@v2 + with: + haxe-version: ${{ env.HAXE_VERSION }} + + - name: Set HAXEPATH + run: | + echo "HAXEPATH=$HAXE_STD_PATH/.." >> $GITHUB_ENV + + - name: Install Haxe dependencies + run: | + curl --output ../hxcpp-4.3.168.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.168/hxcpp-4.3.168.zip + haxelib install ../hxcpp-4.3.168.zip --quiet + haxelib install format --quiet + haxelib install hxp --quiet + + - name: Prepare Lime + run: | + haxelib dev lime ${{ github.workspace }} + haxelib run lime setup -alias -y -nocffi -eval + + - name: Configure Emscripten support + run: | + lime config EMSCRIPTEN_SDK $EMSDK/upstream/emscripten -eval + lime config -eval + + - name: Rebuild Lime (WebAssembly) + run: | + lime rebuild webassembly -release -nocolor -verbose -nocffi -eval + + - uses: actions/upload-artifact@v7 + with: + name: Emscripten-NDLL + path: | + ndll/Emscripten/ + !**/.gitignore + if-no-files-found: error + + - name: Install samples + run: | + haxelib git lime-samples https://github.com/openfl/lime-samples --quiet + + - name: Build SimpleImage sample + run: | + lime create SimpleImage -verbose -nocolor -eval + lime build SimpleImage webassembly -release -verbose -nocolor -eval + + - name: Build SimpleAudio sample + run: | + lime create SimpleAudio -verbose -nocolor -eval + lime build SimpleAudio webassembly -release -verbose -nocolor -eval + + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor -eval + lime build SimpleVideo webassembly -release -verbose -nocolor -eval + android: runs-on: ubuntu-22.04 strategy: @@ -425,8 +694,8 @@ jobs: - name: Install Haxe dependencies run: | - curl --output ../hxcpp-4.3.45.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.45/hxcpp-4.3.45.zip - haxelib install ../hxcpp-4.3.45.zip --quiet + curl --output ../hxcpp-4.3.53.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.53/hxcpp-4.3.53.zip + haxelib install ../hxcpp-4.3.53.zip --quiet haxelib install format --quiet haxelib install hxp --quiet @@ -476,6 +745,17 @@ jobs: run: | lime create SimpleAudio -verbose -nocolor -eval lime build SimpleAudio android -release -verbose -nocolor -eval + + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor -eval + lime build SimpleVideo android -release -verbose -nocolor -eval + + - name: Build Vulkan smoke test app + working-directory: tests/vulkan-smoke + run: | + lime build android -release -verbose -nocolor -eval + ios: runs-on: macos-14 steps: @@ -494,8 +774,8 @@ jobs: - name: Install Haxe dependencies run: | - curl --output ../hxcpp-4.3.45.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.45/hxcpp-4.3.45.zip - haxelib install ../hxcpp-4.3.45.zip --quiet + curl --output ../hxcpp-4.3.53.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.53/hxcpp-4.3.53.zip + haxelib install ../hxcpp-4.3.53.zip --quiet haxelib install format --quiet haxelib install hxp --quiet @@ -535,8 +815,13 @@ jobs: lime create SimpleAudio -verbose -nocolor -eval lime build SimpleAudio ios -release -nosign -verbose -nocolor -eval + - name: Build SimpleVideo sample (Device) + run: | + lime create SimpleVideo -verbose -nocolor -eval + lime build SimpleVideo ios -release -nosign -verbose -nocolor -eval + package-haxelib: - needs: [linux, macos, windows, android, ios] + needs: [linux, macos, macos-intel, windows, android, ios] runs-on: ubuntu-22.04 steps: @@ -554,8 +839,8 @@ jobs: - name: Install Haxe dependencies run: | - curl --output ../hxcpp-4.3.45.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.45/hxcpp-4.3.45.zip - haxelib install ../hxcpp-4.3.45.zip --quiet + curl --output ../hxcpp-4.3.53.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.53/hxcpp-4.3.53.zip + haxelib install ../hxcpp-4.3.53.zip --quiet haxelib install format --quiet haxelib install hxp --quiet haxelib install svg --quiet @@ -724,11 +1009,16 @@ jobs: lime create SimpleAudio -verbose -nocolor -eval lime build SimpleAudio flash -release -verbose -nocolor -eval + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor -eval + lime build SimpleVideo flash -release -verbose -nocolor -eval + air-samples: runs-on: windows-latest strategy: matrix: - haxe-version: [4.0.5, 4.1.5, 4.2.5, 4.3.6] + haxe-version: [4.0.5, 4.1.5, 4.2.5, 4.3.7] steps: - uses: actions/checkout@v6 @@ -750,8 +1040,8 @@ jobs: - name: Install Haxe dependencies run: | - curl --output ../hxcpp-4.3.45.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.45/hxcpp-4.3.45.zip - haxelib install ../hxcpp-4.3.45.zip --quiet + curl --output ../hxcpp-4.3.53.zip --location https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.53/hxcpp-4.3.53.zip + haxelib install ../hxcpp-4.3.53.zip --quiet haxelib install format --quiet haxelib install hxp --quiet haxelib git lime-samples https://github.com/openfl/lime-samples --quiet @@ -777,6 +1067,11 @@ jobs: lime create SimpleAudio -verbose -nocolor -eval lime build SimpleAudio air -release -verbose -nocolor -eval + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor -eval + lime build SimpleVideo air -release -verbose -nocolor -eval + hashlink-samples: needs: package-haxelib strategy: @@ -827,6 +1122,11 @@ jobs: lime create SimpleAudio -verbose -nocolor lime build SimpleAudio hl -release -verbose -nocolor + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor + lime build SimpleVideo hl -release -verbose -nocolor + hashlinkc-samples: needs: package-haxelib strategy: @@ -877,12 +1177,17 @@ jobs: lime create SimpleAudio -verbose -nocolor lime build SimpleAudio hlc -release -verbose -nocolor + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor + lime build SimpleVideo hlc -release -verbose -nocolor + html5-samples: needs: package-haxelib runs-on: ubuntu-22.04 strategy: matrix: - haxe-version: [3.4.7, 4.0.5, 4.1.5, 4.2.5, 4.3.6] + haxe-version: [3.4.7, 4.0.5, 4.1.5, 4.2.5, 4.3.7] steps: - uses: actions/checkout@v6 @@ -937,12 +1242,23 @@ jobs: lime create SimpleAudio -verbose -nocolor lime build SimpleAudio html5 -release -verbose -nocolor + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor + lime build SimpleVideo html5 -release -verbose -nocolor + - name: Build SimpleAudio variants if: ${{ matrix.haxe-version != '3.4.7' }} run: | lime build SimpleAudio html5 -clean -release -verbose -nocolor --haxelib=genes lime build SimpleAudio html5 -clean -release -verbose -nocolor -minify -terser + - name: Build SimpleVideo variants + if: ${{ matrix.haxe-version != '3.4.7' }} + run: | + lime build SimpleVideo html5 -clean -release -verbose -nocolor --haxelib=genes + lime build SimpleVideo html5 -clean -release -verbose -nocolor -minify -terser + neko-samples: needs: package-haxelib strategy: @@ -994,13 +1310,45 @@ jobs: lime create SimpleAudio -verbose -nocolor lime build SimpleAudio neko -release -verbose -nocolor + - name: Build SimpleVideo sample + run: | + lime create SimpleVideo -verbose -nocolor + lime build SimpleVideo neko -release -verbose -nocolor + notify: runs-on: ubuntu-22.04 - needs: [package-haxelib, docs, android, flash-samples, air-samples, hashlink-samples, hashlinkc-samples, html5-samples, ios, linux, macos, neko-samples, windows] - if: ${{ github.repository == 'openfl/lime' && github.event_name != 'pull_request' }} + needs: [package-haxelib, docs, android, flash-samples, air-samples, hashlink-samples, hashlinkc-samples, html5-samples, ios, linux, linux-arm64, vulkan-smoke, macos, macos-intel, neko-samples, webassembly, windows] + # Reports failures too, which is when it matters most + if: ${{ always() && github.repository == 'openfl/lime' && github.event_name != 'pull_request' }} steps: - name: Notify Discord - uses: nobrayner/discord-webhook@v1 - with: - github-token: ${{ secrets.github_token }} - discord-webhook: ${{ secrets.DISCORD_WEBHOOK }} + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + GH_TOKEN: ${{ github.token }} + ACTOR: ${{ github.actor }} + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + TITLE: "[${{ github.repository }}] ${{ github.workflow }}" + run: | + # One message for the run, naming the jobs that did not pass. Discord + # takes at most 25 fields in a message, fewer than the jobs here, so + # they go in its description, and a message it turns down fails this. + jobs=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs?per_page=100" --jq '[.jobs[] | select(.name != "notify")]') + failed=$(echo "$jobs" | jq -r '[.[] | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out")] | map("[\(.name)](\(.html_url)): \(.conclusion)") | join("\n")') + + if [ -z "$failed" ]; then status=Success; color=1560392; else status=Failure; color=15148839; fi + + commit="${{ github.sha }}" + description="\`${commit:0:8}\` $(printf '%s' "$COMMIT_MESSAGE" | head -n 1)" + [ -n "$failed" ] && description="$description"$'\n\n'"$failed" + + payload=$(jq -n --arg title "$TITLE: $status" --arg url "$RUN_URL" --arg actor "$ACTOR" --arg description "$description" --argjson color "$color" '{ + username: "GitHub Actions", + avatar_url: "https://avatars0.githubusercontent.com/u/44036562?s=200&v=4", + embeds: [{ + author: {name: $actor, url: ("https://github.com/" + $actor), icon_url: ("https://github.com/" + $actor + ".png")}, + title: $title, url: $url, description: $description, color: $color + }] + }') + + curl --fail-with-body --silent --show-error -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK" diff --git a/CHANGELOG.md b/CHANGELOG.md index 38d456a168..67464dcddf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,63 @@ Changelog ========= +8.4.0 (06/18/2026) +------------------ + +- Added an experimental Vulkan renderer for native Windows, Linux, and Android targets. +- Added `VulkanRenderContext` and Haxe Vulkan bindings for instance, device, swapchain, command, descriptor, memory, and pipeline helpers. +- Added Vulkan bootstrap logging, error details, MSAA resolve support, mapped memory helpers, and descriptor helpers. +- Added configurable application frame timing with `FrameProfile`, `FrameOptions`, `VSyncMode`, busy-wait, time precision, and uncap controls. +- Added `AudioInput` and audio input device APIs with native OpenAL, HTML5, and Flash backends. +- Added SDL_sound decoding and streaming, including inline stream playback, streams with unknown length, and loop counts. +- Added `Window.alwaysOnTop` and transparent window support. +- Added input event timestamps without changing listener signatures. +- Added `Promise.completeAsync()`. +- Added explicit font rendering flags, DPI-aware glyph rendering, unhinted font decomposition, and HarfBuzz FT font change notifications. +- Added `Font.isBold` and `Font.isItalic`. +- Added project.xml support for Android manifest tags and Gradle plugin entries. +- Added AIR options for Android App Bundles, Android Studio output, and shared runtime deployment. +- Added VAO support on macOS. +- Added `VideoSource` and `VideoDecoder` for native video playback, with Media Foundation, AVFoundation, MediaCodec, and GStreamer backends. +- Added `Window.flash()`, `Window.mouseRect`, and `Window.keyboardGrab`. +- Added `Window.preciseMouseWheel` for smooth scrolling on touchpads and smooth wheels. +- Added `Display.onConnect`, `Display.onDisconnect`, and `Display.onMove`. +- Added `Gamepad.type`, motion sensors, touchpad events, trigger rumble, and light color. +- Added `Locale.preferredLocales`. +- Added `ThreadPool.idleTimeout`, so idle threads wait for more work before they exit. +- Added `CURL.versionInfo()` values for the library's age, version number, and features. +- Changed asset copying and embedding to behave consistently across targets. +- Changed OpenAL Soft HRTF to be disabled by default. +- Changed `allowHighDPI` on Windows to draw at each display's own resolution, rather than stretched from 96 DPI. +- Changed `Window.alert()` to also request attention on macOS and Linux. +- Changed Windows to show input methods' own composition and candidate windows. +- Changed `FrameProfile.Uncapped` to uncap the frame loop. +- Changed native HTTP requests to look up names on a thread of their own on every platform. +- Updated pixman and platform config for LLVM assembly fixes, ARM NEON, Android NDKs, and Apple platforms. +- Updated MojoAL and gamepad mappings. +- Updated SDL submodule to version 2.32.10. +- Updated HashLink to 1.16. +- Fixed an asset embedding regression. +- Fixed Windows cpp icon resource handling when no icon is generated. +- Fixed GL cleanup and desktop GL object collection. +- Fixed native and HashLink `AudioBuffer` layout mismatches. +- Fixed `AudioBuffer.fromBytes()` and `AudioBuffer.fromBase64()` on HTML5 to avoid forcing HTML5 audio buffers. +- Fixed OpenAL output device recovery. +- Fixed perspective camera matrix calculation. +- Fixed Tizen `appinfo.json` templating. +- Fixed Android architecture parsing for `x86_64` and `x86_32`. +- Fixed SDL_sound ARM64 Linux detection outside Raspberry Pi OS. +- Fixed HTML5 transparent docs generation. +- Fixed native HTTP requests freezing the application during slow name lookups (#1769). +- Fixed HashLink collections waiting on threads blocked in native calls. +- Fixed `Future.ready()` and `Future.result()` never seeing threaded work complete. +- Fixed `Promise.completeAsync()` on C++. +- Fixed single-threaded `ThreadPool` work running past its share of a frame while waiting. +- Fixed `Font.renderGlyphs()` reading glyphs in an outdated layout, and on HashLink. +- Fixed `getVertexAttribOffset()` and `getBufferPointerv()` writing through a null pointer. +- Fixed video pan reaching sound that was already queued. +- Fixed compositions longer than 31 bytes being cut short in input methods. + 8.3.2 (05/13/2026) ------------------ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c483f354e6..ae8c283921 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,38 @@ We welcome your feedback and input in how to make Lime better! If you are interested in discussing new features, the direction the project or how to integrate some change, please open an issue so we can talk about. +## Issue Reporting Guidelines + +Please keep bug reports focused, reproducible and actionable. A good issue should describe one specific problem and include enough information for maintainers to reproduce it without guessing. + +Before opening an issue: + + 1. Search existing open and closed issues to avoid duplicates. + + 2. Test with the latest stable release and, when possible, the current development branch. + + 3. Reduce the problem to the smallest project or code sample that still reproduces it. + +Bug reports should include: + + 1. The exact Lime version, OpenFL version if applicable, Haxe version and target/runtime. + + 2. The operating system, architecture and relevant hardware or driver details. + + 3. A minimal project or code sample, including `project.xml` or `project.hxp`. + + 4. Exact steps to reproduce the problem. + + 5. The expected behavior and the actual behavior. + + 6. Any relevant logs, console output, screenshots or videos. + +Please do not combine multiple unrelated problems in one issue. Open one issue per behavior so each report can be reproduced, discussed and fixed independently. + +Performance, timing and rendering reports need especially clear reproduction material. Include a minimal project, the exact frame rate/vsync/window settings, how the result was measured and whether the behavior reproduces across targets, machines or drivers. + +Issues based only on broad observations, speculation, generated code that has not been verified, or project-specific behavior without a minimal reproduction may be closed until enough information is provided. + 1. Fork the repository 2. Make the desired change diff --git a/README.md b/README.md index c6803fb1a5..167628b285 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,13 @@ Lime does not include a renderer, but exposes the current context: * DOM * Flash * GL + * Vulkan The GL context is based upon the WebGL standard, implemented for both OpenGL and OpenGL ES as needed. +Native Vulkan support exposes an explicit Vulkan API layer for renderer backends, including native window/swapchain integration and resource/command primitives. +Native Vulkan support is included in standard Windows and Linux C++ Lime builds, including the matching Haxe API define. Apple targets still require an explicit Vulkan-enabled build and MoltenVK packaging. +To request a Vulkan window, use the standard `RenderContextType.VULKAN` context selection or set `` in project XML. +The in-repo `tests/vulkan-smoke` project is used as the current Vulkan CI smoke app. Lime provides a unified audio API, but also provides access to OpenAL for advanced audio on native targets. diff --git a/docs/ImportAll.hx b/docs/ImportAll.hx index e0039724f6..d1fbcc89cf 100644 --- a/docs/ImportAll.hx +++ b/docs/ImportAll.hx @@ -58,6 +58,41 @@ import lime.graphics.PixelFormat; import lime.graphics.RenderContext; import lime.graphics.RenderContextAttributes; import lime.graphics.RenderContextType; +import lime.graphics.VulkanRenderContext; +import lime.graphics.vulkan.VK; +import lime.graphics.vulkan.VKAcquireResult; +import lime.graphics.vulkan.VKBuffer; +import lime.graphics.vulkan.VKCommandBuffer; +import lime.graphics.vulkan.VKCommandPool; +import lime.graphics.vulkan.VKDescriptorPool; +import lime.graphics.vulkan.VKDescriptorPoolSize; +import lime.graphics.vulkan.VKDescriptorSet; +import lime.graphics.vulkan.VKDescriptorSetLayout; +import lime.graphics.vulkan.VKDescriptorSetLayoutBinding; +import lime.graphics.vulkan.VKDevice; +import lime.graphics.vulkan.VKDeviceMemory; +import lime.graphics.vulkan.VKFence; +import lime.graphics.vulkan.VKFramebuffer; +import lime.graphics.vulkan.VKGraphicsPipelineInfo; +import lime.graphics.vulkan.VKImage; +import lime.graphics.vulkan.VKImageView; +import lime.graphics.vulkan.VKInstance; +import lime.graphics.vulkan.VKMemoryRequirements; +import lime.graphics.vulkan.VKPhysicalDevice; +import lime.graphics.vulkan.VKPipeline; +import lime.graphics.vulkan.VKPipelineCache; +import lime.graphics.vulkan.VKPipelineLayout; +import lime.graphics.vulkan.VKQueue; +import lime.graphics.vulkan.VKQueueFamilyInfo; +import lime.graphics.vulkan.VKRenderPass; +import lime.graphics.vulkan.VKRenderer; +import lime.graphics.vulkan.VKSampler; +import lime.graphics.vulkan.VKSemaphore; +import lime.graphics.vulkan.VKShaderModule; +import lime.graphics.vulkan.VKSurface; +import lime.graphics.vulkan.VKSwapchain; +import lime.graphics.vulkan.VKVertexAttribute; +import lime.graphics.vulkan.VKVertexBinding; import lime.graphics.WebGL2RenderContext; import lime.graphics.WebGLRenderContext; import lime.math.ARGB; @@ -87,6 +122,8 @@ import lime.media.vorbis.VorbisInfo; import lime.media.AudioBuffer; import lime.media.AudioContext; import lime.media.AudioContextType; +import lime.media.AudioInput; +import lime.media.AudioInputDevice; import lime.media.AudioManager; import lime.media.AudioSource; import lime.media.FlashAudioContext; diff --git a/externs/air/flash/data/SQLCollationType.hx b/externs/air/flash/data/SQLCollationType.hx index fecbfc8382..0f93c36c1f 100644 --- a/externs/air/flash/data/SQLCollationType.hx +++ b/externs/air/flash/data/SQLCollationType.hx @@ -1,7 +1,6 @@ package flash.data; -@:native("flash.data.SQLCollationType") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SQLCollationType(String) +@:native("flash.data.SQLCollationType") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SQLCollationType(String) { var BINARY = "binary"; var NO_CASE = "noCase"; diff --git a/externs/air/flash/data/SQLColumnNameStyle.hx b/externs/air/flash/data/SQLColumnNameStyle.hx index 03cb7a5adf..c25b13fc07 100644 --- a/externs/air/flash/data/SQLColumnNameStyle.hx +++ b/externs/air/flash/data/SQLColumnNameStyle.hx @@ -1,7 +1,6 @@ package flash.data; -@:native("flash.data.SQLColumnNameStyle") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SQLColumnNameStyle(String) +@:native("flash.data.SQLColumnNameStyle") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SQLColumnNameStyle(String) { var DEFAULT = "default"; var LONG = "long"; diff --git a/externs/air/flash/data/SQLMode.hx b/externs/air/flash/data/SQLMode.hx index ebddf4c8c9..4b876d2d02 100644 --- a/externs/air/flash/data/SQLMode.hx +++ b/externs/air/flash/data/SQLMode.hx @@ -1,7 +1,6 @@ package flash.data; -@:native("flash.data.SQLMode") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SQLMode(String) +@:native("flash.data.SQLMode") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SQLMode(String) { var CREATE = "create"; var READ = "read"; diff --git a/externs/air/flash/data/SQLTransactionLockType.hx b/externs/air/flash/data/SQLTransactionLockType.hx index 36c24bc1a7..47a06a7a79 100644 --- a/externs/air/flash/data/SQLTransactionLockType.hx +++ b/externs/air/flash/data/SQLTransactionLockType.hx @@ -1,7 +1,6 @@ package flash.data; -@:native("flash.data.SQLTransactionLockType") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SQLTransactionLockType(String) +@:native("flash.data.SQLTransactionLockType") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SQLTransactionLockType(String) { var DEFERRED = "deferred"; var EXCLUSIVE = "exclusive"; diff --git a/externs/air/flash/desktop/InvokeEventReason.hx b/externs/air/flash/desktop/InvokeEventReason.hx index d06e591c1b..1c053ad26c 100644 --- a/externs/air/flash/desktop/InvokeEventReason.hx +++ b/externs/air/flash/desktop/InvokeEventReason.hx @@ -1,7 +1,6 @@ package flash.desktop; -@:native("flash.desktop.InvokeEventReason") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract InvokeEventReason(String) +@:native("flash.desktop.InvokeEventReason") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract InvokeEventReason(String) { var LOGIN = "login"; var NOTIFICATION = "notification"; diff --git a/externs/air/flash/desktop/NativeDragActions.hx b/externs/air/flash/desktop/NativeDragActions.hx index fe67ff9db7..544ad2f1bf 100644 --- a/externs/air/flash/desktop/NativeDragActions.hx +++ b/externs/air/flash/desktop/NativeDragActions.hx @@ -1,7 +1,6 @@ package flash.desktop; -@:native("flash.desktop.NativeDragActions") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NativeDragActions(String) +@:native("flash.desktop.NativeDragActions") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NativeDragActions(String) { var COPY = "copy"; var LINK = "link"; diff --git a/externs/air/flash/desktop/NotificationType.hx b/externs/air/flash/desktop/NotificationType.hx index b2b3dcfb09..c9461c33cf 100644 --- a/externs/air/flash/desktop/NotificationType.hx +++ b/externs/air/flash/desktop/NotificationType.hx @@ -1,7 +1,6 @@ package flash.desktop; -@:native("flash.desktop.NotificationType") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NotificationType(String) +@:native("flash.desktop.NotificationType") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NotificationType(String) { var CRITICAL = "critical"; var INFORMATIONAL = "informational"; diff --git a/externs/air/flash/desktop/SystemIdleMode.hx b/externs/air/flash/desktop/SystemIdleMode.hx index 7fda8df044..c129940766 100644 --- a/externs/air/flash/desktop/SystemIdleMode.hx +++ b/externs/air/flash/desktop/SystemIdleMode.hx @@ -1,7 +1,6 @@ package flash.desktop; -@:native("flash.desktop.SystemIdleMode") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SystemIdleMode(String) +@:native("flash.desktop.SystemIdleMode") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SystemIdleMode(String) { var KEEP_AWAKE = "keepAwake"; var NORMAL = "normal"; diff --git a/externs/air/flash/display/NativeWindowDisplayState.hx b/externs/air/flash/display/NativeWindowDisplayState.hx index 4fb63f10d7..56a073a2e0 100644 --- a/externs/air/flash/display/NativeWindowDisplayState.hx +++ b/externs/air/flash/display/NativeWindowDisplayState.hx @@ -1,7 +1,6 @@ package flash.display; -@:native("flash.display.NativeWindowDisplayState") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NativeWindowDisplayState(String) +@:native("flash.display.NativeWindowDisplayState") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NativeWindowDisplayState(String) { var MAXIMIZED = "maximized"; var MINIMIZED = "minimized"; diff --git a/externs/air/flash/display/NativeWindowRenderMode.hx b/externs/air/flash/display/NativeWindowRenderMode.hx index e2d182c5bd..51a135f715 100644 --- a/externs/air/flash/display/NativeWindowRenderMode.hx +++ b/externs/air/flash/display/NativeWindowRenderMode.hx @@ -1,7 +1,6 @@ package flash.display; -@:native("flash.display.NativeWindowRenderMode") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NativeWindowRenderMode(String) +@:native("flash.display.NativeWindowRenderMode") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NativeWindowRenderMode(String) { var AUTO = "auto"; var CPU = "cpu"; diff --git a/externs/air/flash/display/NativeWindowResize.hx b/externs/air/flash/display/NativeWindowResize.hx index b3cd1a1f9f..5974784f0f 100644 --- a/externs/air/flash/display/NativeWindowResize.hx +++ b/externs/air/flash/display/NativeWindowResize.hx @@ -1,7 +1,6 @@ package flash.display; -@:native("flash.display.NativeWindowResize") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NativeWindowResize(String) +@:native("flash.display.NativeWindowResize") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NativeWindowResize(String) { var BOTTOM = "bottom"; var BOTTOM_LEFT = "bottomLeft"; diff --git a/externs/air/flash/display/NativeWindowSystemChrome.hx b/externs/air/flash/display/NativeWindowSystemChrome.hx index e271969831..367e6d9c22 100644 --- a/externs/air/flash/display/NativeWindowSystemChrome.hx +++ b/externs/air/flash/display/NativeWindowSystemChrome.hx @@ -1,7 +1,6 @@ package flash.display; -@:native("flash.display.NativeWindowSystemChrome") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NativeWindowSystemChrome(String) +@:native("flash.display.NativeWindowSystemChrome") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NativeWindowSystemChrome(String) { var ALTERNATE = "alternate"; var NONE = "none"; diff --git a/externs/air/flash/display/NativeWindowType.hx b/externs/air/flash/display/NativeWindowType.hx index 90ff5bb41f..0ffd8af4bc 100644 --- a/externs/air/flash/display/NativeWindowType.hx +++ b/externs/air/flash/display/NativeWindowType.hx @@ -1,7 +1,6 @@ package flash.display; -@:native("flash.display.NativeWindowType") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NativeWindowType(String) +@:native("flash.display.NativeWindowType") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NativeWindowType(String) { var LIGHTWEIGHT = "lightweight"; var NORMAL = "normal"; diff --git a/externs/air/flash/display/StageAspectRatio.hx b/externs/air/flash/display/StageAspectRatio.hx index 70ede16512..8a308b07bf 100644 --- a/externs/air/flash/display/StageAspectRatio.hx +++ b/externs/air/flash/display/StageAspectRatio.hx @@ -1,7 +1,6 @@ package flash.display; -@:native("flash.display.StageAspectRatio") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract StageAspectRatio(String) +@:native("flash.display.StageAspectRatio") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract StageAspectRatio(String) { var ANY = "any"; var LANDSCAPE = "landscape"; diff --git a/externs/air/flash/display/StageOrientation.hx b/externs/air/flash/display/StageOrientation.hx index d55da84778..2f2a9b34a8 100644 --- a/externs/air/flash/display/StageOrientation.hx +++ b/externs/air/flash/display/StageOrientation.hx @@ -1,7 +1,6 @@ package flash.display; -@:native("flash.display.StageOrientation") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract StageOrientation(String) +@:native("flash.display.StageOrientation") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract StageOrientation(String) { var DEFAULT = "default"; var ROTATED_LEFT = "rotatedLeft"; diff --git a/externs/air/flash/display3D/Context3DProfile.hx b/externs/air/flash/display3D/Context3DProfile.hx index 2c29eea679..a7e697bc09 100644 --- a/externs/air/flash/display3D/Context3DProfile.hx +++ b/externs/air/flash/display3D/Context3DProfile.hx @@ -1,7 +1,6 @@ package flash.display3D; -@:native("flash.display3D.Context3DProfile") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract Context3DProfile(String) +@:native("flash.display3D.Context3DProfile") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract Context3DProfile(String) { var BASELINE = "baseline"; var BASELINE_CONSTRAINED = "baselineConstrained"; diff --git a/externs/air/flash/errors/SQLErrorOperation.hx b/externs/air/flash/errors/SQLErrorOperation.hx index 15e7a651c0..396ba8c237 100644 --- a/externs/air/flash/errors/SQLErrorOperation.hx +++ b/externs/air/flash/errors/SQLErrorOperation.hx @@ -1,7 +1,6 @@ package flash.errors; -@:native("flash.errors.SQLErrorOperation") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SQLErrorOperation(String) +@:native("flash.errors.SQLErrorOperation") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SQLErrorOperation(String) { var ANALYZE = "analyze"; var ATTACH = "attach"; diff --git a/externs/air/flash/events/TouchEventIntent.hx b/externs/air/flash/events/TouchEventIntent.hx index 19a3c6a0e6..0af83e0428 100644 --- a/externs/air/flash/events/TouchEventIntent.hx +++ b/externs/air/flash/events/TouchEventIntent.hx @@ -1,7 +1,6 @@ package flash.events; -@:native("flash.events.TouchEventIntent") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract TouchEventIntent(String) +@:native("flash.events.TouchEventIntent") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract TouchEventIntent(String) { var ERASER = "eraser"; var PEN = "pen"; diff --git a/externs/air/flash/filesystem/FileMode.hx b/externs/air/flash/filesystem/FileMode.hx index 3041db9b92..6644c1627b 100644 --- a/externs/air/flash/filesystem/FileMode.hx +++ b/externs/air/flash/filesystem/FileMode.hx @@ -1,7 +1,6 @@ package flash.filesystem; -@:native("flash.filesystem.FileMode") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract FileMode(String) +@:native("flash.filesystem.FileMode") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract FileMode(String) { var APPEND = "append"; var READ = "read"; diff --git a/externs/air/flash/media/AudioPlaybackMode.hx b/externs/air/flash/media/AudioPlaybackMode.hx index fdc31990c4..9757dadbe0 100644 --- a/externs/air/flash/media/AudioPlaybackMode.hx +++ b/externs/air/flash/media/AudioPlaybackMode.hx @@ -1,7 +1,6 @@ package flash.media; -@:native("flash.media.AudioPlaybackMode") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract AudioPlaybackMode(String) +@:native("flash.media.AudioPlaybackMode") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract AudioPlaybackMode(String) { var AMBIENT = "ambient"; var MEDIA = "media"; diff --git a/externs/air/flash/media/CameraPosition.hx b/externs/air/flash/media/CameraPosition.hx index 06184cc9ed..754d0051f9 100644 --- a/externs/air/flash/media/CameraPosition.hx +++ b/externs/air/flash/media/CameraPosition.hx @@ -1,7 +1,6 @@ package flash.media; -@:native("flash.media.CameraPosition") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract CameraPosition(String) +@:native("flash.media.CameraPosition") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract CameraPosition(String) { var BACK = "back"; var FRONT = "front"; diff --git a/externs/air/flash/media/MediaType.hx b/externs/air/flash/media/MediaType.hx index 41a10306fb..e925b08397 100644 --- a/externs/air/flash/media/MediaType.hx +++ b/externs/air/flash/media/MediaType.hx @@ -1,7 +1,6 @@ package flash.media; -@:native("flash.media.MediaType") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract MediaType(String) +@:native("flash.media.MediaType") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract MediaType(String) { var IMAGE = "image"; var VIDEO = "video"; diff --git a/externs/air/flash/net/IPVersion.hx b/externs/air/flash/net/IPVersion.hx index ef4a44fcf2..c9d8200955 100644 --- a/externs/air/flash/net/IPVersion.hx +++ b/externs/air/flash/net/IPVersion.hx @@ -1,7 +1,6 @@ package flash.net; -@:native("flash.net.IPVersion") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract IPVersion(String) +@:native("flash.net.IPVersion") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract IPVersion(String) { var IPV4 = "ipv4"; var IPV6 = "ipv6"; diff --git a/externs/air/flash/notifications/NotificationStyle.hx b/externs/air/flash/notifications/NotificationStyle.hx index a2a9f3fdd2..715052bf6d 100644 --- a/externs/air/flash/notifications/NotificationStyle.hx +++ b/externs/air/flash/notifications/NotificationStyle.hx @@ -1,7 +1,6 @@ package flash.notifications; -@:native("flash.notifications.NotificationStyle") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NotificationStyle(String) +@:native("flash.notifications.NotificationStyle") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract NotificationStyle(String) { var ALERT = "alert"; var BADGE = "badge"; diff --git a/externs/air/flash/printing/PaperSize.hx b/externs/air/flash/printing/PaperSize.hx index fe607d1e2a..65e60bdbfa 100644 --- a/externs/air/flash/printing/PaperSize.hx +++ b/externs/air/flash/printing/PaperSize.hx @@ -1,7 +1,6 @@ package flash.printing; -@:native("flash.printing.PaperSize") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract PaperSize(String) +@:native("flash.printing.PaperSize") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract PaperSize(String) { var A4 = "a4"; var A5 = "a5"; diff --git a/externs/air/flash/printing/PrintMethod.hx b/externs/air/flash/printing/PrintMethod.hx index 070fcfcf1c..a6f7ac873a 100644 --- a/externs/air/flash/printing/PrintMethod.hx +++ b/externs/air/flash/printing/PrintMethod.hx @@ -1,7 +1,6 @@ package flash.printing; -@:native("flash.printing.PrintMethod") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract PrintMethod(String) +@:native("flash.printing.PrintMethod") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract PrintMethod(String) { var AUTO = "auto"; var BITMAP = "bitmap"; diff --git a/externs/air/flash/security/ReferencesValidationSetting.hx b/externs/air/flash/security/ReferencesValidationSetting.hx index 08fda5c8ca..b357c649d1 100644 --- a/externs/air/flash/security/ReferencesValidationSetting.hx +++ b/externs/air/flash/security/ReferencesValidationSetting.hx @@ -1,7 +1,6 @@ package flash.security; -@:native("flash.security.ReferencesValidationSetting") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract ReferencesValidationSetting(String) +@:native("flash.security.ReferencesValidationSetting") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract ReferencesValidationSetting(String) { var NEVER = "never"; var VALID_IDENTITY = "validIdentity"; diff --git a/externs/air/flash/security/RevocationCheckSettings.hx b/externs/air/flash/security/RevocationCheckSettings.hx index 09725cea85..11365b4a09 100644 --- a/externs/air/flash/security/RevocationCheckSettings.hx +++ b/externs/air/flash/security/RevocationCheckSettings.hx @@ -1,7 +1,6 @@ package flash.security; -@:native("flash.security.RevocationCheckSettings") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract RevocationCheckSettings(String) +@:native("flash.security.RevocationCheckSettings") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract RevocationCheckSettings(String) { var ALWAYS_REQUIRED = "alwaysRequired"; var BEST_EFFORT = "bestEffort"; diff --git a/externs/air/flash/security/SignatureStatus.hx b/externs/air/flash/security/SignatureStatus.hx index f8c632599e..68e03bcaf9 100644 --- a/externs/air/flash/security/SignatureStatus.hx +++ b/externs/air/flash/security/SignatureStatus.hx @@ -1,7 +1,6 @@ package flash.security; -@:native("flash.security.SignatureStatus") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SignatureStatus(String) +@:native("flash.security.SignatureStatus") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SignatureStatus(String) { var INVALID = "invalid"; var UNKNOWN = "unknown"; diff --git a/externs/air/flash/security/SignerTrustSettings.hx b/externs/air/flash/security/SignerTrustSettings.hx index 0164a3a253..0edf890a94 100644 --- a/externs/air/flash/security/SignerTrustSettings.hx +++ b/externs/air/flash/security/SignerTrustSettings.hx @@ -1,7 +1,6 @@ package flash.security; -@:native("flash.security.SignerTrustSettings") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SignerTrustSettings(String) +@:native("flash.security.SignerTrustSettings") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SignerTrustSettings(String) { var CODE_SIGNING = "codeSigning"; var PLAYLIST_SIGNING = "playlistSigning"; diff --git a/externs/air/flash/text/AutoCapitalize.hx b/externs/air/flash/text/AutoCapitalize.hx index e0bb0169df..472737f7cd 100644 --- a/externs/air/flash/text/AutoCapitalize.hx +++ b/externs/air/flash/text/AutoCapitalize.hx @@ -1,7 +1,6 @@ package flash.text; -@:native("flash.text.AutoCapitalize") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract AutoCapitalize(String) +@:native("flash.text.AutoCapitalize") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract AutoCapitalize(String) { var ALL = "all"; var NONE = "none"; diff --git a/externs/air/flash/text/ReturnKeyLabel.hx b/externs/air/flash/text/ReturnKeyLabel.hx index 3936c1722e..573d048ee3 100644 --- a/externs/air/flash/text/ReturnKeyLabel.hx +++ b/externs/air/flash/text/ReturnKeyLabel.hx @@ -1,7 +1,6 @@ package flash.text; -@:native("flash.text.ReturnKeyLabel") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract ReturnKeyLabel(String) +@:native("flash.text.ReturnKeyLabel") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract ReturnKeyLabel(String) { var DEFAULT = "default"; var DONE = "done"; diff --git a/externs/air/flash/text/SoftKeyboardType.hx b/externs/air/flash/text/SoftKeyboardType.hx index 3a9f747dc4..a6ff23fddb 100644 --- a/externs/air/flash/text/SoftKeyboardType.hx +++ b/externs/air/flash/text/SoftKeyboardType.hx @@ -1,7 +1,6 @@ package flash.text; -@:native("flash.text.SoftKeyboardType") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SoftKeyboardType(String) +@:native("flash.text.SoftKeyboardType") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract SoftKeyboardType(String) { var CONTACT = "contact"; var DEFAULT = "default"; diff --git a/externs/air/flash/text/StageTextClearButtonMode.hx b/externs/air/flash/text/StageTextClearButtonMode.hx index 21f09ecb2b..ade3888cb0 100644 --- a/externs/air/flash/text/StageTextClearButtonMode.hx +++ b/externs/air/flash/text/StageTextClearButtonMode.hx @@ -1,7 +1,6 @@ package flash.text; -@:native("flash.text.StageTextClearButtonMode") -#if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract StageTextClearButtonMode(String) +@:native("flash.text.StageTextClearButtonMode") #if (haxe_ver >= 4.0) extern enum #else @:extern @:enum #end abstract StageTextClearButtonMode(String) { var ALWAYS = "always"; var NEVER = "never"; diff --git a/externs/air/sys/io/FileInput.hx b/externs/air/sys/io/FileInput.hx index 93e2f3b19d..7e3558ed28 100644 --- a/externs/air/sys/io/FileInput.hx +++ b/externs/air/sys/io/FileInput.hx @@ -34,9 +34,9 @@ class FileInput extends haxe.io.Input switch (pos) { case SeekBegin: - // this.pos = p; + // this.pos = p; case SeekEnd: - // this.pos = cast Fs.fstatSync(fd).size + p; + // this.pos = cast Fs.fstatSync(fd).size + p; case SeekCur: // this.pos += p; } diff --git a/externs/air/sys/io/FileOutput.hx b/externs/air/sys/io/FileOutput.hx index cdd3ba2414..e0e96e9d57 100644 --- a/externs/air/sys/io/FileOutput.hx +++ b/externs/air/sys/io/FileOutput.hx @@ -31,9 +31,9 @@ class FileOutput extends haxe.io.Output switch (pos) { case SeekBegin: - // this.pos = p; + // this.pos = p; case SeekEnd: - // this.pos = cast Fs.fstatSync(fd).size + p; + // this.pos = cast Fs.fstatSync(fd).size + p; case SeekCur: // this.pos += p; } diff --git a/include.xml b/include.xml index 04f641e3c6..4f210d3678 100644 --- a/include.xml +++ b/include.xml @@ -24,6 +24,8 @@ + + @@ -108,6 +110,8 @@ + + diff --git a/project/Build.xml b/project/Build.xml index ef9bc7458d..86e0c1ba51 100755 --- a/project/Build.xml +++ b/project/Build.xml @@ -35,10 +35,13 @@ + + + @@ -59,6 +62,10 @@ + + + + @@ -236,6 +243,7 @@ + @@ -267,9 +275,15 @@ + + + + + + @@ -328,6 +342,12 @@ + + + + + + @@ -347,9 +367,11 @@ + + @@ -448,6 +470,7 @@ + @@ -472,6 +495,7 @@
+ @@ -498,9 +522,11 @@ + + diff --git a/project/BuildHashlink.xml b/project/BuildHashlink.xml index 7123640ee1..caa5c9f9f1 100644 --- a/project/BuildHashlink.xml +++ b/project/BuildHashlink.xml @@ -1,5 +1,7 @@ - +
+ +
@@ -114,7 +116,14 @@ - +
+
+ +
+
+ +
+
@@ -127,10 +136,21 @@ - - - - +
+
+ + + + +
+
+ + + + +
+
+ @@ -257,21 +277,37 @@
- +
+
+ +
+
+ +
+
+ + + + + + + + + @@ -284,13 +320,10 @@ - - - @@ -318,6 +351,7 @@
+
@@ -332,11 +366,13 @@ - - - - +
+ + + + +
@@ -352,8 +388,11 @@ - - + +
+ + +
@@ -363,10 +402,20 @@ - - - - +
+
+ + + + +
+
+ + + + +
+
@@ -408,7 +457,14 @@ - +
+
+ +
+
+ +
+
@@ -416,11 +472,15 @@
+ + + - - + + +
diff --git a/project/include/app/Application.h b/project/include/app/Application.h index e7e5e7114a..59a6200d43 100644 --- a/project/include/app/Application.h +++ b/project/include/app/Application.h @@ -22,6 +22,7 @@ namespace lime { virtual int Quit () = 0; virtual void SetMainLoop (int profile, double frameRate, int timePrecision, int busyWait, int uncapMode) { SetFrameRate (frameRate); } virtual void SetFrameRate (double frameRate) = 0; + virtual bool SetMenu (const unsigned char* data, int length) { return false; } virtual void SetVSyncMode (int vsyncMode) {} virtual bool Update () = 0; @@ -31,6 +32,9 @@ namespace lime { Application* CreateApplication (); + // Called before CreateApplication where the windows allow high DPI + void AllowHighDPI (); + } diff --git a/project/include/graphics/vulkan/VKRenderer.h b/project/include/graphics/vulkan/VKRenderer.h new file mode 100644 index 0000000000..ae2b0b8f69 --- /dev/null +++ b/project/include/graphics/vulkan/VKRenderer.h @@ -0,0 +1,160 @@ +#ifndef LIME_GRAPHICS_VULKAN_VKRENDERER_H +#define LIME_GRAPHICS_VULKAN_VKRENDERER_H + +#include +#include +#include +#include +#include + + +namespace lime { + + + class VulkanRenderer { + + public: + + explicit VulkanRenderer (Window* window); + ~VulkanRenderer (); + + bool ClearOverlay (); + bool Create (const char* applicationName); + void Destroy (); + bool Render (float red, float green, float blue, float alpha); + bool Resize (); + bool SetOverlay (const unsigned char* data, int width, int height, int x, int y); + + inline const std::string& GetInfo () const { return info; } + inline const std::string& GetLastError () const { return lastError; } + + private: + + bool CreateCommandBuffers (); + bool CreateCommandPool (); + bool CreateDevice (); + bool CreateFramebuffers (); + bool CreateImageViews (); + bool CreateInstance (const char* applicationName); + bool CreateOverlayBuffer (size_t requiredSize); + bool CreateRenderPass (); + bool CreateSurface (); + bool CreateSwapchain (); + bool CreateSwapchainResources (); + bool CreateSyncObjects (); + void DestroyOverlayResources (); + void DestroySwapchainResources (); + uint32_t FindMemoryType (uint32_t typeFilter, VkMemoryPropertyFlags properties); + bool LoadDeviceProcs (); + bool LoadGlobalProcs (); + bool LoadInstanceProcs (); + bool PickPhysicalDevice (); + bool RecordCommandBuffer (uint32_t imageIndex, float red, float green, float blue, float alpha); + bool RecreateSwapchain (); + void SetError (const std::string& value); + + Window* window; + std::string info; + std::string lastError; + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; + PFN_vkAllocateMemory vkAllocateMemory; + PFN_vkBindBufferMemory vkBindBufferMemory; + PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage; + PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier; + PFN_vkCreateBuffer vkCreateBuffer; + PFN_vkCreateDevice vkCreateDevice; + PFN_vkCreateFence vkCreateFence; + PFN_vkCreateFramebuffer vkCreateFramebuffer; + PFN_vkCreateImageView vkCreateImageView; + PFN_vkCreateInstance vkCreateInstance; + PFN_vkCreateRenderPass vkCreateRenderPass; + PFN_vkCreateSemaphore vkCreateSemaphore; + PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR; + PFN_vkCreateCommandPool vkCreateCommandPool; + PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers; + PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR; + PFN_vkBeginCommandBuffer vkBeginCommandBuffer; + PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass; + PFN_vkCmdEndRenderPass vkCmdEndRenderPass; + PFN_vkDestroyCommandPool vkDestroyCommandPool; + PFN_vkDestroyBuffer vkDestroyBuffer; + PFN_vkDestroyDevice vkDestroyDevice; + PFN_vkDestroyFence vkDestroyFence; + PFN_vkDestroyFramebuffer vkDestroyFramebuffer; + PFN_vkDestroyImageView vkDestroyImageView; + PFN_vkDestroyInstance vkDestroyInstance; + PFN_vkDestroyRenderPass vkDestroyRenderPass; + PFN_vkDestroySemaphore vkDestroySemaphore; + PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR; + PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR; + PFN_vkDeviceWaitIdle vkDeviceWaitIdle; + PFN_vkEndCommandBuffer vkEndCommandBuffer; + PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties; + PFN_vkFreeMemory vkFreeMemory; + PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; + PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties; + PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion; + PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices; + PFN_vkGetDeviceQueue vkGetDeviceQueue; + PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; + PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; + PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties; + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR; + PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR; + PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR; + PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR; + PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR; + PFN_vkMapMemory vkMapMemory; + PFN_vkQueuePresentKHR vkQueuePresentKHR; + PFN_vkQueueSubmit vkQueueSubmit; + PFN_vkResetCommandPool vkResetCommandPool; + PFN_vkResetFences vkResetFences; + PFN_vkUnmapMemory vkUnmapMemory; + PFN_vkWaitForFences vkWaitForFences; + + VkInstance instance; + VkSurfaceKHR surface; + VkPhysicalDevice physicalDevice; + VkPhysicalDeviceProperties physicalDeviceProperties; + VkDevice device; + VkQueue queue; + uint32_t queueFamilyIndex; + VkSwapchainKHR swapchain; + VkFormat swapchainFormat; + VkPresentModeKHR swapchainPresentMode; + int requestedVSyncMode; + VkExtent2D swapchainExtent; + VkRenderPass renderPass; + VkCommandPool commandPool; + VkSemaphore imageAvailableSemaphore; + VkSemaphore renderFinishedSemaphore; + VkFence inFlightFence; + VkBuffer overlayBuffer; + VkDeviceMemory overlayBufferMemory; + bool portabilityEnumerationSupported; + bool portabilitySubsetSupported; + bool swapchainDirty; + size_t overlayBufferCapacity; + unsigned char* overlayMappedData; + uint32_t overlayWidth; + uint32_t overlayHeight; + int overlayX; + int overlayY; + bool overlayEnabled; + + std::vector instanceExtensions; + std::vector deviceExtensions; + std::vector swapchainImages; + std::vector swapchainImageViews; + std::vector framebuffers; + std::vector commandBuffers; + + }; + + +} + + +#endif diff --git a/project/include/media/AudioBuffer.h b/project/include/media/AudioBuffer.h index 5f9a8130e0..5cd3e476d9 100644 --- a/project/include/media/AudioBuffer.h +++ b/project/include/media/AudioBuffer.h @@ -40,6 +40,7 @@ namespace lime { vdynamic* __srcAudio; vdynamic* __srcBuffer; + int __srcBufferContext; vdynamic* __srcCustom; vdynamic* __srcHowl; bool __isDisposed; @@ -63,4 +64,4 @@ namespace lime { } -#endif \ No newline at end of file +#endif diff --git a/project/include/media/VideoDecoder.h b/project/include/media/VideoDecoder.h new file mode 100644 index 0000000000..27a667bd9b --- /dev/null +++ b/project/include/media/VideoDecoder.h @@ -0,0 +1,412 @@ +#ifndef LIME_MEDIA_VIDEO_DECODER_H +#define LIME_MEDIA_VIDEO_DECODER_H + + +#include +#include +#include +#include +#include +#include +#include + +#if defined (HX_WINDOWS) && !defined (HX_WINRT) +#define LIME_VIDEO_MEDIA_FOUNDATION +#elif defined (HX_MACOS) || (defined (IPHONE) && !defined (APPLETV)) +#define LIME_VIDEO_AVFOUNDATION +#elif defined (HX_ANDROID) +#define LIME_VIDEO_MEDIACODEC +#elif defined (HX_LINUX) && !defined (EMSCRIPTEN) +#define LIME_VIDEO_GSTREAMER +#endif + + +namespace lime { + + + enum VideoColorMatrix { + + VIDEO_COLOR_MATRIX_BT601 = 0, + VIDEO_COLOR_MATRIX_BT709 = 1, + VIDEO_COLOR_MATRIX_BT2020 = 2 + + }; + + + // How a frame's values are turned into light + enum VideoColorTransfer { + + // Standard range, as BT.709 and BT.601 video are + VIDEO_COLOR_TRANSFER_SDR = 0, + + // SMPTE ST 2084, which HDR10 video uses + VIDEO_COLOR_TRANSFER_PQ = 1, + + // Hybrid log-gamma, ARIB STD-B67, which phones record HDR video in + VIDEO_COLOR_TRANSFER_HLG = 2 + + }; + + + enum VideoDecodeResult { + + // Nothing was decoded, as a seek is waiting, so the call is made again + VIDEO_DECODE_AGAIN = 2, + VIDEO_DECODE_OK = 1, + VIDEO_DECODE_END = 0, + VIDEO_DECODE_ERROR = -1 + + }; + + + enum VideoFrameFormat { + + VIDEO_FRAME_FORMAT_NV12 = 0, + VIDEO_FRAME_FORMAT_RGBA = 1, + VIDEO_FRAME_FORMAT_TEXTURE = 2, + VIDEO_FRAME_FORMAT_BGRA = 3, + VIDEO_FRAME_FORMAT_ARGB = 4 + + }; + + + enum VideoReadResult { + + VIDEO_READ_NOT_READY = 0, + VIDEO_READ_END = -1, + VIDEO_READ_ERROR = -2, + VIDEO_READ_BUFFER_TOO_SMALL = -3 + + }; + + + // A decoded picture in system memory. NV12 sets u to the interleaved UV + // plane, v to u + 1 and uvPixelStride to 2. I420 uses separate U and V + // planes with a uvPixelStride of 1. + struct VideoPlanes { + + VideoColorMatrix colorMatrix; + bool fullRange; + int height; + + // VideoDecoder tone maps frames with an HDR transfer, and BT.2020 + // primaries, to standard range, unless the backend already has + VideoColorTransfer transfer; + + const unsigned char* u; + int uvPixelStride; + int uvStride; + const unsigned char* v; + int width; + const unsigned char* y; + int yStride; + + }; + + + // Shows HDR frames in standard range, writing them as BT.709 video range + // NV12 through a table of what each luma and chroma become, which is built + // once for each kind of frame and kept + class VideoToneMapper { + + + public: + + // Points planes at a tone mapped copy, which stays valid until the next + // call + void Map (VideoPlanes* planes); + + + private: + + std::vector buffer; + + + }; + + + // A decoded frame kept on the GPU by a backend that supports texture output + class VideoTextureFrame { + + + public: + + virtual ~VideoTextureFrame () {}; + + int height; + int width; + + + }; + + + // Where resampling has reached, carried from one packet of audio to the next + struct VideoAudioConverter { + + VideoAudioConverter () : position (0), previousChannels (0) { previous[0] = previous[1] = 0; } + + void Reset () { position = 0; previousChannels = 0; } + + double position; + short previous[2]; + int previousChannels; + + }; + + + struct VideoStreamInfo { + + int audioChannels; + int audioSampleRate; + + // Set when the file has a stream of that kind that cannot be decoded, such + // as one in a codec the platform lacks, so the video plays without it + bool audioUnsupported; + + double duration; + double frameRate; + bool hasAudio; + bool hasVideo; + int height; + + // The width of a pixel relative to its height, or 0 when unknown + double pixelAspectRatio; + + // The clockwise rotation in degrees that shows frames upright, as phones + // record portrait video sideways with a rotation to apply + int rotation; + + bool videoUnsupported; + int width; + + }; + + + // Platform decoding. VideoDecoder calls DecodeAudio and DecodeVideo from + // two threads at once, so the streams must not share decoder state. Open, + // Close and Seek are only called while neither stream is decoding. + class VideoBackend { + + + public: + + virtual ~VideoBackend () {}; + + virtual void Close () = 0; + + // Appends the next packet of interleaved signed 16-bit PCM with + // info->audioChannels channels to pcm, and sets time to the time of + // its first sample in seconds. + virtual VideoDecodeResult DecodeAudio (std::vector* pcm, double* time) = 0; + + // Decodes the next frame. The planes stay valid until the next call to + // DecodeVideo, Seek or Close. + virtual VideoDecodeResult DecodeVideo (VideoPlanes* planes, double* time, double* duration) = 0; + + // Called from another thread before Close, so a stream waiting on a + // stalled network source returns from DecodeAudio or DecodeVideo soon. + virtual void Interrupt () {} + + virtual bool Open (const char* path, bool hardwareDecoding, VideoStreamInfo* info) = 0; + + // Called from another thread when a seek is waiting for the streams, so + // a stream waiting on a stalled network source returns + // VIDEO_DECODE_AGAIN from DecodeAudio or DecodeVideo soon, rather than + // holding up the seek. Seek ends it, whether it succeeds or not. + virtual void PrepareSeek () {} + + // Seeks both streams to the keyframe at or before time. + virtual bool Seek (double time) = 0; + + // Texture output keeps frames on the GPU, which backends support by + // returning true from SupportsTextures once open. DecodeVideoTexture + // decodes the next frame like DecodeVideo, on the video thread. It + // returns VIDEO_DECODE_OK without a frame when it stops supporting + // textures, and the next frame is read with DecodeVideo. + virtual VideoDecodeResult DecodeVideoTexture (VideoTextureFrame** frame, double* time, double* duration) { return VIDEO_DECODE_ERROR; } + virtual bool SupportsTextures () { return false; } + + // With the application's GL context current, makes a frame available + // as an RGBA GL_TEXTURE_2D until UnlockTexture, and returns its name, + // or 0 when this context cannot share the backend's textures. + virtual unsigned int LockTexture (VideoTextureFrame* frame) { return 0; } + virtual void UnlockTexture (VideoTextureFrame* frame) {} + + // Gives a frame back to the backend, from any thread. + virtual void ReleaseTextureFrame (VideoTextureFrame* frame) {} + + // Called before Open with whether frames will be read as textures, for + // backends that decode differently for texture output. When the + // application's context cannot use the backend's textures, it is + // called with false, followed by a Seek to where reading continues. + virtual void SetTextureOutput (bool enabled) {} + + // Deletes the backend's GL objects, with the GL context current. + virtual void ReleaseTextures () {} + + // Deletes the GL objects that backends of this kind left when they closed + // away from the GL thread, where their context is current. Called on the + // GL thread before each texture frame is read. + virtual void ReleaseOrphans () {} + + static VideoBackend* Create (); + static bool IsSupported (); + + // Appends interleaved signed 16-bit PCM with channels channels at rate to + // pcm, mixed to outChannels, 1 or 2, and resampled to outRate, for a + // stream whose format changes after opening + static void ConvertAudio (const short* in, int frames, int channels, int rate, int outChannels, int outRate, VideoAudioConverter* converter, std::vector* pcm); + + static void DownmixToStereo (const short* in, int frames, int channels, std::vector* pcm); + + // The GL context current on this thread, so backends can tell when the + // application's context is replaced, as Android does after losing it, + // or 0 when it is not known + static void* GetCurrentGLContext (); + + static VideoColorMatrix GetDefaultColorMatrix (int width, int height); + + // Turns a rotation in degrees into 0, 90, 180 or 270 + static int NormalizeRotation (int degrees); + + + }; + + + // Decodes a video on two background threads, one per stream, and queues + // a few frames and a fraction of a second of audio ahead of the reader. + class VideoDecoder { + + + public: + + VideoDecoder (VideoBackend* backend); + ~VideoDecoder (); + + void Close (); + bool Open (const char* path, bool hardwareDecoding, VideoFrameFormat format); + + // Returns the number of bytes written, VIDEO_READ_NOT_READY when no + // audio is buffered yet, VIDEO_READ_END or VIDEO_READ_ERROR. Audio is + // interleaved signed 16-bit PCM. + int ReadAudio (unsigned char* data, int length); + + // Copies the next frame in the open format and returns the number of + // bytes written. When time is not negative, frames that ended by time + // are dropped and a frame is only returned once it starts by time. + // Returns VIDEO_READ_NOT_READY when no frame is due, VIDEO_READ_END, + // VIDEO_READ_ERROR, or VIDEO_READ_BUFFER_TOO_SMALL when the frame needs + // frameLength bytes. The frame then stays queued. + // + // With VIDEO_FRAME_FORMAT_TEXTURE, call it with the GL context current. + // It returns 1 for a new frame, which frameTexture shows until the next + // call. Close the decoder on that thread too, so it can delete textures. + int ReadFrame (unsigned char* data, int length, double time); + + // Seeks both streams. An accurate seek drops the frames and audio + // before time, otherwise reading resumes from the preceding keyframe. + // Seeking to the end or past it ends both streams. Returns false when + // the backend cannot seek, such as on a live stream, and decoding then + // carries on from where it was. + bool Seek (double time, bool accurate); + + static int GetFrameLength (VideoFrameFormat format, int width, int height); + static int WriteFrame (const VideoPlanes* planes, VideoFrameFormat format, unsigned char* data, int length); + + std::atomic audioTime; + std::atomic dropLateFrames; + std::atomic droppedFrames; + VideoFrameFormat format; + VideoColorMatrix frameColorMatrix; + double frameDuration; + bool frameFullRange; + int frameHeight; + int frameLength; + unsigned int frameTexture; + double frameTime; + int frameWidth; + VideoStreamInfo info; + + private: + + struct AudioChunk { + + std::vector data; + size_t offset; + double time; + + }; + + struct Frame { + + VideoColorMatrix colorMatrix; + std::vector data; + double duration; + bool fullRange; + int height; + int length; + VideoTextureFrame* texture; + double time; + int width; + + }; + + void AudioThread (); + void Flush (); + bool IsTextureThread (); + int ReadTexture (double time); + void ReleaseFrame (Frame* frame); + void ReleaseTextures (); + bool SeekStreams (double time, bool accurate, bool keepAudio); + void StartThreads (); + void StopThreads (); + void ThreadStopped (); + unsigned int UploadTexture (Frame* frame); + void VideoThread (); + + std::deque audioChunks; + std::condition_variable audioCondition; + std::mutex audioDecodeMutex; + bool audioEnded; + bool audioError; + size_t audioQueued; + double audioQueueEnd; + double audioSeekTime; + std::thread audioThread; + VideoBackend* backend; + Frame* currentFrame; + std::vector freeChunks; + std::vector freeFrames; + std::deque frames; + std::chrono::steady_clock::time_point lastQueued; + std::mutex mutex; + bool open; + double presentationTime; + bool quit; + int runningThreads; + bool seekAccurate; + bool seekPending; + std::condition_variable stoppedCondition; + unsigned int texture; + void* textureContext; + int textureHeight; + std::atomic texturesFailed; + std::thread::id textureThread; + bool textureThreadKnown; + int textureWidth; + VideoToneMapper toneMapper; + std::condition_variable videoCondition; + std::mutex videoDecodeMutex; + bool videoEnded; + bool videoError; + double videoSeekTime; + std::thread videoThread; + + + }; + + +} + + +#endif diff --git a/project/include/system/CFFI.h b/project/include/system/CFFI.h index ef22f49b9e..3442ea4a54 100644 --- a/project/include/system/CFFI.h +++ b/project/include/system/CFFI.h @@ -4,6 +4,8 @@ #define HL_NAME(n) lime_##n #include +// avoid conflicts with windows.h use of _GUID +#undef _GUID #undef DEFINE_PRIM #define DEFINE_HL_PRIM(t, name, args) DEFINE_PRIM_WITH_NAME(t, name, args, name) diff --git a/project/include/system/Locale.h b/project/include/system/Locale.h index 21e0f7a6f5..702e18035e 100644 --- a/project/include/system/Locale.h +++ b/project/include/system/Locale.h @@ -13,6 +13,7 @@ namespace lime { public: static std::string* GetSystemLocale (); + static std::string* GetPreferredLocales (); }; diff --git a/project/include/system/OrientationEvent.h b/project/include/system/OrientationEvent.h index 09b078159a..399ef68b9a 100644 --- a/project/include/system/OrientationEvent.h +++ b/project/include/system/OrientationEvent.h @@ -12,7 +12,10 @@ namespace lime { enum OrientationEventType { DISPLAY_ORIENTATION_CHANGE, - DEVICE_ORIENTATION_CHANGE + DEVICE_ORIENTATION_CHANGE, + DISPLAY_CONNECT, + DISPLAY_DISCONNECT, + DISPLAY_MOVE }; diff --git a/project/include/text/Font.h b/project/include/text/Font.h index 931f453a4b..b760f536fe 100644 --- a/project/include/text/Font.h +++ b/project/include/text/Font.h @@ -59,10 +59,16 @@ namespace lime { int GetStrikethroughPosition (); int GetStrikethroughThickness (); int GetUnitsPerEM (); + bool IsBold (); + bool IsItalic (); int RenderGlyph (int index, Bytes *bytes, int offset = 0); int RenderGlyphWithFlags (int index, int loadFlags, Bytes *bytes, int offset = 0); int RenderGlyphs (value indices, Bytes *bytes); + int RenderGlyphs (hl_varray* indices, Bytes *bytes); + int RenderGlyphs (const int* indices, int count, Bytes *bytes); void SetSize (size_t size, size_t dpi); + void SetStemDarkening (bool enable); + void SetStemDarkeningParameters (int params[]); void* library; void* face; diff --git a/project/include/ui/DockIcon.h b/project/include/ui/DockIcon.h new file mode 100644 index 0000000000..3d0de0dce6 --- /dev/null +++ b/project/include/ui/DockIcon.h @@ -0,0 +1,28 @@ +#ifndef LIME_UI_DOCK_ICON_H +#define LIME_UI_DOCK_ICON_H + + +#include + + +namespace lime { + + + class DockIcon { + + + public: + + static void Bounce (bool critical); + static bool IsSupported (); + static void SetIcon (ImageBuffer* imageBuffer); + static bool SetMenu (const unsigned char* data, int length); + + + }; + + +} + + +#endif diff --git a/project/include/ui/FileDialog.h b/project/include/ui/FileDialog.h index 4a310b3198..1adf918f7b 100644 --- a/project/include/ui/FileDialog.h +++ b/project/include/ui/FileDialog.h @@ -9,14 +9,36 @@ namespace lime { + // Functions in this class return pointers to externally owned memory, which will be reused automatically + // for future dialogs and should not be freed. class FileDialog { public: - static std::wstring* OpenDirectory (std::wstring* title = 0, std::wstring* filter = 0, std::wstring* defaultPath = 0); - static std::wstring* OpenFile (std::wstring* title = 0, std::wstring* filter = 0, std::wstring* defaultPath = 0); - static void OpenFiles (std::vector* files, std::wstring* title = 0, std::wstring* filter = 0, std::wstring* defaultPath = 0); - static std::wstring* SaveFile (std::wstring* title = 0, std::wstring* filter = 0, std::wstring* defaultPath = 0); +#ifdef HX_WINDOWS + using char_t = wchar_t; +#else + using char_t = char; +#endif + + static const char_t* OpenDirectory(const char_t* title = nullptr, const char_t* filter = nullptr, const char_t* defaultPath = nullptr); + static const char_t* OpenFile(const char_t* title = nullptr, const char_t* filter = nullptr, const char_t* defaultPath = nullptr); + + // Use actual string view once we can use c++17 + class string_view { + public: + string_view(const char_t* c_str, size_t length) : c_str(c_str), length(length) {} + + const char_t* data() const { return c_str; } + size_t size() const { return length; } + + private: + const char_t* c_str; + size_t length; + }; + + static std::vector OpenFiles(const char_t* title = nullptr, const char_t* filter = nullptr, const char_t* defaultPath = nullptr); + static const char_t* SaveFile(const char_t* title = nullptr, const char_t* filter = nullptr, const char_t* defaultPath = nullptr); }; @@ -24,4 +46,4 @@ namespace lime { } -#endif \ No newline at end of file +#endif diff --git a/project/include/ui/Gamepad.h b/project/include/ui/Gamepad.h index 3396ca5e3e..3164ae73f4 100644 --- a/project/include/ui/Gamepad.h +++ b/project/include/ui/Gamepad.h @@ -13,6 +13,18 @@ namespace lime { static const char* GetDeviceGUID (int id); static const char* GetDeviceName (int id); static void Rumble (int id, double lowFrequencyRumble, double highFrequencyRumble, int duration); + static int GetType (int id); + static bool HasLED (int id); + static bool HasRumbleTriggers (int id); + static bool HasSensor (int id, int sensor); + static bool RumbleTriggers (int id, double leftRumble, double rightRumble, int duration); + static bool SetLED (int id, int red, int green, int blue); + static bool SetSensorEnabled (int id, int sensor, bool enabled); + + // A virtual gamepad for Lime's tests, which keeps what it is told to do + static int AttachVirtual (); + static void DetachVirtual (int id); + static bool GetVirtualOutput (int id, int* led, int* lowRumble, int* highRumble, int* leftRumble, int* rightRumble); }; diff --git a/project/include/ui/GamepadEvent.h b/project/include/ui/GamepadEvent.h index 607ef9cfc8..0223869c83 100644 --- a/project/include/ui/GamepadEvent.h +++ b/project/include/ui/GamepadEvent.h @@ -15,7 +15,11 @@ namespace lime { GAMEPAD_BUTTON_DOWN, GAMEPAD_BUTTON_UP, GAMEPAD_CONNECT, - GAMEPAD_DISCONNECT + GAMEPAD_DISCONNECT, + GAMEPAD_SENSOR_UPDATE, + GAMEPAD_TOUCHPAD_DOWN, + GAMEPAD_TOUCHPAD_MOVE, + GAMEPAD_TOUCHPAD_UP }; @@ -28,6 +32,10 @@ namespace lime { int id; GamepadEventType type; double axisValue; + int timestamp; + double x; + double y; + double z; static ValuePointer* callback; static ValuePointer* eventObject; @@ -42,4 +50,4 @@ namespace lime { } -#endif \ No newline at end of file +#endif diff --git a/project/include/ui/KeyEvent.h b/project/include/ui/KeyEvent.h index 28e6fcc6fd..8d5ac047c0 100644 --- a/project/include/ui/KeyEvent.h +++ b/project/include/ui/KeyEvent.h @@ -25,6 +25,7 @@ namespace lime { int modifier; KeyEventType type; int windowID; + int timestamp; static ValuePointer* callback; static ValuePointer* eventObject; @@ -39,4 +40,4 @@ namespace lime { } -#endif \ No newline at end of file +#endif diff --git a/project/include/ui/Menu.h b/project/include/ui/Menu.h new file mode 100644 index 0000000000..93ab6cd49f --- /dev/null +++ b/project/include/ui/Menu.h @@ -0,0 +1,29 @@ +#ifndef LIME_UI_MENU_H +#define LIME_UI_MENU_H + + +namespace lime { + + + enum MenuSupport { + + MENU_SUPPORT_POPUP = 0x01, + MENU_SUPPORT_WINDOW = 0x02, + MENU_SUPPORT_APPLICATION = 0x04 + + }; + + + class Menu { + + public: + + static int GetSupport (); + + }; + + +} + + +#endif diff --git a/project/include/ui/MenuEvent.h b/project/include/ui/MenuEvent.h new file mode 100644 index 0000000000..1dde1b3daf --- /dev/null +++ b/project/include/ui/MenuEvent.h @@ -0,0 +1,40 @@ +#ifndef LIME_UI_MENU_EVENT_H +#define LIME_UI_MENU_EVENT_H + + +#include +#include + + +namespace lime { + + + enum MenuEventType { + + MENU_SELECT, + MENU_DOCK_SELECT + + }; + + + struct MenuEvent { + + hl_type* t; + int id; + MenuEventType type; + int windowID; + + static ValuePointer* callback; + static ValuePointer* eventObject; + + MenuEvent (); + + static void Dispatch (MenuEvent* event); + + }; + + +} + + +#endif diff --git a/project/include/ui/TrayIcon.h b/project/include/ui/TrayIcon.h new file mode 100644 index 0000000000..94751e16f6 --- /dev/null +++ b/project/include/ui/TrayIcon.h @@ -0,0 +1,37 @@ +#ifndef LIME_UI_TRAY_ICON_H +#define LIME_UI_TRAY_ICON_H + + +#include + + +namespace lime { + + + class TrayIcon { + + + public: + + virtual ~TrayIcon () {}; + + virtual void Close () = 0; + virtual int GetID () = 0; + virtual int PopupMenu (const unsigned char* data, int length) = 0; + virtual void SetIcon (ImageBuffer* imageBuffer) = 0; + virtual bool SetMenu (const unsigned char* data, int length) = 0; + virtual void SetTooltip (const char* tooltip) = 0; + + static bool IsSupported (); + + + }; + + + TrayIcon* CreateTrayIcon (); + + +} + + +#endif diff --git a/project/include/ui/TrayIconEvent.h b/project/include/ui/TrayIconEvent.h new file mode 100644 index 0000000000..adcbf481c0 --- /dev/null +++ b/project/include/ui/TrayIconEvent.h @@ -0,0 +1,41 @@ +#ifndef LIME_UI_TRAY_ICON_EVENT_H +#define LIME_UI_TRAY_ICON_EVENT_H + + +#include +#include + + +namespace lime { + + + enum TrayIconEventType { + + TRAY_ICON_CLICK, + TRAY_ICON_MENU_SELECT, + TRAY_ICON_RIGHT_CLICK + + }; + + + struct TrayIconEvent { + + hl_type* t; + int id; + int itemID; + TrayIconEventType type; + + static ValuePointer* callback; + static ValuePointer* eventObject; + + TrayIconEvent (); + + static void Dispatch (TrayIconEvent* event); + + }; + + +} + + +#endif diff --git a/project/include/ui/Window.h b/project/include/ui/Window.h index 04321be0b2..7c2c67fa78 100644 --- a/project/include/ui/Window.h +++ b/project/include/ui/Window.h @@ -11,8 +11,13 @@ #include #include #include +#include #include +#ifdef CreateWindow +#undef CreateWindow +#endif + namespace lime { @@ -38,14 +43,19 @@ namespace lime { virtual void GetDisplayMode (DisplayMode* displayMode) = 0; virtual int GetHeight () = 0; virtual uint32_t GetID () = 0; + virtual bool Flash (int mode) { return false; } + virtual bool GetKeyboardGrab () { return false; } virtual bool GetMouseLock () = 0; + virtual bool GetMouseRect (int* x, int* y, int* width, int* height) { return false; } virtual float GetOpacity () = 0; virtual double GetScale () = 0; virtual bool GetTextInputEnabled () = 0; + virtual bool GetTransparent () { return false; } virtual int GetWidth () = 0; virtual int GetX () = 0; virtual int GetY () = 0; virtual void Move (int x, int y) = 0; + virtual int PopupMenu (const unsigned char* data, int length, int x, int y, bool atCursor) { return 0; } virtual void ReadPixels (ImageBuffer *buffer, Rectangle *rect) = 0; virtual void Resize (int width, int height) = 0; virtual void SetMinimumSize (int width, int height) = 0; @@ -56,8 +66,11 @@ namespace lime { virtual bool SetFullscreen (bool fullscreen) = 0; virtual void SetIcon (ImageBuffer *imageBuffer) = 0; virtual bool SetMaximized (bool minimized) = 0; + virtual bool SetMenu (const unsigned char* data, int length) { return false; } virtual bool SetMinimized (bool minimized) = 0; + virtual void SetKeyboardGrab (bool keyboardGrab) {} virtual void SetMouseLock (bool mouseLock) = 0; + virtual void SetMouseRect (int x, int y, int width, int height) {} virtual void SetOpacity (float opacity) = 0; virtual bool SetResizable (bool resizable) = 0; virtual void SetTextInputEnabled (bool enable) = 0; @@ -68,7 +81,17 @@ namespace lime { virtual bool SetAlwaysOnTop (bool alwaysOnTop) = 0; virtual void WarpMouse (int x, int y) = 0; virtual int GetVSyncInterval () const { return 0; } + virtual int GetRequestedVSyncMode () const { return 0; } virtual double GetRefreshRate () const { return 60.0; } + virtual uint64_t CreateVulkanSurface (uintptr_t instance) { return 0; } + virtual void GetVulkanDrawableSize (int* width, int* height) { + + if (width) *width = 0; + if (height) *height = 0; + + } + virtual bool GetVulkanInstanceExtensions (unsigned int* count, const char** names) { return false; } + virtual void* GetVulkanInstanceProcAddr () { return 0; } Application* currentApplication; int flags; @@ -98,7 +121,9 @@ namespace lime { WINDOW_FLAG_MINIMIZED = 0x00002000, WINDOW_FLAG_MAXIMIZED = 0x00004000, WINDOW_FLAG_ALWAYS_ON_TOP = 0x00008000, - WINDOW_FLAG_COLOR_DEPTH_32_BIT = 0x00010000 + WINDOW_FLAG_COLOR_DEPTH_32_BIT = 0x00010000, + WINDOW_FLAG_VULKAN = 0x00020000, + WINDOW_FLAG_TRANSPARENT = 0x00040000 }; diff --git a/project/lib/custom/curl/lib/curl_config.h b/project/lib/custom/curl/lib/curl_config.h index deb42364f0..a03ae89f37 100644 --- a/project/lib/custom/curl/lib/curl_config.h +++ b/project/lib/custom/curl/lib/curl_config.h @@ -987,7 +987,11 @@ /* #undef USE_SCHANNEL */ /* if you want POSIX threaded DNS lookup */ -/* #define USE_THREADS_POSIX 1 */ +/* Lime looks names up on a thread, so a slow lookup doesn't hold up + curl_multi_perform, and with it the application's collector */ +#ifndef EMSCRIPTEN +#define USE_THREADS_POSIX 1 +#endif /* if you want Win32 threaded DNS lookup */ /* #undef USE_THREADS_WIN32 */ diff --git a/project/lib/custom/pixman/config.h b/project/lib/custom/pixman/pixman-config.h similarity index 87% rename from project/lib/custom/pixman/config.h rename to project/lib/custom/pixman/pixman-config.h index 0fa8ab453f..02ffd50113 100644 --- a/project/lib/custom/pixman/config.h +++ b/project/lib/custom/pixman/pixman-config.h @@ -15,9 +15,6 @@ 3. Update the version number(s) if they've changed. **/ -/* Define if building universal (internal helper macro) */ -/* #undef AC_APPLE_UNIVERSAL_BUILD */ - /* Whether we have alarm() */ #ifdef HX_LINUX #define HAVE_ALARM 1 @@ -32,13 +29,6 @@ /* #undef HAVE_BUILTIN_CLZ */ #endif -/* Define to 1 if you have the header file. */ -#ifndef HX_WINDOWS -#define HAVE_DLFCN_H 1 -#else -/* #undef HAVE_DLFCN_H */ -#endif - /* Whether we have FE_DIVBYZERO */ #ifdef HX_LINUX #define HAVE_FEDIVBYZERO 1 @@ -108,13 +98,6 @@ /* #undef HAVE_LIBPNG */ #endif -/* Define to 1 if you have the header file. */ -#ifdef HX_LINUX -#define HAVE_MEMORY_H 1 -#else -/* #undef HAVE_MEMORY_H */ -#endif - /* Whether we have mmap() */ #ifdef HX_LINUX #define HAVE_MMAP 1 @@ -200,24 +183,6 @@ /* Name of package */ #define PACKAGE "pixman" -/* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT "pixman@lists.freedesktop.org" - -/* Define to the full name of this package. */ -#define PACKAGE_NAME "pixman" - -/* Define to the full name and version of this package. */ -#define PACKAGE_STRING "pixman 0.42.2" - -/* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "pixman" - -/* Define to the home page for this package. */ -#define PACKAGE_URL "" - -/* Define to the version of this package. */ -#define PACKAGE_VERSION "0.42.2" - /* enable output that can be piped to gnuplot */ /* #undef PIXMAN_GNUPLOT */ @@ -252,6 +217,12 @@ /* #undef TOOLCHAIN_SUPPORTS_ATTRIBUTE_CONSTRUCTOR */ #endif +#define ASM_HAVE_SYNTAX_UNIFIED + +#if defined(__APPLE__) +#define ASM_LEADING_UNDERSCORE 1 +#endif + /* use ARM A64_NEON assembly optimizations */ /* #undef USE_ARM_A64_NEON */ @@ -316,9 +287,6 @@ /* #undef USE_X86_MMX */ #endif -/* Version number of package */ -#define VERSION "0.42.2" - /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel). */ #if defined AC_APPLE_UNIVERSAL_BUILD @@ -336,6 +304,3 @@ #ifndef __cplusplus /* #undef inline */ #endif - -/* Define to sqrt if you do not have the `sqrtf' function. */ -/* #undef sqrtf */ diff --git a/project/lib/custom/pixman/pixman/pixman-version.h b/project/lib/custom/pixman/pixman/pixman-version.h index 23d30b5500..5767975fcd 100644 --- a/project/lib/custom/pixman/pixman/pixman-version.h +++ b/project/lib/custom/pixman/pixman/pixman-version.h @@ -32,10 +32,10 @@ #endif #define PIXMAN_VERSION_MAJOR 0 -#define PIXMAN_VERSION_MINOR 40 -#define PIXMAN_VERSION_MICRO 1 +#define PIXMAN_VERSION_MINOR 44 +#define PIXMAN_VERSION_MICRO 2 -#define PIXMAN_VERSION_STRING "0.40.1" +#define PIXMAN_VERSION_STRING "0.44.2" #define PIXMAN_VERSION_ENCODE(major, minor, micro) ( \ ((major) * 10000) \ diff --git a/project/lib/custom/sdl/include/SDL_config_linux.h b/project/lib/custom/sdl/include/SDL_config_linux.h index fc5213ebc9..56ddd33d80 100644 --- a/project/lib/custom/sdl/include/SDL_config_linux.h +++ b/project/lib/custom/sdl/include/SDL_config_linux.h @@ -153,6 +153,7 @@ #define HAVE_SIGACTION 1 #define HAVE_SETJMP 1 #define HAVE_NANOSLEEP 1 +#define HAVE_CLOCK_GETTIME 1 #define HAVE_SYSCONF 1 /* #undef HAVE_SYSCTLBYNAME */ @@ -219,6 +220,7 @@ /* #undef SDL_JOYSTICK_DUMMY */ /* #undef SDL_JOYSTICK_IOKIT */ #define SDL_JOYSTICK_LINUX 1 +#define SDL_JOYSTICK_VIRTUAL 1 /* #undef SDL_JOYSTICK_HIDAPI */ /* #undef SDL_JOYSTICK_WINMM */ /* #undef SDL_JOYSTICK_USBHID */ @@ -273,7 +275,7 @@ /* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR */ #define SDL_VIDEO_DRIVER_X11_DYNAMIC_XINERAMA "libXinerama.so.1" #define SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 "libXi.so.6" -#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR "libxrandr.so.2" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR "libXrandr.so.2" /* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS */ /* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XVIDMODE */ /* #undef SDL_VIDEO_DRIVER_X11_XCURSOR */ @@ -308,6 +310,9 @@ /* #undef SDL_VIDEO_OPENGL_OSMESA */ /* #undef SDL_VIDEO_OPENGL_OSMESA_DYNAMIC */ +/* Enable Vulkan support */ +#define SDL_VIDEO_VULKAN 1 + /* Enable system power support */ #define SDL_POWER_LINUX 1 /* #undef SDL_POWER_WINDOWS */ diff --git a/project/lib/custom/sdl/include/SDL_config_macosx.h b/project/lib/custom/sdl/include/SDL_config_macosx.h index cda601ebd0..0ecb977f00 100644 --- a/project/lib/custom/sdl/include/SDL_config_macosx.h +++ b/project/lib/custom/sdl/include/SDL_config_macosx.h @@ -141,6 +141,7 @@ /* Enable various input drivers */ #define SDL_JOYSTICK_IOKIT 1 #define SDL_JOYSTICK_HIDAPI 1 +#define SDL_JOYSTICK_VIRTUAL 1 #define SDL_HAPTIC_IOKIT 1 /* Enable the dummy sensor driver */ diff --git a/project/lib/custom/sdl/include/SDL_config_windows.h b/project/lib/custom/sdl/include/SDL_config_windows.h index 35515de00c..f03077f801 100644 --- a/project/lib/custom/sdl/include/SDL_config_windows.h +++ b/project/lib/custom/sdl/include/SDL_config_windows.h @@ -170,6 +170,7 @@ typedef unsigned int uintptr_t; /* These functions were added with the VC++ 2013 C runtime library */ #if _MSC_VER >= 1800 #define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 #define HAVE_VSSCANF 1 #define HAVE_SCALBN 1 #define HAVE_SCALBNF 1 @@ -198,6 +199,7 @@ typedef unsigned int uintptr_t; #define SDL_JOYSTICK_DINPUT 1 #define SDL_JOYSTICK_XINPUT 1 #define SDL_JOYSTICK_HIDAPI 1 +#define SDL_JOYSTICK_VIRTUAL 1 #define SDL_HAPTIC_DINPUT 1 #define SDL_HAPTIC_XINPUT 1 diff --git a/project/lib/hashlink b/project/lib/hashlink index 03110f7e11..c3f2a1563c 160000 --- a/project/lib/hashlink +++ b/project/lib/hashlink @@ -1 +1 @@ -Subproject commit 03110f7e11fb76acf13312864b1d0bb700a6eda4 +Subproject commit c3f2a1563cf7aa9f4076b0808a7eaf40e18e8f14 diff --git a/project/lib/pixman b/project/lib/pixman index 37216a3283..46655e1567 160000 --- a/project/lib/pixman +++ b/project/lib/pixman @@ -1 +1 @@ -Subproject commit 37216a32839f59e8dcaa4c3951b3fcfc3f07852c +Subproject commit 46655e15671e1ca62d699821d75a85d341d7229c diff --git a/project/lib/pixman-files.xml b/project/lib/pixman-files.xml index ef98744568..cd266b1f0a 100644 --- a/project/lib/pixman-files.xml +++ b/project/lib/pixman-files.xml @@ -1,16 +1,12 @@ - - - + + -
- - -
+ @@ -22,6 +18,7 @@ + @@ -39,7 +36,9 @@ - + + + @@ -73,6 +72,7 @@ +
diff --git a/project/lib/sdl b/project/lib/sdl index 8236e01a9f..5d24957039 160000 --- a/project/lib/sdl +++ b/project/lib/sdl @@ -1 +1 @@ -Subproject commit 8236e01a9f758d15927624925c6043f84d8a261f +Subproject commit 5d249570393f7a37e037abf22cd6012a4cc56a71 diff --git a/project/lib/sdl-files.xml b/project/lib/sdl-files.xml index 0df3db5431..c07cadd23e 100755 --- a/project/lib/sdl-files.xml +++ b/project/lib/sdl-files.xml @@ -359,6 +359,7 @@ + @@ -389,7 +390,6 @@ - diff --git a/project/src/ExternalInterface.cpp b/project/src/ExternalInterface.cpp index 0fcb20e6e7..09e55596d6 100644 --- a/project/src/ExternalInterface.cpp +++ b/project/src/ExternalInterface.cpp @@ -9,6 +9,9 @@ #include #include +#ifdef LIME_SDL +#include "backend/sdl/SDLApplication.h" +#endif #include #include #include @@ -16,6 +19,9 @@ #include #include #include +#ifdef LIME_VULKAN +#include +#endif #include #include #include @@ -31,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -40,9 +47,13 @@ #include #include #include +#include +#include #include #include #include +#include +#include #include #include #include @@ -55,8 +66,12 @@ #endif #include +#include #include +#include #include +#include +#include #ifdef LIME_SDL_SOUND #include @@ -67,3470 +82,13140 @@ DEFINE_KIND (k_finalizer); namespace lime { + static std::string lastVulkanRendererError; + static std::string lastVKError; - void gc_application (value handle) { - - Application* application = (Application*)val_data (handle); - delete application; - } + static void LogVulkanNativeBootstrap (const char* message) { +#ifdef LIME_VULKAN + ::FILE* file = ::fopen ("lime-vulkan-native.log", "a"); + if (file) { - void hl_gc_application (HL_CFFIPointer* handle) { + ::fprintf (file, "%s\n", message ? message : ""); + ::fclose (file); - Application* application = (Application*)handle->ptr; - delete application; + } +#endif } - void gc_file_watcher (value handle) { + static void LogVulkanNativeBootstrap (const std::string& message) { - #ifdef LIME_EFSW - FileWatcher* watcher = (FileWatcher*)val_data (handle); - delete watcher; - #endif + LogVulkanNativeBootstrap (message.c_str ()); } - void hl_gc_file_watcher (HL_CFFIPointer* handle) { + static uint64_t CombineVulkanHandle (int high, int low) { - #ifdef LIME_EFSW - FileWatcher* watcher = (FileWatcher*)handle->ptr; - delete watcher; - #endif + return ((uint64_t)(uint32_t)high << 32) | (uint32_t)low; } - void gc_font (value handle) { + static value CreateVulkanHandleValue (uint64_t handleValue) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (handle); - delete font; - #endif + value result = alloc_empty_object (); + alloc_field (result, val_id ("low"), alloc_int ((int32_t)(handleValue & 0xFFFFFFFFULL))); + alloc_field (result, val_id ("high"), alloc_int ((int32_t)(handleValue >> 32))); + return result; } - void hl_gc_font (HL_CFFIPointer* handle) { + static vdynamic* HLCreateVulkanHandleValue (uint64_t handleValue) { - #ifdef LIME_FREETYPE - Font* font = (Font*)handle->ptr; - delete font; - #endif + const int id_low = hl_hash_utf8 ("low"); + const int id_high = hl_hash_utf8 ("high"); + vdynamic* result = (vdynamic*)hl_alloc_dynobj (); + hl_dyn_seti (result, id_low, &hlt_i32, (int32_t)(handleValue & 0xFFFFFFFFULL)); + hl_dyn_seti (result, id_high, &hlt_i32, (int32_t)(handleValue >> 32)); + return result; } - void gc_window (value handle) { +#ifdef LIME_VULKAN + static VkSurfaceKHR UInt64ToVulkanSurface (uint64_t value) { - Window* window = (Window*)val_data (handle); - delete window; + return (VkSurfaceKHR)value; } - void hl_gc_window (HL_CFFIPointer* handle) { - - Window* window = (Window*)handle->ptr; - delete window; + struct ManagedVulkanMappedMemory { - } + VkDevice device; + VkDeviceMemory memory; + VkDeviceSize offset; + VkDeviceSize size; + void* data; + }; - std::string wstring_utf8 (const std::wstring& val) { - std::string out; - unsigned int codepoint = 0; + static std::unordered_map mappedVulkanMemory; + static std::unordered_map > managedVulkanDeviceProcCache; - for (const wchar_t chr : val) { - if (chr >= 0xd800 && chr <= 0xdbff) { + static uint64_t GetManagedVulkanMappedMemoryKey (VkDeviceMemory memory) { - codepoint = ((chr - 0xd800) << 10) + 0x10000; + return (uint64_t)(uintptr_t)memory; - } else { + } - if (chr >= 0xdc00 && chr <= 0xdfff) { - codepoint |= chr - 0xdc00; + static VkDeviceSize NormalizeVulkanRangeSize (uint64_t size) { - } else { + return size == 0 ? VK_WHOLE_SIZE : (VkDeviceSize)size; - codepoint = chr; + } - } - if (codepoint <= 0x7f) { + static void ClearManagedVulkanDeviceProcCache (VkDevice device) { - out.append (1, static_cast (codepoint)); + if (device) managedVulkanDeviceProcCache.erase ((uint64_t)(uintptr_t)device); - } else if (codepoint <= 0x7ff) { + } - out.append (1, static_cast (0xc0 | ((codepoint >> 6) & 0x1f))); - out.append (1, static_cast (0x80 | (codepoint & 0x3f))); - } else if (codepoint <= 0xffff) { + static bool CreateManagedVulkanInstance (Window* targetWindow, const char* applicationName, VkInstance* outInstance) { - out.append (1, static_cast (0xe0 | ((codepoint >> 12) & 0x0f))); - out.append (1, static_cast (0x80 | ((codepoint >> 6) & 0x3f))); - out.append (1, static_cast (0x80 | (codepoint & 0x3f))); + if (!targetWindow || !outInstance) { - } else { + lastVKError = "Missing Vulkan window or output instance"; + return false; - out.append (1, static_cast (0xf0 | ((codepoint >> 18) & 0x07))); - out.append (1, static_cast (0x80 | ((codepoint >> 12) & 0x3f))); - out.append (1, static_cast (0x80 | ((codepoint >> 6) & 0x3f))); - out.append (1, static_cast (0x80 | (codepoint & 0x3f))); + } - } + *outInstance = VK_NULL_HANDLE; - codepoint = 0; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr (); + if (!vkGetInstanceProcAddr) { - } + lastVKError = "SDL did not expose vkGetInstanceProcAddr"; + return false; } - return out; + PFN_vkCreateInstance vkCreateInstance = (PFN_vkCreateInstance)vkGetInstanceProcAddr (VK_NULL_HANDLE, "vkCreateInstance"); + PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties = + (PFN_vkEnumerateInstanceExtensionProperties)vkGetInstanceProcAddr (VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"); + PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion = + (PFN_vkEnumerateInstanceVersion)vkGetInstanceProcAddr (VK_NULL_HANDLE, "vkEnumerateInstanceVersion"); - } + if (!vkCreateInstance || !vkEnumerateInstanceExtensionProperties) { + lastVKError = "Missing required Vulkan global functions"; + return false; - vbyte* hl_wstring_to_utf8_bytes (const std::wstring& val) { + } - const std::string utf8 (wstring_utf8 (val)); - vbyte* const bytes = hl_alloc_bytes (utf8.size () + 1); - std::memcpy(bytes, utf8.c_str (), utf8.size () + 1); - return bytes; + unsigned int requiredExtensionCount = 0; + if (!targetWindow->GetVulkanInstanceExtensions (&requiredExtensionCount, 0)) { - } + lastVKError = "Failed to query required Vulkan window extensions"; + return false; + } - std::wstring* hxstring_to_wstring (HxString val) { + std::vector requiredExtensions (requiredExtensionCount); + if (requiredExtensionCount > 0 && !targetWindow->GetVulkanInstanceExtensions (&requiredExtensionCount, requiredExtensions.data ())) { - if (val.c_str ()) { + lastVKError = "Failed to fetch required Vulkan window extensions"; + return false; - #ifdef HX_WINDOWS - return new std::wstring (hxs_wchar (val, nullptr)); - #else - const std::string _val (hxs_utf8 (val, nullptr)); - return new std::wstring (_val.begin (), _val.end ()); - #endif + } - } else { + std::vector instanceExtensions; + for (size_t i = 0; i < requiredExtensions.size (); ++i) { - return 0; + instanceExtensions.push_back (requiredExtensions[i]); } - } - - - std::wstring* hxstring_to_wstring (hl_vstring* val) { + bool portabilityEnumerationSupported = false; + uint32_t availableExtensionCount = 0; + if (vkEnumerateInstanceExtensionProperties (0, &availableExtensionCount, 0) == VK_SUCCESS && availableExtensionCount > 0) { - if (val) { + std::vector availableExtensions (availableExtensionCount); + if (vkEnumerateInstanceExtensionProperties (0, &availableExtensionCount, availableExtensions.data ()) == VK_SUCCESS) { - std::string _val = std::string (hl_to_utf8 (val->bytes)); - #ifdef HX_WINDOWS - std::wstring_convert> converter; - return new std::wstring (converter.from_bytes (_val)); - #else - return new std::wstring (_val.begin (), _val.end ()); - #endif + for (size_t i = 0; i < availableExtensions.size (); ++i) { - } else { + #ifdef VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME + if (strcmp (availableExtensions[i].extensionName, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) == 0) { - return 0; + portabilityEnumerationSupported = true; - } + } + #endif - } + } + } - value wstring_to_value (std::wstring* val) { + } - if (val) { + if (portabilityEnumerationSupported) { - #ifdef HX_WINDOWS - return alloc_wstring (val->c_str ()); - #else - std::string _val = std::string (val->begin (), val->end ()); - return alloc_string (_val.c_str ()); + #ifdef VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME + instanceExtensions.push_back (VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); #endif - } else { + } - return 0; + uint32_t instanceVersion = VK_API_VERSION_1_0; + if (vkEnumerateInstanceVersion) { + + vkEnumerateInstanceVersion (&instanceVersion); } - } + VkApplicationInfo applicationInfo; + memset (&applicationInfo, 0, sizeof (applicationInfo)); + applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + applicationInfo.pApplicationName = applicationName ? applicationName : "Lime"; + applicationInfo.pEngineName = "Lime"; + applicationInfo.applicationVersion = VK_MAKE_VERSION (1, 0, 0); + applicationInfo.engineVersion = VK_MAKE_VERSION (1, 0, 0); + applicationInfo.apiVersion = instanceVersion >= VK_API_VERSION_1_0 ? VK_API_VERSION_1_0 : instanceVersion; + VkInstanceCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &applicationInfo; + createInfo.enabledExtensionCount = (uint32_t)instanceExtensions.size (); + createInfo.ppEnabledExtensionNames = instanceExtensions.empty () ? 0 : instanceExtensions.data (); - value lime_application_create () { + #ifdef VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR + if (portabilityEnumerationSupported) { - Application* application = CreateApplication (); - return CFFIPointer (application, gc_application); + createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; - } + } + #endif + VkResult result = vkCreateInstance (&createInfo, 0, outInstance); + if (result != VK_SUCCESS) { - HL_PRIM HL_CFFIPointer* HL_NAME(hl_application_create) () { + lastVKError = "vkCreateInstance failed"; + return false; - Application* application = CreateApplication (); - return HLCFFIPointer (application, (hl_finalizer)hl_gc_application); + } + + lastVKError.clear (); + return true; } - void lime_application_event_manager_register (value callback, value eventObject) { + static std::vector GetVulkanDeviceExtensions (value extensions) { - ApplicationEvent::callback = new ValuePointer (callback); - ApplicationEvent::eventObject = new ValuePointer (eventObject); + std::vector deviceExtensions; - } + if (val_is_null (extensions)) { + deviceExtensions.push_back (VK_KHR_SWAPCHAIN_EXTENSION_NAME); + return deviceExtensions; - HL_PRIM void HL_NAME(hl_application_event_manager_register) (vclosure* callback, ApplicationEvent* eventObject) { + } - ApplicationEvent::callback = new ValuePointer (callback); - ApplicationEvent::eventObject = new ValuePointer ((vobj*)eventObject); + int length = val_array_size (extensions); + for (int i = 0; i < length; ++i) { - } + value extension = val_array_i (extensions, i); + if (!val_is_null (extension)) { + deviceExtensions.push_back (val_string (extension)); - int lime_application_exec (value application) { + } - Application* app = (Application*)val_data (application); - return app->Exec (); + } + + return deviceExtensions; } - HL_PRIM int HL_NAME(hl_application_exec) (HL_CFFIPointer* application) { + static std::vector GetHLVulkanDeviceExtensions (hl_varray* extensions) { - Application* app = (Application*)application->ptr; - return app->Exec (); + std::vector deviceExtensions; - } + if (!extensions) { + deviceExtensions.push_back (VK_KHR_SWAPCHAIN_EXTENSION_NAME); + return deviceExtensions; - void lime_application_init (value application) { + } - Application* app = (Application*)val_data (application); - app->Init (); + int length = extensions->size; + hl_vstring** extensionData = hl_aptr (extensions, hl_vstring*); - } + for (int i = 0; i < length; ++i) { + hl_vstring* extension = *extensionData++; + if (extension) { - HL_PRIM void HL_NAME(hl_application_init) (HL_CFFIPointer* application) { + deviceExtensions.push_back (hl_to_utf8 ((const uchar*)extension->bytes)); - Application* app = (Application*)application->ptr; - app->Init (); + } - } + } + return deviceExtensions; - int lime_application_quit (value application) { + } - Application* app = (Application*)val_data (application); - return app->Quit (); - } + static PFN_vkVoidFunction GetManagedVulkanDeviceProc (Window* targetWindow, VkInstance instance, VkDevice device, const char* name) { + if (!targetWindow || !instance || !device || !name) { - HL_PRIM int HL_NAME(hl_application_quit) (HL_CFFIPointer* application) { + lastVKError = "Missing Vulkan window, instance, or device"; + return 0; - Application* app = (Application*)application->ptr; - return app->Quit (); + } - } + uint64_t deviceKey = (uint64_t)(uintptr_t)device; + std::string procName (name); + std::unordered_map >::iterator deviceCache = + managedVulkanDeviceProcCache.find (deviceKey); + if (deviceCache != managedVulkanDeviceProcCache.end ()) { + std::unordered_map::iterator procCache = deviceCache->second.find (procName); + if (procCache != deviceCache->second.end ()) return procCache->second; - void lime_application_set_frame_rate (value application, double frameRate) { + } - Application* app = (Application*)val_data (application); - app->SetFrameRate (frameRate); + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr (); + if (!vkGetInstanceProcAddr) { - } + lastVKError = "SDL did not expose vkGetInstanceProcAddr"; + return 0; + } - HL_PRIM void HL_NAME(hl_application_set_frame_rate) (HL_CFFIPointer* application, double frameRate) { + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr (instance, "vkGetDeviceProcAddr"); + if (!vkGetDeviceProcAddr) { - Application* app = (Application*)application->ptr; - app->SetFrameRate (frameRate); + lastVKError = "Missing Vulkan device proc address function"; + return 0; - } + } + PFN_vkVoidFunction proc = vkGetDeviceProcAddr (device, procName.c_str ()); + if (!proc) { - void lime_application_set_main_loop (value application, int profile, double frameRate, int timePrecision, int busyWait, int uncapMode) { + lastVKError = std::string ("Missing Vulkan device function: ") + procName; + return 0; - Application* app = (Application*)val_data (application); - app->SetMainLoop (profile, frameRate, timePrecision, busyWait, uncapMode); + } - } + managedVulkanDeviceProcCache[deviceKey][procName] = proc; + return proc; + } - HL_PRIM void HL_NAME(hl_application_set_main_loop) (HL_CFFIPointer* application, int profile, double frameRate, int timePrecision, int busyWait, int uncapMode) { - Application* app = (Application*)application->ptr; - app->SetMainLoop (profile, frameRate, timePrecision, busyWait, uncapMode); + static bool UpdateManagedVulkanDescriptorSets (Window* targetWindow, VkInstance instance, VkDevice device, const std::vector& packed) { - } + if (packed.empty ()) return true; + PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets = + (PFN_vkUpdateDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUpdateDescriptorSets"); + if (!vkUpdateDescriptorSets) return false; - void lime_application_set_vsync_mode (value application, int vsyncMode) { + int writeCount = packed[0]; + if (writeCount < 0) return false; - Application* app = (Application*)val_data (application); - app->SetVSyncMode (vsyncMode); + std::vector bufferInfos; + std::vector imageInfos; + std::vector descriptorWrites; + bufferInfos.reserve ((size_t)writeCount); + imageInfos.reserve ((size_t)writeCount); + descriptorWrites.reserve ((size_t)writeCount); - } + size_t index = 1; + for (int i = 0; i < writeCount; ++i) { + if (index >= packed.size ()) return false; + int kind = packed[index++]; - HL_PRIM void HL_NAME(hl_application_set_vsync_mode) (HL_CFFIPointer* application, int vsyncMode) { + VkWriteDescriptorSet write; + memset (&write, 0, sizeof (write)); + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.descriptorCount = 1; - Application* app = (Application*)application->ptr; - app->SetVSyncMode (vsyncMode); + if (kind == 0) { - } + if (index + 11 > packed.size ()) return false; + write.dstSet = (VkDescriptorSet)(uintptr_t)CombineVulkanHandle (packed[index], packed[index + 1]); + write.dstBinding = (uint32_t)packed[index + 2]; + write.dstArrayElement = (uint32_t)packed[index + 3]; + write.descriptorType = (VkDescriptorType)packed[index + 4]; + VkDescriptorBufferInfo bufferInfo; + memset (&bufferInfo, 0, sizeof (bufferInfo)); + bufferInfo.buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (packed[index + 5], packed[index + 6]); + bufferInfo.offset = (VkDeviceSize)CombineVulkanHandle (packed[index + 7], packed[index + 8]); + bufferInfo.range = NormalizeVulkanRangeSize (CombineVulkanHandle (packed[index + 9], packed[index + 10])); + bufferInfos.push_back (bufferInfo); + write.pBufferInfo = &bufferInfos[bufferInfos.size () - 1]; + index += 11; - bool lime_application_update (value application) { + } else if (kind == 1) { - Application* app = (Application*)val_data (application); - return app->Update (); + if (index + 10 > packed.size ()) return false; + write.dstSet = (VkDescriptorSet)(uintptr_t)CombineVulkanHandle (packed[index], packed[index + 1]); + write.dstBinding = (uint32_t)packed[index + 2]; + write.dstArrayElement = (uint32_t)packed[index + 3]; + write.descriptorType = (VkDescriptorType)packed[index + 4]; - } + VkDescriptorImageInfo imageInfo; + memset (&imageInfo, 0, sizeof (imageInfo)); + imageInfo.imageView = (VkImageView)(uintptr_t)CombineVulkanHandle (packed[index + 5], packed[index + 6]); + imageInfo.sampler = (VkSampler)(uintptr_t)CombineVulkanHandle (packed[index + 7], packed[index + 8]); + imageInfo.imageLayout = (VkImageLayout)packed[index + 9]; + imageInfos.push_back (imageInfo); + write.pImageInfo = &imageInfos[imageInfos.size () - 1]; + index += 10; + } else { - HL_PRIM bool HL_NAME(hl_application_update) (HL_CFFIPointer* application) { + lastVKError = "Invalid Vulkan descriptor write kind"; + return false; - Application* app = (Application*)application->ptr; - return app->Update (); + } - } + if (!write.dstSet) return false; + descriptorWrites.push_back (write); + } - value lime_audio_load_bytes (value data, value buffer) { + vkUpdateDescriptorSets (device, (uint32_t)descriptorWrites.size (), descriptorWrites.empty () ? 0 : descriptorWrites.data (), 0, 0); + lastVKError.clear (); + return true; - Resource resource; - Bytes bytes; + } - AudioBuffer audioBuffer = AudioBuffer (buffer); - bytes.Set (data); - resource = Resource (&bytes); + static bool CreateManagedVulkanDevice (Window* targetWindow, VkInstance instance, VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, + const std::vector& requestedExtensions, VkDevice* outDevice, VkQueue* outQueue) { - #ifdef LIME_SDL_SOUND - if (SDLSound::Decode (&resource, &audioBuffer)) { + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: begin"); + if (!targetWindow || !instance || !physicalDevice || !outDevice || !outQueue) { - return audioBuffer.Value (buffer); + lastVKError = "Missing Vulkan instance, physical device, or output device"; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: missing input"); + return false; } - #endif + *outDevice = VK_NULL_HANDLE; + *outQueue = VK_NULL_HANDLE; - if (WAV::Decode (&resource, &audioBuffer)) { + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr (); + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: acquired vkGetInstanceProcAddr"); + if (!vkGetInstanceProcAddr) { - return audioBuffer.Value (buffer); + lastVKError = "SDL did not expose vkGetInstanceProcAddr"; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: missing vkGetInstanceProcAddr"); + return false; } - #ifdef LIME_OGG - if (OGG::Decode (&resource, &audioBuffer)) { + PFN_vkCreateDevice vkCreateDevice = (PFN_vkCreateDevice)vkGetInstanceProcAddr (instance, "vkCreateDevice"); + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr (instance, "vkGetDeviceProcAddr"); + PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties = + (PFN_vkEnumerateDeviceExtensionProperties)vkGetInstanceProcAddr (instance, "vkEnumerateDeviceExtensionProperties"); + PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties = + (PFN_vkGetPhysicalDeviceQueueFamilyProperties)vkGetInstanceProcAddr (instance, "vkGetPhysicalDeviceQueueFamilyProperties"); + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: loaded device creation procs"); - return audioBuffer.Value (buffer); + if (!vkCreateDevice || !vkGetDeviceProcAddr || !vkEnumerateDeviceExtensionProperties || !vkGetPhysicalDeviceQueueFamilyProperties) { - } - #endif + lastVKError = "Missing required Vulkan device creation functions"; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: missing required procs"); + return false; - return alloc_null (); + } - } + uint32_t queueFamilyCount = 0; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: query queue family count"); + vkGetPhysicalDeviceQueueFamilyProperties (physicalDevice, &queueFamilyCount, 0); + if (queueFamilyIndex >= queueFamilyCount) { + lastVKError = "Invalid Vulkan queue family index"; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: invalid queue family index"); + return false; - HL_PRIM AudioBuffer* HL_NAME(hl_audio_load_bytes) (Bytes* data, AudioBuffer* buffer) { + } - Resource resource = Resource (data); + float queuePriority = 1.0f; + VkDeviceQueueCreateInfo queueCreateInfo; + memset (&queueCreateInfo, 0, sizeof (queueCreateInfo)); + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamilyIndex; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; - #ifdef LIME_SDL_SOUND - if (SDLSound::Decode (&resource, buffer)) { + std::vector deviceExtensions; + for (size_t i = 0; i < requestedExtensions.size (); ++i) { - return buffer; + deviceExtensions.push_back (requestedExtensions[i].c_str ()); } - #endif - if (WAV::Decode (&resource, buffer)) { + uint32_t extensionCount = 0; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: enumerate device extension count"); + VkResult extensionResult = vkEnumerateDeviceExtensionProperties (physicalDevice, 0, &extensionCount, 0); + if (extensionResult != VK_SUCCESS) { - return buffer; + lastVKError = "Could not enumerate Vulkan device extensions"; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: extension count enumeration failed"); + return false; } - #ifdef LIME_OGG - if (OGG::Decode (&resource, buffer)) { + std::vector availableExtensions (extensionCount); + if (extensionCount > 0) { - return buffer; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: enumerate device extensions"); + extensionResult = vkEnumerateDeviceExtensionProperties (physicalDevice, 0, &extensionCount, availableExtensions.data ()); + if (extensionResult != VK_SUCCESS) { + + lastVKError = "Could not enumerate Vulkan device extensions"; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: extension enumeration failed"); + return false; + + } } + + #ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME + bool portabilitySubsetSupported = false; + bool portabilitySubsetRequested = false; #endif - return 0; + for (size_t i = 0; i < requestedExtensions.size (); ++i) { - } + bool extensionSupported = false; + for (size_t e = 0; e < availableExtensions.size (); ++e) { - value lime_audio_load_file (value data, value buffer) { + if (strcmp (availableExtensions[e].extensionName, requestedExtensions[i].c_str ()) == 0) { - Resource resource; + extensionSupported = true; + break; - AudioBuffer audioBuffer = AudioBuffer (buffer); + } - resource = Resource (val_string (data)); + } - #ifdef LIME_SDL_SOUND - if (SDLSound::Decode (&resource, &audioBuffer)) { + if (!extensionSupported) { - return audioBuffer.Value (buffer); + lastVKError = std::string ("Vulkan physical device does not support ") + requestedExtensions[i]; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: requested extension missing"); + return false; - } - #endif + } - if (WAV::Decode (&resource, &audioBuffer)) { + #ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME + if (strcmp (requestedExtensions[i].c_str (), VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) == 0) { - return audioBuffer.Value (buffer); + portabilitySubsetRequested = true; - } + } + #endif - #ifdef LIME_OGG - if (OGG::Decode (&resource, &audioBuffer)) { + } - return audioBuffer.Value (buffer); + #ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME + for (size_t i = 0; i < availableExtensions.size (); ++i) { + + if (strcmp (availableExtensions[i].extensionName, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) == 0) { + + portabilitySubsetSupported = true; + break; + + } } - #endif - return alloc_null (); + if (portabilitySubsetSupported && !portabilitySubsetRequested) { - } + deviceExtensions.push_back (VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); + } + #endif - HL_PRIM AudioBuffer* HL_NAME(hl_audio_load_file) (hl_vstring* data, AudioBuffer* buffer) { + VkPhysicalDeviceFeatures deviceFeatures; + memset (&deviceFeatures, 0, sizeof (deviceFeatures)); - Resource resource = Resource (data ? hl_to_utf8 ((const uchar*)data->bytes) : NULL); + VkDeviceCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = 1; + createInfo.pQueueCreateInfos = &queueCreateInfo; + createInfo.pEnabledFeatures = &deviceFeatures; + createInfo.enabledExtensionCount = (uint32_t)deviceExtensions.size (); + createInfo.ppEnabledExtensionNames = deviceExtensions.empty () ? 0 : deviceExtensions.data (); - #ifdef LIME_SDL_SOUND - if (SDLSound::Decode (&resource, buffer)) { + VkResult result = vkCreateDevice (physicalDevice, &createInfo, 0, outDevice); + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: returned from vkCreateDevice"); + if (result != VK_SUCCESS) { - return buffer; + lastVKError = "vkCreateDevice failed"; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: vkCreateDevice failed"); + return false; } - #endif - if (WAV::Decode (&resource, buffer)) { + PFN_vkGetDeviceQueue vkGetDeviceQueue = (PFN_vkGetDeviceQueue)vkGetDeviceProcAddr (*outDevice, "vkGetDeviceQueue"); + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: loaded vkGetDeviceQueue"); + if (!vkGetDeviceQueue) { - return buffer; + PFN_vkDestroyDevice vkDestroyDevice = (PFN_vkDestroyDevice)vkGetDeviceProcAddr (*outDevice, "vkDestroyDevice"); + if (vkDestroyDevice) vkDestroyDevice (*outDevice, 0); + *outDevice = VK_NULL_HANDLE; + lastVKError = "Missing Vulkan device queue function"; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: missing vkGetDeviceQueue"); + return false; } - #ifdef LIME_OGG - if (OGG::Decode (&resource, buffer)) { + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: get device queue"); + vkGetDeviceQueue (*outDevice, queueFamilyIndex, 0, outQueue); + if (!*outQueue) { - return buffer; + PFN_vkDestroyDevice vkDestroyDevice = (PFN_vkDestroyDevice)vkGetDeviceProcAddr (*outDevice, "vkDestroyDevice"); + if (vkDestroyDevice) vkDestroyDevice (*outDevice, 0); + *outDevice = VK_NULL_HANDLE; + lastVKError = "Failed to get Vulkan device queue"; + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: device queue was null"); + return false; } - #endif - return 0; + lastVKError.clear (); + LogVulkanNativeBootstrap ("CreateManagedVulkanDevice: success"); + return true; } - value lime_audio_load (value data, value buffer) { + struct ManagedVulkanSwapchain { - if (val_is_string (data)) { + VkSwapchainKHR handle; + VkFormat format; + VkColorSpaceKHR colorSpace; + VkExtent2D extent; + VkPresentModeKHR presentMode; + uint32_t imageCount; - return lime_audio_load_file (data, buffer); + }; - } else { - return lime_audio_load_bytes (data, buffer); + static VkPresentModeKHR ChooseManagedVulkanPresentMode (const std::vector& presentModes, VkPresentModeKHR requestedPresentMode) { - } + bool hasRequested = false; + bool hasFifo = false; + bool hasMailbox = false; + bool hasImmediate = false; - } + for (size_t i = 0; i < presentModes.size (); ++i) { + if (presentModes[i] == requestedPresentMode) hasRequested = true; + if (presentModes[i] == VK_PRESENT_MODE_FIFO_KHR) hasFifo = true; + if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) hasMailbox = true; + if (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR) hasImmediate = true; - value lime_bytes_from_data_pointer (double data, int length, value _bytes) { + } - uintptr_t ptr = (uintptr_t)data; - Bytes bytes (_bytes); - bytes.Resize (length); + if (hasRequested) return requestedPresentMode; + if (hasFifo) return VK_PRESENT_MODE_FIFO_KHR; + if (hasMailbox) return VK_PRESENT_MODE_MAILBOX_KHR; + if (hasImmediate) return VK_PRESENT_MODE_IMMEDIATE_KHR; + return VK_PRESENT_MODE_FIFO_KHR; - if (ptr) { + } - memcpy (bytes.b, (const void*)ptr, length); - } + static value CreateVulkanSwapchainValue (const ManagedVulkanSwapchain& swapchain) { - return bytes.Value (_bytes); + value result = alloc_empty_object (); + alloc_field (result, val_id ("handle"), CreateVulkanHandleValue ((uint64_t)(uintptr_t)swapchain.handle)); + alloc_field (result, val_id ("format"), alloc_int ((int)swapchain.format)); + alloc_field (result, val_id ("colorSpace"), alloc_int ((int)swapchain.colorSpace)); + alloc_field (result, val_id ("width"), alloc_int ((int)swapchain.extent.width)); + alloc_field (result, val_id ("height"), alloc_int ((int)swapchain.extent.height)); + alloc_field (result, val_id ("imageCount"), alloc_int ((int)swapchain.imageCount)); + alloc_field (result, val_id ("presentMode"), alloc_int ((int)swapchain.presentMode)); + return result; } - HL_PRIM Bytes* HL_NAME(hl_bytes_from_data_pointer) (double data, int length, Bytes* bytes) { + static vdynamic* HLCreateVulkanSwapchainValue (const ManagedVulkanSwapchain& swapchain) { - uintptr_t ptr = (uintptr_t)data; - bytes->Resize (length); + const int id_handle = hl_hash_utf8 ("handle"); + const int id_format = hl_hash_utf8 ("format"); + const int id_colorSpace = hl_hash_utf8 ("colorSpace"); + const int id_width = hl_hash_utf8 ("width"); + const int id_height = hl_hash_utf8 ("height"); + const int id_imageCount = hl_hash_utf8 ("imageCount"); + const int id_presentMode = hl_hash_utf8 ("presentMode"); - if (ptr) { + vdynamic* result = (vdynamic*)hl_alloc_dynobj (); + hl_dyn_setp (result, id_handle, &hlt_dynobj, HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)swapchain.handle)); + hl_dyn_seti (result, id_format, &hlt_i32, (int)swapchain.format); + hl_dyn_seti (result, id_colorSpace, &hlt_i32, (int)swapchain.colorSpace); + hl_dyn_seti (result, id_width, &hlt_i32, (int)swapchain.extent.width); + hl_dyn_seti (result, id_height, &hlt_i32, (int)swapchain.extent.height); + hl_dyn_seti (result, id_imageCount, &hlt_i32, (int)swapchain.imageCount); + hl_dyn_seti (result, id_presentMode, &hlt_i32, (int)swapchain.presentMode); + return result; - memcpy (bytes->b, (const void*)ptr, length); + } - } - return bytes; + static value CreateVulkanAcquireValue (VkResult acquireResult, int imageIndex) { + + value result = alloc_empty_object (); + alloc_field (result, val_id ("result"), alloc_int ((int)acquireResult)); + alloc_field (result, val_id ("imageIndex"), alloc_int (imageIndex)); + return result; } - double lime_bytes_get_data_pointer (value bytes) { + static vdynamic* HLCreateVulkanAcquireValue (VkResult acquireResult, int imageIndex) { - Bytes data = Bytes (bytes); - return (uintptr_t)data.b; + const int id_result = hl_hash_utf8 ("result"); + const int id_imageIndex = hl_hash_utf8 ("imageIndex"); + + vdynamic* result = (vdynamic*)hl_alloc_dynobj (); + hl_dyn_seti (result, id_result, &hlt_i32, (int)acquireResult); + hl_dyn_seti (result, id_imageIndex, &hlt_i32, imageIndex); + return result; } - HL_PRIM double HL_NAME(hl_bytes_get_data_pointer) (Bytes* bytes) { + static value CreateVulkanMemoryRequirementsValue (const VkMemoryRequirements& requirements) { - return bytes ? (uintptr_t)bytes->b : 0; + value result = alloc_empty_object (); + alloc_field (result, val_id ("size"), CreateVulkanHandleValue ((uint64_t)requirements.size)); + alloc_field (result, val_id ("alignment"), CreateVulkanHandleValue ((uint64_t)requirements.alignment)); + alloc_field (result, val_id ("memoryTypeBits"), alloc_int ((int)requirements.memoryTypeBits)); + return result; } - double lime_bytes_get_data_pointer_offset (value bytes, int offset) { + static vdynamic* HLCreateVulkanMemoryRequirementsValue (const VkMemoryRequirements& requirements) { - if (val_is_null (bytes)) return 0; + const int id_size = hl_hash_utf8 ("size"); + const int id_alignment = hl_hash_utf8 ("alignment"); + const int id_memoryTypeBits = hl_hash_utf8 ("memoryTypeBits"); - Bytes data = Bytes (bytes); - return (uintptr_t)data.b + offset; + vdynamic* result = (vdynamic*)hl_alloc_dynobj (); + hl_dyn_setp (result, id_size, &hlt_dynobj, HLCreateVulkanHandleValue ((uint64_t)requirements.size)); + hl_dyn_setp (result, id_alignment, &hlt_dynobj, HLCreateVulkanHandleValue ((uint64_t)requirements.alignment)); + hl_dyn_seti (result, id_memoryTypeBits, &hlt_i32, (int)requirements.memoryTypeBits); + return result; } - HL_PRIM double HL_NAME(hl_bytes_get_data_pointer_offset) (Bytes* bytes, int offset) { + static value CreateVulkanMemoryValue (VkDeviceMemory memory, uint32_t memoryTypeIndex) { - if (!bytes) return 0; - return (uintptr_t)bytes->b + offset; + value result = alloc_empty_object (); + alloc_field (result, val_id ("handle"), CreateVulkanHandleValue ((uint64_t)(uintptr_t)memory)); + alloc_field (result, val_id ("memoryTypeIndex"), alloc_int ((int)memoryTypeIndex)); + return result; } - value lime_bytes_read_file (HxString path, value bytes) { + static vdynamic* HLCreateVulkanMemoryValue (VkDeviceMemory memory, uint32_t memoryTypeIndex) { - Bytes data (bytes); - data.ReadFile (hxs_utf8 (path, nullptr)); - return data.Value (bytes); + const int id_handle = hl_hash_utf8 ("handle"); + const int id_memoryTypeIndex = hl_hash_utf8 ("memoryTypeIndex"); + + vdynamic* result = (vdynamic*)hl_alloc_dynobj (); + hl_dyn_setp (result, id_handle, &hlt_dynobj, HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)memory)); + hl_dyn_seti (result, id_memoryTypeIndex, &hlt_i32, (int)memoryTypeIndex); + return result; } - HL_PRIM Bytes* HL_NAME(hl_bytes_read_file) (hl_vstring* path, Bytes* bytes) { + static std::vector GetVulkanIntVector (value data) { - if (!path) return 0; - bytes->ReadFile (hl_to_utf8 ((const uchar*)path->bytes)); - return bytes; + std::vector result; + if (val_is_null (data)) return result; - } + int length = val_array_size (data); + result.reserve (length); + for (int i = 0; i < length; ++i) { + result.push_back (val_int (val_array_i (data, i))); - double lime_cffi_get_native_pointer (value handle) { + } - return (uintptr_t)val_data (handle); + return result; } - HL_PRIM double HL_NAME(hl_cffi_get_native_pointer) (HL_CFFIPointer* handle) { + static std::vector GetHLVulkanIntVector (hl_varray* data) { - return (uintptr_t)handle->ptr; + std::vector result; + if (!data) return result; - } + result.reserve (data->size); + int* values = hl_aptr (data, int); + for (int i = 0; i < data->size; ++i) { + result.push_back (values[i]); - void lime_cffi_finalizer (value abstract) { + } - val_call0 ((value)val_data (abstract)); + return result; } - value lime_cffi_set_finalizer (value callback) { + static std::vector GetVulkanDoubleVector (value data) { - value abstract = alloc_abstract (k_finalizer, callback); - val_gc (abstract, lime_cffi_finalizer); - return abstract; + std::vector result; + if (val_is_null (data)) return result; - } + int length = val_array_size (data); + result.reserve (length); + for (int i = 0; i < length; ++i) { + result.push_back (val_number (val_array_i (data, i))); - void lime_clipboard_event_manager_register (value callback, value eventObject) { + } - ClipboardEvent::callback = new ValuePointer (callback); - ClipboardEvent::eventObject = new ValuePointer (eventObject); + return result; } - HL_PRIM void HL_NAME(hl_clipboard_event_manager_register) (vclosure* callback, ClipboardEvent* eventObject) { + static std::vector GetHLVulkanDoubleVector (hl_varray* data) { - ClipboardEvent::callback = new ValuePointer (callback); - ClipboardEvent::eventObject = new ValuePointer ((vobj*)eventObject); + std::vector result; + if (!data) return result; - } + result.reserve (data->size); + double* values = hl_aptr (data, double); + for (int i = 0; i < data->size; ++i) { + result.push_back (values[i]); - value lime_clipboard_get_text () { + } - if (Clipboard::HasText ()) { + return result; - const char* text = Clipboard::GetText (); - value _text = alloc_string (text); + } - // TODO: Should we free for all backends? (SDL requires it) - free ((char*)text); - return _text; + static bool FindManagedVulkanMemoryType (Window* targetWindow, VkInstance instance, VkPhysicalDevice physicalDevice, uint32_t typeFilter, + VkMemoryPropertyFlags properties, uint32_t* outMemoryTypeIndex) { - } else { + if (!targetWindow || !instance || !physicalDevice || !outMemoryTypeIndex) { - return alloc_null (); + lastVKError = "Missing Vulkan window, instance, physical device, or memory type output"; + return false; } - } - - - HL_PRIM vbyte* HL_NAME(hl_clipboard_get_text) () { + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr (); + if (!vkGetInstanceProcAddr) { - if (Clipboard::HasText ()) { + lastVKError = "SDL did not expose vkGetInstanceProcAddr"; + return false; - const char* text = Clipboard::GetText (); - return (vbyte*)text; + } - } else { + PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties = + (PFN_vkGetPhysicalDeviceMemoryProperties)vkGetInstanceProcAddr (instance, "vkGetPhysicalDeviceMemoryProperties"); + if (!vkGetPhysicalDeviceMemoryProperties) { - return 0; + lastVKError = "Missing Vulkan physical device memory properties function"; + return false; } - } - + VkPhysicalDeviceMemoryProperties memoryProperties; + memset (&memoryProperties, 0, sizeof (memoryProperties)); + vkGetPhysicalDeviceMemoryProperties (physicalDevice, &memoryProperties); - void lime_clipboard_set_text (HxString text) { + for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; ++i) { - Clipboard::SetText (hxs_utf8 (text, nullptr)); + if ((typeFilter & (1u << i)) && (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { - } + *outMemoryTypeIndex = i; + lastVKError.clear (); + return true; + } - HL_PRIM void HL_NAME(hl_clipboard_set_text) (hl_vstring* text) { + } - Clipboard::SetText (text ? (const char*)hl_to_utf8 ((const uchar*)text->bytes) : NULL); + lastVKError = "Failed to find a compatible Vulkan memory type"; + return false; } - double lime_data_pointer_offset (double pointer, int offset) { + static bool CreateManagedVulkanSwapchain (Window* targetWindow, VkInstance instance, VkPhysicalDevice physicalDevice, VkDevice device, + VkSurfaceKHR surface, uint32_t queueFamilyIndex, int requestedWidth, int requestedHeight, VkPresentModeKHR requestedPresentMode, + VkSwapchainKHR oldSwapchain, ManagedVulkanSwapchain* outSwapchain) { - return (uintptr_t)pointer + offset; + if (!targetWindow || !instance || !physicalDevice || !device || !surface || !outSwapchain) { - } + lastVKError = "Missing Vulkan window, instance, device, surface, or output swapchain"; + return false; + } - HL_PRIM double HL_NAME(hl_data_pointer_offset) (double pointer, int offset) { + outSwapchain->handle = VK_NULL_HANDLE; + outSwapchain->format = VK_FORMAT_UNDEFINED; + outSwapchain->colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + outSwapchain->extent.width = 0; + outSwapchain->extent.height = 0; + outSwapchain->presentMode = VK_PRESENT_MODE_FIFO_KHR; + outSwapchain->imageCount = 0; - return (uintptr_t)pointer + offset; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr (); + if (!vkGetInstanceProcAddr) { - } + lastVKError = "SDL did not expose vkGetInstanceProcAddr"; + return false; + } - value lime_deflate_compress (value buffer, value bytes) { + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR = + (PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR)vkGetInstanceProcAddr (instance, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); + PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR = + (PFN_vkGetPhysicalDeviceSurfaceFormatsKHR)vkGetInstanceProcAddr (instance, "vkGetPhysicalDeviceSurfaceFormatsKHR"); + PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR = + (PFN_vkGetPhysicalDeviceSurfacePresentModesKHR)vkGetInstanceProcAddr (instance, "vkGetPhysicalDeviceSurfacePresentModesKHR"); + PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR = + (PFN_vkGetPhysicalDeviceSurfaceSupportKHR)vkGetInstanceProcAddr (instance, "vkGetPhysicalDeviceSurfaceSupportKHR"); - #ifdef LIME_ZLIB - Bytes data (buffer); - Bytes result (bytes); + if (!vkGetPhysicalDeviceSurfaceCapabilitiesKHR || !vkGetPhysicalDeviceSurfaceFormatsKHR || !vkGetPhysicalDeviceSurfacePresentModesKHR) { - Zlib::Compress (DEFLATE, &data, &result); + lastVKError = "Missing required Vulkan surface functions"; + return false; - return result.Value (bytes); - #else - return alloc_null(); - #endif + } - } + if (vkGetPhysicalDeviceSurfaceSupportKHR) { + VkBool32 supportsPresent = VK_FALSE; + VkResult supportResult = vkGetPhysicalDeviceSurfaceSupportKHR (physicalDevice, queueFamilyIndex, surface, &supportsPresent); + if (supportResult != VK_SUCCESS || supportsPresent == VK_FALSE) { - HL_PRIM Bytes* HL_NAME(hl_deflate_compress) (Bytes* buffer, Bytes* bytes) { + lastVKError = "Vulkan queue family does not support presenting to this surface"; + return false; - #ifdef LIME_ZLIB - Zlib::Compress (DEFLATE, buffer, bytes); - return bytes; - #else - return 0; - #endif + } - } + } + VkSurfaceCapabilitiesKHR capabilities; + memset (&capabilities, 0, sizeof (capabilities)); + VkResult result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR (physicalDevice, surface, &capabilities); + if (result != VK_SUCCESS) { - value lime_deflate_decompress (value buffer, value bytes) { + lastVKError = "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed"; + return false; - #ifdef LIME_ZLIB - Bytes data (buffer); - Bytes result (bytes); + } - Zlib::Decompress (DEFLATE, &data, &result); + uint32_t formatCount = 0; + result = vkGetPhysicalDeviceSurfaceFormatsKHR (physicalDevice, surface, &formatCount, 0); + if (result != VK_SUCCESS || formatCount == 0) { - return result.Value (bytes); - #else - return alloc_null (); - #endif + lastVKError = "Failed to query Vulkan surface formats"; + return false; - } + } + std::vector formats (formatCount); + result = vkGetPhysicalDeviceSurfaceFormatsKHR (physicalDevice, surface, &formatCount, formats.data ()); + if (result != VK_SUCCESS) { - HL_PRIM Bytes* HL_NAME(hl_deflate_decompress) (Bytes* buffer, Bytes* bytes) { + lastVKError = "vkGetPhysicalDeviceSurfaceFormatsKHR failed"; + return false; - #ifdef LIME_ZLIB - Zlib::Decompress (DEFLATE, buffer, bytes); - return bytes; - #else - return 0; - #endif + } - } + VkSurfaceFormatKHR surfaceFormat = formats[0]; + if (formats.size () == 1 && formats[0].format == VK_FORMAT_UNDEFINED) { + surfaceFormat.format = VK_FORMAT_B8G8R8A8_UNORM; + surfaceFormat.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; - void lime_drop_event_manager_register (value callback, value eventObject) { + } else { - DropEvent::callback = new ValuePointer (callback); - DropEvent::eventObject = new ValuePointer (eventObject); + for (size_t i = 0; i < formats.size (); ++i) { - } + if (formats[i].format == VK_FORMAT_B8G8R8A8_UNORM && formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + surfaceFormat = formats[i]; + break; - HL_PRIM void HL_NAME(hl_drop_event_manager_register) (vclosure* callback, DropEvent* eventObject) { + } - DropEvent::callback = new ValuePointer (callback); - DropEvent::eventObject = new ValuePointer ((vobj*)eventObject); + } - } + } + uint32_t presentModeCount = 0; + result = vkGetPhysicalDeviceSurfacePresentModesKHR (physicalDevice, surface, &presentModeCount, 0); + if (result != VK_SUCCESS) { - value lime_file_dialog_open_directory (HxString title, HxString filter, HxString defaultPath) { + lastVKError = "Failed to query Vulkan present modes"; + return false; - #ifdef LIME_TINYFILEDIALOGS + } - std::wstring* _title = hxstring_to_wstring (title); - std::wstring* _filter = hxstring_to_wstring (filter); - std::wstring* _defaultPath = hxstring_to_wstring (defaultPath); + std::vector presentModes (presentModeCount > 0 ? presentModeCount : 1, VK_PRESENT_MODE_FIFO_KHR); + if (presentModeCount > 0) { - std::wstring* path = FileDialog::OpenDirectory (_title, _filter, _defaultPath); + result = vkGetPhysicalDeviceSurfacePresentModesKHR (physicalDevice, surface, &presentModeCount, presentModes.data ()); + if (result != VK_SUCCESS) { - if (_title) delete _title; - if (_filter) delete _filter; - if (_defaultPath) delete _defaultPath; + lastVKError = "vkGetPhysicalDeviceSurfacePresentModesKHR failed"; + return false; - if (path) { + } - value _path = wstring_to_value (path); - delete path; - return _path; + } - } else { + int drawableWidth = requestedWidth; + int drawableHeight = requestedHeight; + if (drawableWidth <= 0 || drawableHeight <= 0) { - return alloc_null (); + drawableWidth = 0; + drawableHeight = 0; + targetWindow->GetVulkanDrawableSize (&drawableWidth, &drawableHeight); } - #endif + if (drawableWidth <= 0 || drawableHeight <= 0) { - return alloc_null (); + lastVKError = "Vulkan drawable size is zero"; + return false; - } + } + VkExtent2D extent; + if (capabilities.currentExtent.width != UINT32_MAX) { - HL_PRIM vbyte* HL_NAME(hl_file_dialog_open_directory) (hl_vstring* title, hl_vstring* filter, hl_vstring* defaultPath) { + extent = capabilities.currentExtent; - #ifdef LIME_TINYFILEDIALOGS + } else { - std::wstring* _title = hxstring_to_wstring (title); - std::wstring* _filter = hxstring_to_wstring (filter); - std::wstring* _defaultPath = hxstring_to_wstring (defaultPath); + extent.width = (uint32_t)(std::max) ((int)capabilities.minImageExtent.width, + (std::min) (drawableWidth, (int)capabilities.maxImageExtent.width)); + extent.height = (uint32_t)(std::max) ((int)capabilities.minImageExtent.height, + (std::min) (drawableHeight, (int)capabilities.maxImageExtent.height)); - std::wstring* path = FileDialog::OpenDirectory (_title, _filter, _defaultPath); + } - if (_title) delete _title; - if (_filter) delete _filter; - if (_defaultPath) delete _defaultPath; + if ((capabilities.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) == 0) { - if (path) { + lastVKError = "Vulkan surface images do not support color attachments"; + return false; - vbyte* const result = hl_wstring_to_utf8_bytes (*path); - delete path; - return result; + } - } else { + VkImageUsageFlags imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + if (capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) { - return NULL; + imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; } + if (capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) { - #endif - - return NULL; + imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - } + } + uint32_t imageCount = capabilities.minImageCount + 1; + if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) { - value lime_file_dialog_open_file (HxString title, HxString filter, HxString defaultPath) { + imageCount = capabilities.maxImageCount; - #ifdef LIME_TINYFILEDIALOGS + } - std::wstring* _title = hxstring_to_wstring (title); - std::wstring* _filter = hxstring_to_wstring (filter); - std::wstring* _defaultPath = hxstring_to_wstring (defaultPath); + VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + const bool transparentWindow = targetWindow && ((targetWindow->flags & WINDOW_FLAG_TRANSPARENT) != 0); + const VkCompositeAlphaFlagBitsKHR opaqueCompositeAlphaFlags[] = { + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR + }; + const VkCompositeAlphaFlagBitsKHR transparentCompositeAlphaFlags[] = { + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR + }; + const VkCompositeAlphaFlagBitsKHR* compositeAlphaFlags = transparentWindow ? transparentCompositeAlphaFlags : opaqueCompositeAlphaFlags; + const size_t compositeAlphaFlagCount = transparentWindow + ? sizeof (transparentCompositeAlphaFlags) / sizeof (transparentCompositeAlphaFlags[0]) + : sizeof (opaqueCompositeAlphaFlags) / sizeof (opaqueCompositeAlphaFlags[0]); + + for (size_t i = 0; i < compositeAlphaFlagCount; ++i) { + + if (capabilities.supportedCompositeAlpha & compositeAlphaFlags[i]) { + + compositeAlpha = compositeAlphaFlags[i]; + break; - std::wstring* path = FileDialog::OpenFile (_title, _filter, _defaultPath); + } - if (_title) delete _title; - if (_filter) delete _filter; - if (_defaultPath) delete _defaultPath; + } - if (path) { + PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR = (PFN_vkCreateSwapchainKHR)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCreateSwapchainKHR"); + PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR = (PFN_vkGetSwapchainImagesKHR)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkGetSwapchainImagesKHR"); + if (!vkCreateSwapchainKHR || !vkGetSwapchainImagesKHR) return false; + + VkSwapchainCreateInfoKHR createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = imageUsage; + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.preTransform = capabilities.currentTransform; + createInfo.compositeAlpha = compositeAlpha; + createInfo.presentMode = ChooseManagedVulkanPresentMode (presentModes, requestedPresentMode); + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = oldSwapchain; + + VkSwapchainKHR swapchain = VK_NULL_HANDLE; + result = vkCreateSwapchainKHR (device, &createInfo, 0, &swapchain); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateSwapchainKHR failed"; + return false; - value _path = wstring_to_value (path); - delete path; - return _path; + } - } else { + uint32_t swapchainImageCount = 0; + result = vkGetSwapchainImagesKHR (device, swapchain, &swapchainImageCount, 0); + if (result != VK_SUCCESS || swapchainImageCount == 0) { - return alloc_null (); + PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR = (PFN_vkDestroySwapchainKHR)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroySwapchainKHR"); + if (vkDestroySwapchainKHR) vkDestroySwapchainKHR (device, swapchain, 0); + lastVKError = "Failed to query Vulkan swapchain images"; + return false; } - #endif - - return alloc_null (); + outSwapchain->handle = swapchain; + outSwapchain->format = surfaceFormat.format; + outSwapchain->colorSpace = surfaceFormat.colorSpace; + outSwapchain->extent = extent; + outSwapchain->presentMode = createInfo.presentMode; + outSwapchain->imageCount = swapchainImageCount; + lastVKError.clear (); + return true; } +#endif - HL_PRIM vbyte* HL_NAME(hl_file_dialog_open_file) (hl_vstring* title, hl_vstring* filter, hl_vstring* defaultPath) { + void gc_application (value handle) { - #ifdef LIME_TINYFILEDIALOGS + Application* application = (Application*)val_data (handle); + delete application; - std::wstring* _title = hxstring_to_wstring (title); - std::wstring* _filter = hxstring_to_wstring (filter); - std::wstring* _defaultPath = hxstring_to_wstring (defaultPath); + } - std::wstring* path = FileDialog::OpenFile (_title, _filter, _defaultPath); - if (_title) delete _title; - if (_filter) delete _filter; - if (_defaultPath) delete _defaultPath; + void hl_gc_application (HL_CFFIPointer* handle) { - if (path) { - - vbyte* const result = hl_wstring_to_utf8_bytes (*path); - delete path; - return result; + Application* application = (Application*)handle->ptr; + delete application; - } else { + } - return NULL; - } + void gc_file_watcher (value handle) { + #ifdef LIME_EFSW + FileWatcher* watcher = (FileWatcher*)val_data (handle); + delete watcher; #endif - return NULL; - } - value lime_file_dialog_open_files (HxString title, HxString filter, HxString defaultPath) { + void hl_gc_file_watcher (HL_CFFIPointer* handle) { - #ifdef LIME_TINYFILEDIALOGS + #ifdef LIME_EFSW + FileWatcher* watcher = (FileWatcher*)handle->ptr; + delete watcher; + #endif - std::wstring* _title = hxstring_to_wstring (title); - std::wstring* _filter = hxstring_to_wstring (filter); - std::wstring* _defaultPath = hxstring_to_wstring (defaultPath); + } - std::vector files; - FileDialog::OpenFiles (&files, _title, _filter, _defaultPath); - value result = alloc_array (files.size ()); + void gc_font (value handle) { - if (_title) delete _title; - if (_filter) delete _filter; - if (_defaultPath) delete _defaultPath; + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (handle); + delete font; + #endif - for (int i = 0; i < files.size (); i++) { + } - value _file = wstring_to_value (files[i]); - val_array_set_i (result, i, _file); - delete files[i]; - } + void hl_gc_font (HL_CFFIPointer* handle) { - #else - value result = alloc_array (0); + #ifdef LIME_FREETYPE + Font* font = (Font*)handle->ptr; + delete font; #endif - return result; - } - HL_PRIM hl_varray* HL_NAME(hl_file_dialog_open_files) (hl_vstring* title, hl_vstring* filter, hl_vstring* defaultPath) { - - #ifdef LIME_TINYFILEDIALOGS + void gc_tray_icon (value handle) { - std::wstring* _title = hxstring_to_wstring (title); - std::wstring* _filter = hxstring_to_wstring (filter); - std::wstring* _defaultPath = hxstring_to_wstring (defaultPath); + TrayIcon* trayIcon = (TrayIcon*)val_data (handle); + delete trayIcon; - std::vector files; + } - FileDialog::OpenFiles (&files, _title, _filter, _defaultPath); - hl_varray* result = (hl_varray*)hl_alloc_array (&hlt_bytes, files.size ()); - vbyte** resultData = hl_aptr (result, vbyte*); - if (_title) delete _title; - if (_filter) delete _filter; - if (_defaultPath) delete _defaultPath; + void hl_gc_tray_icon (HL_CFFIPointer* handle) { - for (int i = 0; i < files.size (); i++) { + TrayIcon* trayIcon = (TrayIcon*)handle->ptr; + delete trayIcon; - *resultData++ = hl_wstring_to_utf8_bytes (*files[i]); - delete files[i]; + } - } - #else - hl_varray* result = hl_alloc_array (&hlt_bytes, 0); - #endif + void gc_window (value handle) { - return result; + Window* window = (Window*)val_data (handle); + delete window; } - value lime_file_dialog_save_file (HxString title, HxString filter, HxString defaultPath) { + void hl_gc_window (HL_CFFIPointer* handle) { - #ifdef LIME_TINYFILEDIALOGS + Window* window = (Window*)handle->ptr; + delete window; + + } - std::wstring* _title = hxstring_to_wstring (title); - std::wstring* _filter = hxstring_to_wstring (filter); - std::wstring* _defaultPath = hxstring_to_wstring (defaultPath); - std::wstring* path = FileDialog::SaveFile (_title, _filter, _defaultPath); + std::string wstring_utf8 (const std::wstring& val) { - if (_title) delete _title; - if (_filter) delete _filter; - if (_defaultPath) delete _defaultPath; + std::string out; + unsigned int codepoint = 0; - if (path) { + for (const wchar_t chr : val) { - value _path = wstring_to_value (path); - delete path; - return _path; + if (chr >= 0xd800 && chr <= 0xdbff) { - } else { + codepoint = ((chr - 0xd800) << 10) + 0x10000; - return alloc_null (); + } else { - } + if (chr >= 0xdc00 && chr <= 0xdfff) { - #endif + codepoint |= chr - 0xdc00; - return alloc_null (); + } else { - } + codepoint = chr; + } - HL_PRIM vbyte* HL_NAME(hl_file_dialog_save_file) (hl_vstring* title, hl_vstring* filter, hl_vstring* defaultPath) { + if (codepoint <= 0x7f) { - #ifdef LIME_TINYFILEDIALOGS + out.append (1, static_cast (codepoint)); - std::wstring* _title = hxstring_to_wstring (title); - std::wstring* _filter = hxstring_to_wstring (filter); - std::wstring* _defaultPath = hxstring_to_wstring (defaultPath); + } else if (codepoint <= 0x7ff) { - std::wstring* path = FileDialog::SaveFile (_title, _filter, _defaultPath); + out.append (1, static_cast (0xc0 | ((codepoint >> 6) & 0x1f))); + out.append (1, static_cast (0x80 | (codepoint & 0x3f))); - if (_title) delete _title; - if (_filter) delete _filter; - if (_defaultPath) delete _defaultPath; + } else if (codepoint <= 0xffff) { - if (path) { + out.append (1, static_cast (0xe0 | ((codepoint >> 12) & 0x0f))); + out.append (1, static_cast (0x80 | ((codepoint >> 6) & 0x3f))); + out.append (1, static_cast (0x80 | (codepoint & 0x3f))); - vbyte* const result = hl_wstring_to_utf8_bytes (*path); - delete path; - return result; + } else { - } else { + out.append (1, static_cast (0xf0 | ((codepoint >> 18) & 0x07))); + out.append (1, static_cast (0x80 | ((codepoint >> 12) & 0x3f))); + out.append (1, static_cast (0x80 | ((codepoint >> 6) & 0x3f))); + out.append (1, static_cast (0x80 | (codepoint & 0x3f))); - return NULL; + } - } + codepoint = 0; - #endif + } - return NULL; + } + + return out; } - value lime_file_watcher_create (value callback) { + vbyte* hl_wstring_to_utf8_bytes (const std::wstring& val) { - #ifdef LIME_EFSW - FileWatcher* watcher = new FileWatcher (callback); - return CFFIPointer (watcher, gc_file_watcher); - #else - return alloc_null (); - #endif + const std::string utf8 (wstring_utf8 (val)); + vbyte* const bytes = hl_alloc_bytes (utf8.size () + 1); + std::memcpy(bytes, utf8.c_str (), utf8.size () + 1); + return bytes; } - HL_PRIM HL_CFFIPointer* HL_NAME(hl_file_watcher_create) (vclosure* callback) { + #ifdef HX_WINDOWS + #define hxs_to_fd_str(str) hxs_wchar(str, nullptr) + #define alloc_from_fd_str(str) alloc_wstring(str) + #define alloc_from_fd_str_len(str, len) alloc_wstring_len(str, len) - // #ifdef LIME_EFSW - // FileWatcher* watcher = new FileWatcher (callback); - // return HLCFFIPointer (watcher, (hl_finalizer)hl_gc_file_watcher); - // #else - return 0; - // #endif + static const FileDialog::char_t* hl_vstring_to_fd_str (hl_vstring* val) { + if (val) { + return val->bytes; + } + return nullptr; + } + static vbyte* hl_fd_str_to_utf8_bytes(const FileDialog::char_t* src, size_t length) { + // copy first, since hl_to_utf8 has no length parameter, and hl_utf16_to_utf8 is not exposed + FileDialog::char_t* utf16 = (FileDialog::char_t*)hl_copy_bytes((vbyte*)src, (length + 1) * sizeof(FileDialog::char_t)); + utf16[length] = L'\0'; + return (vbyte*)hl_to_utf8(utf16); } + static vbyte* hl_fd_str_to_utf8_bytes(const FileDialog::char_t* src) { + return (vbyte*)hl_to_utf8((uchar*)src); + } - value lime_file_watcher_add_directory (value handle, value path, bool recursive) { + #else + #define hxs_to_fd_str(str) hxs_utf8(str, nullptr) + #define alloc_from_fd_str(str) alloc_string(str) + #define alloc_from_fd_str_len(str, len) alloc_string_len(str, len) - #ifdef LIME_EFSW - FileWatcher* watcher = (FileWatcher*)val_data (handle); - return alloc_int (watcher->AddDirectory (val_string (path), recursive)); - #else - return alloc_int (0); - #endif + static const FileDialog::char_t* hl_vstring_to_fd_str (hl_vstring* val) { + if (val) { + return hl_to_utf8 (val->bytes); + } + return nullptr; + } + static vbyte* hl_fd_str_to_utf8_bytes(const FileDialog::char_t* src, size_t length) { + vbyte* result = hl_copy_bytes((vbyte*)src, length + 1); + result[length] = '\0'; + return result; } + static vbyte* hl_fd_str_to_utf8_bytes(const FileDialog::char_t* src) { + return hl_fd_str_to_utf8_bytes(src, std::strlen(src)); + } + #endif - HL_PRIM int HL_NAME(hl_file_watcher_add_directory) (HL_CFFIPointer* handle, hl_vstring* path, bool recursive) { + void lime_application_allow_high_dpi () { - #ifdef LIME_EFSW - FileWatcher* watcher = (FileWatcher*)handle->ptr; - return watcher->AddDirectory ((const char*)path, recursive); - #else - return 0; - #endif + AllowHighDPI (); } - void lime_file_watcher_remove_directory (value handle, value watchID) { + HL_PRIM void HL_NAME(hl_application_allow_high_dpi) () { - #ifdef LIME_EFSW - FileWatcher* watcher = (FileWatcher*)val_data (handle); - watcher->RemoveDirectory (val_int (watchID)); - #endif + AllowHighDPI (); } - HL_PRIM void HL_NAME(hl_file_watcher_remove_directory) (HL_CFFIPointer* handle, int watchID) { + value lime_application_create () { - #ifdef LIME_EFSW - FileWatcher* watcher = (FileWatcher*)handle->ptr; - watcher->RemoveDirectory (watchID); - #endif + Application* application = CreateApplication (); + return CFFIPointer (application, gc_application); } - void lime_file_watcher_update (value handle) { + HL_PRIM HL_CFFIPointer* HL_NAME(hl_application_create) () { - #ifdef LIME_EFSW - FileWatcher* watcher = (FileWatcher*)val_data (handle); - watcher->Update (); - #endif + Application* application = CreateApplication (); + return HLCFFIPointer (application, (hl_finalizer)hl_gc_application); } - HL_PRIM void HL_NAME(hl_file_watcher_update) (HL_CFFIPointer* handle) { + void lime_application_event_manager_register (value callback, value eventObject) { - #ifdef LIME_EFSW - FileWatcher* watcher = (FileWatcher*)handle->ptr; - watcher->Update (); - #endif + ApplicationEvent::callback = new ValuePointer (callback); + ApplicationEvent::eventObject = new ValuePointer (eventObject); } - int lime_font_get_ascender (value fontHandle) { + HL_PRIM void HL_NAME(hl_application_event_manager_register) (vclosure* callback, ApplicationEvent* eventObject) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return font->GetAscender (); - #else - return 0; - #endif + ApplicationEvent::callback = new ValuePointer (callback); + ApplicationEvent::eventObject = new ValuePointer ((vobj*)eventObject); } - HL_PRIM int HL_NAME(hl_font_get_ascender) (HL_CFFIPointer* fontHandle) { + int lime_application_exec (value application) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return font->GetAscender (); - #else - return 0; - #endif + Application* app = (Application*)val_data (application); + return app->Exec (); } - int lime_font_get_descender (value fontHandle) { + HL_PRIM int HL_NAME(hl_application_exec) (HL_CFFIPointer* application) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return font->GetDescender (); - #else - return 0; - #endif + Application* app = (Application*)application->ptr; + return app->Exec (); } - HL_PRIM int HL_NAME(hl_font_get_descender) (HL_CFFIPointer* fontHandle) { + void lime_application_init (value application) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return font->GetDescender (); - #else - return 0; - #endif + Application* app = (Application*)val_data (application); + app->Init (); } - value lime_font_get_family_name (value fontHandle) { + HL_PRIM void HL_NAME(hl_application_init) (HL_CFFIPointer* application) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - wchar_t *name = font->GetFamilyName (); - value result = alloc_wstring (name); - delete name; - return result; - #else - return alloc_null (); - #endif + Application* app = (Application*)application->ptr; + app->Init (); } - HL_PRIM vbyte* HL_NAME(hl_font_get_family_name) (HL_CFFIPointer* fontHandle) { + int lime_application_quit (value application) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - wchar_t *name = font->GetFamilyName (); - if (!name) - return nullptr; - vbyte* const result = hl_wstring_to_utf8_bytes (name); - delete name; - return result; - #else - return 0; - #endif + Application* app = (Application*)val_data (application); + return app->Quit (); } - int lime_font_get_glyph_index (value fontHandle, HxString character) { + HL_PRIM int HL_NAME(hl_application_quit) (HL_CFFIPointer* application) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return font->GetGlyphIndex (hxs_utf8 (character, nullptr)); - #else - return -1; - #endif + Application* app = (Application*)application->ptr; + return app->Quit (); } - HL_PRIM int HL_NAME(hl_font_get_glyph_index) (HL_CFFIPointer* fontHandle, hl_vstring* character) { + void lime_application_set_modal_callbacks (value application, value update, value render, value window, value rethrow) { +#if defined(LIME_SDL) && defined(HX_WINDOWS) && !defined(HX_WINRT) + ((SDLApplication*)val_data(application))->SetModalCallbacks(new ValuePointer(update), new ValuePointer(render), new ValuePointer(window), new ValuePointer(rethrow)); +#endif + } - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return font->GetGlyphIndex (character ? (char*)hl_to_utf8 ((const uchar*)character->bytes) : NULL); - #else - return -1; - #endif + HL_PRIM void HL_NAME(hl_application_set_modal_callbacks) (HL_CFFIPointer* application, vclosure* update, vclosure* render, vclosure* window, vclosure* rethrow) { +#if defined(LIME_SDL) && defined(HX_WINDOWS) && !defined(HX_WINRT) + ((SDLApplication*)application->ptr)->SetModalCallbacks(new ValuePointer(update), new ValuePointer(render), new ValuePointer(window), new ValuePointer(rethrow)); +#endif + } + void lime_application_defer_modal_exception (value application) { +#if defined(LIME_SDL) && defined(HX_WINDOWS) && !defined(HX_WINRT) + ((SDLApplication*)val_data(application))->DeferModalException(); +#endif } + HL_PRIM void HL_NAME(hl_application_defer_modal_exception) (HL_CFFIPointer* application) { +#if defined(LIME_SDL) && defined(HX_WINDOWS) && !defined(HX_WINRT) + ((SDLApplication*)application->ptr)->DeferModalException(); +#endif + } - value lime_font_get_glyph_indices (value fontHandle, HxString characters) { + void lime_application_set_frame_rate (value application, double frameRate) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return (value)font->GetGlyphIndices (true, hxs_utf8 (characters, nullptr)); - #else - return alloc_null (); - #endif + Application* app = (Application*)val_data (application); + app->SetFrameRate (frameRate); } - HL_PRIM hl_varray* HL_NAME(hl_font_get_glyph_indices) (HL_CFFIPointer* fontHandle, hl_vstring* characters) { + HL_PRIM void HL_NAME(hl_application_set_frame_rate) (HL_CFFIPointer* application, double frameRate) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return (hl_varray*)font->GetGlyphIndices (false, characters ? (char*)hl_to_utf8 ((const uchar*)characters->bytes) : NULL); - #else - return 0; - #endif + Application* app = (Application*)application->ptr; + app->SetFrameRate (frameRate); } - value lime_font_get_glyph_metrics (value fontHandle, int index) { + bool lime_application_set_menu (value application, value data) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return (value)font->GetGlyphMetrics (true, index); - #else - return alloc_null (); - #endif + Application* app = (Application*)val_data (application); + Bytes bytes (data); + return app->SetMenu (bytes.b, bytes.length); } - HL_PRIM vdynamic* HL_NAME(hl_font_get_glyph_metrics) (HL_CFFIPointer* fontHandle, int index) { + HL_PRIM bool HL_NAME(hl_application_set_menu) (HL_CFFIPointer* application, Bytes* data) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return (vdynamic*)font->GetGlyphMetrics (false, index); - #else - return 0; - #endif + Application* app = (Application*)application->ptr; + return app->SetMenu (data ? data->b : 0, data ? data->length : 0); } - int lime_font_get_height (value fontHandle) { + void lime_application_set_main_loop (value application, int profile, double frameRate, int timePrecision, int busyWait, int uncapMode) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return font->GetHeight (); - #else - return 0; - #endif + Application* app = (Application*)val_data (application); + app->SetMainLoop (profile, frameRate, timePrecision, busyWait, uncapMode); } - HL_PRIM int HL_NAME(hl_font_get_height) (HL_CFFIPointer* fontHandle) { + HL_PRIM void HL_NAME(hl_application_set_main_loop) (HL_CFFIPointer* application, int profile, double frameRate, int timePrecision, int busyWait, int uncapMode) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return font->GetHeight (); - #else - return 0; - #endif + Application* app = (Application*)application->ptr; + app->SetMainLoop (profile, frameRate, timePrecision, busyWait, uncapMode); } - int lime_font_get_num_glyphs (value fontHandle) { + void lime_application_set_vsync_mode (value application, int vsyncMode) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return font->GetNumGlyphs (); - #else - return 0; - #endif + Application* app = (Application*)val_data (application); + app->SetVSyncMode (vsyncMode); } - HL_PRIM int HL_NAME(hl_font_get_num_glyphs) (HL_CFFIPointer* fontHandle) { + HL_PRIM void HL_NAME(hl_application_set_vsync_mode) (HL_CFFIPointer* application, int vsyncMode) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return font->GetNumGlyphs (); - #else - return 0; - #endif + Application* app = (Application*)application->ptr; + app->SetVSyncMode (vsyncMode); } - int lime_font_get_underline_position (value fontHandle) { + bool lime_application_update (value application) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return font->GetUnderlinePosition (); - #else - return 0; - #endif + Application* app = (Application*)val_data (application); + return app->Update (); } - HL_PRIM int HL_NAME(hl_font_get_underline_position) (HL_CFFIPointer* fontHandle) { + HL_PRIM bool HL_NAME(hl_application_update) (HL_CFFIPointer* application) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return font->GetUnderlinePosition (); - #else - return 0; - #endif + Application* app = (Application*)application->ptr; + return app->Update (); } - int lime_font_get_underline_thickness (value fontHandle) { + value lime_audio_load_bytes (value data, value buffer) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return font->GetUnderlineThickness (); - #else - return 0; - #endif + Resource resource; + Bytes bytes; - } + AudioBuffer audioBuffer = AudioBuffer (buffer); + bytes.Set (data); + resource = Resource (&bytes); - HL_PRIM int HL_NAME(hl_font_get_underline_thickness) (HL_CFFIPointer* fontHandle) { + #ifdef LIME_SDL_SOUND + if (SDLSound::Decode (&resource, &audioBuffer)) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return font->GetUnderlineThickness (); - #else - return 0; + return audioBuffer.Value (buffer); + + } #endif - } + if (WAV::Decode (&resource, &audioBuffer)) { - int lime_font_get_strikethrough_position (value fontHandle) { + return audioBuffer.Value (buffer); - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return font->GetStrikethroughPosition (); - #else - return 0; + } + + #ifdef LIME_OGG + if (OGG::Decode (&resource, &audioBuffer)) { + + return audioBuffer.Value (buffer); + + } #endif + return alloc_null (); + } - HL_PRIM int HL_NAME(hl_font_get_strikethrough_position) (HL_CFFIPointer* fontHandle) { + HL_PRIM AudioBuffer* HL_NAME(hl_audio_load_bytes) (Bytes* data, AudioBuffer* buffer) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return font->GetStrikethroughPosition (); - #else - return 0; + Resource resource = Resource (data); + + #ifdef LIME_SDL_SOUND + if (SDLSound::Decode (&resource, buffer)) { + + return buffer; + + } #endif - } + if (WAV::Decode (&resource, buffer)) { + return buffer; - int lime_font_get_strikethrough_thickness (value fontHandle) { + } - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return font->GetStrikethroughThickness (); - #else - return 0; + #ifdef LIME_OGG + if (OGG::Decode (&resource, buffer)) { + + return buffer; + + } #endif + return 0; + } - HL_PRIM int HL_NAME(hl_font_get_strikethrough_thickness) (HL_CFFIPointer* fontHandle) { + value lime_audio_load_file (value data, value buffer) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return font->GetStrikethroughThickness (); - #else - return 0; + Resource resource; + + AudioBuffer audioBuffer = AudioBuffer (buffer); + + resource = Resource (val_string (data)); + + #ifdef LIME_SDL_SOUND + if (SDLSound::Decode (&resource, &audioBuffer)) { + + return audioBuffer.Value (buffer); + + } #endif - } + if (WAV::Decode (&resource, &audioBuffer)) { + return audioBuffer.Value (buffer); - int lime_font_get_units_per_em (value fontHandle) { + } - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return font->GetUnitsPerEM (); - #else - return 0; + #ifdef LIME_OGG + if (OGG::Decode (&resource, &audioBuffer)) { + + return audioBuffer.Value (buffer); + + } #endif + return alloc_null (); + } - HL_PRIM int HL_NAME(hl_font_get_units_per_em) (HL_CFFIPointer* fontHandle) { + HL_PRIM AudioBuffer* HL_NAME(hl_audio_load_file) (hl_vstring* data, AudioBuffer* buffer) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return font->GetUnitsPerEM (); - #else - return 0; + Resource resource = Resource (data ? hl_to_utf8 ((const uchar*)data->bytes) : NULL); + + #ifdef LIME_SDL_SOUND + if (SDLSound::Decode (&resource, buffer)) { + + return buffer; + + } #endif - } + if (WAV::Decode (&resource, buffer)) { + return buffer; - value lime_font_load_bytes (value data) { + } - #ifdef LIME_FREETYPE - Resource resource; + #ifdef LIME_OGG + if (OGG::Decode (&resource, buffer)) { + + return buffer; + + } + #endif + + return 0; + + } + + + value lime_audio_load (value data, value buffer) { + + if (val_is_string (data)) { + + return lime_audio_load_file (data, buffer); + + } else { + + return lime_audio_load_bytes (data, buffer); + + } + + } + + + value lime_bytes_from_data_pointer (double data, int length, value _bytes) { + + uintptr_t ptr = (uintptr_t)data; + Bytes bytes (_bytes); + bytes.Resize (length); + + if (ptr) { + + memcpy (bytes.b, (const void*)ptr, length); + + } + + return bytes.Value (_bytes); + + } + + + HL_PRIM Bytes* HL_NAME(hl_bytes_from_data_pointer) (double data, int length, Bytes* bytes) { + + uintptr_t ptr = (uintptr_t)data; + bytes->Resize (length); + + if (ptr) { + + memcpy (bytes->b, (const void*)ptr, length); + + } + + return bytes; + + } + + + double lime_bytes_get_data_pointer (value bytes) { + + Bytes data = Bytes (bytes); + return (uintptr_t)data.b; + + } + + + HL_PRIM double HL_NAME(hl_bytes_get_data_pointer) (Bytes* bytes) { + + return bytes ? (uintptr_t)bytes->b : 0; + + } + + + double lime_bytes_get_data_pointer_offset (value bytes, int offset) { + + if (val_is_null (bytes)) return 0; + + Bytes data = Bytes (bytes); + return (uintptr_t)data.b + offset; + + } + + + HL_PRIM double HL_NAME(hl_bytes_get_data_pointer_offset) (Bytes* bytes, int offset) { + + if (!bytes) return 0; + return (uintptr_t)bytes->b + offset; + + } + + + value lime_bytes_read_file (HxString path, value bytes) { + + Bytes data (bytes); + data.ReadFile (hxs_utf8 (path, nullptr)); + return data.Value (bytes); + + } + + + HL_PRIM Bytes* HL_NAME(hl_bytes_read_file) (hl_vstring* path, Bytes* bytes) { + + if (!path) return 0; + bytes->ReadFile (hl_to_utf8 ((const uchar*)path->bytes)); + return bytes; + + } + + + double lime_cffi_get_native_pointer (value handle) { + + return (uintptr_t)val_data (handle); + + } + + + HL_PRIM double HL_NAME(hl_cffi_get_native_pointer) (HL_CFFIPointer* handle) { + + return (uintptr_t)handle->ptr; + + } + + + void lime_cffi_finalizer (value abstract) { + + val_call0 ((value)val_data (abstract)); + + } + + + value lime_cffi_set_finalizer (value callback) { + + value abstract = alloc_abstract (k_finalizer, callback); + val_gc (abstract, lime_cffi_finalizer); + return abstract; + + } + + + void lime_clipboard_event_manager_register (value callback, value eventObject) { + + ClipboardEvent::callback = new ValuePointer (callback); + ClipboardEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_clipboard_event_manager_register) (vclosure* callback, ClipboardEvent* eventObject) { + + ClipboardEvent::callback = new ValuePointer (callback); + ClipboardEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + value lime_clipboard_get_text () { + + if (Clipboard::HasText ()) { + + const char* text = Clipboard::GetText (); + value _text = alloc_string (text); + + // TODO: Should we free for all backends? (SDL requires it) + + free ((char*)text); + return _text; + + } else { + + return alloc_null (); + + } + + } + + + HL_PRIM vbyte* HL_NAME(hl_clipboard_get_text) () { + + if (Clipboard::HasText ()) { + + const char* text = Clipboard::GetText (); + return (vbyte*)text; + + } else { + + return 0; + + } + + } + + + void lime_clipboard_set_text (HxString text) { + + Clipboard::SetText (hxs_utf8 (text, nullptr)); + + } + + + HL_PRIM void HL_NAME(hl_clipboard_set_text) (hl_vstring* text) { + + Clipboard::SetText (text ? (const char*)hl_to_utf8 ((const uchar*)text->bytes) : NULL); + + } + + + double lime_data_pointer_offset (double pointer, int offset) { + + return (uintptr_t)pointer + offset; + + } + + + HL_PRIM double HL_NAME(hl_data_pointer_offset) (double pointer, int offset) { + + return (uintptr_t)pointer + offset; + + } + + + value lime_deflate_compress (value buffer, value bytes) { + + #ifdef LIME_ZLIB + Bytes data (buffer); + Bytes result (bytes); + + Zlib::Compress (DEFLATE, &data, &result); + + return result.Value (bytes); + #else + return alloc_null(); + #endif + + } + + + HL_PRIM Bytes* HL_NAME(hl_deflate_compress) (Bytes* buffer, Bytes* bytes) { + + #ifdef LIME_ZLIB + Zlib::Compress (DEFLATE, buffer, bytes); + return bytes; + #else + return 0; + #endif + + } + + + value lime_deflate_decompress (value buffer, value bytes) { + + #ifdef LIME_ZLIB + Bytes data (buffer); + Bytes result (bytes); + + Zlib::Decompress (DEFLATE, &data, &result); + + return result.Value (bytes); + #else + return alloc_null (); + #endif + + } + + + HL_PRIM Bytes* HL_NAME(hl_deflate_decompress) (Bytes* buffer, Bytes* bytes) { + + #ifdef LIME_ZLIB + Zlib::Decompress (DEFLATE, buffer, bytes); + return bytes; + #else + return 0; + #endif + + } + + + void lime_dock_icon_bounce (bool critical) { + + DockIcon::Bounce (critical); + + } + + + HL_PRIM void HL_NAME(hl_dock_icon_bounce) (bool critical) { + + DockIcon::Bounce (critical); + + } + + + bool lime_dock_icon_is_supported () { + + return DockIcon::IsSupported (); + + } + + + HL_PRIM bool HL_NAME(hl_dock_icon_is_supported) () { + + return DockIcon::IsSupported (); + + } + + + void lime_dock_icon_set_icon (value buffer) { + + if (val_is_null (buffer)) { + + DockIcon::SetIcon (NULL); + return; + + } + + ImageBuffer imageBuffer = ImageBuffer (buffer); + DockIcon::SetIcon (&imageBuffer); + + } + + + HL_PRIM void HL_NAME(hl_dock_icon_set_icon) (ImageBuffer* buffer) { + + DockIcon::SetIcon (buffer); + + } + + + bool lime_dock_icon_set_menu (value data) { + + Bytes bytes (data); + return DockIcon::SetMenu (bytes.b, bytes.length); + + } + + + HL_PRIM bool HL_NAME(hl_dock_icon_set_menu) (Bytes* data) { + + return DockIcon::SetMenu (data ? data->b : 0, data ? data->length : 0); + + } + + + void lime_drop_event_manager_register (value callback, value eventObject) { + + DropEvent::callback = new ValuePointer (callback); + DropEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_drop_event_manager_register) (vclosure* callback, DropEvent* eventObject) { + + DropEvent::callback = new ValuePointer (callback); + DropEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + value lime_file_dialog_open_directory (HxString title, HxString filter, HxString defaultPath) { + + #ifdef LIME_TINYFILEDIALOGS + + const FileDialog::char_t* path = FileDialog::OpenDirectory (hxs_to_fd_str (title), hxs_to_fd_str (filter), hxs_to_fd_str (defaultPath)); + + if (path) { + + return alloc_from_fd_str (path); + + } + + #endif + + return alloc_null (); + + } + + + HL_PRIM vbyte* HL_NAME(hl_file_dialog_open_directory) (hl_vstring* title, hl_vstring* filter, hl_vstring* defaultPath) { + + #ifdef LIME_TINYFILEDIALOGS + + const FileDialog::char_t* path = FileDialog::OpenDirectory (hl_vstring_to_fd_str (title), hl_vstring_to_fd_str (filter), hl_vstring_to_fd_str (defaultPath)); + + if (path) { + + return hl_fd_str_to_utf8_bytes(path); + + } + + #endif + + return NULL; + + } + + + value lime_file_dialog_open_file (HxString title, HxString filter, HxString defaultPath) { + + #ifdef LIME_TINYFILEDIALOGS + + const FileDialog::char_t* path = FileDialog::OpenFile (hxs_to_fd_str (title), hxs_to_fd_str (filter), hxs_to_fd_str (defaultPath)); + + if (path) { + + return alloc_from_fd_str (path); + + } + + #endif + + return alloc_null (); + + } + + + HL_PRIM vbyte* HL_NAME(hl_file_dialog_open_file) (hl_vstring* title, hl_vstring* filter, hl_vstring* defaultPath) { + + #ifdef LIME_TINYFILEDIALOGS + + const FileDialog::char_t* path = FileDialog::OpenFile (hl_vstring_to_fd_str (title), hl_vstring_to_fd_str (filter), hl_vstring_to_fd_str (defaultPath)); + + if (path) { + + return hl_fd_str_to_utf8_bytes (path); + + } + + #endif + + return NULL; + + } + + + value lime_file_dialog_open_files (HxString title, HxString filter, HxString defaultPath) { + + #ifdef LIME_TINYFILEDIALOGS + + auto files = FileDialog::OpenFiles (hxs_to_fd_str (title), hxs_to_fd_str (filter), hxs_to_fd_str (defaultPath)); + value result = alloc_array (files.size ()); + + for (int i = 0; i < files.size (); i++) { + + auto file_str = files[i]; + + value file = alloc_from_fd_str_len (file_str.data(), file_str.size()); + val_array_set_i (result, i, file); + + } + + #else + value result = alloc_array (0); + #endif + + return result; + + } + + + HL_PRIM hl_varray* HL_NAME(hl_file_dialog_open_files) (hl_vstring* title, hl_vstring* filter, hl_vstring* defaultPath) { + + #ifdef LIME_TINYFILEDIALOGS + + auto files = FileDialog::OpenFiles (hl_vstring_to_fd_str (title), hl_vstring_to_fd_str (filter), hl_vstring_to_fd_str (defaultPath)); + hl_varray* result = (hl_varray*)hl_alloc_array (&hlt_bytes, files.size ()); + vbyte** resultData = hl_aptr (result, vbyte*); + + for (int i = 0; i < files.size (); i++) { + + auto file_str = files[i]; + *resultData++ = hl_fd_str_to_utf8_bytes (file_str.data(), file_str.size()); + + } + + #else + hl_varray* result = hl_alloc_array (&hlt_bytes, 0); + #endif + + return result; + + } + + + value lime_file_dialog_save_file (HxString title, HxString filter, HxString defaultPath) { + + #ifdef LIME_TINYFILEDIALOGS + + const FileDialog::char_t* path = FileDialog::SaveFile (hxs_to_fd_str (title), hxs_to_fd_str (filter), hxs_to_fd_str (defaultPath)); + + if (path) { + + return alloc_from_fd_str (path); + + } + + #endif + + return alloc_null (); + + } + + + HL_PRIM vbyte* HL_NAME(hl_file_dialog_save_file) (hl_vstring* title, hl_vstring* filter, hl_vstring* defaultPath) { + + #ifdef LIME_TINYFILEDIALOGS + + const FileDialog::char_t* path = FileDialog::SaveFile (hl_vstring_to_fd_str (title), hl_vstring_to_fd_str (filter), hl_vstring_to_fd_str (defaultPath)); + + if (path) { + + return hl_fd_str_to_utf8_bytes (path); + + } + + #endif + + return NULL; + + } + + + value lime_file_watcher_create (value callback) { + + #ifdef LIME_EFSW + FileWatcher* watcher = new FileWatcher (callback); + return CFFIPointer (watcher, gc_file_watcher); + #else + return alloc_null (); + #endif + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_file_watcher_create) (vclosure* callback) { + + // #ifdef LIME_EFSW + // FileWatcher* watcher = new FileWatcher (callback); + // return HLCFFIPointer (watcher, (hl_finalizer)hl_gc_file_watcher); + // #else + return 0; + // #endif + + } + + + value lime_file_watcher_add_directory (value handle, value path, bool recursive) { + + #ifdef LIME_EFSW + FileWatcher* watcher = (FileWatcher*)val_data (handle); + return alloc_int (watcher->AddDirectory (val_string (path), recursive)); + #else + return alloc_int (0); + #endif + + } + + + HL_PRIM int HL_NAME(hl_file_watcher_add_directory) (HL_CFFIPointer* handle, hl_vstring* path, bool recursive) { + + #ifdef LIME_EFSW + FileWatcher* watcher = (FileWatcher*)handle->ptr; + return watcher->AddDirectory ((const char*)path, recursive); + #else + return 0; + #endif + + } + + + void lime_file_watcher_remove_directory (value handle, value watchID) { + + #ifdef LIME_EFSW + FileWatcher* watcher = (FileWatcher*)val_data (handle); + watcher->RemoveDirectory (val_int (watchID)); + #endif + + } + + + HL_PRIM void HL_NAME(hl_file_watcher_remove_directory) (HL_CFFIPointer* handle, int watchID) { + + #ifdef LIME_EFSW + FileWatcher* watcher = (FileWatcher*)handle->ptr; + watcher->RemoveDirectory (watchID); + #endif + + } + + + void lime_file_watcher_update (value handle) { + + #ifdef LIME_EFSW + FileWatcher* watcher = (FileWatcher*)val_data (handle); + watcher->Update (); + #endif + + } + + + HL_PRIM void HL_NAME(hl_file_watcher_update) (HL_CFFIPointer* handle) { + + #ifdef LIME_EFSW + FileWatcher* watcher = (FileWatcher*)handle->ptr; + watcher->Update (); + #endif + + } + + + int lime_font_get_ascender (value fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return font->GetAscender (); + #else + return 0; + #endif + + } + + + HL_PRIM int HL_NAME(hl_font_get_ascender) (HL_CFFIPointer* fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return font->GetAscender (); + #else + return 0; + #endif + + } + + + int lime_font_get_descender (value fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return font->GetDescender (); + #else + return 0; + #endif + + } + + + HL_PRIM int HL_NAME(hl_font_get_descender) (HL_CFFIPointer* fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return font->GetDescender (); + #else + return 0; + #endif + + } + + + value lime_font_get_family_name (value fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + wchar_t *name = font->GetFamilyName (); + value result = alloc_wstring (name); + delete name; + return result; + #else + return alloc_null (); + #endif + + } + + + HL_PRIM vbyte* HL_NAME(hl_font_get_family_name) (HL_CFFIPointer* fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + wchar_t *name = font->GetFamilyName (); + if (!name) + return nullptr; + vbyte* const result = hl_wstring_to_utf8_bytes (name); + delete name; + return result; + #else + return 0; + #endif + + } + + + int lime_font_get_glyph_index (value fontHandle, HxString character) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return font->GetGlyphIndex (hxs_utf8 (character, nullptr)); + #else + return -1; + #endif + + } + + + HL_PRIM int HL_NAME(hl_font_get_glyph_index) (HL_CFFIPointer* fontHandle, hl_vstring* character) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return font->GetGlyphIndex (character ? (char*)hl_to_utf8 ((const uchar*)character->bytes) : NULL); + #else + return -1; + #endif + + } + + + value lime_font_get_glyph_indices (value fontHandle, HxString characters) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return (value)font->GetGlyphIndices (true, hxs_utf8 (characters, nullptr)); + #else + return alloc_null (); + #endif + + } + + + HL_PRIM hl_varray* HL_NAME(hl_font_get_glyph_indices) (HL_CFFIPointer* fontHandle, hl_vstring* characters) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return (hl_varray*)font->GetGlyphIndices (false, characters ? (char*)hl_to_utf8 ((const uchar*)characters->bytes) : NULL); + #else + return 0; + #endif + + } + + + value lime_font_get_glyph_metrics (value fontHandle, int index) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return (value)font->GetGlyphMetrics (true, index); + #else + return alloc_null (); + #endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_font_get_glyph_metrics) (HL_CFFIPointer* fontHandle, int index) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return (vdynamic*)font->GetGlyphMetrics (false, index); + #else + return 0; + #endif + + } + + + int lime_font_get_height (value fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return font->GetHeight (); + #else + return 0; + #endif + + } + + + HL_PRIM int HL_NAME(hl_font_get_height) (HL_CFFIPointer* fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return font->GetHeight (); + #else + return 0; + #endif + + } + + + int lime_font_get_num_glyphs (value fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return font->GetNumGlyphs (); + #else + return 0; + #endif + + } + + + HL_PRIM int HL_NAME(hl_font_get_num_glyphs) (HL_CFFIPointer* fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return font->GetNumGlyphs (); + #else + return 0; + #endif + + } + + + int lime_font_get_underline_position (value fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return font->GetUnderlinePosition (); + #else + return 0; + #endif + + } + + + HL_PRIM int HL_NAME(hl_font_get_underline_position) (HL_CFFIPointer* fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return font->GetUnderlinePosition (); + #else + return 0; + #endif + + } + + + int lime_font_get_underline_thickness (value fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return font->GetUnderlineThickness (); + #else + return 0; + #endif + + } + + + HL_PRIM int HL_NAME(hl_font_get_underline_thickness) (HL_CFFIPointer* fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return font->GetUnderlineThickness (); + #else + return 0; + #endif + + } + + + int lime_font_get_strikethrough_position (value fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return font->GetStrikethroughPosition (); + #else + return 0; + #endif + + } + + + HL_PRIM int HL_NAME(hl_font_get_strikethrough_position) (HL_CFFIPointer* fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return font->GetStrikethroughPosition (); + #else + return 0; + #endif + + } + + + int lime_font_get_strikethrough_thickness (value fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return font->GetStrikethroughThickness (); + #else + return 0; + #endif + + } + + + HL_PRIM int HL_NAME(hl_font_get_strikethrough_thickness) (HL_CFFIPointer* fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return font->GetStrikethroughThickness (); + #else + return 0; + #endif + + } + + + int lime_font_get_units_per_em (value fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return font->GetUnitsPerEM (); + #else + return 0; + #endif + + } + + + HL_PRIM int HL_NAME(hl_font_get_units_per_em) (HL_CFFIPointer* fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return font->GetUnitsPerEM (); + #else + return 0; + #endif + + } + + + bool lime_font_is_bold (value fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return font->IsBold (); + #else + return false; + #endif + + } + + + HL_PRIM bool HL_NAME(hl_font_is_bold) (HL_CFFIPointer* fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return font->IsBold (); + #else + return false; + #endif + + } + + + bool lime_font_is_italic (value fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return font->IsItalic (); + #else + return false; + #endif + + } + + + HL_PRIM bool HL_NAME(hl_font_is_italic) (HL_CFFIPointer* fontHandle) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return font->IsItalic (); + #else + return false; + #endif + + } + + + value lime_font_load_bytes (value data) { + + #ifdef LIME_FREETYPE + Resource resource; + Bytes bytes; + + bytes.Set (data); + resource = Resource (&bytes); + + Font *font = new Font (&resource, 0); + + if (font) { + + if (font->face) { + + return CFFIPointer (font, gc_font); + + } else { + + delete font; + + } + + } + #endif + + return alloc_null (); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_font_load_bytes) (Bytes* data) { + + #ifdef LIME_FREETYPE + Resource resource = Resource (data); + + Font *font = new Font (&resource, 0); + + if (font) { + + if (font->face) { + + return HLCFFIPointer (font, (hl_finalizer)hl_gc_font); + + } else { + + delete font; + + } + + } + #endif + + return 0; + + } + + + value lime_font_load_file (value data) { + + #ifdef LIME_FREETYPE + Resource resource = Resource (val_string (data)); + + Font *font = new Font (&resource, 0); + + if (font) { + + if (font->face) { + + return CFFIPointer (font, gc_font); + + } else { + + delete font; + + } + + } + #endif + + return alloc_null (); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_font_load_file) (hl_vstring* data) { + + #ifdef LIME_FREETYPE + Resource resource = Resource (data ? hl_to_utf8 ((const uchar*)data->bytes) : NULL); + + Font *font = new Font (&resource, 0); + + if (font) { + + if (font->face) { + + return HLCFFIPointer (font, (hl_finalizer)hl_gc_font); + + } else { + + delete font; + + } + + } + #endif + + return 0; + + } + + + value lime_font_load (value data) { + + if (val_is_string (data)) { + + return lime_font_load_file (data); + + } else { + + return lime_font_load_bytes (data); + + } + + } + + + value lime_font_outline_decompose (value fontHandle, int size) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return (value)font->Decompose (true, size); + #else + return alloc_null (); + #endif + + } + + + value lime_font_outline_decompose_no_hint (value fontHandle, int size) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + return (value)font->Decompose (true, size, false); + #else + return alloc_null (); + #endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_font_outline_decompose) (HL_CFFIPointer* fontHandle, int size) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return (vdynamic*)font->Decompose (false, size); + #else + return 0; + #endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_font_outline_decompose_no_hint) (HL_CFFIPointer* fontHandle, int size) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + return (vdynamic*)font->Decompose (false, size, false); + #else + return 0; + #endif + + } + + + value lime_font_render_glyph (value fontHandle, int index, value data) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + Bytes bytes (data); + + if (font->RenderGlyph (index, &bytes)) { + + return bytes.Value (data); + + } + #endif + + return alloc_null (); + + } + + + value lime_font_render_glyph_with_flags (value fontHandle, int index, int loadFlags, value data) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + Bytes bytes (data); + + if (font->RenderGlyphWithFlags (index, loadFlags, &bytes)) { + + return bytes.Value (data); + + } + #endif + + return alloc_null (); + + } + + + HL_PRIM Bytes* HL_NAME(hl_font_render_glyph) (HL_CFFIPointer* fontHandle, int index, Bytes* data) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + + if (font->RenderGlyph (index, data)) { + + return data; + + } + #endif + + return NULL; + + } + + + HL_PRIM Bytes* HL_NAME(hl_font_render_glyph_with_flags) (HL_CFFIPointer* fontHandle, int index, int loadFlags, Bytes* data) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + + if (font->RenderGlyphWithFlags (index, loadFlags, data)) { + + return data; + + } + #endif + + return NULL; + + } + + + value lime_font_render_glyphs (value fontHandle, value indices, value data) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + Bytes bytes (data); + + if (font->RenderGlyphs (indices, &bytes)) { + + return bytes.Value (data); + + } + #endif + + return alloc_null (); + + } + + + HL_PRIM Bytes* HL_NAME(hl_font_render_glyphs) (HL_CFFIPointer* fontHandle, hl_varray* indices, Bytes* data) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + + if (font->RenderGlyphs (indices, data)) { + + return data; + + } + #endif + + return NULL; + + } + + + void lime_font_set_size (value fontHandle, int fontSize, int dpi) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + font->SetSize (fontSize, dpi); + #endif + + } + + + HL_PRIM void HL_NAME(hl_font_set_size) (HL_CFFIPointer* fontHandle, int fontSize, int dpi) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + font->SetSize (fontSize, dpi); + #endif + + } + + + void lime_font_set_stem_darkening (value fontHandle, bool enable) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)val_data (fontHandle); + font->SetStemDarkening (enable); + #endif + + } + + + HL_PRIM void HL_NAME(hl_font_set_stem_darkening) (HL_CFFIPointer* fontHandle, bool enable) { + + #ifdef LIME_FREETYPE + Font *font = (Font*)fontHandle->ptr; + font->SetStemDarkening (enable); + #endif + + } + + + void lime_font_set_stem_darkening_parameters (value fontHandle, value params) { + + #ifdef LIME_FREETYPE + int length = val_array_size (params); + if (length != 8) + { + return; + } + + int darkeningParams[8] = { + val_int (val_array_i (params, 0)), + val_int (val_array_i (params, 1)), + val_int (val_array_i (params, 2)), + val_int (val_array_i (params, 3)), + val_int (val_array_i (params, 4)), + val_int (val_array_i (params, 5)), + val_int (val_array_i (params, 6)), + val_int (val_array_i (params, 7)) + }; + + Font *font = (Font*)val_data (fontHandle); + font->SetStemDarkeningParameters(darkeningParams); + #endif + + } + + + HL_PRIM void HL_NAME(hl_font_set_stem_darkening_parameters) (HL_CFFIPointer* fontHandle, hl_varray* params) { + + #ifdef LIME_FREETYPE + int length = params->size; + if (length != 8) + { + return; + } + + int* paramValues = hl_aptr (params, int); + + Font *font = (Font*)fontHandle->ptr; + font->SetStemDarkeningParameters(paramValues); + #endif + + } + + + void lime_gamepad_add_mappings (value mappings) { + + int length = val_array_size (mappings); + + for (int i = 0; i < length; i++) { + + Gamepad::AddMapping (val_string (val_array_i (mappings, i))); + + } + + } + + + HL_PRIM void HL_NAME(hl_gamepad_add_mappings) (hl_varray* mappings) { + + int length = mappings->size; + hl_vstring** mappingsData = hl_aptr (mappings, hl_vstring*); + + for (int i = 0; i < length; i++) { + + Gamepad::AddMapping (hl_to_utf8 ((const uchar*)((*mappingsData++)->bytes))); + + } + + } + + + void lime_gamepad_event_manager_register (value callback, value eventObject) { + + GamepadEvent::callback = new ValuePointer (callback); + GamepadEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_gamepad_event_manager_register) (vclosure* callback, GamepadEvent* eventObject) { + + GamepadEvent::callback = new ValuePointer (callback); + GamepadEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + value lime_gamepad_get_device_guid (int id) { + + const char* guid = Gamepad::GetDeviceGUID (id); + + if (guid) { + + value result = alloc_string (guid); + delete guid; + return result; + + } else { + + return alloc_null (); + + } + + } + + + HL_PRIM vbyte* HL_NAME(hl_gamepad_get_device_guid) (int id) { + + const char* guid = Gamepad::GetDeviceGUID (id); + + if (guid) { + + return (vbyte*)guid; + + } else { + + return 0; + + } + + } + + + value lime_gamepad_get_device_name (int id) { + + const char* name = Gamepad::GetDeviceName (id); + return name ? alloc_string (name) : alloc_null (); + + } + + + HL_PRIM vbyte* HL_NAME(hl_gamepad_get_device_name) (int id) { + + return (vbyte*)Gamepad::GetDeviceName (id); + + } + + + void lime_gamepad_rumble (int id, double lowFrequencyRumble, double highFrequencyRumble, int duration) { + + Gamepad::Rumble (id, lowFrequencyRumble, highFrequencyRumble, duration); + + } + + + HL_PRIM void HL_NAME(hl_gamepad_rumble) (int id, double lowFrequencyRumble, double highFrequencyRumble, int duration) { + + Gamepad::Rumble (id, lowFrequencyRumble, highFrequencyRumble, duration); + + } + + + int lime_gamepad_get_type (int id) { return Gamepad::GetType (id); } + HL_PRIM int HL_NAME(hl_gamepad_get_type) (int id) { return Gamepad::GetType (id); } + + bool lime_gamepad_has_led (int id) { return Gamepad::HasLED (id); } + HL_PRIM bool HL_NAME(hl_gamepad_has_led) (int id) { return Gamepad::HasLED (id); } + + bool lime_gamepad_has_rumble_triggers (int id) { return Gamepad::HasRumbleTriggers (id); } + HL_PRIM bool HL_NAME(hl_gamepad_has_rumble_triggers) (int id) { return Gamepad::HasRumbleTriggers (id); } + + bool lime_gamepad_has_sensor (int id, int sensor) { return Gamepad::HasSensor (id, sensor); } + HL_PRIM bool HL_NAME(hl_gamepad_has_sensor) (int id, int sensor) { return Gamepad::HasSensor (id, sensor); } + + bool lime_gamepad_rumble_triggers (int id, double leftRumble, double rightRumble, int duration) { return Gamepad::RumbleTriggers (id, leftRumble, rightRumble, duration); } + HL_PRIM bool HL_NAME(hl_gamepad_rumble_triggers) (int id, double leftRumble, double rightRumble, int duration) { return Gamepad::RumbleTriggers (id, leftRumble, rightRumble, duration); } + + bool lime_gamepad_set_led (int id, int red, int green, int blue) { return Gamepad::SetLED (id, red, green, blue); } + HL_PRIM bool HL_NAME(hl_gamepad_set_led) (int id, int red, int green, int blue) { return Gamepad::SetLED (id, red, green, blue); } + + bool lime_gamepad_set_sensor_enabled (int id, int sensor, bool enabled) { return Gamepad::SetSensorEnabled (id, sensor, enabled); } + HL_PRIM bool HL_NAME(hl_gamepad_set_sensor_enabled) (int id, int sensor, bool enabled) { return Gamepad::SetSensorEnabled (id, sensor, enabled); } + + int lime_gamepad_attach_virtual () { return Gamepad::AttachVirtual (); } + HL_PRIM int HL_NAME(hl_gamepad_attach_virtual) () { return Gamepad::AttachVirtual (); } + + void lime_gamepad_detach_virtual (int id) { Gamepad::DetachVirtual (id); } + HL_PRIM void HL_NAME(hl_gamepad_detach_virtual) (int id) { Gamepad::DetachVirtual (id); } + + + value lime_gamepad_get_virtual_output (int id) { + + int led, lowRumble, highRumble, leftRumble, rightRumble; + if (!Gamepad::GetVirtualOutput (id, &led, &lowRumble, &highRumble, &leftRumble, &rightRumble)) return alloc_null (); + + value output = alloc_empty_object (); + alloc_field (output, val_id ("led"), alloc_int (led)); + alloc_field (output, val_id ("lowRumble"), alloc_int (lowRumble)); + alloc_field (output, val_id ("highRumble"), alloc_int (highRumble)); + alloc_field (output, val_id ("leftRumble"), alloc_int (leftRumble)); + alloc_field (output, val_id ("rightRumble"), alloc_int (rightRumble)); + return output; + + } + + + HL_PRIM vdynamic* HL_NAME(hl_gamepad_get_virtual_output) (int id) { + + int led, lowRumble, highRumble, leftRumble, rightRumble; + if (!Gamepad::GetVirtualOutput (id, &led, &lowRumble, &highRumble, &leftRumble, &rightRumble)) return NULL; + + vdynamic* output = (vdynamic*)hl_alloc_dynobj (); + hl_dyn_seti (output, hl_hash_utf8 ("led"), &hlt_i32, led); + hl_dyn_seti (output, hl_hash_utf8 ("lowRumble"), &hlt_i32, lowRumble); + hl_dyn_seti (output, hl_hash_utf8 ("highRumble"), &hlt_i32, highRumble); + hl_dyn_seti (output, hl_hash_utf8 ("leftRumble"), &hlt_i32, leftRumble); + hl_dyn_seti (output, hl_hash_utf8 ("rightRumble"), &hlt_i32, rightRumble); + return output; + + } + + + value lime_gzip_compress (value buffer, value bytes) { + + #ifdef LIME_ZLIB + Bytes data (buffer); + Bytes result (bytes); + + Zlib::Compress (GZIP, &data, &result); + + return result.Value (bytes); + #else + return alloc_null (); + #endif + + } + + + HL_PRIM Bytes* HL_NAME(hl_gzip_compress) (Bytes* buffer, Bytes* bytes) { + + #ifdef LIME_ZLIB + Zlib::Compress (GZIP, buffer, bytes); + return bytes; + #else + return 0; + #endif + + } + + + value lime_gzip_decompress (value buffer, value bytes) { + + #ifdef LIME_ZLIB + Bytes data (buffer); + Bytes result (bytes); + + Zlib::Decompress (GZIP, &data, &result); + + return result.Value (bytes); + #else + return alloc_null (); + #endif + + } + + + HL_PRIM Bytes* HL_NAME(hl_gzip_decompress) (Bytes* buffer, Bytes* bytes) { + + #ifdef LIME_ZLIB + Zlib::Decompress (GZIP, buffer, bytes); + return bytes; + #else + return 0; + #endif + + } + + + void lime_haptic_vibrate (int period, int duration) { + + #ifdef IPHONE + Haptic::Vibrate (period, duration); + #endif + + } + + + HL_PRIM void HL_NAME(hl_haptic_vibrate) (int period, int duration) { + + #ifdef IPHONE + Haptic::Vibrate (period, duration); + #endif + + } + + + value lime_image_encode (value buffer, int type, int quality, value bytes) { + + ImageBuffer imageBuffer = ImageBuffer (buffer); + Bytes data = Bytes (bytes); + + switch (type) { + + case 0: + + #ifdef LIME_PNG + if (PNG::Encode (&imageBuffer, &data)) { + + return data.Value (bytes); + + } + #endif + break; + + case 1: + + #ifdef LIME_JPEG + if (JPEG::Encode (&imageBuffer, &data, quality)) { + + return data.Value (bytes); + + } + #endif + break; + + default: break; + + } + + return alloc_null (); + + } + + + HL_PRIM Bytes* HL_NAME(hl_image_encode) (ImageBuffer* buffer, int type, int quality, Bytes* bytes) { + + switch (type) { + + case 0: + + #ifdef LIME_PNG + if (PNG::Encode (buffer, bytes)) { + + return bytes; + + } + #endif + break; + + case 1: + + #ifdef LIME_JPEG + if (JPEG::Encode (buffer, bytes, quality)) { + + return bytes; + + } + #endif + break; + + default: break; + + } + + return 0; + + } + + + value lime_image_load_bytes (value data, value buffer) { + + Resource resource; Bytes bytes; - bytes.Set (data); - resource = Resource (&bytes); + ImageBuffer imageBuffer = ImageBuffer (buffer); + + bytes.Set (data); + resource = Resource (&bytes); + + #ifdef LIME_PNG + if (PNG::Decode (&resource, &imageBuffer)) { + + return imageBuffer.Value (buffer); + + } + #endif + + #ifdef LIME_JPEG + if (JPEG::Decode (&resource, &imageBuffer)) { + + return imageBuffer.Value (buffer); + + } + #endif + + return alloc_null (); + + } + + + HL_PRIM ImageBuffer* HL_NAME(hl_image_load_bytes) (Bytes* data, ImageBuffer* buffer) { + + Resource resource = Resource (data); + + #ifdef LIME_PNG + if (PNG::Decode (&resource, buffer)) { + + return buffer; + + } + #endif + + #ifdef LIME_JPEG + if (JPEG::Decode (&resource, buffer)) { + + return buffer; + + } + #endif + + return 0; + + } + + + value lime_image_load_file (value data, value buffer) { + + Resource resource = Resource (val_string (data)); + ImageBuffer imageBuffer = ImageBuffer (buffer); + + #ifdef LIME_PNG + if (PNG::Decode (&resource, &imageBuffer)) { + + return imageBuffer.Value (buffer); + + } + #endif + + #ifdef LIME_JPEG + if (JPEG::Decode (&resource, &imageBuffer)) { + + return imageBuffer.Value (buffer); + + } + #endif + + return alloc_null (); + + } + + + HL_PRIM ImageBuffer* HL_NAME(hl_image_load_file) (hl_vstring* data, ImageBuffer* buffer) { + + Resource resource = Resource (data); + + #ifdef LIME_PNG + if (PNG::Decode (&resource, buffer)) { + + return buffer; + + } + #endif + + #ifdef LIME_JPEG + if (JPEG::Decode (&resource, buffer)) { + + return buffer; + + } + #endif + + return 0; + + } + + + value lime_image_load (value data, value buffer) { + + if (val_is_string (data)) { + + return lime_image_load_file (data, buffer); + + } else { + + return lime_image_load_bytes (data, buffer); + + } + + } + + + void lime_image_data_util_color_transform (value image, value rect, value colorMatrix) { + + Image _image = Image (image); + Rectangle _rect = Rectangle (rect); + ColorMatrix _colorMatrix = ColorMatrix (colorMatrix); + ImageDataUtil::ColorTransform (&_image, &_rect, &_colorMatrix); + + } + + + HL_PRIM void HL_NAME(hl_image_data_util_color_transform) (Image* image, Rectangle* rect, ArrayBufferView* colorMatrix) { + + ColorMatrix _colorMatrix = ColorMatrix (colorMatrix); + ImageDataUtil::ColorTransform (image, rect, &_colorMatrix); + + } + + + void lime_image_data_util_copy_channel (value image, value sourceImage, value sourceRect, value destPoint, int srcChannel, int destChannel) { + + Image _image = Image (image); + Image _sourceImage = Image (sourceImage); + Rectangle _sourceRect = Rectangle (sourceRect); + Vector2 _destPoint = Vector2 (destPoint); + ImageDataUtil::CopyChannel (&_image, &_sourceImage, &_sourceRect, &_destPoint, srcChannel, destChannel); + + } + + + HL_PRIM void HL_NAME(hl_image_data_util_copy_channel) (Image* image, Image* sourceImage, Rectangle* sourceRect, Vector2* destPoint, int srcChannel, int destChannel) { + + ImageDataUtil::CopyChannel (image, sourceImage, sourceRect, destPoint, srcChannel, destChannel); + + } + + + void lime_image_data_util_copy_pixels (value image, value sourceImage, value sourceRect, value destPoint, value alphaImage, value alphaPoint, bool mergeAlpha) { + + Image _image = Image (image); + Image _sourceImage = Image (sourceImage); + Rectangle _sourceRect = Rectangle (sourceRect); + Vector2 _destPoint = Vector2 (destPoint); + + if (val_is_null (alphaImage)) { + + ImageDataUtil::CopyPixels (&_image, &_sourceImage, &_sourceRect, &_destPoint, 0, 0, mergeAlpha); + + } else { + + Image _alphaImage = Image (alphaImage); + Vector2 _alphaPoint = Vector2 (alphaPoint); + + ImageDataUtil::CopyPixels (&_image, &_sourceImage, &_sourceRect, &_destPoint, &_alphaImage, &_alphaPoint, mergeAlpha); + + } + + } + + + HL_PRIM void HL_NAME(hl_image_data_util_copy_pixels) (Image* image, Image* sourceImage, Rectangle* sourceRect, Vector2* destPoint, Image* alphaImage, Vector2* alphaPoint, bool mergeAlpha) { + + if (!alphaImage) { + + ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, NULL, NULL, mergeAlpha); + + } else { + + if (!alphaPoint) { + + Vector2 _alphaPoint = Vector2 (0, 0); + + ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, alphaImage, &_alphaPoint, mergeAlpha); + + } else { + + ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, alphaImage, alphaPoint, mergeAlpha); + + } + + } + + } + + + void lime_image_data_util_fill_rect (value image, value rect, int rg, int ba) { + + Image _image = Image (image); + Rectangle _rect = Rectangle (rect); + int32_t color = (rg << 16) | ba; + ImageDataUtil::FillRect (&_image, &_rect, color); + + } + + + HL_PRIM void HL_NAME(hl_image_data_util_fill_rect) (Image* image, Rectangle* rect, int rg, int ba) { + + int32_t color = (rg << 16) | ba; + ImageDataUtil::FillRect (image, rect, color); + + } + + + void lime_image_data_util_flood_fill (value image, int x, int y, int rg, int ba) { + + Image _image = Image (image); + int32_t color = (rg << 16) | ba; + ImageDataUtil::FloodFill (&_image, x, y, color); + + } + + + HL_PRIM void HL_NAME(hl_image_data_util_flood_fill) (Image* image, int x, int y, int rg, int ba) { + + int32_t color = (rg << 16) | ba; + ImageDataUtil::FloodFill (image, x, y, color); + + } + + + void lime_image_data_util_get_pixels (value image, value rect, int format, value bytes) { + + Image _image = Image (image); + Rectangle _rect = Rectangle (rect); + PixelFormat _format = (PixelFormat)format; + Bytes pixels = Bytes (bytes); + ImageDataUtil::GetPixels (&_image, &_rect, _format, &pixels); + + } + + + HL_PRIM void HL_NAME(hl_image_data_util_get_pixels) (Image* image, Rectangle* rect, PixelFormat format, Bytes* bytes) { + + ImageDataUtil::GetPixels (image, rect, format, bytes); + + } + + + void lime_image_data_util_merge (value image, value sourceImage, value sourceRect, value destPoint, int redMultiplier, int greenMultiplier, int blueMultiplier, int alphaMultiplier) { + + Image _image = Image (image); + Image _sourceImage = Image (sourceImage); + Rectangle _sourceRect = Rectangle (sourceRect); + Vector2 _destPoint = Vector2 (destPoint); + ImageDataUtil::Merge (&_image, &_sourceImage, &_sourceRect, &_destPoint, redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier); + + } + + + HL_PRIM void HL_NAME(hl_image_data_util_merge) (Image* image, Image* sourceImage, Rectangle* sourceRect, Vector2* destPoint, int redMultiplier, int greenMultiplier, int blueMultiplier, int alphaMultiplier) { + + ImageDataUtil::Merge (image, sourceImage, sourceRect, destPoint, redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier); + + } + + + void lime_image_data_util_multiply_alpha (value image) { + + Image _image = Image (image); + ImageDataUtil::MultiplyAlpha (&_image); + + } + + + HL_PRIM void HL_NAME(hl_image_data_util_multiply_alpha) (Image* image) { + + ImageDataUtil::MultiplyAlpha (image); + + } + + + void lime_image_data_util_resize (value image, value buffer, int width, int height) { + + Image _image = Image (image); + ImageBuffer _buffer = ImageBuffer (buffer); + ImageDataUtil::Resize (&_image, &_buffer, width, height); + + } + + + HL_PRIM void HL_NAME(hl_image_data_util_resize) (Image* image, ImageBuffer* buffer, int width, int height) { + + ImageDataUtil::Resize (image, buffer, width, height); + + } + + + void lime_image_data_util_set_format (value image, int format) { + + Image _image = Image (image); + PixelFormat _format = (PixelFormat)format; + ImageDataUtil::SetFormat (&_image, _format); + + } + + + HL_PRIM void HL_NAME(hl_image_data_util_set_format) (Image* image, PixelFormat format) { + + ImageDataUtil::SetFormat (image, format); + + } + + + void lime_image_data_util_set_pixels (value image, value rect, value bytes, int offset, int format, int endian) { + + Image _image = Image (image); + Rectangle _rect = Rectangle (rect); + Bytes _bytes (bytes); + PixelFormat _format = (PixelFormat)format; + Endian _endian = (Endian)endian; + ImageDataUtil::SetPixels (&_image, &_rect, &_bytes, offset, _format, _endian); + + } + + + HL_PRIM void HL_NAME(hl_image_data_util_set_pixels) (Image* image, Rectangle* rect, Bytes* bytes, int offset, PixelFormat format, Endian endian) { + + ImageDataUtil::SetPixels (image, rect, bytes, offset, format, endian); + + } + + + int lime_image_data_util_threshold (value image, value sourceImage, value sourceRect, value destPoint, int operation, int thresholdRG, int thresholdBA, int colorRG, int colorBA, int maskRG, int maskBA, bool copySource) { + + Image _image = Image (image); + Image _sourceImage = Image (sourceImage); + Rectangle _sourceRect = Rectangle (sourceRect); + Vector2 _destPoint = Vector2 (destPoint); + int32_t threshold = (thresholdRG << 16) | thresholdBA; + int32_t color = (colorRG << 16) | colorBA; + int32_t mask = (maskRG << 16) | maskBA; + return ImageDataUtil::Threshold (&_image, &_sourceImage, &_sourceRect, &_destPoint, operation, threshold, color, mask, copySource); + + } + + + HL_PRIM int HL_NAME(hl_image_data_util_threshold) (Image* image, Image* sourceImage, Rectangle* sourceRect, Vector2* destPoint, int operation, int thresholdRG, int thresholdBA, int colorRG, int colorBA, int maskRG, int maskBA, bool copySource) { + + int32_t threshold = (thresholdRG << 16) | thresholdBA; + int32_t color = (colorRG << 16) | colorBA; + int32_t mask = (maskRG << 16) | maskBA; + return ImageDataUtil::Threshold (image, sourceImage, sourceRect, destPoint, operation, threshold, color, mask, copySource); + + } + + + void lime_image_data_util_unmultiply_alpha (value image) { + + Image _image = Image (image); + ImageDataUtil::UnmultiplyAlpha (&_image); + + } + + + HL_PRIM void HL_NAME(hl_image_data_util_unmultiply_alpha) (Image* image) { + + ImageDataUtil::UnmultiplyAlpha (image); + + } + + + double lime_jni_getenv () { + + #ifdef ANDROID + return (uintptr_t)JNI::GetEnv (); + #else + return 0; + #endif + + } + + + HL_PRIM double HL_NAME(hl_jni_getenv) () { + + #ifdef ANDROID + return (uintptr_t)JNI::GetEnv (); + #else + return 0; + #endif + + } + + + void lime_joystick_event_manager_register (value callback, value eventObject) { + + JoystickEvent::callback = new ValuePointer (callback); + JoystickEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_joystick_event_manager_register) (vclosure* callback, JoystickEvent* eventObject) { + + JoystickEvent::callback = new ValuePointer (callback); + JoystickEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + value lime_joystick_get_device_guid (int id) { + + const char* guid = Joystick::GetDeviceGUID (id); + return guid ? alloc_string (guid) : alloc_null (); + + } + + + HL_PRIM vbyte* HL_NAME(hl_joystick_get_device_guid) (int id) { + + return (vbyte*)Joystick::GetDeviceGUID (id); + + } + + + value lime_joystick_get_device_name (int id) { + + const char* name = Joystick::GetDeviceName (id); + return name ? alloc_string (name) : alloc_null (); + + } + + + HL_PRIM vbyte* HL_NAME(hl_joystick_get_device_name) (int id) { + + return (vbyte*)Joystick::GetDeviceName (id); + + } + + + int lime_joystick_get_num_axes (int id) { + + return Joystick::GetNumAxes (id); + + } + + + HL_PRIM int HL_NAME(hl_joystick_get_num_axes) (int id) { + + return Joystick::GetNumAxes (id); + + } + + + int lime_joystick_get_num_buttons (int id) { + + return Joystick::GetNumButtons (id); + + } + + + HL_PRIM int HL_NAME(hl_joystick_get_num_buttons) (int id) { + + return Joystick::GetNumButtons (id); + + } + + + int lime_joystick_get_num_hats (int id) { + + return Joystick::GetNumHats (id); + + } + + + HL_PRIM int HL_NAME(hl_joystick_get_num_hats) (int id) { + + return Joystick::GetNumHats (id); + + } + + + value lime_jpeg_decode_bytes (value data, bool decodeData, value buffer) { + + ImageBuffer imageBuffer (buffer); + + Bytes bytes (data); + Resource resource = Resource (&bytes); + + #ifdef LIME_JPEG + if (JPEG::Decode (&resource, &imageBuffer, decodeData)) { + + return imageBuffer.Value (buffer); + + } + #endif + + return alloc_null (); + + } + + + HL_PRIM ImageBuffer* HL_NAME(hl_jpeg_decode_bytes) (Bytes* data, bool decodeData, ImageBuffer* buffer) { + + Resource resource = Resource (data); + + #ifdef LIME_JPEG + if (JPEG::Decode (&resource, buffer, decodeData)) { + + return buffer; + + } + #endif + + return 0; + + } + + + value lime_jpeg_decode_file (HxString path, bool decodeData, value buffer) { + + ImageBuffer imageBuffer (buffer); + Resource resource = Resource (hxs_utf8 (path, nullptr)); + + #ifdef LIME_JPEG + if (JPEG::Decode (&resource, &imageBuffer, decodeData)) { + + return imageBuffer.Value (buffer); + + } + #endif + + return alloc_null (); + + } + + + HL_PRIM ImageBuffer* HL_NAME(hl_jpeg_decode_file) (hl_vstring* path, bool decodeData, ImageBuffer* buffer) { + + Resource resource = Resource (path); + + #ifdef LIME_JPEG + if (JPEG::Decode (&resource, buffer, decodeData)) { + + return buffer; + + } + #endif + + return 0; + + } + + + int lime_key_code_from_scan_code (int scanCode) { + + return KeyCode::FromScanCode (scanCode); + + } + + + HL_PRIM int HL_NAME(hl_key_code_from_scan_code) (int scanCode) { + + return KeyCode::FromScanCode (scanCode); + + } + + + int lime_key_code_to_scan_code (int keyCode) { + + return KeyCode::ToScanCode (keyCode); + + } + + + HL_PRIM int HL_NAME(hl_key_code_to_scan_code) (int keyCode) { + + return KeyCode::ToScanCode (keyCode); + + } + + + void lime_key_event_manager_register (value callback, value eventObject) { + + KeyEvent::callback = new ValuePointer (callback); + KeyEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_key_event_manager_register) (vclosure* callback, KeyEvent* eventObject) { + + KeyEvent::callback = new ValuePointer (callback); + KeyEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + value lime_locale_get_system_locale () { + + std::string* locale = Locale::GetSystemLocale (); + + if (!locale) { + + return alloc_null (); + + } else { + + value result = alloc_string (locale->c_str ()); + delete locale; + return result; + + } + + } + + + value lime_locale_get_preferred_locales () { + + std::string* locales = Locale::GetPreferredLocales (); + value result = alloc_string (locales->c_str ()); + delete locales; + return result; + + } + + + HL_PRIM vbyte* HL_NAME(hl_locale_get_preferred_locales) () { + + std::string* locales = Locale::GetPreferredLocales (); + int size = locales->size (); + vbyte* result = hl_alloc_bytes (size + 1); + memcpy (result, locales->c_str (), size + 1); + delete locales; + return result; + + } + + + HL_PRIM vbyte* HL_NAME(hl_locale_get_system_locale) () { + + std::string* locale = Locale::GetSystemLocale (); + + if (!locale) { + + return 0; + + } else { + + int size = locale->size (); + char* _locale = (char*)malloc (size + 1); + strncpy (_locale, locale->c_str (), size); + _locale[size] = '\0'; + delete locale; + + return (vbyte*)_locale; + + } + + } + + + value lime_lzma_compress (value buffer, value bytes) { + + #ifdef LIME_LZMA + Bytes data (buffer); + Bytes result (bytes); + + LZMA::Compress (&data, &result); + + return result.Value (bytes); + #else + return alloc_null (); + #endif + + } + + + HL_PRIM Bytes* HL_NAME(hl_lzma_compress) (Bytes* buffer, Bytes* bytes) { + + #ifdef LIME_LZMA + LZMA::Compress (buffer, bytes); + return bytes; + #else + return 0; + #endif + + } + + + value lime_lzma_decompress (value buffer, value bytes) { + + #ifdef LIME_LZMA + Bytes data (buffer); + Bytes result (bytes); + + LZMA::Decompress (&data, &result); + + return result.Value (bytes); + #else + return alloc_null (); + #endif + + } + + + HL_PRIM Bytes* HL_NAME(hl_lzma_decompress) (Bytes* buffer, Bytes* bytes) { + + #ifdef LIME_LZMA + LZMA::Decompress (buffer, bytes); + return bytes; + #else + return 0; + #endif + + } + + + int lime_menu_get_support () { + + return Menu::GetSupport (); + + } + + + HL_PRIM int HL_NAME(hl_menu_get_support) () { + + return Menu::GetSupport (); + + } + + + void lime_menu_event_manager_register (value callback, value eventObject) { + + MenuEvent::callback = new ValuePointer (callback); + MenuEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_menu_event_manager_register) (vclosure* callback, MenuEvent* eventObject) { + + MenuEvent::callback = new ValuePointer (callback); + MenuEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + void lime_mouse_event_manager_register (value callback, value eventObject) { + + MouseEvent::callback = new ValuePointer (callback); + MouseEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_mouse_event_manager_register) (vclosure* callback, MouseEvent* eventObject) { + + MouseEvent::callback = new ValuePointer (callback); + MouseEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + void lime_neko_execute (HxString module) { + + #ifdef LIME_NEKO + NekoVM::Execute (module.c_str ()); + #endif + + } + + + void lime_orientation_event_manager_register (value callback, value eventObject) { + + OrientationEvent::callback = new ValuePointer (callback); + OrientationEvent::eventObject = new ValuePointer (eventObject); + System::EnableDeviceOrientationChange(true); + + } + + + HL_PRIM void HL_NAME(hl_orientation_event_manager_register) (vclosure* callback, OrientationEvent* eventObject) { + + OrientationEvent::callback = new ValuePointer (callback); + OrientationEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + value lime_png_decode_bytes (value data, bool decodeData, value buffer) { + + ImageBuffer imageBuffer (buffer); + Bytes bytes (data); + Resource resource = Resource (&bytes); + + #ifdef LIME_PNG + if (PNG::Decode (&resource, &imageBuffer, decodeData)) { + + return imageBuffer.Value (buffer); + + } + #endif + + return alloc_null (); + + } + + + HL_PRIM ImageBuffer* HL_NAME(hl_png_decode_bytes) (Bytes* data, bool decodeData, ImageBuffer* buffer) { + + Resource resource = Resource (data); + + #ifdef LIME_PNG + if (PNG::Decode (&resource, buffer, decodeData)) { + + return buffer; + + } + #endif + + return 0; + + } + + + value lime_png_decode_file (HxString path, bool decodeData, value buffer) { + + ImageBuffer imageBuffer (buffer); + Resource resource = Resource (hxs_utf8 (path, nullptr)); + + #ifdef LIME_PNG + if (PNG::Decode (&resource, &imageBuffer, decodeData)) { + + return imageBuffer.Value (buffer); + + } + #endif + + return alloc_null (); + + } + + + HL_PRIM ImageBuffer* HL_NAME(hl_png_decode_file) (hl_vstring* path, bool decodeData, ImageBuffer* buffer) { + + Resource resource = Resource (path); + + #ifdef LIME_PNG + if (PNG::Decode (&resource, buffer, decodeData)) { + + return buffer; + + } + #endif + + return 0; + + } + + + void lime_render_event_manager_register (value callback, value eventObject) { + + RenderEvent::callback = new ValuePointer (callback); + RenderEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_render_event_manager_register) (vclosure* callback, RenderEvent* eventObject) { + + RenderEvent::callback = new ValuePointer (callback); + RenderEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + void lime_sensor_event_manager_register (value callback, value eventObject) { + + SensorEvent::callback = new ValuePointer (callback); + SensorEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_sensor_event_manager_register) (vclosure* callback, SensorEvent* eventObject) { + + SensorEvent::callback = new ValuePointer (callback); + SensorEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + bool lime_system_get_allow_screen_timeout () { + + return System::GetAllowScreenTimeout (); + + } + + + HL_PRIM bool HL_NAME(hl_system_get_allow_screen_timeout) () { + + return System::GetAllowScreenTimeout (); + + } + + + value lime_system_get_device_model () { + + std::wstring* model = System::GetDeviceModel (); + + if (model) { + + value result = alloc_wstring (model->c_str ()); + delete model; + return result; + + } else { + + return alloc_null (); + + } + + } + + + HL_PRIM vbyte* HL_NAME(hl_system_get_device_model) () { + + #ifndef EMSCRIPTEN + + std::wstring* model = System::GetDeviceModel (); + + if (model) { + + vbyte* const result = hl_wstring_to_utf8_bytes (*model); + delete model; + return result; + + } + + #endif + + return 0; + + } + + + value lime_system_get_device_vendor () { + + std::wstring* vendor = System::GetDeviceVendor (); + + if (vendor) { + + value result = alloc_wstring (vendor->c_str ()); + delete vendor; + return result; + + } else { + + return alloc_null (); + + } + + } + + + HL_PRIM vbyte* HL_NAME(hl_system_get_device_vendor) () { + + #ifndef EMSCRIPTEN + + std::wstring* vendor = System::GetDeviceVendor (); + + if (vendor) { + + vbyte* const result = hl_wstring_to_utf8_bytes (*vendor); + delete vendor; + return result; + + } + + #endif + + return 0; + + } + + + value lime_system_get_directory (int type, HxString company, HxString title) { + + std::wstring* path = System::GetDirectory ((SystemDirectory)type, hxs_utf8 (company, nullptr), hxs_utf8 (title, nullptr)); + + if (path) { + + value result = alloc_wstring (path->c_str ()); + delete path; + return result; + + } else { + + return alloc_null (); + + } + + } + + + HL_PRIM vbyte* HL_NAME(hl_system_get_directory) (int type, hl_vstring* company, hl_vstring* title) { + + #ifndef EMSCRIPTEN + + std::wstring* path = System::GetDirectory ((SystemDirectory)type, company ? (char*)hl_to_utf8 ((const uchar*)company->bytes) : NULL, title ? (char*)hl_to_utf8 ((const uchar*)title->bytes) : NULL); + + if (path) { + + vbyte* const result = hl_wstring_to_utf8_bytes (*path); + delete path; + return result; + + } + + #endif + + return 0; + + } + + + value lime_system_get_display (int id) { + + return (value)System::GetDisplay (true, id); + + } + + + HL_PRIM vdynamic* HL_NAME(hl_system_get_display) (int id) { + + return (vdynamic*)System::GetDisplay (false, id); + + } + + + bool lime_system_get_ios_tablet () { + + #ifdef IPHONE + return System::GetIOSTablet (); + #else + return false; + #endif + + } + + + HL_PRIM bool HL_NAME(hl_system_get_ios_tablet) () { + + #ifdef IPHONE + return System::GetIOSTablet (); + #else + return false; + #endif + + } + + + int lime_system_get_num_displays () { + + return System::GetNumDisplays (); + + } + + + HL_PRIM int HL_NAME(hl_system_get_num_displays) () { + + return System::GetNumDisplays (); + + } + + + int lime_system_get_device_orientation () { + + return System::GetDeviceOrientation(); + + } + + + HL_PRIM int HL_NAME(hl_system_get_device_orientation) () { + + return System::GetDeviceOrientation(); + + } + + + value lime_system_get_platform_label () { + + std::wstring* label = System::GetPlatformLabel (); + + if (label) { + + value result = alloc_wstring (label->c_str ()); + delete label; + return result; + + } else { + + return alloc_null (); + + } + + } + + + HL_PRIM vbyte* HL_NAME(hl_system_get_platform_label) () { + + #ifndef EMSCRIPTEN + + std::wstring* label = System::GetPlatformLabel (); + + if (label) { + + vbyte* const result = hl_wstring_to_utf8_bytes (*label); + delete label; + return result; + + } + + #endif + + return 0; + + } + + + value lime_system_get_platform_name () { + + std::wstring* name = System::GetPlatformName (); + + if (name) { + + value result = alloc_wstring (name->c_str ()); + delete name; + return result; + + } else { + + return 0; + + } + + } + + + HL_PRIM vbyte* HL_NAME(hl_system_get_platform_name) () { + + #ifndef EMSCRIPTEN + + std::wstring* name = System::GetPlatformName (); + + if (name) { + + vbyte* const result = hl_wstring_to_utf8_bytes (*name); + delete name; + return result; + + } + + #endif + + return 0; + + } + + + value lime_system_get_platform_version () { + + std::wstring* version = System::GetPlatformVersion (); + + if (version) { + + value result = alloc_wstring (version->c_str ()); + delete version; + return result; + + } else { + + return alloc_null (); + + } + + } + + + HL_PRIM vbyte* HL_NAME(hl_system_get_platform_version) () { + + #ifndef EMSCRIPTEN + + std::wstring* version = System::GetPlatformVersion (); + + if (version) { + + vbyte* const result = hl_wstring_to_utf8_bytes (*version); + delete version; + return result; + } + + #endif + + return 0; + + } + + + double lime_system_get_timer () { + + return System::GetTimer (); + + } + + + HL_PRIM double HL_NAME(hl_system_get_timer) () { + + return System::GetTimer (); + + } + + + int lime_system_get_windows_console_mode (int handleType) { + + #if defined (HX_WINDOWS) && !defined (HX_WINRT) + return System::GetWindowsConsoleMode (handleType); + #else + return 0; + #endif + + } + + + HL_PRIM int HL_NAME(hl_system_get_windows_console_mode) (int handleType) { + + #if defined (HX_WINDOWS) && !defined (HX_WINRT) + return System::GetWindowsConsoleMode (handleType); + #else + return 0; + #endif + + } + + + void lime_system_open_file (HxString path) { + + #ifdef IPHONE + System::OpenFile (path.c_str ()); + #endif + + } + + + HL_PRIM void HL_NAME(hl_system_open_file) (vbyte* path) { + + #ifdef IPHONE + System::OpenFile ((char*)path); + #endif + + } + + + void lime_system_open_url (HxString url, HxString target) { + + #ifdef IPHONE + System::OpenURL (url.c_str (), target.c_str ()); + #endif + + } + + + HL_PRIM void HL_NAME(hl_system_open_url) (vbyte* url, vbyte* target) { + + #ifdef IPHONE + System::OpenURL ((char*)url, (char*)target); + #endif + + } + + + bool lime_system_set_allow_screen_timeout (bool allow) { + + return System::SetAllowScreenTimeout (allow); + + } + + + HL_PRIM bool HL_NAME(hl_system_set_allow_screen_timeout) (bool allow) { + + return System::SetAllowScreenTimeout (allow); + + } + + + bool lime_system_set_windows_console_mode (int handleType, int mode) { + + #if defined (HX_WINDOWS) && !defined (HX_WINRT) + return System::SetWindowsConsoleMode (handleType, mode); + #else + return false; + #endif + + } + + + HL_PRIM bool HL_NAME(hl_system_set_windows_console_mode) (int handleType, int mode) { + + #if defined (HX_WINDOWS) && !defined (HX_WINRT) + return System::SetWindowsConsoleMode (handleType, mode); + #else + return false; + #endif + + } + + + void lime_text_event_manager_register (value callback, value eventObject) { + + TextEvent::callback = new ValuePointer (callback); + TextEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_text_event_manager_register) (vclosure* callback, TextEvent* eventObject) { + + TextEvent::callback = new ValuePointer (callback); + TextEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + void lime_touch_event_manager_register (value callback, value eventObject) { + + TouchEvent::callback = new ValuePointer (callback); + TouchEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_touch_event_manager_register) (vclosure* callback, TouchEvent* eventObject) { + + TouchEvent::callback = new ValuePointer (callback); + TouchEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + void lime_tray_icon_close (value trayIcon) { + + TrayIcon* targetTrayIcon = (TrayIcon*)val_data (trayIcon); + targetTrayIcon->Close (); + + } + + + HL_PRIM void HL_NAME(hl_tray_icon_close) (HL_CFFIPointer* trayIcon) { + + TrayIcon* targetTrayIcon = (TrayIcon*)trayIcon->ptr; + targetTrayIcon->Close (); + + } + + + value lime_tray_icon_create () { + + TrayIcon* trayIcon = CreateTrayIcon (); + return CFFIPointer (trayIcon, gc_tray_icon); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_tray_icon_create) () { + + TrayIcon* trayIcon = CreateTrayIcon (); + return HLCFFIPointer (trayIcon, (hl_finalizer)hl_gc_tray_icon); + + } + + + void lime_tray_icon_event_manager_register (value callback, value eventObject) { + + TrayIconEvent::callback = new ValuePointer (callback); + TrayIconEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_tray_icon_event_manager_register) (vclosure* callback, TrayIconEvent* eventObject) { + + TrayIconEvent::callback = new ValuePointer (callback); + TrayIconEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + int lime_tray_icon_get_id (value trayIcon) { + + TrayIcon* targetTrayIcon = (TrayIcon*)val_data (trayIcon); + return targetTrayIcon->GetID (); + + } + + + HL_PRIM int HL_NAME(hl_tray_icon_get_id) (HL_CFFIPointer* trayIcon) { + + TrayIcon* targetTrayIcon = (TrayIcon*)trayIcon->ptr; + return targetTrayIcon->GetID (); + + } + + + bool lime_tray_icon_is_supported () { + + return TrayIcon::IsSupported (); + + } + + + HL_PRIM bool HL_NAME(hl_tray_icon_is_supported) () { + + return TrayIcon::IsSupported (); + + } + + + int lime_tray_icon_popup_menu (value trayIcon, value data) { + + TrayIcon* targetTrayIcon = (TrayIcon*)val_data (trayIcon); + Bytes bytes (data); + return targetTrayIcon->PopupMenu (bytes.b, bytes.length); + + } + + + HL_PRIM int HL_NAME(hl_tray_icon_popup_menu) (HL_CFFIPointer* trayIcon, Bytes* data) { + + TrayIcon* targetTrayIcon = (TrayIcon*)trayIcon->ptr; + return targetTrayIcon->PopupMenu (data ? data->b : 0, data ? data->length : 0); + + } + + + void lime_tray_icon_set_icon (value trayIcon, value buffer) { + + TrayIcon* targetTrayIcon = (TrayIcon*)val_data (trayIcon); + + if (val_is_null (buffer)) { + + targetTrayIcon->SetIcon (NULL); + return; + + } + + ImageBuffer imageBuffer = ImageBuffer (buffer); + targetTrayIcon->SetIcon (&imageBuffer); + + } + + + HL_PRIM void HL_NAME(hl_tray_icon_set_icon) (HL_CFFIPointer* trayIcon, ImageBuffer* buffer) { + + TrayIcon* targetTrayIcon = (TrayIcon*)trayIcon->ptr; + targetTrayIcon->SetIcon (buffer); + + } + + + bool lime_tray_icon_set_menu (value trayIcon, value data) { + + TrayIcon* targetTrayIcon = (TrayIcon*)val_data (trayIcon); + Bytes bytes (data); + return targetTrayIcon->SetMenu (bytes.b, bytes.length); + + } + + + HL_PRIM bool HL_NAME(hl_tray_icon_set_menu) (HL_CFFIPointer* trayIcon, Bytes* data) { + + TrayIcon* targetTrayIcon = (TrayIcon*)trayIcon->ptr; + return targetTrayIcon->SetMenu (data ? data->b : 0, data ? data->length : 0); + + } + + + void lime_tray_icon_set_tooltip (value trayIcon, HxString tooltip) { + + TrayIcon* targetTrayIcon = (TrayIcon*)val_data (trayIcon); + targetTrayIcon->SetTooltip (tooltip.c_str ()); + + } + + + HL_PRIM void HL_NAME(hl_tray_icon_set_tooltip) (HL_CFFIPointer* trayIcon, hl_vstring* tooltip) { + + TrayIcon* targetTrayIcon = (TrayIcon*)trayIcon->ptr; + targetTrayIcon->SetTooltip (tooltip ? (const char*)hl_to_utf8 ((const uchar*)tooltip->bytes) : NULL); + + } + + + void lime_window_alert (value window, HxString message, HxString title) { + + Window* targetWindow = (Window*)val_data (window); + targetWindow->Alert (hxs_utf8 (message, nullptr), hxs_utf8 (title, nullptr)); + + } + + + HL_PRIM void HL_NAME(hl_window_alert) (HL_CFFIPointer* window, hl_vstring* message, hl_vstring* title) { + + Window* targetWindow = (Window*)window->ptr; + const char *cmessage = message ? hl_to_utf8(message->bytes) : nullptr; + const char *ctitle = title ? hl_to_utf8(title->bytes) : nullptr; + targetWindow->Alert (cmessage, ctitle); + + } + + + void lime_window_close (value window) { + + Window* targetWindow = (Window*)val_data (window); + targetWindow->Close (); + + } + + + HL_PRIM void HL_NAME(hl_window_close) (HL_CFFIPointer* window) { + + Window* targetWindow = (Window*)window->ptr; + targetWindow->Close (); + + } + + + void lime_window_context_flip (value window) { + + ((Window*)val_data (window))->ContextFlip (); + + } + + + HL_PRIM void HL_NAME(hl_window_context_flip) (HL_CFFIPointer* window) { + + ((Window*)window->ptr)->ContextFlip (); + + } + + + value lime_window_context_lock (value window) { + + return (value)((Window*)val_data (window))->ContextLock (true); + + } + + + HL_PRIM vdynamic* HL_NAME(hl_window_context_lock) (HL_CFFIPointer* window) { + + return (vdynamic*)((Window*)window->ptr)->ContextLock (false); + + } + + + void lime_window_context_make_current (value window) { + + ((Window*)val_data (window))->ContextMakeCurrent (); + + } + + + HL_PRIM void HL_NAME(hl_window_context_make_current) (HL_CFFIPointer* window) { + + ((Window*)window->ptr)->ContextMakeCurrent (); + + } + + + void lime_window_context_unlock (value window) { + + ((Window*)val_data (window))->ContextUnlock (); + + } + + + HL_PRIM void HL_NAME(hl_window_context_unlock) (HL_CFFIPointer* window) { + + ((Window*)window->ptr)->ContextUnlock (); + + } + + + value lime_window_create (value application, int width, int height, int flags, HxString title) { + + Window* window = CreateWindow ((Application*)val_data (application), width, height, flags, hxs_utf8 (title, nullptr)); + return CFFIPointer (window, gc_window); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_window_create) (HL_CFFIPointer* application, int width, int height, int flags, hl_vstring* title) { + + Window* window = CreateWindow ((Application*)application->ptr, width, height, flags, (const char*)hl_to_utf8 ((const uchar*)title->bytes)); + return HLCFFIPointer (window, (hl_finalizer)hl_gc_window); + + } + + + void lime_window_event_manager_register (value callback, value eventObject) { + + WindowEvent::callback = new ValuePointer (callback); + WindowEvent::eventObject = new ValuePointer (eventObject); + + } + + + HL_PRIM void HL_NAME(hl_window_event_manager_register) (vclosure* callback, WindowEvent* eventObject) { + + WindowEvent::callback = new ValuePointer (callback); + WindowEvent::eventObject = new ValuePointer ((vobj*)eventObject); + + } + + + void lime_window_focus (value window) { + + Window* targetWindow = (Window*)val_data (window); + targetWindow->Focus (); + + } + + + HL_PRIM void HL_NAME(hl_window_focus) (HL_CFFIPointer* window) { + + Window* targetWindow = (Window*)window->ptr; + targetWindow->Focus (); + + } + + + double lime_window_get_context (value window) { + + Window* targetWindow = (Window*)val_data (window); + return (uintptr_t)targetWindow->GetContext (); + + } + + + HL_PRIM double HL_NAME(hl_window_get_context) (HL_CFFIPointer* window) { + + Window* targetWindow = (Window*)window->ptr; + return (uintptr_t)targetWindow->GetContext (); + + } + + + value lime_window_get_context_type (value window) { + + Window* targetWindow = (Window*)val_data (window); + const char* type = targetWindow->GetContextType (); + return type ? alloc_string (type) : alloc_null (); + + } + + + HL_PRIM vbyte* HL_NAME(hl_window_get_context_type) (HL_CFFIPointer* window) { + + Window* targetWindow = (Window*)window->ptr; + return (vbyte*)targetWindow->GetContextType (); + + } + + + value lime_window_get_vulkan_instance_extensions (value window) { + + Window* targetWindow = (Window*)val_data (window); + unsigned int count = 0; + + if (!targetWindow->GetVulkanInstanceExtensions (&count, 0) || count == 0) { + + return alloc_array (0); + + } + + std::vector names (count); + if (!targetWindow->GetVulkanInstanceExtensions (&count, names.data ())) { + + return alloc_array (0); + + } + + value result = alloc_array (count); + + for (unsigned int i = 0; i < count; ++i) { + + val_array_set_i (result, i, alloc_string (names[i])); + + } + + return result; + + } + + + HL_PRIM hl_varray* HL_NAME(hl_window_get_vulkan_instance_extensions) (HL_CFFIPointer* window) { + + Window* targetWindow = (Window*)window->ptr; + unsigned int count = 0; + + if (!targetWindow->GetVulkanInstanceExtensions (&count, 0) || count == 0) { + + return hl_alloc_array (&hlt_bytes, 0); + + } + + std::vector names (count); + if (!targetWindow->GetVulkanInstanceExtensions (&count, names.data ())) { + + return hl_alloc_array (&hlt_bytes, 0); + + } + + hl_varray* result = (hl_varray*)hl_alloc_array (&hlt_bytes, count); + vbyte** resultData = hl_aptr (result, vbyte*); + + for (unsigned int i = 0; i < count; ++i) { + + *resultData++ = hl_copy_bytes ((const vbyte*)names[i], (int)std::strlen (names[i]) + 1); + + } + + return result; + + } + + + value lime_window_get_vulkan_drawable_size (value window) { + + Window* targetWindow = (Window*)val_data (window); + int width = 0; + int height = 0; + targetWindow->GetVulkanDrawableSize (&width, &height); + + value result = alloc_empty_object (); + alloc_field (result, val_id ("width"), alloc_int (width)); + alloc_field (result, val_id ("height"), alloc_int (height)); + return result; + + } + + + HL_PRIM vdynamic* HL_NAME(hl_window_get_vulkan_drawable_size) (HL_CFFIPointer* window) { + + Window* targetWindow = (Window*)window->ptr; + int width = 0; + int height = 0; + targetWindow->GetVulkanDrawableSize (&width, &height); + + const int id_width = hl_hash_utf8 ("width"); + const int id_height = hl_hash_utf8 ("height"); + vdynamic* result = (vdynamic*)hl_alloc_dynobj(); + hl_dyn_seti (result, id_width, &hlt_i32, width); + hl_dyn_seti (result, id_height, &hlt_i32, height); + return result; + + } + + + value lime_window_get_vulkan_instance_proc_addr (value window) { + + Window* targetWindow = (Window*)val_data (window); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)targetWindow->GetVulkanInstanceProcAddr ()); + + } + + + HL_PRIM vdynamic* HL_NAME(hl_window_get_vulkan_instance_proc_addr) (HL_CFFIPointer* window) { + + Window* targetWindow = (Window*)window->ptr; + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)targetWindow->GetVulkanInstanceProcAddr ()); + + } + + + value lime_window_create_vulkan_surface (value window, int instanceHigh, int instanceLow) { + + Window* targetWindow = (Window*)val_data (window); + uint64_t surface = targetWindow->CreateVulkanSurface ((uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow)); + + if (!surface) { + + return alloc_null (); + + } + + return CreateVulkanHandleValue (surface); + + } + + + HL_PRIM vdynamic* HL_NAME(hl_window_create_vulkan_surface) (HL_CFFIPointer* window, int instanceHigh, int instanceLow) { + + Window* targetWindow = (Window*)window->ptr; + uint64_t surface = targetWindow->CreateVulkanSurface ((uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow)); + + if (!surface) { + + return 0; + + } + + return HLCreateVulkanHandleValue (surface); + + } + + + value lime_vk_create_instance (value window, HxString applicationName) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = VK_NULL_HANDLE; + + if (!CreateManagedVulkanInstance (targetWindow, applicationName.c_str () ? hxs_utf8 (applicationName, nullptr) : 0, &instance)) { + + return alloc_null (); + + } + + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)instance); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_create_instance) (HL_CFFIPointer* window, hl_vstring* applicationName) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = VK_NULL_HANDLE; + const char* applicationNameUTF8 = applicationName ? (const char*)hl_to_utf8 ((const uchar*)applicationName->bytes) : 0; + + if (!CreateManagedVulkanInstance (targetWindow, applicationNameUTF8, &instance)) { + + return 0; + + } + + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)instance); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + void lime_vk_destroy_instance (value window, int instanceHigh, int instanceLow) { + +#ifdef LIME_VULKAN + if (instanceHigh == 0 && instanceLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = targetWindow ? (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr () : 0; + if (!vkGetInstanceProcAddr) return; + + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + PFN_vkDestroyInstance vkDestroyInstance = (PFN_vkDestroyInstance)vkGetInstanceProcAddr (instance, "vkDestroyInstance"); + if (vkDestroyInstance) { + + vkDestroyInstance (instance, 0); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_destroy_instance) (HL_CFFIPointer* window, int instanceHigh, int instanceLow) { + +#ifdef LIME_VULKAN + if (instanceHigh == 0 && instanceLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = targetWindow ? (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr () : 0; + if (!vkGetInstanceProcAddr) return; + + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + PFN_vkDestroyInstance vkDestroyInstance = (PFN_vkDestroyInstance)vkGetInstanceProcAddr (instance, "vkDestroyInstance"); + if (vkDestroyInstance) { + + vkDestroyInstance (instance, 0); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + void lime_vk_destroy_surface (value window, int instanceHigh, int instanceLow, int surfaceHigh, int surfaceLow) { + +#ifdef LIME_VULKAN + if (instanceHigh == 0 && instanceLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = targetWindow ? (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr () : 0; + if (!vkGetInstanceProcAddr) return; + + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkSurfaceKHR surface = UInt64ToVulkanSurface (CombineVulkanHandle (surfaceHigh, surfaceLow)); + if (!surface) return; + + PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR = (PFN_vkDestroySurfaceKHR)vkGetInstanceProcAddr (instance, "vkDestroySurfaceKHR"); + if (vkDestroySurfaceKHR) { + + vkDestroySurfaceKHR (instance, surface, 0); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_destroy_surface) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int surfaceHigh, int surfaceLow) { + +#ifdef LIME_VULKAN + if (instanceHigh == 0 && instanceLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = targetWindow ? (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr () : 0; + if (!vkGetInstanceProcAddr) return; + + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkSurfaceKHR surface = UInt64ToVulkanSurface (CombineVulkanHandle (surfaceHigh, surfaceLow)); + if (!surface) return; + + PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR = (PFN_vkDestroySurfaceKHR)vkGetInstanceProcAddr (instance, "vkDestroySurfaceKHR"); + if (vkDestroySurfaceKHR) { + + vkDestroySurfaceKHR (instance, surface, 0); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + value lime_vk_get_physical_devices (value window, int instanceHigh, int instanceLow, int surfaceHigh, int surfaceLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + + if (!targetWindow || !instance) { + + lastVKError = "Missing Vulkan window or instance"; + return alloc_null (); + + } + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr (); + if (!vkGetInstanceProcAddr) { + + lastVKError = "SDL did not expose vkGetInstanceProcAddr"; + return alloc_null (); + + } + + PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices = (PFN_vkEnumeratePhysicalDevices)vkGetInstanceProcAddr (instance, + "vkEnumeratePhysicalDevices"); + PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties)vkGetInstanceProcAddr (instance, + "vkGetPhysicalDeviceProperties"); + PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties = + (PFN_vkGetPhysicalDeviceQueueFamilyProperties)vkGetInstanceProcAddr (instance, "vkGetPhysicalDeviceQueueFamilyProperties"); + PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR = + (PFN_vkGetPhysicalDeviceSurfaceSupportKHR)vkGetInstanceProcAddr (instance, "vkGetPhysicalDeviceSurfaceSupportKHR"); + + if (!vkEnumeratePhysicalDevices || !vkGetPhysicalDeviceProperties || !vkGetPhysicalDeviceQueueFamilyProperties) { + + lastVKError = "Missing required Vulkan instance functions"; + return alloc_null (); + + } + + VkSurfaceKHR surface = UInt64ToVulkanSurface (CombineVulkanHandle (surfaceHigh, surfaceLow)); + uint32_t deviceCount = 0; + VkResult result = vkEnumeratePhysicalDevices (instance, &deviceCount, 0); + + if (result != VK_SUCCESS) { + + lastVKError = "vkEnumeratePhysicalDevices failed"; + return alloc_null (); + + } + + value devices = alloc_array (deviceCount); + if (deviceCount == 0) { + + lastVKError.clear (); + return devices; + + } + + std::vector physicalDevices (deviceCount); + result = vkEnumeratePhysicalDevices (instance, &deviceCount, physicalDevices.data ()); + + if (result != VK_SUCCESS) { + + lastVKError = "vkEnumeratePhysicalDevices returned incomplete data"; + return alloc_null (); + + } + + const int id_handle = val_id ("handle"); + const int id_name = val_id ("name"); + const int id_apiVersion = val_id ("apiVersion"); + const int id_driverVersion = val_id ("driverVersion"); + const int id_vendorID = val_id ("vendorID"); + const int id_deviceID = val_id ("deviceID"); + const int id_deviceType = val_id ("deviceType"); + const int id_framebufferColorSampleCounts = val_id ("framebufferColorSampleCounts"); + const int id_framebufferDepthSampleCounts = val_id ("framebufferDepthSampleCounts"); + const int id_framebufferNoAttachmentsSampleCounts = val_id ("framebufferNoAttachmentsSampleCounts"); + const int id_framebufferStencilSampleCounts = val_id ("framebufferStencilSampleCounts"); + const int id_maxPushConstantsSize = val_id ("maxPushConstantsSize"); + const int id_minStorageBufferOffsetAlignment = val_id ("minStorageBufferOffsetAlignment"); + const int id_minUniformBufferOffsetAlignment = val_id ("minUniformBufferOffsetAlignment"); + const int id_nonCoherentAtomSize = val_id ("nonCoherentAtomSize"); + const int id_queueFamilies = val_id ("queueFamilies"); + + const int id_index = val_id ("index"); + const int id_flags = val_id ("flags"); + const int id_queueCount = val_id ("queueCount"); + const int id_timestampValidBits = val_id ("timestampValidBits"); + const int id_supportsGraphics = val_id ("supportsGraphics"); + const int id_supportsCompute = val_id ("supportsCompute"); + const int id_supportsTransfer = val_id ("supportsTransfer"); + const int id_supportsPresent = val_id ("supportsPresent"); + + for (uint32_t i = 0; i < deviceCount; ++i) { + + VkPhysicalDeviceProperties properties; + memset (&properties, 0, sizeof (properties)); + vkGetPhysicalDeviceProperties (physicalDevices[i], &properties); + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties (physicalDevices[i], &queueFamilyCount, 0); + std::vector queueFamilies (queueFamilyCount); + if (queueFamilyCount > 0) { + + vkGetPhysicalDeviceQueueFamilyProperties (physicalDevices[i], &queueFamilyCount, queueFamilies.data ()); + + } + + value queueFamilyArray = alloc_array (queueFamilyCount); + + for (uint32_t q = 0; q < queueFamilyCount; ++q) { + + VkBool32 supportsPresent = VK_FALSE; + if (surface && vkGetPhysicalDeviceSurfaceSupportKHR) { + + vkGetPhysicalDeviceSurfaceSupportKHR (physicalDevices[i], q, surface, &supportsPresent); + + } + + value queueFamilyObject = alloc_empty_object (); + alloc_field (queueFamilyObject, id_index, alloc_int (q)); + alloc_field (queueFamilyObject, id_flags, alloc_int (queueFamilies[q].queueFlags)); + alloc_field (queueFamilyObject, id_queueCount, alloc_int (queueFamilies[q].queueCount)); + alloc_field (queueFamilyObject, id_timestampValidBits, alloc_int (queueFamilies[q].timestampValidBits)); + alloc_field (queueFamilyObject, id_supportsGraphics, alloc_bool ((queueFamilies[q].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)); + alloc_field (queueFamilyObject, id_supportsCompute, alloc_bool ((queueFamilies[q].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0)); + alloc_field (queueFamilyObject, id_supportsTransfer, alloc_bool ((queueFamilies[q].queueFlags & VK_QUEUE_TRANSFER_BIT) != 0)); + alloc_field (queueFamilyObject, id_supportsPresent, alloc_bool (supportsPresent != VK_FALSE)); + val_array_set_i (queueFamilyArray, q, queueFamilyObject); + + } + + value deviceObject = alloc_empty_object (); + alloc_field (deviceObject, id_handle, CreateVulkanHandleValue ((uint64_t)(uintptr_t)physicalDevices[i])); + alloc_field (deviceObject, id_name, alloc_string (properties.deviceName)); + alloc_field (deviceObject, id_apiVersion, alloc_int (properties.apiVersion)); + alloc_field (deviceObject, id_driverVersion, alloc_int (properties.driverVersion)); + alloc_field (deviceObject, id_vendorID, alloc_int (properties.vendorID)); + alloc_field (deviceObject, id_deviceID, alloc_int (properties.deviceID)); + alloc_field (deviceObject, id_deviceType, alloc_int (properties.deviceType)); + alloc_field (deviceObject, id_framebufferColorSampleCounts, alloc_int ((int)properties.limits.framebufferColorSampleCounts)); + alloc_field (deviceObject, id_framebufferDepthSampleCounts, alloc_int ((int)properties.limits.framebufferDepthSampleCounts)); + alloc_field (deviceObject, id_framebufferNoAttachmentsSampleCounts, alloc_int ((int)properties.limits.framebufferNoAttachmentsSampleCounts)); + alloc_field (deviceObject, id_framebufferStencilSampleCounts, alloc_int ((int)properties.limits.framebufferStencilSampleCounts)); + alloc_field (deviceObject, id_maxPushConstantsSize, alloc_int ((int)properties.limits.maxPushConstantsSize)); + alloc_field (deviceObject, id_minStorageBufferOffsetAlignment, + CreateVulkanHandleValue ((uint64_t)properties.limits.minStorageBufferOffsetAlignment)); + alloc_field (deviceObject, id_minUniformBufferOffsetAlignment, + CreateVulkanHandleValue ((uint64_t)properties.limits.minUniformBufferOffsetAlignment)); + alloc_field (deviceObject, id_nonCoherentAtomSize, CreateVulkanHandleValue ((uint64_t)properties.limits.nonCoherentAtomSize)); + alloc_field (deviceObject, id_queueFamilies, queueFamilyArray); + val_array_set_i (devices, i, deviceObject); + + } + + lastVKError.clear (); + return devices; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_array (0); +#endif + + } + + + HL_PRIM hl_varray* HL_NAME(hl_vk_get_physical_devices) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int surfaceHigh, int surfaceLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + + if (!targetWindow || !instance) { + + lastVKError = "Missing Vulkan window or instance"; + return 0; + + } + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr (); + if (!vkGetInstanceProcAddr) { + + lastVKError = "SDL did not expose vkGetInstanceProcAddr"; + return 0; + + } + + PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices = (PFN_vkEnumeratePhysicalDevices)vkGetInstanceProcAddr (instance, + "vkEnumeratePhysicalDevices"); + PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties)vkGetInstanceProcAddr (instance, + "vkGetPhysicalDeviceProperties"); + PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties = + (PFN_vkGetPhysicalDeviceQueueFamilyProperties)vkGetInstanceProcAddr (instance, "vkGetPhysicalDeviceQueueFamilyProperties"); + PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR = + (PFN_vkGetPhysicalDeviceSurfaceSupportKHR)vkGetInstanceProcAddr (instance, "vkGetPhysicalDeviceSurfaceSupportKHR"); + + if (!vkEnumeratePhysicalDevices || !vkGetPhysicalDeviceProperties || !vkGetPhysicalDeviceQueueFamilyProperties) { + + lastVKError = "Missing required Vulkan instance functions"; + return 0; + + } + + VkSurfaceKHR surface = UInt64ToVulkanSurface (CombineVulkanHandle (surfaceHigh, surfaceLow)); + uint32_t deviceCount = 0; + VkResult result = vkEnumeratePhysicalDevices (instance, &deviceCount, 0); + + if (result != VK_SUCCESS) { + + lastVKError = "vkEnumeratePhysicalDevices failed"; + return 0; + + } + + hl_varray* devices = (hl_varray*)hl_alloc_array (&hlt_dynobj, deviceCount); + if (deviceCount == 0) { + + lastVKError.clear (); + return devices; + + } + + std::vector physicalDevices (deviceCount); + result = vkEnumeratePhysicalDevices (instance, &deviceCount, physicalDevices.data ()); + + if (result != VK_SUCCESS) { + + lastVKError = "vkEnumeratePhysicalDevices returned incomplete data"; + return 0; + + } + + vdynamic** deviceData = hl_aptr (devices, vdynamic*); + + const int id_handle = hl_hash_utf8 ("handle"); + const int id_name = hl_hash_utf8 ("name"); + const int id_apiVersion = hl_hash_utf8 ("apiVersion"); + const int id_driverVersion = hl_hash_utf8 ("driverVersion"); + const int id_vendorID = hl_hash_utf8 ("vendorID"); + const int id_deviceID = hl_hash_utf8 ("deviceID"); + const int id_deviceType = hl_hash_utf8 ("deviceType"); + const int id_framebufferColorSampleCounts = hl_hash_utf8 ("framebufferColorSampleCounts"); + const int id_framebufferDepthSampleCounts = hl_hash_utf8 ("framebufferDepthSampleCounts"); + const int id_framebufferNoAttachmentsSampleCounts = hl_hash_utf8 ("framebufferNoAttachmentsSampleCounts"); + const int id_framebufferStencilSampleCounts = hl_hash_utf8 ("framebufferStencilSampleCounts"); + const int id_maxPushConstantsSize = hl_hash_utf8 ("maxPushConstantsSize"); + const int id_minStorageBufferOffsetAlignment = hl_hash_utf8 ("minStorageBufferOffsetAlignment"); + const int id_minUniformBufferOffsetAlignment = hl_hash_utf8 ("minUniformBufferOffsetAlignment"); + const int id_nonCoherentAtomSize = hl_hash_utf8 ("nonCoherentAtomSize"); + const int id_queueFamilies = hl_hash_utf8 ("queueFamilies"); + + const int id_index = hl_hash_utf8 ("index"); + const int id_flags = hl_hash_utf8 ("flags"); + const int id_queueCount = hl_hash_utf8 ("queueCount"); + const int id_timestampValidBits = hl_hash_utf8 ("timestampValidBits"); + const int id_supportsGraphics = hl_hash_utf8 ("supportsGraphics"); + const int id_supportsCompute = hl_hash_utf8 ("supportsCompute"); + const int id_supportsTransfer = hl_hash_utf8 ("supportsTransfer"); + const int id_supportsPresent = hl_hash_utf8 ("supportsPresent"); + + for (uint32_t i = 0; i < deviceCount; ++i) { + + VkPhysicalDeviceProperties properties; + memset (&properties, 0, sizeof (properties)); + vkGetPhysicalDeviceProperties (physicalDevices[i], &properties); + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties (physicalDevices[i], &queueFamilyCount, 0); + std::vector queueFamilies (queueFamilyCount); + if (queueFamilyCount > 0) { + + vkGetPhysicalDeviceQueueFamilyProperties (physicalDevices[i], &queueFamilyCount, queueFamilies.data ()); + + } + + hl_varray* queueFamilyArray = (hl_varray*)hl_alloc_array (&hlt_dynobj, queueFamilyCount); + vdynamic** queueFamilyData = hl_aptr (queueFamilyArray, vdynamic*); + + for (uint32_t q = 0; q < queueFamilyCount; ++q) { + + VkBool32 supportsPresent = VK_FALSE; + if (surface && vkGetPhysicalDeviceSurfaceSupportKHR) { + + vkGetPhysicalDeviceSurfaceSupportKHR (physicalDevices[i], q, surface, &supportsPresent); + + } + + vdynamic* queueFamilyObject = (vdynamic*)hl_alloc_dynobj (); + *queueFamilyData++ = queueFamilyObject; + hl_dyn_seti (queueFamilyObject, id_index, &hlt_i32, q); + hl_dyn_seti (queueFamilyObject, id_flags, &hlt_i32, queueFamilies[q].queueFlags); + hl_dyn_seti (queueFamilyObject, id_queueCount, &hlt_i32, queueFamilies[q].queueCount); + hl_dyn_seti (queueFamilyObject, id_timestampValidBits, &hlt_i32, queueFamilies[q].timestampValidBits); + hl_dyn_seti (queueFamilyObject, id_supportsGraphics, &hlt_bool, (queueFamilies[q].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0); + hl_dyn_seti (queueFamilyObject, id_supportsCompute, &hlt_bool, (queueFamilies[q].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0); + hl_dyn_seti (queueFamilyObject, id_supportsTransfer, &hlt_bool, (queueFamilies[q].queueFlags & VK_QUEUE_TRANSFER_BIT) != 0); + hl_dyn_seti (queueFamilyObject, id_supportsPresent, &hlt_bool, supportsPresent != VK_FALSE); + + } + + vdynamic* deviceObject = (vdynamic*)hl_alloc_dynobj (); + *deviceData++ = deviceObject; + hl_dyn_setp (deviceObject, id_handle, &hlt_dynobj, HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)physicalDevices[i])); + hl_dyn_setp (deviceObject, id_name, &hlt_bytes, hl_copy_bytes ((const vbyte*)properties.deviceName, (int)std::strlen (properties.deviceName) + 1)); + hl_dyn_seti (deviceObject, id_apiVersion, &hlt_i32, properties.apiVersion); + hl_dyn_seti (deviceObject, id_driverVersion, &hlt_i32, properties.driverVersion); + hl_dyn_seti (deviceObject, id_vendorID, &hlt_i32, properties.vendorID); + hl_dyn_seti (deviceObject, id_deviceID, &hlt_i32, properties.deviceID); + hl_dyn_seti (deviceObject, id_deviceType, &hlt_i32, properties.deviceType); + hl_dyn_seti (deviceObject, id_framebufferColorSampleCounts, &hlt_i32, properties.limits.framebufferColorSampleCounts); + hl_dyn_seti (deviceObject, id_framebufferDepthSampleCounts, &hlt_i32, properties.limits.framebufferDepthSampleCounts); + hl_dyn_seti (deviceObject, id_framebufferNoAttachmentsSampleCounts, &hlt_i32, properties.limits.framebufferNoAttachmentsSampleCounts); + hl_dyn_seti (deviceObject, id_framebufferStencilSampleCounts, &hlt_i32, properties.limits.framebufferStencilSampleCounts); + hl_dyn_seti (deviceObject, id_maxPushConstantsSize, &hlt_i32, properties.limits.maxPushConstantsSize); + hl_dyn_setp (deviceObject, id_minStorageBufferOffsetAlignment, &hlt_dynobj, + HLCreateVulkanHandleValue ((uint64_t)properties.limits.minStorageBufferOffsetAlignment)); + hl_dyn_setp (deviceObject, id_minUniformBufferOffsetAlignment, &hlt_dynobj, + HLCreateVulkanHandleValue ((uint64_t)properties.limits.minUniformBufferOffsetAlignment)); + hl_dyn_setp (deviceObject, id_nonCoherentAtomSize, &hlt_dynobj, + HLCreateVulkanHandleValue ((uint64_t)properties.limits.nonCoherentAtomSize)); + hl_dyn_setp (deviceObject, id_queueFamilies, &hlt_array, queueFamilyArray); + + } + + lastVKError.clear (); + return devices; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return (hl_varray*)hl_alloc_array (&hlt_dynobj, 0); +#endif + + } + + + value lime_vk_create_device (value window, int instanceHigh, int instanceLow, int physicalDeviceHigh, int physicalDeviceLow, int queueFamilyIndex, + value extensions) { + +#ifdef LIME_VULKAN + LogVulkanNativeBootstrap ("lime_vk_create_device: begin"); + if (val_is_null (window)) { + + lastVKError = "Missing Lime window for Vulkan device creation"; + LogVulkanNativeBootstrap ("lime_vk_create_device: null window"); + return alloc_null (); + + } + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkPhysicalDevice physicalDevice = (VkPhysicalDevice)(uintptr_t)CombineVulkanHandle (physicalDeviceHigh, physicalDeviceLow); + LogVulkanNativeBootstrap ("lime_vk_create_device: decoded handles"); + std::vector deviceExtensions = GetVulkanDeviceExtensions (extensions); + LogVulkanNativeBootstrap ("lime_vk_create_device: decoded extensions"); + VkDevice device = VK_NULL_HANDLE; + VkQueue queue = VK_NULL_HANDLE; + + if (!CreateManagedVulkanDevice (targetWindow, instance, physicalDevice, queueFamilyIndex, deviceExtensions, &device, &queue)) { + + LogVulkanNativeBootstrap ("lime_vk_create_device: CreateManagedVulkanDevice failed"); + return alloc_null (); + + } + + LogVulkanNativeBootstrap ("lime_vk_create_device: create result object"); + value result = alloc_empty_object (); + alloc_field (result, val_id ("handle"), CreateVulkanHandleValue ((uint64_t)(uintptr_t)device)); + alloc_field (result, val_id ("queue"), CreateVulkanHandleValue ((uint64_t)(uintptr_t)queue)); + alloc_field (result, val_id ("queueFamilyIndex"), alloc_int (queueFamilyIndex)); + LogVulkanNativeBootstrap ("lime_vk_create_device: success"); + return result; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_create_device) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int physicalDeviceHigh, + int physicalDeviceLow, int queueFamilyIndex, hl_varray* extensions) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkPhysicalDevice physicalDevice = (VkPhysicalDevice)(uintptr_t)CombineVulkanHandle (physicalDeviceHigh, physicalDeviceLow); + std::vector deviceExtensions = GetHLVulkanDeviceExtensions (extensions); + VkDevice device = VK_NULL_HANDLE; + VkQueue queue = VK_NULL_HANDLE; + + if (!CreateManagedVulkanDevice (targetWindow, instance, physicalDevice, queueFamilyIndex, deviceExtensions, &device, &queue)) { + + return 0; + + } + + const int id_handle = hl_hash_utf8 ("handle"); + const int id_queue = hl_hash_utf8 ("queue"); + const int id_queueFamilyIndex = hl_hash_utf8 ("queueFamilyIndex"); + vdynamic* result = (vdynamic*)hl_alloc_dynobj (); + hl_dyn_setp (result, id_handle, &hlt_dynobj, HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)device)); + hl_dyn_setp (result, id_queue, &hlt_dynobj, HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)queue)); + hl_dyn_seti (result, id_queueFamilyIndex, &hlt_i32, queueFamilyIndex); + return result; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + void lime_vk_destroy_device (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow) { + +#ifdef LIME_VULKAN + if (deviceHigh == 0 && deviceLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = targetWindow ? (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr () : 0; + if (!vkGetInstanceProcAddr) return; + + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr (instance, "vkGetDeviceProcAddr"); + if (!vkGetDeviceProcAddr) return; + + PFN_vkDestroyDevice vkDestroyDevice = (PFN_vkDestroyDevice)vkGetDeviceProcAddr (device, "vkDestroyDevice"); + if (vkDestroyDevice) { + + ClearManagedVulkanDeviceProcCache (device); + vkDestroyDevice (device, 0); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_destroy_device) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow) { + +#ifdef LIME_VULKAN + if (deviceHigh == 0 && deviceLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = targetWindow ? (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr () : 0; + if (!vkGetInstanceProcAddr) return; + + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr (instance, "vkGetDeviceProcAddr"); + if (!vkGetDeviceProcAddr) return; + + PFN_vkDestroyDevice vkDestroyDevice = (PFN_vkDestroyDevice)vkGetDeviceProcAddr (device, "vkDestroyDevice"); + if (vkDestroyDevice) { + + ClearManagedVulkanDeviceProcCache (device); + vkDestroyDevice (device, 0); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + bool lime_vk_device_wait_idle (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = targetWindow ? (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr () : 0; + if (!vkGetInstanceProcAddr || !instance || !device) { + + lastVKError = "Missing Vulkan window, instance, or device"; + return false; + + } + + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr (instance, "vkGetDeviceProcAddr"); + if (!vkGetDeviceProcAddr) { + + lastVKError = "Missing Vulkan device proc address function"; + return false; + + } + + PFN_vkDeviceWaitIdle vkDeviceWaitIdle = (PFN_vkDeviceWaitIdle)vkGetDeviceProcAddr (device, "vkDeviceWaitIdle"); + if (!vkDeviceWaitIdle) { + + lastVKError = "Missing Vulkan device wait idle function"; + return false; + + } + + VkResult result = vkDeviceWaitIdle (device); + if (result != VK_SUCCESS) { + + lastVKError = "vkDeviceWaitIdle failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_device_wait_idle) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = targetWindow ? (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr () : 0; + if (!vkGetInstanceProcAddr || !instance || !device) { + + lastVKError = "Missing Vulkan window, instance, or device"; + return false; + + } + + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr (instance, "vkGetDeviceProcAddr"); + if (!vkGetDeviceProcAddr) { + + lastVKError = "Missing Vulkan device proc address function"; + return false; + + } + + PFN_vkDeviceWaitIdle vkDeviceWaitIdle = (PFN_vkDeviceWaitIdle)vkGetDeviceProcAddr (device, "vkDeviceWaitIdle"); + if (!vkDeviceWaitIdle) { + + lastVKError = "Missing Vulkan device wait idle function"; + return false; + + } + + VkResult result = vkDeviceWaitIdle (device); + if (result != VK_SUCCESS) { + + lastVKError = "vkDeviceWaitIdle failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + bool lime_vk_queue_wait_idle (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int queueHigh, int queueLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkQueue queue = (VkQueue)(uintptr_t)CombineVulkanHandle (queueHigh, queueLow); + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = targetWindow ? (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr () : 0; + if (!vkGetInstanceProcAddr || !instance || !device || !queue) { + + lastVKError = "Missing Vulkan window, instance, device, or queue"; + return false; + + } + + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr (instance, "vkGetDeviceProcAddr"); + if (!vkGetDeviceProcAddr) { + + lastVKError = "Missing Vulkan device proc address function"; + return false; + + } + + PFN_vkQueueWaitIdle vkQueueWaitIdle = (PFN_vkQueueWaitIdle)vkGetDeviceProcAddr (device, "vkQueueWaitIdle"); + if (!vkQueueWaitIdle) { + + lastVKError = "Missing Vulkan queue wait idle function"; + return false; + + } + + VkResult result = vkQueueWaitIdle (queue); + if (result != VK_SUCCESS) { + + lastVKError = "vkQueueWaitIdle failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_queue_wait_idle) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int queueHigh, + int queueLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkQueue queue = (VkQueue)(uintptr_t)CombineVulkanHandle (queueHigh, queueLow); + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = targetWindow ? (PFN_vkGetInstanceProcAddr)targetWindow->GetVulkanInstanceProcAddr () : 0; + if (!vkGetInstanceProcAddr || !instance || !device || !queue) { + + lastVKError = "Missing Vulkan window, instance, device, or queue"; + return false; + + } + + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr (instance, "vkGetDeviceProcAddr"); + if (!vkGetDeviceProcAddr) { + + lastVKError = "Missing Vulkan device proc address function"; + return false; + + } + + PFN_vkQueueWaitIdle vkQueueWaitIdle = (PFN_vkQueueWaitIdle)vkGetDeviceProcAddr (device, "vkQueueWaitIdle"); + if (!vkQueueWaitIdle) { + + lastVKError = "Missing Vulkan queue wait idle function"; + return false; + + } + + VkResult result = vkQueueWaitIdle (queue); + if (result != VK_SUCCESS) { + + lastVKError = "vkQueueWaitIdle failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + value lime_vk_create_command_pool (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int queueFamilyIndex, int flags) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateCommandPool vkCreateCommandPool = (PFN_vkCreateCommandPool)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCreateCommandPool"); + if (!vkCreateCommandPool) return alloc_null (); + + VkCommandPoolCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + createInfo.flags = flags; + createInfo.queueFamilyIndex = queueFamilyIndex; + + VkCommandPool commandPool = VK_NULL_HANDLE; + VkResult result = vkCreateCommandPool (device, &createInfo, 0, &commandPool); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateCommandPool failed"; + return alloc_null (); + + } + + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)commandPool); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_create_command_pool) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int queueFamilyIndex, int flags) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateCommandPool vkCreateCommandPool = (PFN_vkCreateCommandPool)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCreateCommandPool"); + if (!vkCreateCommandPool) return 0; + + VkCommandPoolCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + createInfo.flags = flags; + createInfo.queueFamilyIndex = queueFamilyIndex; + + VkCommandPool commandPool = VK_NULL_HANDLE; + VkResult result = vkCreateCommandPool (device, &createInfo, 0, &commandPool); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateCommandPool failed"; + return 0; + + } + + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)commandPool); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + void lime_vk_destroy_command_pool (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandPoolHigh, + int commandPoolLow) { + +#ifdef LIME_VULKAN + if (commandPoolHigh == 0 && commandPoolLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandPool commandPool = (VkCommandPool)(uintptr_t)CombineVulkanHandle (commandPoolHigh, commandPoolLow); + PFN_vkDestroyCommandPool vkDestroyCommandPool = (PFN_vkDestroyCommandPool)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroyCommandPool"); + if (vkDestroyCommandPool) vkDestroyCommandPool (device, commandPool, 0); +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_destroy_command_pool) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandPoolHigh, int commandPoolLow) { + +#ifdef LIME_VULKAN + if (commandPoolHigh == 0 && commandPoolLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandPool commandPool = (VkCommandPool)(uintptr_t)CombineVulkanHandle (commandPoolHigh, commandPoolLow); + PFN_vkDestroyCommandPool vkDestroyCommandPool = (PFN_vkDestroyCommandPool)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroyCommandPool"); + if (vkDestroyCommandPool) vkDestroyCommandPool (device, commandPool, 0); +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + bool lime_vk_reset_command_pool (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandPoolHigh, + int commandPoolLow, int flags) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandPool commandPool = (VkCommandPool)(uintptr_t)CombineVulkanHandle (commandPoolHigh, commandPoolLow); + PFN_vkResetCommandPool vkResetCommandPool = (PFN_vkResetCommandPool)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkResetCommandPool"); + if (!vkResetCommandPool) return false; + + VkResult result = vkResetCommandPool (device, commandPool, flags); + if (result != VK_SUCCESS) { + + lastVKError = "vkResetCommandPool failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_reset_command_pool) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandPoolHigh, int commandPoolLow, int flags) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandPool commandPool = (VkCommandPool)(uintptr_t)CombineVulkanHandle (commandPoolHigh, commandPoolLow); + PFN_vkResetCommandPool vkResetCommandPool = (PFN_vkResetCommandPool)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkResetCommandPool"); + if (!vkResetCommandPool) return false; + + VkResult result = vkResetCommandPool (device, commandPool, flags); + if (result != VK_SUCCESS) { + + lastVKError = "vkResetCommandPool failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + value lime_vk_allocate_command_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandPoolHigh, + int commandPoolLow, int level) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandPool commandPool = (VkCommandPool)(uintptr_t)CombineVulkanHandle (commandPoolHigh, commandPoolLow); + PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers = (PFN_vkAllocateCommandBuffers)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkAllocateCommandBuffers"); + if (!vkAllocateCommandBuffers) return alloc_null (); + + VkCommandBufferAllocateInfo allocateInfo; + memset (&allocateInfo, 0, sizeof (allocateInfo)); + allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocateInfo.commandPool = commandPool; + allocateInfo.level = (VkCommandBufferLevel)level; + allocateInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + VkResult result = vkAllocateCommandBuffers (device, &allocateInfo, &commandBuffer); + if (result != VK_SUCCESS) { + + lastVKError = "vkAllocateCommandBuffers failed"; + return alloc_null (); + + } + + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)commandBuffer); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_allocate_command_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandPoolHigh, int commandPoolLow, int level) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandPool commandPool = (VkCommandPool)(uintptr_t)CombineVulkanHandle (commandPoolHigh, commandPoolLow); + PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers = (PFN_vkAllocateCommandBuffers)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkAllocateCommandBuffers"); + if (!vkAllocateCommandBuffers) return 0; + + VkCommandBufferAllocateInfo allocateInfo; + memset (&allocateInfo, 0, sizeof (allocateInfo)); + allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocateInfo.commandPool = commandPool; + allocateInfo.level = (VkCommandBufferLevel)level; + allocateInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + VkResult result = vkAllocateCommandBuffers (device, &allocateInfo, &commandBuffer); + if (result != VK_SUCCESS) { + + lastVKError = "vkAllocateCommandBuffers failed"; + return 0; + + } + + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)commandBuffer); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + void lime_vk_free_command_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandPoolHigh, + int commandPoolLow, int commandBufferHigh, int commandBufferLow) { + +#ifdef LIME_VULKAN + if (commandBufferHigh == 0 && commandBufferLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandPool commandPool = (VkCommandPool)(uintptr_t)CombineVulkanHandle (commandPoolHigh, commandPoolLow); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + PFN_vkFreeCommandBuffers vkFreeCommandBuffers = (PFN_vkFreeCommandBuffers)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkFreeCommandBuffers"); + if (vkFreeCommandBuffers) vkFreeCommandBuffers (device, commandPool, 1, &commandBuffer); +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_free_command_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandPoolHigh, int commandPoolLow, int commandBufferHigh, int commandBufferLow) { + +#ifdef LIME_VULKAN + if (commandBufferHigh == 0 && commandBufferLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandPool commandPool = (VkCommandPool)(uintptr_t)CombineVulkanHandle (commandPoolHigh, commandPoolLow); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + PFN_vkFreeCommandBuffers vkFreeCommandBuffers = (PFN_vkFreeCommandBuffers)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkFreeCommandBuffers"); + if (vkFreeCommandBuffers) vkFreeCommandBuffers (device, commandPool, 1, &commandBuffer); +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + bool lime_vk_begin_command_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int flags) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + PFN_vkBeginCommandBuffer vkBeginCommandBuffer = (PFN_vkBeginCommandBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkBeginCommandBuffer"); + if (!vkBeginCommandBuffer) return false; + + VkCommandBufferBeginInfo beginInfo; + memset (&beginInfo, 0, sizeof (beginInfo)); + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = flags; + + VkResult result = vkBeginCommandBuffer (commandBuffer, &beginInfo); + if (result != VK_SUCCESS) { + + lastVKError = "vkBeginCommandBuffer failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_begin_command_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int flags) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + PFN_vkBeginCommandBuffer vkBeginCommandBuffer = (PFN_vkBeginCommandBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkBeginCommandBuffer"); + if (!vkBeginCommandBuffer) return false; + + VkCommandBufferBeginInfo beginInfo; + memset (&beginInfo, 0, sizeof (beginInfo)); + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = flags; + + VkResult result = vkBeginCommandBuffer (commandBuffer, &beginInfo); + if (result != VK_SUCCESS) { + + lastVKError = "vkBeginCommandBuffer failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + bool lime_vk_end_command_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + PFN_vkEndCommandBuffer vkEndCommandBuffer = (PFN_vkEndCommandBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkEndCommandBuffer"); + if (!vkEndCommandBuffer) return false; + + VkResult result = vkEndCommandBuffer (commandBuffer); + if (result != VK_SUCCESS) { + + lastVKError = "vkEndCommandBuffer failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_end_command_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + PFN_vkEndCommandBuffer vkEndCommandBuffer = (PFN_vkEndCommandBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkEndCommandBuffer"); + if (!vkEndCommandBuffer) return false; + + VkResult result = vkEndCommandBuffer (commandBuffer); + if (result != VK_SUCCESS) { + + lastVKError = "vkEndCommandBuffer failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + bool lime_vk_reset_command_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int flags) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + PFN_vkResetCommandBuffer vkResetCommandBuffer = (PFN_vkResetCommandBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkResetCommandBuffer"); + if (!vkResetCommandBuffer) return false; + + VkResult result = vkResetCommandBuffer (commandBuffer, flags); + if (result != VK_SUCCESS) { + + lastVKError = "vkResetCommandBuffer failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_reset_command_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int flags) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + PFN_vkResetCommandBuffer vkResetCommandBuffer = (PFN_vkResetCommandBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkResetCommandBuffer"); + if (!vkResetCommandBuffer) return false; + + VkResult result = vkResetCommandBuffer (commandBuffer, flags); + if (result != VK_SUCCESS) { + + lastVKError = "vkResetCommandBuffer failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + value lime_vk_create_semaphore (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateSemaphore vkCreateSemaphore = (PFN_vkCreateSemaphore)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCreateSemaphore"); + if (!vkCreateSemaphore) return alloc_null (); + + VkSemaphoreCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkSemaphore semaphore = VK_NULL_HANDLE; + VkResult result = vkCreateSemaphore (device, &createInfo, 0, &semaphore); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateSemaphore failed"; + return alloc_null (); + + } + + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)semaphore); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_create_semaphore) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateSemaphore vkCreateSemaphore = (PFN_vkCreateSemaphore)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCreateSemaphore"); + if (!vkCreateSemaphore) return 0; + + VkSemaphoreCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkSemaphore semaphore = VK_NULL_HANDLE; + VkResult result = vkCreateSemaphore (device, &createInfo, 0, &semaphore); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateSemaphore failed"; + return 0; + + } + + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)semaphore); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + void lime_vk_destroy_semaphore (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int semaphoreHigh, + int semaphoreLow) { + +#ifdef LIME_VULKAN + if (semaphoreHigh == 0 && semaphoreLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkSemaphore semaphore = (VkSemaphore)(uintptr_t)CombineVulkanHandle (semaphoreHigh, semaphoreLow); + PFN_vkDestroySemaphore vkDestroySemaphore = (PFN_vkDestroySemaphore)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroySemaphore"); + if (vkDestroySemaphore) vkDestroySemaphore (device, semaphore, 0); +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_destroy_semaphore) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int semaphoreHigh, int semaphoreLow) { + +#ifdef LIME_VULKAN + if (semaphoreHigh == 0 && semaphoreLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkSemaphore semaphore = (VkSemaphore)(uintptr_t)CombineVulkanHandle (semaphoreHigh, semaphoreLow); + PFN_vkDestroySemaphore vkDestroySemaphore = (PFN_vkDestroySemaphore)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroySemaphore"); + if (vkDestroySemaphore) vkDestroySemaphore (device, semaphore, 0); +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + value lime_vk_create_fence (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int flags) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateFence vkCreateFence = (PFN_vkCreateFence)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateFence"); + if (!vkCreateFence) return alloc_null (); + + VkFenceCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + createInfo.flags = flags; + + VkFence fence = VK_NULL_HANDLE; + VkResult result = vkCreateFence (device, &createInfo, 0, &fence); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateFence failed"; + return alloc_null (); + + } + + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)fence); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_create_fence) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int flags) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateFence vkCreateFence = (PFN_vkCreateFence)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateFence"); + if (!vkCreateFence) return 0; + + VkFenceCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + createInfo.flags = flags; + + VkFence fence = VK_NULL_HANDLE; + VkResult result = vkCreateFence (device, &createInfo, 0, &fence); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateFence failed"; + return 0; + + } + + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)fence); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + void lime_vk_destroy_fence (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int fenceHigh, int fenceLow) { + +#ifdef LIME_VULKAN + if (fenceHigh == 0 && fenceLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkFence fence = (VkFence)(uintptr_t)CombineVulkanHandle (fenceHigh, fenceLow); + PFN_vkDestroyFence vkDestroyFence = (PFN_vkDestroyFence)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyFence"); + if (vkDestroyFence) vkDestroyFence (device, fence, 0); +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_destroy_fence) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int fenceHigh, + int fenceLow) { + +#ifdef LIME_VULKAN + if (fenceHigh == 0 && fenceLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkFence fence = (VkFence)(uintptr_t)CombineVulkanHandle (fenceHigh, fenceLow); + PFN_vkDestroyFence vkDestroyFence = (PFN_vkDestroyFence)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyFence"); + if (vkDestroyFence) vkDestroyFence (device, fence, 0); +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + bool lime_vk_wait_for_fence (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int fenceHigh, int fenceLow, + int timeoutHigh, int timeoutLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkFence fence = (VkFence)(uintptr_t)CombineVulkanHandle (fenceHigh, fenceLow); + uint64_t timeout = CombineVulkanHandle (timeoutHigh, timeoutLow); + PFN_vkWaitForFences vkWaitForFences = (PFN_vkWaitForFences)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkWaitForFences"); + if (!vkWaitForFences) return false; + + VkResult result = vkWaitForFences (device, 1, &fence, VK_TRUE, timeout); + if (result != VK_SUCCESS) { + + lastVKError = "vkWaitForFences failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_wait_for_fence) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int fenceHigh, + int fenceLow, int timeoutHigh, int timeoutLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkFence fence = (VkFence)(uintptr_t)CombineVulkanHandle (fenceHigh, fenceLow); + uint64_t timeout = CombineVulkanHandle (timeoutHigh, timeoutLow); + PFN_vkWaitForFences vkWaitForFences = (PFN_vkWaitForFences)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkWaitForFences"); + if (!vkWaitForFences) return false; + + VkResult result = vkWaitForFences (device, 1, &fence, VK_TRUE, timeout); + if (result != VK_SUCCESS) { + + lastVKError = "vkWaitForFences failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + bool lime_vk_reset_fence (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int fenceHigh, int fenceLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkFence fence = (VkFence)(uintptr_t)CombineVulkanHandle (fenceHigh, fenceLow); + PFN_vkResetFences vkResetFences = (PFN_vkResetFences)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkResetFences"); + if (!vkResetFences) return false; + + VkResult result = vkResetFences (device, 1, &fence); + if (result != VK_SUCCESS) { + + lastVKError = "vkResetFences failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_reset_fence) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int fenceHigh, + int fenceLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkFence fence = (VkFence)(uintptr_t)CombineVulkanHandle (fenceHigh, fenceLow); + PFN_vkResetFences vkResetFences = (PFN_vkResetFences)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkResetFences"); + if (!vkResetFences) return false; + + VkResult result = vkResetFences (device, 1, &fence); + if (result != VK_SUCCESS) { + + lastVKError = "vkResetFences failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + bool lime_vk_queue_submit (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int queueHigh, int queueLow, + int commandBufferHigh, int commandBufferLow, int fenceHigh, int fenceLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkQueue queue = (VkQueue)(uintptr_t)CombineVulkanHandle (queueHigh, queueLow); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkFence fence = (VkFence)(uintptr_t)CombineVulkanHandle (fenceHigh, fenceLow); + PFN_vkQueueSubmit vkQueueSubmit = (PFN_vkQueueSubmit)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkQueueSubmit"); + if (!vkQueueSubmit) return false; + + VkSubmitInfo submitInfo; + memset (&submitInfo, 0, sizeof (submitInfo)); + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + VkResult result = vkQueueSubmit (queue, 1, &submitInfo, fence); + if (result != VK_SUCCESS) { + + char error[64]; + ::snprintf (error, sizeof (error), "vkQueueSubmit failed: %d", (int)result); + lastVKError = error; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_queue_submit) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int queueHigh, + int queueLow, int commandBufferHigh, int commandBufferLow, int fenceHigh, int fenceLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkQueue queue = (VkQueue)(uintptr_t)CombineVulkanHandle (queueHigh, queueLow); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkFence fence = (VkFence)(uintptr_t)CombineVulkanHandle (fenceHigh, fenceLow); + PFN_vkQueueSubmit vkQueueSubmit = (PFN_vkQueueSubmit)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkQueueSubmit"); + if (!vkQueueSubmit) return false; + + VkSubmitInfo submitInfo; + memset (&submitInfo, 0, sizeof (submitInfo)); + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + VkResult result = vkQueueSubmit (queue, 1, &submitInfo, fence); + if (result != VK_SUCCESS) { + + char error[64]; + ::snprintf (error, sizeof (error), "vkQueueSubmit failed: %d", (int)result); + lastVKError = error; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + value lime_vk_allocate_memory (value window, int instanceHigh, int instanceLow, int physicalDeviceHigh, int physicalDeviceLow, int deviceHigh, + int deviceLow, int sizeHigh, int sizeLow, int memoryTypeBits, int properties) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkPhysicalDevice physicalDevice = (VkPhysicalDevice)(uintptr_t)CombineVulkanHandle (physicalDeviceHigh, physicalDeviceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + uint64_t size = CombineVulkanHandle (sizeHigh, sizeLow); + uint32_t memoryTypeIndex = 0; + if (!FindManagedVulkanMemoryType (targetWindow, instance, physicalDevice, (uint32_t)memoryTypeBits, (VkMemoryPropertyFlags)properties, + &memoryTypeIndex)) { + + return alloc_null (); + + } + + PFN_vkAllocateMemory vkAllocateMemory = (PFN_vkAllocateMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkAllocateMemory"); + if (!vkAllocateMemory) return alloc_null (); + + VkMemoryAllocateInfo allocateInfo; + memset (&allocateInfo, 0, sizeof (allocateInfo)); + allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocateInfo.allocationSize = (VkDeviceSize)size; + allocateInfo.memoryTypeIndex = memoryTypeIndex; + + VkDeviceMemory memory = VK_NULL_HANDLE; + VkResult result = vkAllocateMemory (device, &allocateInfo, 0, &memory); + if (result != VK_SUCCESS) { + + lastVKError = "vkAllocateMemory failed"; + return alloc_null (); + + } + + lastVKError.clear (); + return CreateVulkanMemoryValue (memory, memoryTypeIndex); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_allocate_memory) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int physicalDeviceHigh, + int physicalDeviceLow, int deviceHigh, int deviceLow, int sizeHigh, int sizeLow, int memoryTypeBits, int properties) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkPhysicalDevice physicalDevice = (VkPhysicalDevice)(uintptr_t)CombineVulkanHandle (physicalDeviceHigh, physicalDeviceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + uint64_t size = CombineVulkanHandle (sizeHigh, sizeLow); + uint32_t memoryTypeIndex = 0; + if (!FindManagedVulkanMemoryType (targetWindow, instance, physicalDevice, (uint32_t)memoryTypeBits, (VkMemoryPropertyFlags)properties, + &memoryTypeIndex)) { + + return 0; + + } + + PFN_vkAllocateMemory vkAllocateMemory = (PFN_vkAllocateMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkAllocateMemory"); + if (!vkAllocateMemory) return 0; + + VkMemoryAllocateInfo allocateInfo; + memset (&allocateInfo, 0, sizeof (allocateInfo)); + allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocateInfo.allocationSize = (VkDeviceSize)size; + allocateInfo.memoryTypeIndex = memoryTypeIndex; + + VkDeviceMemory memory = VK_NULL_HANDLE; + VkResult result = vkAllocateMemory (device, &allocateInfo, 0, &memory); + if (result != VK_SUCCESS) { + + lastVKError = "vkAllocateMemory failed"; + return 0; + + } + + lastVKError.clear (); + return HLCreateVulkanMemoryValue (memory, memoryTypeIndex); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + void lime_vk_free_memory (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int memoryHigh, int memoryLow) { + +#ifdef LIME_VULKAN + if (memoryHigh == 0 && memoryLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + PFN_vkFreeMemory vkFreeMemory = (PFN_vkFreeMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkFreeMemory"); + if (vkFreeMemory) { + + uint64_t mapKey = GetManagedVulkanMappedMemoryKey (memory); + std::unordered_map::iterator mapped = mappedVulkanMemory.find (mapKey); + if (mapped != mappedVulkanMemory.end ()) { + + PFN_vkUnmapMemory vkUnmapMemory = (PFN_vkUnmapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUnmapMemory"); + if (vkUnmapMemory) vkUnmapMemory (device, memory); + mappedVulkanMemory.erase (mapped); + + } + + vkFreeMemory (device, memory, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_free_memory) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int memoryHigh, int memoryLow) { + +#ifdef LIME_VULKAN + if (memoryHigh == 0 && memoryLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + PFN_vkFreeMemory vkFreeMemory = (PFN_vkFreeMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkFreeMemory"); + if (vkFreeMemory) { + + uint64_t mapKey = GetManagedVulkanMappedMemoryKey (memory); + std::unordered_map::iterator mapped = mappedVulkanMemory.find (mapKey); + if (mapped != mappedVulkanMemory.end ()) { + + PFN_vkUnmapMemory vkUnmapMemory = (PFN_vkUnmapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUnmapMemory"); + if (vkUnmapMemory) vkUnmapMemory (device, memory); + mappedVulkanMemory.erase (mapped); + + } + + vkFreeMemory (device, memory, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + bool lime_vk_upload_memory (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int memoryHigh, int memoryLow, + int offsetHigh, int offsetLow, value bytes, int byteOffset, int byteLength) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + uint64_t offset = CombineVulkanHandle (offsetHigh, offsetLow); + Bytes data (bytes); + if (!memory || !data.b || byteOffset < 0 || byteLength < 0 || byteOffset + byteLength > data.length) { + + lastVKError = "Invalid Vulkan memory upload range"; + return false; + + } + + PFN_vkMapMemory vkMapMemory = (PFN_vkMapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkMapMemory"); + PFN_vkUnmapMemory vkUnmapMemory = (PFN_vkUnmapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUnmapMemory"); + if (!vkMapMemory || !vkUnmapMemory) return false; + + void* mappedData = 0; + VkResult result = vkMapMemory (device, memory, (VkDeviceSize)offset, (VkDeviceSize)byteLength, 0, &mappedData); + if (result != VK_SUCCESS || !mappedData) { + + lastVKError = "vkMapMemory failed"; + return false; + + } + + memcpy (mappedData, data.b + byteOffset, byteLength); + vkUnmapMemory (device, memory); + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_upload_memory) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int memoryHigh, int memoryLow, int offsetHigh, int offsetLow, Bytes* bytes, int byteOffset, int byteLength) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + uint64_t offset = CombineVulkanHandle (offsetHigh, offsetLow); + if (!memory || !bytes || !bytes->b || byteOffset < 0 || byteLength < 0 || byteOffset + byteLength > bytes->length) { + + lastVKError = "Invalid Vulkan memory upload range"; + return false; + + } + + PFN_vkMapMemory vkMapMemory = (PFN_vkMapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkMapMemory"); + PFN_vkUnmapMemory vkUnmapMemory = (PFN_vkUnmapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUnmapMemory"); + if (!vkMapMemory || !vkUnmapMemory) return false; + + void* mappedData = 0; + VkResult result = vkMapMemory (device, memory, (VkDeviceSize)offset, (VkDeviceSize)byteLength, 0, &mappedData); + if (result != VK_SUCCESS || !mappedData) { + + lastVKError = "vkMapMemory failed"; + return false; + + } + + memcpy (mappedData, bytes->b + byteOffset, byteLength); + vkUnmapMemory (device, memory); + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + bool lime_vk_download_memory (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int memoryHigh, int memoryLow, + int offsetHigh, int offsetLow, value bytes, int byteOffset, int byteLength) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + uint64_t offset = CombineVulkanHandle (offsetHigh, offsetLow); + Bytes data (bytes); + if (!memory || !data.b || byteOffset < 0 || byteLength < 0 || byteOffset + byteLength > data.length) { + + lastVKError = "Invalid Vulkan memory download range"; + return false; + + } + + PFN_vkMapMemory vkMapMemory = (PFN_vkMapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkMapMemory"); + PFN_vkUnmapMemory vkUnmapMemory = (PFN_vkUnmapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUnmapMemory"); + if (!vkMapMemory || !vkUnmapMemory) return false; + + void* mappedData = 0; + VkResult result = vkMapMemory (device, memory, (VkDeviceSize)offset, (VkDeviceSize)byteLength, 0, &mappedData); + if (result != VK_SUCCESS || !mappedData) { + + lastVKError = "vkMapMemory failed"; + return false; + + } + + memcpy (data.b + byteOffset, mappedData, byteLength); + vkUnmapMemory (device, memory); + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_download_memory) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int memoryHigh, int memoryLow, int offsetHigh, int offsetLow, Bytes* bytes, int byteOffset, int byteLength) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + uint64_t offset = CombineVulkanHandle (offsetHigh, offsetLow); + if (!memory || !bytes || !bytes->b || byteOffset < 0 || byteLength < 0 || byteOffset + byteLength > bytes->length) { + + lastVKError = "Invalid Vulkan memory download range"; + return false; + + } + + PFN_vkMapMemory vkMapMemory = (PFN_vkMapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkMapMemory"); + PFN_vkUnmapMemory vkUnmapMemory = (PFN_vkUnmapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUnmapMemory"); + if (!vkMapMemory || !vkUnmapMemory) return false; + + void* mappedData = 0; + VkResult result = vkMapMemory (device, memory, (VkDeviceSize)offset, (VkDeviceSize)byteLength, 0, &mappedData); + if (result != VK_SUCCESS || !mappedData) { + + lastVKError = "vkMapMemory failed"; + return false; + + } + + memcpy (bytes->b + byteOffset, mappedData, byteLength); + vkUnmapMemory (device, memory); + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + bool lime_vk_map_memory (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int memoryHigh, int memoryLow, + int offsetHigh, int offsetLow, int sizeHigh, int sizeLow, int flags) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + VkDeviceSize offset = (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow); + VkDeviceSize size = NormalizeVulkanRangeSize (CombineVulkanHandle (sizeHigh, sizeLow)); + if (!memory) return false; + + uint64_t mapKey = GetManagedVulkanMappedMemoryKey (memory); + if (mappedVulkanMemory.find (mapKey) != mappedVulkanMemory.end ()) { + + lastVKError.clear (); + return true; + + } + + PFN_vkMapMemory vkMapMemory = (PFN_vkMapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkMapMemory"); + if (!vkMapMemory) return false; + + void* mappedData = 0; + VkResult result = vkMapMemory (device, memory, offset, size, (VkMemoryMapFlags)flags, &mappedData); + if (result != VK_SUCCESS || !mappedData) { + + lastVKError = "vkMapMemory failed"; + return false; + + } + + ManagedVulkanMappedMemory mapped; + mapped.device = device; + mapped.memory = memory; + mapped.offset = offset; + mapped.size = size; + mapped.data = mappedData; + mappedVulkanMemory[mapKey] = mapped; + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_map_memory) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int memoryHigh, int memoryLow, int offsetHigh, int offsetLow, int sizeHigh, int sizeLow, int flags) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + VkDeviceSize offset = (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow); + VkDeviceSize size = NormalizeVulkanRangeSize (CombineVulkanHandle (sizeHigh, sizeLow)); + if (!memory) return false; + + uint64_t mapKey = GetManagedVulkanMappedMemoryKey (memory); + if (mappedVulkanMemory.find (mapKey) != mappedVulkanMemory.end ()) { + + lastVKError.clear (); + return true; + + } + + PFN_vkMapMemory vkMapMemory = (PFN_vkMapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkMapMemory"); + if (!vkMapMemory) return false; + + void* mappedData = 0; + VkResult result = vkMapMemory (device, memory, offset, size, (VkMemoryMapFlags)flags, &mappedData); + if (result != VK_SUCCESS || !mappedData) { + + lastVKError = "vkMapMemory failed"; + return false; + + } + + ManagedVulkanMappedMemory mapped; + mapped.device = device; + mapped.memory = memory; + mapped.offset = offset; + mapped.size = size; + mapped.data = mappedData; + mappedVulkanMemory[mapKey] = mapped; + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + void lime_vk_unmap_memory (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int memoryHigh, int memoryLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + uint64_t mapKey = GetManagedVulkanMappedMemoryKey (memory); + std::unordered_map::iterator mapped = mappedVulkanMemory.find (mapKey); + if (mapped == mappedVulkanMemory.end ()) return; + + PFN_vkUnmapMemory vkUnmapMemory = (PFN_vkUnmapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUnmapMemory"); + if (!vkUnmapMemory) return; + + vkUnmapMemory (device, memory); + mappedVulkanMemory.erase (mapped); + lastVKError.clear (); +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_unmap_memory) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int memoryHigh, int memoryLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + uint64_t mapKey = GetManagedVulkanMappedMemoryKey (memory); + std::unordered_map::iterator mapped = mappedVulkanMemory.find (mapKey); + if (mapped == mappedVulkanMemory.end ()) return; + + PFN_vkUnmapMemory vkUnmapMemory = (PFN_vkUnmapMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUnmapMemory"); + if (!vkUnmapMemory) return; + + vkUnmapMemory (device, memory); + mappedVulkanMemory.erase (mapped); + lastVKError.clear (); +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + bool lime_vk_write_mapped_memory (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int memoryHigh, int memoryLow, + int offsetHigh, int offsetLow, value bytes, int byteOffset, int byteLength) { + +#ifdef LIME_VULKAN + Bytes data (bytes); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + uint64_t offset = CombineVulkanHandle (offsetHigh, offsetLow); + if (!memory || !data.b || byteOffset < 0 || byteLength < 0 || byteOffset + byteLength > data.length) { + + lastVKError = "Invalid mapped Vulkan memory write range"; + return false; + + } + + std::unordered_map::iterator mapped = mappedVulkanMemory.find (GetManagedVulkanMappedMemoryKey (memory)); + if (mapped == mappedVulkanMemory.end ()) { + + lastVKError = "Vulkan memory is not mapped"; + return false; + + } + + if (offset < mapped->second.offset) { + + lastVKError = "Mapped Vulkan memory write offset is outside the mapped range"; + return false; + + } + + uint64_t relativeOffset = offset - mapped->second.offset; + if (mapped->second.size != VK_WHOLE_SIZE && relativeOffset + (uint64_t)byteLength > (uint64_t)mapped->second.size) { + + lastVKError = "Mapped Vulkan memory write exceeds the mapped range"; + return false; + + } + + memcpy ((unsigned char*)mapped->second.data + relativeOffset, data.b + byteOffset, byteLength); + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_write_mapped_memory) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int memoryHigh, int memoryLow, int offsetHigh, int offsetLow, Bytes* bytes, int byteOffset, int byteLength) { + +#ifdef LIME_VULKAN + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + uint64_t offset = CombineVulkanHandle (offsetHigh, offsetLow); + if (!memory || !bytes || !bytes->b || byteOffset < 0 || byteLength < 0 || byteOffset + byteLength > bytes->length) { + + lastVKError = "Invalid mapped Vulkan memory write range"; + return false; + + } + + std::unordered_map::iterator mapped = mappedVulkanMemory.find (GetManagedVulkanMappedMemoryKey (memory)); + if (mapped == mappedVulkanMemory.end ()) { + + lastVKError = "Vulkan memory is not mapped"; + return false; + + } + + if (offset < mapped->second.offset) { + + lastVKError = "Mapped Vulkan memory write offset is outside the mapped range"; + return false; + + } + + uint64_t relativeOffset = offset - mapped->second.offset; + if (mapped->second.size != VK_WHOLE_SIZE && relativeOffset + (uint64_t)byteLength > (uint64_t)mapped->second.size) { + + lastVKError = "Mapped Vulkan memory write exceeds the mapped range"; + return false; + + } + + memcpy ((unsigned char*)mapped->second.data + relativeOffset, bytes->b + byteOffset, byteLength); + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + bool lime_vk_flush_mapped_memory (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int memoryHigh, + int memoryLow, int offsetHigh, int offsetLow, int sizeHigh, int sizeLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges = + (PFN_vkFlushMappedMemoryRanges)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkFlushMappedMemoryRanges"); + if (!vkFlushMappedMemoryRanges || !memory) return false; + + VkMappedMemoryRange range; + memset (&range, 0, sizeof (range)); + range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range.memory = memory; + range.offset = (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow); + range.size = NormalizeVulkanRangeSize (CombineVulkanHandle (sizeHigh, sizeLow)); + VkResult result = vkFlushMappedMemoryRanges (device, 1, &range); + if (result != VK_SUCCESS) { + + lastVKError = "vkFlushMappedMemoryRanges failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_flush_mapped_memory) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int memoryHigh, int memoryLow, int offsetHigh, int offsetLow, int sizeHigh, int sizeLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges = + (PFN_vkFlushMappedMemoryRanges)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkFlushMappedMemoryRanges"); + if (!vkFlushMappedMemoryRanges || !memory) return false; + + VkMappedMemoryRange range; + memset (&range, 0, sizeof (range)); + range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range.memory = memory; + range.offset = (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow); + range.size = NormalizeVulkanRangeSize (CombineVulkanHandle (sizeHigh, sizeLow)); + VkResult result = vkFlushMappedMemoryRanges (device, 1, &range); + if (result != VK_SUCCESS) { + + lastVKError = "vkFlushMappedMemoryRanges failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + bool lime_vk_invalidate_mapped_memory (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int memoryHigh, + int memoryLow, int offsetHigh, int offsetLow, int sizeHigh, int sizeLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges = + (PFN_vkInvalidateMappedMemoryRanges)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkInvalidateMappedMemoryRanges"); + if (!vkInvalidateMappedMemoryRanges || !memory) return false; + + VkMappedMemoryRange range; + memset (&range, 0, sizeof (range)); + range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range.memory = memory; + range.offset = (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow); + range.size = NormalizeVulkanRangeSize (CombineVulkanHandle (sizeHigh, sizeLow)); + VkResult result = vkInvalidateMappedMemoryRanges (device, 1, &range); + if (result != VK_SUCCESS) { + + lastVKError = "vkInvalidateMappedMemoryRanges failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_invalidate_mapped_memory) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int memoryHigh, int memoryLow, int offsetHigh, int offsetLow, int sizeHigh, int sizeLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges = + (PFN_vkInvalidateMappedMemoryRanges)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkInvalidateMappedMemoryRanges"); + if (!vkInvalidateMappedMemoryRanges || !memory) return false; + + VkMappedMemoryRange range; + memset (&range, 0, sizeof (range)); + range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range.memory = memory; + range.offset = (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow); + range.size = NormalizeVulkanRangeSize (CombineVulkanHandle (sizeHigh, sizeLow)); + VkResult result = vkInvalidateMappedMemoryRanges (device, 1, &range); + if (result != VK_SUCCESS) { + + lastVKError = "vkInvalidateMappedMemoryRanges failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + value lime_vk_create_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int sizeHigh, int sizeLow, int usage) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateBuffer vkCreateBuffer = (PFN_vkCreateBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateBuffer"); + if (!vkCreateBuffer) return alloc_null (); + + VkBufferCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + createInfo.size = (VkDeviceSize)CombineVulkanHandle (sizeHigh, sizeLow); + createInfo.usage = (VkBufferUsageFlags)usage; + createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VkBuffer buffer = VK_NULL_HANDLE; + VkResult result = vkCreateBuffer (device, &createInfo, 0, &buffer); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateBuffer failed"; + return alloc_null (); + + } + + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)buffer); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_create_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int sizeHigh, int sizeLow, int usage) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateBuffer vkCreateBuffer = (PFN_vkCreateBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateBuffer"); + if (!vkCreateBuffer) return 0; + + VkBufferCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + createInfo.size = (VkDeviceSize)CombineVulkanHandle (sizeHigh, sizeLow); + createInfo.usage = (VkBufferUsageFlags)usage; + createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VkBuffer buffer = VK_NULL_HANDLE; + VkResult result = vkCreateBuffer (device, &createInfo, 0, &buffer); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateBuffer failed"; + return 0; + + } + + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)buffer); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + void lime_vk_destroy_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int bufferHigh, int bufferLow) { + +#ifdef LIME_VULKAN + if (bufferHigh == 0 && bufferLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + PFN_vkDestroyBuffer vkDestroyBuffer = (PFN_vkDestroyBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyBuffer"); + if (vkDestroyBuffer) { + + vkDestroyBuffer (device, buffer, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_destroy_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int bufferHigh, int bufferLow) { + +#ifdef LIME_VULKAN + if (bufferHigh == 0 && bufferLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + PFN_vkDestroyBuffer vkDestroyBuffer = (PFN_vkDestroyBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyBuffer"); + if (vkDestroyBuffer) { + + vkDestroyBuffer (device, buffer, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + value lime_vk_get_buffer_memory_requirements (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int bufferHigh, + int bufferLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements = + (PFN_vkGetBufferMemoryRequirements)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkGetBufferMemoryRequirements"); + if (!vkGetBufferMemoryRequirements || !buffer) return alloc_null (); + + VkMemoryRequirements requirements; + memset (&requirements, 0, sizeof (requirements)); + vkGetBufferMemoryRequirements (device, buffer, &requirements); + lastVKError.clear (); + return CreateVulkanMemoryRequirementsValue (requirements); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_get_buffer_memory_requirements) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, + int deviceHigh, int deviceLow, int bufferHigh, int bufferLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements = + (PFN_vkGetBufferMemoryRequirements)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkGetBufferMemoryRequirements"); + if (!vkGetBufferMemoryRequirements || !buffer) return 0; + + VkMemoryRequirements requirements; + memset (&requirements, 0, sizeof (requirements)); + vkGetBufferMemoryRequirements (device, buffer, &requirements); + lastVKError.clear (); + return HLCreateVulkanMemoryRequirementsValue (requirements); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + bool lime_vk_bind_buffer_memory (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int bufferHigh, int bufferLow, + int memoryHigh, int memoryLow, int offsetHigh, int offsetLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + PFN_vkBindBufferMemory vkBindBufferMemory = (PFN_vkBindBufferMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkBindBufferMemory"); + if (!vkBindBufferMemory || !buffer || !memory) return false; + + VkResult result = vkBindBufferMemory (device, buffer, memory, (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow)); + if (result != VK_SUCCESS) { + + lastVKError = "vkBindBufferMemory failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_bind_buffer_memory) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int bufferHigh, int bufferLow, int memoryHigh, int memoryLow, int offsetHigh, int offsetLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + PFN_vkBindBufferMemory vkBindBufferMemory = (PFN_vkBindBufferMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkBindBufferMemory"); + if (!vkBindBufferMemory || !buffer || !memory) return false; + + VkResult result = vkBindBufferMemory (device, buffer, memory, (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow)); + if (result != VK_SUCCESS) { + + lastVKError = "vkBindBufferMemory failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + value lime_vk_create_image (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int width, int height, int depth, + int mipLevels, int arrayLayers, int format, int imageType, int tiling, int usage, int samples) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateImage vkCreateImage = (PFN_vkCreateImage)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateImage"); + if (!vkCreateImage) return alloc_null (); + + VkImageCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + createInfo.imageType = (VkImageType)imageType; + createInfo.format = (VkFormat)format; + createInfo.extent.width = (uint32_t)width; + createInfo.extent.height = (uint32_t)height; + createInfo.extent.depth = (uint32_t)depth; + createInfo.mipLevels = (uint32_t)mipLevels; + createInfo.arrayLayers = (uint32_t)arrayLayers; + createInfo.samples = (VkSampleCountFlagBits)samples; + createInfo.tiling = (VkImageTiling)tiling; + createInfo.usage = (VkImageUsageFlags)usage; + createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + if ((VkImageViewType)imageType == VK_IMAGE_VIEW_TYPE_CUBE || arrayLayers == 6) { + + createInfo.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + + } + + VkImage image = VK_NULL_HANDLE; + VkResult result = vkCreateImage (device, &createInfo, 0, &image); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateImage failed"; + return alloc_null (); + + } + + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)image); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_create_image) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int width, int height, int depth, int mipLevels, int arrayLayers, int format, int imageType, int tiling, int usage, int samples) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateImage vkCreateImage = (PFN_vkCreateImage)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateImage"); + if (!vkCreateImage) return 0; + + VkImageCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + createInfo.imageType = (VkImageType)imageType; + createInfo.format = (VkFormat)format; + createInfo.extent.width = (uint32_t)width; + createInfo.extent.height = (uint32_t)height; + createInfo.extent.depth = (uint32_t)depth; + createInfo.mipLevels = (uint32_t)mipLevels; + createInfo.arrayLayers = (uint32_t)arrayLayers; + createInfo.samples = (VkSampleCountFlagBits)samples; + createInfo.tiling = (VkImageTiling)tiling; + createInfo.usage = (VkImageUsageFlags)usage; + createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + if ((VkImageViewType)imageType == VK_IMAGE_VIEW_TYPE_CUBE || arrayLayers == 6) { + + createInfo.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + + } + + VkImage image = VK_NULL_HANDLE; + VkResult result = vkCreateImage (device, &createInfo, 0, &image); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateImage failed"; + return 0; + + } + + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)image); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + void lime_vk_destroy_image (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int imageHigh, int imageLow) { + +#ifdef LIME_VULKAN + if (imageHigh == 0 && imageLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkImage image = (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow); + PFN_vkDestroyImage vkDestroyImage = (PFN_vkDestroyImage)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyImage"); + if (vkDestroyImage) { + + vkDestroyImage (device, image, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_destroy_image) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int imageHigh, int imageLow) { + +#ifdef LIME_VULKAN + if (imageHigh == 0 && imageLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkImage image = (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow); + PFN_vkDestroyImage vkDestroyImage = (PFN_vkDestroyImage)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyImage"); + if (vkDestroyImage) { + + vkDestroyImage (device, image, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + value lime_vk_get_image_memory_requirements (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int imageHigh, + int imageLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkImage image = (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow); + PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements = + (PFN_vkGetImageMemoryRequirements)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkGetImageMemoryRequirements"); + if (!vkGetImageMemoryRequirements || !image) return alloc_null (); + + VkMemoryRequirements requirements; + memset (&requirements, 0, sizeof (requirements)); + vkGetImageMemoryRequirements (device, image, &requirements); + lastVKError.clear (); + return CreateVulkanMemoryRequirementsValue (requirements); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_get_image_memory_requirements) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int imageHigh, int imageLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkImage image = (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow); + PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements = + (PFN_vkGetImageMemoryRequirements)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkGetImageMemoryRequirements"); + if (!vkGetImageMemoryRequirements || !image) return 0; + + VkMemoryRequirements requirements; + memset (&requirements, 0, sizeof (requirements)); + vkGetImageMemoryRequirements (device, image, &requirements); + lastVKError.clear (); + return HLCreateVulkanMemoryRequirementsValue (requirements); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + bool lime_vk_bind_image_memory (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int imageHigh, int imageLow, + int memoryHigh, int memoryLow, int offsetHigh, int offsetLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkImage image = (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + PFN_vkBindImageMemory vkBindImageMemory = (PFN_vkBindImageMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkBindImageMemory"); + if (!vkBindImageMemory || !image || !memory) return false; + + VkResult result = vkBindImageMemory (device, image, memory, (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow)); + if (result != VK_SUCCESS) { + + lastVKError = "vkBindImageMemory failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + HL_PRIM bool HL_NAME(hl_vk_bind_image_memory) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int imageHigh, int imageLow, int memoryHigh, int memoryLow, int offsetHigh, int offsetLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkImage image = (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow); + VkDeviceMemory memory = (VkDeviceMemory)(uintptr_t)CombineVulkanHandle (memoryHigh, memoryLow); + PFN_vkBindImageMemory vkBindImageMemory = (PFN_vkBindImageMemory)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkBindImageMemory"); + if (!vkBindImageMemory || !image || !memory) return false; + + VkResult result = vkBindImageMemory (device, image, memory, (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow)); + if (result != VK_SUCCESS) { + + lastVKError = "vkBindImageMemory failed"; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + + } + + + value lime_vk_create_swapchain (value window, int instanceHigh, int instanceLow, int physicalDeviceHigh, int physicalDeviceLow, int deviceHigh, + int deviceLow, int surfaceHigh, int surfaceLow, int queueFamilyIndex, int width, int height, int presentMode, int oldSwapchainHigh, + int oldSwapchainLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkPhysicalDevice physicalDevice = (VkPhysicalDevice)(uintptr_t)CombineVulkanHandle (physicalDeviceHigh, physicalDeviceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkSurfaceKHR surface = UInt64ToVulkanSurface (CombineVulkanHandle (surfaceHigh, surfaceLow)); + VkSwapchainKHR oldSwapchain = (VkSwapchainKHR)(uintptr_t)CombineVulkanHandle (oldSwapchainHigh, oldSwapchainLow); + ManagedVulkanSwapchain swapchain; + + if (!CreateManagedVulkanSwapchain (targetWindow, instance, physicalDevice, device, surface, (uint32_t)queueFamilyIndex, width, height, + (VkPresentModeKHR)presentMode, oldSwapchain, &swapchain)) { + + return alloc_null (); + + } + + return CreateVulkanSwapchainValue (swapchain); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_create_swapchain) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int physicalDeviceHigh, + int physicalDeviceLow, int deviceHigh, int deviceLow, int surfaceHigh, int surfaceLow, int queueFamilyIndex, int width, int height, + int presentMode, int oldSwapchainHigh, int oldSwapchainLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkPhysicalDevice physicalDevice = (VkPhysicalDevice)(uintptr_t)CombineVulkanHandle (physicalDeviceHigh, physicalDeviceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkSurfaceKHR surface = UInt64ToVulkanSurface (CombineVulkanHandle (surfaceHigh, surfaceLow)); + VkSwapchainKHR oldSwapchain = (VkSwapchainKHR)(uintptr_t)CombineVulkanHandle (oldSwapchainHigh, oldSwapchainLow); + ManagedVulkanSwapchain swapchain; + + if (!CreateManagedVulkanSwapchain (targetWindow, instance, physicalDevice, device, surface, (uint32_t)queueFamilyIndex, width, height, + (VkPresentModeKHR)presentMode, oldSwapchain, &swapchain)) { + + return 0; + + } + + return HLCreateVulkanSwapchainValue (swapchain); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + void lime_vk_destroy_swapchain (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int swapchainHigh, int swapchainLow) { + +#ifdef LIME_VULKAN + if (swapchainHigh == 0 && swapchainLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkSwapchainKHR swapchain = (VkSwapchainKHR)(uintptr_t)CombineVulkanHandle (swapchainHigh, swapchainLow); + PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR = (PFN_vkDestroySwapchainKHR)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroySwapchainKHR"); + if (vkDestroySwapchainKHR) { + + vkDestroySwapchainKHR (device, swapchain, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_destroy_swapchain) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int swapchainHigh, int swapchainLow) { + +#ifdef LIME_VULKAN + if (swapchainHigh == 0 && swapchainLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkSwapchainKHR swapchain = (VkSwapchainKHR)(uintptr_t)CombineVulkanHandle (swapchainHigh, swapchainLow); + PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR = (PFN_vkDestroySwapchainKHR)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroySwapchainKHR"); + if (vkDestroySwapchainKHR) { + + vkDestroySwapchainKHR (device, swapchain, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + value lime_vk_get_swapchain_images (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int swapchainHigh, + int swapchainLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkSwapchainKHR swapchain = (VkSwapchainKHR)(uintptr_t)CombineVulkanHandle (swapchainHigh, swapchainLow); + PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR = (PFN_vkGetSwapchainImagesKHR)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkGetSwapchainImagesKHR"); + if (!vkGetSwapchainImagesKHR || !swapchain) return alloc_null (); + + uint32_t imageCount = 0; + VkResult result = vkGetSwapchainImagesKHR (device, swapchain, &imageCount, 0); + if (result != VK_SUCCESS) { + + lastVKError = "Failed to query Vulkan swapchain image count"; + return alloc_null (); + + } + + value images = alloc_array (imageCount); + if (imageCount == 0) { + + lastVKError.clear (); + return images; + + } + + std::vector swapchainImages (imageCount); + result = vkGetSwapchainImagesKHR (device, swapchain, &imageCount, swapchainImages.data ()); + if (result != VK_SUCCESS) { + + lastVKError = "vkGetSwapchainImagesKHR failed"; + return alloc_null (); + + } + + for (uint32_t i = 0; i < imageCount; ++i) { + + val_array_set_i (images, i, CreateVulkanHandleValue ((uint64_t)(uintptr_t)swapchainImages[i])); + + } + + lastVKError.clear (); + return images; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_array (0); +#endif + + } + + + HL_PRIM hl_varray* HL_NAME(hl_vk_get_swapchain_images) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int swapchainHigh, int swapchainLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkSwapchainKHR swapchain = (VkSwapchainKHR)(uintptr_t)CombineVulkanHandle (swapchainHigh, swapchainLow); + PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR = (PFN_vkGetSwapchainImagesKHR)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkGetSwapchainImagesKHR"); + if (!vkGetSwapchainImagesKHR || !swapchain) return 0; + + uint32_t imageCount = 0; + VkResult result = vkGetSwapchainImagesKHR (device, swapchain, &imageCount, 0); + if (result != VK_SUCCESS) { + + lastVKError = "Failed to query Vulkan swapchain image count"; + return 0; + + } + + hl_varray* images = (hl_varray*)hl_alloc_array (&hlt_dynobj, imageCount); + if (imageCount == 0) { + + lastVKError.clear (); + return images; + + } + + std::vector swapchainImages (imageCount); + result = vkGetSwapchainImagesKHR (device, swapchain, &imageCount, swapchainImages.data ()); + if (result != VK_SUCCESS) { + + lastVKError = "vkGetSwapchainImagesKHR failed"; + return 0; + + } + + vdynamic** imageData = hl_aptr (images, vdynamic*); + for (uint32_t i = 0; i < imageCount; ++i) { + + *imageData++ = HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)swapchainImages[i]); + + } + + lastVKError.clear (); + return images; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return (hl_varray*)hl_alloc_array (&hlt_dynobj, 0); +#endif + + } + + + value lime_vk_acquire_next_image (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int swapchainHigh, + int swapchainLow, int timeoutHigh, int timeoutLow, int semaphoreHigh, int semaphoreLow, int fenceHigh, int fenceLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkSwapchainKHR swapchain = (VkSwapchainKHR)(uintptr_t)CombineVulkanHandle (swapchainHigh, swapchainLow); + uint64_t timeout = CombineVulkanHandle (timeoutHigh, timeoutLow); + VkSemaphore semaphore = (VkSemaphore)(uintptr_t)CombineVulkanHandle (semaphoreHigh, semaphoreLow); + VkFence fence = (VkFence)(uintptr_t)CombineVulkanHandle (fenceHigh, fenceLow); + if (!semaphore && !fence) { + + lastVKError = "Vulkan image acquisition requires a semaphore or fence"; + return CreateVulkanAcquireValue (VK_ERROR_INITIALIZATION_FAILED, -1); + + } + + PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR = (PFN_vkAcquireNextImageKHR)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkAcquireNextImageKHR"); + if (!vkAcquireNextImageKHR || !swapchain) return CreateVulkanAcquireValue (VK_ERROR_INITIALIZATION_FAILED, -1); + + uint32_t imageIndex = 0; + VkResult result = vkAcquireNextImageKHR (device, swapchain, timeout, semaphore, fence, &imageIndex); + if (result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) { + + lastVKError.clear (); + return CreateVulkanAcquireValue (result, (int)imageIndex); + + } + + lastVKError = "vkAcquireNextImageKHR failed"; + return CreateVulkanAcquireValue (result, -1); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_acquire_next_image) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int swapchainHigh, int swapchainLow, int timeoutHigh, int timeoutLow, int semaphoreHigh, int semaphoreLow, int fenceHigh, int fenceLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkSwapchainKHR swapchain = (VkSwapchainKHR)(uintptr_t)CombineVulkanHandle (swapchainHigh, swapchainLow); + uint64_t timeout = CombineVulkanHandle (timeoutHigh, timeoutLow); + VkSemaphore semaphore = (VkSemaphore)(uintptr_t)CombineVulkanHandle (semaphoreHigh, semaphoreLow); + VkFence fence = (VkFence)(uintptr_t)CombineVulkanHandle (fenceHigh, fenceLow); + if (!semaphore && !fence) { + + lastVKError = "Vulkan image acquisition requires a semaphore or fence"; + return HLCreateVulkanAcquireValue (VK_ERROR_INITIALIZATION_FAILED, -1); + + } + + PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR = (PFN_vkAcquireNextImageKHR)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkAcquireNextImageKHR"); + if (!vkAcquireNextImageKHR || !swapchain) return HLCreateVulkanAcquireValue (VK_ERROR_INITIALIZATION_FAILED, -1); + + uint32_t imageIndex = 0; + VkResult result = vkAcquireNextImageKHR (device, swapchain, timeout, semaphore, fence, &imageIndex); + if (result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) { + + lastVKError.clear (); + return HLCreateVulkanAcquireValue (result, (int)imageIndex); + + } + + lastVKError = "vkAcquireNextImageKHR failed"; + return HLCreateVulkanAcquireValue (result, -1); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + int lime_vk_queue_present (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int queueHigh, int queueLow, + int swapchainHigh, int swapchainLow, int imageIndex, int waitSemaphoreHigh, int waitSemaphoreLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkQueue queue = (VkQueue)(uintptr_t)CombineVulkanHandle (queueHigh, queueLow); + VkSwapchainKHR swapchain = (VkSwapchainKHR)(uintptr_t)CombineVulkanHandle (swapchainHigh, swapchainLow); + VkSemaphore waitSemaphore = (VkSemaphore)(uintptr_t)CombineVulkanHandle (waitSemaphoreHigh, waitSemaphoreLow); + PFN_vkQueuePresentKHR vkQueuePresentKHR = (PFN_vkQueuePresentKHR)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkQueuePresentKHR"); + if (!vkQueuePresentKHR || !queue || !swapchain) return VK_ERROR_INITIALIZATION_FAILED; + + VkPresentInfoKHR presentInfo; + memset (&presentInfo, 0, sizeof (presentInfo)); + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.waitSemaphoreCount = waitSemaphore ? 1 : 0; + presentInfo.pWaitSemaphores = waitSemaphore ? &waitSemaphore : 0; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &swapchain; + uint32_t swapchainImageIndex = (uint32_t)imageIndex; + presentInfo.pImageIndices = &swapchainImageIndex; + + VkResult result = vkQueuePresentKHR (queue, &presentInfo); + if (result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) { + + lastVKError.clear (); + + } else { + + lastVKError = "vkQueuePresentKHR failed"; + + } + + return (int)result; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return -3; +#endif + + } + + + HL_PRIM int HL_NAME(hl_vk_queue_present) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int queueHigh, + int queueLow, int swapchainHigh, int swapchainLow, int imageIndex, int waitSemaphoreHigh, int waitSemaphoreLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkQueue queue = (VkQueue)(uintptr_t)CombineVulkanHandle (queueHigh, queueLow); + VkSwapchainKHR swapchain = (VkSwapchainKHR)(uintptr_t)CombineVulkanHandle (swapchainHigh, swapchainLow); + VkSemaphore waitSemaphore = (VkSemaphore)(uintptr_t)CombineVulkanHandle (waitSemaphoreHigh, waitSemaphoreLow); + PFN_vkQueuePresentKHR vkQueuePresentKHR = (PFN_vkQueuePresentKHR)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkQueuePresentKHR"); + if (!vkQueuePresentKHR || !queue || !swapchain) return VK_ERROR_INITIALIZATION_FAILED; + + VkPresentInfoKHR presentInfo; + memset (&presentInfo, 0, sizeof (presentInfo)); + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.waitSemaphoreCount = waitSemaphore ? 1 : 0; + presentInfo.pWaitSemaphores = waitSemaphore ? &waitSemaphore : 0; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &swapchain; + uint32_t swapchainImageIndex = (uint32_t)imageIndex; + presentInfo.pImageIndices = &swapchainImageIndex; + + VkResult result = vkQueuePresentKHR (queue, &presentInfo); + if (result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) { + + lastVKError.clear (); + + } else { + + lastVKError = "vkQueuePresentKHR failed"; + + } + + return (int)result; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return -3; +#endif + + } + + + value lime_vk_create_image_view (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int imageHigh, int imageLow, + int format, int aspectMask, int viewType) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkImage image = (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow); + if (!image || format == VK_FORMAT_UNDEFINED) { + + lastVKError = "Missing Vulkan image or image view format"; + return alloc_null (); + + } + + PFN_vkCreateImageView vkCreateImageView = (PFN_vkCreateImageView)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateImageView"); + if (!vkCreateImageView) return alloc_null (); + + VkImageViewCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = image; + createInfo.viewType = (VkImageViewType)viewType; + createInfo.format = (VkFormat)format; + createInfo.subresourceRange.aspectMask = (VkImageAspectFlags)aspectMask; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + VkImageView imageView = VK_NULL_HANDLE; + VkResult result = vkCreateImageView (device, &createInfo, 0, &imageView); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateImageView failed"; + return alloc_null (); + + } + + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)imageView); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_create_image_view) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int imageHigh, int imageLow, int format, int aspectMask, int viewType) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkImage image = (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow); + if (!image || format == VK_FORMAT_UNDEFINED) { + + lastVKError = "Missing Vulkan image or image view format"; + return 0; + + } + + PFN_vkCreateImageView vkCreateImageView = (PFN_vkCreateImageView)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateImageView"); + if (!vkCreateImageView) return 0; + + VkImageViewCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = image; + createInfo.viewType = (VkImageViewType)viewType; + createInfo.format = (VkFormat)format; + createInfo.subresourceRange.aspectMask = (VkImageAspectFlags)aspectMask; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + VkImageView imageView = VK_NULL_HANDLE; + VkResult result = vkCreateImageView (device, &createInfo, 0, &imageView); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateImageView failed"; + return 0; + + } + + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)imageView); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + void lime_vk_destroy_image_view (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int imageViewHigh, + int imageViewLow) { + +#ifdef LIME_VULKAN + if (imageViewHigh == 0 && imageViewLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkImageView imageView = (VkImageView)(uintptr_t)CombineVulkanHandle (imageViewHigh, imageViewLow); + PFN_vkDestroyImageView vkDestroyImageView = (PFN_vkDestroyImageView)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroyImageView"); + if (vkDestroyImageView) { + + vkDestroyImageView (device, imageView, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_destroy_image_view) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int imageViewHigh, int imageViewLow) { + +#ifdef LIME_VULKAN + if (imageViewHigh == 0 && imageViewLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkImageView imageView = (VkImageView)(uintptr_t)CombineVulkanHandle (imageViewHigh, imageViewLow); + PFN_vkDestroyImageView vkDestroyImageView = (PFN_vkDestroyImageView)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroyImageView"); + if (vkDestroyImageView) { + + vkDestroyImageView (device, imageView, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + value lime_vk_create_image_view_ex (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int imageHigh, int imageLow, + int format, int aspectMask, int viewType, int baseMipLevel, int levelCount, int baseArrayLayer, int layerCount) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkImage image = (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow); + if (!image || format == VK_FORMAT_UNDEFINED) { + + lastVKError = "Missing Vulkan image or image view format"; + return alloc_null (); + + } + + PFN_vkCreateImageView vkCreateImageView = (PFN_vkCreateImageView)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateImageView"); + if (!vkCreateImageView) return alloc_null (); + + VkImageViewCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = image; + createInfo.viewType = (VkImageViewType)viewType; + createInfo.format = (VkFormat)format; + createInfo.subresourceRange.aspectMask = (VkImageAspectFlags)aspectMask; + createInfo.subresourceRange.baseMipLevel = (uint32_t)baseMipLevel; + createInfo.subresourceRange.levelCount = (uint32_t)levelCount; + createInfo.subresourceRange.baseArrayLayer = (uint32_t)baseArrayLayer; + createInfo.subresourceRange.layerCount = (uint32_t)layerCount; + + VkImageView imageView = VK_NULL_HANDLE; + VkResult result = vkCreateImageView (device, &createInfo, 0, &imageView); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateImageView failed"; + return alloc_null (); + + } + + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)imageView); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_create_image_view_ex) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int imageHigh, int imageLow, int format, int aspectMask, int viewType, int baseMipLevel, int levelCount, + int baseArrayLayer, int layerCount) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkImage image = (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow); + if (!image || format == VK_FORMAT_UNDEFINED) { + + lastVKError = "Missing Vulkan image or image view format"; + return 0; + + } + + PFN_vkCreateImageView vkCreateImageView = (PFN_vkCreateImageView)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateImageView"); + if (!vkCreateImageView) return 0; + + VkImageViewCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = image; + createInfo.viewType = (VkImageViewType)viewType; + createInfo.format = (VkFormat)format; + createInfo.subresourceRange.aspectMask = (VkImageAspectFlags)aspectMask; + createInfo.subresourceRange.baseMipLevel = (uint32_t)baseMipLevel; + createInfo.subresourceRange.levelCount = (uint32_t)levelCount; + createInfo.subresourceRange.baseArrayLayer = (uint32_t)baseArrayLayer; + createInfo.subresourceRange.layerCount = (uint32_t)layerCount; + + VkImageView imageView = VK_NULL_HANDLE; + VkResult result = vkCreateImageView (device, &createInfo, 0, &imageView); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateImageView failed"; + return 0; + + } + + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)imageView); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + value lime_vk_create_render_pass (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, value state) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateRenderPass vkCreateRenderPass = (PFN_vkCreateRenderPass)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCreateRenderPass"); + if (!vkCreateRenderPass) return alloc_null (); + std::vector packed = GetVulkanIntVector (state); + if (packed.size () < 16) return alloc_null (); + + int colorFormat = packed[0]; + int depthStencilFormat = packed[1]; + int samples = packed[2]; + int colorLoadOp = packed[3]; + int colorStoreOp = packed[4]; + int colorInitialLayout = packed[5]; + int colorFinalLayout = packed[6]; + int depthInitialLayout = packed[7]; + int depthFinalLayout = packed[8]; + int depthLoadOp = packed[9]; + int depthStoreOp = packed[10]; + int resolveFormat = packed[11]; + int resolveLoadOp = packed[12]; + int resolveStoreOp = packed[13]; + int resolveInitialLayout = packed[14]; + int resolveFinalLayout = packed[15]; + + std::vector attachments; + VkAttachmentDescription colorAttachment; + memset (&colorAttachment, 0, sizeof (colorAttachment)); + colorAttachment.format = (VkFormat)colorFormat; + colorAttachment.samples = (VkSampleCountFlagBits)samples; + colorAttachment.loadOp = (VkAttachmentLoadOp)colorLoadOp; + colorAttachment.storeOp = (VkAttachmentStoreOp)colorStoreOp; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = (VkImageLayout)colorInitialLayout; + colorAttachment.finalLayout = (VkImageLayout)colorFinalLayout; + attachments.push_back (colorAttachment); + + VkAttachmentReference colorReference; + memset (&colorReference, 0, sizeof (colorReference)); + colorReference.attachment = 0; + colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthReference; + memset (&depthReference, 0, sizeof (depthReference)); + bool hasDepth = depthStencilFormat != VK_FORMAT_UNDEFINED; + bool hasResolve = resolveFormat != VK_FORMAT_UNDEFINED; + if (hasDepth) { + + VkAttachmentDescription depthAttachment; + memset (&depthAttachment, 0, sizeof (depthAttachment)); + depthAttachment.format = (VkFormat)depthStencilFormat; + depthAttachment.samples = (VkSampleCountFlagBits)samples; + depthAttachment.loadOp = (VkAttachmentLoadOp)depthLoadOp; + depthAttachment.storeOp = (VkAttachmentStoreOp)depthStoreOp; + depthAttachment.stencilLoadOp = (VkAttachmentLoadOp)depthLoadOp; + depthAttachment.stencilStoreOp = (VkAttachmentStoreOp)depthStoreOp; + depthAttachment.initialLayout = (VkImageLayout)depthInitialLayout; + depthAttachment.finalLayout = (VkImageLayout)depthFinalLayout; + attachments.push_back (depthAttachment); + depthReference.attachment = 1; + depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + } + + VkAttachmentReference resolveReference; + memset (&resolveReference, 0, sizeof (resolveReference)); + if (hasResolve) { + + VkAttachmentDescription resolveAttachment; + memset (&resolveAttachment, 0, sizeof (resolveAttachment)); + resolveAttachment.format = (VkFormat)resolveFormat; + resolveAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + resolveAttachment.loadOp = (VkAttachmentLoadOp)resolveLoadOp; + resolveAttachment.storeOp = (VkAttachmentStoreOp)resolveStoreOp; + resolveAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + resolveAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + resolveAttachment.initialLayout = (VkImageLayout)resolveInitialLayout; + resolveAttachment.finalLayout = (VkImageLayout)resolveFinalLayout; + attachments.push_back (resolveAttachment); + resolveReference.attachment = (uint32_t)(attachments.size () - 1); + resolveReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + } + + VkSubpassDescription subpass; + memset (&subpass, 0, sizeof (subpass)); + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorReference; + subpass.pResolveAttachments = hasResolve ? &resolveReference : 0; + subpass.pDepthStencilAttachment = hasDepth ? &depthReference : 0; + + VkSubpassDependency dependency; + memset (&dependency, 0, sizeof (dependency)); + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + createInfo.attachmentCount = (uint32_t)attachments.size (); + createInfo.pAttachments = attachments.data (); + createInfo.subpassCount = 1; + createInfo.pSubpasses = &subpass; + createInfo.dependencyCount = 1; + createInfo.pDependencies = &dependency; + + VkRenderPass renderPass = VK_NULL_HANDLE; + VkResult result = vkCreateRenderPass (device, &createInfo, 0, &renderPass); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateRenderPass failed"; + return alloc_null (); + + } + + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)renderPass); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_create_render_pass) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, hl_varray* state) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateRenderPass vkCreateRenderPass = (PFN_vkCreateRenderPass)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCreateRenderPass"); + if (!vkCreateRenderPass) return 0; + std::vector packed = GetHLVulkanIntVector (state); + if (packed.size () < 16) return 0; + + int colorFormat = packed[0]; + int depthStencilFormat = packed[1]; + int samples = packed[2]; + int colorLoadOp = packed[3]; + int colorStoreOp = packed[4]; + int colorInitialLayout = packed[5]; + int colorFinalLayout = packed[6]; + int depthInitialLayout = packed[7]; + int depthFinalLayout = packed[8]; + int depthLoadOp = packed[9]; + int depthStoreOp = packed[10]; + int resolveFormat = packed[11]; + int resolveLoadOp = packed[12]; + int resolveStoreOp = packed[13]; + int resolveInitialLayout = packed[14]; + int resolveFinalLayout = packed[15]; + + std::vector attachments; + VkAttachmentDescription colorAttachment; + memset (&colorAttachment, 0, sizeof (colorAttachment)); + colorAttachment.format = (VkFormat)colorFormat; + colorAttachment.samples = (VkSampleCountFlagBits)samples; + colorAttachment.loadOp = (VkAttachmentLoadOp)colorLoadOp; + colorAttachment.storeOp = (VkAttachmentStoreOp)colorStoreOp; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = (VkImageLayout)colorInitialLayout; + colorAttachment.finalLayout = (VkImageLayout)colorFinalLayout; + attachments.push_back (colorAttachment); + + VkAttachmentReference colorReference; + memset (&colorReference, 0, sizeof (colorReference)); + colorReference.attachment = 0; + colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthReference; + memset (&depthReference, 0, sizeof (depthReference)); + bool hasDepth = depthStencilFormat != VK_FORMAT_UNDEFINED; + bool hasResolve = resolveFormat != VK_FORMAT_UNDEFINED; + if (hasDepth) { + + VkAttachmentDescription depthAttachment; + memset (&depthAttachment, 0, sizeof (depthAttachment)); + depthAttachment.format = (VkFormat)depthStencilFormat; + depthAttachment.samples = (VkSampleCountFlagBits)samples; + depthAttachment.loadOp = (VkAttachmentLoadOp)depthLoadOp; + depthAttachment.storeOp = (VkAttachmentStoreOp)depthStoreOp; + depthAttachment.stencilLoadOp = (VkAttachmentLoadOp)depthLoadOp; + depthAttachment.stencilStoreOp = (VkAttachmentStoreOp)depthStoreOp; + depthAttachment.initialLayout = (VkImageLayout)depthInitialLayout; + depthAttachment.finalLayout = (VkImageLayout)depthFinalLayout; + attachments.push_back (depthAttachment); + depthReference.attachment = 1; + depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + } + + VkAttachmentReference resolveReference; + memset (&resolveReference, 0, sizeof (resolveReference)); + if (hasResolve) { + + VkAttachmentDescription resolveAttachment; + memset (&resolveAttachment, 0, sizeof (resolveAttachment)); + resolveAttachment.format = (VkFormat)resolveFormat; + resolveAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + resolveAttachment.loadOp = (VkAttachmentLoadOp)resolveLoadOp; + resolveAttachment.storeOp = (VkAttachmentStoreOp)resolveStoreOp; + resolveAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + resolveAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + resolveAttachment.initialLayout = (VkImageLayout)resolveInitialLayout; + resolveAttachment.finalLayout = (VkImageLayout)resolveFinalLayout; + attachments.push_back (resolveAttachment); + resolveReference.attachment = (uint32_t)(attachments.size () - 1); + resolveReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + } + + VkSubpassDescription subpass; + memset (&subpass, 0, sizeof (subpass)); + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorReference; + subpass.pResolveAttachments = hasResolve ? &resolveReference : 0; + subpass.pDepthStencilAttachment = hasDepth ? &depthReference : 0; + + VkSubpassDependency dependency; + memset (&dependency, 0, sizeof (dependency)); + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + createInfo.attachmentCount = (uint32_t)attachments.size (); + createInfo.pAttachments = attachments.data (); + createInfo.subpassCount = 1; + createInfo.pSubpasses = &subpass; + createInfo.dependencyCount = 1; + createInfo.pDependencies = &dependency; + + VkRenderPass renderPass = VK_NULL_HANDLE; + VkResult result = vkCreateRenderPass (device, &createInfo, 0, &renderPass); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateRenderPass failed"; + return 0; + + } + + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)renderPass); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + value lime_vk_get_last_error () { + + return alloc_string (lastVKError.c_str ()); + + } + + + void lime_vk_destroy_render_pass (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int renderPassHigh, + int renderPassLow) { + +#ifdef LIME_VULKAN + if (renderPassHigh == 0 && renderPassLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkRenderPass renderPass = (VkRenderPass)(uintptr_t)CombineVulkanHandle (renderPassHigh, renderPassLow); + PFN_vkDestroyRenderPass vkDestroyRenderPass = (PFN_vkDestroyRenderPass)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroyRenderPass"); + if (vkDestroyRenderPass) { + + vkDestroyRenderPass (device, renderPass, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_destroy_render_pass) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int renderPassHigh, int renderPassLow) { + +#ifdef LIME_VULKAN + if (renderPassHigh == 0 && renderPassLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkRenderPass renderPass = (VkRenderPass)(uintptr_t)CombineVulkanHandle (renderPassHigh, renderPassLow); + PFN_vkDestroyRenderPass vkDestroyRenderPass = (PFN_vkDestroyRenderPass)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroyRenderPass"); + if (vkDestroyRenderPass) { + + vkDestroyRenderPass (device, renderPass, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + value lime_vk_create_framebuffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int renderPassHigh, + int renderPassLow, value attachments, int width, int height, int layers) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkRenderPass renderPass = (VkRenderPass)(uintptr_t)CombineVulkanHandle (renderPassHigh, renderPassLow); + std::vector packed = GetVulkanIntVector (attachments); + if (packed.empty () || packed.size () < (size_t)(1 + packed[0] * 2)) { + + lastVKError = "Invalid Vulkan framebuffer attachment list"; + return alloc_null (); + + } + + std::vector imageViews; + imageViews.reserve ((size_t)packed[0]); + for (int i = 0; i < packed[0]; ++i) { + + imageViews.push_back ((VkImageView)(uintptr_t)CombineVulkanHandle (packed[1 + i * 2], packed[2 + i * 2])); + + } + + PFN_vkCreateFramebuffer vkCreateFramebuffer = (PFN_vkCreateFramebuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCreateFramebuffer"); + if (!vkCreateFramebuffer || !renderPass) return alloc_null (); + + VkFramebufferCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + createInfo.renderPass = renderPass; + createInfo.attachmentCount = (uint32_t)imageViews.size (); + createInfo.pAttachments = imageViews.data (); + createInfo.width = (uint32_t)width; + createInfo.height = (uint32_t)height; + createInfo.layers = (uint32_t)layers; + + VkFramebuffer framebuffer = VK_NULL_HANDLE; + VkResult result = vkCreateFramebuffer (device, &createInfo, 0, &framebuffer); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateFramebuffer failed"; + return alloc_null (); + + } + + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)framebuffer); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } + + + HL_PRIM vdynamic* HL_NAME(hl_vk_create_framebuffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int renderPassHigh, int renderPassLow, hl_varray* attachments, int width, int height, int layers) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkRenderPass renderPass = (VkRenderPass)(uintptr_t)CombineVulkanHandle (renderPassHigh, renderPassLow); + std::vector packed = GetHLVulkanIntVector (attachments); + if (packed.empty () || packed.size () < (size_t)(1 + packed[0] * 2)) { + + lastVKError = "Invalid Vulkan framebuffer attachment list"; + return 0; + + } + + std::vector imageViews; + imageViews.reserve ((size_t)packed[0]); + for (int i = 0; i < packed[0]; ++i) { + + imageViews.push_back ((VkImageView)(uintptr_t)CombineVulkanHandle (packed[1 + i * 2], packed[2 + i * 2])); + + } + + PFN_vkCreateFramebuffer vkCreateFramebuffer = (PFN_vkCreateFramebuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCreateFramebuffer"); + if (!vkCreateFramebuffer || !renderPass) return 0; + + VkFramebufferCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + createInfo.renderPass = renderPass; + createInfo.attachmentCount = (uint32_t)imageViews.size (); + createInfo.pAttachments = imageViews.data (); + createInfo.width = (uint32_t)width; + createInfo.height = (uint32_t)height; + createInfo.layers = (uint32_t)layers; + + VkFramebuffer framebuffer = VK_NULL_HANDLE; + VkResult result = vkCreateFramebuffer (device, &createInfo, 0, &framebuffer); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateFramebuffer failed"; + return 0; + + } + + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)framebuffer); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + + } + + + void lime_vk_destroy_framebuffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int framebufferHigh, + int framebufferLow) { + +#ifdef LIME_VULKAN + if (framebufferHigh == 0 && framebufferLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkFramebuffer framebuffer = (VkFramebuffer)(uintptr_t)CombineVulkanHandle (framebufferHigh, framebufferLow); + PFN_vkDestroyFramebuffer vkDestroyFramebuffer = (PFN_vkDestroyFramebuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroyFramebuffer"); + if (vkDestroyFramebuffer) { + + vkDestroyFramebuffer (device, framebuffer, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + HL_PRIM void HL_NAME(hl_vk_destroy_framebuffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int framebufferHigh, int framebufferLow) { + +#ifdef LIME_VULKAN + if (framebufferHigh == 0 && framebufferLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkFramebuffer framebuffer = (VkFramebuffer)(uintptr_t)CombineVulkanHandle (framebufferHigh, framebufferLow); + PFN_vkDestroyFramebuffer vkDestroyFramebuffer = (PFN_vkDestroyFramebuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroyFramebuffer"); + if (vkDestroyFramebuffer) { + + vkDestroyFramebuffer (device, framebuffer, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + + } + + + value lime_vk_create_shader_module (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, value bytes, int byteOffset, + int byteLength) { - Font *font = new Font (&resource, 0); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + Bytes data (bytes); + if (!data.b || byteOffset < 0 || byteLength <= 0 || byteOffset + byteLength > data.length || (byteLength % 4) != 0) { - if (font) { + lastVKError = "Invalid Vulkan shader module bytes"; + return alloc_null (); - if (font->face) { + } - return CFFIPointer (font, gc_font); + PFN_vkCreateShaderModule vkCreateShaderModule = (PFN_vkCreateShaderModule)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCreateShaderModule"); + if (!vkCreateShaderModule) return alloc_null (); - } else { + VkShaderModuleCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = (size_t)byteLength; + createInfo.pCode = (const uint32_t*)(data.b + byteOffset); - delete font; + VkShaderModule shaderModule = VK_NULL_HANDLE; + VkResult result = vkCreateShaderModule (device, &createInfo, 0, &shaderModule); + if (result != VK_SUCCESS) { - } + lastVKError = "vkCreateShaderModule failed"; + return alloc_null (); } - #endif + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)shaderModule); +#else + lastVKError = "Lime was built without lime-vulkan support"; return alloc_null (); +#endif } - HL_PRIM HL_CFFIPointer* HL_NAME(hl_font_load_bytes) (Bytes* data) { - - #ifdef LIME_FREETYPE - Resource resource = Resource (data); + HL_PRIM vdynamic* HL_NAME(hl_vk_create_shader_module) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, Bytes* bytes, int byteOffset, int byteLength) { - Font *font = new Font (&resource, 0); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + if (!bytes || !bytes->b || byteOffset < 0 || byteLength <= 0 || byteOffset + byteLength > bytes->length || (byteLength % 4) != 0) { - if (font) { + lastVKError = "Invalid Vulkan shader module bytes"; + return 0; - if (font->face) { + } - return HLCFFIPointer (font, (hl_finalizer)hl_gc_font); + PFN_vkCreateShaderModule vkCreateShaderModule = (PFN_vkCreateShaderModule)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCreateShaderModule"); + if (!vkCreateShaderModule) return 0; - } else { + VkShaderModuleCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = (size_t)byteLength; + createInfo.pCode = (const uint32_t*)(bytes->b + byteOffset); - delete font; + VkShaderModule shaderModule = VK_NULL_HANDLE; + VkResult result = vkCreateShaderModule (device, &createInfo, 0, &shaderModule); + if (result != VK_SUCCESS) { - } + lastVKError = "vkCreateShaderModule failed"; + return 0; } - #endif + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)shaderModule); +#else + lastVKError = "Lime was built without lime-vulkan support"; return 0; +#endif } - value lime_font_load_file (value data) { + void lime_vk_destroy_shader_module (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int shaderModuleHigh, + int shaderModuleLow) { - #ifdef LIME_FREETYPE - Resource resource = Resource (val_string (data)); +#ifdef LIME_VULKAN + if (shaderModuleHigh == 0 && shaderModuleLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkShaderModule shaderModule = (VkShaderModule)(uintptr_t)CombineVulkanHandle (shaderModuleHigh, shaderModuleLow); + PFN_vkDestroyShaderModule vkDestroyShaderModule = (PFN_vkDestroyShaderModule)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroyShaderModule"); + if (vkDestroyShaderModule) { - Font *font = new Font (&resource, 0); + vkDestroyShaderModule (device, shaderModule, 0); + lastVKError.clear (); - if (font) { + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif - if (font->face) { + } - return CFFIPointer (font, gc_font); - } else { + HL_PRIM void HL_NAME(hl_vk_destroy_shader_module) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int shaderModuleHigh, int shaderModuleLow) { - delete font; +#ifdef LIME_VULKAN + if (shaderModuleHigh == 0 && shaderModuleLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkShaderModule shaderModule = (VkShaderModule)(uintptr_t)CombineVulkanHandle (shaderModuleHigh, shaderModuleLow); + PFN_vkDestroyShaderModule vkDestroyShaderModule = (PFN_vkDestroyShaderModule)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroyShaderModule"); + if (vkDestroyShaderModule) { - } + vkDestroyShaderModule (device, shaderModule, 0); + lastVKError.clear (); } - #endif - - return alloc_null (); +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif } - HL_PRIM HL_CFFIPointer* HL_NAME(hl_font_load_file) (hl_vstring* data) { - - #ifdef LIME_FREETYPE - Resource resource = Resource (data ? hl_to_utf8 ((const uchar*)data->bytes) : NULL); + value lime_vk_create_sampler (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int filter, int addressMode, + int mipmapMode, bool anisotropyEnable, double maxAnisotropy, int compareOp, double minLod, double maxLod) { - Font *font = new Font (&resource, 0); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateSampler vkCreateSampler = (PFN_vkCreateSampler)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateSampler"); + if (!vkCreateSampler) return alloc_null (); + + VkSamplerCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + createInfo.magFilter = (VkFilter)filter; + createInfo.minFilter = (VkFilter)filter; + createInfo.mipmapMode = (VkSamplerMipmapMode)mipmapMode; + createInfo.addressModeU = (VkSamplerAddressMode)addressMode; + createInfo.addressModeV = (VkSamplerAddressMode)addressMode; + createInfo.addressModeW = (VkSamplerAddressMode)addressMode; + createInfo.anisotropyEnable = anisotropyEnable ? VK_TRUE : VK_FALSE; + createInfo.maxAnisotropy = (float)maxAnisotropy; + createInfo.compareEnable = compareOp >= 0 ? VK_TRUE : VK_FALSE; + createInfo.compareOp = compareOp >= 0 ? (VkCompareOp)compareOp : VK_COMPARE_OP_ALWAYS; + createInfo.minLod = (float)minLod; + createInfo.maxLod = (float)maxLod; + createInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + + VkSampler sampler = VK_NULL_HANDLE; + VkResult result = vkCreateSampler (device, &createInfo, 0, &sampler); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateSampler failed"; + return alloc_null (); - if (font) { + } - if (font->face) { + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)sampler); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif - return HLCFFIPointer (font, (hl_finalizer)hl_gc_font); + } - } else { - delete font; + HL_PRIM vdynamic* HL_NAME(hl_vk_create_sampler) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int filter, int addressMode, int mipmapMode, bool anisotropyEnable, double maxAnisotropy, int compareOp, double minLod, double maxLod) { - } +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreateSampler vkCreateSampler = (PFN_vkCreateSampler)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateSampler"); + if (!vkCreateSampler) return 0; + + VkSamplerCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + createInfo.magFilter = (VkFilter)filter; + createInfo.minFilter = (VkFilter)filter; + createInfo.mipmapMode = (VkSamplerMipmapMode)mipmapMode; + createInfo.addressModeU = (VkSamplerAddressMode)addressMode; + createInfo.addressModeV = (VkSamplerAddressMode)addressMode; + createInfo.addressModeW = (VkSamplerAddressMode)addressMode; + createInfo.anisotropyEnable = anisotropyEnable ? VK_TRUE : VK_FALSE; + createInfo.maxAnisotropy = (float)maxAnisotropy; + createInfo.compareEnable = compareOp >= 0 ? VK_TRUE : VK_FALSE; + createInfo.compareOp = compareOp >= 0 ? (VkCompareOp)compareOp : VK_COMPARE_OP_ALWAYS; + createInfo.minLod = (float)minLod; + createInfo.maxLod = (float)maxLod; + createInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + + VkSampler sampler = VK_NULL_HANDLE; + VkResult result = vkCreateSampler (device, &createInfo, 0, &sampler); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateSampler failed"; + return 0; } - #endif + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)sampler); +#else + lastVKError = "Lime was built without lime-vulkan support"; return 0; +#endif } - value lime_font_load (value data) { - - if (val_is_string (data)) { - - return lime_font_load_file (data); + void lime_vk_destroy_sampler (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int samplerHigh, int samplerLow) { - } else { +#ifdef LIME_VULKAN + if (samplerHigh == 0 && samplerLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkSampler sampler = (VkSampler)(uintptr_t)CombineVulkanHandle (samplerHigh, samplerLow); + PFN_vkDestroySampler vkDestroySampler = (PFN_vkDestroySampler)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroySampler"); + if (vkDestroySampler) { - return lime_font_load_bytes (data); + vkDestroySampler (device, sampler, 0); + lastVKError.clear (); } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif } - value lime_font_outline_decompose (value fontHandle, int size) { + HL_PRIM void HL_NAME(hl_vk_destroy_sampler) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int samplerHigh, int samplerLow) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return (value)font->Decompose (true, size); - #else - return alloc_null (); - #endif +#ifdef LIME_VULKAN + if (samplerHigh == 0 && samplerLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkSampler sampler = (VkSampler)(uintptr_t)CombineVulkanHandle (samplerHigh, samplerLow); + PFN_vkDestroySampler vkDestroySampler = (PFN_vkDestroySampler)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroySampler"); + if (vkDestroySampler) { - } + vkDestroySampler (device, sampler, 0); + lastVKError.clear (); + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif - value lime_font_outline_decompose_no_hint (value fontHandle, int size) { + } - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - return (value)font->Decompose (true, size, false); - #else - return alloc_null (); - #endif - } + value lime_vk_create_descriptor_set_layout (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, value bindings) { +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + std::vector packed = GetVulkanIntVector (bindings); + if (packed.size () < 2 || packed.size () < (size_t)(2 + packed[1] * 4)) return alloc_null (); + + std::vector nativeBindings; + nativeBindings.reserve ((size_t)packed[1]); + for (int i = 0; i < packed[1]; ++i) { + + int base = 2 + i * 4; + VkDescriptorSetLayoutBinding binding; + memset (&binding, 0, sizeof (binding)); + binding.binding = (uint32_t)packed[base]; + binding.descriptorType = (VkDescriptorType)packed[base + 1]; + binding.descriptorCount = (uint32_t)packed[base + 2]; + binding.stageFlags = (VkShaderStageFlags)packed[base + 3]; + nativeBindings.push_back (binding); - HL_PRIM vdynamic* HL_NAME(hl_font_outline_decompose) (HL_CFFIPointer* fontHandle, int size) { + } - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return (vdynamic*)font->Decompose (false, size); - #else - return 0; - #endif + PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout = + (PFN_vkCreateDescriptorSetLayout)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateDescriptorSetLayout"); + if (!vkCreateDescriptorSetLayout) return alloc_null (); - } + VkDescriptorSetLayoutCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + createInfo.flags = (VkDescriptorSetLayoutCreateFlags)packed[0]; + createInfo.bindingCount = (uint32_t)nativeBindings.size (); + createInfo.pBindings = nativeBindings.empty () ? 0 : nativeBindings.data (); + VkDescriptorSetLayout layout = VK_NULL_HANDLE; + VkResult result = vkCreateDescriptorSetLayout (device, &createInfo, 0, &layout); + if (result != VK_SUCCESS) { - HL_PRIM vdynamic* HL_NAME(hl_font_outline_decompose_no_hint) (HL_CFFIPointer* fontHandle, int size) { + lastVKError = "vkCreateDescriptorSetLayout failed"; + return alloc_null (); - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - return (vdynamic*)font->Decompose (false, size, false); - #else - return 0; - #endif + } + + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)layout); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif } - value lime_font_render_glyph (value fontHandle, int index, value data) { + HL_PRIM vdynamic* HL_NAME(hl_vk_create_descriptor_set_layout) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, hl_varray* bindings) { - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - Bytes bytes (data); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + std::vector packed = GetHLVulkanIntVector (bindings); + if (packed.size () < 2 || packed.size () < (size_t)(2 + packed[1] * 4)) return 0; + + std::vector nativeBindings; + nativeBindings.reserve ((size_t)packed[1]); + for (int i = 0; i < packed[1]; ++i) { + + int base = 2 + i * 4; + VkDescriptorSetLayoutBinding binding; + memset (&binding, 0, sizeof (binding)); + binding.binding = (uint32_t)packed[base]; + binding.descriptorType = (VkDescriptorType)packed[base + 1]; + binding.descriptorCount = (uint32_t)packed[base + 2]; + binding.stageFlags = (VkShaderStageFlags)packed[base + 3]; + nativeBindings.push_back (binding); - if (font->RenderGlyph (index, &bytes)) { + } - return bytes.Value (data); + PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout = + (PFN_vkCreateDescriptorSetLayout)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateDescriptorSetLayout"); + if (!vkCreateDescriptorSetLayout) return 0; + + VkDescriptorSetLayoutCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + createInfo.flags = (VkDescriptorSetLayoutCreateFlags)packed[0]; + createInfo.bindingCount = (uint32_t)nativeBindings.size (); + createInfo.pBindings = nativeBindings.empty () ? 0 : nativeBindings.data (); + + VkDescriptorSetLayout layout = VK_NULL_HANDLE; + VkResult result = vkCreateDescriptorSetLayout (device, &createInfo, 0, &layout); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateDescriptorSetLayout failed"; + return 0; } - #endif - return alloc_null (); + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)layout); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif } - value lime_font_render_glyph_with_flags (value fontHandle, int index, int loadFlags, value data) { - - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - Bytes bytes (data); + void lime_vk_destroy_descriptor_set_layout (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int layoutHigh, + int layoutLow) { - if (font->RenderGlyphWithFlags (index, loadFlags, &bytes)) { +#ifdef LIME_VULKAN + if (layoutHigh == 0 && layoutLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDescriptorSetLayout layout = (VkDescriptorSetLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout = + (PFN_vkDestroyDescriptorSetLayout)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyDescriptorSetLayout"); + if (vkDestroyDescriptorSetLayout) { - return bytes.Value (data); + vkDestroyDescriptorSetLayout (device, layout, 0); + lastVKError.clear (); } - #endif - - return alloc_null (); +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif } - HL_PRIM Bytes* HL_NAME(hl_font_render_glyph) (HL_CFFIPointer* fontHandle, int index, Bytes* data) { - - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; + HL_PRIM void HL_NAME(hl_vk_destroy_descriptor_set_layout) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int layoutHigh, int layoutLow) { - if (font->RenderGlyph (index, data)) { +#ifdef LIME_VULKAN + if (layoutHigh == 0 && layoutLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDescriptorSetLayout layout = (VkDescriptorSetLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout = + (PFN_vkDestroyDescriptorSetLayout)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyDescriptorSetLayout"); + if (vkDestroyDescriptorSetLayout) { - return data; + vkDestroyDescriptorSetLayout (device, layout, 0); + lastVKError.clear (); } - #endif - - return NULL; +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif } - HL_PRIM Bytes* HL_NAME(hl_font_render_glyph_with_flags) (HL_CFFIPointer* fontHandle, int index, int loadFlags, Bytes* data) { + value lime_vk_create_descriptor_pool (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, value poolSizes) { - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + std::vector packed = GetVulkanIntVector (poolSizes); + if (packed.size () < 3 || packed.size () < (size_t)(3 + packed[2] * 2)) return alloc_null (); - if (font->RenderGlyphWithFlags (index, loadFlags, data)) { + std::vector nativeSizes; + nativeSizes.reserve ((size_t)packed[2]); + for (int i = 0; i < packed[2]; ++i) { - return data; + int base = 3 + i * 2; + VkDescriptorPoolSize size; + size.type = (VkDescriptorType)packed[base]; + size.descriptorCount = (uint32_t)packed[base + 1]; + nativeSizes.push_back (size); } - #endif - - return NULL; - } - - - value lime_font_render_glyphs (value fontHandle, value indices, value data) { + PFN_vkCreateDescriptorPool vkCreateDescriptorPool = + (PFN_vkCreateDescriptorPool)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateDescriptorPool"); + if (!vkCreateDescriptorPool) return alloc_null (); - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - Bytes bytes (data); + VkDescriptorPoolCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + createInfo.flags = (VkDescriptorPoolCreateFlags)packed[0]; + createInfo.maxSets = (uint32_t)packed[1]; + createInfo.poolSizeCount = (uint32_t)nativeSizes.size (); + createInfo.pPoolSizes = nativeSizes.empty () ? 0 : nativeSizes.data (); - if (font->RenderGlyphs (indices, &bytes)) { + VkDescriptorPool pool = VK_NULL_HANDLE; + VkResult result = vkCreateDescriptorPool (device, &createInfo, 0, &pool); + if (result != VK_SUCCESS) { - return bytes.Value (data); + lastVKError = "vkCreateDescriptorPool failed"; + return alloc_null (); } - #endif + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)pool); +#else + lastVKError = "Lime was built without lime-vulkan support"; return alloc_null (); +#endif } - HL_PRIM Bytes* HL_NAME(hl_font_render_glyphs) (HL_CFFIPointer* fontHandle, hl_varray* indices, Bytes* data) { + HL_PRIM vdynamic* HL_NAME(hl_vk_create_descriptor_pool) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, hl_varray* poolSizes) { - // #ifdef LIME_FREETYPE - // Font *font = (Font*)fontHandle->ptr; - // return font->RenderGlyphs (indices, &bytes); - // #else - return NULL; - // #endif +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + std::vector packed = GetHLVulkanIntVector (poolSizes); + if (packed.size () < 3 || packed.size () < (size_t)(3 + packed[2] * 2)) return 0; - } + std::vector nativeSizes; + nativeSizes.reserve ((size_t)packed[2]); + for (int i = 0; i < packed[2]; ++i) { + int base = 3 + i * 2; + VkDescriptorPoolSize size; + size.type = (VkDescriptorType)packed[base]; + size.descriptorCount = (uint32_t)packed[base + 1]; + nativeSizes.push_back (size); - void lime_font_set_size (value fontHandle, int fontSize, int dpi) { + } - #ifdef LIME_FREETYPE - Font *font = (Font*)val_data (fontHandle); - font->SetSize (fontSize, dpi); - #endif + PFN_vkCreateDescriptorPool vkCreateDescriptorPool = + (PFN_vkCreateDescriptorPool)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateDescriptorPool"); + if (!vkCreateDescriptorPool) return 0; - } + VkDescriptorPoolCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + createInfo.flags = (VkDescriptorPoolCreateFlags)packed[0]; + createInfo.maxSets = (uint32_t)packed[1]; + createInfo.poolSizeCount = (uint32_t)nativeSizes.size (); + createInfo.pPoolSizes = nativeSizes.empty () ? 0 : nativeSizes.data (); + VkDescriptorPool pool = VK_NULL_HANDLE; + VkResult result = vkCreateDescriptorPool (device, &createInfo, 0, &pool); + if (result != VK_SUCCESS) { - HL_PRIM void HL_NAME(hl_font_set_size) (HL_CFFIPointer* fontHandle, int fontSize, int dpi) { + lastVKError = "vkCreateDescriptorPool failed"; + return 0; - #ifdef LIME_FREETYPE - Font *font = (Font*)fontHandle->ptr; - font->SetSize (fontSize, dpi); - #endif + } + + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)pool); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif } + HL_PRIM vbyte* HL_NAME(hl_vk_get_last_error) () { - void lime_gamepad_add_mappings (value mappings) { + return hl_copy_bytes ((const vbyte*)lastVKError.c_str (), (int)lastVKError.size () + 1); - int length = val_array_size (mappings); + } - for (int i = 0; i < length; i++) { - Gamepad::AddMapping (val_string (val_array_i (mappings, i))); + void lime_vk_destroy_descriptor_pool (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int poolHigh, int poolLow) { + +#ifdef LIME_VULKAN + if (poolHigh == 0 && poolLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDescriptorPool pool = (VkDescriptorPool)(uintptr_t)CombineVulkanHandle (poolHigh, poolLow); + PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool = + (PFN_vkDestroyDescriptorPool)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyDescriptorPool"); + if (vkDestroyDescriptorPool) { + + vkDestroyDescriptorPool (device, pool, 0); + lastVKError.clear (); } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif } - HL_PRIM void HL_NAME(hl_gamepad_add_mappings) (hl_varray* mappings) { - - int length = mappings->size; - hl_vstring** mappingsData = hl_aptr (mappings, hl_vstring*); + HL_PRIM void HL_NAME(hl_vk_destroy_descriptor_pool) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int poolHigh, int poolLow) { - for (int i = 0; i < length; i++) { +#ifdef LIME_VULKAN + if (poolHigh == 0 && poolLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDescriptorPool pool = (VkDescriptorPool)(uintptr_t)CombineVulkanHandle (poolHigh, poolLow); + PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool = + (PFN_vkDestroyDescriptorPool)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyDescriptorPool"); + if (vkDestroyDescriptorPool) { - Gamepad::AddMapping (hl_to_utf8 ((const uchar*)((*mappingsData++)->bytes))); + vkDestroyDescriptorPool (device, pool, 0); + lastVKError.clear (); } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif } - void lime_gamepad_event_manager_register (value callback, value eventObject) { - - GamepadEvent::callback = new ValuePointer (callback); - GamepadEvent::eventObject = new ValuePointer (eventObject); - - } + bool lime_vk_reset_descriptor_pool (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int poolHigh, int poolLow, + int flags) { +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDescriptorPool pool = (VkDescriptorPool)(uintptr_t)CombineVulkanHandle (poolHigh, poolLow); + PFN_vkResetDescriptorPool vkResetDescriptorPool = + (PFN_vkResetDescriptorPool)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkResetDescriptorPool"); + if (!vkResetDescriptorPool || !pool) return false; - HL_PRIM void HL_NAME(hl_gamepad_event_manager_register) (vclosure* callback, GamepadEvent* eventObject) { + VkResult result = vkResetDescriptorPool (device, pool, (VkDescriptorPoolResetFlags)flags); + if (result != VK_SUCCESS) { - GamepadEvent::callback = new ValuePointer (callback); - GamepadEvent::eventObject = new ValuePointer ((vobj*)eventObject); + lastVKError = "vkResetDescriptorPool failed"; + return false; - } + } + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif - value lime_gamepad_get_device_guid (int id) { + } - const char* guid = Gamepad::GetDeviceGUID (id); - if (guid) { + HL_PRIM bool HL_NAME(hl_vk_reset_descriptor_pool) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int poolHigh, int poolLow, int flags) { - value result = alloc_string (guid); - delete guid; - return result; +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDescriptorPool pool = (VkDescriptorPool)(uintptr_t)CombineVulkanHandle (poolHigh, poolLow); + PFN_vkResetDescriptorPool vkResetDescriptorPool = + (PFN_vkResetDescriptorPool)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkResetDescriptorPool"); + if (!vkResetDescriptorPool || !pool) return false; - } else { + VkResult result = vkResetDescriptorPool (device, pool, (VkDescriptorPoolResetFlags)flags); + if (result != VK_SUCCESS) { - return alloc_null (); + lastVKError = "vkResetDescriptorPool failed"; + return false; } + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif + } - HL_PRIM vbyte* HL_NAME(hl_gamepad_get_device_guid) (int id) { + value lime_vk_allocate_descriptor_set (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int poolHigh, + int poolLow, int layoutHigh, int layoutLow) { - const char* guid = Gamepad::GetDeviceGUID (id); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDescriptorPool pool = (VkDescriptorPool)(uintptr_t)CombineVulkanHandle (poolHigh, poolLow); + VkDescriptorSetLayout layout = (VkDescriptorSetLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets = + (PFN_vkAllocateDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkAllocateDescriptorSets"); + if (!vkAllocateDescriptorSets || !pool || !layout) return alloc_null (); + + VkDescriptorSetAllocateInfo allocateInfo; + memset (&allocateInfo, 0, sizeof (allocateInfo)); + allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocateInfo.descriptorPool = pool; + allocateInfo.descriptorSetCount = 1; + allocateInfo.pSetLayouts = &layout; + + VkDescriptorSet descriptorSet = VK_NULL_HANDLE; + VkResult result = vkAllocateDescriptorSets (device, &allocateInfo, &descriptorSet); + if (result != VK_SUCCESS) { + + lastVKError = "vkAllocateDescriptorSets failed"; + return alloc_null (); - if (guid) { + } - return (vbyte*)guid; + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)descriptorSet); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif + + } - } else { + HL_PRIM vdynamic* HL_NAME(hl_vk_allocate_descriptor_set) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int poolHigh, int poolLow, int layoutHigh, int layoutLow) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDescriptorPool pool = (VkDescriptorPool)(uintptr_t)CombineVulkanHandle (poolHigh, poolLow); + VkDescriptorSetLayout layout = (VkDescriptorSetLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets = + (PFN_vkAllocateDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkAllocateDescriptorSets"); + if (!vkAllocateDescriptorSets || !pool || !layout) return 0; + + VkDescriptorSetAllocateInfo allocateInfo; + memset (&allocateInfo, 0, sizeof (allocateInfo)); + allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocateInfo.descriptorPool = pool; + allocateInfo.descriptorSetCount = 1; + allocateInfo.pSetLayouts = &layout; + + VkDescriptorSet descriptorSet = VK_NULL_HANDLE; + VkResult result = vkAllocateDescriptorSets (device, &allocateInfo, &descriptorSet); + if (result != VK_SUCCESS) { + + lastVKError = "vkAllocateDescriptorSets failed"; return 0; } + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)descriptorSet); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif + } - value lime_gamepad_get_device_name (int id) { + bool lime_vk_update_descriptor_set_image (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int setHigh, + int setLow, int binding, int descriptorType, int imageViewHigh, int imageViewLow, int samplerHigh, int samplerLow, int imageLayout) { - const char* name = Gamepad::GetDeviceName (id); - return name ? alloc_string (name) : alloc_null (); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDescriptorSet descriptorSet = (VkDescriptorSet)(uintptr_t)CombineVulkanHandle (setHigh, setLow); + PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets = + (PFN_vkUpdateDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUpdateDescriptorSets"); + if (!vkUpdateDescriptorSets || !descriptorSet) return false; + + VkDescriptorImageInfo imageInfo; + memset (&imageInfo, 0, sizeof (imageInfo)); + imageInfo.sampler = (VkSampler)(uintptr_t)CombineVulkanHandle (samplerHigh, samplerLow); + imageInfo.imageView = (VkImageView)(uintptr_t)CombineVulkanHandle (imageViewHigh, imageViewLow); + imageInfo.imageLayout = (VkImageLayout)imageLayout; + + VkWriteDescriptorSet write; + memset (&write, 0, sizeof (write)); + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = descriptorSet; + write.dstBinding = (uint32_t)binding; + write.descriptorCount = 1; + write.descriptorType = (VkDescriptorType)descriptorType; + write.pImageInfo = &imageInfo; + vkUpdateDescriptorSets (device, 1, &write, 0, 0); + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM vbyte* HL_NAME(hl_gamepad_get_device_name) (int id) { + HL_PRIM bool HL_NAME(hl_vk_update_descriptor_set_image) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int setHigh, int setLow, int binding, int descriptorType, int imageViewHigh, int imageViewLow, int samplerHigh, + int samplerLow, int imageLayout) { - return (vbyte*)Gamepad::GetDeviceName (id); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDescriptorSet descriptorSet = (VkDescriptorSet)(uintptr_t)CombineVulkanHandle (setHigh, setLow); + PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets = + (PFN_vkUpdateDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUpdateDescriptorSets"); + if (!vkUpdateDescriptorSets || !descriptorSet) return false; + + VkDescriptorImageInfo imageInfo; + memset (&imageInfo, 0, sizeof (imageInfo)); + imageInfo.sampler = (VkSampler)(uintptr_t)CombineVulkanHandle (samplerHigh, samplerLow); + imageInfo.imageView = (VkImageView)(uintptr_t)CombineVulkanHandle (imageViewHigh, imageViewLow); + imageInfo.imageLayout = (VkImageLayout)imageLayout; + + VkWriteDescriptorSet write; + memset (&write, 0, sizeof (write)); + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = descriptorSet; + write.dstBinding = (uint32_t)binding; + write.descriptorCount = 1; + write.descriptorType = (VkDescriptorType)descriptorType; + write.pImageInfo = &imageInfo; + vkUpdateDescriptorSets (device, 1, &write, 0, 0); + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } +#ifdef LIME_VULKAN + value lime_vulkan_renderer_create (value window, HxString applicationName) { - void lime_gamepad_rumble (int id, double lowFrequencyRumble, double highFrequencyRumble, int duration) { - - Gamepad::Rumble (id, lowFrequencyRumble, highFrequencyRumble, duration); + Window* targetWindow = (Window*)val_data (window); + VulkanRenderer* renderer = new VulkanRenderer (targetWindow); - } + if (!renderer->Create (applicationName.c_str () ? hxs_utf8 (applicationName, nullptr) : 0)) { + lastVulkanRendererError = renderer->GetLastError (); + delete renderer; + return alloc_null (); - HL_PRIM void HL_NAME(hl_gamepad_rumble) (int id, double lowFrequencyRumble, double highFrequencyRumble, int duration) { + } - Gamepad::Rumble (id, lowFrequencyRumble, highFrequencyRumble, duration); + lastVulkanRendererError.clear (); + return CFFIPointer (renderer); } - value lime_gzip_compress (value buffer, value bytes) { - - #ifdef LIME_ZLIB - Bytes data (buffer); - Bytes result (bytes); - - Zlib::Compress (GZIP, &data, &result); + bool lime_vk_update_descriptor_set_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int setHigh, + int setLow, int binding, int descriptorType, int bufferHigh, int bufferLow, int offset, int range) { - return result.Value (bytes); - #else - return alloc_null (); - #endif +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDescriptorSet descriptorSet = (VkDescriptorSet)(uintptr_t)CombineVulkanHandle (setHigh, setLow); + PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets = + (PFN_vkUpdateDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUpdateDescriptorSets"); + if (!vkUpdateDescriptorSets || !descriptorSet) return false; + + VkDescriptorBufferInfo bufferInfo; + memset (&bufferInfo, 0, sizeof (bufferInfo)); + bufferInfo.buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + bufferInfo.offset = (VkDeviceSize)(uint32_t)offset; + bufferInfo.range = range > 0 ? (VkDeviceSize)(uint32_t)range : VK_WHOLE_SIZE; + + VkWriteDescriptorSet write; + memset (&write, 0, sizeof (write)); + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = descriptorSet; + write.dstBinding = (uint32_t)binding; + write.descriptorCount = 1; + write.descriptorType = (VkDescriptorType)descriptorType; + write.pBufferInfo = &bufferInfo; + vkUpdateDescriptorSets (device, 1, &write, 0, 0); + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM Bytes* HL_NAME(hl_gzip_compress) (Bytes* buffer, Bytes* bytes) { + HL_PRIM bool HL_NAME(hl_vk_update_descriptor_set_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int setHigh, int setLow, int binding, int descriptorType, int bufferHigh, int bufferLow, int offset, int range) { - #ifdef LIME_ZLIB - Zlib::Compress (GZIP, buffer, bytes); - return bytes; - #else - return 0; - #endif +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkDescriptorSet descriptorSet = (VkDescriptorSet)(uintptr_t)CombineVulkanHandle (setHigh, setLow); + PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets = + (PFN_vkUpdateDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkUpdateDescriptorSets"); + if (!vkUpdateDescriptorSets || !descriptorSet) return false; + + VkDescriptorBufferInfo bufferInfo; + memset (&bufferInfo, 0, sizeof (bufferInfo)); + bufferInfo.buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + bufferInfo.offset = (VkDeviceSize)(uint32_t)offset; + bufferInfo.range = range > 0 ? (VkDeviceSize)(uint32_t)range : VK_WHOLE_SIZE; + + VkWriteDescriptorSet write; + memset (&write, 0, sizeof (write)); + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = descriptorSet; + write.dstBinding = (uint32_t)binding; + write.descriptorCount = 1; + write.descriptorType = (VkDescriptorType)descriptorType; + write.pBufferInfo = &bufferInfo; + vkUpdateDescriptorSets (device, 1, &write, 0, 0); + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - value lime_gzip_decompress (value buffer, value bytes) { - - #ifdef LIME_ZLIB - Bytes data (buffer); - Bytes result (bytes); - - Zlib::Decompress (GZIP, &data, &result); + bool lime_vk_update_descriptor_sets (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, value writes) { - return result.Value (bytes); - #else - return alloc_null (); - #endif +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + return UpdateManagedVulkanDescriptorSets (targetWindow, instance, device, GetVulkanIntVector (writes)); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM Bytes* HL_NAME(hl_gzip_decompress) (Bytes* buffer, Bytes* bytes) { + HL_PRIM bool HL_NAME(hl_vk_update_descriptor_sets) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, hl_varray* writes) { - #ifdef LIME_ZLIB - Zlib::Decompress (GZIP, buffer, bytes); - return bytes; - #else - return 0; - #endif +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + return UpdateManagedVulkanDescriptorSets (targetWindow, instance, device, GetHLVulkanIntVector (writes)); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - void lime_haptic_vibrate (int period, int duration) { + value lime_vk_create_pipeline_layout (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, value setLayouts, + int pushConstantStages, int pushConstantSize) { - #ifdef IPHONE - Haptic::Vibrate (period, duration); - #endif +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + std::vector packed = GetVulkanIntVector (setLayouts); + if (packed.empty () || packed.size () < (size_t)(1 + packed[0] * 2)) return alloc_null (); - } + std::vector layouts; + layouts.reserve ((size_t)packed[0]); + for (int i = 0; i < packed[0]; ++i) { + layouts.push_back ((VkDescriptorSetLayout)(uintptr_t)CombineVulkanHandle (packed[1 + i * 2], packed[2 + i * 2])); - HL_PRIM void HL_NAME(hl_haptic_vibrate) (int period, int duration) { + } - #ifdef IPHONE - Haptic::Vibrate (period, duration); - #endif + VkPushConstantRange pushRange; + memset (&pushRange, 0, sizeof (pushRange)); + pushRange.stageFlags = (VkShaderStageFlags)pushConstantStages; + pushRange.offset = 0; + pushRange.size = (uint32_t)pushConstantSize; + + PFN_vkCreatePipelineLayout vkCreatePipelineLayout = + (PFN_vkCreatePipelineLayout)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreatePipelineLayout"); + if (!vkCreatePipelineLayout) return alloc_null (); + + VkPipelineLayoutCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + createInfo.setLayoutCount = (uint32_t)layouts.size (); + createInfo.pSetLayouts = layouts.empty () ? 0 : layouts.data (); + createInfo.pushConstantRangeCount = pushConstantSize > 0 ? 1 : 0; + createInfo.pPushConstantRanges = pushConstantSize > 0 ? &pushRange : 0; + + VkPipelineLayout layout = VK_NULL_HANDLE; + VkResult result = vkCreatePipelineLayout (device, &createInfo, 0, &layout); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreatePipelineLayout failed"; + return alloc_null (); + + } + + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)layout); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif } - value lime_image_encode (value buffer, int type, int quality, value bytes) { + HL_PRIM vdynamic* HL_NAME(hl_vk_create_pipeline_layout) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, hl_varray* setLayouts, int pushConstantStages, int pushConstantSize) { - ImageBuffer imageBuffer = ImageBuffer (buffer); - Bytes data = Bytes (bytes); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + std::vector packed = GetHLVulkanIntVector (setLayouts); + if (packed.empty () || packed.size () < (size_t)(1 + packed[0] * 2)) return 0; - switch (type) { + std::vector layouts; + layouts.reserve ((size_t)packed[0]); + for (int i = 0; i < packed[0]; ++i) { - case 0: + layouts.push_back ((VkDescriptorSetLayout)(uintptr_t)CombineVulkanHandle (packed[1 + i * 2], packed[2 + i * 2])); - #ifdef LIME_PNG - if (PNG::Encode (&imageBuffer, &data)) { + } - return data.Value (bytes); + VkPushConstantRange pushRange; + memset (&pushRange, 0, sizeof (pushRange)); + pushRange.stageFlags = (VkShaderStageFlags)pushConstantStages; + pushRange.offset = 0; + pushRange.size = (uint32_t)pushConstantSize; + + PFN_vkCreatePipelineLayout vkCreatePipelineLayout = + (PFN_vkCreatePipelineLayout)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreatePipelineLayout"); + if (!vkCreatePipelineLayout) return 0; + + VkPipelineLayoutCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + createInfo.setLayoutCount = (uint32_t)layouts.size (); + createInfo.pSetLayouts = layouts.empty () ? 0 : layouts.data (); + createInfo.pushConstantRangeCount = pushConstantSize > 0 ? 1 : 0; + createInfo.pPushConstantRanges = pushConstantSize > 0 ? &pushRange : 0; + + VkPipelineLayout layout = VK_NULL_HANDLE; + VkResult result = vkCreatePipelineLayout (device, &createInfo, 0, &layout); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreatePipelineLayout failed"; + return 0; - } - #endif - break; + } - case 1: + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)layout); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif - #ifdef LIME_JPEG - if (JPEG::Encode (&imageBuffer, &data, quality)) { + } - return data.Value (bytes); + HL_PRIM HL_CFFIPointer* HL_NAME(hl_vulkan_renderer_create) (HL_CFFIPointer* window, hl_vstring* applicationName) { - } - #endif - break; + Window* targetWindow = (Window*)window->ptr; + VulkanRenderer* renderer = new VulkanRenderer (targetWindow); + const char* applicationNameUTF8 = applicationName ? (const char*)hl_to_utf8 ((const uchar*)applicationName->bytes) : 0; - default: break; + if (!renderer->Create (applicationNameUTF8)) { + + lastVulkanRendererError = renderer->GetLastError (); + delete renderer; + return 0; } - return alloc_null (); + lastVulkanRendererError.clear (); + return HLCFFIPointer (renderer); } - HL_PRIM Bytes* HL_NAME(hl_image_encode) (ImageBuffer* buffer, int type, int quality, Bytes* bytes) { - - switch (type) { - - case 0: + void lime_vk_destroy_pipeline_layout (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int layoutHigh, + int layoutLow) { - #ifdef LIME_PNG - if (PNG::Encode (buffer, bytes)) { +#ifdef LIME_VULKAN + if (layoutHigh == 0 && layoutLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkPipelineLayout layout = (VkPipelineLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout = + (PFN_vkDestroyPipelineLayout)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyPipelineLayout"); + if (vkDestroyPipelineLayout) { - return bytes; + vkDestroyPipelineLayout (device, layout, 0); + lastVKError.clear (); - } - #endif - break; + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif - case 1: + } - #ifdef LIME_JPEG - if (JPEG::Encode (buffer, bytes, quality)) { - return bytes; + HL_PRIM void HL_NAME(hl_vk_destroy_pipeline_layout) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int layoutHigh, int layoutLow) { - } - #endif - break; +#ifdef LIME_VULKAN + if (layoutHigh == 0 && layoutLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkPipelineLayout layout = (VkPipelineLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout = + (PFN_vkDestroyPipelineLayout)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyPipelineLayout"); + if (vkDestroyPipelineLayout) { - default: break; + vkDestroyPipelineLayout (device, layout, 0); + lastVKError.clear (); } - - return 0; +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif } - value lime_image_load_bytes (value data, value buffer) { + value lime_vk_create_pipeline_cache (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, value bytes, int byteOffset, + int byteLength) { - Resource resource; - Bytes bytes; +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreatePipelineCache vkCreatePipelineCache = + (PFN_vkCreatePipelineCache)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreatePipelineCache"); + if (!vkCreatePipelineCache) return alloc_null (); - ImageBuffer imageBuffer = ImageBuffer (buffer); + VkPipelineCacheCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + Bytes initialData (bytes); + if (initialData.b && byteLength > 0) { - bytes.Set (data); - resource = Resource (&bytes); + if (byteOffset < 0 || byteOffset + byteLength > initialData.length) { - #ifdef LIME_PNG - if (PNG::Decode (&resource, &imageBuffer)) { + lastVKError = "Invalid Vulkan pipeline cache data range"; + return alloc_null (); - return imageBuffer.Value (buffer); + } + + createInfo.initialDataSize = (size_t)byteLength; + createInfo.pInitialData = initialData.b + byteOffset; } - #endif - #ifdef LIME_JPEG - if (JPEG::Decode (&resource, &imageBuffer)) { + VkPipelineCache cache = VK_NULL_HANDLE; + VkResult result = vkCreatePipelineCache (device, &createInfo, 0, &cache); + if (result != VK_SUCCESS) { - return imageBuffer.Value (buffer); + lastVKError = "vkCreatePipelineCache failed"; + return alloc_null (); } - #endif + lastVKError.clear (); + return CreateVulkanHandleValue ((uint64_t)(uintptr_t)cache); +#else + lastVKError = "Lime was built without lime-vulkan support"; return alloc_null (); +#endif } - HL_PRIM ImageBuffer* HL_NAME(hl_image_load_bytes) (Bytes* data, ImageBuffer* buffer) { + HL_PRIM vdynamic* HL_NAME(hl_vk_create_pipeline_cache) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, Bytes* bytes, int byteOffset, int byteLength) { - Resource resource = Resource (data); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCreatePipelineCache vkCreatePipelineCache = + (PFN_vkCreatePipelineCache)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreatePipelineCache"); + if (!vkCreatePipelineCache) return 0; - #ifdef LIME_PNG - if (PNG::Decode (&resource, buffer)) { + VkPipelineCacheCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + if (bytes && bytes->b && byteLength > 0) { - return buffer; + if (byteOffset < 0 || byteOffset + byteLength > bytes->length) { + + lastVKError = "Invalid Vulkan pipeline cache data range"; + return 0; + + } + + createInfo.initialDataSize = (size_t)byteLength; + createInfo.pInitialData = bytes->b + byteOffset; } - #endif - #ifdef LIME_JPEG - if (JPEG::Decode (&resource, buffer)) { + VkPipelineCache cache = VK_NULL_HANDLE; + VkResult result = vkCreatePipelineCache (device, &createInfo, 0, &cache); + if (result != VK_SUCCESS) { - return buffer; + lastVKError = "vkCreatePipelineCache failed"; + return 0; } - #endif + lastVKError.clear (); + return HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)cache); +#else + lastVKError = "Lime was built without lime-vulkan support"; return 0; +#endif } - value lime_image_load_file (value data, value buffer) { + bool lime_vk_get_pipeline_cache_data (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int cacheHigh, + int cacheLow, value bytes) { - Resource resource = Resource (val_string (data)); - ImageBuffer imageBuffer = ImageBuffer (buffer); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkPipelineCache cache = (VkPipelineCache)(uintptr_t)CombineVulkanHandle (cacheHigh, cacheLow); + PFN_vkGetPipelineCacheData vkGetPipelineCacheData = + (PFN_vkGetPipelineCacheData)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkGetPipelineCacheData"); + if (!vkGetPipelineCacheData || !cache) return false; - #ifdef LIME_PNG - if (PNG::Decode (&resource, &imageBuffer)) { + size_t size = 0; + VkResult result = vkGetPipelineCacheData (device, cache, &size, 0); + if (result != VK_SUCCESS) { - return imageBuffer.Value (buffer); + lastVKError = "vkGetPipelineCacheData failed"; + return false; } - #endif - #ifdef LIME_JPEG - if (JPEG::Decode (&resource, &imageBuffer)) { + Bytes data (bytes); + data.Resize ((int)size); + if (size > 0 && !data.b) return false; - return imageBuffer.Value (buffer); + result = vkGetPipelineCacheData (device, cache, &size, data.b); + if (result != VK_SUCCESS) { + + lastVKError = "vkGetPipelineCacheData failed"; + return false; } - #endif - return alloc_null (); + data.Value (bytes); + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM ImageBuffer* HL_NAME(hl_image_load_file) (hl_vstring* data, ImageBuffer* buffer) { + HL_PRIM bool HL_NAME(hl_vk_get_pipeline_cache_data) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int cacheHigh, int cacheLow, Bytes* bytes) { - Resource resource = Resource (data); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkPipelineCache cache = (VkPipelineCache)(uintptr_t)CombineVulkanHandle (cacheHigh, cacheLow); + PFN_vkGetPipelineCacheData vkGetPipelineCacheData = + (PFN_vkGetPipelineCacheData)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkGetPipelineCacheData"); + if (!vkGetPipelineCacheData || !cache || !bytes) return false; - #ifdef LIME_PNG - if (PNG::Decode (&resource, buffer)) { + size_t size = 0; + VkResult result = vkGetPipelineCacheData (device, cache, &size, 0); + if (result != VK_SUCCESS) { - return buffer; + lastVKError = "vkGetPipelineCacheData failed"; + return false; } - #endif - #ifdef LIME_JPEG - if (JPEG::Decode (&resource, buffer)) { + bytes->Resize ((int)size); + if (size > 0 && !bytes->b) return false; - return buffer; + result = vkGetPipelineCacheData (device, cache, &size, bytes->b); + if (result != VK_SUCCESS) { + + lastVKError = "vkGetPipelineCacheData failed"; + return false; } - #endif - return 0; + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - value lime_image_load (value data, value buffer) { - - if (val_is_string (data)) { - - return lime_image_load_file (data, buffer); + void lime_vk_destroy_pipeline_cache (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int cacheHigh, + int cacheLow) { - } else { +#ifdef LIME_VULKAN + if (cacheHigh == 0 && cacheLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkPipelineCache cache = (VkPipelineCache)(uintptr_t)CombineVulkanHandle (cacheHigh, cacheLow); + PFN_vkDestroyPipelineCache vkDestroyPipelineCache = + (PFN_vkDestroyPipelineCache)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyPipelineCache"); + if (vkDestroyPipelineCache) { - return lime_image_load_bytes (data, buffer); + vkDestroyPipelineCache (device, cache, 0); + lastVKError.clear (); } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif } - void lime_image_data_util_color_transform (value image, value rect, value colorMatrix) { + HL_PRIM void HL_NAME(hl_vk_destroy_pipeline_cache) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int cacheHigh, int cacheLow) { - Image _image = Image (image); - Rectangle _rect = Rectangle (rect); - ColorMatrix _colorMatrix = ColorMatrix (colorMatrix); - ImageDataUtil::ColorTransform (&_image, &_rect, &_colorMatrix); +#ifdef LIME_VULKAN + if (cacheHigh == 0 && cacheLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkPipelineCache cache = (VkPipelineCache)(uintptr_t)CombineVulkanHandle (cacheHigh, cacheLow); + PFN_vkDestroyPipelineCache vkDestroyPipelineCache = + (PFN_vkDestroyPipelineCache)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkDestroyPipelineCache"); + if (vkDestroyPipelineCache) { - } + vkDestroyPipelineCache (device, cache, 0); + lastVKError.clear (); + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif - HL_PRIM void HL_NAME(hl_image_data_util_color_transform) (Image* image, Rectangle* rect, ArrayBufferView* colorMatrix) { + } - ColorMatrix _colorMatrix = ColorMatrix (colorMatrix); - ImageDataUtil::ColorTransform (image, rect, &_colorMatrix); + void lime_vulkan_renderer_destroy (value handle) { + + if (val_is_null (handle)) return; + VulkanRenderer* renderer = (VulkanRenderer*)val_data (handle); + delete renderer; } - void lime_image_data_util_copy_channel (value image, value sourceImage, value sourceRect, value destPoint, int srcChannel, int destChannel) { +#ifdef LIME_VULKAN + static VkPipeline CreateManagedVulkanGraphicsPipeline (Window* targetWindow, VkInstance instance, VkDevice device, VkRenderPass renderPass, + VkPipelineLayout layout, VkShaderModule vertexShader, VkShaderModule fragmentShader, const std::vector& state) { - Image _image = Image (image); - Image _sourceImage = Image (sourceImage); - Rectangle _sourceRect = Rectangle (sourceRect); - Vector2 _destPoint = Vector2 (destPoint); - ImageDataUtil::CopyChannel (&_image, &_sourceImage, &_sourceRect, &_destPoint, srcChannel, destChannel); + if (state.size () < 41 || !renderPass || !layout || !vertexShader || !fragmentShader) { - } + lastVKError = "Invalid Vulkan graphics pipeline state"; + return VK_NULL_HANDLE; + } - HL_PRIM void HL_NAME(hl_image_data_util_copy_channel) (Image* image, Image* sourceImage, Rectangle* sourceRect, Vector2* destPoint, int srcChannel, int destChannel) { + int cursor = 0; + VkPipelineCache cache = (VkPipelineCache)(uintptr_t)CombineVulkanHandle (state[cursor], state[cursor + 1]); + cursor += 2; + int topology = state[cursor++]; + bool primitiveRestart = state[cursor++] != 0; + int polygonMode = state[cursor++]; + int cullMode = state[cursor++]; + int frontFace = state[cursor++]; + bool depthTest = state[cursor++] != 0; + bool depthWrite = state[cursor++] != 0; + int depthCompareOp = state[cursor++]; + bool blend = state[cursor++] != 0; + int srcColorBlendFactor = state[cursor++]; + int dstColorBlendFactor = state[cursor++]; + int colorBlendOp = state[cursor++]; + int srcAlphaBlendFactor = state[cursor++]; + int dstAlphaBlendFactor = state[cursor++]; + int alphaBlendOp = state[cursor++]; + int dynamicStateFlags = state[cursor++]; + int colorWriteMask = state[cursor++]; + bool stencilTest = state[cursor++] != 0; + int frontFailOp = state[cursor++]; + int frontPassOp = state[cursor++]; + int frontDepthFailOp = state[cursor++]; + int frontCompareOp = state[cursor++]; + int frontCompareMask = state[cursor++]; + int frontWriteMask = state[cursor++]; + int frontReference = state[cursor++]; + int backFailOp = state[cursor++]; + int backPassOp = state[cursor++]; + int backDepthFailOp = state[cursor++]; + int backCompareOp = state[cursor++]; + int backCompareMask = state[cursor++]; + int backWriteMask = state[cursor++]; + int backReference = state[cursor++]; + int rasterizationSamples = state[cursor++]; + bool sampleShading = state[cursor++] != 0; + int minSampleShading = state[cursor++]; + int sampleMask = state[cursor++]; + bool alphaToCoverage = state[cursor++] != 0; + bool alphaToOne = state[cursor++] != 0; + int bindingCount = state[cursor++]; + + if ((int)state.size () < cursor + bindingCount * 3 + 1) { + + lastVKError = "Invalid Vulkan vertex binding state"; + return VK_NULL_HANDLE; - ImageDataUtil::CopyChannel (image, sourceImage, sourceRect, destPoint, srcChannel, destChannel); + } - } + std::vector bindings; + bindings.reserve ((size_t)bindingCount); + for (int i = 0; i < bindingCount; ++i) { + VkVertexInputBindingDescription binding; + binding.binding = (uint32_t)state[cursor++]; + binding.stride = (uint32_t)state[cursor++]; + binding.inputRate = (VkVertexInputRate)state[cursor++]; + bindings.push_back (binding); - void lime_image_data_util_copy_pixels (value image, value sourceImage, value sourceRect, value destPoint, value alphaImage, value alphaPoint, bool mergeAlpha) { + } - Image _image = Image (image); - Image _sourceImage = Image (sourceImage); - Rectangle _sourceRect = Rectangle (sourceRect); - Vector2 _destPoint = Vector2 (destPoint); + int attributeCount = state[cursor++]; + if ((int)state.size () < cursor + attributeCount * 4) { - if (val_is_null (alphaImage)) { + lastVKError = "Invalid Vulkan vertex attribute state"; + return VK_NULL_HANDLE; - ImageDataUtil::CopyPixels (&_image, &_sourceImage, &_sourceRect, &_destPoint, 0, 0, mergeAlpha); + } - } else { + std::vector attributes; + attributes.reserve ((size_t)attributeCount); + for (int i = 0; i < attributeCount; ++i) { - Image _alphaImage = Image (alphaImage); - Vector2 _alphaPoint = Vector2 (alphaPoint); + VkVertexInputAttributeDescription attribute; + attribute.location = (uint32_t)state[cursor++]; + attribute.binding = (uint32_t)state[cursor++]; + attribute.format = (VkFormat)state[cursor++]; + attribute.offset = (uint32_t)state[cursor++]; + attributes.push_back (attribute); - ImageDataUtil::CopyPixels (&_image, &_sourceImage, &_sourceRect, &_destPoint, &_alphaImage, &_alphaPoint, mergeAlpha); + } + + PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines = + (PFN_vkCreateGraphicsPipelines)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCreateGraphicsPipelines"); + if (!vkCreateGraphicsPipelines) return VK_NULL_HANDLE; + + VkPipelineShaderStageCreateInfo stages[2]; + memset (stages, 0, sizeof (stages)); + stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + stages[0].module = vertexShader; + stages[0].pName = "main"; + stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + stages[1].module = fragmentShader; + stages[1].pName = "main"; + + VkPipelineVertexInputStateCreateInfo vertexInput; + memset (&vertexInput, 0, sizeof (vertexInput)); + vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInput.vertexBindingDescriptionCount = (uint32_t)bindings.size (); + vertexInput.pVertexBindingDescriptions = bindings.empty () ? 0 : bindings.data (); + vertexInput.vertexAttributeDescriptionCount = (uint32_t)attributes.size (); + vertexInput.pVertexAttributeDescriptions = attributes.empty () ? 0 : attributes.data (); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly; + memset (&inputAssembly, 0, sizeof (inputAssembly)); + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = (VkPrimitiveTopology)topology; + inputAssembly.primitiveRestartEnable = primitiveRestart ? VK_TRUE : VK_FALSE; + + VkViewport viewport; + memset (&viewport, 0, sizeof (viewport)); + viewport.width = 1.0f; + viewport.height = 1.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor; + memset (&scissor, 0, sizeof (scissor)); + scissor.extent.width = 1; + scissor.extent.height = 1; + + VkPipelineViewportStateCreateInfo viewportState; + memset (&viewportState, 0, sizeof (viewportState)); + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterization; + memset (&rasterization, 0, sizeof (rasterization)); + rasterization.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterization.polygonMode = (VkPolygonMode)polygonMode; + rasterization.cullMode = (VkCullModeFlags)cullMode; + rasterization.frontFace = (VkFrontFace)frontFace; + rasterization.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo multisample; + memset (&multisample, 0, sizeof (multisample)); + multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisample.rasterizationSamples = (VkSampleCountFlagBits)rasterizationSamples; + multisample.sampleShadingEnable = sampleShading ? VK_TRUE : VK_FALSE; + multisample.minSampleShading = (float)minSampleShading / 1000000.0f; + VkSampleMask sampleMaskValue = (VkSampleMask)sampleMask; + multisample.pSampleMask = sampleMask != 0 ? &sampleMaskValue : 0; + multisample.alphaToCoverageEnable = alphaToCoverage ? VK_TRUE : VK_FALSE; + multisample.alphaToOneEnable = alphaToOne ? VK_TRUE : VK_FALSE; + + VkPipelineDepthStencilStateCreateInfo depthStencil; + memset (&depthStencil, 0, sizeof (depthStencil)); + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = depthTest ? VK_TRUE : VK_FALSE; + depthStencil.depthWriteEnable = depthWrite ? VK_TRUE : VK_FALSE; + depthStencil.depthCompareOp = (VkCompareOp)depthCompareOp; + depthStencil.stencilTestEnable = stencilTest ? VK_TRUE : VK_FALSE; + depthStencil.front.failOp = (VkStencilOp)frontFailOp; + depthStencil.front.passOp = (VkStencilOp)frontPassOp; + depthStencil.front.depthFailOp = (VkStencilOp)frontDepthFailOp; + depthStencil.front.compareOp = (VkCompareOp)frontCompareOp; + depthStencil.front.compareMask = (uint32_t)frontCompareMask; + depthStencil.front.writeMask = (uint32_t)frontWriteMask; + depthStencil.front.reference = (uint32_t)frontReference; + depthStencil.back.failOp = (VkStencilOp)backFailOp; + depthStencil.back.passOp = (VkStencilOp)backPassOp; + depthStencil.back.depthFailOp = (VkStencilOp)backDepthFailOp; + depthStencil.back.compareOp = (VkCompareOp)backCompareOp; + depthStencil.back.compareMask = (uint32_t)backCompareMask; + depthStencil.back.writeMask = (uint32_t)backWriteMask; + depthStencil.back.reference = (uint32_t)backReference; + + VkPipelineColorBlendAttachmentState colorAttachment; + memset (&colorAttachment, 0, sizeof (colorAttachment)); + colorAttachment.colorWriteMask = (VkColorComponentFlags)colorWriteMask; + colorAttachment.blendEnable = blend ? VK_TRUE : VK_FALSE; + colorAttachment.srcColorBlendFactor = (VkBlendFactor)srcColorBlendFactor; + colorAttachment.dstColorBlendFactor = (VkBlendFactor)dstColorBlendFactor; + colorAttachment.colorBlendOp = (VkBlendOp)colorBlendOp; + colorAttachment.srcAlphaBlendFactor = (VkBlendFactor)srcAlphaBlendFactor; + colorAttachment.dstAlphaBlendFactor = (VkBlendFactor)dstAlphaBlendFactor; + colorAttachment.alphaBlendOp = (VkBlendOp)alphaBlendOp; + + VkPipelineColorBlendStateCreateInfo colorBlend; + memset (&colorBlend, 0, sizeof (colorBlend)); + colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlend.attachmentCount = 1; + colorBlend.pAttachments = &colorAttachment; + + std::vector dynamicStates; + if (dynamicStateFlags & 0x1) dynamicStates.push_back (VK_DYNAMIC_STATE_VIEWPORT); + if (dynamicStateFlags & 0x2) dynamicStates.push_back (VK_DYNAMIC_STATE_SCISSOR); + VkPipelineDynamicStateCreateInfo dynamicState; + memset (&dynamicState, 0, sizeof (dynamicState)); + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = (uint32_t)dynamicStates.size (); + dynamicState.pDynamicStates = dynamicStates.empty () ? 0 : dynamicStates.data (); + + VkGraphicsPipelineCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + createInfo.stageCount = 2; + createInfo.pStages = stages; + createInfo.pVertexInputState = &vertexInput; + createInfo.pInputAssemblyState = &inputAssembly; + createInfo.pViewportState = &viewportState; + createInfo.pRasterizationState = &rasterization; + createInfo.pMultisampleState = &multisample; + createInfo.pDepthStencilState = &depthStencil; + createInfo.pColorBlendState = &colorBlend; + createInfo.pDynamicState = dynamicStates.empty () ? 0 : &dynamicState; + createInfo.layout = layout; + createInfo.renderPass = renderPass; + createInfo.subpass = 0; + + VkPipeline pipeline = VK_NULL_HANDLE; + VkResult result = vkCreateGraphicsPipelines (device, cache, 1, &createInfo, 0, &pipeline); + if (result != VK_SUCCESS) { + + lastVKError = "vkCreateGraphicsPipelines failed"; + return VK_NULL_HANDLE; } + lastVKError.clear (); + return pipeline; + } +#endif - HL_PRIM void HL_NAME(hl_image_data_util_copy_pixels) (Image* image, Image* sourceImage, Rectangle* sourceRect, Vector2* destPoint, Image* alphaImage, Vector2* alphaPoint, bool mergeAlpha) { - if (!alphaImage) { + value lime_vk_create_graphics_pipeline (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int renderPassHigh, + int renderPassLow, int layoutHigh, int layoutLow, int vertexShaderHigh, int vertexShaderLow, int fragmentShaderHigh, int fragmentShaderLow, + value state) { - ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, NULL, NULL, mergeAlpha); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkPipeline pipeline = CreateManagedVulkanGraphicsPipeline (targetWindow, instance, device, + (VkRenderPass)(uintptr_t)CombineVulkanHandle (renderPassHigh, renderPassLow), + (VkPipelineLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow), + (VkShaderModule)(uintptr_t)CombineVulkanHandle (vertexShaderHigh, vertexShaderLow), + (VkShaderModule)(uintptr_t)CombineVulkanHandle (fragmentShaderHigh, fragmentShaderLow), + GetVulkanIntVector (state)); + return pipeline ? CreateVulkanHandleValue ((uint64_t)(uintptr_t)pipeline) : alloc_null (); +#else + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); +#endif - } else { + } - if (!alphaPoint) { - Vector2 _alphaPoint = Vector2 (0, 0); + HL_PRIM vdynamic* HL_NAME(hl_vk_create_graphics_pipeline) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int renderPassHigh, int renderPassLow, int layoutHigh, int layoutLow, int vertexShaderHigh, int vertexShaderLow, + int fragmentShaderHigh, int fragmentShaderLow, hl_varray* state) { - ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, alphaImage, &_alphaPoint, mergeAlpha); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkPipeline pipeline = CreateManagedVulkanGraphicsPipeline (targetWindow, instance, device, + (VkRenderPass)(uintptr_t)CombineVulkanHandle (renderPassHigh, renderPassLow), + (VkPipelineLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow), + (VkShaderModule)(uintptr_t)CombineVulkanHandle (vertexShaderHigh, vertexShaderLow), + (VkShaderModule)(uintptr_t)CombineVulkanHandle (fragmentShaderHigh, fragmentShaderLow), + GetHLVulkanIntVector (state)); + return pipeline ? HLCreateVulkanHandleValue ((uint64_t)(uintptr_t)pipeline) : 0; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return 0; +#endif - } else { + } - ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, alphaImage, alphaPoint, mergeAlpha); - } + void lime_vk_destroy_pipeline (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int pipelineHigh, int pipelineLow) { + +#ifdef LIME_VULKAN + if (pipelineHigh == 0 && pipelineLow == 0) return; + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkPipeline pipeline = (VkPipeline)(uintptr_t)CombineVulkanHandle (pipelineHigh, pipelineLow); + PFN_vkDestroyPipeline vkDestroyPipeline = (PFN_vkDestroyPipeline)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroyPipeline"); + if (vkDestroyPipeline) { + + vkDestroyPipeline (device, pipeline, 0); + lastVKError.clear (); } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif } - void lime_image_data_util_fill_rect (value image, value rect, int rg, int ba) { + HL_PRIM void HL_NAME(hl_vk_destroy_pipeline) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int pipelineHigh, int pipelineLow) { - Image _image = Image (image); - Rectangle _rect = Rectangle (rect); - int32_t color = (rg << 16) | ba; - ImageDataUtil::FillRect (&_image, &_rect, color); +#ifdef LIME_VULKAN + if (pipelineHigh == 0 && pipelineLow == 0) return; + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkPipeline pipeline = (VkPipeline)(uintptr_t)CombineVulkanHandle (pipelineHigh, pipelineLow); + PFN_vkDestroyPipeline vkDestroyPipeline = (PFN_vkDestroyPipeline)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkDestroyPipeline"); + if (vkDestroyPipeline) { - } + vkDestroyPipeline (device, pipeline, 0); + lastVKError.clear (); + + } +#else + lastVKError = "Lime was built without lime-vulkan support"; +#endif + } - HL_PRIM void HL_NAME(hl_image_data_util_fill_rect) (Image* image, Rectangle* rect, int rg, int ba) { + HL_PRIM void HL_NAME(hl_vulkan_renderer_destroy) (HL_CFFIPointer* handle) { - int32_t color = (rg << 16) | ba; - ImageDataUtil::FillRect (image, rect, color); + if (!handle || !handle->ptr) return; + VulkanRenderer* renderer = (VulkanRenderer*)handle->ptr; + handle->ptr = 0; + delete renderer; } - void lime_image_data_util_flood_fill (value image, int x, int y, int rg, int ba) { + bool lime_vk_queue_submit_synced (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int queueHigh, + int queueLow, int commandBufferHigh, int commandBufferLow, value state) { - Image _image = Image (image); - int32_t color = (rg << 16) | ba; - ImageDataUtil::FloodFill (&_image, x, y, color); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkQueue queue = (VkQueue)(uintptr_t)CombineVulkanHandle (queueHigh, queueLow); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetVulkanIntVector (state); + if (packed.size () < 7) return false; + + VkSemaphore waitSemaphore = (VkSemaphore)(uintptr_t)CombineVulkanHandle (packed[0], packed[1]); + VkPipelineStageFlags waitStage = (VkPipelineStageFlags)packed[2]; + VkSemaphore signalSemaphore = (VkSemaphore)(uintptr_t)CombineVulkanHandle (packed[3], packed[4]); + VkFence fence = (VkFence)(uintptr_t)CombineVulkanHandle (packed[5], packed[6]); + PFN_vkQueueSubmit vkQueueSubmit = (PFN_vkQueueSubmit)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkQueueSubmit"); + if (!vkQueueSubmit || !queue || !commandBuffer) return false; + + VkSubmitInfo submitInfo; + memset (&submitInfo, 0, sizeof (submitInfo)); + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.waitSemaphoreCount = waitSemaphore ? 1 : 0; + submitInfo.pWaitSemaphores = waitSemaphore ? &waitSemaphore : 0; + submitInfo.pWaitDstStageMask = waitSemaphore ? &waitStage : 0; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + submitInfo.signalSemaphoreCount = signalSemaphore ? 1 : 0; + submitInfo.pSignalSemaphores = signalSemaphore ? &signalSemaphore : 0; + + VkResult result = vkQueueSubmit (queue, 1, &submitInfo, fence); + if (result != VK_SUCCESS) { + + char error[64]; + ::snprintf (error, sizeof (error), "vkQueueSubmit failed: %d", (int)result); + lastVKError = error; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM void HL_NAME(hl_image_data_util_flood_fill) (Image* image, int x, int y, int rg, int ba) { + HL_PRIM bool HL_NAME(hl_vk_queue_submit_synced) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int queueHigh, int queueLow, int commandBufferHigh, int commandBufferLow, hl_varray* state) { - int32_t color = (rg << 16) | ba; - ImageDataUtil::FloodFill (image, x, y, color); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + VkQueue queue = (VkQueue)(uintptr_t)CombineVulkanHandle (queueHigh, queueLow); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetHLVulkanIntVector (state); + if (packed.size () < 7) return false; + + VkSemaphore waitSemaphore = (VkSemaphore)(uintptr_t)CombineVulkanHandle (packed[0], packed[1]); + VkPipelineStageFlags waitStage = (VkPipelineStageFlags)packed[2]; + VkSemaphore signalSemaphore = (VkSemaphore)(uintptr_t)CombineVulkanHandle (packed[3], packed[4]); + VkFence fence = (VkFence)(uintptr_t)CombineVulkanHandle (packed[5], packed[6]); + PFN_vkQueueSubmit vkQueueSubmit = (PFN_vkQueueSubmit)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkQueueSubmit"); + if (!vkQueueSubmit || !queue || !commandBuffer) return false; + + VkSubmitInfo submitInfo; + memset (&submitInfo, 0, sizeof (submitInfo)); + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.waitSemaphoreCount = waitSemaphore ? 1 : 0; + submitInfo.pWaitSemaphores = waitSemaphore ? &waitSemaphore : 0; + submitInfo.pWaitDstStageMask = waitSemaphore ? &waitStage : 0; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + submitInfo.signalSemaphoreCount = signalSemaphore ? 1 : 0; + submitInfo.pSignalSemaphores = signalSemaphore ? &signalSemaphore : 0; + + VkResult result = vkQueueSubmit (queue, 1, &submitInfo, fence); + if (result != VK_SUCCESS) { + + char error[64]; + ::snprintf (error, sizeof (error), "vkQueueSubmit failed: %d", (int)result); + lastVKError = error; + return false; + + } + + lastVKError.clear (); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - void lime_image_data_util_get_pixels (value image, value rect, int format, value bytes) { + bool lime_vk_cmd_begin_render_pass (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int renderPassHigh, int renderPassLow, int framebufferHigh, int framebufferLow, value state, value clear) { - Image _image = Image (image); - Rectangle _rect = Rectangle (rect); - PixelFormat _format = (PixelFormat)format; - Bytes pixels = Bytes (bytes); - ImageDataUtil::GetPixels (&_image, &_rect, _format, &pixels); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass = (PFN_vkCmdBeginRenderPass)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCmdBeginRenderPass"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetVulkanIntVector (state); + std::vector clearValues = GetVulkanDoubleVector (clear); + if (!vkCmdBeginRenderPass || !commandBuffer || packed.size () < 5 || clearValues.size () < 5) return false; + uint32_t clearValueCount = packed.size () > 5 ? (uint32_t)packed[5] : 2; + if (clearValueCount < 1) clearValueCount = 1; + if (clearValueCount > 2) clearValueCount = 2; + + VkClearValue clears[2]; + memset (clears, 0, sizeof (clears)); + clears[0].color.float32[0] = (float)clearValues[0]; + clears[0].color.float32[1] = (float)clearValues[1]; + clears[0].color.float32[2] = (float)clearValues[2]; + clears[0].color.float32[3] = (float)clearValues[3]; + clears[1].depthStencil.depth = (float)clearValues[4]; + clears[1].depthStencil.stencil = (uint32_t)packed[4]; + + VkRenderPassBeginInfo beginInfo; + memset (&beginInfo, 0, sizeof (beginInfo)); + beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + beginInfo.renderPass = (VkRenderPass)(uintptr_t)CombineVulkanHandle (renderPassHigh, renderPassLow); + beginInfo.framebuffer = (VkFramebuffer)(uintptr_t)CombineVulkanHandle (framebufferHigh, framebufferLow); + beginInfo.renderArea.offset.x = packed[0]; + beginInfo.renderArea.offset.y = packed[1]; + beginInfo.renderArea.extent.width = (uint32_t)packed[2]; + beginInfo.renderArea.extent.height = (uint32_t)packed[3]; + beginInfo.clearValueCount = clearValueCount; + beginInfo.pClearValues = clears; + + vkCmdBeginRenderPass (commandBuffer, &beginInfo, VK_SUBPASS_CONTENTS_INLINE); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM void HL_NAME(hl_image_data_util_get_pixels) (Image* image, Rectangle* rect, PixelFormat format, Bytes* bytes) { + HL_PRIM bool HL_NAME(hl_vk_cmd_begin_render_pass) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int renderPassHigh, int renderPassLow, int framebufferHigh, int framebufferLow, + hl_varray* state, hl_varray* clear) { - ImageDataUtil::GetPixels (image, rect, format, bytes); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass = (PFN_vkCmdBeginRenderPass)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCmdBeginRenderPass"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetHLVulkanIntVector (state); + std::vector clearValues = GetHLVulkanDoubleVector (clear); + if (!vkCmdBeginRenderPass || !commandBuffer || packed.size () < 5 || clearValues.size () < 5) return false; + uint32_t clearValueCount = packed.size () > 5 ? (uint32_t)packed[5] : 2; + if (clearValueCount < 1) clearValueCount = 1; + if (clearValueCount > 2) clearValueCount = 2; + + VkClearValue clears[2]; + memset (clears, 0, sizeof (clears)); + clears[0].color.float32[0] = (float)clearValues[0]; + clears[0].color.float32[1] = (float)clearValues[1]; + clears[0].color.float32[2] = (float)clearValues[2]; + clears[0].color.float32[3] = (float)clearValues[3]; + clears[1].depthStencil.depth = (float)clearValues[4]; + clears[1].depthStencil.stencil = (uint32_t)packed[4]; + + VkRenderPassBeginInfo beginInfo; + memset (&beginInfo, 0, sizeof (beginInfo)); + beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + beginInfo.renderPass = (VkRenderPass)(uintptr_t)CombineVulkanHandle (renderPassHigh, renderPassLow); + beginInfo.framebuffer = (VkFramebuffer)(uintptr_t)CombineVulkanHandle (framebufferHigh, framebufferLow); + beginInfo.renderArea.offset.x = packed[0]; + beginInfo.renderArea.offset.y = packed[1]; + beginInfo.renderArea.extent.width = (uint32_t)packed[2]; + beginInfo.renderArea.extent.height = (uint32_t)packed[3]; + beginInfo.clearValueCount = clearValueCount; + beginInfo.pClearValues = clears; + + vkCmdBeginRenderPass (commandBuffer, &beginInfo, VK_SUBPASS_CONTENTS_INLINE); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - void lime_image_data_util_merge (value image, value sourceImage, value sourceRect, value destPoint, int redMultiplier, int greenMultiplier, int blueMultiplier, int alphaMultiplier) { + bool lime_vk_cmd_end_render_pass (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow) { - Image _image = Image (image); - Image _sourceImage = Image (sourceImage); - Rectangle _sourceRect = Rectangle (sourceRect); - Vector2 _destPoint = Vector2 (destPoint); - ImageDataUtil::Merge (&_image, &_sourceImage, &_sourceRect, &_destPoint, redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdEndRenderPass vkCmdEndRenderPass = (PFN_vkCmdEndRenderPass)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCmdEndRenderPass"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + if (!vkCmdEndRenderPass || !commandBuffer) return false; + vkCmdEndRenderPass (commandBuffer); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM void HL_NAME(hl_image_data_util_merge) (Image* image, Image* sourceImage, Rectangle* sourceRect, Vector2* destPoint, int redMultiplier, int greenMultiplier, int blueMultiplier, int alphaMultiplier) { + HL_PRIM bool HL_NAME(hl_vk_cmd_end_render_pass) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow) { - ImageDataUtil::Merge (image, sourceImage, sourceRect, destPoint, redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdEndRenderPass vkCmdEndRenderPass = (PFN_vkCmdEndRenderPass)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCmdEndRenderPass"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + if (!vkCmdEndRenderPass || !commandBuffer) return false; + vkCmdEndRenderPass (commandBuffer); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - void lime_image_data_util_multiply_alpha (value image) { + bool lime_vk_cmd_bind_pipeline (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int pipelineHigh, int pipelineLow, int bindPoint) { - Image _image = Image (image); - ImageDataUtil::MultiplyAlpha (&_image); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBindPipeline vkCmdBindPipeline = (PFN_vkCmdBindPipeline)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCmdBindPipeline"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkPipeline pipeline = (VkPipeline)(uintptr_t)CombineVulkanHandle (pipelineHigh, pipelineLow); + if (!vkCmdBindPipeline || !commandBuffer || !pipeline) return false; + vkCmdBindPipeline (commandBuffer, (VkPipelineBindPoint)bindPoint, pipeline); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM void HL_NAME(hl_image_data_util_multiply_alpha) (Image* image) { + HL_PRIM bool HL_NAME(hl_vk_cmd_bind_pipeline) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int pipelineHigh, int pipelineLow, int bindPoint) { - ImageDataUtil::MultiplyAlpha (image); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBindPipeline vkCmdBindPipeline = (PFN_vkCmdBindPipeline)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCmdBindPipeline"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkPipeline pipeline = (VkPipeline)(uintptr_t)CombineVulkanHandle (pipelineHigh, pipelineLow); + if (!vkCmdBindPipeline || !commandBuffer || !pipeline) return false; + vkCmdBindPipeline (commandBuffer, (VkPipelineBindPoint)bindPoint, pipeline); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - void lime_image_data_util_resize (value image, value buffer, int width, int height) { + bool lime_vk_cmd_bind_descriptor_set (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int layoutHigh, int layoutLow, int setHigh, int setLow, int bindPoint, int firstSet) { - Image _image = Image (image); - ImageBuffer _buffer = ImageBuffer (buffer); - ImageDataUtil::Resize (&_image, &_buffer, width, height); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets = + (PFN_vkCmdBindDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdBindDescriptorSets"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkPipelineLayout layout = (VkPipelineLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + VkDescriptorSet descriptorSet = (VkDescriptorSet)(uintptr_t)CombineVulkanHandle (setHigh, setLow); + if (!vkCmdBindDescriptorSets || !commandBuffer || !layout || !descriptorSet) return false; + vkCmdBindDescriptorSets (commandBuffer, (VkPipelineBindPoint)bindPoint, layout, (uint32_t)firstSet, 1, &descriptorSet, 0, 0); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM void HL_NAME(hl_image_data_util_resize) (Image* image, ImageBuffer* buffer, int width, int height) { + HL_PRIM bool HL_NAME(hl_vk_cmd_bind_descriptor_set) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int layoutHigh, int layoutLow, int setHigh, int setLow, int bindPoint, + int firstSet) { - ImageDataUtil::Resize (image, buffer, width, height); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets = + (PFN_vkCmdBindDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdBindDescriptorSets"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkPipelineLayout layout = (VkPipelineLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + VkDescriptorSet descriptorSet = (VkDescriptorSet)(uintptr_t)CombineVulkanHandle (setHigh, setLow); + if (!vkCmdBindDescriptorSets || !commandBuffer || !layout || !descriptorSet) return false; + vkCmdBindDescriptorSets (commandBuffer, (VkPipelineBindPoint)bindPoint, layout, (uint32_t)firstSet, 1, &descriptorSet, 0, 0); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } + bool lime_vk_cmd_bind_descriptor_set_ex (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int layoutHigh, int layoutLow, int setHigh, int setLow, value state) { - void lime_image_data_util_set_format (value image, int format) { - - Image _image = Image (image); - PixelFormat _format = (PixelFormat)format; - ImageDataUtil::SetFormat (&_image, _format); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets = + (PFN_vkCmdBindDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdBindDescriptorSets"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkPipelineLayout layout = (VkPipelineLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + VkDescriptorSet descriptorSet = (VkDescriptorSet)(uintptr_t)CombineVulkanHandle (setHigh, setLow); + std::vector packed = GetVulkanIntVector (state); + if (!vkCmdBindDescriptorSets || !commandBuffer || !layout || !descriptorSet || packed.size () < 3) return false; + + int dynamicOffsetCount = packed[2]; + if ((int)packed.size () < 3 + dynamicOffsetCount) return false; + std::vector dynamicOffsets; + dynamicOffsets.reserve ((size_t)dynamicOffsetCount); + for (int i = 0; i < dynamicOffsetCount; ++i) dynamicOffsets.push_back ((uint32_t)packed[3 + i]); + + vkCmdBindDescriptorSets (commandBuffer, (VkPipelineBindPoint)packed[0], layout, (uint32_t)packed[1], 1, &descriptorSet, + (uint32_t)dynamicOffsets.size (), dynamicOffsets.empty () ? 0 : dynamicOffsets.data ()); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM void HL_NAME(hl_image_data_util_set_format) (Image* image, PixelFormat format) { + HL_PRIM bool HL_NAME(hl_vk_cmd_bind_descriptor_set_ex) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int layoutHigh, int layoutLow, int setHigh, int setLow, hl_varray* state) { - ImageDataUtil::SetFormat (image, format); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets = + (PFN_vkCmdBindDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdBindDescriptorSets"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkPipelineLayout layout = (VkPipelineLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + VkDescriptorSet descriptorSet = (VkDescriptorSet)(uintptr_t)CombineVulkanHandle (setHigh, setLow); + std::vector packed = GetHLVulkanIntVector (state); + if (!vkCmdBindDescriptorSets || !commandBuffer || !layout || !descriptorSet || packed.size () < 3) return false; + + int dynamicOffsetCount = packed[2]; + if ((int)packed.size () < 3 + dynamicOffsetCount) return false; + std::vector dynamicOffsets; + dynamicOffsets.reserve ((size_t)dynamicOffsetCount); + for (int i = 0; i < dynamicOffsetCount; ++i) dynamicOffsets.push_back ((uint32_t)packed[3 + i]); + + vkCmdBindDescriptorSets (commandBuffer, (VkPipelineBindPoint)packed[0], layout, (uint32_t)packed[1], 1, &descriptorSet, + (uint32_t)dynamicOffsets.size (), dynamicOffsets.empty () ? 0 : dynamicOffsets.data ()); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - void lime_image_data_util_set_pixels (value image, value rect, value bytes, int offset, int format, int endian) { + bool lime_vk_cmd_bind_descriptor_set_dynamic_offset (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int layoutHigh, int layoutLow, int setHigh, int setLow, int dynamicOffset, int firstSet, + int bindPoint) { - Image _image = Image (image); - Rectangle _rect = Rectangle (rect); - Bytes _bytes (bytes); - PixelFormat _format = (PixelFormat)format; - Endian _endian = (Endian)endian; - ImageDataUtil::SetPixels (&_image, &_rect, &_bytes, offset, _format, _endian); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets = + (PFN_vkCmdBindDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdBindDescriptorSets"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkPipelineLayout layout = (VkPipelineLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + VkDescriptorSet descriptorSet = (VkDescriptorSet)(uintptr_t)CombineVulkanHandle (setHigh, setLow); + uint32_t offset = (uint32_t)dynamicOffset; + if (!vkCmdBindDescriptorSets || !commandBuffer || !layout || !descriptorSet) return false; + vkCmdBindDescriptorSets (commandBuffer, (VkPipelineBindPoint)bindPoint, layout, (uint32_t)firstSet, 1, &descriptorSet, 1, &offset); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM void HL_NAME(hl_image_data_util_set_pixels) (Image* image, Rectangle* rect, Bytes* bytes, int offset, PixelFormat format, Endian endian) { + HL_PRIM bool HL_NAME(hl_vk_cmd_bind_descriptor_set_dynamic_offset) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, + int deviceHigh, int deviceLow, int commandBufferHigh, int commandBufferLow, int layoutHigh, int layoutLow, int setHigh, int setLow, + int dynamicOffset, int firstSet, int bindPoint) { - ImageDataUtil::SetPixels (image, rect, bytes, offset, format, endian); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets = + (PFN_vkCmdBindDescriptorSets)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdBindDescriptorSets"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkPipelineLayout layout = (VkPipelineLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + VkDescriptorSet descriptorSet = (VkDescriptorSet)(uintptr_t)CombineVulkanHandle (setHigh, setLow); + uint32_t offset = (uint32_t)dynamicOffset; + if (!vkCmdBindDescriptorSets || !commandBuffer || !layout || !descriptorSet) return false; + vkCmdBindDescriptorSets (commandBuffer, (VkPipelineBindPoint)bindPoint, layout, (uint32_t)firstSet, 1, &descriptorSet, 1, &offset); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - int lime_image_data_util_threshold (value image, value sourceImage, value sourceRect, value destPoint, int operation, int thresholdRG, int thresholdBA, int colorRG, int colorBA, int maskRG, int maskBA, bool copySource) { + value lime_vulkan_renderer_get_info (value handle) { - Image _image = Image (image); - Image _sourceImage = Image (sourceImage); - Rectangle _sourceRect = Rectangle (sourceRect); - Vector2 _destPoint = Vector2 (destPoint); - int32_t threshold = (thresholdRG << 16) | thresholdBA; - int32_t color = (colorRG << 16) | colorBA; - int32_t mask = (maskRG << 16) | maskBA; - return ImageDataUtil::Threshold (&_image, &_sourceImage, &_sourceRect, &_destPoint, operation, threshold, color, mask, copySource); + if (val_is_null (handle)) return alloc_string (""); + VulkanRenderer* renderer = (VulkanRenderer*)val_data (handle); + return alloc_string (renderer->GetInfo ().c_str ()); } - HL_PRIM int HL_NAME(hl_image_data_util_threshold) (Image* image, Image* sourceImage, Rectangle* sourceRect, Vector2* destPoint, int operation, int thresholdRG, int thresholdBA, int colorRG, int colorBA, int maskRG, int maskBA, bool copySource) { + bool lime_vk_cmd_bind_vertex_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int bufferHigh, int bufferLow, int binding, int offsetHigh, int offsetLow) { - int32_t threshold = (thresholdRG << 16) | thresholdBA; - int32_t color = (colorRG << 16) | colorBA; - int32_t mask = (maskRG << 16) | maskBA; - return ImageDataUtil::Threshold (image, sourceImage, sourceRect, destPoint, operation, threshold, color, mask, copySource); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers = + (PFN_vkCmdBindVertexBuffers)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdBindVertexBuffers"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + VkDeviceSize offset = (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow); + if (!vkCmdBindVertexBuffers || !commandBuffer || !buffer) return false; + vkCmdBindVertexBuffers (commandBuffer, (uint32_t)binding, 1, &buffer, &offset); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - void lime_image_data_util_unmultiply_alpha (value image) { + HL_PRIM bool HL_NAME(hl_vk_cmd_bind_vertex_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int binding, int offsetHigh, int offsetLow) { - Image _image = Image (image); - ImageDataUtil::UnmultiplyAlpha (&_image); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers = + (PFN_vkCmdBindVertexBuffers)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdBindVertexBuffers"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + VkDeviceSize offset = (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow); + if (!vkCmdBindVertexBuffers || !commandBuffer || !buffer) return false; + vkCmdBindVertexBuffers (commandBuffer, (uint32_t)binding, 1, &buffer, &offset); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM void HL_NAME(hl_image_data_util_unmultiply_alpha) (Image* image) { + bool lime_vk_cmd_bind_index_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int bufferHigh, int bufferLow, int offsetHigh, int offsetLow, int indexType) { - ImageDataUtil::UnmultiplyAlpha (image); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer = + (PFN_vkCmdBindIndexBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdBindIndexBuffer"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + if (!vkCmdBindIndexBuffer || !commandBuffer || !buffer) return false; + vkCmdBindIndexBuffer (commandBuffer, buffer, (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow), (VkIndexType)indexType); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - double lime_jni_getenv () { + HL_PRIM bool HL_NAME(hl_vk_cmd_bind_index_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int offsetHigh, int offsetLow, int indexType) { - #ifdef ANDROID - return (uintptr_t)JNI::GetEnv (); - #else - return 0; - #endif +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer = + (PFN_vkCmdBindIndexBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdBindIndexBuffer"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + if (!vkCmdBindIndexBuffer || !commandBuffer || !buffer) return false; + vkCmdBindIndexBuffer (commandBuffer, buffer, (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow), (VkIndexType)indexType); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM double HL_NAME(hl_jni_getenv) () { + bool lime_vk_cmd_set_viewport (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, double x, double y, double width, double height, double minDepth, double maxDepth) { - #ifdef ANDROID - return (uintptr_t)JNI::GetEnv (); - #else - return 0; - #endif +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdSetViewport vkCmdSetViewport = (PFN_vkCmdSetViewport)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCmdSetViewport"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + if (!vkCmdSetViewport || !commandBuffer) return false; + VkViewport viewport; + viewport.x = (float)x; + viewport.y = (float)y; + viewport.width = (float)width; + viewport.height = (float)height; + viewport.minDepth = (float)minDepth; + viewport.maxDepth = (float)maxDepth; + vkCmdSetViewport (commandBuffer, 0, 1, &viewport); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - void lime_joystick_event_manager_register (value callback, value eventObject) { + HL_PRIM bool HL_NAME(hl_vk_cmd_set_viewport) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, double x, double y, double width, double height, double minDepth, double maxDepth) { - JoystickEvent::callback = new ValuePointer (callback); - JoystickEvent::eventObject = new ValuePointer (eventObject); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdSetViewport vkCmdSetViewport = (PFN_vkCmdSetViewport)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCmdSetViewport"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + if (!vkCmdSetViewport || !commandBuffer) return false; + VkViewport viewport; + viewport.x = (float)x; + viewport.y = (float)y; + viewport.width = (float)width; + viewport.height = (float)height; + viewport.minDepth = (float)minDepth; + viewport.maxDepth = (float)maxDepth; + vkCmdSetViewport (commandBuffer, 0, 1, &viewport); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } + HL_PRIM vbyte* HL_NAME(hl_vulkan_renderer_get_info) (HL_CFFIPointer* handle) { - HL_PRIM void HL_NAME(hl_joystick_event_manager_register) (vclosure* callback, JoystickEvent* eventObject) { - - JoystickEvent::callback = new ValuePointer (callback); - JoystickEvent::eventObject = new ValuePointer ((vobj*)eventObject); + if (!handle || !handle->ptr) return 0; + VulkanRenderer* renderer = (VulkanRenderer*)handle->ptr; + return hl_copy_bytes ((const vbyte*)renderer->GetInfo ().c_str (), (int)renderer->GetInfo ().size () + 1); } - value lime_joystick_get_device_guid (int id) { + bool lime_vk_cmd_set_scissor (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int x, int y, int width, int height) { - const char* guid = Joystick::GetDeviceGUID (id); - return guid ? alloc_string (guid) : alloc_null (); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdSetScissor vkCmdSetScissor = (PFN_vkCmdSetScissor)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdSetScissor"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + if (!vkCmdSetScissor || !commandBuffer) return false; + VkRect2D scissor; + scissor.offset.x = x; + scissor.offset.y = y; + scissor.extent.width = (uint32_t)width; + scissor.extent.height = (uint32_t)height; + vkCmdSetScissor (commandBuffer, 0, 1, &scissor); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM vbyte* HL_NAME(hl_joystick_get_device_guid) (int id) { + HL_PRIM bool HL_NAME(hl_vk_cmd_set_scissor) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int x, int y, int width, int height) { - return (vbyte*)Joystick::GetDeviceGUID (id); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdSetScissor vkCmdSetScissor = (PFN_vkCmdSetScissor)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdSetScissor"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + if (!vkCmdSetScissor || !commandBuffer) return false; + VkRect2D scissor; + scissor.offset.x = x; + scissor.offset.y = y; + scissor.extent.width = (uint32_t)width; + scissor.extent.height = (uint32_t)height; + vkCmdSetScissor (commandBuffer, 0, 1, &scissor); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - value lime_joystick_get_device_name (int id) { + bool lime_vk_cmd_draw (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int vertexCount, int instanceCount, int firstVertex, int firstInstance) { - const char* name = Joystick::GetDeviceName (id); - return name ? alloc_string (name) : alloc_null (); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdDraw vkCmdDraw = (PFN_vkCmdDraw)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdDraw"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + if (!vkCmdDraw || !commandBuffer) return false; + vkCmdDraw (commandBuffer, (uint32_t)vertexCount, (uint32_t)instanceCount, (uint32_t)firstVertex, (uint32_t)firstInstance); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM vbyte* HL_NAME(hl_joystick_get_device_name) (int id) { + HL_PRIM bool HL_NAME(hl_vk_cmd_draw) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int vertexCount, int instanceCount, int firstVertex, int firstInstance) { - return (vbyte*)Joystick::GetDeviceName (id); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdDraw vkCmdDraw = (PFN_vkCmdDraw)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdDraw"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + if (!vkCmdDraw || !commandBuffer) return false; + vkCmdDraw (commandBuffer, (uint32_t)vertexCount, (uint32_t)instanceCount, (uint32_t)firstVertex, (uint32_t)firstInstance); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - int lime_joystick_get_num_axes (int id) { + bool lime_vk_cmd_draw_indexed (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int indexCount, int instanceCount, int firstIndex, int vertexOffset, int firstInstance) { - return Joystick::GetNumAxes (id); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdDrawIndexed vkCmdDrawIndexed = (PFN_vkCmdDrawIndexed)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCmdDrawIndexed"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + if (!vkCmdDrawIndexed || !commandBuffer) return false; + vkCmdDrawIndexed (commandBuffer, (uint32_t)indexCount, (uint32_t)instanceCount, (uint32_t)firstIndex, vertexOffset, (uint32_t)firstInstance); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM int HL_NAME(hl_joystick_get_num_axes) (int id) { + HL_PRIM bool HL_NAME(hl_vk_cmd_draw_indexed) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int indexCount, int instanceCount, int firstIndex, int vertexOffset, int firstInstance) { - return Joystick::GetNumAxes (id); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdDrawIndexed vkCmdDrawIndexed = (PFN_vkCmdDrawIndexed)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCmdDrawIndexed"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + if (!vkCmdDrawIndexed || !commandBuffer) return false; + vkCmdDrawIndexed (commandBuffer, (uint32_t)indexCount, (uint32_t)instanceCount, (uint32_t)firstIndex, vertexOffset, (uint32_t)firstInstance); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - int lime_joystick_get_num_buttons (int id) { + bool lime_vk_cmd_draw_indirect (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int bufferHigh, int bufferLow, int offsetHigh, int offsetLow, int drawCount, int stride) { - return Joystick::GetNumButtons (id); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdDrawIndirect vkCmdDrawIndirect = (PFN_vkCmdDrawIndirect)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCmdDrawIndirect"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + if (!vkCmdDrawIndirect || !commandBuffer || !buffer) return false; + vkCmdDrawIndirect (commandBuffer, buffer, (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow), (uint32_t)drawCount, + (uint32_t)stride); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM int HL_NAME(hl_joystick_get_num_buttons) (int id) { + HL_PRIM bool HL_NAME(hl_vk_cmd_draw_indirect) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int offsetHigh, int offsetLow, int drawCount, int stride) { - return Joystick::GetNumButtons (id); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdDrawIndirect vkCmdDrawIndirect = (PFN_vkCmdDrawIndirect)GetManagedVulkanDeviceProc (targetWindow, instance, device, + "vkCmdDrawIndirect"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + if (!vkCmdDrawIndirect || !commandBuffer || !buffer) return false; + vkCmdDrawIndirect (commandBuffer, buffer, (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow), (uint32_t)drawCount, + (uint32_t)stride); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - int lime_joystick_get_num_hats (int id) { + bool lime_vk_cmd_draw_indexed_indirect (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int offsetHigh, int offsetLow, int drawCount, int stride) { - return Joystick::GetNumHats (id); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect = + (PFN_vkCmdDrawIndexedIndirect)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdDrawIndexedIndirect"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + if (!vkCmdDrawIndexedIndirect || !commandBuffer || !buffer) return false; + vkCmdDrawIndexedIndirect (commandBuffer, buffer, (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow), (uint32_t)drawCount, + (uint32_t)stride); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM int HL_NAME(hl_joystick_get_num_hats) (int id) { + HL_PRIM bool HL_NAME(hl_vk_cmd_draw_indexed_indirect) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int offsetHigh, int offsetLow, int drawCount, + int stride) { - return Joystick::GetNumHats (id); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect = + (PFN_vkCmdDrawIndexedIndirect)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdDrawIndexedIndirect"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkBuffer buffer = (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow); + if (!vkCmdDrawIndexedIndirect || !commandBuffer || !buffer) return false; + vkCmdDrawIndexedIndirect (commandBuffer, buffer, (VkDeviceSize)CombineVulkanHandle (offsetHigh, offsetLow), (uint32_t)drawCount, + (uint32_t)stride); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } + value lime_vulkan_renderer_get_last_error () { - value lime_jpeg_decode_bytes (value data, bool decodeData, value buffer) { - - ImageBuffer imageBuffer (buffer); - - Bytes bytes (data); - Resource resource = Resource (&bytes); + return alloc_string (lastVulkanRendererError.c_str ()); - #ifdef LIME_JPEG - if (JPEG::Decode (&resource, &imageBuffer, decodeData)) { + } - return imageBuffer.Value (buffer); - } - #endif + bool lime_vk_cmd_copy_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int sourceHigh, int sourceLow, int destinationHigh, int destinationLow, value state) { - return alloc_null (); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdCopyBuffer vkCmdCopyBuffer = (PFN_vkCmdCopyBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdCopyBuffer"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetVulkanIntVector (state); + if (!vkCmdCopyBuffer || !commandBuffer || packed.size () < 6) return false; + + VkBufferCopy region; + region.size = (VkDeviceSize)CombineVulkanHandle (packed[0], packed[1]); + region.srcOffset = (VkDeviceSize)CombineVulkanHandle (packed[2], packed[3]); + region.dstOffset = (VkDeviceSize)CombineVulkanHandle (packed[4], packed[5]); + vkCmdCopyBuffer (commandBuffer, (VkBuffer)(uintptr_t)CombineVulkanHandle (sourceHigh, sourceLow), + (VkBuffer)(uintptr_t)CombineVulkanHandle (destinationHigh, destinationLow), 1, ®ion); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM ImageBuffer* HL_NAME(hl_jpeg_decode_bytes) (Bytes* data, bool decodeData, ImageBuffer* buffer) { - - Resource resource = Resource (data); + HL_PRIM bool HL_NAME(hl_vk_cmd_copy_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int sourceHigh, int sourceLow, int destinationHigh, int destinationLow, hl_varray* state) { - #ifdef LIME_JPEG - if (JPEG::Decode (&resource, buffer, decodeData)) { +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdCopyBuffer vkCmdCopyBuffer = (PFN_vkCmdCopyBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdCopyBuffer"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetHLVulkanIntVector (state); + if (!vkCmdCopyBuffer || !commandBuffer || packed.size () < 6) return false; + + VkBufferCopy region; + region.size = (VkDeviceSize)CombineVulkanHandle (packed[0], packed[1]); + region.srcOffset = (VkDeviceSize)CombineVulkanHandle (packed[2], packed[3]); + region.dstOffset = (VkDeviceSize)CombineVulkanHandle (packed[4], packed[5]); + vkCmdCopyBuffer (commandBuffer, (VkBuffer)(uintptr_t)CombineVulkanHandle (sourceHigh, sourceLow), + (VkBuffer)(uintptr_t)CombineVulkanHandle (destinationHigh, destinationLow), 1, ®ion); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif - return buffer; + } - } - #endif + bool lime_vk_cmd_copy_buffer_to_image_region (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int imageHigh, int imageLow, value state) { - return 0; +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage = + (PFN_vkCmdCopyBufferToImage)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdCopyBufferToImage"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetVulkanIntVector (state); + if (!vkCmdCopyBufferToImage || !commandBuffer || packed.size () < 15) return false; + + VkBufferImageCopy region; + memset (®ion, 0, sizeof (region)); + region.bufferOffset = (VkDeviceSize)CombineVulkanHandle (packed[4], packed[5]); + region.bufferRowLength = (uint32_t)packed[6]; + region.bufferImageHeight = (uint32_t)packed[7]; + region.imageSubresource.aspectMask = (VkImageAspectFlags)packed[14]; + region.imageSubresource.mipLevel = (uint32_t)packed[11]; + region.imageSubresource.baseArrayLayer = (uint32_t)packed[12]; + region.imageSubresource.layerCount = (uint32_t)packed[13]; + region.imageOffset.x = packed[8]; + region.imageOffset.y = packed[9]; + region.imageOffset.z = packed[10]; + region.imageExtent.width = (uint32_t)packed[0]; + region.imageExtent.height = (uint32_t)packed[1]; + region.imageExtent.depth = (uint32_t)packed[2]; + vkCmdCopyBufferToImage (commandBuffer, (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow), + (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow), (VkImageLayout)packed[3], 1, ®ion); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - value lime_jpeg_decode_file (HxString path, bool decodeData, value buffer) { + HL_PRIM bool HL_NAME(hl_vk_cmd_copy_buffer_to_image_region) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int imageHigh, int imageLow, hl_varray* state) { - ImageBuffer imageBuffer (buffer); - Resource resource = Resource (hxs_utf8 (path, nullptr)); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage = + (PFN_vkCmdCopyBufferToImage)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdCopyBufferToImage"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetHLVulkanIntVector (state); + if (!vkCmdCopyBufferToImage || !commandBuffer || packed.size () < 15) return false; + + VkBufferImageCopy region; + memset (®ion, 0, sizeof (region)); + region.bufferOffset = (VkDeviceSize)CombineVulkanHandle (packed[4], packed[5]); + region.bufferRowLength = (uint32_t)packed[6]; + region.bufferImageHeight = (uint32_t)packed[7]; + region.imageSubresource.aspectMask = (VkImageAspectFlags)packed[14]; + region.imageSubresource.mipLevel = (uint32_t)packed[11]; + region.imageSubresource.baseArrayLayer = (uint32_t)packed[12]; + region.imageSubresource.layerCount = (uint32_t)packed[13]; + region.imageOffset.x = packed[8]; + region.imageOffset.y = packed[9]; + region.imageOffset.z = packed[10]; + region.imageExtent.width = (uint32_t)packed[0]; + region.imageExtent.height = (uint32_t)packed[1]; + region.imageExtent.depth = (uint32_t)packed[2]; + vkCmdCopyBufferToImage (commandBuffer, (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow), + (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow), (VkImageLayout)packed[3], 1, ®ion); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif - #ifdef LIME_JPEG - if (JPEG::Decode (&resource, &imageBuffer, decodeData)) { + } - return imageBuffer.Value (buffer); - } - #endif + bool lime_vk_cmd_copy_image_to_buffer_region (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, int bufferHigh, int bufferLow, value state) { - return alloc_null (); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer = + (PFN_vkCmdCopyImageToBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdCopyImageToBuffer"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetVulkanIntVector (state); + if (!vkCmdCopyImageToBuffer || !commandBuffer || packed.size () < 15) return false; + + VkBufferImageCopy region; + memset (®ion, 0, sizeof (region)); + region.bufferOffset = (VkDeviceSize)CombineVulkanHandle (packed[4], packed[5]); + region.bufferRowLength = (uint32_t)packed[6]; + region.bufferImageHeight = (uint32_t)packed[7]; + region.imageSubresource.aspectMask = (VkImageAspectFlags)packed[14]; + region.imageSubresource.mipLevel = (uint32_t)packed[11]; + region.imageSubresource.baseArrayLayer = (uint32_t)packed[12]; + region.imageSubresource.layerCount = (uint32_t)packed[13]; + region.imageOffset.x = packed[8]; + region.imageOffset.y = packed[9]; + region.imageOffset.z = packed[10]; + region.imageExtent.width = (uint32_t)packed[0]; + region.imageExtent.height = (uint32_t)packed[1]; + region.imageExtent.depth = (uint32_t)packed[2]; + vkCmdCopyImageToBuffer (commandBuffer, (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow), (VkImageLayout)packed[3], + (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow), 1, ®ion); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM ImageBuffer* HL_NAME(hl_jpeg_decode_file) (hl_vstring* path, bool decodeData, ImageBuffer* buffer) { - - Resource resource = Resource (path); + HL_PRIM bool HL_NAME(hl_vk_cmd_copy_image_to_buffer_region) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, int bufferHigh, int bufferLow, hl_varray* state) { - #ifdef LIME_JPEG - if (JPEG::Decode (&resource, buffer, decodeData)) { +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer = + (PFN_vkCmdCopyImageToBuffer)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdCopyImageToBuffer"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetHLVulkanIntVector (state); + if (!vkCmdCopyImageToBuffer || !commandBuffer || packed.size () < 15) return false; + + VkBufferImageCopy region; + memset (®ion, 0, sizeof (region)); + region.bufferOffset = (VkDeviceSize)CombineVulkanHandle (packed[4], packed[5]); + region.bufferRowLength = (uint32_t)packed[6]; + region.bufferImageHeight = (uint32_t)packed[7]; + region.imageSubresource.aspectMask = (VkImageAspectFlags)packed[14]; + region.imageSubresource.mipLevel = (uint32_t)packed[11]; + region.imageSubresource.baseArrayLayer = (uint32_t)packed[12]; + region.imageSubresource.layerCount = (uint32_t)packed[13]; + region.imageOffset.x = packed[8]; + region.imageOffset.y = packed[9]; + region.imageOffset.z = packed[10]; + region.imageExtent.width = (uint32_t)packed[0]; + region.imageExtent.height = (uint32_t)packed[1]; + region.imageExtent.depth = (uint32_t)packed[2]; + vkCmdCopyImageToBuffer (commandBuffer, (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow), (VkImageLayout)packed[3], + (VkBuffer)(uintptr_t)CombineVulkanHandle (bufferHigh, bufferLow), 1, ®ion); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif - return buffer; + } - } - #endif + HL_PRIM vbyte* HL_NAME(hl_vulkan_renderer_get_last_error) () { - return 0; + return hl_copy_bytes ((const vbyte*)lastVulkanRendererError.c_str (), (int)lastVulkanRendererError.size () + 1); } - int lime_key_code_from_scan_code (int scanCode) { + bool lime_vk_cmd_pipeline_barrier_image (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, value state) { - return KeyCode::FromScanCode (scanCode); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier = + (PFN_vkCmdPipelineBarrier)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdPipelineBarrier"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetVulkanIntVector (state); + if (!vkCmdPipelineBarrier || !commandBuffer || packed.size () < 11) return false; + + VkImageMemoryBarrier barrier; + memset (&barrier, 0, sizeof (barrier)); + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = (VkImageLayout)packed[0]; + barrier.newLayout = (VkImageLayout)packed[1]; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow); + barrier.srcAccessMask = (VkAccessFlags)packed[4]; + barrier.dstAccessMask = (VkAccessFlags)packed[5]; + barrier.subresourceRange.aspectMask = (VkImageAspectFlags)packed[6]; + barrier.subresourceRange.baseMipLevel = (uint32_t)packed[7]; + barrier.subresourceRange.levelCount = (uint32_t)packed[8]; + barrier.subresourceRange.baseArrayLayer = (uint32_t)packed[9]; + barrier.subresourceRange.layerCount = (uint32_t)packed[10]; + + vkCmdPipelineBarrier (commandBuffer, (VkPipelineStageFlags)packed[2], (VkPipelineStageFlags)packed[3], 0, 0, 0, 0, 0, 1, &barrier); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM int HL_NAME(hl_key_code_from_scan_code) (int scanCode) { + HL_PRIM bool HL_NAME(hl_vk_cmd_pipeline_barrier_image) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, hl_varray* state) { - return KeyCode::FromScanCode (scanCode); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier = + (PFN_vkCmdPipelineBarrier)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdPipelineBarrier"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetHLVulkanIntVector (state); + if (!vkCmdPipelineBarrier || !commandBuffer || packed.size () < 11) return false; + + VkImageMemoryBarrier barrier; + memset (&barrier, 0, sizeof (barrier)); + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = (VkImageLayout)packed[0]; + barrier.newLayout = (VkImageLayout)packed[1]; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow); + barrier.srcAccessMask = (VkAccessFlags)packed[4]; + barrier.dstAccessMask = (VkAccessFlags)packed[5]; + barrier.subresourceRange.aspectMask = (VkImageAspectFlags)packed[6]; + barrier.subresourceRange.baseMipLevel = (uint32_t)packed[7]; + barrier.subresourceRange.levelCount = (uint32_t)packed[8]; + barrier.subresourceRange.baseArrayLayer = (uint32_t)packed[9]; + barrier.subresourceRange.layerCount = (uint32_t)packed[10]; + + vkCmdPipelineBarrier (commandBuffer, (VkPipelineStageFlags)packed[2], (VkPipelineStageFlags)packed[3], 0, 0, 0, 0, 0, 1, &barrier); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - int lime_key_code_to_scan_code (int keyCode) { + bool lime_vk_cmd_clear_color_image (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int imageHigh, int imageLow, int layout, int aspectMask, value clear) { - return KeyCode::ToScanCode (keyCode); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdClearColorImage vkCmdClearColorImage = + (PFN_vkCmdClearColorImage)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdClearColorImage"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector clearValues = GetVulkanDoubleVector (clear); + if (!vkCmdClearColorImage || !commandBuffer || clearValues.size () < 4) return false; + + VkClearColorValue color; + color.float32[0] = (float)clearValues[0]; + color.float32[1] = (float)clearValues[1]; + color.float32[2] = (float)clearValues[2]; + color.float32[3] = (float)clearValues[3]; + VkImageSubresourceRange range; + memset (&range, 0, sizeof (range)); + range.aspectMask = (VkImageAspectFlags)aspectMask; + range.levelCount = 1; + range.layerCount = 1; + vkCmdClearColorImage (commandBuffer, (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow), (VkImageLayout)layout, &color, 1, &range); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM int HL_NAME(hl_key_code_to_scan_code) (int keyCode) { + HL_PRIM bool HL_NAME(hl_vk_cmd_clear_color_image) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, int layout, int aspectMask, hl_varray* clear) { - return KeyCode::ToScanCode (keyCode); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdClearColorImage vkCmdClearColorImage = + (PFN_vkCmdClearColorImage)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdClearColorImage"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector clearValues = GetHLVulkanDoubleVector (clear); + if (!vkCmdClearColorImage || !commandBuffer || clearValues.size () < 4) return false; + + VkClearColorValue color; + color.float32[0] = (float)clearValues[0]; + color.float32[1] = (float)clearValues[1]; + color.float32[2] = (float)clearValues[2]; + color.float32[3] = (float)clearValues[3]; + VkImageSubresourceRange range; + memset (&range, 0, sizeof (range)); + range.aspectMask = (VkImageAspectFlags)aspectMask; + range.levelCount = 1; + range.layerCount = 1; + vkCmdClearColorImage (commandBuffer, (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow), (VkImageLayout)layout, &color, 1, &range); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - void lime_key_event_manager_register (value callback, value eventObject) { + bool lime_vk_cmd_clear_depth_stencil_image (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, value state, value clear) { - KeyEvent::callback = new ValuePointer (callback); - KeyEvent::eventObject = new ValuePointer (eventObject); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdClearDepthStencilImage vkCmdClearDepthStencilImage = + (PFN_vkCmdClearDepthStencilImage)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdClearDepthStencilImage"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetVulkanIntVector (state); + std::vector clearValues = GetVulkanDoubleVector (clear); + if (!vkCmdClearDepthStencilImage || !commandBuffer || packed.size () < 7 || clearValues.size () < 1) return false; + + VkClearDepthStencilValue depthStencil; + depthStencil.depth = (float)clearValues[0]; + depthStencil.stencil = (uint32_t)packed[1]; + VkImageSubresourceRange range; + memset (&range, 0, sizeof (range)); + range.aspectMask = (VkImageAspectFlags)packed[2]; + range.baseMipLevel = (uint32_t)packed[3]; + range.levelCount = (uint32_t)packed[4]; + range.baseArrayLayer = (uint32_t)packed[5]; + range.layerCount = (uint32_t)packed[6]; + vkCmdClearDepthStencilImage (commandBuffer, (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow), (VkImageLayout)packed[0], + &depthStencil, 1, &range); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM void HL_NAME(hl_key_event_manager_register) (vclosure* callback, KeyEvent* eventObject) { + HL_PRIM bool HL_NAME(hl_vk_cmd_clear_depth_stencil_image) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, hl_varray* state, hl_varray* clear) { - KeyEvent::callback = new ValuePointer (callback); - KeyEvent::eventObject = new ValuePointer ((vobj*)eventObject); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdClearDepthStencilImage vkCmdClearDepthStencilImage = + (PFN_vkCmdClearDepthStencilImage)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdClearDepthStencilImage"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetHLVulkanIntVector (state); + std::vector clearValues = GetHLVulkanDoubleVector (clear); + if (!vkCmdClearDepthStencilImage || !commandBuffer || packed.size () < 7 || clearValues.size () < 1) return false; + + VkClearDepthStencilValue depthStencil; + depthStencil.depth = (float)clearValues[0]; + depthStencil.stencil = (uint32_t)packed[1]; + VkImageSubresourceRange range; + memset (&range, 0, sizeof (range)); + range.aspectMask = (VkImageAspectFlags)packed[2]; + range.baseMipLevel = (uint32_t)packed[3]; + range.levelCount = (uint32_t)packed[4]; + range.baseArrayLayer = (uint32_t)packed[5]; + range.layerCount = (uint32_t)packed[6]; + vkCmdClearDepthStencilImage (commandBuffer, (VkImage)(uintptr_t)CombineVulkanHandle (imageHigh, imageLow), (VkImageLayout)packed[0], + &depthStencil, 1, &range); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - value lime_locale_get_system_locale () { - - std::string* locale = Locale::GetSystemLocale (); + bool lime_vk_cmd_clear_attachments (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, value state, value clear) { - if (!locale) { +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdClearAttachments vkCmdClearAttachments = + (PFN_vkCmdClearAttachments)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdClearAttachments"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetVulkanIntVector (state); + std::vector clearValues = GetVulkanDoubleVector (clear); + if (!vkCmdClearAttachments || !commandBuffer || packed.size () < 8 || clearValues.size () < 5) return false; + + VkClearAttachment attachments[2]; + memset (attachments, 0, sizeof (attachments)); + uint32_t attachmentCount = 0; + + if (packed[0] != 0) { + + attachments[attachmentCount].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + attachments[attachmentCount].colorAttachment = 0; + attachments[attachmentCount].clearValue.color.float32[0] = (float)clearValues[0]; + attachments[attachmentCount].clearValue.color.float32[1] = (float)clearValues[1]; + attachments[attachmentCount].clearValue.color.float32[2] = (float)clearValues[2]; + attachments[attachmentCount].clearValue.color.float32[3] = (float)clearValues[3]; + attachmentCount++; - return alloc_null (); + } - } else { + if (packed[1] != 0 || packed[2] != 0) { - value result = alloc_string (locale->c_str ()); - delete locale; - return result; + attachments[attachmentCount].aspectMask = 0; + if (packed[1] != 0) attachments[attachmentCount].aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; + if (packed[2] != 0) attachments[attachmentCount].aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + attachments[attachmentCount].clearValue.depthStencil.depth = (float)clearValues[4]; + attachments[attachmentCount].clearValue.depthStencil.stencil = (uint32_t)packed[7]; + attachmentCount++; } - } - + if (attachmentCount == 0) return true; + + VkClearRect rect; + memset (&rect, 0, sizeof (rect)); + rect.rect.offset.x = packed[3]; + rect.rect.offset.y = packed[4]; + rect.rect.extent.width = (uint32_t)packed[5]; + rect.rect.extent.height = (uint32_t)packed[6]; + rect.baseArrayLayer = 0; + rect.layerCount = 1; + vkCmdClearAttachments (commandBuffer, attachmentCount, attachments, 1, &rect); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif - HL_PRIM vbyte* HL_NAME(hl_locale_get_system_locale) () { + } - std::string* locale = Locale::GetSystemLocale (); - if (!locale) { + HL_PRIM bool HL_NAME(hl_vk_cmd_clear_attachments) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, hl_varray* state, hl_varray* clear) { - return 0; +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdClearAttachments vkCmdClearAttachments = + (PFN_vkCmdClearAttachments)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdClearAttachments"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetHLVulkanIntVector (state); + std::vector clearValues = GetHLVulkanDoubleVector (clear); + if (!vkCmdClearAttachments || !commandBuffer || packed.size () < 8 || clearValues.size () < 5) return false; + + VkClearAttachment attachments[2]; + memset (attachments, 0, sizeof (attachments)); + uint32_t attachmentCount = 0; + + if (packed[0] != 0) { + + attachments[attachmentCount].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + attachments[attachmentCount].colorAttachment = 0; + attachments[attachmentCount].clearValue.color.float32[0] = (float)clearValues[0]; + attachments[attachmentCount].clearValue.color.float32[1] = (float)clearValues[1]; + attachments[attachmentCount].clearValue.color.float32[2] = (float)clearValues[2]; + attachments[attachmentCount].clearValue.color.float32[3] = (float)clearValues[3]; + attachmentCount++; - } else { + } - int size = locale->size (); - char* _locale = (char*)malloc (size + 1); - strncpy (_locale, locale->c_str (), size); - _locale[size] = '\0'; - delete locale; + if (packed[1] != 0 || packed[2] != 0) { - return (vbyte*)_locale; + attachments[attachmentCount].aspectMask = 0; + if (packed[1] != 0) attachments[attachmentCount].aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; + if (packed[2] != 0) attachments[attachmentCount].aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + attachments[attachmentCount].clearValue.depthStencil.depth = (float)clearValues[4]; + attachments[attachmentCount].clearValue.depthStencil.stencil = (uint32_t)packed[7]; + attachmentCount++; } - } - + if (attachmentCount == 0) return true; + + VkClearRect rect; + memset (&rect, 0, sizeof (rect)); + rect.rect.offset.x = packed[3]; + rect.rect.offset.y = packed[4]; + rect.rect.extent.width = (uint32_t)packed[5]; + rect.rect.extent.height = (uint32_t)packed[6]; + rect.baseArrayLayer = 0; + rect.layerCount = 1; + vkCmdClearAttachments (commandBuffer, attachmentCount, attachments, 1, &rect); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif - value lime_lzma_compress (value buffer, value bytes) { + } - #ifdef LIME_LZMA - Bytes data (buffer); - Bytes result (bytes); - LZMA::Compress (&data, &result); + bool lime_vk_cmd_blit_image (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int sourceHigh, int sourceLow, int destinationHigh, int destinationLow, value state) { - return result.Value (bytes); - #else - return alloc_null (); - #endif +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBlitImage vkCmdBlitImage = (PFN_vkCmdBlitImage)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdBlitImage"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetVulkanIntVector (state); + if (!vkCmdBlitImage || !commandBuffer || packed.size () < 21) return false; + + VkImageBlit blit; + memset (&blit, 0, sizeof (blit)); + blit.srcOffsets[0].x = packed[0]; + blit.srcOffsets[0].y = packed[1]; + blit.srcOffsets[0].z = packed[2]; + blit.srcOffsets[1].x = packed[3]; + blit.srcOffsets[1].y = packed[4]; + blit.srcOffsets[1].z = packed[5]; + blit.dstOffsets[0].x = packed[6]; + blit.dstOffsets[0].y = packed[7]; + blit.dstOffsets[0].z = packed[8]; + blit.dstOffsets[1].x = packed[9]; + blit.dstOffsets[1].y = packed[10]; + blit.dstOffsets[1].z = packed[11]; + int sourceLayout = packed[12]; + int destinationLayout = packed[13]; + int filter = packed[14]; + blit.srcSubresource.mipLevel = (uint32_t)packed[15]; + blit.dstSubresource.mipLevel = (uint32_t)packed[16]; + blit.srcSubresource.baseArrayLayer = (uint32_t)packed[17]; + blit.dstSubresource.baseArrayLayer = (uint32_t)packed[18]; + blit.srcSubresource.layerCount = (uint32_t)packed[19]; + blit.dstSubresource.layerCount = (uint32_t)packed[19]; + blit.srcSubresource.aspectMask = (VkImageAspectFlags)packed[20]; + blit.dstSubresource.aspectMask = (VkImageAspectFlags)packed[20]; + vkCmdBlitImage (commandBuffer, (VkImage)(uintptr_t)CombineVulkanHandle (sourceHigh, sourceLow), (VkImageLayout)sourceLayout, + (VkImage)(uintptr_t)CombineVulkanHandle (destinationHigh, destinationLow), (VkImageLayout)destinationLayout, 1, &blit, + (VkFilter)filter); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM Bytes* HL_NAME(hl_lzma_compress) (Bytes* buffer, Bytes* bytes) { + HL_PRIM bool HL_NAME(hl_vk_cmd_blit_image) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int sourceHigh, int sourceLow, int destinationHigh, int destinationLow, hl_varray* state) { - #ifdef LIME_LZMA - LZMA::Compress (buffer, bytes); - return bytes; - #else - return 0; - #endif +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdBlitImage vkCmdBlitImage = (PFN_vkCmdBlitImage)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdBlitImage"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + std::vector packed = GetHLVulkanIntVector (state); + if (!vkCmdBlitImage || !commandBuffer || packed.size () < 21) return false; + + VkImageBlit blit; + memset (&blit, 0, sizeof (blit)); + blit.srcOffsets[0].x = packed[0]; + blit.srcOffsets[0].y = packed[1]; + blit.srcOffsets[0].z = packed[2]; + blit.srcOffsets[1].x = packed[3]; + blit.srcOffsets[1].y = packed[4]; + blit.srcOffsets[1].z = packed[5]; + blit.dstOffsets[0].x = packed[6]; + blit.dstOffsets[0].y = packed[7]; + blit.dstOffsets[0].z = packed[8]; + blit.dstOffsets[1].x = packed[9]; + blit.dstOffsets[1].y = packed[10]; + blit.dstOffsets[1].z = packed[11]; + int sourceLayout = packed[12]; + int destinationLayout = packed[13]; + int filter = packed[14]; + blit.srcSubresource.mipLevel = (uint32_t)packed[15]; + blit.dstSubresource.mipLevel = (uint32_t)packed[16]; + blit.srcSubresource.baseArrayLayer = (uint32_t)packed[17]; + blit.dstSubresource.baseArrayLayer = (uint32_t)packed[18]; + blit.srcSubresource.layerCount = (uint32_t)packed[19]; + blit.dstSubresource.layerCount = (uint32_t)packed[19]; + blit.srcSubresource.aspectMask = (VkImageAspectFlags)packed[20]; + blit.dstSubresource.aspectMask = (VkImageAspectFlags)packed[20]; + vkCmdBlitImage (commandBuffer, (VkImage)(uintptr_t)CombineVulkanHandle (sourceHigh, sourceLow), (VkImageLayout)sourceLayout, + (VkImage)(uintptr_t)CombineVulkanHandle (destinationHigh, destinationLow), (VkImageLayout)destinationLayout, 1, &blit, + (VkFilter)filter); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - value lime_lzma_decompress (value buffer, value bytes) { + bool lime_vk_cmd_push_constants (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int layoutHigh, int layoutLow, value state, value bytes) { - #ifdef LIME_LZMA - Bytes data (buffer); - Bytes result (bytes); +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)val_data (window); + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdPushConstants vkCmdPushConstants = + (PFN_vkCmdPushConstants)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdPushConstants"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkPipelineLayout layout = (VkPipelineLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + std::vector packed = GetVulkanIntVector (state); + Bytes data (bytes); + if (!vkCmdPushConstants || !commandBuffer || !layout || !data.b || packed.size () < 4) return false; + if (packed[2] < 0 || packed[3] < 0 || packed[2] + packed[3] > data.length) return false; - LZMA::Decompress (&data, &result); + vkCmdPushConstants (commandBuffer, layout, (VkShaderStageFlags)packed[0], (uint32_t)packed[1], (uint32_t)packed[3], data.b + packed[2]); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif - return result.Value (bytes); - #else - return alloc_null (); - #endif + } + + + HL_PRIM bool HL_NAME(hl_vk_cmd_push_constants) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int layoutHigh, int layoutLow, hl_varray* state, Bytes* bytes) { + +#ifdef LIME_VULKAN + Window* targetWindow = (Window*)window->ptr; + VkInstance instance = (VkInstance)(uintptr_t)CombineVulkanHandle (instanceHigh, instanceLow); + VkDevice device = (VkDevice)(uintptr_t)CombineVulkanHandle (deviceHigh, deviceLow); + PFN_vkCmdPushConstants vkCmdPushConstants = + (PFN_vkCmdPushConstants)GetManagedVulkanDeviceProc (targetWindow, instance, device, "vkCmdPushConstants"); + VkCommandBuffer commandBuffer = (VkCommandBuffer)(uintptr_t)CombineVulkanHandle (commandBufferHigh, commandBufferLow); + VkPipelineLayout layout = (VkPipelineLayout)(uintptr_t)CombineVulkanHandle (layoutHigh, layoutLow); + std::vector packed = GetHLVulkanIntVector (state); + if (!vkCmdPushConstants || !commandBuffer || !layout || !bytes || !bytes->b || packed.size () < 4) return false; + if (packed[2] < 0 || packed[3] < 0 || packed[2] + packed[3] > bytes->length) return false; + + vkCmdPushConstants (commandBuffer, layout, (VkShaderStageFlags)packed[0], (uint32_t)packed[1], (uint32_t)packed[3], bytes->b + packed[2]); + return true; +#else + lastVKError = "Lime was built without lime-vulkan support"; + return false; +#endif } - HL_PRIM Bytes* HL_NAME(hl_lzma_decompress) (Bytes* buffer, Bytes* bytes) { + bool lime_vulkan_renderer_set_overlay (value handle, value bytes, int width, int height, int x, int y) { - #ifdef LIME_LZMA - LZMA::Decompress (buffer, bytes); - return bytes; - #else - return 0; - #endif + if (val_is_null (handle)) return false; + VulkanRenderer* renderer = (VulkanRenderer*)val_data (handle); + Bytes data (bytes); + bool result = renderer->SetOverlay (data.b, width, height, x, y); + lastVulkanRendererError = renderer->GetLastError (); + return result; } - void lime_mouse_event_manager_register (value callback, value eventObject) { + HL_PRIM bool HL_NAME(hl_vulkan_renderer_set_overlay) (HL_CFFIPointer* handle, Bytes* bytes, int width, int height, int x, int y) { - MouseEvent::callback = new ValuePointer (callback); - MouseEvent::eventObject = new ValuePointer (eventObject); + if (!handle || !handle->ptr) return false; + VulkanRenderer* renderer = (VulkanRenderer*)handle->ptr; + bool result = renderer->SetOverlay (bytes ? bytes->b : 0, width, height, x, y); + lastVulkanRendererError = renderer->GetLastError (); + return result; } - HL_PRIM void HL_NAME(hl_mouse_event_manager_register) (vclosure* callback, MouseEvent* eventObject) { + bool lime_vulkan_renderer_clear_overlay (value handle) { - MouseEvent::callback = new ValuePointer (callback); - MouseEvent::eventObject = new ValuePointer ((vobj*)eventObject); + if (val_is_null (handle)) return false; + VulkanRenderer* renderer = (VulkanRenderer*)val_data (handle); + bool result = renderer->ClearOverlay (); + lastVulkanRendererError = renderer->GetLastError (); + return result; } - void lime_neko_execute (HxString module) { + HL_PRIM bool HL_NAME(hl_vulkan_renderer_clear_overlay) (HL_CFFIPointer* handle) { - #ifdef LIME_NEKO - NekoVM::Execute (module.c_str ()); - #endif + if (!handle || !handle->ptr) return false; + VulkanRenderer* renderer = (VulkanRenderer*)handle->ptr; + bool result = renderer->ClearOverlay (); + lastVulkanRendererError = renderer->GetLastError (); + return result; } - void lime_orientation_event_manager_register (value callback, value eventObject) { + bool lime_vulkan_renderer_render (value handle, double red, double green, double blue, double alpha) { - OrientationEvent::callback = new ValuePointer (callback); - OrientationEvent::eventObject = new ValuePointer (eventObject); - System::EnableDeviceOrientationChange(true); + if (val_is_null (handle)) return false; + VulkanRenderer* renderer = (VulkanRenderer*)val_data (handle); + bool result = renderer->Render (red, green, blue, alpha); + lastVulkanRendererError = renderer->GetLastError (); + return result; } - HL_PRIM void HL_NAME(hl_orientation_event_manager_register) (vclosure* callback, OrientationEvent* eventObject) { + HL_PRIM bool HL_NAME(hl_vulkan_renderer_render) (HL_CFFIPointer* handle, double red, double green, double blue, double alpha) { - OrientationEvent::callback = new ValuePointer (callback); - OrientationEvent::eventObject = new ValuePointer ((vobj*)eventObject); + if (!handle || !handle->ptr) return false; + VulkanRenderer* renderer = (VulkanRenderer*)handle->ptr; + bool result = renderer->Render (red, green, blue, alpha); + lastVulkanRendererError = renderer->GetLastError (); + return result; } - value lime_png_decode_bytes (value data, bool decodeData, value buffer) { + bool lime_vulkan_renderer_resize (value handle) { - ImageBuffer imageBuffer (buffer); - Bytes bytes (data); - Resource resource = Resource (&bytes); + if (val_is_null (handle)) return false; + VulkanRenderer* renderer = (VulkanRenderer*)val_data (handle); + bool result = renderer->Resize (); + lastVulkanRendererError = renderer->GetLastError (); + return result; - #ifdef LIME_PNG - if (PNG::Decode (&resource, &imageBuffer, decodeData)) { + } - return imageBuffer.Value (buffer); - } - #endif + HL_PRIM bool HL_NAME(hl_vulkan_renderer_resize) (HL_CFFIPointer* handle) { - return alloc_null (); + if (!handle || !handle->ptr) return false; + VulkanRenderer* renderer = (VulkanRenderer*)handle->ptr; + bool result = renderer->Resize (); + lastVulkanRendererError = renderer->GetLastError (); + return result; } +#else + static inline bool LimeVulkanUnavailableBool () { + lastVKError = "Lime was built without lime-vulkan support"; + return false; - HL_PRIM ImageBuffer* HL_NAME(hl_png_decode_bytes) (Bytes* data, bool decodeData, ImageBuffer* buffer) { + } - Resource resource = Resource (data); - #ifdef LIME_PNG - if (PNG::Decode (&resource, buffer, decodeData)) { + static inline value LimeVulkanUnavailableValue () { - return buffer; + lastVKError = "Lime was built without lime-vulkan support"; + return alloc_null (); + + } - } - #endif + static inline vdynamic* HLLimeVulkanUnavailableValue () { + + lastVKError = "Lime was built without lime-vulkan support"; return 0; } - value lime_png_decode_file (HxString path, bool decodeData, value buffer) { + static inline void LimeVulkanUnavailableVoid () { - ImageBuffer imageBuffer (buffer); - Resource resource = Resource (hxs_utf8 (path, nullptr)); + lastVKError = "Lime was built without lime-vulkan support"; - #ifdef LIME_PNG - if (PNG::Decode (&resource, &imageBuffer, decodeData)) { + } - return imageBuffer.Value (buffer); - } - #endif + bool lime_vk_update_descriptor_set_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int setHigh, + int setLow, int binding, int descriptorType, int bufferHigh, int bufferLow, int offset, int range) { - return alloc_null (); + return LimeVulkanUnavailableBool (); } - HL_PRIM ImageBuffer* HL_NAME(hl_png_decode_file) (hl_vstring* path, bool decodeData, ImageBuffer* buffer) { + HL_PRIM bool HL_NAME(hl_vk_update_descriptor_set_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int setHigh, int setLow, int binding, int descriptorType, int bufferHigh, int bufferLow, int offset, int range) { - Resource resource = Resource (path); + return LimeVulkanUnavailableBool (); - #ifdef LIME_PNG - if (PNG::Decode (&resource, buffer, decodeData)) { + } - return buffer; - } - #endif + bool lime_vk_update_descriptor_sets (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, value writes) { - return 0; + return LimeVulkanUnavailableBool (); } - void lime_render_event_manager_register (value callback, value eventObject) { + HL_PRIM bool HL_NAME(hl_vk_update_descriptor_sets) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, hl_varray* writes) { - RenderEvent::callback = new ValuePointer (callback); - RenderEvent::eventObject = new ValuePointer (eventObject); + return LimeVulkanUnavailableBool (); } - HL_PRIM void HL_NAME(hl_render_event_manager_register) (vclosure* callback, RenderEvent* eventObject) { + value lime_vk_create_pipeline_layout (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, value setLayouts, + int pushConstantStages, int pushConstantSize) { - RenderEvent::callback = new ValuePointer (callback); - RenderEvent::eventObject = new ValuePointer ((vobj*)eventObject); + return LimeVulkanUnavailableValue (); } - void lime_sensor_event_manager_register (value callback, value eventObject) { + HL_PRIM vdynamic* HL_NAME(hl_vk_create_pipeline_layout) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, hl_varray* setLayouts, int pushConstantStages, int pushConstantSize) { - SensorEvent::callback = new ValuePointer (callback); - SensorEvent::eventObject = new ValuePointer (eventObject); + return HLLimeVulkanUnavailableValue (); } - HL_PRIM void HL_NAME(hl_sensor_event_manager_register) (vclosure* callback, SensorEvent* eventObject) { + void lime_vk_destroy_pipeline_layout (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int layoutHigh, + int layoutLow) { - SensorEvent::callback = new ValuePointer (callback); - SensorEvent::eventObject = new ValuePointer ((vobj*)eventObject); + LimeVulkanUnavailableVoid (); } - bool lime_system_get_allow_screen_timeout () { + HL_PRIM void HL_NAME(hl_vk_destroy_pipeline_layout) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int layoutHigh, int layoutLow) { - return System::GetAllowScreenTimeout (); + LimeVulkanUnavailableVoid (); } - HL_PRIM bool HL_NAME(hl_system_get_allow_screen_timeout) () { + value lime_vk_create_pipeline_cache (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, value bytes, int byteOffset, + int byteLength) { - return System::GetAllowScreenTimeout (); + return LimeVulkanUnavailableValue (); } - value lime_system_get_device_model () { - - std::wstring* model = System::GetDeviceModel (); + HL_PRIM vdynamic* HL_NAME(hl_vk_create_pipeline_cache) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, Bytes* bytes, int byteOffset, int byteLength) { - if (model) { + return HLLimeVulkanUnavailableValue (); - value result = alloc_wstring (model->c_str ()); - delete model; - return result; + } - } else { - return alloc_null (); + void lime_vk_destroy_pipeline_cache (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int cacheHigh, + int cacheLow) { - } + LimeVulkanUnavailableVoid (); } - HL_PRIM vbyte* HL_NAME(hl_system_get_device_model) () { - - #ifndef EMSCRIPTEN - - std::wstring* model = System::GetDeviceModel (); + HL_PRIM void HL_NAME(hl_vk_destroy_pipeline_cache) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int cacheHigh, int cacheLow) { - if (model) { + LimeVulkanUnavailableVoid (); - vbyte* const result = hl_wstring_to_utf8_bytes (*model); - delete model; - return result; + } - } - #endif + bool lime_vk_get_pipeline_cache_data (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int cacheHigh, + int cacheLow, value bytes) { - return 0; + return LimeVulkanUnavailableBool (); } - value lime_system_get_device_vendor () { - - std::wstring* vendor = System::GetDeviceVendor (); + HL_PRIM bool HL_NAME(hl_vk_get_pipeline_cache_data) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int cacheHigh, int cacheLow, Bytes* bytes) { - if (vendor) { + return LimeVulkanUnavailableBool (); - value result = alloc_wstring (vendor->c_str ()); - delete vendor; - return result; + } - } else { - return alloc_null (); + value lime_vk_create_graphics_pipeline (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int renderPassHigh, + int renderPassLow, int layoutHigh, int layoutLow, int vertexShaderHigh, int vertexShaderLow, int fragmentShaderHigh, int fragmentShaderLow, + value state) { - } + return LimeVulkanUnavailableValue (); } - HL_PRIM vbyte* HL_NAME(hl_system_get_device_vendor) () { - - #ifndef EMSCRIPTEN - - std::wstring* vendor = System::GetDeviceVendor (); + HL_PRIM vdynamic* HL_NAME(hl_vk_create_graphics_pipeline) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int renderPassHigh, int renderPassLow, int layoutHigh, int layoutLow, int vertexShaderHigh, int vertexShaderLow, + int fragmentShaderHigh, int fragmentShaderLow, hl_varray* state) { - if (vendor) { + return HLLimeVulkanUnavailableValue (); - vbyte* const result = hl_wstring_to_utf8_bytes (*vendor); - delete vendor; - return result; + } - } - #endif + void lime_vk_destroy_pipeline (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int pipelineHigh, + int pipelineLow) { - return 0; + LimeVulkanUnavailableVoid (); } - value lime_system_get_directory (int type, HxString company, HxString title) { - - std::wstring* path = System::GetDirectory ((SystemDirectory)type, hxs_utf8 (company, nullptr), hxs_utf8 (title, nullptr)); + HL_PRIM void HL_NAME(hl_vk_destroy_pipeline) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int pipelineHigh, int pipelineLow) { - if (path) { + LimeVulkanUnavailableVoid (); - value result = alloc_wstring (path->c_str ()); - delete path; - return result; + } - } else { - return alloc_null (); + bool lime_vk_queue_submit_synced (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int queueHigh, + int queueLow, int commandBufferHigh, int commandBufferLow, value state) { - } + return LimeVulkanUnavailableBool (); } - HL_PRIM vbyte* HL_NAME(hl_system_get_directory) (int type, hl_vstring* company, hl_vstring* title) { - - #ifndef EMSCRIPTEN - - std::wstring* path = System::GetDirectory ((SystemDirectory)type, company ? (char*)hl_to_utf8 ((const uchar*)company->bytes) : NULL, title ? (char*)hl_to_utf8 ((const uchar*)title->bytes) : NULL); + HL_PRIM bool HL_NAME(hl_vk_queue_submit_synced) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int queueHigh, int queueLow, int commandBufferHigh, int commandBufferLow, hl_varray* state) { - if (path) { + return LimeVulkanUnavailableBool (); - vbyte* const result = hl_wstring_to_utf8_bytes (*path); - delete path; - return result; + } - } - #endif + bool lime_vk_cmd_begin_render_pass (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int renderPassHigh, int renderPassLow, int framebufferHigh, int framebufferLow, value state, value clear) { - return 0; + return LimeVulkanUnavailableBool (); } - value lime_system_get_display (int id) { + HL_PRIM bool HL_NAME(hl_vk_cmd_begin_render_pass) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int renderPassHigh, int renderPassLow, int framebufferHigh, int framebufferLow, + hl_varray* state, hl_varray* clear) { - return (value)System::GetDisplay (true, id); + return LimeVulkanUnavailableBool (); } - HL_PRIM vdynamic* HL_NAME(hl_system_get_display) (int id) { + bool lime_vk_cmd_end_render_pass (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow) { - return (vdynamic*)System::GetDisplay (false, id); + return LimeVulkanUnavailableBool (); } - bool lime_system_get_ios_tablet () { + HL_PRIM bool HL_NAME(hl_vk_cmd_end_render_pass) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow) { - #ifdef IPHONE - return System::GetIOSTablet (); - #else - return false; - #endif + return LimeVulkanUnavailableBool (); } - HL_PRIM bool HL_NAME(hl_system_get_ios_tablet) () { + bool lime_vk_cmd_bind_pipeline (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int pipelineHigh, int pipelineLow, int bindPoint) { - #ifdef IPHONE - return System::GetIOSTablet (); - #else - return false; - #endif + return LimeVulkanUnavailableBool (); } - int lime_system_get_num_displays () { + HL_PRIM bool HL_NAME(hl_vk_cmd_bind_pipeline) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int pipelineHigh, int pipelineLow, int bindPoint) { - return System::GetNumDisplays (); + return LimeVulkanUnavailableBool (); } - HL_PRIM int HL_NAME(hl_system_get_num_displays) () { + bool lime_vk_cmd_bind_descriptor_set (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int layoutHigh, int layoutLow, int setHigh, int setLow, int bindPoint, int firstSet) { - return System::GetNumDisplays (); + return LimeVulkanUnavailableBool (); } - int lime_system_get_device_orientation () { + HL_PRIM bool HL_NAME(hl_vk_cmd_bind_descriptor_set) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int layoutHigh, int layoutLow, int setHigh, int setLow, int bindPoint, + int firstSet) { - return System::GetDeviceOrientation(); + return LimeVulkanUnavailableBool (); } - HL_PRIM int HL_NAME(hl_system_get_device_orientation) () { + bool lime_vk_cmd_bind_descriptor_set_ex (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int layoutHigh, int layoutLow, int setHigh, int setLow, value state) { - return System::GetDeviceOrientation(); + return LimeVulkanUnavailableBool (); } - value lime_system_get_platform_label () { - - std::wstring* label = System::GetPlatformLabel (); + HL_PRIM bool HL_NAME(hl_vk_cmd_bind_descriptor_set_ex) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int layoutHigh, int layoutLow, int setHigh, int setLow, hl_varray* state) { - if (label) { + return LimeVulkanUnavailableBool (); - value result = alloc_wstring (label->c_str ()); - delete label; - return result; + } - } else { - return alloc_null (); + bool lime_vk_cmd_bind_descriptor_set_dynamic_offset (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int layoutHigh, int layoutLow, int setHigh, int setLow, int dynamicOffset, int firstSet, + int bindPoint) { - } + return LimeVulkanUnavailableBool (); } - HL_PRIM vbyte* HL_NAME(hl_system_get_platform_label) () { + HL_PRIM bool HL_NAME(hl_vk_cmd_bind_descriptor_set_dynamic_offset) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, + int deviceHigh, int deviceLow, int commandBufferHigh, int commandBufferLow, int layoutHigh, int layoutLow, int setHigh, int setLow, + int dynamicOffset, int firstSet, int bindPoint) { - #ifndef EMSCRIPTEN + return LimeVulkanUnavailableBool (); - std::wstring* label = System::GetPlatformLabel (); + } - if (label) { - vbyte* const result = hl_wstring_to_utf8_bytes (*label); - delete label; - return result; + bool lime_vk_cmd_bind_vertex_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int bufferHigh, int bufferLow, int binding, int offsetHigh, int offsetLow) { - } + return LimeVulkanUnavailableBool (); - #endif + } - return 0; - } + HL_PRIM bool HL_NAME(hl_vk_cmd_bind_vertex_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int binding, int offsetHigh, int offsetLow) { + return LimeVulkanUnavailableBool (); - value lime_system_get_platform_name () { + } - std::wstring* name = System::GetPlatformName (); - if (name) { + bool lime_vk_cmd_bind_index_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int bufferHigh, int bufferLow, int offsetHigh, int offsetLow, int indexType) { - value result = alloc_wstring (name->c_str ()); - delete name; - return result; + return LimeVulkanUnavailableBool (); - } else { + } - return 0; - } + HL_PRIM bool HL_NAME(hl_vk_cmd_bind_index_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int offsetHigh, int offsetLow, int indexType) { + + return LimeVulkanUnavailableBool (); } - HL_PRIM vbyte* HL_NAME(hl_system_get_platform_name) () { + bool lime_vk_cmd_set_viewport (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, double x, double y, double width, double height, double minDepth, double maxDepth) { - #ifndef EMSCRIPTEN + return LimeVulkanUnavailableBool (); - std::wstring* name = System::GetPlatformName (); + } - if (name) { - vbyte* const result = hl_wstring_to_utf8_bytes (*name); - delete name; - return result; + HL_PRIM bool HL_NAME(hl_vk_cmd_set_viewport) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, double x, double y, double width, double height, double minDepth, double maxDepth) { - } + return LimeVulkanUnavailableBool (); - #endif + } - return 0; - } + bool lime_vk_cmd_set_scissor (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int x, int y, int width, int height) { + return LimeVulkanUnavailableBool (); - value lime_system_get_platform_version () { + } - std::wstring* version = System::GetPlatformVersion (); - if (version) { + HL_PRIM bool HL_NAME(hl_vk_cmd_set_scissor) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int x, int y, int width, int height) { - value result = alloc_wstring (version->c_str ()); - delete version; - return result; + return LimeVulkanUnavailableBool (); - } else { + } - return alloc_null (); - } + bool lime_vk_cmd_draw (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int vertexCount, int instanceCount, int firstVertex, int firstInstance) { - } + return LimeVulkanUnavailableBool (); + } - HL_PRIM vbyte* HL_NAME(hl_system_get_platform_version) () { - #ifndef EMSCRIPTEN + HL_PRIM bool HL_NAME(hl_vk_cmd_draw) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int vertexCount, int instanceCount, int firstVertex, int firstInstance) { - std::wstring* version = System::GetPlatformVersion (); + return LimeVulkanUnavailableBool (); - if (version) { + } - vbyte* const result = hl_wstring_to_utf8_bytes (*version); - delete version; - return result; - } - #endif + bool lime_vk_cmd_draw_indexed (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int indexCount, int instanceCount, int firstIndex, int vertexOffset, int firstInstance) { - return 0; + return LimeVulkanUnavailableBool (); } - double lime_system_get_timer () { + HL_PRIM bool HL_NAME(hl_vk_cmd_draw_indexed) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int indexCount, int instanceCount, int firstIndex, int vertexOffset, int firstInstance) { - return System::GetTimer (); + return LimeVulkanUnavailableBool (); } - HL_PRIM double HL_NAME(hl_system_get_timer) () { + bool lime_vk_cmd_draw_indirect (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int bufferHigh, int bufferLow, int offsetHigh, int offsetLow, int drawCount, int stride) { - return System::GetTimer (); + return LimeVulkanUnavailableBool (); } - int lime_system_get_windows_console_mode (int handleType) { + HL_PRIM bool HL_NAME(hl_vk_cmd_draw_indirect) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int offsetHigh, int offsetLow, int drawCount, int stride) { - #if defined (HX_WINDOWS) && !defined (HX_WINRT) - return System::GetWindowsConsoleMode (handleType); - #else - return 0; - #endif + return LimeVulkanUnavailableBool (); } - HL_PRIM int HL_NAME(hl_system_get_windows_console_mode) (int handleType) { + bool lime_vk_cmd_draw_indexed_indirect (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int offsetHigh, int offsetLow, int drawCount, int stride) { - #if defined (HX_WINDOWS) && !defined (HX_WINRT) - return System::GetWindowsConsoleMode (handleType); - #else - return 0; - #endif + return LimeVulkanUnavailableBool (); } - void lime_system_open_file (HxString path) { + HL_PRIM bool HL_NAME(hl_vk_cmd_draw_indexed_indirect) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int offsetHigh, int offsetLow, int drawCount, + int stride) { - #ifdef IPHONE - System::OpenFile (path.c_str ()); - #endif + return LimeVulkanUnavailableBool (); } - HL_PRIM void HL_NAME(hl_system_open_file) (vbyte* path) { + bool lime_vk_cmd_copy_buffer (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int sourceHigh, int sourceLow, int destinationHigh, int destinationLow, value state) { - #ifdef IPHONE - System::OpenFile ((char*)path); - #endif + return LimeVulkanUnavailableBool (); } - void lime_system_open_url (HxString url, HxString target) { + HL_PRIM bool HL_NAME(hl_vk_cmd_copy_buffer) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int sourceHigh, int sourceLow, int destinationHigh, int destinationLow, hl_varray* state) { - #ifdef IPHONE - System::OpenURL (url.c_str (), target.c_str ()); - #endif + return LimeVulkanUnavailableBool (); } + bool lime_vk_cmd_copy_buffer_to_image_region (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int imageHigh, int imageLow, value state) { - HL_PRIM void HL_NAME(hl_system_open_url) (vbyte* url, vbyte* target) { - - #ifdef IPHONE - System::OpenURL ((char*)url, (char*)target); - #endif + return LimeVulkanUnavailableBool (); } - bool lime_system_set_allow_screen_timeout (bool allow) { + HL_PRIM bool HL_NAME(hl_vk_cmd_copy_buffer_to_image_region) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int bufferHigh, int bufferLow, int imageHigh, int imageLow, hl_varray* state) { - return System::SetAllowScreenTimeout (allow); + return LimeVulkanUnavailableBool (); } - HL_PRIM bool HL_NAME(hl_system_set_allow_screen_timeout) (bool allow) { + bool lime_vk_cmd_copy_image_to_buffer_region (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, int bufferHigh, int bufferLow, value state) { - return System::SetAllowScreenTimeout (allow); + return LimeVulkanUnavailableBool (); } - bool lime_system_set_windows_console_mode (int handleType, int mode) { + HL_PRIM bool HL_NAME(hl_vk_cmd_copy_image_to_buffer_region) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, int bufferHigh, int bufferLow, hl_varray* state) { - #if defined (HX_WINDOWS) && !defined (HX_WINRT) - return System::SetWindowsConsoleMode (handleType, mode); - #else - return false; - #endif + return LimeVulkanUnavailableBool (); } - HL_PRIM bool HL_NAME(hl_system_set_windows_console_mode) (int handleType, int mode) { + bool lime_vk_cmd_pipeline_barrier_image (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, value state) { - #if defined (HX_WINDOWS) && !defined (HX_WINRT) - return System::SetWindowsConsoleMode (handleType, mode); - #else - return false; - #endif + return LimeVulkanUnavailableBool (); } - void lime_text_event_manager_register (value callback, value eventObject) { + HL_PRIM bool HL_NAME(hl_vk_cmd_pipeline_barrier_image) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, hl_varray* state) { - TextEvent::callback = new ValuePointer (callback); - TextEvent::eventObject = new ValuePointer (eventObject); + return LimeVulkanUnavailableBool (); } - HL_PRIM void HL_NAME(hl_text_event_manager_register) (vclosure* callback, TextEvent* eventObject) { + bool lime_vk_cmd_clear_color_image (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int imageHigh, int imageLow, int layout, int aspectMask, value clear) { - TextEvent::callback = new ValuePointer (callback); - TextEvent::eventObject = new ValuePointer ((vobj*)eventObject); + return LimeVulkanUnavailableBool (); } - void lime_touch_event_manager_register (value callback, value eventObject) { + HL_PRIM bool HL_NAME(hl_vk_cmd_clear_color_image) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, int layout, int aspectMask, hl_varray* clear) { - TouchEvent::callback = new ValuePointer (callback); - TouchEvent::eventObject = new ValuePointer (eventObject); + return LimeVulkanUnavailableBool (); } - HL_PRIM void HL_NAME(hl_touch_event_manager_register) (vclosure* callback, TouchEvent* eventObject) { + bool lime_vk_cmd_clear_depth_stencil_image (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, value state, value clear) { - TouchEvent::callback = new ValuePointer (callback); - TouchEvent::eventObject = new ValuePointer ((vobj*)eventObject); + return LimeVulkanUnavailableBool (); } - void lime_window_alert (value window, HxString message, HxString title) { + HL_PRIM bool HL_NAME(hl_vk_cmd_clear_depth_stencil_image) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, int imageHigh, int imageLow, hl_varray* state, hl_varray* clear) { - Window* targetWindow = (Window*)val_data (window); - targetWindow->Alert (hxs_utf8 (message, nullptr), hxs_utf8 (title, nullptr)); + return LimeVulkanUnavailableBool (); } - HL_PRIM void HL_NAME(hl_window_alert) (HL_CFFIPointer* window, hl_vstring* message, hl_vstring* title) { + bool lime_vk_cmd_clear_attachments (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, value state, value clear) { - Window* targetWindow = (Window*)window->ptr; - const char *cmessage = message ? hl_to_utf8(message->bytes) : nullptr; - const char *ctitle = title ? hl_to_utf8(title->bytes) : nullptr; - targetWindow->Alert (cmessage, ctitle); + return LimeVulkanUnavailableBool (); } - void lime_window_close (value window) { + HL_PRIM bool HL_NAME(hl_vk_cmd_clear_attachments) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, + int deviceLow, int commandBufferHigh, int commandBufferLow, hl_varray* state, hl_varray* clear) { - Window* targetWindow = (Window*)val_data (window); - targetWindow->Close (); + return LimeVulkanUnavailableBool (); } - HL_PRIM void HL_NAME(hl_window_close) (HL_CFFIPointer* window) { + bool lime_vk_cmd_blit_image (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int sourceHigh, int sourceLow, int destinationHigh, int destinationLow, value state) { - Window* targetWindow = (Window*)window->ptr; - targetWindow->Close (); + return LimeVulkanUnavailableBool (); } - void lime_window_context_flip (value window) { + HL_PRIM bool HL_NAME(hl_vk_cmd_blit_image) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int sourceHigh, int sourceLow, int destinationHigh, int destinationLow, hl_varray* state) { - ((Window*)val_data (window))->ContextFlip (); + return LimeVulkanUnavailableBool (); } - HL_PRIM void HL_NAME(hl_window_context_flip) (HL_CFFIPointer* window) { + bool lime_vk_cmd_push_constants (value window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, int commandBufferHigh, + int commandBufferLow, int layoutHigh, int layoutLow, value state, value bytes) { - ((Window*)window->ptr)->ContextFlip (); + return LimeVulkanUnavailableBool (); } - value lime_window_context_lock (value window) { + HL_PRIM bool HL_NAME(hl_vk_cmd_push_constants) (HL_CFFIPointer* window, int instanceHigh, int instanceLow, int deviceHigh, int deviceLow, + int commandBufferHigh, int commandBufferLow, int layoutHigh, int layoutLow, hl_varray* state, Bytes* bytes) { - return (value)((Window*)val_data (window))->ContextLock (true); + return LimeVulkanUnavailableBool (); } - HL_PRIM vdynamic* HL_NAME(hl_window_context_lock) (HL_CFFIPointer* window) { + value lime_vulkan_renderer_create (value window, HxString applicationName) { - return (vdynamic*)((Window*)window->ptr)->ContextLock (false); + lastVulkanRendererError = "Lime was built without lime-vulkan support"; + return alloc_null (); } - void lime_window_context_make_current (value window) { + HL_PRIM HL_CFFIPointer* HL_NAME(hl_vulkan_renderer_create) (HL_CFFIPointer* window, hl_vstring* applicationName) { - ((Window*)val_data (window))->ContextMakeCurrent (); + lastVulkanRendererError = "Lime was built without lime-vulkan support"; + return 0; } - HL_PRIM void HL_NAME(hl_window_context_make_current) (HL_CFFIPointer* window) { + void lime_vulkan_renderer_destroy (value handle) {} - ((Window*)window->ptr)->ContextMakeCurrent (); - } + HL_PRIM void HL_NAME(hl_vulkan_renderer_destroy) (HL_CFFIPointer* handle) {} - void lime_window_context_unlock (value window) { + value lime_vulkan_renderer_get_info (value handle) { - ((Window*)val_data (window))->ContextUnlock (); + return alloc_string (""); } - HL_PRIM void HL_NAME(hl_window_context_unlock) (HL_CFFIPointer* window) { + HL_PRIM vbyte* HL_NAME(hl_vulkan_renderer_get_info) (HL_CFFIPointer* handle) { - ((Window*)window->ptr)->ContextUnlock (); + return hl_copy_bytes ((const vbyte*)"", 1); } - value lime_window_create (value application, int width, int height, int flags, HxString title) { + value lime_vulkan_renderer_get_last_error () { - Window* window = CreateWindow ((Application*)val_data (application), width, height, flags, hxs_utf8 (title, nullptr)); - return CFFIPointer (window, gc_window); + return alloc_string (lastVulkanRendererError.c_str ()); } - HL_PRIM HL_CFFIPointer* HL_NAME(hl_window_create) (HL_CFFIPointer* application, int width, int height, int flags, hl_vstring* title) { + HL_PRIM vbyte* HL_NAME(hl_vulkan_renderer_get_last_error) () { - Window* window = CreateWindow ((Application*)application->ptr, width, height, flags, (const char*)hl_to_utf8 ((const uchar*)title->bytes)); - return HLCFFIPointer (window, (hl_finalizer)hl_gc_window); + return hl_copy_bytes ((const vbyte*)lastVulkanRendererError.c_str (), (int)lastVulkanRendererError.size () + 1); } - void lime_window_event_manager_register (value callback, value eventObject) { + bool lime_vulkan_renderer_set_overlay (value handle, value bytes, int width, int height, int x, int y) { - WindowEvent::callback = new ValuePointer (callback); - WindowEvent::eventObject = new ValuePointer (eventObject); + lastVulkanRendererError = "Lime was built without lime-vulkan support"; + return false; } - HL_PRIM void HL_NAME(hl_window_event_manager_register) (vclosure* callback, WindowEvent* eventObject) { + HL_PRIM bool HL_NAME(hl_vulkan_renderer_set_overlay) (HL_CFFIPointer* handle, Bytes* bytes, int width, int height, int x, int y) { - WindowEvent::callback = new ValuePointer (callback); - WindowEvent::eventObject = new ValuePointer ((vobj*)eventObject); + lastVulkanRendererError = "Lime was built without lime-vulkan support"; + return false; } - void lime_window_focus (value window) { + bool lime_vulkan_renderer_clear_overlay (value handle) { - Window* targetWindow = (Window*)val_data (window); - targetWindow->Focus (); + lastVulkanRendererError = "Lime was built without lime-vulkan support"; + return false; } - HL_PRIM void HL_NAME(hl_window_focus) (HL_CFFIPointer* window) { + HL_PRIM bool HL_NAME(hl_vulkan_renderer_clear_overlay) (HL_CFFIPointer* handle) { - Window* targetWindow = (Window*)window->ptr; - targetWindow->Focus (); + lastVulkanRendererError = "Lime was built without lime-vulkan support"; + return false; } - double lime_window_get_context (value window) { + bool lime_vulkan_renderer_render (value handle, double red, double green, double blue, double alpha) { - Window* targetWindow = (Window*)val_data (window); - return (uintptr_t)targetWindow->GetContext (); + lastVulkanRendererError = "Lime was built without lime-vulkan support"; + return false; } - HL_PRIM double HL_NAME(hl_window_get_context) (HL_CFFIPointer* window) { + HL_PRIM bool HL_NAME(hl_vulkan_renderer_render) (HL_CFFIPointer* handle, double red, double green, double blue, double alpha) { - Window* targetWindow = (Window*)window->ptr; - return (uintptr_t)targetWindow->GetContext (); + lastVulkanRendererError = "Lime was built without lime-vulkan support"; + return false; } - value lime_window_get_context_type (value window) { + bool lime_vulkan_renderer_resize (value handle) { - Window* targetWindow = (Window*)val_data (window); - const char* type = targetWindow->GetContextType (); - return type ? alloc_string (type) : alloc_null (); + lastVulkanRendererError = "Lime was built without lime-vulkan support"; + return false; } - HL_PRIM vbyte* HL_NAME(hl_window_get_context_type) (HL_CFFIPointer* window) { + HL_PRIM bool HL_NAME(hl_vulkan_renderer_resize) (HL_CFFIPointer* handle) { - Window* targetWindow = (Window*)window->ptr; - return (vbyte*)targetWindow->GetContextType (); + lastVulkanRendererError = "Lime was built without lime-vulkan support"; + return false; } +#endif int lime_window_get_display (value window) { @@ -3617,6 +13302,22 @@ namespace lime { } + bool lime_window_get_transparent (value window) { + + Window* targetWindow = (Window*)val_data (window); + return targetWindow->GetTransparent (); + + } + + + HL_PRIM bool HL_NAME(hl_window_get_transparent) (HL_CFFIPointer* window) { + + Window* targetWindow = (Window*)window->ptr; + return targetWindow->GetTransparent (); + + } + + double lime_window_get_opacity (value window) { Window* targetWindow = (Window*)val_data (window); @@ -3729,6 +13430,23 @@ namespace lime { } + int lime_window_popup_menu (value window, value data, int x, int y, bool atCursor) { + + Window* targetWindow = (Window*)val_data (window); + Bytes bytes (data); + return targetWindow->PopupMenu (bytes.b, bytes.length, x, y, atCursor); + + } + + + HL_PRIM int HL_NAME(hl_window_popup_menu) (HL_CFFIPointer* window, Bytes* data, int x, int y, bool atCursor) { + + Window* targetWindow = (Window*)window->ptr; + return targetWindow->PopupMenu (data ? data->b : 0, data ? data->length : 0, x, y, atCursor); + + } + + value lime_window_read_pixels (value window, value rect, value imageBuffer) { Window* targetWindow = (Window*)val_data (window); @@ -3919,6 +13637,23 @@ namespace lime { } + bool lime_window_set_menu (value window, value data) { + + Window* targetWindow = (Window*)val_data (window); + Bytes bytes (data); + return targetWindow->SetMenu (bytes.b, bytes.length); + + } + + + HL_PRIM bool HL_NAME(hl_window_set_menu) (HL_CFFIPointer* window, Bytes* data) { + + Window* targetWindow = (Window*)window->ptr; + return targetWindow->SetMenu (data ? data->b : 0, data ? data->length : 0); + + } + + bool lime_window_set_minimized (value window, bool minimized) { Window* targetWindow = (Window*)val_data (window); @@ -3935,6 +13670,112 @@ namespace lime { } + bool lime_window_flash (value window, int mode) { + + Window* targetWindow = (Window*)val_data (window); + return targetWindow->Flash (mode); + + } + + + HL_PRIM bool HL_NAME(hl_window_flash) (HL_CFFIPointer* window, int mode) { + + Window* targetWindow = (Window*)window->ptr; + return targetWindow->Flash (mode); + + } + + + bool lime_window_get_keyboard_grab (value window) { + + Window* targetWindow = (Window*)val_data (window); + return targetWindow->GetKeyboardGrab (); + + } + + + HL_PRIM bool HL_NAME(hl_window_get_keyboard_grab) (HL_CFFIPointer* window) { + + Window* targetWindow = (Window*)window->ptr; + return targetWindow->GetKeyboardGrab (); + + } + + + value lime_window_get_mouse_rect (value window) { + + Window* targetWindow = (Window*)val_data (window); + int x, y, width, height; + + if (!targetWindow->GetMouseRect (&x, &y, &width, &height)) { + + return alloc_null (); + + } + + value rect = alloc_empty_object (); + alloc_field (rect, val_id ("x"), alloc_int (x)); + alloc_field (rect, val_id ("y"), alloc_int (y)); + alloc_field (rect, val_id ("width"), alloc_int (width)); + alloc_field (rect, val_id ("height"), alloc_int (height)); + return rect; + + } + + + HL_PRIM vdynamic* HL_NAME(hl_window_get_mouse_rect) (HL_CFFIPointer* window) { + + Window* targetWindow = (Window*)window->ptr; + int x, y, width, height; + + if (!targetWindow->GetMouseRect (&x, &y, &width, &height)) { + + return NULL; + + } + + vdynamic* rect = (vdynamic*)hl_alloc_dynobj (); + hl_dyn_seti (rect, hl_hash_utf8 ("x"), &hlt_i32, x); + hl_dyn_seti (rect, hl_hash_utf8 ("y"), &hlt_i32, y); + hl_dyn_seti (rect, hl_hash_utf8 ("width"), &hlt_i32, width); + hl_dyn_seti (rect, hl_hash_utf8 ("height"), &hlt_i32, height); + return rect; + + } + + + void lime_window_set_keyboard_grab (value window, bool keyboardGrab) { + + Window* targetWindow = (Window*)val_data (window); + targetWindow->SetKeyboardGrab (keyboardGrab); + + } + + + HL_PRIM void HL_NAME(hl_window_set_keyboard_grab) (HL_CFFIPointer* window, bool keyboardGrab) { + + Window* targetWindow = (Window*)window->ptr; + targetWindow->SetKeyboardGrab (keyboardGrab); + + } + + + void lime_window_set_mouse_rect (value window, int x, int y, int width, int height) { + + Window* targetWindow = (Window*)val_data (window); + targetWindow->SetMouseRect (x, y, width, height); + + } + + + HL_PRIM void HL_NAME(hl_window_set_mouse_rect) (HL_CFFIPointer* window, int x, int y, int width, int height) { + + Window* targetWindow = (Window*)window->ptr; + targetWindow->SetMouseRect (x, y, width, height); + + } + + void lime_window_set_mouse_lock (value window, bool mouseLock) { Window* targetWindow = (Window*)val_data (window); @@ -4165,6 +14006,7 @@ namespace lime { } + DEFINE_PRIME0v (lime_application_allow_high_dpi); DEFINE_PRIME0 (lime_application_create); DEFINE_PRIME2v (lime_application_event_manager_register); DEFINE_PRIME1 (lime_application_exec); @@ -4172,6 +14014,9 @@ namespace lime { DEFINE_PRIME1 (lime_application_quit); DEFINE_PRIME6v (lime_application_set_main_loop); DEFINE_PRIME2v (lime_application_set_frame_rate); + DEFINE_PRIME5v (lime_application_set_modal_callbacks); + DEFINE_PRIME1v (lime_application_defer_modal_exception); + DEFINE_PRIME2 (lime_application_set_menu); DEFINE_PRIME2v (lime_application_set_vsync_mode); DEFINE_PRIME1 (lime_application_update); DEFINE_PRIME2 (lime_audio_load); @@ -4189,6 +14034,10 @@ namespace lime { DEFINE_PRIME2 (lime_data_pointer_offset); DEFINE_PRIME2 (lime_deflate_compress); DEFINE_PRIME2 (lime_deflate_decompress); + DEFINE_PRIME1v (lime_dock_icon_bounce); + DEFINE_PRIME0 (lime_dock_icon_is_supported); + DEFINE_PRIME1v (lime_dock_icon_set_icon); + DEFINE_PRIME1 (lime_dock_icon_set_menu); DEFINE_PRIME2v (lime_drop_event_manager_register); DEFINE_PRIME3 (lime_file_dialog_open_directory); DEFINE_PRIME3 (lime_file_dialog_open_file); @@ -4211,6 +14060,8 @@ namespace lime { DEFINE_PRIME1 (lime_font_get_strikethrough_position); DEFINE_PRIME1 (lime_font_get_strikethrough_thickness); DEFINE_PRIME1 (lime_font_get_units_per_em); + DEFINE_PRIME1 (lime_font_is_bold); + DEFINE_PRIME1 (lime_font_is_italic); DEFINE_PRIME1 (lime_font_load); DEFINE_PRIME1 (lime_font_load_bytes); DEFINE_PRIME1 (lime_font_load_file); @@ -4220,11 +14071,23 @@ namespace lime { DEFINE_PRIME4 (lime_font_render_glyph_with_flags); DEFINE_PRIME3 (lime_font_render_glyphs); DEFINE_PRIME3v (lime_font_set_size); + DEFINE_PRIME2v (lime_font_set_stem_darkening); + DEFINE_PRIME2v (lime_font_set_stem_darkening_parameters); DEFINE_PRIME1v (lime_gamepad_add_mappings); DEFINE_PRIME2v (lime_gamepad_event_manager_register); DEFINE_PRIME1 (lime_gamepad_get_device_guid); DEFINE_PRIME1 (lime_gamepad_get_device_name); DEFINE_PRIME4v (lime_gamepad_rumble); + DEFINE_PRIME0 (lime_gamepad_attach_virtual); + DEFINE_PRIME1v (lime_gamepad_detach_virtual); + DEFINE_PRIME1 (lime_gamepad_get_type); + DEFINE_PRIME1 (lime_gamepad_get_virtual_output); + DEFINE_PRIME1 (lime_gamepad_has_led); + DEFINE_PRIME1 (lime_gamepad_has_rumble_triggers); + DEFINE_PRIME2 (lime_gamepad_has_sensor); + DEFINE_PRIME4 (lime_gamepad_rumble_triggers); + DEFINE_PRIME4 (lime_gamepad_set_led); + DEFINE_PRIME3 (lime_gamepad_set_sensor_enabled); DEFINE_PRIME2 (lime_gzip_compress); DEFINE_PRIME2 (lime_gzip_decompress); DEFINE_PRIME2v (lime_haptic_vibrate); @@ -4257,9 +14120,12 @@ namespace lime { DEFINE_PRIME1 (lime_key_code_from_scan_code); DEFINE_PRIME1 (lime_key_code_to_scan_code); DEFINE_PRIME2v (lime_key_event_manager_register); + DEFINE_PRIME0 (lime_locale_get_preferred_locales); DEFINE_PRIME0 (lime_locale_get_system_locale); DEFINE_PRIME2 (lime_lzma_compress); DEFINE_PRIME2 (lime_lzma_decompress); + DEFINE_PRIME2v (lime_menu_event_manager_register); + DEFINE_PRIME0 (lime_menu_get_support); DEFINE_PRIME2v (lime_mouse_event_manager_register); DEFINE_PRIME1v (lime_neko_execute); DEFINE_PRIME2v (lime_orientation_event_manager_register); @@ -4286,6 +14152,15 @@ namespace lime { DEFINE_PRIME2 (lime_system_set_windows_console_mode); DEFINE_PRIME2v (lime_text_event_manager_register); DEFINE_PRIME2v (lime_touch_event_manager_register); + DEFINE_PRIME1v (lime_tray_icon_close); + DEFINE_PRIME0 (lime_tray_icon_create); + DEFINE_PRIME2v (lime_tray_icon_event_manager_register); + DEFINE_PRIME1 (lime_tray_icon_get_id); + DEFINE_PRIME0 (lime_tray_icon_is_supported); + DEFINE_PRIME2 (lime_tray_icon_popup_menu); + DEFINE_PRIME2v (lime_tray_icon_set_icon); + DEFINE_PRIME2 (lime_tray_icon_set_menu); + DEFINE_PRIME2v (lime_tray_icon_set_tooltip); DEFINE_PRIME3v (lime_window_alert); DEFINE_PRIME1v (lime_window_close); DEFINE_PRIME1v (lime_window_context_flip); @@ -4297,17 +14172,128 @@ namespace lime { DEFINE_PRIME1v (lime_window_focus); DEFINE_PRIME1 (lime_window_get_context); DEFINE_PRIME1 (lime_window_get_context_type); + DEFINE_PRIME3 (lime_window_create_vulkan_surface); DEFINE_PRIME1 (lime_window_get_display); DEFINE_PRIME1 (lime_window_get_display_mode); DEFINE_PRIME1 (lime_window_get_height); DEFINE_PRIME1 (lime_window_get_id); DEFINE_PRIME1 (lime_window_get_mouse_lock); + DEFINE_PRIME1 (lime_window_get_transparent); DEFINE_PRIME1 (lime_window_get_scale); DEFINE_PRIME1 (lime_window_get_text_input_enabled); + DEFINE_PRIME1 (lime_window_get_vulkan_drawable_size); + DEFINE_PRIME1 (lime_window_get_vulkan_instance_extensions); + DEFINE_PRIME1 (lime_window_get_vulkan_instance_proc_addr); + DEFINE_PRIME2 (lime_vk_create_instance); + DEFINE_PRIME3v (lime_vk_destroy_instance); + DEFINE_PRIME5v (lime_vk_destroy_surface); + DEFINE_PRIME5 (lime_vk_get_physical_devices); + DEFINE_PRIME7 (lime_vk_create_device); + DEFINE_PRIME5v (lime_vk_destroy_device); + DEFINE_PRIME5 (lime_vk_device_wait_idle); + DEFINE_PRIME7 (lime_vk_queue_wait_idle); + DEFINE_PRIME7 (lime_vk_create_command_pool); + DEFINE_PRIME7v (lime_vk_destroy_command_pool); + DEFINE_PRIME8 (lime_vk_reset_command_pool); + DEFINE_PRIME8 (lime_vk_allocate_command_buffer); + DEFINE_PRIME9v (lime_vk_free_command_buffer); + DEFINE_PRIME8 (lime_vk_begin_command_buffer); + DEFINE_PRIME7 (lime_vk_end_command_buffer); + DEFINE_PRIME8 (lime_vk_reset_command_buffer); + DEFINE_PRIME5 (lime_vk_create_semaphore); + DEFINE_PRIME7v (lime_vk_destroy_semaphore); + DEFINE_PRIME6 (lime_vk_create_fence); + DEFINE_PRIME7v (lime_vk_destroy_fence); + DEFINE_PRIME9 (lime_vk_wait_for_fence); + DEFINE_PRIME7 (lime_vk_reset_fence); + DEFINE_PRIME11 (lime_vk_queue_submit); + DEFINE_PRIME11 (lime_vk_allocate_memory); + DEFINE_PRIME7v (lime_vk_free_memory); + DEFINE_PRIME12 (lime_vk_upload_memory); + DEFINE_PRIME12 (lime_vk_download_memory); + DEFINE_PRIME12 (lime_vk_map_memory); + DEFINE_PRIME7v (lime_vk_unmap_memory); + DEFINE_PRIME12 (lime_vk_write_mapped_memory); + DEFINE_PRIME11 (lime_vk_flush_mapped_memory); + DEFINE_PRIME11 (lime_vk_invalidate_mapped_memory); + DEFINE_PRIME8 (lime_vk_create_buffer); + DEFINE_PRIME7v (lime_vk_destroy_buffer); + DEFINE_PRIME7 (lime_vk_get_buffer_memory_requirements); + DEFINE_PRIME11 (lime_vk_bind_buffer_memory); + DEFINE_PRIME15 (lime_vk_create_image); + DEFINE_PRIME7v (lime_vk_destroy_image); + DEFINE_PRIME7 (lime_vk_get_image_memory_requirements); + DEFINE_PRIME11 (lime_vk_bind_image_memory); + DEFINE_PRIME15 (lime_vk_create_swapchain); + DEFINE_PRIME7v (lime_vk_destroy_swapchain); + DEFINE_PRIME7 (lime_vk_get_swapchain_images); + DEFINE_PRIME13 (lime_vk_acquire_next_image); + DEFINE_PRIME12 (lime_vk_queue_present); + DEFINE_PRIME10 (lime_vk_create_image_view); + DEFINE_PRIME7v (lime_vk_destroy_image_view); + DEFINE_PRIME14 (lime_vk_create_image_view_ex); + DEFINE_PRIME6 (lime_vk_create_render_pass); + DEFINE_PRIME7v (lime_vk_destroy_render_pass); + DEFINE_PRIME11 (lime_vk_create_framebuffer); + DEFINE_PRIME7v (lime_vk_destroy_framebuffer); + DEFINE_PRIME8 (lime_vk_create_shader_module); + DEFINE_PRIME7v (lime_vk_destroy_shader_module); + DEFINE_PRIME13 (lime_vk_create_sampler); + DEFINE_PRIME7v (lime_vk_destroy_sampler); + DEFINE_PRIME6 (lime_vk_create_descriptor_set_layout); + DEFINE_PRIME7v (lime_vk_destroy_descriptor_set_layout); + DEFINE_PRIME6 (lime_vk_create_descriptor_pool); + DEFINE_PRIME7v (lime_vk_destroy_descriptor_pool); + DEFINE_PRIME8 (lime_vk_reset_descriptor_pool); + DEFINE_PRIME9 (lime_vk_allocate_descriptor_set); + DEFINE_PRIME14 (lime_vk_update_descriptor_set_image); + DEFINE_PRIME13 (lime_vk_update_descriptor_set_buffer); + DEFINE_PRIME6 (lime_vk_update_descriptor_sets); + DEFINE_PRIME8 (lime_vk_create_pipeline_layout); + DEFINE_PRIME7v (lime_vk_destroy_pipeline_layout); + DEFINE_PRIME8 (lime_vk_create_pipeline_cache); + DEFINE_PRIME7v (lime_vk_destroy_pipeline_cache); + DEFINE_PRIME8 (lime_vk_get_pipeline_cache_data); + DEFINE_PRIME14 (lime_vk_create_graphics_pipeline); + DEFINE_PRIME7v (lime_vk_destroy_pipeline); + DEFINE_PRIME10 (lime_vk_queue_submit_synced); + DEFINE_PRIME13 (lime_vk_cmd_begin_render_pass); + DEFINE_PRIME7 (lime_vk_cmd_end_render_pass); + DEFINE_PRIME10 (lime_vk_cmd_bind_pipeline); + DEFINE_PRIME13 (lime_vk_cmd_bind_descriptor_set); + DEFINE_PRIME12 (lime_vk_cmd_bind_descriptor_set_ex); + DEFINE_PRIME14 (lime_vk_cmd_bind_descriptor_set_dynamic_offset); + DEFINE_PRIME12 (lime_vk_cmd_bind_vertex_buffer); + DEFINE_PRIME12 (lime_vk_cmd_bind_index_buffer); + DEFINE_PRIME13 (lime_vk_cmd_set_viewport); + DEFINE_PRIME11 (lime_vk_cmd_set_scissor); + DEFINE_PRIME11 (lime_vk_cmd_draw); + DEFINE_PRIME12 (lime_vk_cmd_draw_indexed); + DEFINE_PRIME13 (lime_vk_cmd_draw_indirect); + DEFINE_PRIME13 (lime_vk_cmd_draw_indexed_indirect); + DEFINE_PRIME12 (lime_vk_cmd_copy_buffer); + DEFINE_PRIME12 (lime_vk_cmd_copy_buffer_to_image_region); + DEFINE_PRIME12 (lime_vk_cmd_copy_image_to_buffer_region); + DEFINE_PRIME10 (lime_vk_cmd_pipeline_barrier_image); + DEFINE_PRIME12 (lime_vk_cmd_clear_color_image); + DEFINE_PRIME11 (lime_vk_cmd_clear_depth_stencil_image); + DEFINE_PRIME9 (lime_vk_cmd_clear_attachments); + DEFINE_PRIME12 (lime_vk_cmd_blit_image); + DEFINE_PRIME11 (lime_vk_cmd_push_constants); + DEFINE_PRIME0 (lime_vk_get_last_error); + DEFINE_PRIME2 (lime_vulkan_renderer_create); + DEFINE_PRIME1v (lime_vulkan_renderer_destroy); + DEFINE_PRIME1 (lime_vulkan_renderer_get_info); + DEFINE_PRIME0 (lime_vulkan_renderer_get_last_error); + DEFINE_PRIME6 (lime_vulkan_renderer_set_overlay); + DEFINE_PRIME1 (lime_vulkan_renderer_clear_overlay); + DEFINE_PRIME5 (lime_vulkan_renderer_render); + DEFINE_PRIME1 (lime_vulkan_renderer_resize); DEFINE_PRIME1 (lime_window_get_width); DEFINE_PRIME1 (lime_window_get_x); DEFINE_PRIME1 (lime_window_get_y); DEFINE_PRIME3v (lime_window_move); + DEFINE_PRIME5 (lime_window_popup_menu); DEFINE_PRIME3 (lime_window_read_pixels); DEFINE_PRIME3v (lime_window_resize); DEFINE_PRIME3v (lime_window_set_minimum_size); @@ -4318,8 +14304,14 @@ namespace lime { DEFINE_PRIME2 (lime_window_set_fullscreen); DEFINE_PRIME2v (lime_window_set_icon); DEFINE_PRIME2 (lime_window_set_maximized); + DEFINE_PRIME2 (lime_window_set_menu); DEFINE_PRIME2 (lime_window_set_minimized); + DEFINE_PRIME2 (lime_window_flash); + DEFINE_PRIME1 (lime_window_get_keyboard_grab); + DEFINE_PRIME1 (lime_window_get_mouse_rect); + DEFINE_PRIME2v (lime_window_set_keyboard_grab); DEFINE_PRIME2v (lime_window_set_mouse_lock); + DEFINE_PRIME5v (lime_window_set_mouse_rect); DEFINE_PRIME2 (lime_window_set_resizable); DEFINE_PRIME2v (lime_window_set_text_input_enabled); DEFINE_PRIME2v (lime_window_set_text_input_rect); @@ -4341,9 +14333,10 @@ namespace lime { #define _TCLIPBOARD_EVENT _OBJ (_I32) #define _TDISPLAYMODE _OBJ (_I32 _I32 _I32 _I32) #define _TDROP_EVENT _OBJ (_BYTES _I32) - #define _TGAMEPAD_EVENT _OBJ (_I32 _I32 _I32 _I32 _F64) + #define _TGAMEPAD_EVENT _OBJ (_I32 _I32 _I32 _I32 _F64 _I32 _F64 _F64 _F64) #define _TJOYSTICK_EVENT _OBJ (_I32 _I32 _I32 _I32 _F64 _F64) - #define _TKEY_EVENT _OBJ (_F64 _I32 _I32 _I32) + #define _TKEY_EVENT _OBJ (_F64 _I32 _I32 _I32 _I32) + #define _TMENU_EVENT _OBJ (_I32 _I32 _I32) #define _TMOUSE_EVENT _OBJ (_I32 _F64 _F64 _I32 _I32 _F64 _F64 _I32) #define _TORIENTATION_EVENT _OBJ (_I32 _I32 _I32) #define _TRECTANGLE _OBJ (_F64 _F64 _F64 _F64) @@ -4351,13 +14344,14 @@ namespace lime { #define _TSENSOR_EVENT _OBJ (_I32 _F64 _F64 _F64 _I32) #define _TTEXT_EVENT _OBJ (_I32 _I32 _I32 _BYTES _I32 _I32) #define _TTOUCH_EVENT _OBJ (_I32 _F64 _F64 _I32 _F64 _I32 _F64 _F64) + #define _TTRAY_ICON_EVENT _OBJ (_I32 _I32 _I32) #define _TVECTOR2 _OBJ (_F64 _F64) #define _TVORBISFILE _OBJ (_I32 _DYN) #define _TWINDOW_EVENT _OBJ (_I32 _I32 _I32 _I32 _I32 _I32) #define _TARRAYBUFFER _TBYTES #define _TARRAYBUFFERVIEW _OBJ (_I32 _TARRAYBUFFER _I32 _I32 _I32 _I32) - #define _TAUDIOBUFFER _OBJ (_I32 _I32 _TARRAYBUFFERVIEW _I32 _DYN _DYN _DYN _DYN _BOOL _DYN _TBYTES _BOOL _I32 _STRING _TBYTES _STRING _TVORBISFILE) + #define _TAUDIOBUFFER _OBJ (_I32 _I32 _TARRAYBUFFERVIEW _I32 _DYN _DYN _I32 _DYN _DYN _BOOL _DYN _TBYTES _BOOL _I32 _STRING _TBYTES _STRING _TVORBISFILE) #define _TIMAGEBUFFER _OBJ (_I32 _TARRAYBUFFERVIEW _I32 _I32 _BOOL _BOOL _I32 _DYN _DYN _DYN _DYN _DYN _DYN) #define _TIMAGE _OBJ (_TIMAGEBUFFER _BOOL _I32 _I32 _I32 _TRECTANGLE _ENUM _I32 _I32 _F64 _F64) @@ -4365,6 +14359,7 @@ namespace lime { #define _TARRAY2 _OBJ (_ARR) + DEFINE_HL_PRIM (_VOID, hl_application_allow_high_dpi, _NO_ARG); DEFINE_HL_PRIM (_TCFFIPOINTER, hl_application_create, _NO_ARG); DEFINE_HL_PRIM (_VOID, hl_application_event_manager_register, _FUN(_VOID, _NO_ARG) _TAPPLICATION_EVENT); DEFINE_HL_PRIM (_I32, hl_application_exec, _TCFFIPOINTER); @@ -4372,6 +14367,9 @@ namespace lime { DEFINE_HL_PRIM (_I32, hl_application_quit, _TCFFIPOINTER); DEFINE_HL_PRIM (_VOID, hl_application_set_main_loop, _TCFFIPOINTER _I32 _F64 _I32 _I32 _I32); DEFINE_HL_PRIM (_VOID, hl_application_set_frame_rate, _TCFFIPOINTER _F64); + DEFINE_HL_PRIM (_VOID, hl_application_set_modal_callbacks, _TCFFIPOINTER _FUN(_VOID, _NO_ARG) _FUN(_VOID, _NO_ARG) _FUN(_VOID, _NO_ARG) _FUN(_VOID, _NO_ARG)); + DEFINE_HL_PRIM (_VOID, hl_application_defer_modal_exception, _TCFFIPOINTER); + DEFINE_HL_PRIM (_BOOL, hl_application_set_menu, _TCFFIPOINTER _TBYTES); DEFINE_HL_PRIM (_VOID, hl_application_set_vsync_mode, _TCFFIPOINTER _I32); DEFINE_HL_PRIM (_BOOL, hl_application_update, _TCFFIPOINTER); DEFINE_HL_PRIM (_TAUDIOBUFFER, hl_audio_load_bytes, _TBYTES _TAUDIOBUFFER); @@ -4388,6 +14386,10 @@ namespace lime { DEFINE_HL_PRIM (_F64, hl_data_pointer_offset, _F64 _I32); DEFINE_HL_PRIM (_TBYTES, hl_deflate_compress, _TBYTES _TBYTES); DEFINE_HL_PRIM (_TBYTES, hl_deflate_decompress, _TBYTES _TBYTES); + DEFINE_HL_PRIM (_VOID, hl_dock_icon_bounce, _BOOL); + DEFINE_HL_PRIM (_BOOL, hl_dock_icon_is_supported, _NO_ARG); + DEFINE_HL_PRIM (_VOID, hl_dock_icon_set_icon, _TIMAGEBUFFER); + DEFINE_HL_PRIM (_BOOL, hl_dock_icon_set_menu, _TBYTES); DEFINE_HL_PRIM (_VOID, hl_drop_event_manager_register, _FUN(_VOID, _NO_ARG) _TDROP_EVENT); DEFINE_HL_PRIM (_BYTES, hl_file_dialog_open_directory, _STRING _STRING _STRING); DEFINE_HL_PRIM (_BYTES, hl_file_dialog_open_file, _STRING _STRING _STRING); @@ -4410,6 +14412,8 @@ namespace lime { DEFINE_HL_PRIM (_I32, hl_font_get_strikethrough_position, _TCFFIPOINTER); DEFINE_HL_PRIM (_I32, hl_font_get_strikethrough_thickness, _TCFFIPOINTER); DEFINE_HL_PRIM (_I32, hl_font_get_units_per_em, _TCFFIPOINTER); + DEFINE_HL_PRIM (_BOOL, hl_font_is_bold, _TCFFIPOINTER); + DEFINE_HL_PRIM (_BOOL, hl_font_is_italic, _TCFFIPOINTER); // DEFINE_PRIME1 (lime_font_load); DEFINE_HL_PRIM (_TCFFIPOINTER, hl_font_load_bytes, _TBYTES); DEFINE_HL_PRIM (_TCFFIPOINTER, hl_font_load_file, _STRING); @@ -4419,11 +14423,23 @@ namespace lime { DEFINE_HL_PRIM (_TBYTES, hl_font_render_glyph_with_flags, _TCFFIPOINTER _I32 _I32 _TBYTES); DEFINE_HL_PRIM (_TBYTES, hl_font_render_glyphs, _TCFFIPOINTER _ARR _TBYTES); DEFINE_HL_PRIM (_VOID, hl_font_set_size, _TCFFIPOINTER _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_font_set_stem_darkening, _TCFFIPOINTER _BOOL); + DEFINE_HL_PRIM (_VOID, hl_font_set_stem_darkening_parameters, _TCFFIPOINTER _ARR); DEFINE_HL_PRIM (_VOID, hl_gamepad_add_mappings, _ARR); DEFINE_HL_PRIM (_VOID, hl_gamepad_event_manager_register, _FUN(_VOID, _NO_ARG) _TGAMEPAD_EVENT); DEFINE_HL_PRIM (_BYTES, hl_gamepad_get_device_guid, _I32); DEFINE_HL_PRIM (_BYTES, hl_gamepad_get_device_name, _I32); DEFINE_HL_PRIM (_VOID, hl_gamepad_rumble, _I32 _F64 _F64 _I32); + DEFINE_HL_PRIM (_I32, hl_gamepad_attach_virtual, _NO_ARG); + DEFINE_HL_PRIM (_VOID, hl_gamepad_detach_virtual, _I32); + DEFINE_HL_PRIM (_I32, hl_gamepad_get_type, _I32); + DEFINE_HL_PRIM (_DYN, hl_gamepad_get_virtual_output, _I32); + DEFINE_HL_PRIM (_BOOL, hl_gamepad_has_led, _I32); + DEFINE_HL_PRIM (_BOOL, hl_gamepad_has_rumble_triggers, _I32); + DEFINE_HL_PRIM (_BOOL, hl_gamepad_has_sensor, _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_gamepad_rumble_triggers, _I32 _F64 _F64 _I32); + DEFINE_HL_PRIM (_BOOL, hl_gamepad_set_led, _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_gamepad_set_sensor_enabled, _I32 _I32 _BOOL); DEFINE_HL_PRIM (_TBYTES, hl_gzip_compress, _TBYTES _TBYTES); DEFINE_HL_PRIM (_TBYTES, hl_gzip_decompress, _TBYTES _TBYTES); DEFINE_HL_PRIM (_VOID, hl_haptic_vibrate, _I32 _I32); @@ -4456,9 +14472,12 @@ namespace lime { DEFINE_HL_PRIM (_I32, hl_key_code_from_scan_code, _I32); DEFINE_HL_PRIM (_I32, hl_key_code_to_scan_code, _I32); DEFINE_HL_PRIM (_VOID, hl_key_event_manager_register, _FUN (_VOID, _NO_ARG) _TKEY_EVENT); + DEFINE_HL_PRIM (_BYTES, hl_locale_get_preferred_locales, _NO_ARG); DEFINE_HL_PRIM (_BYTES, hl_locale_get_system_locale, _NO_ARG); DEFINE_HL_PRIM (_TBYTES, hl_lzma_compress, _TBYTES _TBYTES); DEFINE_HL_PRIM (_TBYTES, hl_lzma_decompress, _TBYTES _TBYTES); + DEFINE_HL_PRIM (_VOID, hl_menu_event_manager_register, _FUN (_VOID, _NO_ARG) _TMENU_EVENT); + DEFINE_HL_PRIM (_I32, hl_menu_get_support, _NO_ARG); DEFINE_HL_PRIM (_VOID, hl_mouse_event_manager_register, _FUN (_VOID, _NO_ARG) _TMOUSE_EVENT); // DEFINE_PRIME1v (lime_neko_execute); DEFINE_HL_PRIM (_VOID, hl_orientation_event_manager_register, _FUN (_VOID, _NO_ARG) _TORIENTATION_EVENT); @@ -4485,6 +14504,15 @@ namespace lime { DEFINE_HL_PRIM (_BOOL, hl_system_set_windows_console_mode, _I32 _I32); DEFINE_HL_PRIM (_VOID, hl_text_event_manager_register, _FUN (_VOID, _NO_ARG) _TTEXT_EVENT); DEFINE_HL_PRIM (_VOID, hl_touch_event_manager_register, _FUN (_VOID, _NO_ARG) _TTOUCH_EVENT); + DEFINE_HL_PRIM (_VOID, hl_tray_icon_close, _TCFFIPOINTER); + DEFINE_HL_PRIM (_TCFFIPOINTER, hl_tray_icon_create, _NO_ARG); + DEFINE_HL_PRIM (_VOID, hl_tray_icon_event_manager_register, _FUN (_VOID, _NO_ARG) _TTRAY_ICON_EVENT); + DEFINE_HL_PRIM (_I32, hl_tray_icon_get_id, _TCFFIPOINTER); + DEFINE_HL_PRIM (_BOOL, hl_tray_icon_is_supported, _NO_ARG); + DEFINE_HL_PRIM (_I32, hl_tray_icon_popup_menu, _TCFFIPOINTER _TBYTES); + DEFINE_HL_PRIM (_VOID, hl_tray_icon_set_icon, _TCFFIPOINTER _TIMAGEBUFFER); + DEFINE_HL_PRIM (_BOOL, hl_tray_icon_set_menu, _TCFFIPOINTER _TBYTES); + DEFINE_HL_PRIM (_VOID, hl_tray_icon_set_tooltip, _TCFFIPOINTER _STRING); DEFINE_HL_PRIM (_VOID, hl_window_alert, _TCFFIPOINTER _STRING _STRING); DEFINE_HL_PRIM (_VOID, hl_window_close, _TCFFIPOINTER); DEFINE_HL_PRIM (_VOID, hl_window_context_flip, _TCFFIPOINTER); @@ -4496,17 +14524,128 @@ namespace lime { DEFINE_HL_PRIM (_VOID, hl_window_focus, _TCFFIPOINTER); DEFINE_HL_PRIM (_F64, hl_window_get_context, _TCFFIPOINTER); DEFINE_HL_PRIM (_BYTES, hl_window_get_context_type, _TCFFIPOINTER); + DEFINE_HL_PRIM (_DYN, hl_window_create_vulkan_surface, _TCFFIPOINTER _I32 _I32); DEFINE_HL_PRIM (_I32, hl_window_get_display, _TCFFIPOINTER); DEFINE_HL_PRIM (_VOID, hl_window_get_display_mode, _TCFFIPOINTER _TDISPLAYMODE); DEFINE_HL_PRIM (_I32, hl_window_get_height, _TCFFIPOINTER); DEFINE_HL_PRIM (_I32, hl_window_get_id, _TCFFIPOINTER); DEFINE_HL_PRIM (_BOOL, hl_window_get_mouse_lock, _TCFFIPOINTER); + DEFINE_HL_PRIM (_BOOL, hl_window_get_transparent, _TCFFIPOINTER); DEFINE_HL_PRIM (_F64, hl_window_get_scale, _TCFFIPOINTER); DEFINE_HL_PRIM (_BOOL, hl_window_get_text_input_enabled, _TCFFIPOINTER); + DEFINE_HL_PRIM (_DYN, hl_window_get_vulkan_drawable_size, _TCFFIPOINTER); + DEFINE_HL_PRIM (_ARR, hl_window_get_vulkan_instance_extensions, _TCFFIPOINTER); + DEFINE_HL_PRIM (_DYN, hl_window_get_vulkan_instance_proc_addr, _TCFFIPOINTER); + DEFINE_HL_PRIM (_DYN, hl_vk_create_instance, _TCFFIPOINTER _STRING); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_instance, _TCFFIPOINTER _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_surface, _TCFFIPOINTER _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_ARR, hl_vk_get_physical_devices, _TCFFIPOINTER _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_device, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_device, _TCFFIPOINTER _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_device_wait_idle, _TCFFIPOINTER _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_queue_wait_idle, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_command_pool, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_command_pool, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_reset_command_pool, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_allocate_command_buffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_free_command_buffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_begin_command_buffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_end_command_buffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_reset_command_buffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_semaphore, _TCFFIPOINTER _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_semaphore, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_fence, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_fence, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_wait_for_fence, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_reset_fence, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_queue_submit, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_allocate_memory, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_free_memory, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_upload_memory, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _TBYTES _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_download_memory, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _TBYTES _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_map_memory, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_unmap_memory, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_write_mapped_memory, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _TBYTES _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_flush_mapped_memory, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_invalidate_mapped_memory, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_buffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_buffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_get_buffer_memory_requirements, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_bind_buffer_memory, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_image, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_image, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_get_image_memory_requirements, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_bind_image_memory, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_swapchain, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_swapchain, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_ARR, hl_vk_get_swapchain_images, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_acquire_next_image, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_I32, hl_vk_queue_present, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_image_view, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_image_view, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_image_view_ex, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_render_pass, _TCFFIPOINTER _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_render_pass, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_framebuffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _ARR _I32 _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_framebuffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_shader_module, _TCFFIPOINTER _I32 _I32 _I32 _I32 _TBYTES _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_shader_module, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_sampler, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _BOOL _F64 _I32 _F64 _F64); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_sampler, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_descriptor_set_layout, _TCFFIPOINTER _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_descriptor_set_layout, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_descriptor_pool, _TCFFIPOINTER _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_descriptor_pool, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_reset_descriptor_pool, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_allocate_descriptor_set, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_update_descriptor_set_image, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_update_descriptor_set_buffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_update_descriptor_sets, _TCFFIPOINTER _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_DYN, hl_vk_create_pipeline_layout, _TCFFIPOINTER _I32 _I32 _I32 _I32 _ARR _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_pipeline_layout, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_vk_create_pipeline_cache, _TCFFIPOINTER _I32 _I32 _I32 _I32 _TBYTES _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_pipeline_cache, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_get_pipeline_cache_data, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _TBYTES); + DEFINE_HL_PRIM (_DYN, hl_vk_create_graphics_pipeline, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_VOID, hl_vk_destroy_pipeline, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_queue_submit_synced, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_begin_render_pass, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _ARR _ARR); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_end_render_pass, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_bind_pipeline, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_bind_descriptor_set, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_bind_descriptor_set_ex, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_bind_descriptor_set_dynamic_offset, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_bind_vertex_buffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_bind_index_buffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_set_viewport, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _F64 _F64 _F64 _F64 _F64 _F64); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_set_scissor, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_draw, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_draw_indexed, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_draw_indirect, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_draw_indexed_indirect, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_copy_buffer, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_copy_buffer_to_image_region, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_copy_image_to_buffer_region, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_pipeline_barrier_image, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_clear_color_image, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_clear_depth_stencil_image, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _ARR _ARR); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_clear_attachments, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _ARR _ARR); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_blit_image, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _ARR); + DEFINE_HL_PRIM (_BOOL, hl_vk_cmd_push_constants, _TCFFIPOINTER _I32 _I32 _I32 _I32 _I32 _I32 _I32 _I32 _ARR _TBYTES); + DEFINE_HL_PRIM (_BYTES, hl_vk_get_last_error, _NO_ARG); + DEFINE_HL_PRIM (_TCFFIPOINTER, hl_vulkan_renderer_create, _TCFFIPOINTER _STRING); + DEFINE_HL_PRIM (_VOID, hl_vulkan_renderer_destroy, _TCFFIPOINTER); + DEFINE_HL_PRIM (_BYTES, hl_vulkan_renderer_get_info, _TCFFIPOINTER); + DEFINE_HL_PRIM (_BYTES, hl_vulkan_renderer_get_last_error, _NO_ARG); + DEFINE_HL_PRIM (_BOOL, hl_vulkan_renderer_set_overlay, _TCFFIPOINTER _TBYTES _I32 _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_vulkan_renderer_clear_overlay, _TCFFIPOINTER); + DEFINE_HL_PRIM (_BOOL, hl_vulkan_renderer_render, _TCFFIPOINTER _F64 _F64 _F64 _F64); + DEFINE_HL_PRIM (_BOOL, hl_vulkan_renderer_resize, _TCFFIPOINTER); DEFINE_HL_PRIM (_I32, hl_window_get_width, _TCFFIPOINTER); DEFINE_HL_PRIM (_I32, hl_window_get_x, _TCFFIPOINTER); DEFINE_HL_PRIM (_I32, hl_window_get_y, _TCFFIPOINTER); DEFINE_HL_PRIM (_VOID, hl_window_move, _TCFFIPOINTER _I32 _I32); + DEFINE_HL_PRIM (_I32, hl_window_popup_menu, _TCFFIPOINTER _TBYTES _I32 _I32 _BOOL); DEFINE_HL_PRIM (_DYN, hl_window_read_pixels, _TCFFIPOINTER _TRECTANGLE _TIMAGEBUFFER); DEFINE_HL_PRIM (_VOID, hl_window_resize, _TCFFIPOINTER _I32 _I32); DEFINE_HL_PRIM (_VOID, hl_window_set_minimum_size, _TCFFIPOINTER _I32 _I32); @@ -4517,8 +14656,14 @@ namespace lime { DEFINE_HL_PRIM (_BOOL, hl_window_set_fullscreen, _TCFFIPOINTER _BOOL); DEFINE_HL_PRIM (_VOID, hl_window_set_icon, _TCFFIPOINTER _TIMAGEBUFFER); DEFINE_HL_PRIM (_BOOL, hl_window_set_maximized, _TCFFIPOINTER _BOOL); + DEFINE_HL_PRIM (_BOOL, hl_window_set_menu, _TCFFIPOINTER _TBYTES); DEFINE_HL_PRIM (_BOOL, hl_window_set_minimized, _TCFFIPOINTER _BOOL); + DEFINE_HL_PRIM (_BOOL, hl_window_flash, _TCFFIPOINTER _I32); + DEFINE_HL_PRIM (_BOOL, hl_window_get_keyboard_grab, _TCFFIPOINTER); + DEFINE_HL_PRIM (_DYN, hl_window_get_mouse_rect, _TCFFIPOINTER); + DEFINE_HL_PRIM (_VOID, hl_window_set_keyboard_grab, _TCFFIPOINTER _BOOL); DEFINE_HL_PRIM (_VOID, hl_window_set_mouse_lock, _TCFFIPOINTER _BOOL); + DEFINE_HL_PRIM (_VOID, hl_window_set_mouse_rect, _TCFFIPOINTER _I32 _I32 _I32 _I32); DEFINE_HL_PRIM (_BOOL, hl_window_set_resizable, _TCFFIPOINTER _BOOL); DEFINE_HL_PRIM (_VOID, hl_window_set_text_input_enabled, _TCFFIPOINTER _BOOL); DEFINE_HL_PRIM (_VOID, hl_window_set_text_input_rect, _TCFFIPOINTER _TRECTANGLE); @@ -4565,6 +14710,8 @@ extern "C" int lime_opengl_register_prims (); extern "C" int lime_opengl_register_prims () { return 0; } #endif +extern "C" int lime_video_register_prims (); + #ifdef LIME_VORBIS extern "C" int lime_vorbis_register_prims (); #else @@ -4579,6 +14726,7 @@ extern "C" int lime_register_prims () { lime_harfbuzz_register_prims (); lime_openal_register_prims (); lime_opengl_register_prims (); + lime_video_register_prims (); lime_vorbis_register_prims (); return 0; diff --git a/project/src/backend/sdl/SDLApplication.cpp b/project/src/backend/sdl/SDLApplication.cpp index 821648774e..9f81b3ff08 100644 --- a/project/src/backend/sdl/SDLApplication.cpp +++ b/project/src/backend/sdl/SDLApplication.cpp @@ -1,7 +1,11 @@ #include "SDLApplication.h" #include "SDLGamepad.h" #include "SDLJoystick.h" +#include "SDLMenu.h" +#include "SDLTrayIcon.h" #include +#include +#include #include #ifdef HX_MACOS @@ -31,27 +35,97 @@ namespace lime #if defined(HX_WINDOWS) && !defined(HX_WINRT) static SDL_atomic_t s_nativeModalLoopDepth; static SDL_atomic_t s_inModalEventWatch; + + // Restore native callback state even if a dispatched callback throws. + class ScopedModalWatch { + bool entered; + public: + ScopedModalWatch():entered(SDL_AtomicCAS(&s_inModalEventWatch, 0, 1) != 0) {} + ~ScopedModalWatch() { if (entered) SDL_AtomicSet(&s_inModalEventWatch, 0); } + bool Entered() const { return entered; } + }; + + class ScopedModalGC { + bool wasBlocking; + public: + ScopedModalGC():wasBlocking(SDL_AtomicGet(&s_waitEventBlocking) != 0) { + if (wasBlocking) { + System::GCExitBlocking(); + SDL_AtomicSet(&s_waitEventBlocking, 0); + } + } + ~ScopedModalGC() { + if (wasBlocking) { + System::GCEnterBlocking(); + SDL_AtomicSet(&s_waitEventBlocking, 1); + } + } + }; + + class ScopedModalCallbacks { + ValuePointer* update; + ValuePointer* render; + ValuePointer* window; + public: + ScopedModalCallbacks(ValuePointer** callbacks):update(ApplicationEvent::callback),render(RenderEvent::callback),window(WindowEvent::callback) + { + ApplicationEvent::callback = callbacks[0]; + RenderEvent::callback = callbacks[1]; + WindowEvent::callback = callbacks[2]; + } + ~ScopedModalCallbacks() + { + ApplicationEvent::callback = update; + RenderEvent::callback = render; + WindowEvent::callback = window; + } + }; #endif + void AllowHighDPI() + { + +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + // Windows otherwise draws the application at 96 DPI and stretches it to + // each display's scale. Window sizes stay in points, as on macOS, with + // the drawable in pixels. Both are read as SDL starts. + SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2"); + SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "1"); +#endif + } + SDLApplication::SDLApplication() { + active = false; allowBusyWait = true; busyWaitOnly = false; currentUpdate = 0.0; + deltaRemainder = 0.0; + dispatchedFrames = 0; displayRefreshRate = 60.0; initFlags = SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_TIMER | SDL_INIT_JOYSTICK; firstTime = true; + mouseCaptureRequested = false; #if defined(LIME_MOJOAL) || defined(LIME_OPENALSOFT) initFlags |= SDL_INIT_AUDIO; #endif + // Input methods show their own composition and candidate windows, which + // Lime apps don't draw, and compositions longer than an SDL text event + // holds arrive whole. Both are read as the video subsystem starts. + SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); + SDL_SetHint(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, "1"); + if (SDL_Init(initFlags) != 0) { printf("Could not initialize SDL: %s.\n", SDL_GetError()); } + // Lime's cancelable window-close callback owns last-window termination. + SDL_SetHintWithPriority(SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, "0", SDL_HINT_OVERRIDE); + #ifdef LIME_SDL_SOUND if (!Sound_Init ()) { @@ -65,9 +139,10 @@ namespace lime currentApplication = this; #if defined(HX_WINDOWS) && !defined(HX_WINRT) modalWatchInstalled = false; + modalExceptionPending = false; + for (int i = 0; i < 4; ++i) + modalCallbacks[i] = 0; mainThreadID = SDL_ThreadID(); - pendingResizeDispatchSkips = 0; - pendingWatchRenderSkips = 0; #endif framePeriod = 1000.0 / 60.0; @@ -75,12 +150,21 @@ namespace lime lastSleepCalibration = 0; nextUpdate = 0.0; performanceFrequency = SDL_GetPerformanceFrequency(); + clockStartCounter = SDL_GetPerformanceCounter(); +#if SDL_VERSION_ATLEAST(2, 0, 18) + clockStartTicks = SDL_GetTicks64(); +#else + clockStartTicks = 0; +#endif + clockLastTicks = SDL_GetTicks(); + clockElapsedTicks = 0; realVSyncActive = false; requestedBusyWaitMode = MAIN_LOOP_BUSY_WAIT_AUTO; requestedFrameRate = 60.0; requestedProfile = MAIN_LOOP_PROFILE_BALANCED; requestedTimePrecisionMode = MAIN_LOOP_TIME_PRECISION_AUTO; requestedUncapMode = MAIN_LOOP_UNCAP_OFF; + activeUncapMode = MAIN_LOOP_UNCAP_OFF; requestedVSyncMode = MAIN_LOOP_VSYNC_OFF; schedulerUnthrottled = false; sleepGuardMs = 2.0; @@ -135,9 +219,13 @@ namespace lime } nextUpdate += framePeriod; - while (nextUpdate <= currentUpdate) + if (nextUpdate <= currentUpdate) { - nextUpdate += framePeriod; + double skippedPeriods = std::floor((currentUpdate - nextUpdate) / framePeriod) + 1.0; + nextUpdate += skippedPeriods * framePeriod; + // Rounding at a large clock value must not leave the deadline due. + if (nextUpdate <= currentUpdate) + nextUpdate = std::nextafter(currentUpdate, (std::numeric_limits::infinity)()); } } @@ -168,7 +256,12 @@ namespace lime bool hasExplicitFrameRate = (clampedFrameRate > 0.0); double effectiveFrameRate = hasExplicitFrameRate ? clampedFrameRate : 1.0; - if (requestedUncapMode == MAIN_LOOP_UNCAP_SOFT || requestedUncapMode == MAIN_LOOP_UNCAP_HARD) + // The uncapped profile uncaps the loop unless the options ask for more + activeUncapMode = requestedUncapMode; + if (activeUncapMode == MAIN_LOOP_UNCAP_OFF && requestedProfile == MAIN_LOOP_PROFILE_UNCAPPED) + activeUncapMode = MAIN_LOOP_UNCAP_SOFT; + + if (activeUncapMode == MAIN_LOOP_UNCAP_SOFT || activeUncapMode == MAIN_LOOP_UNCAP_HARD) { schedulerUnthrottled = true; } @@ -196,7 +289,7 @@ namespace lime default: useHighResolutionTimer = (requestedProfile == MAIN_LOOP_PROFILE_PRECISION || requestedProfile == MAIN_LOOP_PROFILE_UNCAPPED || - requestedUncapMode != MAIN_LOOP_UNCAP_OFF || clampedFrameRate > 1000.0); + activeUncapMode != MAIN_LOOP_UNCAP_OFF || clampedFrameRate > 1000.0); break; } @@ -215,7 +308,7 @@ namespace lime break; } - busyWaitOnly = allowBusyWait && (requestedUncapMode == MAIN_LOOP_UNCAP_HARD || (framePeriod > 0.0 && framePeriod <= 2.0)); + busyWaitOnly = allowBusyWait && (activeUncapMode == MAIN_LOOP_UNCAP_HARD || (framePeriod > 0.0 && framePeriod <= 2.0)); currentUpdate = GetCurrentTimeMs(); nextUpdate = currentUpdate; } @@ -269,12 +362,22 @@ namespace lime double SDLApplication::GetCurrentTimeMs() const { - if (useHighResolutionTimer && performanceFrequency != 0) + if (performanceFrequency != 0) { - return ((double)SDL_GetPerformanceCounter() * 1000.0) / (double)performanceFrequency; + // Both precision modes use the same epoch; only their resolution differs. + double now = ((double)(SDL_GetPerformanceCounter() - clockStartCounter) * 1000.0) / (double)performanceFrequency; + return useHighResolutionTimer ? now : std::floor(now); } - return (double)SDL_GetTicks(); + // Extend legacy ticks without requiring a newer SDL minimum version. +#if SDL_VERSION_ATLEAST(2, 0, 18) + return (double)(SDL_GetTicks64() - clockStartTicks); +#else + Uint32 ticks = SDL_GetTicks(); + clockElapsedTicks += (Uint32)(ticks - clockLastTicks); + clockLastTicks = ticks; + return (double)clockElapsedTicks; +#endif } double SDLApplication::GetDisplayRefreshRate() const @@ -296,15 +399,29 @@ namespace lime void SDLApplication::DispatchFrame(double now, bool renderFrame) { - currentUpdate = now; +#if defined(IPHONE) || defined(EMSCRIPTEN) + int top = 0; + gc_set_top_of_stack(&top, false); +#endif + + currentUpdate = (std::max)(now, lastUpdate); applicationEvent.type = UPDATE; double delta = currentUpdate - lastUpdate; if (delta < 0.0) delta = 0.0; - applicationEvent.deltaTime = (int)(delta + 0.5); + double elapsed = delta + deltaRemainder; + double rounded = std::floor(elapsed + 0.5); + if (rounded > (double)(std::numeric_limits::max)()) + { + rounded = (double)(std::numeric_limits::max)(); + elapsed = rounded; + } + applicationEvent.deltaTime = (int)rounded; + deltaRemainder = elapsed - rounded; lastUpdate = currentUpdate; + dispatchedFrames++; AdvanceNextUpdate(); @@ -362,6 +479,8 @@ namespace lime SDL_DelEventWatch(ModalEventWatch, this); modalWatchInstalled = false; } + for (int i = 0; i < 4; ++i) + delete modalCallbacks[i]; #endif } @@ -393,23 +512,67 @@ namespace lime return 0; SDLApplication *application = (SDLApplication *)userdata; - if (!application || !application->active || inBackground) + if (!application || SDL_ThreadID() != application->mainThreadID) + return 0; + if (!application->active || inBackground) return 0; if (SDL_AtomicGet(&s_waitEventBlocking) == 0 && SDL_AtomicGet(&s_nativeModalLoopDepth) == 0) return 0; - if (SDL_ThreadID() != application->mainThreadID) + if (application->modalExceptionPending || application->modalNativeException || !application->modalCallbacks[3]) return 0; // Prevent re-entry if rendering queues another window event. - if (!SDL_AtomicCAS(&s_inModalEventWatch, 0, 1)) + ScopedModalWatch watch; + if (!watch.Entered()) return 0; - application->PumpOneFrameFromWatch(event); - SDL_AtomicSet(&s_inModalEventWatch, 0); + try + { + ScopedModalCallbacks callbacks(application->modalCallbacks); + application->PumpOneFrameFromWatch(event); + } + catch (...) + { + application->modalNativeException = std::current_exception(); + } return 0; } + void SDLApplication::SetModalCallbacks(ValuePointer* update, ValuePointer* render, ValuePointer* window, ValuePointer* rethrow) + { + for (int i = 0; i < 4; ++i) + delete modalCallbacks[i]; + modalCallbacks[0] = update; + modalCallbacks[1] = render; + modalCallbacks[2] = window; + modalCallbacks[3] = rethrow; + } + + void SDLApplication::DeferModalException() + { + modalExceptionPending = true; + } + + void SDLApplication::CheckModalException(bool blocking) + { + if (!modalExceptionPending && !modalNativeException) + return; + if (blocking) + { + SDL_AtomicSet(&s_waitEventBlocking, 0); + System::GCExitBlocking(); + } + if (modalNativeException) + { + std::exception_ptr exception = modalNativeException; + modalNativeException = std::exception_ptr(); + std::rethrow_exception(exception); + } + modalExceptionPending = false; + modalCallbacks[3]->Call(); + } + void SDLApplication::PumpOneFrameFromWatch(SDL_Event *watchEvent) { @@ -417,47 +580,26 @@ namespace lime return; bool isResizeEvent = false; - bool isExposeEvent = false; if (watchEvent && watchEvent->type == SDL_WINDOWEVENT) { isResizeEvent = (watchEvent->window.event == SDL_WINDOWEVENT_SIZE_CHANGED || watchEvent->window.event == SDL_WINDOWEVENT_RESIZED); - isExposeEvent = (watchEvent->window.event == SDL_WINDOWEVENT_EXPOSED); } double now = GetCurrentTimeMs(); bool frameDue = IsFrameDue(now); - bool isResizeOrExposeEvent = (isResizeEvent || isExposeEvent); if (!frameDue && !isResizeEvent) return; - bool exitedBlocking = false; - if (SDL_AtomicGet(&s_waitEventBlocking)) - { - System::GCExitBlocking(); - SDL_AtomicSet(&s_waitEventBlocking, 0); - exitedBlocking = true; - } + ScopedModalGC gc; if (isResizeEvent) { ProcessWindowEvent(watchEvent); - pendingResizeDispatchSkips++; } if (frameDue) { DispatchFrame(now); - - if (watchEvent && isResizeOrExposeEvent) - { - pendingWatchRenderSkips++; - } - } - - if (exitedBlocking) - { - System::GCEnterBlocking(); - SDL_AtomicSet(&s_waitEventBlocking, 1); } } #endif @@ -503,16 +645,6 @@ namespace lime switch (event->type) { - case SDL_USEREVENT: - - if (!inBackground) - { - - DispatchFrame(GetCurrentTimeMs()); - } - - break; - case SDL_APP_WILLENTERBACKGROUND: inBackground = true; @@ -527,6 +659,9 @@ namespace lime case SDL_APP_DIDENTERFOREGROUND: + lastUpdate = GetCurrentTimeMs(); + nextUpdate = lastUpdate; + deltaRemainder = 0.0; windowEvent.type = WINDOW_ACTIVATE; WindowEvent::Dispatch(&windowEvent); @@ -543,6 +678,10 @@ namespace lime case SDL_CONTROLLERBUTTONUP: case SDL_CONTROLLERDEVICEADDED: case SDL_CONTROLLERDEVICEREMOVED: + case SDL_CONTROLLERSENSORUPDATE: + case SDL_CONTROLLERTOUCHPADDOWN: + case SDL_CONTROLLERTOUCHPADMOTION: + case SDL_CONTROLLERTOUCHPADUP: ProcessGamepadEvent(event); break; @@ -562,6 +701,19 @@ namespace lime orientationEvent.display = event->display.display; OrientationEvent::Dispatch(&orientationEvent); + break; + + case SDL_DISPLAYEVENT_CONNECTED: + case SDL_DISPLAYEVENT_DISCONNECTED: + case SDL_DISPLAYEVENT_MOVED: + + // Displays come and go, and move as they are arranged or the primary + // display changes + orientationEvent.type = event->display.event == SDL_DISPLAYEVENT_CONNECTED ? DISPLAY_CONNECT : (event->display.event == SDL_DISPLAYEVENT_DISCONNECTED ? DISPLAY_DISCONNECT : DISPLAY_MOVE); + orientationEvent.orientation = 0; + orientationEvent.display = event->display.display; + OrientationEvent::Dispatch(&orientationEvent); + break; } break; @@ -630,8 +782,14 @@ namespace lime break; #endif + case SDL_SYSWMEVENT: + + ProcessMenuEvent(event); + break; + case SDL_TEXTINPUT: case SDL_TEXTEDITING: + case SDL_TEXTEDITING_EXT: ProcessTextEvent(event); break; @@ -658,96 +816,32 @@ namespace lime case SDL_WINDOWEVENT_EXPOSED: ProcessWindowEvent(event); - - if (!inBackground) + // Rendering is scheduled by Update, or by the watch while a native + // modal loop owns the thread. Do not consume a frame without drawing. +#if !defined(HX_WINDOWS) || defined(HX_WINRT) { - bool skipImmediateRender = false; -#if defined(HX_WINDOWS) && !defined(HX_WINRT) - if (pendingWatchRenderSkips > 0) - { - pendingWatchRenderSkips--; - skipImmediateRender = true; - } - - if (SDL_AtomicGet(&s_waitEventBlocking)) - { - PumpOneFrameFromWatch(); - } - else -#endif - { - double now = GetCurrentTimeMs(); - bool frameDue = IsFrameDue(now); - if (frameDue) - { - DispatchFrame(now, !skipImmediateRender); - } - } + double now = GetCurrentTimeMs(); + if (!inBackground && IsFrameDue(now)) DispatchFrame(now); } - +#endif break; case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_RESIZED: - { - - bool skipResizeDispatch = false; - bool skipImmediateRender = false; #if defined(HX_WINDOWS) && !defined(HX_WINRT) - if (pendingResizeDispatchSkips > 0) - { - pendingResizeDispatchSkips--; - skipResizeDispatch = true; - } - if (pendingWatchRenderSkips > 0) + ProcessWindowEvent(event, true); +#else + ProcessWindowEvent(event); { - pendingWatchRenderSkips--; - skipImmediateRender = true; + double now = GetCurrentTimeMs(); + if (!inBackground && IsFrameDue(now)) DispatchFrame(now); } #endif - if (!skipResizeDispatch) - { - ProcessWindowEvent(event); - } - - if (!inBackground) - { -#if defined(HX_WINDOWS) && !defined(HX_WINRT) - if (SDL_AtomicGet(&s_waitEventBlocking)) - { - PumpOneFrameFromWatch(); - } - else -#endif - { - double now = GetCurrentTimeMs(); - bool frameDue = IsFrameDue(now); - if (frameDue) - { - DispatchFrame(now, !skipImmediateRender); - } - } - } - break; - } case SDL_WINDOWEVENT_CLOSE: ProcessWindowEvent(event); - - // Avoid handling SDL_QUIT if in response to window.close - SDL_Event event; - - if (SDL_PollEvent(&event)) - { - - if (event.type != SDL_QUIT) - { - - HandleEvent(&event); - } - } break; } @@ -757,6 +851,13 @@ namespace lime active = false; break; + + default: + + // Menu and tray icon events queued by SDLMenu use a registered event type + ProcessMenuEvent(event); + ProcessTrayIconEvent(event); + break; } } @@ -764,12 +865,12 @@ namespace lime { active = true; + deltaRemainder = 0.0; lastUpdate = GetCurrentTimeMs(); nextUpdate = lastUpdate; firstTime = true; #if defined(HX_WINDOWS) && !defined(HX_WINRT) - pendingResizeDispatchSkips = 0; - pendingWatchRenderSkips = 0; + dispatchedWindowSizes.clear(); SDL_AtomicSet(&s_nativeModalLoopDepth, 0); #endif CalibrateSleepGuard(true); @@ -826,6 +927,7 @@ namespace lime gamepadEvent.type = GAMEPAD_AXIS_MOVE; gamepadEvent.axis = event->caxis.axis; gamepadEvent.id = event->caxis.which; + gamepadEvent.timestamp = event->common.timestamp; if (event->caxis.value > -analogAxisDeadZone && event->caxis.value < analogAxisDeadZone) { @@ -852,6 +954,7 @@ namespace lime gamepadEvent.type = GAMEPAD_BUTTON_DOWN; gamepadEvent.button = event->cbutton.button; gamepadEvent.id = event->cbutton.which; + gamepadEvent.timestamp = event->common.timestamp; GamepadEvent::Dispatch(&gamepadEvent); break; @@ -861,6 +964,7 @@ namespace lime gamepadEvent.type = GAMEPAD_BUTTON_UP; gamepadEvent.button = event->cbutton.button; gamepadEvent.id = event->cbutton.which; + gamepadEvent.timestamp = event->common.timestamp; GamepadEvent::Dispatch(&gamepadEvent); break; @@ -872,6 +976,7 @@ namespace lime gamepadEvent.type = GAMEPAD_CONNECT; gamepadEvent.id = SDLGamepad::GetInstanceID(event->cdevice.which); + gamepadEvent.timestamp = event->common.timestamp; GamepadEvent::Dispatch(&gamepadEvent); } @@ -883,11 +988,42 @@ namespace lime gamepadEvent.type = GAMEPAD_DISCONNECT; gamepadEvent.id = event->cdevice.which; + gamepadEvent.timestamp = event->common.timestamp; GamepadEvent::Dispatch(&gamepadEvent); SDLGamepad::Disconnect(event->cdevice.which); break; } + + case SDL_CONTROLLERSENSORUPDATE: + + // The sensor goes in axis, and its readings in x, y and z + gamepadEvent.type = GAMEPAD_SENSOR_UPDATE; + gamepadEvent.id = event->csensor.which; + gamepadEvent.axis = event->csensor.sensor; + gamepadEvent.x = event->csensor.data[0]; + gamepadEvent.y = event->csensor.data[1]; + gamepadEvent.z = event->csensor.data[2]; + gamepadEvent.timestamp = event->common.timestamp; + + GamepadEvent::Dispatch(&gamepadEvent); + break; + + case SDL_CONTROLLERTOUCHPADDOWN: + case SDL_CONTROLLERTOUCHPADMOTION: + case SDL_CONTROLLERTOUCHPADUP: + + // The finger goes in button, and where it is in x and y + gamepadEvent.type = event->type == SDL_CONTROLLERTOUCHPADDOWN ? GAMEPAD_TOUCHPAD_DOWN : (event->type == SDL_CONTROLLERTOUCHPADMOTION ? GAMEPAD_TOUCHPAD_MOVE : GAMEPAD_TOUCHPAD_UP); + gamepadEvent.id = event->ctouchpad.which; + gamepadEvent.button = event->ctouchpad.finger; + gamepadEvent.x = event->ctouchpad.x; + gamepadEvent.y = event->ctouchpad.y; + gamepadEvent.z = event->ctouchpad.pressure; + gamepadEvent.timestamp = event->common.timestamp; + + GamepadEvent::Dispatch(&gamepadEvent); + break; } } } @@ -1002,6 +1138,7 @@ namespace lime keyEvent.keyCode = event->key.keysym.sym; keyEvent.modifier = event->key.keysym.mod; + keyEvent.timestamp = event->common.timestamp; keyEvent.windowID = event->key.windowID; if (keyEvent.type == KEY_DOWN) @@ -1035,6 +1172,35 @@ namespace lime } } + void SDLApplication::ProcessMenuEvent(SDL_Event *event) + { + + Uint32 windowID; + int id; + + if (!MenuEvent::callback) + return; + + if (SDLMenu::GetSelection(event, &windowID, &id)) + { + + menuEvent.type = MENU_SELECT; + menuEvent.id = id; + menuEvent.windowID = windowID; + + MenuEvent::Dispatch(&menuEvent); + } + else if (SDLMenu::GetDockMenuSelection(event, &id)) + { + + menuEvent.type = MENU_DOCK_SELECT; + menuEvent.id = id; + menuEvent.windowID = 0; + + MenuEvent::Dispatch(&menuEvent); + } + } + void SDLApplication::ProcessMouseEvent(SDL_Event *event) { @@ -1055,7 +1221,18 @@ namespace lime case SDL_MOUSEBUTTONDOWN: - SDL_CaptureMouse(SDL_TRUE); + // SDL 2.0.22+ captures while buttons are held, before queued events + // reach Lime. Replaying capture here can recapture after SDL has + // already processed a complete down/up batch. Keep the legacy + // path for older SDL, touch-synthesized mouse events (excluded + // by SDL auto capture), and applications that disable it. +#if defined(HX_WINDOWS) && !defined(HX_WINRT) && SDL_VERSION_ATLEAST(2, 0, 22) + if (event->button.which == SDL_TOUCH_MOUSEID || !SDL_GetHintBoolean(SDL_HINT_MOUSE_AUTO_CAPTURE, SDL_TRUE)) +#endif + { + SDL_CaptureMouse(SDL_TRUE); + mouseCaptureRequested = true; + } mouseEvent.type = MOUSE_DOWN; mouseEvent.button = event->button.button - 1; @@ -1066,7 +1243,14 @@ namespace lime case SDL_MOUSEBUTTONUP: - SDL_CaptureMouse(SDL_FALSE); +#if defined(HX_WINDOWS) && !defined(HX_WINRT) && SDL_VERSION_ATLEAST(2, 0, 22) + // Release our own request even if the hint changed while held. + if (mouseCaptureRequested || event->button.which == SDL_TOUCH_MOUSEID || !SDL_GetHintBoolean(SDL_HINT_MOUSE_AUTO_CAPTURE, SDL_TRUE)) +#endif + { + SDL_CaptureMouse(SDL_FALSE); + mouseCaptureRequested = false; + } mouseEvent.type = MOUSE_UP; mouseEvent.button = event->button.button - 1; @@ -1079,17 +1263,24 @@ namespace lime mouseEvent.type = MOUSE_WHEEL; + // Whole steps, and, in movementX and movementY, the fractions of a + // step that touchpads and smooth wheels scroll by, for windows + // with preciseMouseWheel if (event->wheel.direction == SDL_MOUSEWHEEL_FLIPPED) { mouseEvent.x = -event->wheel.x; mouseEvent.y = -event->wheel.y; + mouseEvent.movementX = -event->wheel.preciseX; + mouseEvent.movementY = -event->wheel.preciseY; } else { mouseEvent.x = event->wheel.x; mouseEvent.y = event->wheel.y; + mouseEvent.movementX = event->wheel.preciseX; + mouseEvent.movementY = event->wheel.preciseY; } break; } @@ -1133,6 +1324,8 @@ namespace lime if (TextEvent::callback) { + const char *text = event->text.text; + switch (event->type) { @@ -1147,6 +1340,15 @@ namespace lime textEvent.start = event->edit.start; textEvent.length = event->edit.length; break; + + case SDL_TEXTEDITING_EXT: + + // A composition too long for SDL_TEXTEDITING + textEvent.type = TEXT_EDIT; + textEvent.start = event->editExt.start; + textEvent.length = event->editExt.length; + text = event->editExt.text ? event->editExt.text : ""; + break; } if (textEvent.text) @@ -1155,12 +1357,19 @@ namespace lime free(textEvent.text); } - textEvent.text = (vbyte *)malloc(strlen(event->text.text) + 1); - strcpy((char *)textEvent.text, event->text.text); + textEvent.text = (vbyte *)malloc(strlen(text) + 1); + strcpy((char *)textEvent.text, text); textEvent.windowID = event->text.windowID; TextEvent::Dispatch(&textEvent); } + + // SDL leaves the long composition's text to the application to free + if (event->type == SDL_TEXTEDITING_EXT) + { + + SDL_free(event->editExt.text); + } } void SDLApplication::ProcessTouchEvent(SDL_Event *event) @@ -1200,7 +1409,46 @@ namespace lime } } - void SDLApplication::ProcessWindowEvent(SDL_Event *event) +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + bool SDLApplication::PrepareResizeEvent(SDL_Event *event, bool currentSize) + { + // SDL coalesces its own events, but directly pushed live-resize events + // can still contain older sizes. Reconcile queued events to current native + // dimensions; the watch uses its immediate dimensions before SDL updates. + if (currentSize) + { + SDL_Window *window = SDL_GetWindowFromID(event->window.windowID); + if (window) + SDL_GetWindowSize(window, &event->window.data1, &event->window.data2); + } + std::pair size(event->window.data1, event->window.data2); + std::map>::iterator previous = dispatchedWindowSizes.find(event->window.windowID); + if (previous != dispatchedWindowSizes.end() && previous->second == size) + return false; + dispatchedWindowSizes[event->window.windowID] = size; + return true; + } +#endif + + void SDLApplication::ProcessTrayIconEvent(SDL_Event *event) + { + + int id; + int type; + int itemID; + + if (TrayIconEvent::callback && SDLMenu::GetTrayIconEvent(event, &id, &type, &itemID)) + { + + trayIconEvent.type = (TrayIconEventType)type; + trayIconEvent.id = id; + trayIconEvent.itemID = itemID; + + TrayIconEvent::Dispatch(&trayIconEvent); + } + } + + void SDLApplication::ProcessWindowEvent(SDL_Event *event, bool currentSize) { if (WindowEvent::callback) @@ -1250,6 +1498,9 @@ namespace lime case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_RESIZED: +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + if (!PrepareResizeEvent(event, currentSize)) return; +#endif windowEvent.type = WINDOW_RESIZE; windowEvent.width = event->window.data1; windowEvent.height = event->window.data2; @@ -1261,7 +1512,20 @@ namespace lime } windowEvent.windowID = event->window.windowID; +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + try { + WindowEvent::Dispatch(&windowEvent); + if (modalExceptionPending && (event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED || event->window.event == SDL_WINDOWEVENT_RESIZED)) + dispatchedWindowSizes.erase(event->window.windowID); + } catch (...) { + // A failed resize callback must be retryable at the same dimensions. + if (event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED || event->window.event == SDL_WINDOWEVENT_RESIZED) + dispatchedWindowSizes.erase(event->window.windowID); + throw; + } +#else WindowEvent::Dispatch(&windowEvent); +#endif } } @@ -1308,6 +1572,9 @@ namespace lime void SDLApplication::UnregisterWindow(SDLWindow *window) { +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + if (window) dispatchedWindowSizes.erase(window->GetID()); +#endif windows.erase (std::remove (windows.begin (), windows.end (), window), windows.end ()); RefreshVSyncState (); } @@ -1330,6 +1597,12 @@ namespace lime ApplyMainLoopSettings(); } + bool SDLApplication::SetMenu(const unsigned char *data, int length) + { + + return SDLMenu::SetApplicationMenu(data, length); + } + void SDLApplication::SetVSyncMode(int vsyncMode) { @@ -1349,57 +1622,88 @@ namespace lime bool SDLApplication::Update() { + SDLTrayIcon::Update(); + +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + CheckModalException(); +#endif + Uint64 framesBeforeEvents = dispatchedFrames; SDL_Event event; event.type = -1; +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + bool pumpedEvents = false; + bool handledEvent = false; +#endif + #if (!defined(IPHONE) && !defined(EMSCRIPTEN)) - if (active && !firstTime && requestedUncapMode != MAIN_LOOP_UNCAP_HARD && WaitEvent(&event)) + if (active && (inBackground || (!firstTime && activeUncapMode != MAIN_LOOP_UNCAP_HARD))) { - - HandleEvent(&event); - event.type = -1; - if (!active) - return active; +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + // Windows WaitEvent always pumps before returning. Do not pump the OS again + // merely to drain the SDL events that it has already collected. + pumpedEvents = true; +#endif + if (WaitEvent(&event)) + { +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + CheckModalException(); +#endif +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + handledEvent = true; +#endif + HandleEvent(&event); + event.type = -1; + if (!active) + return active; + } } firstTime = false; #endif +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + if (!pumpedEvents) + SDL_PumpEvents(); + CheckModalException(); + + // Snapshot the batch size without dequeuing it. New arrivals cannot extend this + // turn indefinitely, preserving a finite cycle in uncapped modes too. Fetch + // one event at a time so SYSWMEVENT storage and early-exit payload ownership + // remain valid. Uncapped modes must not yield after every event just because + // IsFrameDue is always true; they drain this finite batch instead. + int queuedEvents = SDL_PeepEvents(NULL, 0, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT); + while (queuedEvents-- > 0) + { + if (handledEvent && !schedulerUnthrottled && IsFrameDue(GetCurrentTimeMs())) + break; + if (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) != 1) + break; + handledEvent = true; +#else while (SDL_PollEvent(&event)) { +#endif HandleEvent(&event); event.type = -1; +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + CheckModalException(); +#endif if (!active) return active; } currentUpdate = GetCurrentTimeMs(); -#if defined(IPHONE) || defined(EMSCRIPTEN) - - if (IsFrameDue(currentUpdate)) - { - - event.type = SDL_USEREVENT; - HandleEvent(&event); - event.type = -1; - } - -#else - - if (IsFrameDue(currentUpdate)) + if (!inBackground && dispatchedFrames == framesBeforeEvents && IsFrameDue(currentUpdate)) { - event.type = SDL_USEREVENT; - HandleEvent(&event); - event.type = -1; + DispatchFrame(currentUpdate); } -#endif - return active; } @@ -1426,6 +1730,16 @@ namespace lime int SDLApplication::WaitEvent(SDL_Event *event) { + if (inBackground) + { + System::GCEnterBlocking(); + SDL_AtomicSet(&s_waitEventBlocking, 1); + int result = SDL_WaitEventTimeout(event, 100); + SDL_AtomicSet(&s_waitEventBlocking, 0); + System::GCExitBlocking(); + return result; + } + #if defined(HX_MACOS) || defined(ANDROID) for (;;) @@ -1437,9 +1751,24 @@ namespace lime if (schedulerUnthrottled || remaining <= 0.0) { - SDL_zero(*event); - event->type = SDL_USEREVENT; - return 1; + return 0; + } + + if (allowBusyWait && busyWaitOnly) + { + System::GCEnterBlocking(); + while ((nextUpdate - GetCurrentTimeMs()) > 0.0) + { + SDL_PumpEvents(); + int result = SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT); + if (result != 0) + { + System::GCExitBlocking(); + return result == 1 ? 1 : 0; + } + } + System::GCExitBlocking(); + return 0; } int waitMs = (int)(remaining + 0.999); @@ -1476,35 +1805,8 @@ namespace lime if (result == -1) return 0; - if (allowBusyWait && !busyWaitOnly) + if (allowBusyWait) UpdateSleepGuard((Uint32)waitMs, waitElapsed); - - if (allowBusyWait && busyWaitOnly) - { - - while ((nextUpdate - GetCurrentTimeMs()) > 0.0) - { - - SDL_PumpEvents(); - - switch (SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) - { - - case -1: - return 0; - - case 1: - return 1; - - default: - break; - } - } - - SDL_zero(*event); - event->type = SDL_USEREVENT; - return 1; - } } #else @@ -1517,6 +1819,9 @@ namespace lime SDL_PumpEvents(); +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + CheckModalException(isBlocking); +#endif switch (SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) { @@ -1553,15 +1858,12 @@ namespace lime if (schedulerUnthrottled || remaining <= 0.0) { - SDL_zero(*event); - event->type = SDL_USEREVENT; - if (isBlocking) { SDL_AtomicSet(&s_waitEventBlocking, 0); System::GCExitBlocking(); } - return 1; + return 0; } if (!allowBusyWait) @@ -1650,8 +1952,25 @@ namespace lime for (;;) { +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + // The initial pump above already provided fresh input. Once the frame + // deadline is reached, do not start another potentially costly OS pump. + if (!schedulerUnthrottled && IsFrameDue(GetCurrentTimeMs())) + { + if (isBlocking) + { + SDL_AtomicSet(&s_waitEventBlocking, 0); + System::GCExitBlocking(); + } + return 0; + } +#endif + SDL_PumpEvents(); +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + CheckModalException(isBlocking); +#endif switch (SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) { @@ -1678,15 +1997,12 @@ namespace lime if ((nextUpdate - GetCurrentTimeMs()) <= 0.0) { - SDL_zero(*event); - event->type = SDL_USEREVENT; - if (isBlocking) { SDL_AtomicSet(&s_waitEventBlocking, 0); System::GCExitBlocking(); } - return 1; + return 0; } break; diff --git a/project/src/backend/sdl/SDLApplication.h b/project/src/backend/sdl/SDLApplication.h index 397aa7ebc2..1e13b831e0 100644 --- a/project/src/backend/sdl/SDLApplication.h +++ b/project/src/backend/sdl/SDLApplication.h @@ -4,6 +4,9 @@ #include #include +#include +#include +#include #include #include #include @@ -14,6 +17,8 @@ #include #include #include +#include +#include #include #include #include @@ -36,6 +41,7 @@ namespace lime { virtual int Quit (); virtual void SetMainLoop (int profile, double frameRate, int timePrecision, int busyWait, int uncapMode); virtual void SetFrameRate (double frameRate); + virtual bool SetMenu (const unsigned char* data, int length); virtual void SetVSyncMode (int vsyncMode); virtual bool Update (); @@ -44,6 +50,8 @@ namespace lime { #if defined(HX_WINDOWS) && !defined(HX_WINRT) static void EnterNativeModalLoop (); static void ExitNativeModalLoop (); + void SetModalCallbacks (ValuePointer* update, ValuePointer* render, ValuePointer* window, ValuePointer* rethrow); + void DeferModalException (); #endif private: @@ -62,17 +70,23 @@ namespace lime { #if defined(HX_WINDOWS) && !defined(HX_WINRT) void PumpOneFrameFromWatch (SDL_Event* watchEvent = 0); static int ModalEventWatch (void* userdata, SDL_Event* event); + void CheckModalException (bool blocking = false); #endif void ProcessClipboardEvent (SDL_Event* event); void ProcessDropEvent (SDL_Event* event); void ProcessGamepadEvent (SDL_Event* event); void ProcessJoystickEvent (SDL_Event* event); void ProcessKeyEvent (SDL_Event* event); + void ProcessMenuEvent (SDL_Event* event); void ProcessMouseEvent (SDL_Event* event); void ProcessSensorEvent (SDL_Event* event); void ProcessTextEvent (SDL_Event* event); void ProcessTouchEvent (SDL_Event* event); - void ProcessWindowEvent (SDL_Event* event); + void ProcessTrayIconEvent (SDL_Event* event); + void ProcessWindowEvent (SDL_Event* event, bool currentSize = false); +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + bool PrepareResizeEvent (SDL_Event* event, bool currentSize); +#endif void UpdateSleepGuard (Uint32 requestedMs, Uint32 elapsedMs); int WaitEvent (SDL_Event* event); @@ -124,11 +138,14 @@ namespace lime { }; bool active; + int activeUncapMode; bool allowBusyWait; ApplicationEvent applicationEvent; bool busyWaitOnly; ClipboardEvent clipboardEvent; double currentUpdate; + double deltaRemainder; + Uint64 dispatchedFrames; double displayRefreshRate; double framePeriod; Uint32 initFlags; @@ -139,10 +156,16 @@ namespace lime { KeyEvent keyEvent; double lastUpdate; Uint32 lastSleepCalibration; + MenuEvent menuEvent; MouseEvent mouseEvent; + bool mouseCaptureRequested; double nextUpdate; OrientationEvent orientationEvent; Uint64 performanceFrequency; + Uint64 clockStartCounter; + Uint64 clockStartTicks; + mutable Uint32 clockLastTicks; + mutable Uint64 clockElapsedTicks; bool realVSyncActive; int requestedBusyWaitMode; double requestedFrameRate; @@ -156,15 +179,18 @@ namespace lime { double sleepGuardMs; TextEvent textEvent; TouchEvent touchEvent; + TrayIconEvent trayIconEvent; bool useDisplayDrivenFallback; bool useHighResolutionTimer; WindowEvent windowEvent; std::vector windows; #if defined(HX_WINDOWS) && !defined(HX_WINRT) bool modalWatchInstalled; + ValuePointer* modalCallbacks[4]; + bool modalExceptionPending; + std::exception_ptr modalNativeException; Uint32 mainThreadID; - int pendingResizeDispatchSkips; - int pendingWatchRenderSkips; + std::map> dispatchedWindowSizes; #endif }; diff --git a/project/src/backend/sdl/SDLGamepad.cpp b/project/src/backend/sdl/SDLGamepad.cpp index bae1fbf70e..ae1c55796f 100644 --- a/project/src/backend/sdl/SDLGamepad.cpp +++ b/project/src/backend/sdl/SDLGamepad.cpp @@ -84,6 +84,199 @@ namespace lime { } + static SDL_GameController* GetController (int id) { + + auto it = gameControllers.find (id); + return it != gameControllers.end () ? it->second : nullptr; + + } + + + static Uint16 ToRumble (double value) { + + if (value < 0.0) value = 0.0; + if (value > 1.0) value = 1.0; + return (Uint16)(value * 0xFFFF); + + } + + + int Gamepad::GetType (int id) { + + SDL_GameController* controller = GetController (id); + return controller ? SDL_GameControllerGetType (controller) : SDL_CONTROLLER_TYPE_UNKNOWN; + + } + + + bool Gamepad::HasLED (int id) { + + SDL_GameController* controller = GetController (id); + return controller && SDL_GameControllerHasLED (controller) == SDL_TRUE; + + } + + + bool Gamepad::HasRumbleTriggers (int id) { + + SDL_GameController* controller = GetController (id); + return controller && SDL_GameControllerHasRumbleTriggers (controller) == SDL_TRUE; + + } + + + bool Gamepad::HasSensor (int id, int sensor) { + + SDL_GameController* controller = GetController (id); + return controller && SDL_GameControllerHasSensor (controller, (SDL_SensorType)sensor) == SDL_TRUE; + + } + + + bool Gamepad::RumbleTriggers (int id, double leftRumble, double rightRumble, int duration) { + + SDL_GameController* controller = GetController (id); + return controller && SDL_GameControllerRumbleTriggers (controller, ToRumble (leftRumble), ToRumble (rightRumble), duration) == 0; + + } + + + bool Gamepad::SetLED (int id, int red, int green, int blue) { + + SDL_GameController* controller = GetController (id); + return controller && SDL_GameControllerSetLED (controller, red, green, blue) == 0; + + } + + + bool Gamepad::SetSensorEnabled (int id, int sensor, bool enabled) { + + SDL_GameController* controller = GetController (id); + return controller && SDL_GameControllerSetSensorEnabled (controller, (SDL_SensorType)sensor, enabled ? SDL_TRUE : SDL_FALSE) == 0; + + } + + + struct VirtualGamepadOutput { + + int led; + int lowRumble; + int highRumble; + int leftRumble; + int rightRumble; + + }; + + + static std::map virtualGamepads; + + + static int SDLCALL VirtualRumble (void* userdata, Uint16 lowRumble, Uint16 highRumble) { + + VirtualGamepadOutput* output = (VirtualGamepadOutput*)userdata; + output->lowRumble = lowRumble; + output->highRumble = highRumble; + return 0; + + } + + + static int SDLCALL VirtualRumbleTriggers (void* userdata, Uint16 leftRumble, Uint16 rightRumble) { + + VirtualGamepadOutput* output = (VirtualGamepadOutput*)userdata; + output->leftRumble = leftRumble; + output->rightRumble = rightRumble; + return 0; + + } + + + static int SDLCALL VirtualSetLED (void* userdata, Uint8 red, Uint8 green, Uint8 blue) { + + VirtualGamepadOutput* output = (VirtualGamepadOutput*)userdata; + output->led = (red << 16) | (green << 8) | blue; + return 0; + + } + + + int Gamepad::AttachVirtual () { + + VirtualGamepadOutput* output = new VirtualGamepadOutput (); + output->led = -1; + output->lowRumble = output->highRumble = output->leftRumble = output->rightRumble = -1; + + SDL_VirtualJoystickDesc desc; + SDL_zero (desc); + desc.version = SDL_VIRTUAL_JOYSTICK_DESC_VERSION; + desc.type = SDL_JOYSTICK_TYPE_GAMECONTROLLER; + desc.naxes = SDL_CONTROLLER_AXIS_MAX; + desc.nbuttons = SDL_CONTROLLER_BUTTON_MAX; + desc.name = "Lime Virtual Gamepad"; + desc.userdata = output; + desc.Rumble = VirtualRumble; + desc.RumbleTriggers = VirtualRumbleTriggers; + desc.SetLED = VirtualSetLED; + + int deviceIndex = SDL_JoystickAttachVirtualEx (&desc); + + if (deviceIndex < 0 || !SDLGamepad::Connect (deviceIndex)) { + + if (deviceIndex >= 0) SDL_JoystickDetachVirtual (deviceIndex); + delete output; + return -1; + + } + + int id = SDLGamepad::GetInstanceID (deviceIndex); + virtualGamepads[id] = output; + return id; + + } + + + void Gamepad::DetachVirtual (int id) { + + SDLGamepad::Disconnect (id); + + for (int i = 0; i < SDL_NumJoysticks (); i++) { + + if (SDL_JoystickIsVirtual (i) && SDL_JoystickGetDeviceInstanceID (i) == id) { + + SDL_JoystickDetachVirtual (i); + break; + + } + + } + + auto it = virtualGamepads.find (id); + + if (it != virtualGamepads.end ()) { + + delete it->second; + virtualGamepads.erase (it); + + } + + } + + + bool Gamepad::GetVirtualOutput (int id, int* led, int* lowRumble, int* highRumble, int* leftRumble, int* rightRumble) { + + auto it = virtualGamepads.find (id); + if (it == virtualGamepads.end ()) return false; + + *led = it->second->led; + *lowRumble = it->second->lowRumble; + *highRumble = it->second->highRumble; + *leftRumble = it->second->leftRumble; + *rightRumble = it->second->rightRumble; + return true; + + } + + void Gamepad::Rumble (int id, double lowFrequencyRumble, double highFrequencyRumble, int duration) { auto it = gameControllers.find (id); diff --git a/project/src/backend/sdl/SDLMenu.cpp b/project/src/backend/sdl/SDLMenu.cpp new file mode 100644 index 0000000000..fc53012ddf --- /dev/null +++ b/project/src/backend/sdl/SDLMenu.cpp @@ -0,0 +1,350 @@ +#include "SDLMenu.h" +#include "SDLTrayIcon.h" +#include +#include + + +namespace lime { + + + // Menu data is written by NativeMenu.hx as little-endian Int32 values: + // + // menu := itemCount, item[itemCount] + // item := id, flags, keyCode, keyModifiers, labelLength, label, [menu if flags & MENU_ITEM_SUBMENU] + // + // Labels are UTF-8, with "&" marking the mnemonic and "&&" for a literal ampersand. + + enum MenuEventKind { + + MENU_EVENT_SELECTION = 1, + MENU_EVENT_TRAY_ICON = 2, + MENU_EVENT_DOCK_SELECTION = 3 + + }; + + + static const int MAX_MENU_DEPTH = 32; + static Uint32 menuEventType = 0; + + + static bool ReadInt (const unsigned char* data, int length, int* position, int* result) { + + if (length - *position < 4) return false; + + const unsigned char* bytes = data + *position; + *result = (int)((Uint32)bytes[0] | ((Uint32)bytes[1] << 8) | ((Uint32)bytes[2] << 16) | ((Uint32)bytes[3] << 24)); + *position += 4; + return true; + + } + + + static bool ParseItems (const unsigned char* data, int length, int* position, std::vector* items, int depth) { + + int count; + + if (depth > MAX_MENU_DEPTH || !ReadInt (data, length, position, &count) || count < 0) return false; + + items->resize (count); + + for (int i = 0; i < count; i++) { + + SDLMenuItem* item = &(*items)[i]; + int labelLength; + + if (!ReadInt (data, length, position, &item->id) || !ReadInt (data, length, position, &item->flags) + || !ReadInt (data, length, position, &item->keyCode) || !ReadInt (data, length, position, &item->keyModifiers) + || !ReadInt (data, length, position, &labelLength) || labelLength < 0 || length - *position < labelLength) { + + return false; + + } + + item->label.assign ((const char*)(data + *position), labelLength); + *position += labelLength; + + if ((item->flags & MENU_ITEM_SUBMENU) && !ParseItems (data, length, position, &item->submenu, depth + 1)) { + + return false; + + } + + } + + return true; + + } + + + TrayIcon* CreateTrayIcon () { + + return new SDLTrayIcon (); + + } + + + int Menu::GetSupport () { + + return SDLMenu::GetSupport (); + + } + + + std::string SDLMenu::GetLabel (const std::string& label, char mnemonicPrefix) { + + if (mnemonicPrefix == '&') return label; + + std::string result; + result.reserve (label.size ()); + + for (size_t i = 0; i < label.size (); i++) { + + char c = label[i]; + + if (c == '&') { + + if (i + 1 < label.size () && label[i + 1] == '&') { + + result += '&'; + i++; + + } else if (mnemonicPrefix) { + + result += mnemonicPrefix; + + } + + } else if (mnemonicPrefix && c == mnemonicPrefix) { + + result += mnemonicPrefix; + result += mnemonicPrefix; + + } else { + + result += c; + + } + + } + + return result; + + } + + + bool SDLMenu::GetDockMenuSelection (SDL_Event* event, int* id) { + + if (menuEventType == 0 || event->type != menuEventType || (intptr_t)event->user.data1 != MENU_EVENT_DOCK_SELECTION) return false; + + *id = event->user.code; + return true; + + } + + + bool SDLMenu::GetSelection (SDL_Event* event, Uint32* windowID, int* id) { + + if (menuEventType != 0 && event->type == menuEventType && (intptr_t)event->user.data1 == MENU_EVENT_SELECTION) { + + *windowID = event->user.windowID; + *id = event->user.code; + return true; + + } + + return GetPlatformSelection (event, windowID, id); + + } + + + bool SDLMenu::GetTrayIconEvent (SDL_Event* event, int* trayIconID, int* type, int* itemID) { + + if (menuEventType == 0 || event->type != menuEventType || (intptr_t)event->user.data1 != MENU_EVENT_TRAY_ICON) return false; + + *trayIconID = (int)event->user.windowID; + *type = event->user.code; + *itemID = (int)(intptr_t)event->user.data2; + return true; + + } + + + bool SDLMenu::Parse (const unsigned char* data, int length, std::vector* items) { + + int position = 0; + return data && length > 0 && ParseItems (data, length, &position, items, 0); + + } + + + void SDLMenu::PushEvent (int kind, Uint32 target, int code, int value) { + + if (menuEventType == 0) { + + menuEventType = SDL_RegisterEvents (1); + + // SDLApplication treats SDL_USEREVENT as a frame request, so never share it + if (menuEventType == SDL_USEREVENT) menuEventType = SDL_RegisterEvents (1); + + if (menuEventType == (Uint32)-1) { + + menuEventType = 0; + return; + + } + + } + + SDL_Event event; + SDL_zero (event); + event.type = menuEventType; + event.user.windowID = target; + event.user.code = code; + event.user.data1 = (void*)(intptr_t)kind; + event.user.data2 = (void*)(intptr_t)value; + SDL_PushEvent (&event); + + } + + + void SDLMenu::PushDockMenuSelection (int id) { + + PushEvent (MENU_EVENT_DOCK_SELECTION, 0, id, 0); + + } + + + void SDLMenu::PushSelection (Uint32 windowID, int id) { + + PushEvent (MENU_EVENT_SELECTION, windowID, id, 0); + + } + + + void SDLMenu::PushTrayIconEvent (int trayIconID, int type, int itemID) { + + PushEvent (MENU_EVENT_TRAY_ICON, (Uint32)trayIconID, type, itemID); + + } + + + int SDLTrayIcon::GetID () { + + return id; + + } + + +#ifndef LIME_SDL_MENU_COCOA + + // The Dock only exists on macOS + + void DockIcon::Bounce (bool critical) {} + + + bool DockIcon::IsSupported () { + + return false; + + } + + + void DockIcon::SetIcon (ImageBuffer* imageBuffer) {} + + + bool DockIcon::SetMenu (const unsigned char* data, int length) { + + return false; + + } + +#endif + + +#if !defined (LIME_SDL_MENU_WIN32) && !defined (LIME_SDL_MENU_COCOA) && !defined (LIME_SDL_MENU_GTK) + + bool SDLMenu::GetPlatformSelection (SDL_Event* event, Uint32* windowID, int* id) { + + return false; + + } + + + int SDLMenu::GetSupport () { + + return 0; + + } + + + int SDLMenu::Popup (SDL_Window* window, const unsigned char* data, int length, int x, int y, bool atCursor) { + + return 0; + + } + + + void SDLMenu::Remove (SDL_Window* window) {} + + + bool SDLMenu::SetApplicationMenu (const unsigned char* data, int length) { + + return false; + + } + + + bool SDLMenu::SetWindowMenu (SDL_Window* window, const unsigned char* data, int length) { + + return false; + + } + + + bool TrayIcon::IsSupported () { + + return false; + + } + + + SDLTrayIcon::SDLTrayIcon () { + + id = 0; + platform = 0; + + } + + + SDLTrayIcon::~SDLTrayIcon () {} + + + void SDLTrayIcon::Close () {} + + + int SDLTrayIcon::PopupMenu (const unsigned char* data, int length) { + + return 0; + + } + + + void SDLTrayIcon::SetIcon (ImageBuffer* imageBuffer) {} + + + bool SDLTrayIcon::SetMenu (const unsigned char* data, int length) { + + return false; + + } + + + void SDLTrayIcon::SetTooltip (const char* tooltip) {} + + + void SDLTrayIcon::Update () {} + +#endif + + +} diff --git a/project/src/backend/sdl/SDLMenu.h b/project/src/backend/sdl/SDLMenu.h new file mode 100644 index 0000000000..191f677c1d --- /dev/null +++ b/project/src/backend/sdl/SDLMenu.h @@ -0,0 +1,74 @@ +#ifndef LIME_SDL_MENU_H +#define LIME_SDL_MENU_H + + +#include +#include +#include +#include + +#if defined (HX_WINDOWS) && !defined (HX_WINRT) +#define LIME_SDL_MENU_WIN32 +#elif defined (HX_MACOS) +#define LIME_SDL_MENU_COCOA +#elif defined (HX_LINUX) && !defined (HX_ANDROID) && !defined (EMSCRIPTEN) +#define LIME_SDL_MENU_GTK +#endif + + +namespace lime { + + + enum SDLMenuItemFlags { + + MENU_ITEM_ENABLED = 0x01, + MENU_ITEM_CHECKED = 0x02, + MENU_ITEM_SEPARATOR = 0x04, + MENU_ITEM_SUBMENU = 0x08 + + }; + + + struct SDLMenuItem { + + int flags; + int id; + int keyCode; + int keyModifiers; + std::string label; + std::vector submenu; + + }; + + + class SDLMenu { + + public: + + static bool GetDockMenuSelection (SDL_Event* event, int* id); + static bool GetSelection (SDL_Event* event, Uint32* windowID, int* id); + static int GetSupport (); + static bool GetTrayIconEvent (SDL_Event* event, int* trayIconID, int* type, int* itemID); + static int Popup (SDL_Window* window, const unsigned char* data, int length, int x, int y, bool atCursor); + static void Remove (SDL_Window* window); + static bool SetApplicationMenu (const unsigned char* data, int length); + static bool SetWindowMenu (SDL_Window* window, const unsigned char* data, int length); + + static std::string GetLabel (const std::string& label, char mnemonicPrefix); + static bool Parse (const unsigned char* data, int length, std::vector* items); + static void PushDockMenuSelection (int id); + static void PushSelection (Uint32 windowID, int id); + static void PushTrayIconEvent (int trayIconID, int type, int itemID); + + private: + + static bool GetPlatformSelection (SDL_Event* event, Uint32* windowID, int* id); + static void PushEvent (int kind, Uint32 target, int code, int value); + + }; + + +} + + +#endif diff --git a/project/src/backend/sdl/SDLMenuLinux.cpp b/project/src/backend/sdl/SDLMenuLinux.cpp new file mode 100644 index 0000000000..8cef23086c --- /dev/null +++ b/project/src/backend/sdl/SDLMenuLinux.cpp @@ -0,0 +1,629 @@ +#include "SDLMenu.h" + +#ifdef LIME_SDL_MENU_GTK + +// Native menus and tray icons for Linux + +// Lime's SDL backend uses the minimal SDL configuration, so enable the X11 window info explicitly +#ifndef SDL_VIDEO_DRIVER_X11 +#define SDL_VIDEO_DRIVER_X11 1 +#endif + +#include "SDLTrayIcon.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LIME_PNG +#include +#include +#endif + + +namespace lime { + + + // GTK 3 and AppIndicator are loaded at runtime, so Lime has no build or link dependency on them. + // Popup menus and tray icons are supported: an SDL window cannot host a GTK menu bar. + + typedef void (*GCallbackFunc) (void); + typedef void (*GtkMenuPositionFunc) (void* menu, int* x, int* y, int* pushIn, void* userData); + + + struct GTKLibrary { + + bool initialized; + bool available; + + void (*gdk_set_allowed_backends) (const char*); + int (*g_main_context_iteration) (void*, int); + void* (*g_object_ref_sink) (void*); + void (*g_object_unref) (void*); + unsigned long (*g_signal_connect_data) (void*, const char*, GCallbackFunc, void*, void*, int); + void (*gtk_accel_label_set_accel) (void*, unsigned int, int); + void* (*gtk_bin_get_child) (void*); + void* (*gtk_check_menu_item_new_with_mnemonic) (const char*); + void (*gtk_check_menu_item_set_active) (void*, int); + void (*gtk_disable_setlocale) (void); + int (*gtk_events_pending) (void); + int (*gtk_init_check) (int*, char***); + void (*gtk_main) (void); + int (*gtk_main_iteration) (void); + void (*gtk_main_quit) (void); + void* (*gtk_menu_item_new_with_mnemonic) (const char*); + void (*gtk_menu_item_set_submenu) (void*, void*); + void* (*gtk_menu_new) (void); + void (*gtk_menu_popup) (void*, void*, void*, GtkMenuPositionFunc, void*, unsigned int, unsigned int); + void (*gtk_menu_shell_append) (void*, void*); + void* (*gtk_separator_menu_item_new) (void); + void (*gtk_widget_destroy) (void*); + void* (*gtk_widget_get_toplevel) (void*); + int (*gtk_widget_get_visible) (void*); + void (*gtk_widget_set_sensitive) (void*, int); + void (*gtk_widget_show_all) (void*); + int (*XFlush) (void*); + int (*XUngrabPointer) (void*, unsigned long); + + }; + + + struct PopupPosition { + + int x; + int y; + + }; + + + static GTKLibrary gtk; + static int popupSelection = 0; + + + static bool LoadGTK () { + + if (gtk.initialized) return gtk.available; + + gtk.initialized = true; + + void* library = dlopen ("libgtk-3.so.0", RTLD_NOW | RTLD_LOCAL); + + if (!library) return false; + + #define LIME_GTK_REQUIRE(name) gtk.name = (decltype (gtk.name))dlsym (library, #name); if (!gtk.name) return false; + #define LIME_GTK_OPTIONAL(name) gtk.name = (decltype (gtk.name))dlsym (library, #name); + + LIME_GTK_REQUIRE (g_main_context_iteration); + LIME_GTK_REQUIRE (g_object_ref_sink); + LIME_GTK_REQUIRE (g_object_unref); + LIME_GTK_REQUIRE (g_signal_connect_data); + LIME_GTK_REQUIRE (gtk_bin_get_child); + LIME_GTK_REQUIRE (gtk_check_menu_item_new_with_mnemonic); + LIME_GTK_REQUIRE (gtk_check_menu_item_set_active); + LIME_GTK_REQUIRE (gtk_events_pending); + LIME_GTK_REQUIRE (gtk_init_check); + LIME_GTK_REQUIRE (gtk_main); + LIME_GTK_REQUIRE (gtk_main_iteration); + LIME_GTK_REQUIRE (gtk_main_quit); + LIME_GTK_REQUIRE (gtk_menu_item_new_with_mnemonic); + LIME_GTK_REQUIRE (gtk_menu_item_set_submenu); + LIME_GTK_REQUIRE (gtk_menu_new); + LIME_GTK_REQUIRE (gtk_menu_popup); + LIME_GTK_REQUIRE (gtk_menu_shell_append); + LIME_GTK_REQUIRE (gtk_separator_menu_item_new); + LIME_GTK_REQUIRE (gtk_widget_destroy); + LIME_GTK_REQUIRE (gtk_widget_get_toplevel); + LIME_GTK_REQUIRE (gtk_widget_get_visible); + LIME_GTK_REQUIRE (gtk_widget_set_sensitive); + LIME_GTK_REQUIRE (gtk_widget_show_all); + + LIME_GTK_OPTIONAL (gdk_set_allowed_backends); + LIME_GTK_OPTIONAL (gtk_accel_label_set_accel); + LIME_GTK_OPTIONAL (gtk_disable_setlocale); + LIME_GTK_OPTIONAL (XFlush); + LIME_GTK_OPTIONAL (XUngrabPointer); + + #undef LIME_GTK_REQUIRE + #undef LIME_GTK_OPTIONAL + + // Keep the application's locale, and use the same windowing system as SDL + if (gtk.gtk_disable_setlocale) gtk.gtk_disable_setlocale (); + if (gtk.gdk_set_allowed_backends) gtk.gdk_set_allowed_backends ("x11"); + + if (!gtk.gtk_init_check (NULL, NULL)) return false; + + gtk.available = true; + return true; + + } + + + static unsigned int GetKeyval (int keyCode) { + + // Printable ASCII matches the GDK keysyms (SDL letter keycodes are lowercase) + if (keyCode >= 0x20 && keyCode < 0x7F) return (unsigned int)keyCode; + if (keyCode >= SDLK_F1 && keyCode <= SDLK_F12) return 0xFFBE + (keyCode - SDLK_F1); + if (keyCode >= SDLK_F13 && keyCode <= SDLK_F24) return 0xFFCA + (keyCode - SDLK_F13); + + switch (keyCode) { + + case SDLK_BACKSPACE: return 0xFF08; + case SDLK_TAB: return 0xFF09; + case SDLK_RETURN: return 0xFF0D; + case SDLK_ESCAPE: return 0xFF1B; + case SDLK_HOME: return 0xFF50; + case SDLK_LEFT: return 0xFF51; + case SDLK_UP: return 0xFF52; + case SDLK_RIGHT: return 0xFF53; + case SDLK_DOWN: return 0xFF54; + case SDLK_PAGEUP: return 0xFF55; + case SDLK_PAGEDOWN: return 0xFF56; + case SDLK_END: return 0xFF57; + case SDLK_INSERT: return 0xFF63; + case SDLK_DELETE: return 0xFFFF; + default: return 0; + + } + + } + + + static int GetModifierMask (int keyModifiers) { + + int mask = 0; + + if (keyModifiers & KMOD_SHIFT) mask |= (1 << 0); + if (keyModifiers & KMOD_CTRL) mask |= (1 << 2); + if (keyModifiers & KMOD_ALT) mask |= (1 << 3); + if (keyModifiers & KMOD_GUI) mask |= (1 << 26); + + return mask; + + } + + + // Menu item callbacks receive the tray icon ID (or zero) in the high bits and the item ID in the low 16 bits + + static void OnItemActivate (void* widget, void* userData) { + + popupSelection = (int)((intptr_t)userData & 0xFFFF); + + } + + + static void OnTrayIconItemActivate (void* widget, void* userData) { + + intptr_t value = (intptr_t)userData; + SDLMenu::PushTrayIconEvent ((int)(value >> 16), TRAY_ICON_MENU_SELECT, (int)(value & 0xFFFF)); + + } + + + static void OnMenuDeactivate (void* widget, void* userData) { + + gtk.gtk_main_quit (); + + } + + + static void PositionMenu (void* menu, int* x, int* y, int* pushIn, void* userData) { + + PopupPosition* position = (PopupPosition*)userData; + *x = position->x; + *y = position->y; + *pushIn = 1; + + } + + + static void AppendMenuItems (void* menu, const std::vector& items, GCallbackFunc onActivate, int tag) { + + for (size_t i = 0; i < items.size (); i++) { + + const SDLMenuItem& item = items[i]; + bool hasSubmenu = (item.flags & MENU_ITEM_SUBMENU) != 0; + void* widget; + + if ((item.flags & MENU_ITEM_SEPARATOR) && !hasSubmenu) { + + widget = gtk.gtk_separator_menu_item_new (); + + } else { + + std::string label = SDLMenu::GetLabel (item.label, '_'); + + if ((item.flags & MENU_ITEM_CHECKED) && !hasSubmenu) { + + // Setting the active state emits "activate", so connect afterwards + widget = gtk.gtk_check_menu_item_new_with_mnemonic (label.c_str ()); + gtk.gtk_check_menu_item_set_active (widget, 1); + + } else { + + widget = gtk.gtk_menu_item_new_with_mnemonic (label.c_str ()); + + } + + gtk.gtk_widget_set_sensitive (widget, (item.flags & MENU_ITEM_ENABLED) ? 1 : 0); + + if (hasSubmenu) { + + void* submenu = gtk.gtk_menu_new (); + AppendMenuItems (submenu, item.submenu, onActivate, tag); + gtk.gtk_menu_item_set_submenu (widget, submenu); + + } else { + + unsigned int keyval = item.keyCode != 0 ? GetKeyval (item.keyCode) : 0; + void* accelLabel = keyval && gtk.gtk_accel_label_set_accel ? gtk.gtk_bin_get_child (widget) : NULL; + + if (accelLabel) { + + gtk.gtk_accel_label_set_accel (accelLabel, keyval, GetModifierMask (item.keyModifiers)); + + } + + intptr_t userData = ((intptr_t)tag << 16) | (item.id & 0xFFFF); + gtk.g_signal_connect_data (widget, "activate", onActivate, (void*)userData, NULL, 0); + + } + + } + + gtk.gtk_menu_shell_append (menu, widget); + + } + + } + + + bool SDLMenu::GetPlatformSelection (SDL_Event* event, Uint32* windowID, int* id) { + + return false; + + } + + + int SDLMenu::GetSupport () { + + const char* driver = SDL_GetCurrentVideoDriver (); + + if (driver && strcmp (driver, "x11") != 0) return 0; + + return LoadGTK () ? MENU_SUPPORT_POPUP : 0; + + } + + + int SDLMenu::Popup (SDL_Window* window, const unsigned char* data, int length, int x, int y, bool atCursor) { + + SDL_SysWMinfo info; + SDL_VERSION (&info.version); + + if (!window || !SDL_GetWindowWMInfo (window, &info) || info.subsystem != SDL_SYSWM_X11) return 0; + + std::vector items; + + if (!Parse (data, length, &items) || !LoadGTK ()) return 0; + + // A pressed mouse button holds an implicit pointer grab on SDL's window, which would stop the menu from taking its own + if (gtk.XUngrabPointer) { + + gtk.XUngrabPointer (info.info.x11.display, 0); + if (gtk.XFlush) gtk.XFlush (info.info.x11.display); + + } + + void* menu = gtk.gtk_menu_new (); + gtk.g_object_ref_sink (menu); + + AppendMenuItems (menu, items, (GCallbackFunc)OnItemActivate, 0); + gtk.gtk_widget_show_all (menu); + gtk.g_signal_connect_data (menu, "deactivate", (GCallbackFunc)OnMenuDeactivate, NULL, NULL, 0); + + PopupPosition position; + SDL_GetWindowPosition (window, &position.x, &position.y); + position.x += x; + position.y += y; + + popupSelection = 0; + + gtk.gtk_menu_popup (menu, NULL, NULL, atCursor ? NULL : PositionMenu, atCursor ? NULL : &position, 0, 0); + + // GTK gives up without emitting "deactivate" if it cannot grab the pointer, so only wait for a menu that opened + if (gtk.gtk_widget_get_visible (gtk.gtk_widget_get_toplevel (menu))) { + + gtk.gtk_main (); + + } + + gtk.gtk_widget_destroy (menu); + gtk.g_object_unref (menu); + + while (gtk.gtk_events_pending ()) { + + gtk.gtk_main_iteration (); + + } + + return popupSelection; + + } + + + void SDLMenu::Remove (SDL_Window* window) {} + + + bool SDLMenu::SetApplicationMenu (const unsigned char* data, int length) { + + return false; + + } + + + bool SDLMenu::SetWindowMenu (SDL_Window* window, const unsigned char* data, int length) { + + return false; + + } + + + // Tray icons use AppIndicator, which publishes them to the desktop as StatusNotifierItems over + // D-Bus, with the menu exported by dbusmenu. The desktop shows the menu, and AppIndicator does + // not report clicks on the icon itself. + + struct AppIndicatorLibrary { + + bool initialized; + bool available; + + void* (*app_indicator_new) (const char*, const char*, int); + void (*app_indicator_set_icon_full) (void*, const char*, const char*); + void (*app_indicator_set_menu) (void*, void*); + void (*app_indicator_set_status) (void*, int); + void (*app_indicator_set_title) (void*, const char*); + + }; + + + struct GTKTrayIcon { + + void* indicator; + void* menu; + std::string iconPath; + + }; + + + static const int APP_INDICATOR_CATEGORY_APPLICATION_STATUS = 0; + static const int APP_INDICATOR_STATUS_PASSIVE = 0; + static const int APP_INDICATOR_STATUS_ACTIVE = 1; + + static AppIndicatorLibrary appIndicator; + static int nextTrayIconID = 0; + static int trayIconCount = 0; + + + static bool LoadAppIndicator () { + + if (appIndicator.initialized) return appIndicator.available; + + appIndicator.initialized = true; + + if (!LoadGTK ()) return false; + + void* library = dlopen ("libayatana-appindicator3.so.1", RTLD_NOW | RTLD_LOCAL); + if (!library) library = dlopen ("libappindicator3.so.1", RTLD_NOW | RTLD_LOCAL); + if (!library) return false; + + #define LIME_APP_INDICATOR_REQUIRE(name) appIndicator.name = (decltype (appIndicator.name))dlsym (library, #name); if (!appIndicator.name) return false; + + LIME_APP_INDICATOR_REQUIRE (app_indicator_new); + LIME_APP_INDICATOR_REQUIRE (app_indicator_set_icon_full); + LIME_APP_INDICATOR_REQUIRE (app_indicator_set_menu); + LIME_APP_INDICATOR_REQUIRE (app_indicator_set_status); + LIME_APP_INDICATOR_REQUIRE (app_indicator_set_title); + + #undef LIME_APP_INDICATOR_REQUIRE + + appIndicator.available = true; + return true; + + } + + + static void IterateMainContext () { + + for (int i = 0; i < 64 && gtk.g_main_context_iteration (NULL, 0); i++) {} + + } + + + static std::string WriteIconFile (int id, ImageBuffer* imageBuffer) { + + #ifdef LIME_PNG + + Bytes bytes; + + if (!imageBuffer || !PNG::Encode (imageBuffer, &bytes) || bytes.length <= 0) return std::string (); + + static int counter = 0; + const char* directory = getenv ("XDG_RUNTIME_DIR"); + if (!directory || !directory[0]) directory = "/tmp"; + + // Use a new name for each icon, since the desktop may cache icons by name + char path[1024]; + snprintf (path, sizeof (path), "%s/lime-tray-%d-%d-%d.png", directory, (int)getpid (), id, ++counter); + + FILE* file = fopen (path, "wb"); + if (!file) return std::string (); + + size_t written = fwrite (bytes.b, 1, bytes.length, file); + fclose (file); + + if (written != (size_t)bytes.length) { + + unlink (path); + return std::string (); + + } + + return std::string (path); + + #else + + return std::string (); + + #endif + + } + + + bool TrayIcon::IsSupported () { + + return LoadAppIndicator (); + + } + + + SDLTrayIcon::SDLTrayIcon () { + + id = ++nextTrayIconID; + platform = NULL; + + if (!LoadAppIndicator ()) return; + + char name[64]; + snprintf (name, sizeof (name), "lime-%d-%d", (int)getpid (), id); + + GTKTrayIcon* data = new GTKTrayIcon (); + data->indicator = appIndicator.app_indicator_new (name, "application-x-executable", APP_INDICATOR_CATEGORY_APPLICATION_STATUS); + data->menu = NULL; + platform = data; + trayIconCount++; + + // AppIndicator only shows icons that have a menu + SetMenu (NULL, 0); + appIndicator.app_indicator_set_status (data->indicator, APP_INDICATOR_STATUS_ACTIVE); + IterateMainContext (); + + } + + + SDLTrayIcon::~SDLTrayIcon () { + + Close (); + + } + + + void SDLTrayIcon::Close () { + + GTKTrayIcon* data = (GTKTrayIcon*)platform; + + if (!data) return; + + appIndicator.app_indicator_set_status (data->indicator, APP_INDICATOR_STATUS_PASSIVE); + gtk.g_object_unref (data->indicator); + + if (data->menu) { + + gtk.gtk_widget_destroy (data->menu); + gtk.g_object_unref (data->menu); + + } + + if (!data->iconPath.empty ()) unlink (data->iconPath.c_str ()); + + delete data; + platform = NULL; + trayIconCount--; + + IterateMainContext (); + + } + + + int SDLTrayIcon::PopupMenu (const unsigned char* data, int length) { + + // The desktop shows the menu exported by AppIndicator + return 0; + + } + + + void SDLTrayIcon::SetIcon (ImageBuffer* imageBuffer) { + + GTKTrayIcon* data = (GTKTrayIcon*)platform; + + if (!data) return; + + std::string path = WriteIconFile (id, imageBuffer); + + if (path.empty ()) return; + + appIndicator.app_indicator_set_icon_full (data->indicator, path.c_str (), ""); + + if (!data->iconPath.empty ()) unlink (data->iconPath.c_str ()); + data->iconPath = path; + + } + + + bool SDLTrayIcon::SetMenu (const unsigned char* data, int length) { + + GTKTrayIcon* trayData = (GTKTrayIcon*)platform; + + if (!trayData) return false; + + std::vector items; + + if (data && length > 0 && !SDLMenu::Parse (data, length, &items)) return false; + + void* menu = gtk.gtk_menu_new (); + gtk.g_object_ref_sink (menu); + + AppendMenuItems (menu, items, (GCallbackFunc)OnTrayIconItemActivate, id); + gtk.gtk_widget_show_all (menu); + appIndicator.app_indicator_set_menu (trayData->indicator, menu); + + if (trayData->menu) { + + gtk.gtk_widget_destroy (trayData->menu); + gtk.g_object_unref (trayData->menu); + + } + + trayData->menu = menu; + return true; + + } + + + void SDLTrayIcon::SetTooltip (const char* tooltip) { + + GTKTrayIcon* data = (GTKTrayIcon*)platform; + + if (!data) return; + + appIndicator.app_indicator_set_title (data->indicator, tooltip ? tooltip : ""); + + } + + + void SDLTrayIcon::Update () { + + // AppIndicator communicates over D-Bus, which needs the GLib main context to run + if (trayIconCount > 0) IterateMainContext (); + + } + + +} + + +#endif diff --git a/project/src/backend/sdl/SDLMenuMac.mm b/project/src/backend/sdl/SDLMenuMac.mm new file mode 100644 index 0000000000..d8314e6d37 --- /dev/null +++ b/project/src/backend/sdl/SDLMenuMac.mm @@ -0,0 +1,672 @@ +#include "SDLMenu.h" + +#ifdef LIME_SDL_MENU_COCOA + +// Native menus, tray icons and the Dock icon for macOS + +#import +#import +#include "SDLTrayIcon.h" +#include +#include +#include +#include + +#if __has_feature(objc_arc) +#define LIME_AUTORELEASE(object) (object) +#define LIME_RETAIN(object) (object) +#define LIME_RELEASE(object) +#define LIME_BRIDGE(type, pointer) ((__bridge type)(pointer)) +#define LIME_BRIDGE_RETAIN(object) ((void*)CFBridgingRetain (object)) +#define LIME_BRIDGE_RELEASE(pointer) CFRelease ((CFTypeRef)(pointer)) +#else +#define LIME_AUTORELEASE(object) [(object) autorelease] +#define LIME_RETAIN(object) [(object) retain] +#define LIME_RELEASE(object) [(object) release] +#define LIME_BRIDGE(type, pointer) ((type)(pointer)) +#define LIME_BRIDGE_RETAIN(object) ((void*)[(object) retain]) +#define LIME_BRIDGE_RELEASE(pointer) [(NSObject*)(pointer) release] +#endif + + +@interface LimeMenuTarget : NSObject { + + @public + NSInteger highlightedTag; + NSInteger popupSelection; + +} + +- (void)clickTrayIcon:(id)sender; +- (void)selectApplicationMenuItem:(NSMenuItem*)sender; +- (void)selectDockMenuItem:(NSMenuItem*)sender; +- (void)selectPopupMenuItem:(NSMenuItem*)sender; +- (void)selectTrayIconMenuItem:(NSMenuItem*)sender; + +@end + + +@implementation LimeMenuTarget + +- (void)clickTrayIcon:(id)sender { + + NSEvent* event = [NSApp currentEvent]; + bool rightClick = event && ([event type] == NSEventTypeRightMouseUp || ([event modifierFlags] & NSEventModifierFlagControl)); + + lime::SDLMenu::PushTrayIconEvent ((int)[sender tag], rightClick ? lime::TRAY_ICON_RIGHT_CLICK : lime::TRAY_ICON_CLICK, 0); + +} + +- (void)selectApplicationMenuItem:(NSMenuItem*)sender { + + // Called while SDL pumps Cocoa events, so queue the selection for SDLApplication + lime::SDLMenu::PushSelection (0, (int)[sender tag]); + +} + +- (void)selectDockMenuItem:(NSMenuItem*)sender { + + lime::SDLMenu::PushDockMenuSelection ((int)[sender tag]); + +} + +- (void)selectPopupMenuItem:(NSMenuItem*)sender { + + popupSelection = [sender tag]; + +} + +- (void)selectTrayIconMenuItem:(NSMenuItem*)sender { + + // Tray icon menu items carry the tray icon ID in the high 32 bits of their tag + NSInteger tag = [sender tag]; + lime::SDLMenu::PushTrayIconEvent ((int)(tag >> 32), lime::TRAY_ICON_MENU_SELECT, (int)(tag & 0xFFFFFFFF)); + +} + +- (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item { + + highlightedTag = (item && [item action] == @selector(selectPopupMenuItem:)) ? [item tag] : 0; + +} + +@end + + +namespace lime { + + + // Cocoa has a single menu bar for the application, which SDL fills with a + // default application menu. The first item of a Lime menu becomes the + // application menu, as on other macOS applications. + + static NSMenu* defaultApplicationMenu = nil; + static bool defaultApplicationMenuSaved = false; + static LimeMenuTarget* menuTarget = nil; + + + static LimeMenuTarget* GetMenuTarget () { + + if (!menuTarget) menuTarget = [[LimeMenuTarget alloc] init]; + return menuTarget; + + } + + + static NSWindow* GetNSWindow (SDL_Window* window) { + + if (!window) return nil; + + SDL_SysWMinfo info; + SDL_VERSION (&info.version); + + if (!SDL_GetWindowWMInfo (window, &info) || info.subsystem != SDL_SYSWM_COCOA) return nil; + + return info.info.cocoa.window; + + } + + + static NSString* GetString (const std::string& text) { + + NSString* result = [[NSString alloc] initWithBytes:text.data () length:text.size () encoding:NSUTF8StringEncoding]; + return result ? LIME_AUTORELEASE (result) : @""; + + } + + + static NSString* GetKeyEquivalent (int keyCode) { + + unichar character = 0; + + // Use lowercase characters, since an uppercase key equivalent implies Shift + if (keyCode >= 0x20 && keyCode < 0x7F) { + + character = (unichar)keyCode; + + } else if (keyCode >= SDLK_F1 && keyCode <= SDLK_F12) { + + character = (unichar)(NSF1FunctionKey + (keyCode - SDLK_F1)); + + } else if (keyCode >= SDLK_F13 && keyCode <= SDLK_F24) { + + character = (unichar)(NSF13FunctionKey + (keyCode - SDLK_F13)); + + } else { + + switch (keyCode) { + + case SDLK_BACKSPACE: character = NSBackspaceCharacter; break; + case SDLK_TAB: character = NSTabCharacter; break; + case SDLK_RETURN: character = NSCarriageReturnCharacter; break; + case SDLK_ESCAPE: character = 0x1B; break; + case SDLK_DELETE: character = NSDeleteFunctionKey; break; + case SDLK_HOME: character = NSHomeFunctionKey; break; + case SDLK_END: character = NSEndFunctionKey; break; + case SDLK_PAGEUP: character = NSPageUpFunctionKey; break; + case SDLK_PAGEDOWN: character = NSPageDownFunctionKey; break; + case SDLK_INSERT: character = NSInsertFunctionKey; break; + case SDLK_LEFT: character = NSLeftArrowFunctionKey; break; + case SDLK_RIGHT: character = NSRightArrowFunctionKey; break; + case SDLK_UP: character = NSUpArrowFunctionKey; break; + case SDLK_DOWN: character = NSDownArrowFunctionKey; break; + default: break; + + } + + } + + return character ? [NSString stringWithCharacters:&character length:1] : @""; + + } + + + static NSUInteger GetModifierMask (int keyModifiers) { + + NSUInteger mask = 0; + + if (keyModifiers & KMOD_GUI) mask |= NSEventModifierFlagCommand; + if (keyModifiers & KMOD_SHIFT) mask |= NSEventModifierFlagShift; + if (keyModifiers & KMOD_ALT) mask |= NSEventModifierFlagOption; + if (keyModifiers & KMOD_CTRL) mask |= NSEventModifierFlagControl; + + return mask; + + } + + + static NSImage* CreateImage (ImageBuffer* imageBuffer) { + + if (!imageBuffer || !imageBuffer->data || !imageBuffer->data->buffer || imageBuffer->width <= 0 || imageBuffer->height <= 0 || imageBuffer->bitsPerPixel != 32) { + + return nil; + + } + + int width = imageBuffer->width; + int height = imageBuffer->height; + int stride = imageBuffer->Stride (); + + if (imageBuffer->data->buffer->length < stride * height) return nil; + + NSBitmapImageRep* bitmap = LIME_AUTORELEASE ([[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:width pixelsHigh:height bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace bitmapFormat:NSBitmapFormatAlphaNonpremultiplied bytesPerRow:width * 4 bitsPerPixel:32]); + + if (!bitmap) return nil; + + const unsigned char* source = imageBuffer->data->buffer->b; + unsigned char* dest = [bitmap bitmapData]; + + for (int y = 0; y < height; y++) { + + memcpy (dest + y * width * 4, source + y * stride, width * 4); + + } + + NSImage* image = LIME_AUTORELEASE ([[NSImage alloc] initWithSize:NSMakeSize (width, height)]); + [image addRepresentation:bitmap]; + return image; + + } + + + static NSMenu* CreateMenu (NSString* title) { + + NSMenu* menu = LIME_AUTORELEASE ([[NSMenu alloc] initWithTitle:title]); + + // Use the enabled state from Lime instead of validating against the responder chain + [menu setAutoenablesItems:NO]; + [menu setDelegate:GetMenuTarget ()]; + + return menu; + + } + + + static void AppendMenuItems (NSMenu* menu, const std::vector& items, SEL action, NSInteger tagBase) { + + for (size_t i = 0; i < items.size (); i++) { + + const SDLMenuItem& item = items[i]; + bool hasSubmenu = (item.flags & MENU_ITEM_SUBMENU) != 0; + + if ((item.flags & MENU_ITEM_SEPARATOR) && !hasSubmenu) { + + [menu addItem:[NSMenuItem separatorItem]]; + continue; + + } + + // macOS menus do not use mnemonics + NSString* title = GetString (SDLMenu::GetLabel (item.label, 0)); + NSString* keyEquivalent = (!hasSubmenu && item.keyCode != 0) ? GetKeyEquivalent (item.keyCode) : @""; + + NSMenuItem* menuItem = LIME_AUTORELEASE ([[NSMenuItem alloc] initWithTitle:title action:(hasSubmenu ? NULL : action) keyEquivalent:keyEquivalent]); + + if ([keyEquivalent length] > 0) { + + [menuItem setKeyEquivalentModifierMask:GetModifierMask (item.keyModifiers)]; + + } + + [menuItem setTag:(tagBase | item.id)]; + [menuItem setTarget:GetMenuTarget ()]; + [menuItem setEnabled:(item.flags & MENU_ITEM_ENABLED) != 0]; + [menuItem setState:(item.flags & MENU_ITEM_CHECKED) ? NSControlStateValueOn : NSControlStateValueOff]; + + if (hasSubmenu) { + + NSMenu* submenu = CreateMenu (title); + AppendMenuItems (submenu, item.submenu, action, tagBase); + [menuItem setSubmenu:submenu]; + + } + + [menu addItem:menuItem]; + + } + + } + + + bool SDLMenu::GetPlatformSelection (SDL_Event* event, Uint32* windowID, int* id) { + + return false; + + } + + + int SDLMenu::GetSupport () { + + return MENU_SUPPORT_POPUP | MENU_SUPPORT_APPLICATION; + + } + + + int SDLMenu::Popup (SDL_Window* window, const unsigned char* data, int length, int x, int y, bool atCursor) { + + @autoreleasepool { + + NSWindow* nsWindow = GetNSWindow (window); + std::vector items; + + if (!nsWindow || !Parse (data, length, &items)) return 0; + + LimeMenuTarget* target = GetMenuTarget (); + NSMenu* menu = CreateMenu (@""); + AppendMenuItems (menu, items, @selector(selectPopupMenuItem:), 0); + + NSView* view = [nsWindow contentView]; + NSPoint location; + + if (atCursor || !view) { + + // Screen coordinates, used when no view is given + location = [NSEvent mouseLocation]; + view = nil; + + } else { + + // SDL window coordinates start at the top-left of the content view + location = NSMakePoint (x, [view isFlipped] ? y : NSHeight ([view bounds]) - y); + + } + + target->popupSelection = 0; + target->highlightedTag = 0; + + BOOL selected = [menu popUpMenuPositioningItem:nil atLocation:location inView:view]; + + // The action is normally sent before tracking ends, the highlighted item is a fallback + int result = (int)target->popupSelection; + if (result == 0 && selected) result = (int)target->highlightedTag; + + return result; + + } + + } + + + void SDLMenu::Remove (SDL_Window* window) {} + + + bool SDLMenu::SetApplicationMenu (const unsigned char* data, int length) { + + @autoreleasepool { + + if (!NSApp) return false; + + if (!defaultApplicationMenuSaved) { + + defaultApplicationMenu = LIME_RETAIN ([NSApp mainMenu]); + defaultApplicationMenuSaved = true; + + } + + if (!data || length <= 0) { + + [NSApp setMainMenu:defaultApplicationMenu]; + return true; + + } + + std::vector items; + + if (!Parse (data, length, &items)) return false; + + NSMenu* menu = CreateMenu (@"MainMenu"); + AppendMenuItems (menu, items, @selector(selectApplicationMenuItem:), 0); + + [NSApp setMainMenu:menu]; + return true; + + } + + } + + + bool SDLMenu::SetWindowMenu (SDL_Window* window, const unsigned char* data, int length) { + + // macOS windows do not have menu bars, use the application menu instead + return false; + + } + + + // Tray icons use NSStatusItem. When an icon has a menu, macOS shows it when the icon is + // clicked, otherwise clicks are reported through the button action. + + static int nextTrayIconID = 0; + + + static NSStatusItem* GetStatusItem (SDLTrayIcon* trayIcon) { + + return trayIcon->platform ? LIME_BRIDGE (NSStatusItem*, trayIcon->platform) : nil; + + } + + + static NSImage* GetTrayImage (NSImage* image) { + + if (!image) return nil; + + CGFloat size = [[NSStatusBar systemStatusBar] thickness] - 4.0; + if (size < 16.0) size = 16.0; + + NSImage* copy = LIME_AUTORELEASE ([image copy]); + NSSize imageSize = [copy size]; + + if (imageSize.width > 0 && imageSize.height > 0) { + + [copy setSize:NSMakeSize (size * imageSize.width / imageSize.height, size)]; + + } + + return copy; + + } + + + bool TrayIcon::IsSupported () { + + return NSApp != nil; + + } + + + SDLTrayIcon::SDLTrayIcon () { + + id = ++nextTrayIconID; + platform = NULL; + + @autoreleasepool { + + NSStatusItem* statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; + + if (!statusItem) return; + + NSStatusBarButton* button = [statusItem button]; + [button setImage:GetTrayImage ([NSApp applicationIconImage])]; + [button setTarget:GetMenuTarget ()]; + [button setAction:@selector(clickTrayIcon:)]; + [button setTag:id]; + [button sendActionOn:(NSEventMaskLeftMouseUp | NSEventMaskRightMouseUp)]; + + // The status bar does not keep the item alive + platform = LIME_BRIDGE_RETAIN (statusItem); + + } + + } + + + SDLTrayIcon::~SDLTrayIcon () { + + Close (); + + } + + + void SDLTrayIcon::Close () { + + if (!platform) return; + + @autoreleasepool { + + [[NSStatusBar systemStatusBar] removeStatusItem:GetStatusItem (this)]; + + } + + LIME_BRIDGE_RELEASE (platform); + platform = NULL; + + } + + + int SDLTrayIcon::PopupMenu (const unsigned char* data, int length) { + + if (!platform) return 0; + + @autoreleasepool { + + std::vector items; + + if (!SDLMenu::Parse (data, length, &items)) return 0; + + LimeMenuTarget* target = GetMenuTarget (); + NSMenu* menu = CreateMenu (@""); + AppendMenuItems (menu, items, @selector(selectPopupMenuItem:), 0); + + NSStatusBarButton* button = [GetStatusItem (this) button]; + NSPoint location = NSMakePoint (0, [button isFlipped] ? NSHeight ([button bounds]) + 4.0 : -4.0); + + target->popupSelection = 0; + target->highlightedTag = 0; + + BOOL selected = [menu popUpMenuPositioningItem:nil atLocation:location inView:button]; + + int result = (int)target->popupSelection; + if (result == 0 && selected) result = (int)target->highlightedTag; + + return result; + + } + + } + + + void SDLTrayIcon::SetIcon (ImageBuffer* imageBuffer) { + + if (!platform) return; + + @autoreleasepool { + + NSImage* image = CreateImage (imageBuffer); + [[GetStatusItem (this) button] setImage:GetTrayImage (image ? image : [NSApp applicationIconImage])]; + + } + + } + + + bool SDLTrayIcon::SetMenu (const unsigned char* data, int length) { + + if (!platform) return false; + + @autoreleasepool { + + NSStatusItem* statusItem = GetStatusItem (this); + std::vector items; + + if (!data || length <= 0 || !SDLMenu::Parse (data, length, &items)) { + + [statusItem setMenu:nil]; + return false; + + } + + NSMenu* menu = CreateMenu (@""); + AppendMenuItems (menu, items, @selector(selectTrayIconMenuItem:), (NSInteger)id << 32); + + [statusItem setMenu:menu]; + return true; + + } + + } + + + void SDLTrayIcon::SetTooltip (const char* tooltip) { + + if (!platform) return; + + @autoreleasepool { + + [[GetStatusItem (this) button] setToolTip:GetString (tooltip ? std::string (tooltip) : std::string ())]; + + } + + } + + + void SDLTrayIcon::Update () {} + + + // AppKit asks the application delegate for the Dock menu. SDL's delegate does not provide + // one, so the method is added to its class when a Dock menu is first set. + + static NSMenu* dockMenu = nil; + static bool dockMenuHandlerInstalled = false; + + + static NSMenu* GetApplicationDockMenu (id self, SEL command, NSApplication* sender) { + + return dockMenu; + + } + + + static bool InstallDockMenuHandler () { + + if (dockMenuHandlerInstalled) return true; + + id delegate = [NSApp delegate]; + SEL selector = @selector(applicationDockMenu:); + + // Another delegate that already provides a Dock menu is left alone + if (!delegate || [delegate respondsToSelector:selector]) return false; + + if (!class_addMethod ([delegate class], selector, (IMP)GetApplicationDockMenu, "@@:@")) return false; + + // NSApplication caches which methods its delegate implements, so set it again + [NSApp setDelegate:nil]; + [NSApp setDelegate:delegate]; + + dockMenuHandlerInstalled = true; + return true; + + } + + + void DockIcon::Bounce (bool critical) { + + if (NSApp) [NSApp requestUserAttention:(critical ? NSCriticalRequest : NSInformationalRequest)]; + + } + + + bool DockIcon::IsSupported () { + + return NSApp != nil; + + } + + + void DockIcon::SetIcon (ImageBuffer* imageBuffer) { + + if (!NSApp) return; + + @autoreleasepool { + + // A nil image restores the icon from the application bundle + [NSApp setApplicationIconImage:CreateImage (imageBuffer)]; + + } + + } + + + bool DockIcon::SetMenu (const unsigned char* data, int length) { + + if (!NSApp) return false; + + @autoreleasepool { + + NSMenu* menu = nil; + + if (data && length > 0) { + + std::vector items; + + if (!SDLMenu::Parse (data, length, &items) || !InstallDockMenuHandler ()) return false; + + menu = CreateMenu (@""); + AppendMenuItems (menu, items, @selector(selectDockMenuItem:), 0); + + } + + NSMenu* previous = dockMenu; + dockMenu = LIME_RETAIN (menu); + LIME_RELEASE (previous); + + return menu != nil; + + } + + } + + +} + + +#endif diff --git a/project/src/backend/sdl/SDLMenuWindows.cpp b/project/src/backend/sdl/SDLMenuWindows.cpp new file mode 100644 index 0000000000..5b5a8f7a3c --- /dev/null +++ b/project/src/backend/sdl/SDLMenuWindows.cpp @@ -0,0 +1,659 @@ +#include "SDLMenu.h" + +#ifdef LIME_SDL_MENU_WIN32 + +// Native menus and tray icons for Windows + +#include "SDLTrayIcon.h" +#include +#include +#include +#include +#include +#include +#include +#undef CreateWindow + +// SDL_config_windows.h includes sdkddkver.h before _WIN32_WINNT is set, which MinGW +// then defaults to Windows Server 2003, hiding the Vista notification icon values +#ifndef NIF_SHOWTIP +#define NIF_SHOWTIP 0x00000080 +#endif +#ifndef NOTIFYICON_VERSION_4 +#define NOTIFYICON_VERSION_4 4 +#endif + + +namespace lime { + + + struct MenuBar { + + HMENU menu; + Uint32 windowID; + + }; + + + // Keep menu commands clear of the standard dialog command IDs (IDOK, IDCANCEL...) + static const int MENU_COMMAND_BASE = 0x1000; + static const int MENU_COMMAND_MAX = 0xFFFF; + + static std::map menuBars; + + + static HWND GetWindowHandle (SDL_Window* window) { + + if (!window) return NULL; + + SDL_SysWMinfo info; + SDL_VERSION (&info.version); + + if (!SDL_GetWindowWMInfo (window, &info) || info.subsystem != SDL_SYSWM_WINDOWS) return NULL; + + return info.info.win.window; + + } + + + static std::wstring ToWide (const std::string& text) { + + if (text.empty ()) return std::wstring (); + + int size = MultiByteToWideChar (CP_UTF8, 0, text.c_str (), (int)text.size (), NULL, 0); + if (size <= 0) return std::wstring (); + + std::wstring result (size, L'\0'); + MultiByteToWideChar (CP_UTF8, 0, text.c_str (), (int)text.size (), &result[0], size); + return result; + + } + + + static std::string GetKeyEquivalentText (int keyCode, int keyModifiers) { + + const char* keyName = SDL_GetKeyName ((SDL_Keycode)keyCode); + + if (!keyName || !keyName[0]) return std::string (); + + std::string text; + + if (keyModifiers & KMOD_CTRL) text += "Ctrl+"; + if (keyModifiers & KMOD_SHIFT) text += "Shift+"; + if (keyModifiers & KMOD_ALT) text += "Alt+"; + if (keyModifiers & KMOD_GUI) text += "Win+"; + + return text + keyName; + + } + + + static bool AppendMenuItems (HMENU menu, const std::vector& items) { + + for (size_t i = 0; i < items.size (); i++) { + + const SDLMenuItem& item = items[i]; + + UINT itemFlags = MF_STRING; + if (!(item.flags & MENU_ITEM_ENABLED)) itemFlags |= MF_GRAYED; + + if (item.flags & MENU_ITEM_SUBMENU) { + + HMENU submenu = CreatePopupMenu (); + + if (!submenu) return false; + + if (!AppendMenuItems (submenu, item.submenu) || !AppendMenuW (menu, itemFlags | MF_POPUP, (UINT_PTR)submenu, ToWide (item.label).c_str ())) { + + DestroyMenu (submenu); + return false; + + } + + } else if (item.flags & MENU_ITEM_SEPARATOR) { + + AppendMenuW (menu, MF_SEPARATOR, 0, NULL); + + } else { + + std::string label = item.label; + + if (item.keyCode != 0) { + + std::string keyText = GetKeyEquivalentText (item.keyCode, item.keyModifiers); + if (!keyText.empty ()) label += "\t" + keyText; + + } + + if (item.flags & MENU_ITEM_CHECKED) itemFlags |= MF_CHECKED; + + // WM_COMMAND only carries the low word of a menu item identifier + int command = MENU_COMMAND_BASE + item.id; + + if (command > MENU_COMMAND_MAX) { + + command = 0; + itemFlags |= MF_GRAYED; + + } + + AppendMenuW (menu, itemFlags, (UINT_PTR)command, ToWide (label).c_str ()); + + } + + } + + return true; + + } + + + static HMENU CreateMenuFromData (const unsigned char* data, int length, bool popup) { + + std::vector items; + + if (!SDLMenu::Parse (data, length, &items)) return NULL; + + HMENU menu = popup ? CreatePopupMenu () : CreateMenu (); + + if (menu && !AppendMenuItems (menu, items)) { + + DestroyMenu (menu); + return NULL; + + } + + return menu; + + } + + + bool SDLMenu::GetPlatformSelection (SDL_Event* event, Uint32* windowID, int* id) { + + if (event->type != SDL_SYSWMEVENT || !event->syswm.msg || event->syswm.msg->subsystem != SDL_SYSWM_WINDOWS) return false; + + const SDL_SysWMmsg* msg = event->syswm.msg; + + // Menu commands have a zero notification code and no control handle (accelerators use 1) + if (msg->msg.win.msg != WM_COMMAND || HIWORD (msg->msg.win.wParam) != 0 || msg->msg.win.lParam != 0) return false; + + int command = LOWORD (msg->msg.win.wParam); + std::map::iterator it = menuBars.find (msg->msg.win.hwnd); + + if (command <= MENU_COMMAND_BASE || it == menuBars.end ()) return false; + + *windowID = it->second.windowID; + *id = command - MENU_COMMAND_BASE; + return true; + + } + + + int SDLMenu::GetSupport () { + + return MENU_SUPPORT_POPUP | MENU_SUPPORT_WINDOW; + + } + + + int SDLMenu::Popup (SDL_Window* window, const unsigned char* data, int length, int x, int y, bool atCursor) { + + HWND hwnd = GetWindowHandle (window); + + if (!hwnd) return 0; + + HMENU menu = CreateMenuFromData (data, length, true); + + if (!menu) return 0; + + POINT point; + + if (atCursor) { + + GetCursorPos (&point); + + } else { + + point.x = x; + point.y = y; + ClientToScreen (hwnd, &point); + + } + + UINT flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTBUTTON; + flags |= GetSystemMetrics (SM_MENUDROPALIGNMENT) ? TPM_RIGHTALIGN : TPM_LEFTALIGN; + + int command = (int)TrackPopupMenuEx (menu, flags, point.x, point.y, hwnd, NULL); + + DestroyMenu (menu); + return command > MENU_COMMAND_BASE ? command - MENU_COMMAND_BASE : 0; + + } + + + void SDLMenu::Remove (SDL_Window* window) { + + // The attached menu is destroyed along with the window + HWND hwnd = GetWindowHandle (window); + if (hwnd) menuBars.erase (hwnd); + + } + + + bool SDLMenu::SetApplicationMenu (const unsigned char* data, int length) { + + return false; + + } + + + bool SDLMenu::SetWindowMenu (SDL_Window* window, const unsigned char* data, int length) { + + HWND hwnd = GetWindowHandle (window); + + if (!hwnd) return false; + + HMENU menu = NULL; + + if (data && length > 0) { + + menu = CreateMenuFromData (data, length, false); + if (!menu) return false; + + } + + std::map::iterator it = menuBars.find (hwnd); + HMENU previous = (it != menuBars.end ()) ? it->second.menu : NULL; + + Uint32 windowFlags = SDL_GetWindowFlags (window); + bool preserveSize = ((previous != NULL) != (menu != NULL)) && !(windowFlags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED)); + + int width = 0; + int height = 0; + SDL_GetWindowSize (window, &width, &height); + + if (!::SetMenu (hwnd, menu)) { + + if (menu) DestroyMenu (menu); + return false; + + } + + if (previous) DestroyMenu (previous); + + if (menu) { + + MenuBar menuBar; + menuBar.menu = menu; + menuBar.windowID = SDL_GetWindowID (window); + menuBars[hwnd] = menuBar; + + // Menu commands arrive as WM_COMMAND, which SDL only forwards as SDL_SYSWMEVENT + SDL_EventState (SDL_SYSWMEVENT, SDL_ENABLE); + + } else { + + menuBars.erase (hwnd); + + } + + DrawMenuBar (hwnd); + + // Adding or removing the menu bar changes the client area, so restore the previous content size + if (preserveSize && width > 0 && height > 0) { + + SDL_SetWindowSize (window, width, height); + + } + + return true; + + } + + + bool TrayIcon::IsSupported () { + + return true; + + } + + + // Tray icons use Shell_NotifyIcon, with a hidden window that receives their notifications + // and owns their context menus. It is a top-level window (not message-only) so that it can + // take the foreground, which a tray context menu needs to close when clicking elsewhere. + + struct Win32TrayIcon { + + HICON icon; + POINT anchor; + bool hasAnchor; + std::wstring tooltip; + + }; + + + static const UINT TRAY_ICON_MESSAGE = WM_APP + 1; + static const wchar_t* TRAY_ICON_WINDOW_CLASS = L"LimeTrayIconWindow"; + + static int nextTrayIconID = 0; + static UINT taskbarCreatedMessage = 0; + static HWND trayIconWindow = NULL; + static std::map trayIcons; + + + static bool UpdateNotifyIcon (SDLTrayIcon* trayIcon, DWORD action); + + + static LRESULT CALLBACK TrayIconWindowProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { + + if (message == TRAY_ICON_MESSAGE) { + + // With NOTIFYICON_VERSION_4, the event is in the low word and the icon ID in the high word + int id = HIWORD (lParam); + std::map::iterator it = trayIcons.find (id); + + if (it != trayIcons.end () && it->second->platform) { + + Win32TrayIcon* data = (Win32TrayIcon*)it->second->platform; + + switch (LOWORD (lParam)) { + + case NIN_SELECT: + case NIN_KEYSELECT: + + SDLMenu::PushTrayIconEvent (id, TRAY_ICON_CLICK, 0); + break; + + case WM_CONTEXTMENU: + + data->anchor.x = GET_X_LPARAM (wParam); + data->anchor.y = GET_Y_LPARAM (wParam); + data->hasAnchor = true; + SDLMenu::PushTrayIconEvent (id, TRAY_ICON_RIGHT_CLICK, 0); + break; + + default: break; + + } + + } + + return 0; + + } + + if (taskbarCreatedMessage != 0 && message == taskbarCreatedMessage) { + + // Explorer restarted, so the icons have to be added again + for (std::map::iterator it = trayIcons.begin (); it != trayIcons.end (); ++it) { + + UpdateNotifyIcon (it->second, NIM_ADD); + + } + + return 0; + + } + + return DefWindowProcW (hwnd, message, wParam, lParam); + + } + + + static HWND GetTrayIconWindow () { + + if (trayIconWindow) return trayIconWindow; + + HINSTANCE instance = NULL; + GetModuleHandleExW (GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCWSTR)&TrayIconWindowProc, &instance); + + WNDCLASSEXW windowClass; + ZeroMemory (&windowClass, sizeof (windowClass)); + windowClass.cbSize = sizeof (windowClass); + windowClass.lpfnWndProc = TrayIconWindowProc; + windowClass.hInstance = instance; + windowClass.lpszClassName = TRAY_ICON_WINDOW_CLASS; + RegisterClassExW (&windowClass); + + trayIconWindow = CreateWindowExW (WS_EX_TOOLWINDOW, TRAY_ICON_WINDOW_CLASS, L"", WS_POPUP, 0, 0, 0, 0, NULL, NULL, instance, NULL); + taskbarCreatedMessage = RegisterWindowMessageW (L"TaskbarCreated"); + + return trayIconWindow; + + } + + + static HICON GetDefaultIcon () { + + int size = GetSystemMetrics (SM_CXSMICON); + + // Lime executables embed the application icon as resource 1 + HICON icon = (HICON)LoadImageW (GetModuleHandleW (NULL), MAKEINTRESOURCEW (1), IMAGE_ICON, size, size, LR_SHARED); + return icon ? icon : LoadIcon (NULL, IDI_APPLICATION); + + } + + + static HICON CreateIconFromImage (ImageBuffer* imageBuffer) { + + if (!imageBuffer || !imageBuffer->data || !imageBuffer->data->buffer || imageBuffer->width <= 0 || imageBuffer->height <= 0 || imageBuffer->bitsPerPixel != 32) { + + return NULL; + + } + + int width = imageBuffer->width; + int height = imageBuffer->height; + + if (imageBuffer->data->buffer->length < imageBuffer->Stride () * height) return NULL; + + BITMAPV5HEADER header; + ZeroMemory (&header, sizeof (header)); + header.bV5Size = sizeof (header); + header.bV5Width = width; + header.bV5Height = -height; + header.bV5Planes = 1; + header.bV5BitCount = 32; + header.bV5Compression = BI_BITFIELDS; + header.bV5RedMask = 0x00FF0000; + header.bV5GreenMask = 0x0000FF00; + header.bV5BlueMask = 0x000000FF; + header.bV5AlphaMask = 0xFF000000; + + void* bits = NULL; + HDC dc = GetDC (NULL); + HBITMAP color = CreateDIBSection (dc, (BITMAPINFO*)&header, DIB_RGB_COLORS, &bits, NULL, 0); + ReleaseDC (NULL, dc); + + if (!color) return NULL; + + // Lime images are RGBA, Windows bitmaps are BGRA + const unsigned char* source = imageBuffer->data->buffer->b; + unsigned char* dest = (unsigned char*)bits; + int stride = imageBuffer->Stride (); + + for (int y = 0; y < height; y++) { + + const unsigned char* sourceRow = source + y * stride; + unsigned char* destRow = dest + y * width * 4; + + for (int x = 0; x < width; x++) { + + destRow[x * 4 + 0] = sourceRow[x * 4 + 2]; + destRow[x * 4 + 1] = sourceRow[x * 4 + 1]; + destRow[x * 4 + 2] = sourceRow[x * 4 + 0]; + destRow[x * 4 + 3] = sourceRow[x * 4 + 3]; + + } + + } + + // The alpha channel is used for transparency, so the mask only needs to exist + std::vector maskBits (((width + 15) / 16) * 2 * height, 0); + HBITMAP mask = CreateBitmap (width, height, 1, 1, &maskBits[0]); + + ICONINFO info; + info.fIcon = TRUE; + info.xHotspot = 0; + info.yHotspot = 0; + info.hbmMask = mask; + info.hbmColor = color; + + HICON icon = CreateIconIndirect (&info); + + DeleteObject (mask); + DeleteObject (color); + return icon; + + } + + + static bool UpdateNotifyIcon (SDLTrayIcon* trayIcon, DWORD action) { + + Win32TrayIcon* data = (Win32TrayIcon*)trayIcon->platform; + + if (!data) return false; + + NOTIFYICONDATAW notifyIcon; + ZeroMemory (¬ifyIcon, sizeof (notifyIcon)); + notifyIcon.cbSize = sizeof (notifyIcon); + notifyIcon.hWnd = GetTrayIconWindow (); + notifyIcon.uID = trayIcon->id; + notifyIcon.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_SHOWTIP; + notifyIcon.uCallbackMessage = TRAY_ICON_MESSAGE; + notifyIcon.hIcon = data->icon ? data->icon : GetDefaultIcon (); + wcsncpy_s (notifyIcon.szTip, data->tooltip.c_str (), _TRUNCATE); + + if (!Shell_NotifyIconW (action, ¬ifyIcon)) return false; + + if (action == NIM_ADD) { + + notifyIcon.uVersion = NOTIFYICON_VERSION_4; + Shell_NotifyIconW (NIM_SETVERSION, ¬ifyIcon); + + } + + return true; + + } + + + SDLTrayIcon::SDLTrayIcon () { + + id = ++nextTrayIconID; + + Win32TrayIcon* data = new Win32TrayIcon (); + data->icon = NULL; + data->hasAnchor = false; + platform = data; + + trayIcons[id] = this; + UpdateNotifyIcon (this, NIM_ADD); + + } + + + SDLTrayIcon::~SDLTrayIcon () { + + Close (); + + } + + + void SDLTrayIcon::Close () { + + Win32TrayIcon* data = (Win32TrayIcon*)platform; + + if (!data) return; + + NOTIFYICONDATAW notifyIcon; + ZeroMemory (¬ifyIcon, sizeof (notifyIcon)); + notifyIcon.cbSize = sizeof (notifyIcon); + notifyIcon.hWnd = GetTrayIconWindow (); + notifyIcon.uID = id; + Shell_NotifyIconW (NIM_DELETE, ¬ifyIcon); + + if (data->icon) DestroyIcon (data->icon); + + delete data; + platform = NULL; + trayIcons.erase (id); + + } + + + int SDLTrayIcon::PopupMenu (const unsigned char* data, int length) { + + Win32TrayIcon* trayData = (Win32TrayIcon*)platform; + + if (!trayData) return 0; + + HMENU menu = CreateMenuFromData (data, length, true); + + if (!menu) return 0; + + HWND hwnd = GetTrayIconWindow (); + POINT point = trayData->anchor; + + if (!trayData->hasAnchor) GetCursorPos (&point); + trayData->hasAnchor = false; + + // Without the foreground, the menu would stay open when clicking elsewhere + SetForegroundWindow (hwnd); + + UINT flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_BOTTOMALIGN; + flags |= GetSystemMetrics (SM_MENUDROPALIGNMENT) ? TPM_RIGHTALIGN : TPM_LEFTALIGN; + + int command = (int)TrackPopupMenuEx (menu, flags, point.x, point.y, hwnd, NULL); + + // Lets the menu close properly after it is dismissed (see TrackPopupMenu) + PostMessageW (hwnd, WM_NULL, 0, 0); + + DestroyMenu (menu); + return command > MENU_COMMAND_BASE ? command - MENU_COMMAND_BASE : 0; + + } + + + void SDLTrayIcon::SetIcon (ImageBuffer* imageBuffer) { + + Win32TrayIcon* data = (Win32TrayIcon*)platform; + + if (!data) return; + + HICON previous = data->icon; + data->icon = CreateIconFromImage (imageBuffer); + UpdateNotifyIcon (this, NIM_MODIFY); + + if (previous) DestroyIcon (previous); + + } + + + bool SDLTrayIcon::SetMenu (const unsigned char* data, int length) { + + // The menu is shown by PopupMenu when the icon is right-clicked + return false; + + } + + + void SDLTrayIcon::SetTooltip (const char* tooltip) { + + Win32TrayIcon* data = (Win32TrayIcon*)platform; + + if (!data) return; + + data->tooltip = ToWide (tooltip ? std::string (tooltip) : std::string ()); + UpdateNotifyIcon (this, NIM_MODIFY); + + } + + + void SDLTrayIcon::Update () {} + + +} + + +#endif diff --git a/project/src/backend/sdl/SDLSystem.cpp b/project/src/backend/sdl/SDLSystem.cpp index 876734ab42..3947b5cea6 100644 --- a/project/src/backend/sdl/SDLSystem.cpp +++ b/project/src/backend/sdl/SDLSystem.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #ifdef HX_MACOS @@ -94,6 +95,37 @@ namespace lime { } + std::string* Locale::GetPreferredLocales () { + + // The languages the user prefers, most preferred first, as "en-US,fr,ja" + SDL_Locale* locales = SDL_GetPreferredLocales (); + std::string* result = new std::string (); + + if (locales) { + + for (SDL_Locale* locale = locales; locale->language; locale++) { + + if (!result->empty ()) result->append (","); + result->append (locale->language); + + if (locale->country) { + + result->append ("-"); + result->append (locale->country); + + } + + } + + SDL_free (locales); + + } + + return result; + + } + + bool System::GetAllowScreenTimeout () { return SDL_IsScreenSaverEnabled (); @@ -574,7 +606,12 @@ namespace lime { double System::GetTimer () { + // SDL_GetTicks is 32-bit and wraps after 49.7 days + #if SDL_VERSION_ATLEAST(2, 0, 18) + return SDL_GetTicks64 (); + #else return SDL_GetTicks (); + #endif } @@ -618,8 +655,10 @@ namespace lime { off_t outStart; off_t outLength; fd = AAsset_openFileDescriptor ((AAsset*)(((SDL_RWops*)handle)->hidden.androidio.asset), &outStart, &outLength); - FILE* file = ::fdopen (fd, "rb"); - ::fseek (file, outStart, 0); + + // Compressed assets have no file descriptor + FILE* file = fd >= 0 ? ::fdopen (fd, "rb") : NULL; + if (file) ::fseek (file, outStart, 0); System::GCExitBlocking (); return file; #endif diff --git a/project/src/backend/sdl/SDLTrayIcon.h b/project/src/backend/sdl/SDLTrayIcon.h new file mode 100644 index 0000000000..0f39932566 --- /dev/null +++ b/project/src/backend/sdl/SDLTrayIcon.h @@ -0,0 +1,37 @@ +#ifndef LIME_SDL_TRAY_ICON_H +#define LIME_SDL_TRAY_ICON_H + + +#include +#include + + +namespace lime { + + + class SDLTrayIcon : public TrayIcon { + + public: + + SDLTrayIcon (); + ~SDLTrayIcon (); + + virtual void Close (); + virtual int GetID (); + virtual int PopupMenu (const unsigned char* data, int length); + virtual void SetIcon (ImageBuffer* imageBuffer); + virtual bool SetMenu (const unsigned char* data, int length); + virtual void SetTooltip (const char* tooltip); + + static void Update (); + + int id; + void* platform; + + }; + + +} + + +#endif diff --git a/project/src/backend/sdl/SDLWindow.cpp b/project/src/backend/sdl/SDLWindow.cpp index 07cd39693b..8a13e2b0b7 100644 --- a/project/src/backend/sdl/SDLWindow.cpp +++ b/project/src/backend/sdl/SDLWindow.cpp @@ -1,15 +1,30 @@ #include "SDLWindow.h" #include "SDLCursor.h" #include "SDLApplication.h" +#include "SDLMenu.h" #include "../../graphics/opengl/OpenGL.h" #include "../../graphics/opengl/OpenGLBindings.h" #ifdef HX_WINDOWS #include #include +#ifndef HX_WINRT +#include +#endif #undef CreateWindow #endif +#if defined (HX_LINUX) && !defined (RASPBERRYPI) +// As in SDLMenuLinux.cpp, for SDL_SysWMinfo's X11 members +#ifndef SDL_VIDEO_DRIVER_X11 +#define SDL_VIDEO_DRIVER_X11 1 +#endif +#include +#include +#include +#include +#endif + namespace lime { @@ -30,8 +45,151 @@ namespace lime { static bool displayModeSet = false; +#if defined (HX_LINUX) && !defined (RASPBERRYPI) + // Asks SDL for a 32-bit visual, which a compositor blends by its alpha, for + // the next window. Returns whether it set the hint, which is then reset, so + // that later windows are not made transparent too. + static bool SetTransparentX11VisualHint (int flags) { + + bool set = false; + + #ifdef SDL_HINT_VIDEO_X11_WINDOW_VISUALID + const char* existingVisualID = SDL_GetHint (SDL_HINT_VIDEO_X11_WINDOW_VISUALID); + if (existingVisualID && existingVisualID[0] != '\0') return false; + + const char* videoDriver = SDL_GetHint (SDL_HINT_VIDEODRIVER); + if (videoDriver && videoDriver[0] != '\0' && SDL_strcmp (videoDriver, "x11") != 0) return false; + + void* x11 = SDL_LoadObject ("libX11.so.6"); + if (!x11) return false; + + void* gl = SDL_LoadObject ("libGL.so.1"); + if (!gl) { + + SDL_UnloadObject (x11); + return false; + + } + + typedef Display* (*XOpenDisplayFunction) (const char*); + typedef int (*XCloseDisplayFunction) (Display*); + typedef int (*XDefaultScreenFunction) (Display*); + typedef int (*XFreeFunction) (void*); + typedef GLXFBConfig* (*glXChooseFBConfigFunction) (Display*, int, const int*, int*); + typedef XVisualInfo* (*glXGetVisualFromFBConfigFunction) (Display*, GLXFBConfig); + + XOpenDisplayFunction limeXOpenDisplay = (XOpenDisplayFunction)SDL_LoadFunction (x11, "XOpenDisplay"); + XCloseDisplayFunction limeXCloseDisplay = (XCloseDisplayFunction)SDL_LoadFunction (x11, "XCloseDisplay"); + XDefaultScreenFunction limeXDefaultScreen = (XDefaultScreenFunction)SDL_LoadFunction (x11, "XDefaultScreen"); + XFreeFunction limeXFree = (XFreeFunction)SDL_LoadFunction (x11, "XFree"); + glXChooseFBConfigFunction limeGlXChooseFBConfig = (glXChooseFBConfigFunction)SDL_LoadFunction (gl, "glXChooseFBConfig"); + glXGetVisualFromFBConfigFunction limeGlXGetVisualFromFBConfig = (glXGetVisualFromFBConfigFunction)SDL_LoadFunction (gl, "glXGetVisualFromFBConfig"); + + if (limeXOpenDisplay && limeXCloseDisplay && limeXDefaultScreen && limeXFree && limeGlXChooseFBConfig && limeGlXGetVisualFromFBConfig) { + + Display* display = limeXOpenDisplay (NULL); + if (display) { + + int attribs[64]; + int i = 0; + attribs[i++] = GLX_X_RENDERABLE; + attribs[i++] = True; + attribs[i++] = GLX_DRAWABLE_TYPE; + attribs[i++] = GLX_WINDOW_BIT; + attribs[i++] = GLX_RENDER_TYPE; + attribs[i++] = GLX_RGBA_BIT; + attribs[i++] = GLX_X_VISUAL_TYPE; + attribs[i++] = GLX_TRUE_COLOR; + attribs[i++] = GLX_RED_SIZE; + attribs[i++] = 8; + attribs[i++] = GLX_GREEN_SIZE; + attribs[i++] = 8; + attribs[i++] = GLX_BLUE_SIZE; + attribs[i++] = 8; + attribs[i++] = GLX_ALPHA_SIZE; + attribs[i++] = 8; + + if (flags & WINDOW_FLAG_DEPTH_BUFFER) { + + attribs[i++] = GLX_DEPTH_SIZE; + attribs[i++] = 24; + + } + + if (flags & WINDOW_FLAG_STENCIL_BUFFER) { + + attribs[i++] = GLX_STENCIL_SIZE; + attribs[i++] = 8; + + } + + if (flags & WINDOW_FLAG_HW_AA_HIRES) { + + attribs[i++] = GLX_SAMPLE_BUFFERS; + attribs[i++] = 1; + attribs[i++] = GLX_SAMPLES; + attribs[i++] = 4; + + } else if (flags & WINDOW_FLAG_HW_AA) { + + attribs[i++] = GLX_SAMPLE_BUFFERS; + attribs[i++] = 1; + attribs[i++] = GLX_SAMPLES; + attribs[i++] = 2; + + } + + attribs[i++] = None; + + int fbcount = 0; + GLXFBConfig* configs = limeGlXChooseFBConfig (display, limeXDefaultScreen (display), attribs, &fbcount); + if (configs) { + + for (int config = 0; config < fbcount; config++) { + + XVisualInfo* visualInfo = limeGlXGetVisualFromFBConfig (display, configs[config]); + if (visualInfo) { + + if (visualInfo->depth == 32) { + + char visualID[32]; + SDL_snprintf (visualID, sizeof (visualID), "0x%lx", (unsigned long)visualInfo->visualid); + set = SDL_SetHint (SDL_HINT_VIDEO_X11_WINDOW_VISUALID, visualID) == SDL_TRUE; + limeXFree (visualInfo); + break; + + } + + limeXFree (visualInfo); + + } + + } + + limeXFree (configs); + + } + + limeXCloseDisplay (display); + + } + + } + + SDL_UnloadObject (gl); + SDL_UnloadObject (x11); + #endif + + return set; + + } +#endif + #if defined (HX_WINDOWS) && !defined (HX_WINRT) - static const wchar_t* LIME_SDL_OLD_RESIZE_WNDPROC_PROP = L"LimeSDL.OldResizeWndProc"; + static const UINT_PTR LIME_SDL_RESIZE_SUBCLASS_ID = 1; + // Outside the documented WM_MOUSEMOVE key/button bits. Never reaches SDL. + static const WPARAM LIME_SDL_CAPTION_WAKE_TAG = (WPARAM)0x4C490000; + static const wchar_t* LIME_SDL_IN_SIZE_MOVE_PROP = L"LimeSDL.InSizeMove"; static const wchar_t* LIME_SDL_WINDOW_ID_PROP = L"LimeSDL.WindowID"; static const wchar_t* LIME_SDL_LAST_RESIZE_WIDTH_PROP = L"LimeSDL.LastResizeWidth"; static const wchar_t* LIME_SDL_LAST_RESIZE_HEIGHT_PROP = L"LimeSDL.LastResizeHeight"; @@ -107,15 +265,39 @@ namespace lime { } - static LRESULT CALLBACK LimeResizeWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { + static void ClearResizeEventProperties (HWND hwnd) { + + if (RemovePropW (hwnd, LIME_SDL_IN_SIZE_MOVE_PROP)) SDLApplication::ExitNativeModalLoop (); + RemovePropW (hwnd, LIME_SDL_WINDOW_ID_PROP); + RemovePropW (hwnd, LIME_SDL_LAST_RESIZE_WIDTH_PROP); + RemovePropW (hwnd, LIME_SDL_LAST_RESIZE_HEIGHT_PROP); + RemovePropW (hwnd, LIME_SDL_LAST_RESIZE_TICK_PROP); + } + + static LRESULT CALLBACK LimeResizeWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR subclassID, DWORD_PTR referenceData) { + + if (message == WM_MOUSEMOVE && wParam == LIME_SDL_CAPTION_WAKE_TAG && lParam == 0) { + + // This message only wakes Windows' pre-drag mouse wait. Do not turn + // it into game input or move the application's remembered pointer. + return 0; - if (message == WM_ENTERSIZEMOVE) { + } else if (message == WM_NCLBUTTONDOWN && wParam == HTCAPTION) { - SDLApplication::EnterNativeModalLoop (); + // DefWindowProc otherwise waits about 500 ms for caption drag intent + // before WM_ENTERSIZEMOVE, where SDL's modal timer finally starts. + // A posted zero-position move wakes that wait. Tag our message so + // the existing window chain never interprets it as physical input. + PostMessageW (hwnd, WM_MOUSEMOVE, LIME_SDL_CAPTION_WAKE_TAG, 0); + + } else if (message == WM_ENTERSIZEMOVE) { + + if (!GetPropW (hwnd, LIME_SDL_IN_SIZE_MOVE_PROP) && SetPropW (hwnd, LIME_SDL_IN_SIZE_MOVE_PROP, (HANDLE)1)) + SDLApplication::EnterNativeModalLoop (); } else if (message == WM_EXITSIZEMOVE) { - SDLApplication::ExitNativeModalLoop (); + if (RemovePropW (hwnd, LIME_SDL_IN_SIZE_MOVE_PROP)) SDLApplication::ExitNativeModalLoop (); } else if (message == WM_SIZING) { @@ -132,14 +314,12 @@ namespace lime { } - WNDPROC oldWndProc = (WNDPROC)GetPropW (hwnd, LIME_SDL_OLD_RESIZE_WNDPROC_PROP); - if (oldWndProc) { - - return CallWindowProc (oldWndProc, hwnd, message, wParam, lParam); - + if (message == WM_NCDESTROY) { + RemoveWindowSubclass (hwnd, LimeResizeWndProc, subclassID); + ClearResizeEventProperties (hwnd); } - return DefWindowProc (hwnd, message, wParam, lParam); + return DefSubclassProc (hwnd, message, wParam, lParam); } @@ -153,14 +333,11 @@ namespace lime { HWND hwnd = wminfo.info.win.window; if (!hwnd) return; - if (GetPropW (hwnd, LIME_SDL_OLD_RESIZE_WNDPROC_PROP)) return; - - SetLastError (0); - LONG_PTR previous = SetWindowLongPtr (hwnd, GWLP_WNDPROC, (LONG_PTR)LimeResizeWndProc); - if (previous == 0 && GetLastError () != 0) return; - - SetPropW (hwnd, LIME_SDL_OLD_RESIZE_WNDPROC_PROP, (HANDLE)previous); - SetPropW (hwnd, LIME_SDL_WINDOW_ID_PROP, (HANDLE)(UINT_PTR)SDL_GetWindowID (sdlWindow)); + DWORD_PTR data; + if (GetWindowSubclass (hwnd, LimeResizeWndProc, LIME_SDL_RESIZE_SUBCLASS_ID, &data)) return; + if (!SetPropW (hwnd, LIME_SDL_WINDOW_ID_PROP, (HANDLE)(UINT_PTR)SDL_GetWindowID (sdlWindow))) return; + if (!SetWindowSubclass (hwnd, LimeResizeWndProc, LIME_SDL_RESIZE_SUBCLASS_ID, 0)) + ClearResizeEventProperties (hwnd); } @@ -175,22 +352,91 @@ namespace lime { HWND hwnd = wminfo.info.win.window; if (!hwnd) return; - WNDPROC oldWndProc = (WNDPROC)GetPropW (hwnd, LIME_SDL_OLD_RESIZE_WNDPROC_PROP); - if (oldWndProc) { + if (RemoveWindowSubclass (hwnd, LimeResizeWndProc, LIME_SDL_RESIZE_SUBCLASS_ID)) + ClearResizeEventProperties (hwnd); + + } + + static bool EnableTransparentWindow (SDL_Window* sdlWindow) { + + if (!sdlWindow) return false; + + SDL_SysWMinfo wminfo; + SDL_VERSION (&wminfo.version); + if (SDL_GetWindowWMInfo (sdlWindow, &wminfo) != 1) return false; + + HWND hwnd = wminfo.info.win.window; + if (!hwnd) return false; + + typedef struct { + int leftWidth; + int rightWidth; + int topHeight; + int bottomHeight; + } DwmMargins; + typedef HRESULT (WINAPI *DwmExtendFrameIntoClientAreaFunc) (HWND hwnd, const DwmMargins* margins); - SetWindowLongPtr (hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc); + HMODULE dwmapi = LoadLibraryW (L"dwmapi.dll"); + if (!dwmapi) return false; + + DwmExtendFrameIntoClientAreaFunc extendFrame = (DwmExtendFrameIntoClientAreaFunc)GetProcAddress (dwmapi, "DwmExtendFrameIntoClientArea"); + bool enabled = false; + + if (extendFrame) { + + DwmMargins margins = { -1, -1, -1, -1 }; + enabled = SUCCEEDED (extendFrame (hwnd, &margins)); } - RemovePropW (hwnd, LIME_SDL_WINDOW_ID_PROP); - RemovePropW (hwnd, LIME_SDL_OLD_RESIZE_WNDPROC_PROP); - RemovePropW (hwnd, LIME_SDL_LAST_RESIZE_WIDTH_PROP); - RemovePropW (hwnd, LIME_SDL_LAST_RESIZE_HEIGHT_PROP); - RemovePropW (hwnd, LIME_SDL_LAST_RESIZE_TICK_PROP); + FreeLibrary (dwmapi); + return enabled; } #endif +#ifdef HX_MACOS + bool EnableTransparentWindow (SDL_Window* sdlWindow); + void EnableTransparentOpenGLSurface (); +#else + static void EnableTransparentOpenGLSurface () {} +#endif + +#if defined (HX_LINUX) && !defined (RASPBERRYPI) + // Whether the window has a 32-bit visual, which a compositor blends by its + // alpha. Its visual is chosen when it is created, see SetTransparentX11VisualHint. + static bool HasTransparentX11Visual (SDL_Window* sdlWindow) { + + if (!sdlWindow) return false; + + SDL_SysWMinfo wminfo; + SDL_VERSION (&wminfo.version); + if (SDL_GetWindowWMInfo (sdlWindow, &wminfo) != SDL_TRUE || wminfo.subsystem != SDL_SYSWM_X11) return false; + + void* x11 = SDL_LoadObject ("libX11.so.6"); + if (!x11) return false; + + typedef Status (*XGetWindowAttributesFunction) (Display*, ::Window, XWindowAttributes*); + XGetWindowAttributesFunction limeXGetWindowAttributes = (XGetWindowAttributesFunction)SDL_LoadFunction (x11, "XGetWindowAttributes"); + + XWindowAttributes attributes; + bool transparent = limeXGetWindowAttributes && limeXGetWindowAttributes (wminfo.info.x11.display, wminfo.info.x11.window, &attributes) && attributes.depth == 32; + + SDL_UnloadObject (x11); + return transparent; + + } + + static bool EnableTransparentWindow (SDL_Window* sdlWindow) { + + return HasTransparentX11Visual (sdlWindow); + + } +#elif !defined (HX_WINDOWS) || defined (HX_WINRT) + #ifndef HX_MACOS + static bool EnableTransparentWindow (SDL_Window* sdlWindow) { return false; } + #endif +#endif SDLWindow::SDLWindow (Application* application, int width, int height, int flags, const char* title) { @@ -199,6 +445,8 @@ namespace lime { sdlTexture = 0; sdlRenderer = 0; context = 0; + transparent = false; + useVulkan = (flags & WINDOW_FLAG_VULKAN) != 0; contextWidth = 0; contextHeight = 0; @@ -206,6 +454,14 @@ namespace lime { currentApplication = application; this->flags = flags; + // The first window is titled for the application, which Linux names it by + // as it keeps the screen awake, unless SDL_APP_NAME says otherwise + if (title && title[0] != '\0' && !SDL_GetHint (SDL_HINT_APP_NAME)) { + + SDL_SetHint (SDL_HINT_APP_NAME, title); + + } + int sdlWindowFlags = 0; if (flags & WINDOW_FLAG_FULLSCREEN) sdlWindowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; @@ -240,9 +496,29 @@ namespace lime { SDL_SetHint (SDL_HINT_TOUCH_MOUSE_EVENTS, "1"); #endif + if (flags & WINDOW_FLAG_TRANSPARENT) { + + #ifdef SDL_HINT_VIDEO_EGL_ALLOW_TRANSPARENCY + SDL_SetHint (SDL_HINT_VIDEO_EGL_ALLOW_TRANSPARENCY, "1"); + #endif + + #ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR + SDL_SetHint (SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); + #endif + + } + if (flags & WINDOW_FLAG_HARDWARE) { - sdlWindowFlags |= SDL_WINDOW_OPENGL; + if (useVulkan) { + + sdlWindowFlags |= SDL_WINDOW_VULKAN; + + } else { + + sdlWindowFlags |= SDL_WINDOW_OPENGL; + + } if (flags & WINDOW_FLAG_ALLOW_HIGHDPI) { @@ -250,70 +526,101 @@ namespace lime { } - #if defined (HX_WINDOWS) && defined (NATIVE_TOOLKIT_SDL_ANGLE) - SDL_GL_SetAttribute (SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute (SDL_GL_CONTEXT_MINOR_VERSION, 0); - SDL_SetHint (SDL_HINT_VIDEO_WIN_D3DCOMPILER, "d3dcompiler_47.dll"); - #endif + if (!useVulkan) { - #if defined (RASPBERRYPI) - SDL_GL_SetAttribute (SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute (SDL_GL_CONTEXT_MINOR_VERSION, 0); - SDL_SetHint (SDL_HINT_RENDER_DRIVER, "opengles2"); - #endif + #if defined (HX_WINDOWS) && defined (NATIVE_TOOLKIT_SDL_ANGLE) + SDL_GL_SetAttribute (SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute (SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_SetHint (SDL_HINT_VIDEO_WIN_D3DCOMPILER, "d3dcompiler_47.dll"); + #endif - #if defined (IPHONE) || defined (APPLETV) - SDL_GL_SetAttribute (SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 3); - #endif + #if defined (RASPBERRYPI) + SDL_GL_SetAttribute (SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute (SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_SetHint (SDL_HINT_RENDER_DRIVER, "opengles2"); + #endif - if (flags & WINDOW_FLAG_DEPTH_BUFFER) { + #if defined (IPHONE) || defined (APPLETV) + SDL_GL_SetAttribute (SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 3); + #endif - SDL_GL_SetAttribute (SDL_GL_DEPTH_SIZE, 32 - (flags & WINDOW_FLAG_STENCIL_BUFFER) ? 8 : 0); + if (flags & WINDOW_FLAG_DEPTH_BUFFER) { - } + SDL_GL_SetAttribute (SDL_GL_DEPTH_SIZE, 32 - (flags & WINDOW_FLAG_STENCIL_BUFFER) ? 8 : 0); - if (flags & WINDOW_FLAG_STENCIL_BUFFER) { + } - SDL_GL_SetAttribute (SDL_GL_STENCIL_SIZE, 8); + if (flags & WINDOW_FLAG_STENCIL_BUFFER) { - } + SDL_GL_SetAttribute (SDL_GL_STENCIL_SIZE, 8); - if (flags & WINDOW_FLAG_HW_AA_HIRES) { + } - SDL_GL_SetAttribute (SDL_GL_MULTISAMPLEBUFFERS, true); - SDL_GL_SetAttribute (SDL_GL_MULTISAMPLESAMPLES, 4); + if (flags & WINDOW_FLAG_HW_AA_HIRES) { - } else if (flags & WINDOW_FLAG_HW_AA) { + SDL_GL_SetAttribute (SDL_GL_MULTISAMPLEBUFFERS, true); + SDL_GL_SetAttribute (SDL_GL_MULTISAMPLESAMPLES, 4); - SDL_GL_SetAttribute (SDL_GL_MULTISAMPLEBUFFERS, true); - SDL_GL_SetAttribute (SDL_GL_MULTISAMPLESAMPLES, 2); + } else if (flags & WINDOW_FLAG_HW_AA) { - } + SDL_GL_SetAttribute (SDL_GL_MULTISAMPLEBUFFERS, true); + SDL_GL_SetAttribute (SDL_GL_MULTISAMPLESAMPLES, 2); - if (flags & WINDOW_FLAG_COLOR_DEPTH_32_BIT) { + } - SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 8); - SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 8); - SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 8); - SDL_GL_SetAttribute (SDL_GL_ALPHA_SIZE, 8); + if (flags & (WINDOW_FLAG_COLOR_DEPTH_32_BIT | WINDOW_FLAG_TRANSPARENT)) { - } else { + SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_ALPHA_SIZE, 8); + + } else { + + SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 6); + SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 5); - SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 5); - SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 6); - SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 5); + } } } + #if defined (HX_LINUX) && !defined (RASPBERRYPI) + bool transparentVisual = false; + + if ((flags & WINDOW_FLAG_TRANSPARENT) && (flags & WINDOW_FLAG_HARDWARE) && !useVulkan) { + + transparentVisual = SetTransparentX11VisualHint (flags); + + } + #endif + sdlWindow = SDL_CreateWindow (title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, sdlWindowFlags); + // The window has its visual now, and later windows choose their own + #if defined (HX_LINUX) && !defined (RASPBERRYPI) + if (transparentVisual) { + + SDL_ResetHint (SDL_HINT_VIDEO_X11_WINDOW_VISUALID); + + } + #endif + + #ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR + if (flags & WINDOW_FLAG_TRANSPARENT) { + + SDL_ResetHint (SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR); + + } + #endif + #if defined (IPHONE) || defined (APPLETV) - if (sdlWindow && !SDL_GL_CreateContext (sdlWindow)) { + if (!useVulkan && sdlWindow && !SDL_GL_CreateContext (sdlWindow)) { SDL_DestroyWindow (sdlWindow); SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 2); @@ -334,6 +641,12 @@ namespace lime { InstallResizeEventHook (sdlWindow); #endif + if ((flags & WINDOW_FLAG_TRANSPARENT) && !useVulkan) { + + transparent = EnableTransparentWindow (sdlWindow); + + } + #if defined (HX_WINDOWS) && !defined (HX_WINRT) HINSTANCE handle = ::GetModuleHandle (nullptr); @@ -362,7 +675,7 @@ namespace lime { int sdlRendererFlags = 0; - if (flags & WINDOW_FLAG_HARDWARE) { + if ((flags & WINDOW_FLAG_HARDWARE) && !useVulkan) { sdlRendererFlags |= SDL_RENDERER_ACCELERATED; @@ -386,6 +699,12 @@ namespace lime { if (context && SDL_GL_MakeCurrent (sdlWindow, context) == 0) { + if (flags & WINDOW_FLAG_TRANSPARENT) { + + EnableTransparentOpenGLSurface (); + + } + SetVSyncMode (requestedVSyncMode); OpenGLBindings::Init (); @@ -430,7 +749,7 @@ namespace lime { } - if (!context) { + if (!context && !useVulkan) { sdlRendererFlags &= ~SDL_RENDERER_ACCELERATED; sdlRendererFlags &= ~SDL_RENDERER_PRESENTVSYNC; @@ -441,7 +760,7 @@ namespace lime { } - if (context || sdlRenderer) { + if (context || sdlRenderer || useVulkan) { ((SDLApplication*)currentApplication)->RegisterWindow (this); @@ -462,51 +781,43 @@ namespace lime { } - if (sdlWindow) { - - #if defined (HX_WINDOWS) && !defined (HX_WINRT) - RestoreResizeEventHook (sdlWindow); - #endif - - SDL_DestroyWindow (sdlWindow); - sdlWindow = 0; - - } - if (sdlRenderer) { SDL_DestroyRenderer (sdlRenderer); + sdlRenderer = 0; } else if (context) { + if (SDL_GL_GetCurrentContext () == context) { + + SDL_GL_MakeCurrent (sdlWindow, NULL); + + } + SDL_GL_DeleteContext (context); + context = 0; } - } + if (sdlWindow) { + #if defined (HX_WINDOWS) && !defined (HX_WINRT) + RestoreResizeEventHook (sdlWindow); + #endif - void SDLWindow::Alert (const char* message, const char* title) { + SDLMenu::Remove (sdlWindow); + SDL_DestroyWindow (sdlWindow); + sdlWindow = 0; - #if defined (HX_WINDOWS) && !defined (HX_WINRT) + } - int count = 0; - int speed = 0; - bool stopOnForeground = true; + } - SDL_SysWMinfo info; - SDL_VERSION (&info.version); - SDL_GetWindowWMInfo (sdlWindow, &info); - FLASHWINFO fi; - fi.cbSize = sizeof (FLASHWINFO); - fi.hwnd = info.info.win.window; - fi.dwFlags = stopOnForeground ? FLASHW_ALL | FLASHW_TIMERNOFG : FLASHW_ALL | FLASHW_TIMER; - fi.uCount = count; - fi.dwTimeout = speed; - FlashWindowEx (&fi); + void SDLWindow::Alert (const char* message, const char* title) { - #endif + // As Windows always did, until the window comes to the front + Flash (SDL_FLASH_UNTIL_FOCUSED); if (message) { @@ -531,6 +842,25 @@ namespace lime { RestoreResizeEventHook (sdlWindow); #endif + if (sdlRenderer) { + + SDL_DestroyRenderer (sdlRenderer); + sdlRenderer = 0; + + } else if (context) { + + if (SDL_GL_GetCurrentContext () == context) { + + SDL_GL_MakeCurrent (sdlWindow, NULL); + + } + + SDL_GL_DeleteContext (context); + context = 0; + + } + + SDLMenu::Remove (sdlWindow); SDL_DestroyWindow (sdlWindow); sdlWindow = 0; @@ -558,6 +888,12 @@ namespace lime { void SDLWindow::ContextFlip () { + if (useVulkan) { + + return; + + } + if (context && !sdlRenderer) { SDL_GL_SwapWindow (sdlWindow); @@ -578,6 +914,13 @@ namespace lime { } + int SDLWindow::GetRequestedVSyncMode () const { + + return requestedVSyncMode; + + } + + double SDLWindow::GetRefreshRate () const { if (!sdlWindow) { @@ -605,6 +948,72 @@ namespace lime { } + uint64_t SDLWindow::CreateVulkanSurface (uintptr_t instance) { + + if (!useVulkan || !instance) { + + return 0; + + } + + SDL_vulkanSurface surface = 0; + if (SDL_Vulkan_CreateSurface (sdlWindow, (SDL_vulkanInstance)instance, &surface) != SDL_TRUE) { + + return 0; + + } + + #if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) + return (uint64_t)(uintptr_t)surface; + #else + return (uint64_t)surface; + #endif + + } + + + void SDLWindow::GetVulkanDrawableSize (int* width, int* height) { + + if (!useVulkan) { + + if (width) *width = 0; + if (height) *height = 0; + return; + + } + + SDL_Vulkan_GetDrawableSize (sdlWindow, width, height); + + } + + + bool SDLWindow::GetVulkanInstanceExtensions (unsigned int* count, const char** names) { + + if (!useVulkan) { + + if (count) *count = 0; + return false; + + } + + return SDL_Vulkan_GetInstanceExtensions (sdlWindow, count, names) == SDL_TRUE; + + } + + + void* SDLWindow::GetVulkanInstanceProcAddr () { + + if (!useVulkan) { + + return 0; + + } + + return SDL_Vulkan_GetVkGetInstanceProcAddr (); + + } + + void* SDLWindow::ContextLock (bool useCFFIValue) { if (sdlRenderer) { @@ -692,6 +1101,12 @@ namespace lime { void SDLWindow::ContextMakeCurrent () { + if (useVulkan) { + + return; + + } + if (sdlWindow && context) { SDL_GL_MakeCurrent (sdlWindow, context); @@ -723,6 +1138,12 @@ namespace lime { void* SDLWindow::GetContext () { + if (useVulkan) { + + return sdlWindow; + + } + return context; } @@ -730,6 +1151,12 @@ namespace lime { const char* SDLWindow::GetContextType () { + if (useVulkan) { + + return "vulkan"; + + } + if (context) { return "opengl"; @@ -821,6 +1248,18 @@ namespace lime { } + bool SDLWindow::GetTransparent () { + + // Linux checks the window's visual, which a window can have without asking + #if defined (HX_LINUX) && !defined (RASPBERRYPI) + return HasTransparentX11Visual (sdlWindow); + #else + return transparent; + #endif + + } + + float SDLWindow::GetOpacity () { float opacity = 1.0f; @@ -921,6 +1360,13 @@ namespace lime { } + int SDLWindow::PopupMenu (const unsigned char* data, int length, int x, int y, bool atCursor) { + + return SDLMenu::Popup (sdlWindow, data, length, x, y, atCursor); + + } + + void SDLWindow::ReadPixels (ImageBuffer *buffer, Rectangle *rect) { if (sdlRenderer) { @@ -1232,6 +1678,13 @@ namespace lime { } + bool SDLWindow::SetMenu (const unsigned char* data, int length) { + + return SDLMenu::SetWindowMenu (sdlWindow, data, length); + + } + + bool SDLWindow::SetMinimized (bool minimized) { if (minimized) { @@ -1249,6 +1702,65 @@ namespace lime { } + bool SDLWindow::Flash (int mode) { + + return sdlWindow && SDL_FlashWindow (sdlWindow, (SDL_FlashOperation)mode) == 0; + + } + + + bool SDLWindow::GetKeyboardGrab () { + + // Asked for, rather than SDL_GetWindowKeyboardGrab, which is true only + // while the window has the focus and the keys are taken + return sdlWindow && (SDL_GetWindowFlags (sdlWindow) & SDL_WINDOW_KEYBOARD_GRABBED) != 0; + + } + + + bool SDLWindow::GetMouseRect (int* x, int* y, int* width, int* height) { + + const SDL_Rect* rect = sdlWindow ? SDL_GetWindowMouseRect (sdlWindow) : NULL; + if (!rect) return false; + + *x = rect->x; + *y = rect->y; + *width = rect->w; + *height = rect->h; + return true; + + } + + + void SDLWindow::SetKeyboardGrab (bool keyboardGrab) { + + if (sdlWindow) { + + SDL_SetWindowKeyboardGrab (sdlWindow, keyboardGrab ? SDL_TRUE : SDL_FALSE); + + } + + } + + + void SDLWindow::SetMouseRect (int x, int y, int width, int height) { + + if (!sdlWindow) return; + + if (width < 0 || height < 0) { + + SDL_SetWindowMouseRect (sdlWindow, NULL); + + } else { + + SDL_Rect rect = { x, y, width, height }; + SDL_SetWindowMouseRect (sdlWindow, &rect); + + } + + } + + void SDLWindow::SetMouseLock (bool mouseLock) { if (mouseLock) { @@ -1333,6 +1845,35 @@ namespace lime { requestedVSyncMode = vsyncMode; activeSwapInterval = 0; + if (useVulkan) { + + switch (vsyncMode) { + + case 1: + activeSwapInterval = 1; + flags |= WINDOW_FLAG_VSYNC; + break; + + case 2: + activeSwapInterval = -1; + flags |= WINDOW_FLAG_VSYNC; + break; + + case 3: + activeSwapInterval = 1; + flags |= WINDOW_FLAG_VSYNC; + break; + + default: + flags &= ~WINDOW_FLAG_VSYNC; + break; + + } + + return; + + } + if (!sdlWindow || !context || sdlRenderer) { flags &= ~WINDOW_FLAG_VSYNC; @@ -1439,7 +1980,16 @@ namespace lime { Window* CreateWindow (Application* application, int width, int height, int flags, const char* title) { - return new SDLWindow (application, width, height, flags, title); + SDLWindow* window = new SDLWindow (application, width, height, flags, title); + + if (!window->sdlWindow) { + + delete window; + return 0; + + } + + return window; } diff --git a/project/src/backend/sdl/SDLWindow.h b/project/src/backend/sdl/SDLWindow.h index 51cd02403a..4271f5fde7 100644 --- a/project/src/backend/sdl/SDLWindow.h +++ b/project/src/backend/sdl/SDLWindow.h @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -32,14 +33,19 @@ namespace lime { virtual void GetDisplayMode (DisplayMode* displayMode); virtual int GetHeight (); virtual uint32_t GetID (); + virtual bool Flash (int mode); + virtual bool GetKeyboardGrab (); virtual bool GetMouseLock (); + virtual bool GetMouseRect (int* x, int* y, int* width, int* height); virtual float GetOpacity (); virtual double GetScale (); virtual bool GetTextInputEnabled (); + virtual bool GetTransparent (); virtual int GetWidth (); virtual int GetX (); virtual int GetY (); virtual void Move (int x, int y); + virtual int PopupMenu (const unsigned char* data, int length, int x, int y, bool atCursor); virtual void ReadPixels (ImageBuffer *buffer, Rectangle *rect); virtual void Resize (int width, int height); virtual void SetMinimumSize (int width, int height); @@ -50,8 +56,11 @@ namespace lime { virtual bool SetFullscreen (bool fullscreen); virtual void SetIcon (ImageBuffer *imageBuffer); virtual bool SetMaximized (bool maximized); + virtual bool SetMenu (const unsigned char* data, int length); virtual bool SetMinimized (bool minimized); + virtual void SetKeyboardGrab (bool keyboardGrab); virtual void SetMouseLock (bool mouseLock); + virtual void SetMouseRect (int x, int y, int width, int height); virtual void SetOpacity (float opacity); virtual bool SetResizable (bool resizable); virtual void SetTextInputEnabled (bool enabled); @@ -62,7 +71,12 @@ namespace lime { virtual bool SetVisible (bool visible); virtual void WarpMouse (int x, int y); virtual int GetVSyncInterval () const; + virtual int GetRequestedVSyncMode () const; virtual double GetRefreshRate () const; + virtual uint64_t CreateVulkanSurface (uintptr_t instance); + virtual void GetVulkanDrawableSize (int* width, int* height); + virtual bool GetVulkanInstanceExtensions (unsigned int* count, const char** names); + virtual void* GetVulkanInstanceProcAddr (); SDL_Renderer* sdlRenderer; SDL_Texture* sdlTexture; SDL_Window* sdlWindow; @@ -74,6 +88,8 @@ namespace lime { int contextHeight; int contextWidth; int requestedVSyncMode; + bool transparent; + bool useVulkan; }; diff --git a/project/src/backend/sdl/SDLWindowMac.mm b/project/src/backend/sdl/SDLWindowMac.mm new file mode 100644 index 0000000000..9cdc72b58d --- /dev/null +++ b/project/src/backend/sdl/SDLWindowMac.mm @@ -0,0 +1,60 @@ +#ifdef HX_MACOS + +#import +#import +#include +#include + +namespace lime { + + + bool EnableTransparentWindow (SDL_Window* sdlWindow) { + + if (!sdlWindow) return false; + + @autoreleasepool { + + SDL_SysWMinfo wminfo; + SDL_VERSION (&wminfo.version); + if (SDL_GetWindowWMInfo (sdlWindow, &wminfo) != SDL_TRUE) return false; + if (wminfo.subsystem != SDL_SYSWM_COCOA) return false; + + NSWindow* window = wminfo.info.cocoa.window; + if (!window) return false; + + [window setOpaque:NO]; + [window setBackgroundColor:[NSColor clearColor]]; + + NSView* contentView = [window contentView]; + if (contentView && [contentView layer]) { + + [[contentView layer] setOpaque:NO]; + [[contentView layer] setBackgroundColor:[[NSColor clearColor] CGColor]]; + + } + + return true; + + } + + } + + + void EnableTransparentOpenGLSurface () { + + @autoreleasepool { + + NSOpenGLContext* context = [NSOpenGLContext currentContext]; + if (!context) return; + + GLint opaque = 0; + [context setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity]; + + } + + } + + +} + +#endif diff --git a/project/src/graphics/format/PNG.cpp b/project/src/graphics/format/PNG.cpp index c532bced6f..fa0ef8683d 100644 --- a/project/src/graphics/format/PNG.cpp +++ b/project/src/graphics/format/PNG.cpp @@ -21,21 +21,13 @@ namespace lime { ReadBuffer (const unsigned char* data, int length) : data (data), length (length), position (0) {} - bool Read (unsigned char* out, int count) { + bool Read (unsigned char* out, png_size_t count) { - if (position >= length) return false; + if (position > length || count > (png_size_t)(length - position)) return false; + if (count == 0) return true; - if (count > length - position) { - - memcpy (out, data + position, length - position); - position = length; - - } else { - - memcpy (out, data + position, count); - position += count; - - } + memcpy (out, data + position, count); + position += (int)count; return true; @@ -88,6 +80,7 @@ namespace lime { FILE_HANDLE* file = NULL; Bytes* data = NULL; + ReadBuffer buffer (NULL, 0); if (resource->path) { @@ -148,14 +141,14 @@ namespace lime { data = new Bytes (); data->ReadFile (resource->path); - ReadBuffer buffer (data->b, data->length); + buffer = ReadBuffer (data->b, data->length); png_set_read_fn (png_ptr, &buffer, user_read_data_fn); } } else { - ReadBuffer buffer (resource->data->b, resource->data->length); + buffer = ReadBuffer (resource->data->b, resource->data->length); png_set_read_fn (png_ptr, &buffer, user_read_data_fn); } diff --git a/project/src/graphics/opengl/OpenGLBindings.cpp b/project/src/graphics/opengl/OpenGLBindings.cpp index dbcc4f1aec..324e9d79b0 100644 --- a/project/src/graphics/opengl/OpenGLBindings.cpp +++ b/project/src/graphics/opengl/OpenGLBindings.cpp @@ -228,10 +228,13 @@ namespace lime { if (glIsRenderbuffer (id)) glDeleteRenderbuffers (1, &id); break; - #ifdef LIME_GLES3_API + #if defined(LIME_GLES3_API) || !defined(LIME_GLES) case TYPE_SAMPLER: - if (glIsSampler (id)) glDeleteSamplers (1, &id); + if (glIsSampler && glIsSampler (id)) { + if (glDeleteSamplers) + glDeleteSamplers (1, &id); + } break; #endif @@ -245,10 +248,20 @@ namespace lime { if (glIsTexture (id)) glDeleteTextures (1, &id); break; - #ifdef LIME_GLES3_API + #if defined(LIME_GLES3_API) || !defined(LIME_GLES) case TYPE_VERTEX_ARRAY_OBJECT: - - if (glIsVertexArray (id)) glDeleteVertexArrays (1, &id); + + #if defined(HX_MACOS) + if (glIsVertexArrayAPPLE && glIsVertexArrayAPPLE (id)) { + if (glDeleteVertexArraysAPPLE) + glDeleteVertexArraysAPPLE (1, &id); + } + else + #endif + if (glIsVertexArray && glIsVertexArray (id)) { + if (glDeleteVertexArrays) + glDeleteVertexArrays (1, &id); + } break; #endif @@ -531,6 +544,12 @@ namespace lime { void lime_gl_bind_vertex_array (int vertexArray) { #if defined (LIME_GLES3_API) || !defined (LIME_GLES) + #if defined (HX_MACOS) + if (glBindVertexArrayAPPLE) { + glBindVertexArrayAPPLE (vertexArray); + } + else + #endif if (glBindVertexArray) { glBindVertexArray (vertexArray); } @@ -542,6 +561,12 @@ namespace lime { HL_PRIM void HL_NAME(hl_gl_bind_vertex_array) (int vertexArray) { #if defined (LIME_GLES3_API) || !defined (LIME_GLES) + #if defined (HX_MACOS) + if (glBindVertexArrayAPPLE) { + glBindVertexArrayAPPLE (vertexArray); + } + else + #endif if (glBindVertexArray) { glBindVertexArray (vertexArray); } @@ -1200,6 +1225,12 @@ namespace lime { GLuint id = 0; #if defined (LIME_GLES3_API) || !defined (LIME_GLES) + #if defined (HX_MACOS) + if (glGenVertexArraysAPPLE) { + glGenVertexArraysAPPLE (1, &id); + } + else + #endif if (glGenVertexArrays) { glGenVertexArrays (1, &id); } @@ -1213,6 +1244,12 @@ namespace lime { GLuint id = 0; #if defined (LIME_GLES3_API) || !defined (LIME_GLES) + #if defined (HX_MACOS) + if (glGenVertexArraysAPPLE) { + glGenVertexArraysAPPLE (1, &id); + } + else + #endif if (glGenVertexArrays) { glGenVertexArrays (1, &id); } @@ -1401,6 +1438,12 @@ namespace lime { void lime_gl_delete_vertex_array (int vertexArray) { #if defined (LIME_GLES3_API) || !defined (LIME_GLES) + #if defined (HX_MACOS) + if (glDeleteVertexArraysAPPLE) { + glDeleteVertexArraysAPPLE (1, (GLuint*)&vertexArray); + } + else + #endif if (glDeleteVertexArrays) { glDeleteVertexArrays (1, (GLuint*)&vertexArray); } @@ -1412,6 +1455,12 @@ namespace lime { HL_PRIM void HL_NAME(hl_gl_delete_vertex_array) (int vertexArray) { #if defined (LIME_GLES3_API) || !defined (LIME_GLES) + #if defined (HX_MACOS) + if (glDeleteVertexArraysAPPLE) { + glDeleteVertexArraysAPPLE (1, (GLuint*)&vertexArray); + } + else + #endif if (glDeleteVertexArrays) { glDeleteVertexArrays (1, (GLuint*)&vertexArray); } @@ -2189,7 +2238,7 @@ namespace lime { uintptr_t result = 0; #ifdef LIME_GLES3_API - glGetBufferPointerv (target, pname, (void**)result); + glGetBufferPointerv (target, pname, (void**)&result); #endif return (double)result; @@ -2200,7 +2249,7 @@ namespace lime { uintptr_t result = 0; #ifdef LIME_GLES3_API - glGetBufferPointerv (target, pname, (void**)result); + glGetBufferPointerv (target, pname, (void**)&result); #endif return (double)result; @@ -3596,7 +3645,7 @@ namespace lime { double lime_gl_get_vertex_attrib_pointerv (int index, int pname) { uintptr_t result = 0; - glGetVertexAttribPointerv (index, pname, (void**)result); + glGetVertexAttribPointerv (index, pname, (void**)&result); return (double)result; } @@ -3605,7 +3654,7 @@ namespace lime { HL_PRIM double HL_NAME(hl_gl_get_vertex_attrib_pointerv) (int index, int pname) { uintptr_t result = 0; - glGetVertexAttribPointerv (index, pname, (void**)result); + glGetVertexAttribPointerv (index, pname, (void**)&result); return (double)result; } @@ -3880,7 +3929,17 @@ namespace lime { bool lime_gl_is_vertex_array (int handle) { #if defined (LIME_GLES3_API) || !defined (LIME_GLES) - return glIsVertexArray ? glIsVertexArray (handle) : false; + #if defined (HX_MACOS) + if (glIsVertexArrayAPPLE) { + return glIsVertexArrayAPPLE (handle); + } + else + #endif + if (glIsVertexArray) { + return glIsVertexArray (handle); + } + + return false; #else return false; #endif @@ -3891,7 +3950,17 @@ namespace lime { HL_PRIM bool HL_NAME(hl_gl_is_vertex_array) (int handle) { #if defined (LIME_GLES3_API) || !defined (LIME_GLES) - return glIsVertexArray ? glIsVertexArray (handle) : false; + #if defined (HX_MACOS) + if (glIsVertexArrayAPPLE) { + return glIsVertexArrayAPPLE (handle); + } + else + #endif + if (glIsVertexArray) { + return glIsVertexArray (handle); + } + + return false; #else return false; #endif diff --git a/project/src/graphics/vulkan/VKRenderer.cpp b/project/src/graphics/vulkan/VKRenderer.cpp new file mode 100644 index 0000000000..a8b9069a0d --- /dev/null +++ b/project/src/graphics/vulkan/VKRenderer.cpp @@ -0,0 +1,1750 @@ +#include + +#include +#include + +#include +#include +#include +#include + + +namespace lime { + + + static const char* VulkanResultToString (VkResult result) { + + switch (result) { + + case VK_SUCCESS: return "VK_SUCCESS"; + case VK_NOT_READY: return "VK_NOT_READY"; + case VK_TIMEOUT: return "VK_TIMEOUT"; + case VK_EVENT_SET: return "VK_EVENT_SET"; + case VK_EVENT_RESET: return "VK_EVENT_RESET"; + case VK_INCOMPLETE: return "VK_INCOMPLETE"; + case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY"; + case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY"; + case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED"; + case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST"; + case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED"; + case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT"; + case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT"; + case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT"; + case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER"; + case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS"; + case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED"; + case VK_ERROR_SURFACE_LOST_KHR: return "VK_ERROR_SURFACE_LOST_KHR"; + case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"; + case VK_SUBOPTIMAL_KHR: return "VK_SUBOPTIMAL_KHR"; + case VK_ERROR_OUT_OF_DATE_KHR: return "VK_ERROR_OUT_OF_DATE_KHR"; + default: return "VK_RESULT_UNKNOWN"; + + } + + } + + + static std::string FormatVulkanVersion (uint32_t version) { + + std::ostringstream result; + result << VK_API_VERSION_MAJOR (version) << "." << VK_API_VERSION_MINOR (version) << "." << VK_API_VERSION_PATCH (version); + return result.str (); + + } + + + static const char* VulkanPresentModeToString (VkPresentModeKHR mode) { + + switch (mode) { + + case VK_PRESENT_MODE_IMMEDIATE_KHR: return "immediate"; + case VK_PRESENT_MODE_MAILBOX_KHR: return "mailbox"; + case VK_PRESENT_MODE_FIFO_KHR: return "fifo"; + #ifdef VK_PRESENT_MODE_FIFO_RELAXED_KHR + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: return "fifo-relaxed"; + #endif + default: return "unknown"; + + } + + } + + + static VkPresentModeKHR ChoosePresentMode (const std::vector& presentModes, int requestedVSyncMode) { + + bool hasImmediate = false; + bool hasMailbox = false; + bool hasFifo = false; + bool hasFifoRelaxed = false; + + for (size_t i = 0; i < presentModes.size (); ++i) { + + switch (presentModes[i]) { + + case VK_PRESENT_MODE_IMMEDIATE_KHR: + hasImmediate = true; + break; + + case VK_PRESENT_MODE_MAILBOX_KHR: + hasMailbox = true; + break; + + case VK_PRESENT_MODE_FIFO_KHR: + hasFifo = true; + break; + + #ifdef VK_PRESENT_MODE_FIFO_RELAXED_KHR + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: + hasFifoRelaxed = true; + break; + #endif + + default: + break; + + } + + } + + switch (requestedVSyncMode) { + + case 0: + if (hasImmediate) return VK_PRESENT_MODE_IMMEDIATE_KHR; + if (hasMailbox) return VK_PRESENT_MODE_MAILBOX_KHR; + break; + + case 2: + if (hasMailbox) return VK_PRESENT_MODE_MAILBOX_KHR; + #ifdef VK_PRESENT_MODE_FIFO_RELAXED_KHR + if (hasFifoRelaxed) return VK_PRESENT_MODE_FIFO_RELAXED_KHR; + #endif + break; + + case 3: + if (hasMailbox) return VK_PRESENT_MODE_MAILBOX_KHR; + if (hasFifo) return VK_PRESENT_MODE_FIFO_KHR; + if (hasImmediate) return VK_PRESENT_MODE_IMMEDIATE_KHR; + break; + + default: + if (hasFifo) return VK_PRESENT_MODE_FIFO_KHR; + break; + + } + + if (hasFifo) return VK_PRESENT_MODE_FIFO_KHR; + if (hasMailbox) return VK_PRESENT_MODE_MAILBOX_KHR; + if (hasImmediate) return VK_PRESENT_MODE_IMMEDIATE_KHR; + return VK_PRESENT_MODE_FIFO_KHR; + + } + + + VulkanRenderer::VulkanRenderer (Window* window) + : window (window), + vkGetInstanceProcAddr (0), + vkGetDeviceProcAddr (0), + vkAllocateMemory (0), + vkBindBufferMemory (0), + vkCmdCopyBufferToImage (0), + vkCmdPipelineBarrier (0), + vkCreateBuffer (0), + vkCreateDevice (0), + vkCreateFence (0), + vkCreateFramebuffer (0), + vkCreateImageView (0), + vkCreateInstance (0), + vkCreateRenderPass (0), + vkCreateSemaphore (0), + vkCreateSwapchainKHR (0), + vkCreateCommandPool (0), + vkAllocateCommandBuffers (0), + vkAcquireNextImageKHR (0), + vkBeginCommandBuffer (0), + vkCmdBeginRenderPass (0), + vkCmdEndRenderPass (0), + vkDestroyCommandPool (0), + vkDestroyBuffer (0), + vkDestroyDevice (0), + vkDestroyFence (0), + vkDestroyFramebuffer (0), + vkDestroyImageView (0), + vkDestroyInstance (0), + vkDestroyRenderPass (0), + vkDestroySemaphore (0), + vkDestroySurfaceKHR (0), + vkDestroySwapchainKHR (0), + vkDeviceWaitIdle (0), + vkEndCommandBuffer (0), + vkEnumerateDeviceExtensionProperties (0), + vkFreeMemory (0), + vkGetBufferMemoryRequirements (0), + vkEnumerateInstanceExtensionProperties (0), + vkEnumerateInstanceVersion (0), + vkEnumeratePhysicalDevices (0), + vkGetDeviceQueue (0), + vkGetPhysicalDeviceMemoryProperties (0), + vkGetPhysicalDeviceProperties (0), + vkGetPhysicalDeviceQueueFamilyProperties (0), + vkGetPhysicalDeviceSurfaceCapabilitiesKHR (0), + vkGetPhysicalDeviceSurfaceFormatsKHR (0), + vkGetPhysicalDeviceSurfacePresentModesKHR (0), + vkGetPhysicalDeviceSurfaceSupportKHR (0), + vkGetSwapchainImagesKHR (0), + vkMapMemory (0), + vkQueuePresentKHR (0), + vkQueueSubmit (0), + vkResetCommandPool (0), + vkResetFences (0), + vkUnmapMemory (0), + vkWaitForFences (0), + instance (VK_NULL_HANDLE), + surface (VK_NULL_HANDLE), + physicalDevice (VK_NULL_HANDLE), + device (VK_NULL_HANDLE), + queue (VK_NULL_HANDLE), + queueFamilyIndex (0), + swapchain (VK_NULL_HANDLE), + swapchainFormat (VK_FORMAT_UNDEFINED), + swapchainPresentMode (VK_PRESENT_MODE_FIFO_KHR), + requestedVSyncMode (0), + renderPass (VK_NULL_HANDLE), + commandPool (VK_NULL_HANDLE), + imageAvailableSemaphore (VK_NULL_HANDLE), + renderFinishedSemaphore (VK_NULL_HANDLE), + inFlightFence (VK_NULL_HANDLE), + overlayBuffer (VK_NULL_HANDLE), + overlayBufferMemory (VK_NULL_HANDLE), + portabilityEnumerationSupported (false), + portabilitySubsetSupported (false), + swapchainDirty (false), + overlayBufferCapacity (0), + overlayMappedData (0), + overlayWidth (0), + overlayHeight (0), + overlayX (0), + overlayY (0), + overlayEnabled (false) { + + memset (&physicalDeviceProperties, 0, sizeof (physicalDeviceProperties)); + memset (&swapchainExtent, 0, sizeof (swapchainExtent)); + + } + + + VulkanRenderer::~VulkanRenderer () { + + Destroy (); + + } + + + bool VulkanRenderer::Create (const char* applicationName) { + + Destroy (); + lastError.clear (); + info.clear (); + + if (!window) { + + SetError ("Vulkan renderer creation failed: missing window"); + return false; + + } + + if (SDL_Vulkan_LoadLibrary (NULL) != 0) { + + SetError (std::string ("SDL_Vulkan_LoadLibrary failed: ") + SDL_GetError ()); + return false; + + } + + if (!LoadGlobalProcs ()) { + + return false; + + } + + if (!CreateInstance (applicationName)) return false; + if (!LoadInstanceProcs ()) return false; + if (!CreateSurface ()) return false; + if (!PickPhysicalDevice ()) return false; + if (!CreateDevice ()) return false; + if (!LoadDeviceProcs ()) return false; + vkGetDeviceQueue (device, queueFamilyIndex, 0, &queue); + if (!CreateSwapchainResources ()) return false; + if (!CreateSyncObjects ()) return false; + + std::ostringstream result; + result << physicalDeviceProperties.deviceName << " | Vulkan " << FormatVulkanVersion (physicalDeviceProperties.apiVersion) + << " | present " << VulkanPresentModeToString (swapchainPresentMode); + info = result.str (); + return true; + + } + + + bool VulkanRenderer::ClearOverlay () { + + overlayEnabled = false; + overlayWidth = 0; + overlayHeight = 0; + overlayX = 0; + overlayY = 0; + lastError.clear (); + return true; + + } + + + void VulkanRenderer::Destroy () { + + if (device && vkDeviceWaitIdle) { + + vkDeviceWaitIdle (device); + + } + + if (device && vkDestroyFence && inFlightFence) { + + vkDestroyFence (device, inFlightFence, 0); + inFlightFence = VK_NULL_HANDLE; + + } + + if (device && vkDestroySemaphore && renderFinishedSemaphore) { + + vkDestroySemaphore (device, renderFinishedSemaphore, 0); + renderFinishedSemaphore = VK_NULL_HANDLE; + + } + + if (device && vkDestroySemaphore && imageAvailableSemaphore) { + + vkDestroySemaphore (device, imageAvailableSemaphore, 0); + imageAvailableSemaphore = VK_NULL_HANDLE; + + } + + DestroyOverlayResources (); + DestroySwapchainResources (); + + if (device && vkDestroyDevice) { + + vkDestroyDevice (device, 0); + device = VK_NULL_HANDLE; + + } + + queue = VK_NULL_HANDLE; + physicalDevice = VK_NULL_HANDLE; + memset (&physicalDeviceProperties, 0, sizeof (physicalDeviceProperties)); + + if (surface && vkDestroySurfaceKHR && instance) { + + vkDestroySurfaceKHR (instance, surface, 0); + surface = VK_NULL_HANDLE; + + } + + if (instance && vkDestroyInstance) { + + vkDestroyInstance (instance, 0); + instance = VK_NULL_HANDLE; + + } + + instanceExtensions.clear (); + deviceExtensions.clear (); + portabilityEnumerationSupported = false; + portabilitySubsetSupported = false; + swapchainDirty = false; + swapchainFormat = VK_FORMAT_UNDEFINED; + swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR; + requestedVSyncMode = 0; + memset (&swapchainExtent, 0, sizeof (swapchainExtent)); + + } + + + bool VulkanRenderer::Render (float red, float green, float blue, float alpha) { + + if (!device) { + + SetError ("Vulkan renderer is not initialized"); + return false; + + } + + int currentRequestedVSyncMode = window ? window->GetRequestedVSyncMode () : 0; + if (swapchainDirty || !swapchain || swapchainExtent.width == 0 || swapchainExtent.height == 0 || currentRequestedVSyncMode != requestedVSyncMode) { + + if (!RecreateSwapchain ()) { + + return false; + + } + + } + + if (!swapchain || swapchainExtent.width == 0 || swapchainExtent.height == 0) { + + return true; + + } + + VkResult result = vkWaitForFences (device, 1, &inFlightFence, VK_TRUE, UINT64_MAX); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkWaitForFences failed: ") + VulkanResultToString (result)); + return false; + + } + + uint32_t imageIndex = 0; + result = vkAcquireNextImageKHR (device, swapchain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + + return RecreateSwapchain (); + + } + + if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + + SetError (std::string ("vkAcquireNextImageKHR failed: ") + VulkanResultToString (result)); + return false; + + } + + result = vkResetFences (device, 1, &inFlightFence); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkResetFences failed: ") + VulkanResultToString (result)); + return false; + + } + + result = vkResetCommandPool (device, commandPool, 0); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkResetCommandPool failed: ") + VulkanResultToString (result)); + return false; + + } + + if (!RecordCommandBuffer (imageIndex, red, green, blue, alpha)) { + + return false; + + } + + VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + VkSubmitInfo submitInfo; + memset (&submitInfo, 0, sizeof (submitInfo)); + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = &imageAvailableSemaphore; + submitInfo.pWaitDstStageMask = waitStages; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &renderFinishedSemaphore; + + result = vkQueueSubmit (queue, 1, &submitInfo, inFlightFence); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkQueueSubmit failed: ") + VulkanResultToString (result)); + return false; + + } + + VkPresentInfoKHR presentInfo; + memset (&presentInfo, 0, sizeof (presentInfo)); + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphore; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &swapchain; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR (queue, &presentInfo); + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + + return RecreateSwapchain (); + + } + + if (result != VK_SUCCESS) { + + SetError (std::string ("vkQueuePresentKHR failed: ") + VulkanResultToString (result)); + return false; + + } + + return true; + + } + + + bool VulkanRenderer::Resize () { + + return RecreateSwapchain (); + + } + + + bool VulkanRenderer::SetOverlay (const unsigned char* data, int width, int height, int x, int y) { + + if (!data || width <= 0 || height <= 0) { + + return ClearOverlay (); + + } + + size_t requiredSize = (size_t)width * (size_t)height * 4; + if (!CreateOverlayBuffer (requiredSize)) { + + return false; + + } + + memcpy (overlayMappedData, data, requiredSize); + + overlayWidth = (uint32_t)width; + overlayHeight = (uint32_t)height; + overlayX = x; + overlayY = y; + overlayEnabled = true; + lastError.clear (); + return true; + + } + + + bool VulkanRenderer::CreateCommandBuffers () { + + commandBuffers.clear (); + + if (swapchainImages.empty ()) { + + SetError ("Cannot allocate Vulkan command buffers without swapchain images"); + return false; + + } + + commandBuffers.resize (swapchainImages.size (), VK_NULL_HANDLE); + + VkCommandBufferAllocateInfo allocateInfo; + memset (&allocateInfo, 0, sizeof (allocateInfo)); + allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocateInfo.commandPool = commandPool; + allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocateInfo.commandBufferCount = (uint32_t)commandBuffers.size (); + + VkResult result = vkAllocateCommandBuffers (device, &allocateInfo, commandBuffers.data ()); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkAllocateCommandBuffers failed: ") + VulkanResultToString (result)); + return false; + + } + + return true; + + } + + + bool VulkanRenderer::CreateCommandPool () { + + VkCommandPoolCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + createInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + createInfo.queueFamilyIndex = queueFamilyIndex; + + VkResult result = vkCreateCommandPool (device, &createInfo, 0, &commandPool); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkCreateCommandPool failed: ") + VulkanResultToString (result)); + return false; + + } + + return true; + + } + + + bool VulkanRenderer::CreateDevice () { + + const float queuePriority = 1.0f; + + VkDeviceQueueCreateInfo queueCreateInfo; + memset (&queueCreateInfo, 0, sizeof (queueCreateInfo)); + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamilyIndex; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + + deviceExtensions.clear (); + deviceExtensions.push_back (VK_KHR_SWAPCHAIN_EXTENSION_NAME); + if (portabilitySubsetSupported) { + + #ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME + deviceExtensions.push_back (VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); + #endif + + } + + VkDeviceCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = 1; + createInfo.pQueueCreateInfos = &queueCreateInfo; + createInfo.enabledExtensionCount = (uint32_t)deviceExtensions.size (); + createInfo.ppEnabledExtensionNames = deviceExtensions.data (); + + VkResult result = vkCreateDevice (physicalDevice, &createInfo, 0, &device); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkCreateDevice failed: ") + VulkanResultToString (result)); + return false; + + } + + return true; + + } + + + bool VulkanRenderer::CreateFramebuffers () { + + framebuffers.clear (); + framebuffers.resize (swapchainImageViews.size (), VK_NULL_HANDLE); + + for (size_t i = 0; i < swapchainImageViews.size (); ++i) { + + VkImageView attachments[] = { swapchainImageViews[i] }; + VkFramebufferCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + createInfo.renderPass = renderPass; + createInfo.attachmentCount = 1; + createInfo.pAttachments = attachments; + createInfo.width = swapchainExtent.width; + createInfo.height = swapchainExtent.height; + createInfo.layers = 1; + + VkResult result = vkCreateFramebuffer (device, &createInfo, 0, &framebuffers[i]); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkCreateFramebuffer failed: ") + VulkanResultToString (result)); + return false; + + } + + } + + return true; + + } + + + bool VulkanRenderer::CreateImageViews () { + + swapchainImageViews.clear (); + swapchainImageViews.resize (swapchainImages.size (), VK_NULL_HANDLE); + + for (size_t i = 0; i < swapchainImages.size (); ++i) { + + VkImageViewCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapchainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapchainFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + VkResult result = vkCreateImageView (device, &createInfo, 0, &swapchainImageViews[i]); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkCreateImageView failed: ") + VulkanResultToString (result)); + return false; + + } + + } + + return true; + + } + + + bool VulkanRenderer::CreateInstance (const char* applicationName) { + + instanceExtensions.clear (); + + unsigned int requiredExtensionCount = 0; + if (!window->GetVulkanInstanceExtensions (&requiredExtensionCount, 0)) { + + SetError ("Failed to query required Vulkan window extensions"); + return false; + + } + + std::vector requiredExtensions (requiredExtensionCount); + if (requiredExtensionCount > 0 && !window->GetVulkanInstanceExtensions (&requiredExtensionCount, requiredExtensions.data ())) { + + SetError ("Failed to fetch required Vulkan window extensions"); + return false; + + } + + for (size_t i = 0; i < requiredExtensions.size (); ++i) { + + instanceExtensions.push_back (requiredExtensions[i]); + + } + + uint32_t availableCount = 0; + if (vkEnumerateInstanceExtensionProperties && + vkEnumerateInstanceExtensionProperties (0, &availableCount, 0) == VK_SUCCESS && availableCount > 0) { + + std::vector availableExtensions (availableCount); + if (vkEnumerateInstanceExtensionProperties (0, &availableCount, availableExtensions.data ()) == VK_SUCCESS) { + + for (size_t i = 0; i < availableExtensions.size (); ++i) { + + #ifdef VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME + if (strcmp (availableExtensions[i].extensionName, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) == 0) { + + portabilityEnumerationSupported = true; + + } + #endif + + } + + } + + } + + if (portabilityEnumerationSupported) { + + #ifdef VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME + instanceExtensions.push_back (VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + #endif + + } + + uint32_t instanceVersion = VK_API_VERSION_1_0; + if (vkEnumerateInstanceVersion) { + + vkEnumerateInstanceVersion (&instanceVersion); + + } + + VkApplicationInfo applicationInfo; + memset (&applicationInfo, 0, sizeof (applicationInfo)); + applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + applicationInfo.pApplicationName = applicationName ? applicationName : "Lime Vulkan Sample"; + applicationInfo.pEngineName = "Lime"; + applicationInfo.applicationVersion = VK_MAKE_VERSION (1, 0, 0); + applicationInfo.engineVersion = VK_MAKE_VERSION (1, 0, 0); + applicationInfo.apiVersion = instanceVersion >= VK_API_VERSION_1_0 ? VK_API_VERSION_1_0 : instanceVersion; + + VkInstanceCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &applicationInfo; + createInfo.enabledExtensionCount = (uint32_t)instanceExtensions.size (); + createInfo.ppEnabledExtensionNames = instanceExtensions.empty () ? 0 : instanceExtensions.data (); + + #ifdef VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR + if (portabilityEnumerationSupported) { + + createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + + } + #endif + + VkResult result = vkCreateInstance (&createInfo, 0, &instance); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkCreateInstance failed: ") + VulkanResultToString (result)); + return false; + + } + + return true; + + } + + + bool VulkanRenderer::CreateOverlayBuffer (size_t requiredSize) { + + if (!device) { + + SetError ("Cannot create Vulkan overlay buffer before device initialization"); + return false; + + } + + if (requiredSize == 0) { + + DestroyOverlayResources (); + return true; + + } + + if (overlayBuffer != VK_NULL_HANDLE && overlayMappedData && overlayBufferCapacity >= requiredSize) { + + return true; + + } + + DestroyOverlayResources (); + + VkBufferCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + createInfo.size = requiredSize; + createInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VkResult result = vkCreateBuffer (device, &createInfo, 0, &overlayBuffer); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkCreateBuffer failed for overlay upload: ") + VulkanResultToString (result)); + return false; + + } + + VkMemoryRequirements memoryRequirements; + memset (&memoryRequirements, 0, sizeof (memoryRequirements)); + vkGetBufferMemoryRequirements (device, overlayBuffer, &memoryRequirements); + + VkMemoryAllocateInfo allocateInfo; + memset (&allocateInfo, 0, sizeof (allocateInfo)); + allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocateInfo.allocationSize = memoryRequirements.size; + allocateInfo.memoryTypeIndex = FindMemoryType ( + memoryRequirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT + ); + if (allocateInfo.memoryTypeIndex == UINT32_MAX) { + + return false; + + } + + result = vkAllocateMemory (device, &allocateInfo, 0, &overlayBufferMemory); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkAllocateMemory failed for overlay upload: ") + VulkanResultToString (result)); + return false; + + } + + result = vkBindBufferMemory (device, overlayBuffer, overlayBufferMemory, 0); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkBindBufferMemory failed for overlay upload: ") + VulkanResultToString (result)); + return false; + + } + + void* mappedData = 0; + result = vkMapMemory (device, overlayBufferMemory, 0, allocateInfo.allocationSize, 0, &mappedData); + if (result != VK_SUCCESS || !mappedData) { + + SetError (std::string ("vkMapMemory failed for overlay upload: ") + VulkanResultToString (result)); + return false; + + } + + overlayMappedData = (unsigned char*)mappedData; + overlayBufferCapacity = requiredSize; + return true; + + } + + + bool VulkanRenderer::CreateRenderPass () { + + VkAttachmentDescription colorAttachment; + memset (&colorAttachment, 0, sizeof (colorAttachment)); + colorAttachment.format = swapchainFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentRef; + memset (&colorAttachmentRef, 0, sizeof (colorAttachmentRef)); + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass; + memset (&subpass, 0, sizeof (subpass)); + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency; + memset (&dependency, 0, sizeof (dependency)); + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + createInfo.attachmentCount = 1; + createInfo.pAttachments = &colorAttachment; + createInfo.subpassCount = 1; + createInfo.pSubpasses = &subpass; + createInfo.dependencyCount = 1; + createInfo.pDependencies = &dependency; + + VkResult result = vkCreateRenderPass (device, &createInfo, 0, &renderPass); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkCreateRenderPass failed: ") + VulkanResultToString (result)); + return false; + + } + + return true; + + } + + + bool VulkanRenderer::CreateSurface () { + + uint64_t nativeSurface = window->CreateVulkanSurface ((uintptr_t)instance); + if (!nativeSurface) { + + SetError ("Failed to create Vulkan surface from Lime window"); + return false; + + } + + surface = (VkSurfaceKHR)(uintptr_t)nativeSurface; + return true; + + } + + + bool VulkanRenderer::CreateSwapchain () { + + VkSurfaceCapabilitiesKHR capabilities; + memset (&capabilities, 0, sizeof (capabilities)); + VkResult result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR (physicalDevice, surface, &capabilities); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: ") + VulkanResultToString (result)); + return false; + + } + + uint32_t formatCount = 0; + result = vkGetPhysicalDeviceSurfaceFormatsKHR (physicalDevice, surface, &formatCount, 0); + if (result != VK_SUCCESS || formatCount == 0) { + + SetError ("Failed to query Vulkan surface formats"); + return false; + + } + + std::vector formats (formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR (physicalDevice, surface, &formatCount, formats.data ()); + + VkSurfaceFormatKHR surfaceFormat = formats[0]; + for (size_t i = 0; i < formats.size (); ++i) { + + if (formats[i].format == VK_FORMAT_B8G8R8A8_UNORM && formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + + surfaceFormat = formats[i]; + break; + + } + + } + + uint32_t presentModeCount = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR (physicalDevice, surface, &presentModeCount, 0); + std::vector presentModes (presentModeCount > 0 ? presentModeCount : 1, VK_PRESENT_MODE_FIFO_KHR); + if (presentModeCount > 0) { + + vkGetPhysicalDeviceSurfacePresentModesKHR (physicalDevice, surface, &presentModeCount, presentModes.data ()); + + } + + int currentRequestedVSyncMode = window ? window->GetRequestedVSyncMode () : 0; + VkPresentModeKHR presentMode = ChoosePresentMode (presentModes, currentRequestedVSyncMode); + + int drawableWidth = 0; + int drawableHeight = 0; + window->GetVulkanDrawableSize (&drawableWidth, &drawableHeight); + if (drawableWidth <= 0 || drawableHeight <= 0) { + + swapchainExtent.width = 0; + swapchainExtent.height = 0; + swapchainDirty = true; + return true; + + } + + if (capabilities.currentExtent.width != UINT32_MAX) { + + swapchainExtent = capabilities.currentExtent; + + } else { + + swapchainExtent.width = (uint32_t)(std::max) ((int)capabilities.minImageExtent.width, + (std::min) (drawableWidth, (int)capabilities.maxImageExtent.width)); + swapchainExtent.height = (uint32_t)(std::max) ((int)capabilities.minImageExtent.height, + (std::min) (drawableHeight, (int)capabilities.maxImageExtent.height)); + + } + + uint32_t imageCount = capabilities.minImageCount + 1; + if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) { + + imageCount = capabilities.maxImageCount; + + } + + VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + const bool transparentWindow = window && ((window->flags & WINDOW_FLAG_TRANSPARENT) != 0); + const VkCompositeAlphaFlagBitsKHR opaqueCompositeAlphaFlags[] = { + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR + }; + const VkCompositeAlphaFlagBitsKHR transparentCompositeAlphaFlags[] = { + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR + }; + const VkCompositeAlphaFlagBitsKHR* compositeAlphaFlags = transparentWindow ? transparentCompositeAlphaFlags : opaqueCompositeAlphaFlags; + const size_t compositeAlphaFlagCount = transparentWindow + ? sizeof (transparentCompositeAlphaFlags) / sizeof (transparentCompositeAlphaFlags[0]) + : sizeof (opaqueCompositeAlphaFlags) / sizeof (opaqueCompositeAlphaFlags[0]); + + for (size_t i = 0; i < compositeAlphaFlagCount; ++i) { + + if (capabilities.supportedCompositeAlpha & compositeAlphaFlags[i]) { + + compositeAlpha = compositeAlphaFlags[i]; + break; + + } + + } + + VkSwapchainCreateInfoKHR createInfo; + memset (&createInfo, 0, sizeof (createInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = swapchainExtent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.preTransform = capabilities.currentTransform; + createInfo.compositeAlpha = compositeAlpha; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = VK_NULL_HANDLE; + + result = vkCreateSwapchainKHR (device, &createInfo, 0, &swapchain); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkCreateSwapchainKHR failed: ") + VulkanResultToString (result)); + return false; + + } + + swapchainFormat = surfaceFormat.format; + swapchainPresentMode = presentMode; + requestedVSyncMode = currentRequestedVSyncMode; + swapchainDirty = false; + + uint32_t swapchainImageCount = 0; + result = vkGetSwapchainImagesKHR (device, swapchain, &swapchainImageCount, 0); + if (result != VK_SUCCESS || swapchainImageCount == 0) { + + SetError ("Failed to query Vulkan swapchain images"); + return false; + + } + + swapchainImages.resize (swapchainImageCount); + result = vkGetSwapchainImagesKHR (device, swapchain, &swapchainImageCount, swapchainImages.data ()); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkGetSwapchainImagesKHR failed: ") + VulkanResultToString (result)); + return false; + + } + + return true; + + } + + + bool VulkanRenderer::CreateSwapchainResources () { + + if (!CreateSwapchain ()) return false; + if (swapchainExtent.width == 0 || swapchainExtent.height == 0) return true; + if (!CreateImageViews ()) return false; + if (!CreateRenderPass ()) return false; + if (!CreateCommandPool ()) return false; + if (!CreateFramebuffers ()) return false; + if (!CreateCommandBuffers ()) return false; + return true; + + } + + + bool VulkanRenderer::CreateSyncObjects () { + + VkSemaphoreCreateInfo semaphoreInfo; + memset (&semaphoreInfo, 0, sizeof (semaphoreInfo)); + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo; + memset (&fenceInfo, 0, sizeof (fenceInfo)); + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + VkResult result = vkCreateSemaphore (device, &semaphoreInfo, 0, &imageAvailableSemaphore); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkCreateSemaphore failed: ") + VulkanResultToString (result)); + return false; + + } + + result = vkCreateSemaphore (device, &semaphoreInfo, 0, &renderFinishedSemaphore); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkCreateSemaphore failed: ") + VulkanResultToString (result)); + return false; + + } + + result = vkCreateFence (device, &fenceInfo, 0, &inFlightFence); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkCreateFence failed: ") + VulkanResultToString (result)); + return false; + + } + + return true; + + } + + + void VulkanRenderer::DestroyOverlayResources () { + + overlayEnabled = false; + overlayWidth = 0; + overlayHeight = 0; + overlayX = 0; + overlayY = 0; + + if (device && overlayBufferMemory && overlayMappedData && vkUnmapMemory) { + + vkUnmapMemory (device, overlayBufferMemory); + overlayMappedData = 0; + + } + + if (device && vkDestroyBuffer && overlayBuffer) { + + vkDestroyBuffer (device, overlayBuffer, 0); + overlayBuffer = VK_NULL_HANDLE; + + } + + if (device && vkFreeMemory && overlayBufferMemory) { + + vkFreeMemory (device, overlayBufferMemory, 0); + overlayBufferMemory = VK_NULL_HANDLE; + + } + + overlayMappedData = 0; + overlayBufferCapacity = 0; + + } + + + void VulkanRenderer::DestroySwapchainResources () { + + commandBuffers.clear (); + + if (device && vkDestroyCommandPool && commandPool) { + + vkDestroyCommandPool (device, commandPool, 0); + commandPool = VK_NULL_HANDLE; + + } + + for (size_t i = 0; i < framebuffers.size (); ++i) { + + if (framebuffers[i] && device && vkDestroyFramebuffer) { + + vkDestroyFramebuffer (device, framebuffers[i], 0); + + } + + } + + framebuffers.clear (); + + if (device && vkDestroyRenderPass && renderPass) { + + vkDestroyRenderPass (device, renderPass, 0); + renderPass = VK_NULL_HANDLE; + + } + + for (size_t i = 0; i < swapchainImageViews.size (); ++i) { + + if (swapchainImageViews[i] && device && vkDestroyImageView) { + + vkDestroyImageView (device, swapchainImageViews[i], 0); + + } + + } + + swapchainImageViews.clear (); + swapchainImages.clear (); + + if (device && vkDestroySwapchainKHR && swapchain) { + + vkDestroySwapchainKHR (device, swapchain, 0); + swapchain = VK_NULL_HANDLE; + + } + + } + + + uint32_t VulkanRenderer::FindMemoryType (uint32_t typeFilter, VkMemoryPropertyFlags properties) { + + VkPhysicalDeviceMemoryProperties memoryProperties; + memset (&memoryProperties, 0, sizeof (memoryProperties)); + vkGetPhysicalDeviceMemoryProperties (physicalDevice, &memoryProperties); + + for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; ++i) { + + if ((typeFilter & (1u << i)) && + (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { + + return i; + + } + + } + + SetError ("Failed to find a compatible Vulkan memory type"); + return UINT32_MAX; + + } + + + bool VulkanRenderer::LoadDeviceProcs () { + + if (!vkGetDeviceProcAddr) { + + SetError ("Missing vkGetDeviceProcAddr"); + return false; + + } + + #define LIME_LOAD_DEVICE_PROC(name) \ + name = (PFN_##name)vkGetDeviceProcAddr (device, #name); \ + if (!name) { SetError (std::string ("Missing Vulkan device proc: ") + #name); return false; } + + LIME_LOAD_DEVICE_PROC (vkAllocateMemory); + LIME_LOAD_DEVICE_PROC (vkAcquireNextImageKHR); + LIME_LOAD_DEVICE_PROC (vkAllocateCommandBuffers); + LIME_LOAD_DEVICE_PROC (vkBeginCommandBuffer); + LIME_LOAD_DEVICE_PROC (vkBindBufferMemory); + LIME_LOAD_DEVICE_PROC (vkCmdCopyBufferToImage); + LIME_LOAD_DEVICE_PROC (vkCmdBeginRenderPass); + LIME_LOAD_DEVICE_PROC (vkCmdEndRenderPass); + LIME_LOAD_DEVICE_PROC (vkCmdPipelineBarrier); + LIME_LOAD_DEVICE_PROC (vkCreateBuffer); + LIME_LOAD_DEVICE_PROC (vkCreateCommandPool); + LIME_LOAD_DEVICE_PROC (vkCreateFence); + LIME_LOAD_DEVICE_PROC (vkCreateFramebuffer); + LIME_LOAD_DEVICE_PROC (vkCreateImageView); + LIME_LOAD_DEVICE_PROC (vkCreateRenderPass); + LIME_LOAD_DEVICE_PROC (vkCreateSemaphore); + LIME_LOAD_DEVICE_PROC (vkCreateSwapchainKHR); + LIME_LOAD_DEVICE_PROC (vkDestroyCommandPool); + LIME_LOAD_DEVICE_PROC (vkDestroyBuffer); + LIME_LOAD_DEVICE_PROC (vkDestroyDevice); + LIME_LOAD_DEVICE_PROC (vkDestroyFence); + LIME_LOAD_DEVICE_PROC (vkDestroyFramebuffer); + LIME_LOAD_DEVICE_PROC (vkDestroyImageView); + LIME_LOAD_DEVICE_PROC (vkDestroyRenderPass); + LIME_LOAD_DEVICE_PROC (vkDestroySemaphore); + LIME_LOAD_DEVICE_PROC (vkDestroySwapchainKHR); + LIME_LOAD_DEVICE_PROC (vkDeviceWaitIdle); + LIME_LOAD_DEVICE_PROC (vkEndCommandBuffer); + LIME_LOAD_DEVICE_PROC (vkFreeMemory); + LIME_LOAD_DEVICE_PROC (vkGetBufferMemoryRequirements); + LIME_LOAD_DEVICE_PROC (vkGetSwapchainImagesKHR); + LIME_LOAD_DEVICE_PROC (vkMapMemory); + LIME_LOAD_DEVICE_PROC (vkQueuePresentKHR); + LIME_LOAD_DEVICE_PROC (vkQueueSubmit); + LIME_LOAD_DEVICE_PROC (vkResetCommandPool); + LIME_LOAD_DEVICE_PROC (vkResetFences); + LIME_LOAD_DEVICE_PROC (vkUnmapMemory); + LIME_LOAD_DEVICE_PROC (vkWaitForFences); + + #undef LIME_LOAD_DEVICE_PROC + + return true; + + } + + + bool VulkanRenderer::LoadGlobalProcs () { + + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)window->GetVulkanInstanceProcAddr (); + + if (!vkGetInstanceProcAddr) { + + SetError ("SDL did not expose vkGetInstanceProcAddr"); + return false; + + } + + #define LIME_LOAD_GLOBAL_PROC(name) \ + name = (PFN_##name)vkGetInstanceProcAddr (VK_NULL_HANDLE, #name); \ + if (!name) { SetError (std::string ("Missing Vulkan global proc: ") + #name); return false; } + + LIME_LOAD_GLOBAL_PROC (vkCreateInstance); + LIME_LOAD_GLOBAL_PROC (vkEnumerateInstanceExtensionProperties); + vkEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion)vkGetInstanceProcAddr (VK_NULL_HANDLE, "vkEnumerateInstanceVersion"); + + #undef LIME_LOAD_GLOBAL_PROC + + return true; + + } + + + bool VulkanRenderer::LoadInstanceProcs () { + + #define LIME_LOAD_INSTANCE_PROC(name) \ + name = (PFN_##name)vkGetInstanceProcAddr (instance, #name); \ + if (!name) { SetError (std::string ("Missing Vulkan instance proc: ") + #name); return false; } + + LIME_LOAD_INSTANCE_PROC (vkCreateDevice); + LIME_LOAD_INSTANCE_PROC (vkDestroyInstance); + LIME_LOAD_INSTANCE_PROC (vkDestroySurfaceKHR); + LIME_LOAD_INSTANCE_PROC (vkEnumerateDeviceExtensionProperties); + LIME_LOAD_INSTANCE_PROC (vkEnumeratePhysicalDevices); + LIME_LOAD_INSTANCE_PROC (vkGetDeviceProcAddr); + LIME_LOAD_INSTANCE_PROC (vkGetPhysicalDeviceMemoryProperties); + LIME_LOAD_INSTANCE_PROC (vkGetPhysicalDeviceProperties); + LIME_LOAD_INSTANCE_PROC (vkGetPhysicalDeviceQueueFamilyProperties); + LIME_LOAD_INSTANCE_PROC (vkGetPhysicalDeviceSurfaceCapabilitiesKHR); + LIME_LOAD_INSTANCE_PROC (vkGetPhysicalDeviceSurfaceFormatsKHR); + LIME_LOAD_INSTANCE_PROC (vkGetPhysicalDeviceSurfacePresentModesKHR); + LIME_LOAD_INSTANCE_PROC (vkGetPhysicalDeviceSurfaceSupportKHR); + LIME_LOAD_INSTANCE_PROC (vkGetDeviceQueue); + + #undef LIME_LOAD_INSTANCE_PROC + + return true; + + } + + + bool VulkanRenderer::PickPhysicalDevice () { + + uint32_t deviceCount = 0; + VkResult result = vkEnumeratePhysicalDevices (instance, &deviceCount, 0); + if (result != VK_SUCCESS || deviceCount == 0) { + + SetError ("No Vulkan physical devices were found"); + return false; + + } + + std::vector physicalDevices (deviceCount); + vkEnumeratePhysicalDevices (instance, &deviceCount, physicalDevices.data ()); + + for (size_t deviceIndex = 0; deviceIndex < physicalDevices.size (); ++deviceIndex) { + + VkPhysicalDevice candidate = physicalDevices[deviceIndex]; + + uint32_t extensionCount = 0; + vkEnumerateDeviceExtensionProperties (candidate, 0, &extensionCount, 0); + std::vector extensions (extensionCount); + if (extensionCount > 0) { + + vkEnumerateDeviceExtensionProperties (candidate, 0, &extensionCount, extensions.data ()); + + } + + bool hasSwapchain = false; + bool hasPortabilitySubset = false; + + for (size_t i = 0; i < extensions.size (); ++i) { + + if (strcmp (extensions[i].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) { + + hasSwapchain = true; + + } + + #ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME + if (strcmp (extensions[i].extensionName, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) == 0) { + + hasPortabilitySubset = true; + + } + #endif + + } + + if (!hasSwapchain) { + + continue; + + } + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties (candidate, &queueFamilyCount, 0); + if (queueFamilyCount == 0) { + + continue; + + } + + std::vector queueFamilies (queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties (candidate, &queueFamilyCount, queueFamilies.data ()); + + for (uint32_t queueIndex = 0; queueIndex < queueFamilyCount; ++queueIndex) { + + VkBool32 supportsPresent = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR (candidate, queueIndex, surface, &supportsPresent); + + if ((queueFamilies[queueIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) && supportsPresent) { + + physicalDevice = candidate; + queueFamilyIndex = queueIndex; + portabilitySubsetSupported = hasPortabilitySubset; + vkGetPhysicalDeviceProperties (physicalDevice, &physicalDeviceProperties); + return true; + + } + + } + + } + + SetError ("Failed to find a Vulkan physical device with graphics and present support"); + return false; + + } + + + bool VulkanRenderer::RecordCommandBuffer (uint32_t imageIndex, float red, float green, float blue, float alpha) { + + if (imageIndex >= commandBuffers.size () || imageIndex >= framebuffers.size ()) { + + SetError ("Invalid Vulkan swapchain image index"); + return false; + + } + + VkCommandBuffer commandBuffer = commandBuffers[imageIndex]; + + VkCommandBufferBeginInfo beginInfo; + memset (&beginInfo, 0, sizeof (beginInfo)); + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + VkResult result = vkBeginCommandBuffer (commandBuffer, &beginInfo); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkBeginCommandBuffer failed: ") + VulkanResultToString (result)); + return false; + + } + + VkClearValue clearValue; + clearValue.color.float32[0] = red; + clearValue.color.float32[1] = green; + clearValue.color.float32[2] = blue; + clearValue.color.float32[3] = alpha; + + VkRenderPassBeginInfo renderPassInfo; + memset (&renderPassInfo, 0, sizeof (renderPassInfo)); + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = framebuffers[imageIndex]; + renderPassInfo.renderArea.offset.x = 0; + renderPassInfo.renderArea.offset.y = 0; + renderPassInfo.renderArea.extent = swapchainExtent; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearValue; + + vkCmdBeginRenderPass (commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdEndRenderPass (commandBuffer); + + uint32_t copyWidth = overlayWidth; + uint32_t copyHeight = overlayHeight; + int copyX = overlayX; + int copyY = overlayY; + VkDeviceSize bufferOffset = 0; + bool hasOverlayCopy = overlayEnabled && overlayBuffer && overlayMappedData && overlayWidth > 0 && overlayHeight > 0; + + if (hasOverlayCopy) { + + if (copyX < 0) { + + uint32_t clipped = (uint32_t)(-copyX); + if (clipped >= copyWidth) { + + hasOverlayCopy = false; + + } else { + + bufferOffset += (VkDeviceSize)clipped * 4; + copyWidth -= clipped; + copyX = 0; + + } + + } + + if (hasOverlayCopy && copyY < 0) { + + uint32_t clipped = (uint32_t)(-copyY); + if (clipped >= copyHeight) { + + hasOverlayCopy = false; + + } else { + + bufferOffset += (VkDeviceSize)clipped * (VkDeviceSize)overlayWidth * 4; + copyHeight -= clipped; + copyY = 0; + + } + + } + + if (hasOverlayCopy && + (copyX >= (int)swapchainExtent.width || copyY >= (int)swapchainExtent.height)) { + + hasOverlayCopy = false; + + } + + if (hasOverlayCopy && (uint32_t)copyX + copyWidth > swapchainExtent.width) { + + copyWidth = swapchainExtent.width - (uint32_t)copyX; + + } + + if (hasOverlayCopy && (uint32_t)copyY + copyHeight > swapchainExtent.height) { + + copyHeight = swapchainExtent.height - (uint32_t)copyY; + + } + + hasOverlayCopy = hasOverlayCopy && copyWidth > 0 && copyHeight > 0; + + } + + VkImageMemoryBarrier imageBarrier; + memset (&imageBarrier, 0, sizeof (imageBarrier)); + imageBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageBarrier.image = swapchainImages[imageIndex]; + imageBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageBarrier.subresourceRange.baseMipLevel = 0; + imageBarrier.subresourceRange.levelCount = 1; + imageBarrier.subresourceRange.baseArrayLayer = 0; + imageBarrier.subresourceRange.layerCount = 1; + + if (hasOverlayCopy) { + + imageBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + imageBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + imageBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + imageBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + + vkCmdPipelineBarrier ( + commandBuffer, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + 0, 0, + 0, 0, + 1, &imageBarrier + ); + + VkBufferImageCopy copyRegion; + memset (©Region, 0, sizeof (copyRegion)); + copyRegion.bufferOffset = bufferOffset; + copyRegion.bufferRowLength = overlayWidth; + copyRegion.bufferImageHeight = overlayHeight; + copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copyRegion.imageSubresource.mipLevel = 0; + copyRegion.imageSubresource.baseArrayLayer = 0; + copyRegion.imageSubresource.layerCount = 1; + copyRegion.imageOffset.x = copyX; + copyRegion.imageOffset.y = copyY; + copyRegion.imageOffset.z = 0; + copyRegion.imageExtent.width = copyWidth; + copyRegion.imageExtent.height = copyHeight; + copyRegion.imageExtent.depth = 1; + + vkCmdCopyBufferToImage (commandBuffer, overlayBuffer, swapchainImages[imageIndex], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region); + + imageBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + imageBarrier.dstAccessMask = 0; + imageBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + imageBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + vkCmdPipelineBarrier ( + commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, + 0, 0, + 0, 0, + 1, &imageBarrier + ); + + } else { + + imageBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + imageBarrier.dstAccessMask = 0; + imageBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + imageBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + vkCmdPipelineBarrier ( + commandBuffer, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, + 0, 0, + 0, 0, + 1, &imageBarrier + ); + + } + + result = vkEndCommandBuffer (commandBuffer); + if (result != VK_SUCCESS) { + + SetError (std::string ("vkEndCommandBuffer failed: ") + VulkanResultToString (result)); + return false; + + } + + return true; + + } + + + bool VulkanRenderer::RecreateSwapchain () { + + if (!device) { + + return false; + + } + + int drawableWidth = 0; + int drawableHeight = 0; + window->GetVulkanDrawableSize (&drawableWidth, &drawableHeight); + if (drawableWidth <= 0 || drawableHeight <= 0) { + + if (swapchain || !swapchainImages.empty () || !swapchainImageViews.empty () || !framebuffers.empty () || !commandBuffers.empty () || commandPool || renderPass) { + + if (vkDeviceWaitIdle (device) != VK_SUCCESS) { + + SetError ("vkDeviceWaitIdle failed while suspending the swapchain"); + return false; + + } + + DestroySwapchainResources (); + + } + + swapchainExtent.width = 0; + swapchainExtent.height = 0; + swapchainDirty = true; + lastError.clear (); + return true; + + } + + if (vkDeviceWaitIdle (device) != VK_SUCCESS) { + + SetError ("vkDeviceWaitIdle failed during swapchain recreation"); + return false; + + } + + DestroySwapchainResources (); + if (!CreateSwapchainResources ()) { + + swapchainDirty = true; + return false; + + } + + swapchainDirty = (swapchain == VK_NULL_HANDLE || swapchainExtent.width == 0 || swapchainExtent.height == 0); + lastError.clear (); + return true; + + } + + + void VulkanRenderer::SetError (const std::string& value) { + + lastError = value; + + } + + +} diff --git a/project/src/media/openal/OpenALBindings.cpp b/project/src/media/openal/OpenALBindings.cpp index a26c5c679a..27ea4c4e31 100644 --- a/project/src/media/openal/OpenALBindings.cpp +++ b/project/src/media/openal/OpenALBindings.cpp @@ -170,6 +170,7 @@ namespace lime { } */ + void lime_al_auxf (value aux, int param, float value) { #ifdef LIME_OPENALSOFT @@ -235,7 +236,7 @@ namespace lime { #ifdef LIME_OPENALSOFT ALuint id = (ALuint)(uintptr_t)val_data (aux); - ALuint data; + ALuint data = 0; if (param == AL_EFFECTSLOT_EFFECT) { @@ -257,7 +258,7 @@ namespace lime { #ifdef LIME_OPENALSOFT ALuint id = (ALuint)(uintptr_t)aux->ptr; - ALuint data; + ALuint data = 0; if (param == AL_EFFECTSLOT_EFFECT) { @@ -473,7 +474,7 @@ namespace lime { #ifdef LIME_OPENAL_DELETION_DELAY time_t currentTime = time (0); - ALuint deletedData; + ALuint deletedData = 0; time_t deletedTime; std::list::const_iterator itSource = alDeletedSource.begin (); @@ -621,7 +622,7 @@ namespace lime { al_gc_mutex.Lock (); #ifdef LIME_OPENAL_DELETION_DELAY - ALuint data; + ALuint data = 0; for (int i = 0; i < size; ++i) { @@ -669,7 +670,7 @@ namespace lime { al_gc_mutex.Lock (); #ifdef LIME_OPENAL_DELETION_DELAY - ALuint data; + ALuint data = 0; for (int i = 0; i < size; ++i) { @@ -817,7 +818,7 @@ namespace lime { #ifdef LIME_OPENAL_DELETION_DELAY al_gc_mutex.Lock (); - ALuint data; + ALuint data = 0; for (int i = 0; i < size; ++i) { @@ -863,7 +864,7 @@ namespace lime { #ifdef LIME_OPENAL_DELETION_DELAY al_gc_mutex.Lock (); - ALuint data; + ALuint data = 0; for (int i = 0; i < size; ++i) { @@ -1094,7 +1095,7 @@ namespace lime { #ifdef LIME_OPENALSOFT ALuint id = (ALuint)(uintptr_t)val_data (filter); - ALuint data; + ALuint data = 0; data = val_int (val); @@ -1108,7 +1109,7 @@ namespace lime { #ifdef LIME_OPENALSOFT ALuint id = (ALuint)(uintptr_t)filter->ptr; - ALuint data; + ALuint data = 0; data = val; @@ -1141,7 +1142,7 @@ namespace lime { value lime_al_gen_aux () { #ifdef LIME_OPENALSOFT - ALuint aux; + ALuint aux = 0; alGenAuxiliaryEffectSlots ((ALuint)1, &aux); return CFFIPointer ((void*)(uintptr_t)aux, gc_al_auxiliary_effect_slot); #else @@ -1154,7 +1155,7 @@ namespace lime { HL_PRIM HL_CFFIPointer* HL_NAME(hl_al_gen_aux) () { #ifdef LIME_OPENALSOFT - ALuint aux; + ALuint aux = 0; alGenAuxiliaryEffectSlots ((ALuint)1, &aux); return HLCFFIPointer ((void*)(uintptr_t)aux, (hl_finalizer)hl_gc_al_auxiliary_effect_slot); #else @@ -1223,7 +1224,7 @@ namespace lime { value result = alloc_array (n); - ALuint buffer; + ALuint buffer = 0; value ptr; al_gc_mutex.Lock (); @@ -1263,7 +1264,7 @@ namespace lime { varray* result = hl_alloc_array (&hlt_dyn, n); HL_CFFIPointer** resultData = hl_aptr (result, HL_CFFIPointer*); - ALuint buffer; + ALuint buffer = 0; HL_CFFIPointer* ptr; al_gc_mutex.Lock (); @@ -1295,7 +1296,7 @@ namespace lime { alGetError (); #ifdef LIME_OPENALSOFT - ALuint effect; + ALuint effect = 0; alGenEffects ((ALuint)1, &effect); if (alGetError () == AL_NO_ERROR) { @@ -1315,7 +1316,7 @@ namespace lime { alGetError (); #ifdef LIME_OPENALSOFT - ALuint effect; + ALuint effect = 0; alGenEffects ((ALuint)1, &effect); if (alGetError () == AL_NO_ERROR) { @@ -1335,7 +1336,7 @@ namespace lime { alGetError (); #ifdef LIME_OPENALSOFT - ALuint filter; + ALuint filter = 0; alGenFilters ((ALuint)1, &filter); if (alGetError () == AL_NO_ERROR) { @@ -1355,7 +1356,7 @@ namespace lime { alGetError (); #ifdef LIME_OPENALSOFT - ALuint filter; + ALuint filter = 0; alGenFilters ((ALuint)1, &filter); if (alGetError () == AL_NO_ERROR) { @@ -1374,7 +1375,7 @@ namespace lime { alGetError (); - ALuint source; + ALuint source = 0; alGenSources ((ALuint)1, &source); if (alGetError () == AL_NO_ERROR) { @@ -1394,7 +1395,7 @@ namespace lime { alGetError (); - ALuint source; + ALuint source = 0; alGenSources ((ALuint)1, &source); if (alGetError () == AL_NO_ERROR) { @@ -1516,7 +1517,7 @@ namespace lime { value lime_al_get_buffer3f (value buffer, int param) { ALuint id = (ALuint)(uintptr_t)val_data (buffer); - ALfloat val1, val2, val3; + ALfloat val1 = 0, val2 = 0, val3 = 0; alGetBuffer3f (id, param, &val1, &val2, &val3); @@ -1542,7 +1543,7 @@ namespace lime { value lime_al_get_buffer3i (value buffer, int param) { ALuint id = (ALuint)(uintptr_t)val_data (buffer); - ALint val1, val2, val3; + ALint val1 = 0, val2 = 0, val3 = 0; alGetBuffer3i (id, param, &val1, &val2, &val3); @@ -1569,7 +1570,7 @@ namespace lime { float lime_al_get_bufferf (value buffer, int param) { ALuint id = (ALuint)(uintptr_t)val_data (buffer); - ALfloat data; + ALfloat data = 0; alGetBufferf (id, param, &data); return data; @@ -1579,7 +1580,7 @@ namespace lime { HL_PRIM float HL_NAME(hl_al_get_bufferf) (HL_CFFIPointer* buffer, int param) { ALuint id = (ALuint)(uintptr_t)buffer->ptr; - ALfloat data; + ALfloat data = 0; alGetBufferf (id, param, &data); return data; @@ -1619,7 +1620,7 @@ namespace lime { int lime_al_get_bufferi (value buffer, int param) { ALuint id = (ALuint)(uintptr_t)val_data (buffer); - ALint data; + ALint data = 0; alGetBufferi (id, param, &data); return data; @@ -1629,7 +1630,7 @@ namespace lime { HL_PRIM int HL_NAME(hl_al_get_bufferi) (HL_CFFIPointer* buffer, int param) { ALuint id = (ALuint)(uintptr_t)buffer->ptr; - ALint data; + ALint data = 0; alGetBufferi (id, param, &data); return data; @@ -1740,7 +1741,7 @@ namespace lime { #ifdef LIME_OPENALSOFT ALuint id = (ALuint)(uintptr_t)val_data (filter); - ALint data; + ALint data = 0; alGetFilteri (id, param, &data); return data; #else @@ -1754,7 +1755,7 @@ namespace lime { #ifdef LIME_OPENALSOFT ALuint id = (ALuint)(uintptr_t)filter->ptr; - ALint data; + ALint data = 0; alGetFilteri (id, param, &data); return data; #else @@ -1850,7 +1851,7 @@ namespace lime { value lime_al_get_listener3f (int param) { - ALfloat val1, val2, val3; + ALfloat val1 = 0, val2 = 0, val3 = 0; alGetListener3f (param, &val1, &val2, &val3); @@ -1874,7 +1875,7 @@ namespace lime { value lime_al_get_listener3i (int param) { - ALint val1, val2, val3; + ALint val1 = 0, val2 = 0, val3 = 0; alGetListener3i (param, &val1, &val2, &val3); @@ -1898,7 +1899,7 @@ namespace lime { float lime_al_get_listenerf (int param) { - ALfloat data; + ALfloat data = 0; alGetListenerf (param, &data); return data; @@ -1907,7 +1908,7 @@ namespace lime { HL_PRIM float HL_NAME(hl_al_get_listenerf) (int param) { - ALfloat data; + ALfloat data = 0; alGetListenerf (param, &data); return data; @@ -1944,7 +1945,7 @@ namespace lime { int lime_al_get_listeneri (int param) { - ALint data; + ALint data = 0; alGetListeneri (param, &data); return data; @@ -1953,7 +1954,7 @@ namespace lime { HL_PRIM int HL_NAME(hl_al_get_listeneri) (int param) { - ALint data; + ALint data = 0; alGetListeneri (param, &data); return data; @@ -2005,7 +2006,7 @@ namespace lime { value lime_al_get_source3f (value source, int param) { ALuint id = (ALuint)(uintptr_t)val_data (source); - ALfloat val1, val2, val3; + ALfloat val1 = 0, val2 = 0, val3 = 0; alGetSource3f (id, param, &val1, &val2, &val3); @@ -2031,7 +2032,7 @@ namespace lime { value lime_al_get_source3i (value source, int param) { ALuint id = (ALuint)(uintptr_t)val_data (source); - ALint val1, val2, val3; + ALint val1 = 0, val2 = 0, val3 = 0; alGetSource3i (id, param, &val1, &val2, &val3); @@ -2054,10 +2055,50 @@ namespace lime { } + value lime_al_get_sourcedv_soft (value source, int param, int count) { + + ALdouble* values = new ALdouble[count]; + for (int i = 0; i < count; i++) values[i] = 0; + + #ifdef LIME_OPENALSOFT + ALuint id = (ALuint)(uintptr_t)val_data (source); + alGetSourcedvSOFT (id, param, values); + #endif + + value result = alloc_array (count); + + for (int i = 0; i < count; i++) { + + val_array_set_i (result, i, alloc_float (values[i])); + + } + + delete[] values; + return result; + + } + + + HL_PRIM varray* HL_NAME(hl_al_get_sourcedv_soft) (HL_CFFIPointer* source, int param, int count) { + + varray* result = hl_alloc_array (&hlt_f64, count); + double* values = hl_aptr (result, double); + for (int i = 0; i < count; i++) values[i] = 0; + + #ifdef LIME_OPENALSOFT + ALuint id = (ALuint)(uintptr_t)source->ptr; + alGetSourcedvSOFT (id, param, values); + #endif + + return result; + + } + + float lime_al_get_sourcef (value source, int param) { ALuint id = (ALuint)(uintptr_t)val_data (source); - ALfloat data; + ALfloat data = 0; alGetSourcef (id, param, &data); return data; @@ -2067,7 +2108,7 @@ namespace lime { HL_PRIM float HL_NAME(hl_al_get_sourcef) (HL_CFFIPointer* source, int param) { ALuint id = (ALuint)(uintptr_t)source->ptr; - ALfloat data; + ALfloat data = 0; alGetSourcef (id, param, &data); return data; @@ -2107,7 +2148,7 @@ namespace lime { value lime_al_get_sourcei (value source, int param) { ALuint id = (ALuint)(uintptr_t)val_data (source); - ALint data; + ALint data = 0; alGetSourcei (id, param, &data); if (param == AL_BUFFER) { @@ -2138,7 +2179,7 @@ namespace lime { HL_PRIM vdynamic* HL_NAME(hl_al_get_sourcei) (HL_CFFIPointer* source, int param) { ALuint id = (ALuint)(uintptr_t)source->ptr; - ALint data; + ALint data = 0; alGetSourcei (id, param, &data); if (param == AL_BUFFER) { @@ -2317,6 +2358,30 @@ namespace lime { } + bool lime_alc_is_extension_present (value device, HxString extname) { + + #ifdef LIME_OPENALSOFT + ALCdevice* alcDevice = val_is_null (device) ? NULL : (ALCdevice*)val_data (device); + return extname.__s ? alcIsExtensionPresent (alcDevice, extname.__s) : false; + #else + return false; + #endif + + } + + + HL_PRIM bool HL_NAME(hl_alc_is_extension_present) (HL_CFFIPointer* device, hl_vstring* extname) { + + #ifdef LIME_OPENALSOFT + ALCdevice* alcDevice = device ? (ALCdevice*)device->ptr : NULL; + return extname ? alcIsExtensionPresent (alcDevice, hl_to_utf8 (extname->bytes)) : false; + #else + return false; + #endif + + } + + bool lime_al_is_filter (value filter) { #ifdef LIME_OPENALSOFT @@ -2802,17 +2867,35 @@ namespace lime { } + static int unqueue_al_buffers (ALuint source, int nb, ALuint* buffers) { + + // OpenAL unqueues nothing when it refuses, such as for buffers that have + // not played, and leaves the array as it was + ALint queued = 0; + alGetSourcei (source, AL_BUFFERS_QUEUED, &queued); + + alSourceUnqueueBuffers (source, nb, buffers); + + ALint remaining = queued; + alGetSourcei (source, AL_BUFFERS_QUEUED, &remaining); + + int count = queued - remaining; + return count < 0 ? 0 : (count > nb ? nb : count); + + } + + value lime_al_source_unqueue_buffers (value source, int nb) { ALuint id = (ALuint)(uintptr_t)val_data (source); ALuint* buffers = new ALuint[nb]; - alSourceUnqueueBuffers (id, nb, buffers); + int count = unqueue_al_buffers (id, nb, buffers); - value result = alloc_array (nb); - ALuint buffer; + value result = alloc_array (count); + ALuint buffer = 0; value ptr; - for (int i = 0; i < nb; i++) { + for (int i = 0; i < count; i++) { buffer = buffers[i]; @@ -2843,14 +2926,14 @@ namespace lime { ALuint id = (ALuint)(uintptr_t)source->ptr; ALuint* buffers = new ALuint[nb]; - alSourceUnqueueBuffers (id, nb, buffers); + int count = unqueue_al_buffers (id, nb, buffers); - varray* result = hl_alloc_array (&hlt_dyn, nb); + varray* result = hl_alloc_array (&hlt_dyn, count); HL_CFFIPointer** resultData = hl_aptr (result, HL_CFFIPointer*); - ALuint buffer; + ALuint buffer = 0; HL_CFFIPointer* ptr; - for (int i = 0; i < nb; i++) { + for (int i = 0; i < count; i++) { buffer = buffers[i]; @@ -2896,7 +2979,7 @@ namespace lime { void lime_al_source3i (value source, int param, value value1, int value2, int value3) { ALuint id = (ALuint)(uintptr_t)val_data (source); - ALuint data1; + ALuint data1 = 0; #ifdef LIME_OPENALSOFT if (param == AL_AUXILIARY_SEND_FILTER) { @@ -2920,7 +3003,7 @@ namespace lime { HL_PRIM void HL_NAME(hl_al_source3i) (HL_CFFIPointer* source, int param, vdynamic* value1, int value2, int value3) { ALuint id = (ALuint)(uintptr_t)source->ptr; - ALuint data1; + ALuint data1 = 0; #ifdef LIME_OPENALSOFT if (param == AL_AUXILIARY_SEND_FILTER) { @@ -3379,7 +3462,7 @@ namespace lime { value lime_alc_get_string (value device, int param) { - ALCdevice* alcDevice = (ALCdevice*)val_data (device); + ALCdevice* alcDevice = val_is_null (device) ? NULL : (ALCdevice*)val_data (device); const char* result = alcGetString (alcDevice, param); return result ? alloc_string (result) : alloc_null (); @@ -3388,8 +3471,9 @@ namespace lime { HL_PRIM vbyte* HL_NAME(hl_alc_get_string) (HL_CFFIPointer* device, int param) { - ALCdevice* alcDevice = device ? (ALCdevice*)device->ptr : 0; + ALCdevice* alcDevice = device ? (ALCdevice*)device->ptr : NULL; const char* result = alcGetString (alcDevice, param); + if (!result) return 0; int length = strlen (result); char* _result = (char*)malloc (length + 1); strcpy (_result, result); @@ -3512,6 +3596,101 @@ namespace lime { } + value lime_alc_capture_open_device (HxString devicename, int frequency, int format, int buffersize) { + + ALCdevice* alcDevice = alcCaptureOpenDevice (devicename.__s, frequency, format, buffersize); + + value ptr = CFFIPointer (alcDevice, gc_alc_object); + alcObjects[alcDevice] = ptr; + return ptr; + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_alc_capture_open_device) (hl_vstring* devicename, int frequency, int format, int buffersize) { + + ALCdevice* alcDevice = alcCaptureOpenDevice (devicename ? (char*)hl_to_utf8 ((const uchar*)devicename->bytes) : 0, frequency, format, buffersize); + + HL_CFFIPointer* ptr = HLCFFIPointer (alcDevice, (hl_finalizer)hl_gc_alc_object); + alcObjects[alcDevice] = ptr; + return ptr; + + } + + + bool lime_alc_capture_close_device (value device) { + + al_gc_mutex.Lock (); + ALCdevice* alcDevice = (ALCdevice*)val_data (device); + alcObjects.erase (alcDevice); + al_gc_mutex.Unlock (); + + return alcCaptureCloseDevice (alcDevice); + + } + + + HL_PRIM bool HL_NAME(hl_alc_capture_close_device) (HL_CFFIPointer* device) { + + al_gc_mutex.Lock (); + ALCdevice* alcDevice = (ALCdevice*)device->ptr; + alcObjects.erase (alcDevice); + al_gc_mutex.Unlock (); + + return alcCaptureCloseDevice (alcDevice); + + } + + + void lime_alc_capture_start (value device) { + + ALCdevice* alcDevice = (ALCdevice*)val_data (device); + alcCaptureStart (alcDevice); + + } + + + HL_PRIM void HL_NAME(hl_alc_capture_start) (HL_CFFIPointer* device) { + + ALCdevice* alcDevice = (ALCdevice*)device->ptr; + alcCaptureStart (alcDevice); + + } + + + void lime_alc_capture_stop (value device) { + + ALCdevice* alcDevice = (ALCdevice*)val_data (device); + alcCaptureStop (alcDevice); + + } + + + HL_PRIM void HL_NAME(hl_alc_capture_stop) (HL_CFFIPointer* device) { + + ALCdevice* alcDevice = (ALCdevice*)device->ptr; + alcCaptureStop (alcDevice); + + } + + + void lime_alc_capture_samples (value device, value buffer, int samples) { + + ALCdevice* alcDevice = (ALCdevice*)val_data (device); + Bytes bytes (buffer); + alcCaptureSamples (alcDevice, bytes.b, samples); + + } + + + HL_PRIM void HL_NAME(hl_alc_capture_samples) (HL_CFFIPointer* device, Bytes* buffer, int samples) { + + ALCdevice* alcDevice = (ALCdevice*)device->ptr; + alcCaptureSamples (alcDevice, buffer->b, samples); + + } + + DEFINE_PRIME3v (lime_al_auxf); @@ -3577,6 +3756,7 @@ namespace lime { DEFINE_PRIME1 (lime_al_get_proc_address); DEFINE_PRIME2 (lime_al_get_source3f); DEFINE_PRIME2 (lime_al_get_source3i); + DEFINE_PRIME3 (lime_al_get_sourcedv_soft); DEFINE_PRIME2 (lime_al_get_sourcef); DEFINE_PRIME3 (lime_al_get_sourcefv); DEFINE_PRIME2 (lime_al_get_sourcei); @@ -3587,6 +3767,7 @@ namespace lime { DEFINE_PRIME1 (lime_al_is_effect); DEFINE_PRIME1 (lime_al_is_enabled); DEFINE_PRIME1 (lime_al_is_extension_present); + DEFINE_PRIME2 (lime_alc_is_extension_present); DEFINE_PRIME1 (lime_al_is_filter); DEFINE_PRIME1 (lime_al_is_source); DEFINE_PRIME4v (lime_al_listener3f); @@ -3628,6 +3809,11 @@ namespace lime { DEFINE_PRIME1v (lime_alc_process_context); DEFINE_PRIME1v (lime_alc_resume_device); DEFINE_PRIME1v (lime_alc_suspend_context); + DEFINE_PRIME4 (lime_alc_capture_open_device); + DEFINE_PRIME1 (lime_alc_capture_close_device); + DEFINE_PRIME1v (lime_alc_capture_start); + DEFINE_PRIME1v (lime_alc_capture_stop); + DEFINE_PRIME3v (lime_alc_capture_samples); #define _TBYTES _OBJ (_I32 _BYTES) @@ -3701,6 +3887,7 @@ namespace lime { DEFINE_HL_PRIM (_F64, hl_al_get_proc_address, _STRING); DEFINE_HL_PRIM (_ARR, hl_al_get_source3f, _TCFFIPOINTER _I32); DEFINE_HL_PRIM (_ARR, hl_al_get_source3i, _TCFFIPOINTER _I32); + DEFINE_HL_PRIM (_ARR, hl_al_get_sourcedv_soft, _TCFFIPOINTER _I32 _I32); DEFINE_HL_PRIM (_F32, hl_al_get_sourcef, _TCFFIPOINTER _I32); DEFINE_HL_PRIM (_ARR, hl_al_get_sourcefv, _TCFFIPOINTER _I32 _I32); DEFINE_HL_PRIM (_DYN, hl_al_get_sourcei, _TCFFIPOINTER _I32); @@ -3711,6 +3898,7 @@ namespace lime { DEFINE_HL_PRIM (_BOOL, hl_al_is_effect, _TCFFIPOINTER); DEFINE_HL_PRIM (_BOOL, hl_al_is_enabled, _I32); DEFINE_HL_PRIM (_BOOL, hl_al_is_extension_present, _STRING); + DEFINE_HL_PRIM (_BOOL, hl_alc_is_extension_present, _TCFFIPOINTER _STRING); DEFINE_HL_PRIM (_BOOL, hl_al_is_filter, _TCFFIPOINTER); DEFINE_HL_PRIM (_BOOL, hl_al_is_source, _TCFFIPOINTER); DEFINE_HL_PRIM (_VOID, hl_al_listener3f, _I32 _F32 _F32 _F32); @@ -3752,6 +3940,11 @@ namespace lime { DEFINE_HL_PRIM (_VOID, hl_alc_process_context, _TCFFIPOINTER); DEFINE_HL_PRIM (_VOID, hl_alc_resume_device, _TCFFIPOINTER); DEFINE_HL_PRIM (_VOID, hl_alc_suspend_context, _TCFFIPOINTER); + DEFINE_HL_PRIM (_TCFFIPOINTER, hl_alc_capture_open_device, _STRING _I32 _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_alc_capture_close_device, _TCFFIPOINTER); + DEFINE_HL_PRIM (_VOID, hl_alc_capture_start, _TCFFIPOINTER); + DEFINE_HL_PRIM (_VOID, hl_alc_capture_stop, _TCFFIPOINTER); + DEFINE_HL_PRIM (_VOID, hl_alc_capture_samples, _TCFFIPOINTER _TBYTES _I32); } diff --git a/project/src/media/video/AVFVideoBackend.mm b/project/src/media/video/AVFVideoBackend.mm new file mode 100644 index 0000000000..c71ea0569a --- /dev/null +++ b/project/src/media/video/AVFVideoBackend.mm @@ -0,0 +1,1468 @@ +#include + + +#ifdef LIME_VIDEO_AVFOUNDATION + + +// OpenGL, OpenGL ES and the CoreVideo texture caches for them are deprecated +#ifndef GL_SILENCE_DEPRECATION +#define GL_SILENCE_DEPRECATION +#endif +#ifndef GLES_SILENCE_DEPRECATION +#define GLES_SILENCE_DEPRECATION +#endif + +// ANGLE contexts are not CGL or EAGL contexts that can share CoreVideo textures +#ifndef NATIVE_TOOLKIT_SDL_ANGLE +#define LIME_VIDEO_AVF_TEXTURES +#endif + +#ifdef LIME_VIDEO_AVF_TEXTURES +#ifdef HX_MACOS +#import +#import +#import +#import +#else +#import +#import +#import +#endif +#endif + +#import +#import +#import +#include +#include +#include +#include + +#if __has_feature(objc_arc) +#define LIME_AUTORELEASE(object) (object) +#define LIME_RETAIN(object) (object) +#define LIME_RELEASE(object) +#define LIME_BRIDGE(type, pointer) ((__bridge type)(pointer)) +#else +#define LIME_AUTORELEASE(object) [(object) autorelease] +#define LIME_RETAIN(object) [(object) retain] +#define LIME_RELEASE(object) [(object) release] +#define LIME_BRIDGE(type, pointer) ((type)(pointer)) +#endif + + +namespace lime { + + + // AVAssetReader starts each stream at the first frame or sample in its time + // range, so a seek reads from a little earlier to include the frame showing + // at the seek time. VideoDecoder drops what precedes an accurate seek. + static const double SEEK_PREROLL = 1.0; + + // A reader that fails mid-stream, as iOS makes them when it reclaims the + // decoder of an application in the background, is started again this many + // times in a row before the stream ends with an error + static const int MAX_READER_RESTARTS = 3; + + + static VideoColorTransfer ToTransfer (CFTypeRef transfer) { + + // The PQ and HLG constants need macOS 10.13 and iOS 11, so their values + // are compared + if (transfer && CFGetTypeID (transfer) == CFStringGetTypeID ()) { + + if (CFEqual (transfer, CFSTR ("SMPTE_ST_2084_PQ"))) return VIDEO_COLOR_TRANSFER_PQ; + if (CFEqual (transfer, CFSTR ("ITU_R_2100_HLG"))) return VIDEO_COLOR_TRANSFER_HLG; + + } + + return VIDEO_COLOR_TRANSFER_SDR; + + } + + + // CVBufferCopyAttachment, which replaces CVBufferGetAttachment, needs macOS 12 and iOS 15 + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + + static VideoColorTransfer GetTransfer (CVBufferRef buffer, VideoColorTransfer source) { + + // AVFoundation tone maps HDR to the standard range it is asked for, but + // not all of it, such as H.264, whose frames keep their transfer + CFTypeRef transfer = CVBufferGetAttachment (buffer, kCVImageBufferTransferFunctionKey, NULL); + return transfer ? ToTransfer (transfer) : source; + + } + + static VideoColorMatrix GetColorMatrix (CVBufferRef buffer, int width, int height) { + + CFTypeRef matrix = CVBufferGetAttachment (buffer, kCVImageBufferYCbCrMatrixKey, NULL); + + if (matrix) { + + if (CFEqual (matrix, kCVImageBufferYCbCrMatrix_ITU_R_601_4)) return VIDEO_COLOR_MATRIX_BT601; + if (CFEqual (matrix, kCVImageBufferYCbCrMatrix_ITU_R_709_2)) return VIDEO_COLOR_MATRIX_BT709; + + // kCVImageBufferYCbCrMatrix_ITU_R_2020 needs macOS 10.11, so compare with its value + if (CFEqual (matrix, CFSTR ("ITU_R_2020"))) return VIDEO_COLOR_MATRIX_BT2020; + + } + + return VideoBackend::GetDefaultColorMatrix (width, height); + + } + + #pragma clang diagnostic pop + + + static VideoDecodeResult GetEndResult (AVAssetReader* reader) { + + // copyNextSampleBuffer returns NULL at the end of the time range or on failure + return [reader status] == AVAssetReaderStatusFailed ? VIDEO_DECODE_ERROR : VIDEO_DECODE_END; + + } + + + // The synchronous track properties are deprecated in favor of asynchronous + // loading, but Open has to wait for them anyway + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + + static VideoColorTransfer GetSourceTransfer (AVAssetTrack* track) { + + NSArray* formats = [track formatDescriptions]; + if ([formats count] == 0) return VIDEO_COLOR_TRANSFER_SDR; + + CMVideoFormatDescriptionRef format = LIME_BRIDGE (CMVideoFormatDescriptionRef, [formats objectAtIndex:0]); + return ToTransfer (CMFormatDescriptionGetExtension (format, kCMFormatDescriptionExtension_TransferFunction)); + + } + + + static double GetPixelAspectRatio (AVAssetTrack* track) { + + NSArray* formats = [track formatDescriptions]; + if ([formats count] == 0) return 0; + + CMVideoFormatDescriptionRef format = LIME_BRIDGE (CMVideoFormatDescriptionRef, [formats objectAtIndex:0]); + + // A pasp box gives the shape of the pixels + CFTypeRef shape = CMFormatDescriptionGetExtension (format, kCMFormatDescriptionExtension_PixelAspectRatio); + + if (shape && CFGetTypeID (shape) == CFDictionaryGetTypeID ()) { + + CFNumberRef horizontal = (CFNumberRef)CFDictionaryGetValue ((CFDictionaryRef)shape, kCMFormatDescriptionKey_PixelAspectRatioHorizontalSpacing); + CFNumberRef vertical = (CFNumberRef)CFDictionaryGetValue ((CFDictionaryRef)shape, kCMFormatDescriptionKey_PixelAspectRatioVerticalSpacing); + double horizontalSpacing = 0; + double verticalSpacing = 0; + + if (horizontal && vertical && CFNumberGetValue (horizontal, kCFNumberDoubleType, &horizontalSpacing) && CFNumberGetValue (vertical, kCFNumberDoubleType, &verticalSpacing) && horizontalSpacing > 0 && verticalSpacing > 0) { + + return horizontalSpacing / verticalSpacing; + + } + + } + + // Otherwise the size the track is shown at, where it differs from the + // size it is stored at + CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions (format); + CGSize natural = [track naturalSize]; + + if (dimensions.width > 0 && dimensions.height > 0 && natural.width > 0 && natural.height > 0) { + + double ratio = (natural.width / dimensions.width) / (natural.height / dimensions.height); + if (fabs (ratio - 1.0) > 0.01) return ratio; + + } + + return 0; + + } + + #pragma clang diagnostic pop + + + static double GetSeconds (CMTime time, double fallback) { + + return CMTIME_IS_NUMERIC (time) ? CMTimeGetSeconds (time) : fallback; + + } + + + static NSURL* GetURL (const char* path) { + + NSString* string = [NSString stringWithUTF8String:path]; + if (!string) return nil; + + if ([string rangeOfString:@"://"].location != NSNotFound) { + + NSURL* url = [NSURL URLWithString:string]; + + // File URLs are often written without escaping spaces and other characters + if (!url && [string hasPrefix:@"file://"]) { + + NSString* filePath = [string substringFromIndex:7]; + if ([filePath hasPrefix:@"localhost/"]) filePath = [filePath substringFromIndex:9]; + url = [NSURL fileURLWithPath:filePath]; + + } + + // AVAssetReader only reads local files, so other URLs fail straight + // away rather than after loading over the network + return url && [url isFileURL] ? url : nil; + + } + + if ([string hasPrefix:@"/"]) return [NSURL fileURLWithPath:string]; + + // Relative paths are resources in the application bundle, or files in the current directory + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSString* resourcePath = [[NSBundle mainBundle] resourcePath]; + + if (resourcePath) { + + NSString* bundlePath = [resourcePath stringByAppendingPathComponent:string]; + if ([fileManager fileExistsAtPath:bundlePath]) return [NSURL fileURLWithPath:bundlePath]; + + } + + return [NSURL fileURLWithPath:[[fileManager currentDirectoryPath] stringByAppendingPathComponent:string]]; + + } + + + static void RegisterDecoders () { + + // VideoToolbox decodes VP9 from macOS 11 once its decoder is registered. + // It is looked up at runtime, so older systems and iOS go without. + static std::once_flag flag; + + std::call_once (flag, [] { + + typedef void (*RegisterDecoderFunc) (uint32_t); + void* videoToolbox = dlopen ("/System/Library/Frameworks/VideoToolbox.framework/VideoToolbox", RTLD_LAZY | RTLD_LOCAL); + RegisterDecoderFunc registerDecoder = videoToolbox ? (RegisterDecoderFunc)dlsym (videoToolbox, "VTRegisterSupplementalVideoDecoderIfAvailable") : NULL; + + // kCMVideoCodecType_VP9, 'vp09' + if (registerDecoder) registerDecoder (0x76703039); + + }); + + } + + + static bool IsBGRA (CVImageBufferRef image) { + + return CFGetTypeID (image) == CVPixelBufferGetTypeID () && CVPixelBufferGetPixelFormatType (image) == kCVPixelFormatType_32BGRA; + + } + + + static bool IsNV12 (CVImageBufferRef image) { + + return CFGetTypeID (image) == CVPixelBufferGetTypeID () && CVPixelBufferGetPixelFormatType (image) == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange && CVPixelBufferGetPlaneCount (image) >= 2; + + } + + + #if defined (LIME_VIDEO_AVF_TEXTURES) && defined (HX_MACOS) + + // Framebuffer objects and blits are core from OpenGL 3.0. Legacy 2.1 + // contexts have them as ARB_framebuffer_object, which uses the same + // functions, or as the EXT extensions. + + static void BindFramebuffer (bool ext, GLenum target, GLuint framebuffer) { + + if (ext) glBindFramebufferEXT (target, framebuffer); + else glBindFramebuffer (target, framebuffer); + + } + + + static void BlitFramebuffer (bool ext, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1) { + + if (ext) glBlitFramebufferEXT (srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, GL_COLOR_BUFFER_BIT, GL_NEAREST); + else glBlitFramebuffer (srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + } + + + static void DeleteFramebuffer (bool ext, GLuint framebuffer) { + + if (ext) glDeleteFramebuffersEXT (1, &framebuffer); + else glDeleteFramebuffers (1, &framebuffer); + + } + + + static void FramebufferTexture2D (bool ext, GLenum target, GLenum textureTarget, GLuint texture) { + + if (ext) glFramebufferTexture2DEXT (target, GL_COLOR_ATTACHMENT0, textureTarget, texture, 0); + else glFramebufferTexture2D (target, GL_COLOR_ATTACHMENT0, textureTarget, texture, 0); + + } + + + static GLuint GenFramebuffer (bool ext) { + + GLuint framebuffer = 0; + + if (ext) glGenFramebuffersEXT (1, &framebuffer); + else glGenFramebuffers (1, &framebuffer); + + return framebuffer; + + } + + + static bool HasExtension (const char* extensions, const char* name) { + + if (!extensions) return false; + + size_t length = strlen (name); + + for (const char* match = strstr (extensions, name); match; match = strstr (match + length, name)) { + + if ((match == extensions || match[-1] == ' ') && (match[length] == ' ' || match[length] == '\0')) return true; + + } + + return false; + + } + + + static bool IsFramebufferComplete (bool ext, GLenum target) { + + return (ext ? glCheckFramebufferStatusEXT (target) : glCheckFramebufferStatus (target)) == GL_FRAMEBUFFER_COMPLETE; + + } + + #endif + + + #if defined (LIME_VIDEO_AVF_TEXTURES) && defined (HX_MACOS) + + // GL objects of a backend closed away from the GL thread, deleted the next + // time a decoder reads a texture frame where their context is current + struct AVFOrphan { + + void* context; + GLuint copyFramebuffer; + GLuint copyTexture; + bool framebufferEXT; + GLuint sourceFramebuffer; + GLuint sourceTexture; + + }; + + // Never destroyed, as exiting runs static destructors while threads can + // still be closing videos + static std::mutex* orphanMutex = new std::mutex (); + static std::vector* orphans = new std::vector (); + + #endif + + + // Only iOS uses a CoreVideo texture cache + #if defined (LIME_VIDEO_AVF_TEXTURES) && !defined (HX_MACOS) + typedef CVOpenGLESTextureCacheRef AVFTextureCacheRef; + #else + typedef void* AVFTextureCacheRef; + #endif + + + // A BGRA frame that VideoToolbox converted on the GPU, kept in its IOSurface + class AVFTextureFrame : public VideoTextureFrame { + + + public: + + AVFTextureFrame (CVPixelBufferRef pixelBuffer); + ~AVFTextureFrame (); + + CVPixelBufferRef buffer; + + // The CVOpenGLESTextureRef made from the buffer while the frame is + // locked on iOS + CVImageBufferRef texture; + + + }; + + + AVFTextureFrame::AVFTextureFrame (CVPixelBufferRef pixelBuffer) { + + buffer = CVPixelBufferRetain (pixelBuffer); + height = (int)CVPixelBufferGetHeight (pixelBuffer); + texture = NULL; + width = (int)CVPixelBufferGetWidth (pixelBuffer); + + } + + + AVFTextureFrame::~AVFTextureFrame () { + + if (texture) CFRelease (texture); + CVPixelBufferRelease (buffer); + + } + + + // Returns an autoreleased reader that has started reading the output from + // time, or nil + static AVAssetReader* StartReader (AVAsset* asset, AVAssetReaderTrackOutput* output, double time) { + + AVAssetReader* reader = LIME_AUTORELEASE ([[AVAssetReader alloc] initWithAsset:asset error:NULL]); + if (!reader || ![reader canAddOutput:output]) return nil; + + if (time > 0) { + + [reader setTimeRange:CMTimeRangeMake (CMTimeMakeWithSeconds (time, 1000000), kCMTimePositiveInfinity)]; + + } + + [reader addOutput:output]; + return [reader startReading] ? reader : nil; + + } + + + // Audio and video use separate readers of one asset. A reader with two + // outputs stalls when only one of them is read, and VideoDecoder reads the + // streams on different threads at different rates. AVAssetReader cannot + // seek, so seeking replaces the readers with ones that start at the new time. + class AVFVideoBackend : public VideoBackend { + + + public: + + AVFVideoBackend (); + ~AVFVideoBackend (); + + virtual void Close (); + virtual VideoDecodeResult DecodeAudio (std::vector* pcm, double* time); + virtual VideoDecodeResult DecodeVideo (VideoPlanes* planes, double* time, double* duration); + virtual VideoDecodeResult DecodeVideoTexture (VideoTextureFrame** frame, double* time, double* duration); + virtual unsigned int LockTexture (VideoTextureFrame* frame); + virtual bool Open (const char* path, bool hardwareDecoding, VideoStreamInfo* info); + virtual void ReleaseTextureFrame (VideoTextureFrame* frame); + virtual void ReleaseOrphans (); + virtual void ReleaseTextures (); + virtual bool Seek (double time); + virtual void SetTextureOutput (bool enabled); + virtual bool SupportsTextures (); + virtual void UnlockTexture (VideoTextureFrame* frame); + + private: + + CMSampleBufferRef CopyNextVideoSample (); + void ForgetTextures (); + void GetSampleTimes (CMSampleBufferRef sample, double* time, double* duration); + bool OpenAudio (AVAssetTrack* track); + bool OpenVideo (AVAssetTrack* track, int* width, int* height); + bool RestartVideo (bool textures); + bool StartAudio (double time); + bool StartVideo (double time, bool textures, bool standardColor = true); + void StopAudio (); + void StopVideo (); + void UnlockVideo (); + + AVURLAsset* asset; + double assetDuration; + int audioChannels; + AVAssetReaderTrackOutput* audioOutput; + double audioPosition; + AVAssetReader* audioReader; + int audioRestarts; + int audioSampleRate; + AVAssetTrack* audioTrack; + unsigned int copyFramebuffer; + unsigned int copyTexture; + int copyTextureHeight; + int copyTextureWidth; + bool framebufferEXT; + double frameRate; + VideoColorTransfer frameTransfer; + CMSampleBufferRef lockedSample; + CMSampleBufferRef pendingSample; + unsigned int sourceFramebuffer; + unsigned int sourceTexture; + VideoColorTransfer sourceTransfer; + AVFTextureCacheRef textureCache; + void* textureContext; + std::atomic textureOutput; + AVAssetReaderTrackOutput* videoOutput; + double videoPosition; + AVAssetReader* videoReader; + int videoRestarts; + bool videoTextures; + AVAssetTrack* videoTrack; + + + }; + + + AVFVideoBackend::AVFVideoBackend () { + + asset = nil; + assetDuration = 0; + audioChannels = 0; + audioOutput = nil; + audioPosition = 0; + audioReader = nil; + audioRestarts = 0; + audioSampleRate = 0; + audioTrack = nil; + copyFramebuffer = 0; + copyTexture = 0; + copyTextureHeight = 0; + copyTextureWidth = 0; + framebufferEXT = false; + frameRate = 0; + frameTransfer = VIDEO_COLOR_TRANSFER_SDR; + lockedSample = NULL; + pendingSample = NULL; + sourceFramebuffer = 0; + sourceTexture = 0; + sourceTransfer = VIDEO_COLOR_TRANSFER_SDR; + textureCache = NULL; + textureContext = NULL; + textureOutput = false; + videoOutput = nil; + videoPosition = 0; + videoReader = nil; + videoRestarts = 0; + videoTextures = false; + videoTrack = nil; + + } + + + AVFVideoBackend::~AVFVideoBackend () { + + Close (); + + // A decoder collected away from its GL thread still lets go of the + // texture cache, which keeps its context alive + ForgetTextures (); + + } + + + void AVFVideoBackend::Close () { + + #if defined (LIME_VIDEO_AVF_TEXTURES) && defined (HX_MACOS) + // GL objects still held were left by closing away from the GL thread, as + // closing there releases them first + if (copyTexture || sourceTexture || copyFramebuffer || sourceFramebuffer) { + + AVFOrphan orphan = { textureContext, copyFramebuffer, copyTexture, framebufferEXT, sourceFramebuffer, sourceTexture }; + + { + std::lock_guard lock (*orphanMutex); + orphans->push_back (orphan); + } + + ForgetTextures (); + + } + #endif + + @autoreleasepool { + + StopAudio (); + StopVideo (); + + LIME_RELEASE (audioTrack); + audioTrack = nil; + LIME_RELEASE (videoTrack); + videoTrack = nil; + LIME_RELEASE (asset); + asset = nil; + + } + + assetDuration = 0; + audioChannels = 0; + audioPosition = 0; + audioSampleRate = 0; + frameRate = 0; + frameTransfer = VIDEO_COLOR_TRANSFER_SDR; + sourceTransfer = VIDEO_COLOR_TRANSFER_SDR; + videoPosition = 0; + + } + + + CMSampleBufferRef AVFVideoBackend::CopyNextVideoSample () { + + if (pendingSample) { + + CMSampleBufferRef sample = pendingSample; + pendingSample = NULL; + return sample; + + } + + while (true) { + + CMSampleBufferRef sample = [videoOutput copyNextSampleBuffer]; + if (!sample || CMSampleBufferGetImageBuffer (sample)) return sample; + + // Skip sample buffers that carry no picture + CFRelease (sample); + + } + + } + + + VideoDecodeResult AVFVideoBackend::DecodeAudio (std::vector* pcm, double* time) { + + if (!audioOutput) return audioTrack ? VIDEO_DECODE_ERROR : VIDEO_DECODE_END; + + @autoreleasepool { + + while (true) { + + CMSampleBufferRef sample = [audioOutput copyNextSampleBuffer]; + + if (!sample && [audioReader status] == AVAssetReaderStatusFailed && audioRestarts < MAX_READER_RESTARTS) { + + audioRestarts++; + StopAudio (); + if (StartAudio (audioPosition)) continue; + + } + + if (!sample) return GetEndResult (audioReader); + audioRestarts = 0; + + CMBlockBufferRef buffer = CMSampleBufferGetDataBuffer (sample); + size_t length = buffer ? CMBlockBufferGetDataLength (buffer) : 0; + + if (length == 0) { + + CFRelease (sample); + continue; + + } + + size_t offset = pcm->size (); + pcm->resize (offset + length); + + OSStatus status = CMBlockBufferCopyDataBytes (buffer, 0, length, pcm->data () + offset); + double timestamp = GetSeconds (CMSampleBufferGetPresentationTimeStamp (sample), audioPosition); + CFRelease (sample); + + if (status != kCMBlockBufferNoErr) { + + pcm->resize (offset); + return VIDEO_DECODE_ERROR; + + } + + *time = timestamp; + audioPosition = timestamp + (double)(length / (audioChannels * 2)) / audioSampleRate; + + return VIDEO_DECODE_OK; + + } + + } + + } + + + VideoDecodeResult AVFVideoBackend::DecodeVideo (VideoPlanes* planes, double* time, double* duration) { + + UnlockVideo (); + + @autoreleasepool { + + // Texture output stopped, and VideoDecoder reads in memory until the + // seek that follows, so continue from the next frame as NV12 + if (videoTextures) { + + StopVideo (); + StartVideo (videoPosition, false); + + } + + if (!videoOutput) return videoTrack ? VIDEO_DECODE_ERROR : VIDEO_DECODE_END; + + CMSampleBufferRef sample = CopyNextVideoSample (); + if (!sample && RestartVideo (false)) sample = CopyNextVideoSample (); + if (!sample) return GetEndResult (videoReader); + videoRestarts = 0; + + CVPixelBufferRef image = CMSampleBufferGetImageBuffer (sample); + + if (!IsNV12 (image) || CVPixelBufferLockBaseAddress (image, kCVPixelBufferLock_ReadOnly) != kCVReturnSuccess) { + + CFRelease (sample); + return VIDEO_DECODE_ERROR; + + } + + lockedSample = sample; + + const unsigned char* y = (const unsigned char*)CVPixelBufferGetBaseAddressOfPlane (image, 0); + const unsigned char* uv = (const unsigned char*)CVPixelBufferGetBaseAddressOfPlane (image, 1); + int width = (int)CVPixelBufferGetWidthOfPlane (image, 0); + int height = (int)CVPixelBufferGetHeightOfPlane (image, 0); + + if (!y || !uv || width <= 0 || height <= 0) { + + UnlockVideo (); + return VIDEO_DECODE_ERROR; + + } + + // The output format is video range, which full range sources are converted to + planes->colorMatrix = GetColorMatrix (image, width, height); + planes->fullRange = false; + planes->transfer = frameTransfer = GetTransfer (image, sourceTransfer); + planes->width = width; + planes->height = height; + planes->y = y; + planes->yStride = (int)CVPixelBufferGetBytesPerRowOfPlane (image, 0); + planes->u = uv; + planes->v = uv + 1; + planes->uvStride = (int)CVPixelBufferGetBytesPerRowOfPlane (image, 1); + planes->uvPixelStride = 2; + + GetSampleTimes (sample, time, duration); + + return VIDEO_DECODE_OK; + + } + + } + + + VideoDecodeResult AVFVideoBackend::DecodeVideoTexture (VideoTextureFrame** frame, double* time, double* duration) { + + *frame = NULL; + + // VideoDecoder reads the next frame with DecodeVideo instead + if (!SupportsTextures ()) return VIDEO_DECODE_OK; + + @autoreleasepool { + + CMSampleBufferRef sample = CopyNextVideoSample (); + if (!sample && RestartVideo (true)) sample = CopyNextVideoSample (); + if (!sample) return GetEndResult (videoReader); + videoRestarts = 0; + + CVPixelBufferRef image = CMSampleBufferGetImageBuffer (sample); + + if (!IsBGRA (image)) { + + CFRelease (sample); + return VIDEO_DECODE_ERROR; + + } + + // HDR that was not tone mapped is decoded into memory from here on, + // where VideoDecoder tone maps it + frameTransfer = GetTransfer (image, sourceTransfer); + + if (frameTransfer != VIDEO_COLOR_TRANSFER_SDR) { + + CFRelease (sample); + return VIDEO_DECODE_OK; + + } + + GetSampleTimes (sample, time, duration); + *frame = new AVFTextureFrame (image); + CFRelease (sample); + + return VIDEO_DECODE_OK; + + } + + } + + + void AVFVideoBackend::ForgetTextures () { + + // Leaves the GL objects, which belong to a context that may be gone + if (textureCache) { + + CFRelease (textureCache); + textureCache = NULL; + + } + + copyFramebuffer = 0; + copyTexture = 0; + copyTextureHeight = 0; + copyTextureWidth = 0; + framebufferEXT = false; + sourceFramebuffer = 0; + sourceTexture = 0; + textureContext = NULL; + + } + + + void AVFVideoBackend::GetSampleTimes (CMSampleBufferRef sample, double* time, double* duration) { + + double sampleDuration = GetSeconds (CMSampleBufferGetDuration (sample), 0); + + *time = GetSeconds (CMSampleBufferGetPresentationTimeStamp (sample), videoPosition); + *duration = sampleDuration > 0 ? sampleDuration : (frameRate > 0 ? 1.0 / frameRate : 0); + videoPosition = *time + *duration; + + } + + + unsigned int AVFVideoBackend::LockTexture (VideoTextureFrame* frame) { + + #ifdef LIME_VIDEO_AVF_TEXTURES + AVFTextureFrame* textureFrame = (AVFTextureFrame*)frame; + + if (textureFrame->texture) { + + CFRelease (textureFrame->texture); + textureFrame->texture = NULL; + + } + + int width = textureFrame->width; + int height = textureFrame->height; + + @autoreleasepool { + + #ifdef HX_MACOS + CGLContextObj context = CGLGetCurrentContext (); + if (!context) return 0; + + if ((void*)context != textureContext) ForgetTextures (); + textureContext = (void*)context; + + if (!sourceFramebuffer) { + + const char* version = (const char*)glGetString (GL_VERSION); + int major = version ? atoi (version) : 0; + const char* extensions = major < 3 ? (const char*)glGetString (GL_EXTENSIONS) : NULL; + + if (major >= 3 || HasExtension (extensions, "GL_ARB_framebuffer_object")) { + + framebufferEXT = false; + + } else if (HasExtension (extensions, "GL_EXT_framebuffer_object") && HasExtension (extensions, "GL_EXT_framebuffer_blit")) { + + framebufferEXT = true; + + } else { + + return 0; + + } + + copyFramebuffer = GenFramebuffer (framebufferEXT); + sourceFramebuffer = GenFramebuffer (framebufferEXT); + + } + + IOSurfaceRef surface = CVPixelBufferGetIOSurface (textureFrame->buffer); + if (!surface) return 0; + + // OpenFL caches GL state, so everything changed here is restored + GLint previousDrawFramebuffer = 0; + GLint previousReadFramebuffer = 0; + GLint previousRectangleTexture = 0; + GLint previousTexture = 0; + GLint previousUnpackBuffer = 0; + glGetIntegerv (GL_DRAW_FRAMEBUFFER_BINDING, &previousDrawFramebuffer); + glGetIntegerv (GL_READ_FRAMEBUFFER_BINDING, &previousReadFramebuffer); + glGetIntegerv (GL_TEXTURE_BINDING_2D, &previousTexture); + glGetIntegerv (GL_TEXTURE_BINDING_RECTANGLE_ARB, &previousRectangleTexture); + glGetIntegerv (GL_PIXEL_UNPACK_BUFFER_BINDING, &previousUnpackBuffer); + GLboolean scissorTest = glIsEnabled (GL_SCISSOR_TEST); + + // The IOSurface is bound to a rectangle texture for every frame, and + // copied into a 2D texture on the GPU. VideoToolbox reuses surfaces, and + // on Apple silicon, the textures CoreVideo's texture cache keeps for a + // surface can go on showing an earlier frame. + unsigned int name = 0; + + if (!sourceTexture) glGenTextures (1, &sourceTexture); + glBindTexture (GL_TEXTURE_RECTANGLE_ARB, sourceTexture); + + if (CGLTexImageIOSurface2D (context, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA8, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, surface, 0) == kCGLNoError) { + + if (!copyTexture || copyTextureWidth != width || copyTextureHeight != height) { + + if (!copyTexture) glGenTextures (1, ©Texture); + + glBindTexture (GL_TEXTURE_2D, copyTexture); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // A bound unpack buffer would be read as the initial contents + if (previousUnpackBuffer) glBindBuffer (GL_PIXEL_UNPACK_BUFFER, 0); + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + if (previousUnpackBuffer) glBindBuffer (GL_PIXEL_UNPACK_BUFFER, previousUnpackBuffer); + + BindFramebuffer (framebufferEXT, GL_DRAW_FRAMEBUFFER, copyFramebuffer); + FramebufferTexture2D (framebufferEXT, GL_DRAW_FRAMEBUFFER, GL_TEXTURE_2D, copyTexture); + + copyTextureWidth = width; + copyTextureHeight = height; + + } + + BindFramebuffer (framebufferEXT, GL_DRAW_FRAMEBUFFER, copyFramebuffer); + BindFramebuffer (framebufferEXT, GL_READ_FRAMEBUFFER, sourceFramebuffer); + FramebufferTexture2D (framebufferEXT, GL_READ_FRAMEBUFFER, GL_TEXTURE_RECTANGLE_ARB, sourceTexture); + + if (IsFramebufferComplete (framebufferEXT, GL_READ_FRAMEBUFFER) && IsFramebufferComplete (framebufferEXT, GL_DRAW_FRAMEBUFFER)) { + + // Rows of an IOSurface start at the top of the picture, as those + // of uploaded frames do, so the copy is not flipped + if (scissorTest) glDisable (GL_SCISSOR_TEST); + BlitFramebuffer (framebufferEXT, 0, 0, width, height, 0, 0, width, height); + if (scissorTest) glEnable (GL_SCISSOR_TEST); + + name = copyTexture; + + } + + FramebufferTexture2D (framebufferEXT, GL_READ_FRAMEBUFFER, GL_TEXTURE_RECTANGLE_ARB, 0); + + } + + BindFramebuffer (framebufferEXT, GL_READ_FRAMEBUFFER, previousReadFramebuffer); + BindFramebuffer (framebufferEXT, GL_DRAW_FRAMEBUFFER, previousDrawFramebuffer); + glBindTexture (GL_TEXTURE_RECTANGLE_ARB, previousRectangleTexture); + glBindTexture (GL_TEXTURE_2D, previousTexture); + + if (!name) return 0; + + // VideoToolbox can decode into the IOSurface again once the frame is + // released, so send the copy to the GPU now + glFlush (); + return name; + #else + EAGLContext* context = [EAGLContext currentContext]; + if (!context) return 0; + + if (LIME_BRIDGE (void*, context) != textureContext) ForgetTextures (); + textureContext = LIME_BRIDGE (void*, context); + + if (!textureCache && CVOpenGLESTextureCacheCreate (kCFAllocatorDefault, NULL, context, NULL, &textureCache) != kCVReturnSuccess) { + + textureCache = NULL; + return 0; + + } + + GLint previousTexture = 0; + glGetIntegerv (GL_TEXTURE_BINDING_2D, &previousTexture); + + // The texture samples the IOSurface directly, swizzling BGRA to RGBA + CVOpenGLESTextureRef texture = NULL; + CVReturn result = CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault, textureCache, textureFrame->buffer, NULL, GL_TEXTURE_2D, GL_RGBA, width, height, GL_BGRA_EXT, GL_UNSIGNED_BYTE, 0, &texture); + + if (result != kCVReturnSuccess || !texture) { + + if (texture) CFRelease (texture); + glBindTexture (GL_TEXTURE_2D, previousTexture); + return 0; + + } + + // Without mipmaps, the default minification filter would leave the + // texture incomplete + GLuint name = CVOpenGLESTextureGetName (texture); + glBindTexture (GL_TEXTURE_2D, name); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture (GL_TEXTURE_2D, previousTexture); + + textureFrame->texture = texture; + return name; + #endif + + } + #else + return 0; + #endif + + } + + + // The synchronous asset and track properties are deprecated in favor of + // asynchronous loading, but Open has to wait for them anyway + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + + bool AVFVideoBackend::Open (const char* path, bool hardwareDecoding, VideoStreamInfo* info) { + + Close (); + + if (!path || !path[0]) return false; + + RegisterDecoders (); + + @autoreleasepool { + + NSURL* url = GetURL (path); + if (!url) return false; + + // Precise timing makes the duration and the readers' time ranges exact for formats such as MP3 + asset = [[AVURLAsset alloc] initWithURL:url options:@{ AVURLAssetPreferPreciseDurationAndTimingKey: @YES }]; + + NSArray* audioTracks = [asset tracksWithMediaType:AVMediaTypeAudio]; + NSArray* videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo]; + assetDuration = GetSeconds ([asset duration], 0); + + // VideoToolbox decodes in hardware whenever it can and AVAssetReader + // has no option to prevent it, so hardwareDecoding is only a hint + int width = 0; + int height = 0; + + info->hasVideo = [videoTracks count] > 0 && OpenVideo ([videoTracks objectAtIndex:0], &width, &height); + info->hasAudio = [audioTracks count] > 0 && OpenAudio ([audioTracks objectAtIndex:0]); + + // A track that was found but cannot be decoded, such as one in a codec + // the system lacks + info->videoUnsupported = [videoTracks count] > 0 && !info->hasVideo; + info->audioUnsupported = [audioTracks count] > 0 && !info->hasAudio; + + if (!info->hasVideo && !info->hasAudio) return false; + + if (info->hasVideo) { + + // Phones store video recorded upright sideways, with a transform + // that turns it, and some video has pixels that are not square. + // Frames are decoded as they are stored. + CGAffineTransform transform = [videoTrack preferredTransform]; + info->rotation = (int)lround (atan2 (transform.b, transform.a) * 180.0 / M_PI); + info->pixelAspectRatio = GetPixelAspectRatio (videoTrack); + sourceTransfer = GetSourceTransfer (videoTrack); + + } + + info->audioChannels = audioChannels; + info->audioSampleRate = audioSampleRate; + info->duration = assetDuration; + info->frameRate = frameRate; + info->width = width; + info->height = height; + + return true; + + } + + } + + + bool AVFVideoBackend::OpenAudio (AVAssetTrack* track) { + + NSArray* formats = [track formatDescriptions]; + if ([formats count] == 0) return false; + + CMAudioFormatDescriptionRef format = LIME_BRIDGE (CMAudioFormatDescriptionRef, [formats objectAtIndex:0]); + const AudioStreamBasicDescription* description = CMAudioFormatDescriptionGetStreamBasicDescription (format); + + // HE-AAC describes its base layer, at half the decoded sample rate + const AudioFormatListItem* richest = CMAudioFormatDescriptionGetRichestDecodableFormat (format); + if (richest && richest->mASBD.mSampleRate > 0) description = &richest->mASBD; + + if (!description || description->mSampleRate <= 0) return false; + + audioChannels = description->mChannelsPerFrame == 1 ? 1 : 2; + audioSampleRate = (int)(description->mSampleRate + 0.5); + audioTrack = LIME_RETAIN (track); + + if (StartAudio (0)) return true; + + LIME_RELEASE (audioTrack); + audioTrack = nil; + audioChannels = 0; + audioSampleRate = 0; + + return false; + + } + + + bool AVFVideoBackend::OpenVideo (AVAssetTrack* track, int* width, int* height) { + + videoTrack = LIME_RETAIN (track); + frameRate = [track nominalFrameRate]; + + if (StartVideo (0, textureOutput)) { + + // Decode the first frame for its size, which can differ from the + // track's natural size + pendingSample = CopyNextVideoSample (); + CVImageBufferRef image = pendingSample ? CMSampleBufferGetImageBuffer (pendingSample) : NULL; + + if (image && videoTextures && IsBGRA (image)) { + + *width = (int)CVPixelBufferGetWidth (image); + *height = (int)CVPixelBufferGetHeight (image); + + } else if (image && !videoTextures && IsNV12 (image)) { + + *width = (int)CVPixelBufferGetWidthOfPlane (image, 0); + *height = (int)CVPixelBufferGetHeightOfPlane (image, 0); + + } + + if (*width > 0 && *height > 0) { + + if (frameRate <= 0) { + + double sampleDuration = GetSeconds (CMSampleBufferGetDuration (pendingSample), 0); + if (sampleDuration > 0) frameRate = 1.0 / sampleDuration; + + } + + return true; + + } + + } + + StopVideo (); + + LIME_RELEASE (videoTrack); + videoTrack = nil; + frameRate = 0; + *width = 0; + *height = 0; + + return false; + + } + + #pragma clang diagnostic pop + + + void AVFVideoBackend::ReleaseTextureFrame (VideoTextureFrame* frame) { + + delete frame; + + } + + + void AVFVideoBackend::ReleaseOrphans () { + + #if defined (LIME_VIDEO_AVF_TEXTURES) && defined (HX_MACOS) + std::vector list; + + { + std::lock_guard lock (*orphanMutex); + if (orphans->empty ()) return; + list.swap (*orphans); + } + + void* context = (void*)CGLGetCurrentContext (); + + for (size_t i = 0; i < list.size (); i++) { + + // Objects from a context that is no longer current are left with it + AVFOrphan& orphan = list[i]; + if (orphan.context != context) continue; + + if (orphan.copyTexture) glDeleteTextures (1, &orphan.copyTexture); + if (orphan.sourceTexture) glDeleteTextures (1, &orphan.sourceTexture); + if (orphan.copyFramebuffer) DeleteFramebuffer (orphan.framebufferEXT, orphan.copyFramebuffer); + if (orphan.sourceFramebuffer) DeleteFramebuffer (orphan.framebufferEXT, orphan.sourceFramebuffer); + + } + #endif + + } + + + void AVFVideoBackend::ReleaseTextures () { + + #ifdef LIME_VIDEO_AVF_TEXTURES + #ifdef HX_MACOS + // GL names only mean these objects in the context that made them + if (textureContext && (void*)CGLGetCurrentContext () == textureContext) { + + if (copyTexture) glDeleteTextures (1, ©Texture); + if (sourceTexture) glDeleteTextures (1, &sourceTexture); + if (copyFramebuffer) DeleteFramebuffer (framebufferEXT, copyFramebuffer); + if (sourceFramebuffer) DeleteFramebuffer (framebufferEXT, sourceFramebuffer); + + } + #endif + #endif + + ForgetTextures (); + + } + + + bool AVFVideoBackend::RestartVideo (bool textures) { + + if ([videoReader status] != AVAssetReaderStatusFailed || videoRestarts >= MAX_READER_RESTARTS) return false; + + // Carries on from the frame after the last one read + videoRestarts++; + StopVideo (); + return StartVideo (videoPosition, textures); + + } + + + bool AVFVideoBackend::Seek (double time) { + + @autoreleasepool { + + // A seek past the end reads the last frames, which an accurate seek drops + double start = time - SEEK_PREROLL; + if (assetDuration > 0 && start > assetDuration - SEEK_PREROLL) start = assetDuration - SEEK_PREROLL; + if (start < 0) start = 0; + + bool success = true; + + if (videoTrack) { + + StopVideo (); + success = StartVideo (start, textureOutput) && success; + + } + + if (audioTrack) { + + StopAudio (); + success = StartAudio (start) && success; + + } + + return success; + + } + + } + + + void AVFVideoBackend::SetTextureOutput (bool enabled) { + + // This can be called while the video thread decodes, so the video reader + // only changes format at the next Open or Seek + #ifdef LIME_VIDEO_AVF_TEXTURES + textureOutput = enabled; + #endif + + } + + + bool AVFVideoBackend::StartAudio (double time) { + + // The channel layout lets AVFoundation downmix multichannel audio + AudioChannelLayout layout; + memset (&layout, 0, sizeof (layout)); + layout.mChannelLayoutTag = (audioChannels == 1) ? kAudioChannelLayoutTag_Mono : kAudioChannelLayoutTag_Stereo; + + NSDictionary* settings = @{ + AVFormatIDKey: @(kAudioFormatLinearPCM), + AVSampleRateKey: @((double)audioSampleRate), + AVNumberOfChannelsKey: @(audioChannels), + AVChannelLayoutKey: [NSData dataWithBytes:&layout length:sizeof (layout)], + AVLinearPCMBitDepthKey: @16, + AVLinearPCMIsBigEndianKey: @NO, + AVLinearPCMIsFloatKey: @NO, + AVLinearPCMIsNonInterleaved: @NO + }; + + // AVFoundation throws instead of failing for some assets and settings, + // such as remote URLs on some versions + @try { + + audioOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:audioTrack outputSettings:settings]; + [audioOutput setAlwaysCopiesSampleData:NO]; + audioReader = LIME_RETAIN (StartReader (asset, audioOutput, time)); + + } @catch (NSException* exception) {} + + audioPosition = time; + + if (!audioReader) StopAudio (); + return audioReader != nil; + + } + + + bool AVFVideoBackend::StartVideo (double time, bool textures, bool standardColor) { + + NSMutableDictionary* settings = [NSMutableDictionary dictionary]; + [settings setObject:@(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) forKey:LIME_BRIDGE (NSString*, kCVPixelBufferPixelFormatTypeKey)]; + + #ifdef LIME_VIDEO_AVF_TEXTURES + if (textures) { + + #ifdef HX_MACOS + CFStringRef compatibilityKey = kCVPixelBufferOpenGLCompatibilityKey; + #else + CFStringRef compatibilityKey = kCVPixelBufferOpenGLESCompatibilityKey; + #endif + + // VideoToolbox converts to BGRA on the GPU, into IOSurfaces that OpenGL + // can use + [settings setObject:@(kCVPixelFormatType_32BGRA) forKey:LIME_BRIDGE (NSString*, kCVPixelBufferPixelFormatTypeKey)]; + [settings setObject:@{} forKey:LIME_BRIDGE (NSString*, kCVPixelBufferIOSurfacePropertiesKey)]; + [settings setObject:@YES forKey:LIME_BRIDGE (NSString*, compatibilityKey)]; + + } + #else + textures = false; + #endif + + if (standardColor) { + + // HDR and wide gamut sources, such as the HLG video iPhones record, are + // tone mapped to standard BT.709, which frames are shown in. The keys' + // values are used, as the constants need macOS 10.10 and iOS 10. + [settings setObject:@{ + @"ColorPrimaries": @"ITU_R_709_2", + @"TransferFunction": @"ITU_R_709_2", + @"YCbCrMatrix": @"ITU_R_709_2" + } forKey:@"ColorProperties"]; + + } + + @try { + + videoOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack outputSettings:settings]; + [videoOutput setAlwaysCopiesSampleData:NO]; + videoReader = LIME_RETAIN (StartReader (asset, videoOutput, time)); + + } @catch (NSException* exception) {} + + videoPosition = time; + + if (!videoReader) { + + StopVideo (); + + // Systems that refuse the color setting decode as the source is, and + // memory is used when the texture output is not accepted + if (standardColor) return StartVideo (time, textures, false); + return textures ? StartVideo (time, false) : false; + + } + + videoTextures = textures; + return true; + + } + + + void AVFVideoBackend::StopAudio () { + + if (audioReader) { + + [audioReader cancelReading]; + LIME_RELEASE (audioReader); + audioReader = nil; + + } + + LIME_RELEASE (audioOutput); + audioOutput = nil; + + } + + + void AVFVideoBackend::StopVideo () { + + UnlockVideo (); + + if (pendingSample) { + + CFRelease (pendingSample); + pendingSample = NULL; + + } + + if (videoReader) { + + [videoReader cancelReading]; + LIME_RELEASE (videoReader); + videoReader = nil; + + } + + LIME_RELEASE (videoOutput); + videoOutput = nil; + videoTextures = false; + + } + + + bool AVFVideoBackend::SupportsTextures () { + + return videoTextures && textureOutput && frameTransfer == VIDEO_COLOR_TRANSFER_SDR; + + } + + + void AVFVideoBackend::UnlockTexture (VideoTextureFrame* frame) { + + AVFTextureFrame* textureFrame = (AVFTextureFrame*)frame; + + if (textureFrame->texture) { + + CFRelease (textureFrame->texture); + textureFrame->texture = NULL; + + } + + // Lets the cache reuse the textures of released frames + #if defined (LIME_VIDEO_AVF_TEXTURES) && !defined (HX_MACOS) + if (textureCache) CVOpenGLESTextureCacheFlush (textureCache, 0); + #endif + + } + + + void AVFVideoBackend::UnlockVideo () { + + if (lockedSample) { + + CVPixelBufferUnlockBaseAddress (CMSampleBufferGetImageBuffer (lockedSample), kCVPixelBufferLock_ReadOnly); + CFRelease (lockedSample); + lockedSample = NULL; + + } + + } + + + VideoBackend* VideoBackend::Create () { + + return new AVFVideoBackend (); + + } + + + bool VideoBackend::IsSupported () { + + return true; + + } + + +} + + +#endif diff --git a/project/src/media/video/GStreamerVideoBackend.cpp b/project/src/media/video/GStreamerVideoBackend.cpp new file mode 100644 index 0000000000..97882822d2 --- /dev/null +++ b/project/src/media/video/GStreamerVideoBackend.cpp @@ -0,0 +1,1611 @@ +#include + + +#ifdef LIME_VIDEO_GSTREAMER + + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace lime { + + + // GStreamer is loaded at runtime, so Lime has no build or link dependency + // on it. The few types and constants used here are declared by hand. + + typedef int gboolean; + typedef void (*GCallback) (); + + struct GList; + struct GstBus; + struct GstCaps; + struct GstElement; + struct GstElementFactory; + struct GstEvent; + struct GstPad; + struct GstSample; + struct GstSegment; + struct GstStructure; + struct GstTagList; + + + // Every GObject instance starts with a pointer to its class + struct GTypeInstance { + + void* g_class; + + }; + + + // GStreamer 1.x keeps these public structures ABI stable. They follow + // gstminiobject.h, gstbuffer.h, gstmemory.h, gstmessage.h, gstquery.h, + // gstmeta.h and gstvideometa.h. Only GstMapInfo is allocated here, so the + // others declare just their leading fields. + + struct GstMiniObject { + + size_t type; + int refcount; + int lockstate; + unsigned int flags; + void* copy; + void* dispose; + void* free; + unsigned int n_qdata; + void* qdata; + + }; + + + struct GstBuffer { + + GstMiniObject mini_object; + void* pool; + uint64_t pts; + uint64_t dts; + uint64_t duration; + + }; + + + struct GstMapInfo { + + void* memory; + int flags; + unsigned char* data; + size_t size; + size_t maxsize; + void* user_data[4]; + void* _gst_reserved[4]; + + }; + + + struct GstMessage { + + GstMiniObject mini_object; + int type; + + }; + + + struct GstMeta { + + int flags; + const void* info; + + }; + + + struct GstQuery { + + GstMiniObject mini_object; + int type; + + }; + + + struct GstVideoMeta { + + GstMeta meta; + GstBuffer* buffer; + int flags; + int format; + int id; + unsigned int width; + unsigned int height; + unsigned int n_planes; + size_t offset[4]; + int stride[4]; + + }; + + + typedef int (*GstBusSyncHandler) (GstBus*, GstMessage*, void*); + + + static const int GST_AUTOPLUG_SELECT_SKIP = 2; + static const int GST_AUTOPLUG_SELECT_TRY = 0; + static const int GST_BUS_DROP = 0; + static const int GST_BUS_PASS = 1; + static const uint64_t GST_CLOCK_TIME_NONE = (uint64_t)-1; + static const uint64_t GST_ELEMENT_FACTORY_TYPE_DEMUXER = 1 << 5; + static const int GST_EVENT_TAG = (80 << 8) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4); + static const int GST_FORMAT_TIME = 3; + static const int GST_MAP_READ = 1; + static const int GST_MESSAGE_ASYNC_DONE = 1 << 21; + static const int GST_MESSAGE_ERROR = 1 << 1; + static const uint64_t GST_MSECOND = 1000000; + static const int GST_PAD_LINK_OK = 0; + static const int GST_PAD_SINK = 2; + static const int GST_QUERY_CAPS = (170 << 8) | 3; + static const int GST_RANK_MARGINAL = 64; + static const uint64_t GST_SECOND = 1000000000; + static const int GST_SEEK_FLAG_FLUSH = 1 << 0; + static const int GST_SEEK_FLAG_KEY_UNIT = 1 << 2; + static const int GST_SEEK_FLAG_SNAP_BEFORE = 1 << 5; + static const int GST_STATE_CHANGE_ASYNC = 2; + static const int GST_STATE_CHANGE_FAILURE = 0; + static const int GST_STATE_CHANGE_NO_PREROLL = 3; + static const int GST_STATE_NULL = 1; + static const int GST_STATE_PAUSED = 3; + static const int GST_STATE_PLAYING = 4; + static const int GST_VIDEO_COLOR_MATRIX_BT2020 = 6; + static const int GST_VIDEO_COLOR_MATRIX_BT601 = 4; + static const int GST_VIDEO_COLOR_MATRIX_BT709 = 3; + static const int GST_VIDEO_COLOR_MATRIX_FCC = 2; + static const int GST_VIDEO_COLOR_MATRIX_RGB = 1; + static const int GST_VIDEO_COLOR_MATRIX_SMPTE240M = 5; + static const int GST_VIDEO_COLOR_MATRIX_UNKNOWN = 0; + static const int GST_VIDEO_COLOR_RANGE_0_255 = 1; + static const int GST_VIDEO_COLOR_RANGE_16_235 = 2; + static const int GST_VIDEO_COLOR_RANGE_UNKNOWN = 0; + static const int GST_VIDEO_TRANSFER_ARIB_STD_B67 = 15; + static const int GST_VIDEO_TRANSFER_SMPTE2084 = 14; + + // A stalled stream fails after longer than souphttpsrc waits before it + // reconnects, which is 15 seconds + static const int DECODE_TIMEOUT_SECONDS = 20; + static const unsigned int MAX_AUDIO_BUFFERS = 4; + static const unsigned int MAX_VIDEO_BUFFERS = 2; + static const int OPEN_TIMEOUT_SECONDS = 20; + static const uint64_t PULL_INTERVAL = 100 * GST_MSECOND; + + // A hardware decoder that fails partway is replaced in software within this + static const int SOFTWARE_REOPEN_SECONDS = 5; + + + struct GStreamerLibrary { + + void (*g_free) (void*); + void* (*g_object_class_find_property) (void*, const char*); + void (*g_object_set) (void*, const char*, ...); + unsigned long (*g_signal_connect_data) (void*, const char*, GCallback, void*, void*, int); + gboolean (*gst_app_sink_is_eos) (GstElement*); + GstSample* (*gst_app_sink_try_pull_sample) (GstElement*, uint64_t); + gboolean (*gst_bin_add) (GstElement*, GstElement*); + GstElement* (*gst_bin_new) (const char*); + gboolean (*gst_bin_remove) (GstElement*, GstElement*); + GstMeta* (*gst_buffer_get_meta) (GstBuffer*, size_t); + gboolean (*gst_buffer_map) (GstBuffer*, GstMapInfo*, int); + void (*gst_buffer_unmap) (GstBuffer*, GstMapInfo*); + GstMessage* (*gst_bus_pop) (GstBus*); + GstMessage* (*gst_bus_pop_filtered) (GstBus*, int); + void (*gst_bus_set_sync_handler) (GstBus*, GstBusSyncHandler, void*, void*); + GstMessage* (*gst_bus_timed_pop_filtered) (GstBus*, uint64_t, int); + GstCaps* (*gst_caps_from_string) (const char*); + unsigned int (*gst_caps_get_size) (const GstCaps*); + GstStructure* (*gst_caps_get_structure) (const GstCaps*, unsigned int); + gboolean (*gst_element_add_pad) (GstElement*, GstPad*); + GstElementFactory* (*gst_element_factory_find) (const char*); + const char* (*gst_element_factory_get_metadata) (GstElementFactory*, const char*); + GList* (*gst_element_factory_list_filter) (GList*, const GstCaps*, int, gboolean); + GList* (*gst_element_factory_list_get_elements) (uint64_t, int); + GstElement* (*gst_element_factory_make) (const char*, const char*); + GstBus* (*gst_element_get_bus) (GstElement*); + GstElementFactory* (*gst_element_get_factory) (GstElement*); + GstPad* (*gst_element_get_static_pad) (GstElement*, const char*); + gboolean (*gst_element_link) (GstElement*, GstElement*); + gboolean (*gst_element_query) (GstElement*, GstQuery*); + gboolean (*gst_element_query_duration) (GstElement*, int, int64_t*); + gboolean (*gst_element_seek_simple) (GstElement*, int, int, int64_t); + int (*gst_element_set_state) (GstElement*, int); + gboolean (*gst_element_sync_state_with_parent) (GstElement*); + void (*gst_event_parse_tag) (GstEvent*, GstTagList**); + char* (*gst_filename_to_uri) (const char*, void**); + GstPad* (*gst_ghost_pad_new) (const char*, GstPad*); + gboolean (*gst_init_check) (int*, char***, void**); + void* (*gst_mini_object_ref) (void*); + void (*gst_mini_object_unref) (void*); + void* (*gst_object_ref_sink) (void*); + void (*gst_object_unref) (void*); + GstCaps* (*gst_pad_get_current_caps) (GstPad*); + GstEvent* (*gst_pad_get_sticky_event) (GstPad*, int, unsigned int); + int (*gst_pad_link) (GstPad*, GstPad*); + gboolean (*gst_pad_query) (GstPad*, GstQuery*); + GstCaps* (*gst_pad_query_caps) (GstPad*, GstCaps*); + GstElement* (*gst_pipeline_new) (const char*); + void (*gst_plugin_feature_list_free) (GList*); + GstQuery* (*gst_query_new_seeking) (int); + void (*gst_query_parse_seeking) (GstQuery*, int*, gboolean*, int64_t*, int64_t*); + GstBuffer* (*gst_sample_get_buffer) (GstSample*); + GstCaps* (*gst_sample_get_caps) (GstSample*); + GstSegment* (*gst_sample_get_segment) (GstSample*); + int (*gst_segment_to_stream_time_full) (const GstSegment*, int, uint64_t, uint64_t*); + gboolean (*gst_structure_get_fraction) (const GstStructure*, const char*, int*, int*); + gboolean (*gst_structure_get_int) (const GstStructure*, const char*, int*); + const char* (*gst_structure_get_name) (const GstStructure*); + const char* (*gst_structure_get_string) (const GstStructure*, const char*); + gboolean (*gst_tag_list_get_string) (const GstTagList*, const char*, char**); + size_t (*gst_video_meta_api_get_type) (); + + }; + + + static GStreamerLibrary gst; + static std::once_flag loadFlag; + static bool loaded = false; + static size_t videoMetaAPI = 0; + + + static bool LoadGStreamer () { + + std::call_once (loadFlag, [] { + + void* glib = dlopen ("libglib-2.0.so.0", RTLD_NOW | RTLD_LOCAL); + void* gobject = dlopen ("libgobject-2.0.so.0", RTLD_NOW | RTLD_LOCAL); + void* gstreamer = dlopen ("libgstreamer-1.0.so.0", RTLD_NOW | RTLD_LOCAL); + void* app = dlopen ("libgstapp-1.0.so.0", RTLD_NOW | RTLD_LOCAL); + + if (!glib || !gobject || !gstreamer || !app) return; + + #define LIME_GST_REQUIRE(library, name) gst.name = (decltype (gst.name))dlsym (library, #name); if (!gst.name) return; + + LIME_GST_REQUIRE (glib, g_free); + LIME_GST_REQUIRE (gobject, g_object_class_find_property); + LIME_GST_REQUIRE (gobject, g_object_set); + LIME_GST_REQUIRE (gobject, g_signal_connect_data); + LIME_GST_REQUIRE (app, gst_app_sink_is_eos); + LIME_GST_REQUIRE (app, gst_app_sink_try_pull_sample); + LIME_GST_REQUIRE (gstreamer, gst_bin_add); + LIME_GST_REQUIRE (gstreamer, gst_bin_new); + LIME_GST_REQUIRE (gstreamer, gst_bin_remove); + LIME_GST_REQUIRE (gstreamer, gst_buffer_get_meta); + LIME_GST_REQUIRE (gstreamer, gst_buffer_map); + LIME_GST_REQUIRE (gstreamer, gst_buffer_unmap); + LIME_GST_REQUIRE (gstreamer, gst_bus_pop); + LIME_GST_REQUIRE (gstreamer, gst_bus_pop_filtered); + LIME_GST_REQUIRE (gstreamer, gst_bus_set_sync_handler); + LIME_GST_REQUIRE (gstreamer, gst_bus_timed_pop_filtered); + LIME_GST_REQUIRE (gstreamer, gst_caps_from_string); + LIME_GST_REQUIRE (gstreamer, gst_caps_get_size); + LIME_GST_REQUIRE (gstreamer, gst_caps_get_structure); + LIME_GST_REQUIRE (gstreamer, gst_element_add_pad); + LIME_GST_REQUIRE (gstreamer, gst_element_factory_find); + LIME_GST_REQUIRE (gstreamer, gst_element_factory_get_metadata); + LIME_GST_REQUIRE (gstreamer, gst_element_factory_list_filter); + LIME_GST_REQUIRE (gstreamer, gst_element_factory_list_get_elements); + LIME_GST_REQUIRE (gstreamer, gst_element_factory_make); + LIME_GST_REQUIRE (gstreamer, gst_element_get_bus); + LIME_GST_REQUIRE (gstreamer, gst_element_get_factory); + LIME_GST_REQUIRE (gstreamer, gst_element_get_static_pad); + LIME_GST_REQUIRE (gstreamer, gst_element_link); + LIME_GST_REQUIRE (gstreamer, gst_element_query_duration); + LIME_GST_REQUIRE (gstreamer, gst_element_seek_simple); + LIME_GST_REQUIRE (gstreamer, gst_element_set_state); + LIME_GST_REQUIRE (gstreamer, gst_element_sync_state_with_parent); + LIME_GST_REQUIRE (gstreamer, gst_filename_to_uri); + LIME_GST_REQUIRE (gstreamer, gst_ghost_pad_new); + LIME_GST_REQUIRE (gstreamer, gst_init_check); + LIME_GST_REQUIRE (gstreamer, gst_mini_object_ref); + LIME_GST_REQUIRE (gstreamer, gst_mini_object_unref); + LIME_GST_REQUIRE (gstreamer, gst_object_ref_sink); + LIME_GST_REQUIRE (gstreamer, gst_object_unref); + LIME_GST_REQUIRE (gstreamer, gst_pad_get_current_caps); + LIME_GST_REQUIRE (gstreamer, gst_pad_link); + LIME_GST_REQUIRE (gstreamer, gst_pad_query); + LIME_GST_REQUIRE (gstreamer, gst_pad_query_caps); + LIME_GST_REQUIRE (gstreamer, gst_pipeline_new); + LIME_GST_REQUIRE (gstreamer, gst_plugin_feature_list_free); + LIME_GST_REQUIRE (gstreamer, gst_sample_get_buffer); + LIME_GST_REQUIRE (gstreamer, gst_sample_get_caps); + LIME_GST_REQUIRE (gstreamer, gst_sample_get_segment); + LIME_GST_REQUIRE (gstreamer, gst_segment_to_stream_time_full); + LIME_GST_REQUIRE (gstreamer, gst_structure_get_fraction); + LIME_GST_REQUIRE (gstreamer, gst_structure_get_int); + LIME_GST_REQUIRE (gstreamer, gst_structure_get_name); + LIME_GST_REQUIRE (gstreamer, gst_structure_get_string); + + #undef LIME_GST_REQUIRE + + // Tags give the rotation, and queries whether a stream can seek, where + // the library has them + #define LIME_GST_OPTIONAL(library, name) gst.name = (decltype (gst.name))dlsym (library, #name); + + LIME_GST_OPTIONAL (gstreamer, gst_element_query); + LIME_GST_OPTIONAL (gstreamer, gst_event_parse_tag); + LIME_GST_OPTIONAL (gstreamer, gst_pad_get_sticky_event); + LIME_GST_OPTIONAL (gstreamer, gst_query_new_seeking); + LIME_GST_OPTIONAL (gstreamer, gst_query_parse_seeking); + LIME_GST_OPTIONAL (gstreamer, gst_tag_list_get_string); + + #undef LIME_GST_OPTIONAL + + // Video metas describe padded frames, and are read when available + void* video = dlopen ("libgstvideo-1.0.so.0", RTLD_NOW | RTLD_LOCAL); + if (video) gst.gst_video_meta_api_get_type = (decltype (gst.gst_video_meta_api_get_type))dlsym (video, "gst_video_meta_api_get_type"); + + if (!gst.gst_init_check (NULL, NULL, NULL)) return; + + // The elements come from the core, base and app plugins + static const char* factories[] = { "appsink", "audioconvert", "fakesink", "uridecodebin", "videoconvert" }; + + for (size_t i = 0; i < sizeof (factories) / sizeof (factories[0]); i++) { + + GstElementFactory* factory = gst.gst_element_factory_find (factories[i]); + if (!factory) return; + gst.gst_object_unref (factory); + + } + + if (gst.gst_video_meta_api_get_type) videoMetaAPI = gst.gst_video_meta_api_get_type (); + + loaded = true; + + }); + + return loaded; + + } + + + static GstElement* AddElement (GstElement* bin, const char* factory) { + + GstElement* element = gst.gst_element_factory_make (factory, NULL); + if (element) gst.gst_bin_add (bin, element); + return element; + + } + + + // GStreamer writes the predefined colorimetries by name, and the others as + // range:matrix:transfer:primaries + static void GetColorimetry (const char* colorimetry, int* range, int* matrix, int* transfer) { + + static const struct { const char* name; int range; int matrix; int transfer; } names[] = { + + { "bt2020", GST_VIDEO_COLOR_RANGE_16_235, GST_VIDEO_COLOR_MATRIX_BT2020, 0 }, + { "bt2020-10", GST_VIDEO_COLOR_RANGE_16_235, GST_VIDEO_COLOR_MATRIX_BT2020, 0 }, + { "bt2100-hlg", GST_VIDEO_COLOR_RANGE_16_235, GST_VIDEO_COLOR_MATRIX_BT2020, GST_VIDEO_TRANSFER_ARIB_STD_B67 }, + { "bt2100-pq", GST_VIDEO_COLOR_RANGE_16_235, GST_VIDEO_COLOR_MATRIX_BT2020, GST_VIDEO_TRANSFER_SMPTE2084 }, + { "bt601", GST_VIDEO_COLOR_RANGE_16_235, GST_VIDEO_COLOR_MATRIX_BT601, 0 }, + { "bt709", GST_VIDEO_COLOR_RANGE_16_235, GST_VIDEO_COLOR_MATRIX_BT709, 0 }, + { "smpte240m", GST_VIDEO_COLOR_RANGE_16_235, GST_VIDEO_COLOR_MATRIX_SMPTE240M, 0 }, + { "sRGB", GST_VIDEO_COLOR_RANGE_0_255, GST_VIDEO_COLOR_MATRIX_RGB, 0 } + + }; + + *range = GST_VIDEO_COLOR_RANGE_UNKNOWN; + *matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN; + *transfer = 0; + + if (!colorimetry) return; + + for (size_t i = 0; i < sizeof (names) / sizeof (names[0]); i++) { + + if (strcmp (colorimetry, names[i].name) == 0) { + + *range = names[i].range; + *matrix = names[i].matrix; + *transfer = names[i].transfer; + return; + + } + + } + + // Parsed by hand, since glibc 2.38 builds link sscanf to a symbol that + // older systems lack + const char* matrixText = strchr (colorimetry, ':'); + if (!matrixText) return; + + for (const char* c = colorimetry; *c >= '0' && *c <= '9'; c++) *range = *range * 10 + (*c - '0'); + for (const char* c = matrixText + 1; *c >= '0' && *c <= '9'; c++) *matrix = *matrix * 10 + (*c - '0'); + + const char* transferText = strchr (matrixText + 1, ':'); + if (transferText) for (const char* c = transferText + 1; *c >= '0' && *c <= '9'; c++) *transfer = *transfer * 10 + (*c - '0'); + + } + + + static GstStructure* GetStructure (GstCaps* caps) { + + return (caps && gst.gst_caps_get_size (caps) > 0) ? gst.gst_caps_get_structure (caps, 0) : NULL; + + } + + + static double GetTime (GstSample* sample, double position) { + + GstBuffer* buffer = gst.gst_sample_get_buffer (sample); + if (!buffer || buffer->pts == GST_CLOCK_TIME_NONE) return position; + + // Stream time applies edit lists, and is the time that seeks use + GstSegment* segment = gst.gst_sample_get_segment (sample); + uint64_t time = 0; + int sign = segment ? gst.gst_segment_to_stream_time_full (segment, GST_FORMAT_TIME, buffer->pts, &time) : 0; + + if (sign == 0) return (double)buffer->pts / GST_SECOND; + return sign > 0 ? (double)time / GST_SECOND : -(double)time / GST_SECOND; + + } + + + static std::string GetURI (const char* path) { + + if (strstr (path, "://")) return path; + + char* uri = gst.gst_filename_to_uri (path, NULL); + if (!uri) return std::string (); + + std::string result (uri); + gst.g_free (uri); + return result; + + } + + + static bool HasClass (GstElementFactory* factory, const char* name) { + + const char* klass = factory ? gst.gst_element_factory_get_metadata (factory, "klass") : NULL; + return klass && strstr (klass, name); + + } + + + static bool IsAudio (const char* mediaType) { + + return strncmp (mediaType, "audio/", 6) == 0; + + } + + + static bool IsSubtitle (const char* mediaType) { + + static const char* prefixes[] = { "text/", "subpicture/", "closedcaption/", "application/x-ass", "application/x-ssa", "application/x-sub", "application/x-teletext", "application/x-kate", "application/x-usf", "application/ttml" }; + + for (size_t i = 0; i < sizeof (prefixes) / sizeof (prefixes[0]); i++) { + + if (strncmp (mediaType, prefixes[i], strlen (prefixes[i])) == 0) return true; + + } + + return false; + + } + + + static bool IsVideo (const char* mediaType) { + + return strncmp (mediaType, "video/", 6) == 0 || strncmp (mediaType, "image/", 6) == 0; + + } + + + // One pipeline decoding one stream type. The converter and appsink wait in + // a bin until uridecodebin exposes a raw stream of that type. + class GStreamerStream { + + + public: + + GStreamerStream (); + ~GStreamerStream (); + + void CancelSeek (); + void Close (); + GstCaps* GetCaps (); + double GetDuration (); + int GetRotation (); + void Interrupt (); + bool IsInterrupted (); + bool IsSeekable (); + bool Open (const char* uri, bool video, bool hardware, std::chrono::steady_clock::time_point deadline); + void Play (); + void PrepareSeek (); + VideoDecodeResult Pull (GstSample** sample); + bool Seek (double time); + void SetCaps (const char* caps); + + std::atomic foundAudio; + std::atomic foundUnsupported; + GstElement* pipeline; + std::atomic triedHardware; + + private: + + static gboolean AutoplugContinue (GstElement* decodeBin, GstPad* pad, GstCaps* caps, void* data); + static gboolean AutoplugQuery (GstElement* decodeBin, GstPad* pad, GstElement* element, GstQuery* query, void* data); + static int AutoplugSelect (GstElement* decodeBin, GstPad* pad, GstCaps* caps, GstElementFactory* factory, void* data); + static int BusSync (GstBus* bus, GstMessage* message, void* data); + static void ElementAdded (GstElement* pipeline, GstElement* bin, GstElement* element, void* data); + static void PadAdded (GstElement* decodeBin, GstPad* pad, void* data); + static void UnknownType (GstElement* decodeBin, GstPad* pad, GstCaps* caps, void* data); + + void FlushBus (); + bool Preroll (std::chrono::steady_clock::time_point deadline); + + GstElement* appSink; + GstElement* bin; + GstBus* bus; + bool hardware; + std::atomic interrupted; + std::atomic linked; + std::atomic seeking; + bool video; + + + }; + + + GStreamerStream::GStreamerStream () { + + appSink = NULL; + bin = NULL; + bus = NULL; + foundAudio = false; + foundUnsupported = false; + hardware = false; + interrupted = false; + linked = false; + pipeline = NULL; + seeking = false; + triedHardware = false; + video = false; + + } + + + GStreamerStream::~GStreamerStream () { + + Close (); + + } + + + gboolean GStreamerStream::AutoplugContinue (GstElement* decodeBin, GstPad* pad, GstCaps* caps, void* data) { + + GStreamerStream* stream = (GStreamerStream*)data; + + GstStructure* structure = GetStructure (caps); + const char* name = structure ? gst.gst_structure_get_name (structure) : ""; + bool audio = IsAudio (name); + bool video = IsVideo (name); + + // Subtitles and captions are exposed undecoded, so a missing or failing + // decoder for them does not stop the video + if (IsSubtitle (name)) return false; + if (!audio && !video) return true; + + // Containers such as video/quicktime are demuxed further. Elementary + // streams of the other type are exposed undecoded, so each pipeline + // only decodes its own stream. + GList* demuxers = gst.gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_DEMUXER, GST_RANK_MARGINAL); + GList* matches = gst.gst_element_factory_list_filter (demuxers, caps, GST_PAD_SINK, false); + bool container = (matches != NULL); + gst.gst_plugin_feature_list_free (matches); + gst.gst_plugin_feature_list_free (demuxers); + + if (container) return true; + + if (audio) stream->foundAudio = true; + + return stream->video ? !audio : !video; + + } + + + gboolean GStreamerStream::AutoplugQuery (GstElement* decodeBin, GstPad* pad, GstElement* element, GstQuery* query, void* data) { + + GStreamerStream* stream = (GStreamerStream*)data; + + // Decoders choose their output before decodebin exposes them, so answer + // with what the converter reads. Hardware decoders would otherwise + // choose video memory that it cannot map. + if (query->type != GST_QUERY_CAPS) return false; + + GstElementFactory* factory = gst.gst_element_get_factory (element); + if (!HasClass (factory, "Decoder") || !HasClass (factory, stream->video ? "Video" : "Audio")) return false; + + GstPad* sinkPad = gst.gst_element_get_static_pad (stream->bin, "sink"); + gboolean result = gst.gst_pad_query (sinkPad, query); + gst.gst_object_unref (sinkPad); + return result; + + } + + + int GStreamerStream::AutoplugSelect (GstElement* decodeBin, GstPad* pad, GstCaps* caps, GstElementFactory* factory, void* data) { + + GStreamerStream* stream = (GStreamerStream*)data; + + // Decoders are chosen by rank, so hardware decoding can only be turned off + if (!HasClass (factory, "Hardware")) return GST_AUTOPLUG_SELECT_TRY; + if (!stream->hardware) return GST_AUTOPLUG_SELECT_SKIP; + + stream->triedHardware = true; + return GST_AUTOPLUG_SELECT_TRY; + + } + + + int GStreamerStream::BusSync (GstBus* bus, GstMessage* message, void* data) { + + // Only errors and prerolling are read, so nothing else is queued. A + // sync handler owns the messages that it drops. + if (message->type == GST_MESSAGE_ERROR || message->type == GST_MESSAGE_ASYNC_DONE) return GST_BUS_PASS; + + gst.gst_mini_object_unref (message); + return GST_BUS_DROP; + + } + + + void GStreamerStream::Close () { + + if (pipeline) gst.gst_element_set_state (pipeline, GST_STATE_NULL); + + if (bus) { + + // Queued messages hold references to the pipeline + FlushBus (); + gst.gst_object_unref (bus); + bus = NULL; + + } + + if (pipeline) { + + gst.gst_object_unref (pipeline); + pipeline = NULL; + + } + + if (bin) { + + gst.gst_object_unref (bin); + bin = NULL; + + } + + appSink = NULL; + seeking = false; + + } + + + void GStreamerStream::ElementAdded (GstElement* pipeline, GstElement* bin, GstElement* element, void* data) { + + // libav decoders write into buffers that GStreamer allocates. When a + // seek changes the output size while their frame threads still hold + // buffers, GStreamer 1.24 fails an assertion and aborts the process. + // Copying each decoded frame instead avoids that path. + if (gst.g_object_class_find_property (((GTypeInstance*)element)->g_class, "direct-rendering")) { + + gst.g_object_set (element, "direct-rendering", false, NULL); + + } + + } + + + void GStreamerStream::FlushBus () { + + GstMessage* message; + while ((message = gst.gst_bus_pop (bus))) gst.gst_mini_object_unref (message); + + } + + + GstCaps* GStreamerStream::GetCaps () { + + GstPad* pad = gst.gst_element_get_static_pad (appSink, "sink"); + GstCaps* caps = gst.gst_pad_get_current_caps (pad); + gst.gst_object_unref (pad); + return caps; + + } + + + double GStreamerStream::GetDuration () { + + int64_t duration = 0; + return (gst.gst_element_query_duration (pipeline, GST_FORMAT_TIME, &duration) && duration > 0) ? (double)duration / GST_SECOND : 0; + + } + + + int GStreamerStream::GetRotation () { + + if (!appSink || !gst.gst_pad_get_sticky_event || !gst.gst_event_parse_tag || !gst.gst_tag_list_get_string) return 0; + + // Demuxers tag a stream with the turn that shows it upright, such as + // rotate-90 for a quarter clockwise, and the tags follow it to the sink. + // Flipped orientations are taken as their rotation. + GstPad* pad = gst.gst_element_get_static_pad (appSink, "sink"); + int rotation = 0; + + for (unsigned int i = 0; i < 4; i++) { + + GstEvent* event = gst.gst_pad_get_sticky_event (pad, GST_EVENT_TAG, i); + if (!event) break; + + GstTagList* tags = NULL; + char* orientation = NULL; + gst.gst_event_parse_tag (event, &tags); + + if (tags && gst.gst_tag_list_get_string (tags, "image-orientation", &orientation)) { + + const char* turn = orientation ? strstr (orientation, "rotate-") : NULL; + if (turn) rotation = atoi (turn + 7); + gst.g_free (orientation); + + } + + gst.gst_mini_object_unref (event); + + } + + gst.gst_object_unref (pad); + return rotation; + + } + + + bool GStreamerStream::IsInterrupted () { + + return interrupted; + + } + + + bool GStreamerStream::IsSeekable () { + + // A stream that says it cannot seek, such as a live one, is not asked to. + // One that does not say is tried. + if (!pipeline || !gst.gst_query_new_seeking || !gst.gst_element_query || !gst.gst_query_parse_seeking) return true; + + GstQuery* query = gst.gst_query_new_seeking (GST_FORMAT_TIME); + if (!query) return true; + + gboolean seekable = true; + if (gst.gst_element_query (pipeline, query)) gst.gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL); + gst.gst_mini_object_unref (query); + + return seekable != 0; + + } + + + bool GStreamerStream::Open (const char* uri, bool video, bool hardware, std::chrono::steady_clock::time_point deadline) { + + Close (); + + foundAudio = false; + foundUnsupported = false; + this->hardware = hardware; + interrupted = false; + linked = false; + seeking = false; + triedHardware = false; + this->video = video; + + pipeline = (GstElement*)gst.gst_object_ref_sink (gst.gst_pipeline_new (NULL)); + bin = (GstElement*)gst.gst_object_ref_sink (gst.gst_bin_new (NULL)); + bus = gst.gst_element_get_bus (pipeline); + gst.gst_bus_set_sync_handler (bus, BusSync, NULL, NULL); + gst.g_signal_connect_data (pipeline, "deep-element-added", (GCallback)ElementAdded, this, NULL, 0); + + GstElement* decodeBin = AddElement (pipeline, "uridecodebin"); + GstElement* convert = AddElement (bin, video ? "videoconvert" : "audioconvert"); + + // Interlaced video is deinterlaced, after conversion so the deinterlacer + // does not limit the formats decoders can give, and sound is resampled + // once its format is fixed. Progressive video and sound at that format + // pass through, and either is left out where its plugin is missing. + GstElement* adjust = AddElement (bin, video ? "deinterlace" : "audioresample"); + appSink = AddElement (bin, "appsink"); + + if (!decodeBin || !convert || !appSink) { + + Close (); + return false; + + } + + if (adjust && !(gst.gst_element_link (convert, adjust) && gst.gst_element_link (adjust, appSink))) { + + gst.gst_bin_remove (bin, adjust); + adjust = NULL; + + } + + if (!adjust && !gst.gst_element_link (convert, appSink)) { + + Close (); + return false; + + } + + if (video && gst.g_object_class_find_property (((GTypeInstance*)convert)->g_class, "n-threads")) { + + // Converting 10-bit video to 8-bit uses every core + gst.g_object_set (convert, "n-threads", 0, NULL); + + } + + GstPad* convertPad = gst.gst_element_get_static_pad (convert, "sink"); + gst.gst_element_add_pad (bin, gst.gst_ghost_pad_new ("sink", convertPad)); + gst.gst_object_unref (convertPad); + + // audioconvert downmixes to stereo at the source rate. Frames are taken + // as NV12, or as the I420 that software decoders give, without repacking. + GstCaps* caps = gst.gst_caps_from_string (video ? "video/x-raw,format=(string){ NV12, I420 }" : "audio/x-raw,format=S16LE,layout=interleaved,channels=[1,2]"); + gst.g_object_set (appSink, "caps", caps, "drop", false, "enable-last-sample", false, "max-buffers", video ? MAX_VIDEO_BUFFERS : MAX_AUDIO_BUFFERS, "sync", false, NULL); + gst.gst_mini_object_unref (caps); + + gst.g_object_set (decodeBin, "uri", uri, NULL); + gst.g_signal_connect_data (decodeBin, "autoplug-continue", (GCallback)AutoplugContinue, this, NULL, 0); + gst.g_signal_connect_data (decodeBin, "autoplug-query", (GCallback)AutoplugQuery, this, NULL, 0); + gst.g_signal_connect_data (decodeBin, "autoplug-select", (GCallback)AutoplugSelect, this, NULL, 0); + gst.g_signal_connect_data (decodeBin, "pad-added", (GCallback)PadAdded, this, NULL, 0); + gst.g_signal_connect_data (decodeBin, "unknown-type", (GCallback)UnknownType, this, NULL, 0); + + if (!Preroll (deadline) || !linked) { + + Close (); + return false; + + } + + return true; + + } + + + void GStreamerStream::PadAdded (GstElement* decodeBin, GstPad* pad, void* data) { + + GStreamerStream* stream = (GStreamerStream*)data; + + GstCaps* caps = gst.gst_pad_get_current_caps (pad); + if (!caps) caps = gst.gst_pad_query_caps (pad, NULL); + + GstStructure* structure = GetStructure (caps); + const char* name = structure ? gst.gst_structure_get_name (structure) : ""; + bool raw = strcmp (name, stream->video ? "video/x-raw" : "audio/x-raw") == 0; + + if (IsAudio (name)) stream->foundAudio = true; + + if (caps) gst.gst_mini_object_unref (caps); + + if (raw && !stream->linked && gst.gst_bin_add (stream->pipeline, stream->bin)) { + + GstPad* sinkPad = gst.gst_element_get_static_pad (stream->bin, "sink"); + bool linked = (gst.gst_pad_link (pad, sinkPad) == GST_PAD_LINK_OK); + gst.gst_object_unref (sinkPad); + + if (linked) { + + gst.gst_element_sync_state_with_parent (stream->bin); + stream->linked = true; + return; + + } + + gst.gst_bin_remove (stream->pipeline, stream->bin); + + } + + // Discard the other streams + GstElement* sink = gst.gst_element_factory_make ("fakesink", NULL); + if (!sink) return; + + gst.g_object_set (sink, "async", false, "enable-last-sample", false, "sync", false, NULL); + gst.gst_bin_add (stream->pipeline, sink); + + GstPad* sinkPad = gst.gst_element_get_static_pad (sink, "sink"); + gst.gst_pad_link (pad, sinkPad); + gst.gst_object_unref (sinkPad); + + gst.gst_element_sync_state_with_parent (sink); + + } + + + void GStreamerStream::CancelSeek () { + + seeking = false; + + } + + + void GStreamerStream::Play () { + + gst.gst_element_set_state (pipeline, GST_STATE_PLAYING); + + } + + + void GStreamerStream::PrepareSeek () { + + seeking = true; + + } + + + bool GStreamerStream::Preroll (std::chrono::steady_clock::time_point deadline) { + + int result = gst.gst_element_set_state (pipeline, GST_STATE_PAUSED); + + if (result == GST_STATE_CHANGE_FAILURE) return false; + + if (result == GST_STATE_CHANGE_NO_PREROLL) { + + // A live source, such as RTSP, only produces data while playing, so it + // plays until its stream is linked and has a format + if (gst.gst_element_set_state (pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) return false; + + while (std::chrono::steady_clock::now () < deadline) { + + GstMessage* message = gst.gst_bus_pop_filtered (bus, GST_MESSAGE_ERROR); + + if (message) { + + gst.gst_mini_object_unref (message); + return false; + + } + + if (linked) { + + GstCaps* caps = GetCaps (); + + if (caps) { + + gst.gst_mini_object_unref (caps); + return true; + + } + + } + + std::this_thread::sleep_for (std::chrono::milliseconds (10)); + + } + + return false; + + } + + if (result != GST_STATE_CHANGE_ASYNC) return true; + + // An error does not end the pending state change, so wait for either + int64_t timeout = std::chrono::duration_cast (deadline - std::chrono::steady_clock::now ()).count (); + GstMessage* message = gst.gst_bus_timed_pop_filtered (bus, timeout > 0 ? (uint64_t)timeout : 0, GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR); + if (!message) return false; + + bool prerolled = (message->type == GST_MESSAGE_ASYNC_DONE); + gst.gst_mini_object_unref (message); + return prerolled; + + } + + + VideoDecodeResult GStreamerStream::Pull (GstSample** sample) { + + std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now () + std::chrono::seconds (DECODE_TIMEOUT_SECONDS); + + while (true) { + + *sample = gst.gst_app_sink_try_pull_sample (appSink, PULL_INTERVAL); + if (*sample) return VIDEO_DECODE_OK; + + if (interrupted) return VIDEO_DECODE_ERROR; + + // A seek is waiting for this stream, which can be stalled on a network + if (seeking) return VIDEO_DECODE_AGAIN; + + if (gst.gst_app_sink_is_eos (appSink)) return VIDEO_DECODE_END; + + // An error stops the stream without an end of stream + GstMessage* message = gst.gst_bus_pop_filtered (bus, GST_MESSAGE_ERROR); + + if (message) { + + gst.gst_mini_object_unref (message); + return VIDEO_DECODE_ERROR; + + } + + if (std::chrono::steady_clock::now () >= deadline) return VIDEO_DECODE_ERROR; + + } + + } + + + void GStreamerStream::Interrupt () { + + interrupted = true; + + } + + + bool GStreamerStream::Seek (double time) { + + // A flushing seek restarts a stream that stopped with an error + seeking = false; + FlushBus (); + + int flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_BEFORE; + return gst.gst_element_seek_simple (pipeline, GST_FORMAT_TIME, flags, (int64_t)(time * GST_SECOND)); + + } + + + void GStreamerStream::SetCaps (const char* caps) { + + GstCaps* value = gst.gst_caps_from_string (caps); + if (!value) return; + + gst.g_object_set (appSink, "caps", value, NULL); + gst.gst_mini_object_unref (value); + + } + + + void GStreamerStream::UnknownType (GstElement* decodeBin, GstPad* pad, GstCaps* caps, void* data) { + + GStreamerStream* stream = (GStreamerStream*)data; + + // A stream that nothing installed can decode, such as one in a codec + // whose plugin is missing + GstStructure* structure = GetStructure (caps); + const char* name = structure ? gst.gst_structure_get_name (structure) : ""; + + if (IsAudio (name)) stream->foundAudio = true; + if (stream->video ? IsVideo (name) : IsAudio (name)) stream->foundUnsupported = true; + + } + + + class GStreamerVideoBackend : public VideoBackend { + + + public: + + GStreamerVideoBackend (); + ~GStreamerVideoBackend (); + + virtual void Close (); + virtual VideoDecodeResult DecodeAudio (std::vector* pcm, double* time); + virtual VideoDecodeResult DecodeVideo (VideoPlanes* planes, double* time, double* duration); + virtual void Interrupt (); + virtual bool Open (const char* path, bool hardwareDecoding, VideoStreamInfo* info); + virtual void PrepareSeek (); + virtual bool Seek (double time); + + private: + + void ReadVideoFormat (GstCaps* caps); + bool ReopenVideoInSoftware (); + void UnmapVideo (); + + GStreamerStream audio; + int audioChannels; + double audioPosition; + int audioSampleRate; + VideoColorMatrix colorMatrix; + VideoColorTransfer colorTransfer; + double frameRate; + bool fullRange; + int height; + std::atomic interrupted; + double lastFrameTime; + double pixelAspectRatio; + bool planar; + double skipUntil; + std::string uri; + GStreamerStream video; + GstCaps* videoCaps; + GstMapInfo videoMap; + double videoPosition; + GstSample* videoSample; + int width; + + + }; + + + GStreamerVideoBackend::GStreamerVideoBackend () { + + audioChannels = 0; + audioPosition = 0; + audioSampleRate = 0; + colorMatrix = VIDEO_COLOR_MATRIX_BT709; + colorTransfer = VIDEO_COLOR_TRANSFER_SDR; + frameRate = 0; + fullRange = false; + height = 0; + interrupted = false; + lastFrameTime = -1; + pixelAspectRatio = 0; + planar = false; + skipUntil = -1; + videoCaps = NULL; + memset (&videoMap, 0, sizeof (videoMap)); + videoPosition = 0; + videoSample = NULL; + width = 0; + + } + + + GStreamerVideoBackend::~GStreamerVideoBackend () { + + Close (); + + } + + + void GStreamerVideoBackend::Close () { + + UnmapVideo (); + + video.Close (); + audio.Close (); + + if (videoCaps) { + + gst.gst_mini_object_unref (videoCaps); + videoCaps = NULL; + + } + + audioChannels = 0; + audioPosition = 0; + audioSampleRate = 0; + frameRate = 0; + height = 0; + lastFrameTime = -1; + pixelAspectRatio = 0; + planar = false; + skipUntil = -1; + videoPosition = 0; + width = 0; + + } + + + VideoDecodeResult GStreamerVideoBackend::DecodeAudio (std::vector* pcm, double* time) { + + if (!audio.pipeline) return VIDEO_DECODE_END; + + GstSample* sample = NULL; + VideoDecodeResult result = audio.Pull (&sample); + if (result != VIDEO_DECODE_OK) return result; + + GstBuffer* buffer = gst.gst_sample_get_buffer (sample); + GstMapInfo map; + + if (!buffer || !gst.gst_buffer_map (buffer, &map, GST_MAP_READ)) { + + gst.gst_mini_object_unref (sample); + return VIDEO_DECODE_ERROR; + + } + + *time = GetTime (sample, audioPosition); + pcm->insert (pcm->end (), map.data, map.data + map.size); + audioPosition = *time + (double)map.size / (audioSampleRate * audioChannels * 2); + + gst.gst_buffer_unmap (buffer, &map); + gst.gst_mini_object_unref (sample); + + return VIDEO_DECODE_OK; + + } + + + VideoDecodeResult GStreamerVideoBackend::DecodeVideo (VideoPlanes* planes, double* time, double* duration) { + + UnmapVideo (); + + if (!video.pipeline) return VIDEO_DECODE_END; + + GstSample* sample = NULL; + VideoDecodeResult result; + + while (true) { + + result = video.Pull (&sample); + + // A hardware decoder can fail on a stream it accepted, such as after a + // seek or a change of size, so decoding carries on in software + if (result == VIDEO_DECODE_ERROR && video.triedHardware && !video.IsInterrupted () && ReopenVideoInSoftware ()) continue; + + if (result != VIDEO_DECODE_OK) return result; + + // After reopening, the frames up to the last one returned are skipped + if (skipUntil >= 0) { + + double time = GetTime (sample, videoPosition); + + if (time <= skipUntil + 0.0001) { + + gst.gst_mini_object_unref (sample); + continue; + + } + + skipUntil = -1; + + } + + break; + + } + + GstCaps* caps = gst.gst_sample_get_caps (sample); + + if (caps && caps != videoCaps) { + + if (videoCaps) gst.gst_mini_object_unref (videoCaps); + videoCaps = (GstCaps*)gst.gst_mini_object_ref (caps); + ReadVideoFormat (videoCaps); + + } + + GstBuffer* buffer = gst.gst_sample_get_buffer (sample); + + if (!buffer || !gst.gst_buffer_map (buffer, &videoMap, GST_MAP_READ)) { + + gst.gst_mini_object_unref (sample); + return VIDEO_DECODE_ERROR; + + } + + videoSample = sample; + + // Without a video meta the frame has GStreamer's default layout: NV12 + // with interleaved chroma, or I420 with a plane for each + GstVideoMeta* meta = videoMetaAPI ? (GstVideoMeta*)gst.gst_buffer_get_meta (buffer, videoMetaAPI) : NULL; + size_t offsets[3] = { 0, 0, 0 }; + int strides[3] = { 0, 0, 0 }; + unsigned int planeCount = planar ? 3 : 2; + + if (meta && meta->n_planes == planeCount) { + + for (unsigned int i = 0; i < planeCount; i++) { + + offsets[i] = meta->offset[i]; + strides[i] = meta->stride[i]; + + } + + } else if (planar) { + + strides[0] = (width + 3) & ~3; + strides[1] = ((((width + 1) & ~1) / 2) + 3) & ~3; + strides[2] = strides[1]; + offsets[1] = (size_t)strides[0] * ((height + 1) & ~1); + offsets[2] = offsets[1] + (size_t)strides[1] * (((height + 1) & ~1) / 2); + + } else { + + strides[0] = (width + 3) & ~3; + strides[1] = strides[0]; + offsets[1] = (size_t)strides[0] * ((height + 1) & ~1); + + } + + int chromaHeight = (height + 1) / 2; + int chromaRow = planar ? (width + 1) / 2 : ((width + 1) / 2) * 2; + bool valid = width > 0 && height > 0 && strides[0] >= width && strides[1] >= chromaRow + && offsets[0] + (size_t)strides[0] * (height - 1) + width <= videoMap.size + && offsets[1] + (size_t)strides[1] * (chromaHeight - 1) + chromaRow <= videoMap.size; + + // Both chroma planes share a stride + if (planar) valid = valid && strides[2] == strides[1] && offsets[2] + (size_t)strides[2] * (chromaHeight - 1) + chromaRow <= videoMap.size; + + if (!valid) { + + UnmapVideo (); + return VIDEO_DECODE_ERROR; + + } + + planes->colorMatrix = colorMatrix; + planes->fullRange = fullRange; + planes->transfer = colorTransfer; + planes->width = width; + planes->height = height; + planes->y = videoMap.data + offsets[0]; + planes->yStride = strides[0]; + planes->u = videoMap.data + offsets[1]; + planes->v = planar ? videoMap.data + offsets[2] : planes->u + 1; + planes->uvStride = strides[1]; + planes->uvPixelStride = planar ? 1 : 2; + + // A frame without a timestamp follows the one before it. Without a + // duration or frame rate either, frames are spaced as at 30 fps, as frames + // that all had the same time would play as fast as they are decoded. + *time = GetTime (sample, videoPosition); + *duration = buffer->duration != GST_CLOCK_TIME_NONE ? (double)buffer->duration / GST_SECOND : (frameRate > 0 ? 1.0 / frameRate : 1.0 / 30); + lastFrameTime = *time; + videoPosition = *time + *duration; + + return VIDEO_DECODE_OK; + + } + + + void GStreamerVideoBackend::Interrupt () { + + // Kept for the backend too, as reopening the video in software starts a + // new stream, which would not know it was interrupted + interrupted = true; + audio.Interrupt (); + video.Interrupt (); + + } + + + bool GStreamerVideoBackend::Open (const char* path, bool hardwareDecoding, VideoStreamInfo* info) { + + Close (); + interrupted = false; + + if (!path || !path[0]) return false; + + uri = GetURI (path); + if (uri.empty ()) return false; + + // The pipelines share one timeout, so a stalled source fails once. The + // video pipeline also finds the audio stream, so a file without one is + // only opened once. A hardware decoder can fail on a stream it + // accepted, so that is retried in software. + std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now () + std::chrono::seconds (OPEN_TIMEOUT_SECONDS); + + if (!video.Open (uri.c_str (), true, hardwareDecoding, deadline) && video.triedHardware) video.Open (uri.c_str (), true, false, deadline); + if (video.foundAudio) audio.Open (uri.c_str (), false, hardwareDecoding, deadline); + + if (video.pipeline) { + + videoCaps = video.GetCaps (); + ReadVideoFormat (videoCaps); + if (width <= 0 || height <= 0) video.Close (); + + } + + // A stream that was found but cannot be decoded, such as one in a codec + // whose plugin is not installed + info->videoUnsupported = !video.pipeline && video.foundUnsupported; + info->audioUnsupported = video.foundAudio && !audio.pipeline && audio.foundUnsupported; + + if (audio.pipeline) { + + GstCaps* caps = audio.GetCaps (); + GstStructure* structure = GetStructure (caps); + + if (structure) { + + gst.gst_structure_get_int (structure, "channels", &audioChannels); + gst.gst_structure_get_int (structure, "rate", &audioSampleRate); + + } + + if (caps) gst.gst_mini_object_unref (caps); + + if (audioChannels < 1 || audioChannels > 2 || audioSampleRate <= 0) { + + audio.Close (); + audioChannels = 0; + audioSampleRate = 0; + + } else { + + // The format is kept from here on, so a stream that changes rate or + // channels, such as a broadcast between programmes, is converted + char caps[128]; + snprintf (caps, sizeof (caps), "audio/x-raw,format=S16LE,layout=interleaved,channels=%d,rate=%d", audioChannels, audioSampleRate); + audio.SetCaps (caps); + + } + + } + + if (!video.pipeline && !audio.pipeline) return false; + + double duration = video.pipeline ? video.GetDuration () : 0; + if (duration <= 0 && audio.pipeline) duration = audio.GetDuration (); + + info->audioChannels = audioChannels; + info->audioSampleRate = audioSampleRate; + info->duration = duration; + info->frameRate = video.pipeline ? frameRate : 0; + info->hasAudio = (audio.pipeline != NULL); + info->hasVideo = (video.pipeline != NULL); + info->height = info->hasVideo ? height : 0; + info->pixelAspectRatio = info->hasVideo ? pixelAspectRatio : 0; + info->rotation = info->hasVideo ? video.GetRotation () : 0; + info->width = info->hasVideo ? width : 0; + + if (video.pipeline) video.Play (); + if (audio.pipeline) audio.Play (); + + return true; + + } + + + void GStreamerVideoBackend::PrepareSeek () { + + audio.PrepareSeek (); + video.PrepareSeek (); + + } + + + void GStreamerVideoBackend::ReadVideoFormat (GstCaps* caps) { + + GstStructure* structure = GetStructure (caps); + if (!structure) return; + + gst.gst_structure_get_int (structure, "width", &width); + gst.gst_structure_get_int (structure, "height", &height); + + const char* format = gst.gst_structure_get_string (structure, "format"); + planar = format && strcmp (format, "I420") == 0; + + int pixelWidth = 0; + int pixelHeight = 0; + pixelAspectRatio = gst.gst_structure_get_fraction (structure, "pixel-aspect-ratio", &pixelWidth, &pixelHeight) && pixelWidth > 0 && pixelHeight > 0 ? (double)pixelWidth / pixelHeight : 0; + + int numerator = 0; + int denominator = 0; + + if (gst.gst_structure_get_fraction (structure, "framerate", &numerator, &denominator) && numerator > 0 && denominator > 0) { + + frameRate = (double)numerator / denominator; + + } + + int range; + int matrix; + int transfer; + GetColorimetry (gst.gst_structure_get_string (structure, "colorimetry"), &range, &matrix, &transfer); + + // HDR is tone mapped by VideoDecoder, as no element here can + colorTransfer = transfer == GST_VIDEO_TRANSFER_SMPTE2084 ? VIDEO_COLOR_TRANSFER_PQ : (transfer == GST_VIDEO_TRANSFER_ARIB_STD_B67 ? VIDEO_COLOR_TRANSFER_HLG : VIDEO_COLOR_TRANSFER_SDR); + + switch (matrix) { + + case GST_VIDEO_COLOR_MATRIX_BT601: case GST_VIDEO_COLOR_MATRIX_FCC: colorMatrix = VIDEO_COLOR_MATRIX_BT601; break; + case GST_VIDEO_COLOR_MATRIX_BT709: case GST_VIDEO_COLOR_MATRIX_SMPTE240M: colorMatrix = VIDEO_COLOR_MATRIX_BT709; break; + case GST_VIDEO_COLOR_MATRIX_BT2020: colorMatrix = VIDEO_COLOR_MATRIX_BT2020; break; + default: colorMatrix = GetDefaultColorMatrix (width, height); break; + + } + + fullRange = (range == GST_VIDEO_COLOR_RANGE_0_255); + + } + + + bool GStreamerVideoBackend::ReopenVideoInSoftware () { + + std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now () + std::chrono::seconds (SOFTWARE_REOPEN_SECONDS); + + UnmapVideo (); + + if (interrupted || !video.Open (uri.c_str (), true, false, deadline)) return false; + + // Closing can have begun while the new stream opened + if (interrupted) { + + video.Interrupt (); + return false; + + } + + // Decoding resumes from the keyframe before the last frame returned + if (lastFrameTime >= 0) { + + video.Seek (lastFrameTime); + skipUntil = lastFrameTime; + + } + + video.Play (); + return true; + + } + + + bool GStreamerVideoBackend::Seek (double time) { + + UnmapVideo (); + + // A stream that cannot seek, such as a live one, is left playing, rather + // than one stream moving without the other + if ((video.pipeline && !video.IsSeekable ()) || (audio.pipeline && !audio.IsSeekable ())) { + + audio.CancelSeek (); + video.CancelSeek (); + return false; + + } + + lastFrameTime = -1; + skipUntil = -1; + + bool success = true; + + if (video.pipeline) { + + success = video.Seek (time) && success; + videoPosition = time; + + } + + if (audio.pipeline) { + + success = audio.Seek (time) && success; + audioPosition = time; + + } + + return success; + + } + + + void GStreamerVideoBackend::UnmapVideo () { + + if (videoSample) { + + gst.gst_buffer_unmap (gst.gst_sample_get_buffer (videoSample), &videoMap); + gst.gst_mini_object_unref (videoSample); + videoSample = NULL; + + } + + } + + + VideoBackend* VideoBackend::Create () { + + return LoadGStreamer () ? new GStreamerVideoBackend () : NULL; + + } + + + bool VideoBackend::IsSupported () { + + return LoadGStreamer (); + + } + + +} + + +#endif diff --git a/project/src/media/video/MFVideoBackend.cpp b/project/src/media/video/MFVideoBackend.cpp new file mode 100644 index 0000000000..6c33222395 --- /dev/null +++ b/project/src/media/video/MFVideoBackend.cpp @@ -0,0 +1,2881 @@ +#if defined (HX_WINDOWS) && !defined (HX_WINRT) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif + +// Declares the Windows 8 DXGI device manager, which is loaded at runtime +#if !defined (_WIN32_WINNT) || _WIN32_WINNT < 0x0602 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0602 +#endif +#if !defined (WINVER) || WINVER < 0x0602 +#undef WINVER +#define WINVER 0x0602 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NATIVE_TOOLKIT_SDL_ANGLE +#include +#define LIME_VIDEO_WGL_INTEROP +#endif + +#endif + +#include + + +#ifdef LIME_VIDEO_MEDIA_FOUNDATION + + +namespace lime { + + + // Media Foundation is missing from Windows N editions, so it is loaded at + // runtime instead of linked + typedef HRESULT (WINAPI *MFCreateAttributesFunc) (IMFAttributes**, UINT32); + typedef HRESULT (WINAPI *MFCreateDXGIDeviceManagerFunc) (UINT*, IMFDXGIDeviceManager**); + typedef HRESULT (WINAPI *MFCreateMediaTypeFunc) (IMFMediaType**); + typedef HRESULT (WINAPI *MFCreateSourceReaderFromByteStreamFunc) (IMFByteStream*, IMFAttributes*, IMFSourceReader**); + typedef HRESULT (WINAPI *MFCreateSourceReaderFromURLFunc) (LPCWSTR, IMFAttributes*, IMFSourceReader**); + typedef HRESULT (WINAPI *MFCreateSourceResolverFunc) (IMFSourceResolver**); + typedef HRESULT (WINAPI *MFShutdownFunc) (); + typedef HRESULT (WINAPI *MFStartupFunc) (ULONG, DWORD); + + // GUID_NULL, the 100-nanosecond time format for SetCurrentPosition + static const GUID TIME_FORMAT_100NS = {}; + + // Hardware frames are copied to staging textures and mapped STAGING_DELAY + // frames later, so reading them back does not wait for the GPU + static const int STAGING_DELAY = 2; + static const int STAGING_TEXTURES = 4; + + // Texture output converts frames on the GPU into a ring of RGB textures, + // enough for the decoder's queue, the frame on screen and one being written + static const int OUTPUT_TEXTURES = 8; + + // How long a seek waits for reads in progress before giving up on them, as + // reads from a source that is still sending finish well within it + static const int SEEK_WAIT_MS = 250; + + // MFVideoTransFunc_2084 and _HLG, MFVideoPrimaries_BT2020 and + // MFVideoTransferMatrix_BT2020_10, which older SDKs lack + static const UINT32 TRANSFER_PQ = 15; + static const UINT32 TRANSFER_HLG = 16; + static const UINT32 PRIMARIES_BT2020 = 9; + static const UINT32 MATRIX_BT2020 = 4; + + + // Reads bits from an H.264 parameter set, skipping emulation prevention + class MFBitReader { + + + public: + + MFBitReader (const UINT8* data, size_t size) : bit (0), data (data), size (size), zeros (0) {} + + bool Bit () { + + if (bit == 0) { + + // A 3 after two zeros only keeps a start code from appearing + if (zeros >= 2 && size > 0 && *data == 3) { + + data++; + size--; + zeros = 0; + + } + + if (size == 0) return false; + + } + + bool value = ((*data >> (7 - bit)) & 1) != 0; + + if (++bit == 8) { + + zeros = *data == 0 ? zeros + 1 : 0; + bit = 0; + data++; + size--; + + } + + return value; + + } + + UINT32 Bits (int count) { + + UINT32 value = 0; + for (int i = 0; i < count; i++) value = (value << 1) | (Bit () ? 1 : 0); + return value; + + } + + UINT32 Exp () { + + int count = 0; + while (!Bit () && count < 32) count++; + return (UINT32)((1ULL << count) - 1) + Bits (count); + + } + + int SignedExp () { + + UINT32 value = Exp (); + return (value & 1) ? (int)((value + 1) / 2) : -(int)(value / 2); + + } + + + private: + + int bit; + const UINT8* data; + size_t size; + int zeros; + + + }; + + + static VideoColorTransfer ReadH264Transfer (const UINT8* sps, size_t size) { + + // The transfer characteristics in the SPS's video usability information + MFBitReader reader (sps, size); + reader.Bits (8); + UINT32 profile = reader.Bits (8); + reader.Bits (16); + reader.Exp (); + + if (profile == 100 || profile == 110 || profile == 122 || profile == 244 || profile == 44 || profile == 83 || profile == 86 || profile == 118 || profile == 128 || profile == 138 || profile == 139 || profile == 134 || profile == 135) { + + UINT32 chroma = reader.Exp (); + if (chroma == 3) reader.Bit (); + reader.Exp (); + reader.Exp (); + reader.Bit (); + + if (reader.Bit ()) { + + for (int i = 0; i < (chroma != 3 ? 8 : 12); i++) { + + if (!reader.Bit ()) continue; + + int last = 8; + int next = 8; + + for (int j = 0; j < (i < 6 ? 16 : 64) && next != 0; j++) { + + next = (last + reader.SignedExp () + 256) % 256; + if (next != 0) last = next; + + } + + } + + } + + } + + reader.Exp (); + UINT32 order = reader.Exp (); + + if (order == 0) { + + reader.Exp (); + + } else if (order == 1) { + + reader.Bit (); + reader.SignedExp (); + reader.SignedExp (); + UINT32 cycle = reader.Exp (); + for (UINT32 i = 0; i < cycle && i < 256; i++) reader.SignedExp (); + + } + + reader.Exp (); + reader.Bit (); + reader.Exp (); + reader.Exp (); + if (!reader.Bit ()) reader.Bit (); + reader.Bit (); + + if (reader.Bit ()) { + + reader.Exp (); + reader.Exp (); + reader.Exp (); + reader.Exp (); + + } + + if (!reader.Bit ()) return VIDEO_COLOR_TRANSFER_SDR; + + if (reader.Bit () && reader.Bits (8) == 255) reader.Bits (32); + if (reader.Bit ()) reader.Bit (); + if (!reader.Bit ()) return VIDEO_COLOR_TRANSFER_SDR; + + reader.Bits (4); + if (!reader.Bit ()) return VIDEO_COLOR_TRANSFER_SDR; + + reader.Bits (8); + UINT32 transfer = reader.Bits (8); + return transfer == 16 ? VIDEO_COLOR_TRANSFER_PQ : (transfer == 18 ? VIDEO_COLOR_TRANSFER_HLG : VIDEO_COLOR_TRANSFER_SDR); + + } + + + static void ReleaseReader (IMFSourceReader** reader, bool shutDown) { + + // A reader of a network source that stalled takes seconds to release, + // as Media Foundation waits on it, unless its media source is shut down + // first, which is safe once no read is in progress + if (!*reader) return; + + if (shutDown) { + + IMFMediaSource* source = NULL; + GUID none = {}; + + if (SUCCEEDED ((*reader)->GetServiceForStream (MF_SOURCE_READER_MEDIASOURCE, none, IID_PPV_ARGS (&source))) && source) { + + source->Shutdown (); + source->Release (); + + } + + } + + (*reader)->Release (); + *reader = NULL; + + } + + + static VideoColorTransfer ReadTransfer (IMFMediaType* type) { + + UINT32 transfer = MFGetAttributeUINT32 (type, MF_MT_TRANSFER_FUNCTION, 0); + if (transfer == TRANSFER_PQ) return VIDEO_COLOR_TRANSFER_PQ; + if (transfer == TRANSFER_HLG) return VIDEO_COLOR_TRANSFER_HLG; + + // Media Foundation does not say for H.264, so its SPS is read, from the + // start codes before it or the avcC record it comes in + GUID subtype = {}; + UINT8* header = NULL; + UINT32 size = 0; + + if (FAILED (type->GetGUID (MF_MT_SUBTYPE, &subtype)) || subtype != MFVideoFormat_H264 || FAILED (type->GetAllocatedBlob (MF_MT_MPEG_SEQUENCE_HEADER, &header, &size))) { + + return VIDEO_COLOR_TRANSFER_SDR; + + } + + VideoColorTransfer result = VIDEO_COLOR_TRANSFER_SDR; + + if (size > 8 && header[0] == 1) { + + UINT32 length = (header[6] << 8) | header[7]; + if ((header[5] & 31) > 0 && 8 + length <= size && length > 0) result = ReadH264Transfer (header + 8, length); + + } else { + + for (UINT32 i = 0; i + 3 < size; i++) { + + if (header[i] == 0 && header[i + 1] == 0 && header[i + 2] == 1 && (header[i + 3] & 31) == 7) { + + result = ReadH264Transfer (header + i + 3, size - i - 3); + break; + + } + + } + + } + + CoTaskMemFree (header); + return result; + + } + + #ifdef LIME_VIDEO_WGL_INTEROP + // WGL_NV_DX_interop2, which shares Direct3D 11 textures with OpenGL + typedef BOOL (WINAPI *WGLDXCloseDeviceFunc) (HANDLE); + typedef BOOL (WINAPI *WGLDXLockObjectsFunc) (HANDLE, GLint, HANDLE*); + typedef HANDLE (WINAPI *WGLDXOpenDeviceFunc) (void*); + typedef HANDLE (WINAPI *WGLDXRegisterObjectFunc) (HANDLE, void*, GLuint, GLenum, GLenum); + typedef BOOL (WINAPI *WGLDXUnlockObjectsFunc) (HANDLE, GLint, HANDLE*); + typedef BOOL (WINAPI *WGLDXUnregisterObjectFunc) (HANDLE, HANDLE); + + static const GLenum WGL_ACCESS_READ_ONLY = 0x0000; + + static WGLDXCloseDeviceFunc _wglDXCloseDeviceNV = 0; + static WGLDXLockObjectsFunc _wglDXLockObjectsNV = 0; + static WGLDXOpenDeviceFunc _wglDXOpenDeviceNV = 0; + static WGLDXRegisterObjectFunc _wglDXRegisterObjectNV = 0; + static WGLDXUnlockObjectsFunc _wglDXUnlockObjectsNV = 0; + static WGLDXUnregisterObjectFunc _wglDXUnregisterObjectNV = 0; + #endif + + + struct MFOutputTexture { + + unsigned int glTexture; + HANDLE interop; + // Locked for OpenGL, as the frame being shown is + bool locked; + ID3D11Texture2D* texture; + ID3D11VideoProcessorOutputView* view; + + }; + + + class MFTextureFrame : public VideoTextureFrame { + + public: + + int index; + + }; + + static PFN_D3D11_CREATE_DEVICE _D3D11CreateDevice = 0; + static MFCreateAttributesFunc _MFCreateAttributes = 0; + static MFCreateDXGIDeviceManagerFunc _MFCreateDXGIDeviceManager = 0; + static MFCreateMediaTypeFunc _MFCreateMediaType = 0; + static MFCreateSourceReaderFromByteStreamFunc _MFCreateSourceReaderFromByteStream = 0; + static MFCreateSourceReaderFromURLFunc _MFCreateSourceReaderFromURL = 0; + static MFCreateSourceResolverFunc _MFCreateSourceResolver = 0; + static MFShutdownFunc _MFShutdown = 0; + static MFStartupFunc _MFStartup = 0; + + static std::once_flag loadFlag; + static bool loaded = false; + + + static HMODULE LoadSystemLibrary (const wchar_t* name) { + + // From the system directory only, so a DLL of the same name beside the + // application or in the working directory is not loaded instead + wchar_t path[MAX_PATH]; + UINT length = GetSystemDirectoryW (path, MAX_PATH); + if (length == 0 || length + wcslen (name) + 2 > MAX_PATH) return NULL; + + path[length] = L'\\'; + wcscpy (path + length + 1, name); + return LoadLibraryW (path); + + } + + + static bool LoadMediaFoundation () { + + std::call_once (loadFlag, [] { + + HMODULE mfplat = LoadSystemLibrary (L"mfplat.dll"); + HMODULE mfreadwrite = LoadSystemLibrary (L"mfreadwrite.dll"); + + if (!mfplat || !mfreadwrite) return; + + _MFCreateAttributes = (MFCreateAttributesFunc)GetProcAddress (mfplat, "MFCreateAttributes"); + _MFCreateDXGIDeviceManager = (MFCreateDXGIDeviceManagerFunc)GetProcAddress (mfplat, "MFCreateDXGIDeviceManager"); + _MFCreateMediaType = (MFCreateMediaTypeFunc)GetProcAddress (mfplat, "MFCreateMediaType"); + _MFShutdown = (MFShutdownFunc)GetProcAddress (mfplat, "MFShutdown"); + _MFStartup = (MFStartupFunc)GetProcAddress (mfplat, "MFStartup"); + _MFCreateSourceReaderFromURL = (MFCreateSourceReaderFromURLFunc)GetProcAddress (mfreadwrite, "MFCreateSourceReaderFromURL"); + _MFCreateSourceReaderFromByteStream = (MFCreateSourceReaderFromByteStreamFunc)GetProcAddress (mfreadwrite, "MFCreateSourceReaderFromByteStream"); + _MFCreateSourceResolver = (MFCreateSourceResolverFunc)GetProcAddress (mfplat, "MFCreateSourceResolver"); + + HMODULE d3d11 = LoadSystemLibrary (L"d3d11.dll"); + if (d3d11) _D3D11CreateDevice = (PFN_D3D11_CREATE_DEVICE)GetProcAddress (d3d11, "D3D11CreateDevice"); + + loaded = (_MFCreateAttributes && _MFCreateMediaType && _MFShutdown && _MFStartup && _MFCreateSourceReaderFromURL); + + }); + + return loaded; + + } + + + struct MFThreadApartment { + + bool initialized; + + MFThreadApartment () : initialized (false) {} + ~MFThreadApartment () { if (initialized) CoUninitialize (); } + + }; + + + static void InitializeCOM () { + + // Media Foundation objects are free-threaded, so each decoding thread + // joins the multithreaded apartment until it ends + static thread_local MFThreadApartment apartment; + + if (!apartment.initialized) { + + apartment.initialized = SUCCEEDED (CoInitializeEx (NULL, COINIT_MULTITHREADED)); + + } + + } + + + static void UseMultithreadedApartment () { + + // The application's threads are not put in an apartment, which would + // stop one joining the apartment it needs later, such as for drag and + // drop. The multithreaded apartment is kept instead, which threads that + // are in none use, and other threads use Media Foundation from theirs. + static std::once_flag flag; + + std::call_once (flag, [] { + + typedef HRESULT (WINAPI *CoIncrementMTAUsageFunc) (void**); + HMODULE ole32 = GetModuleHandleW (L"ole32.dll"); + CoIncrementMTAUsageFunc increment = ole32 ? (CoIncrementMTAUsageFunc)GetProcAddress (ole32, "CoIncrementMTAUsage") : NULL; + void* cookie = NULL; + + if (increment && SUCCEEDED (increment (&cookie))) return; + + // Windows 7 keeps the apartment with a thread that stays in it + std::mutex mutex; + std::condition_variable condition; + bool ready = false; + + std::thread ([&mutex, &condition, &ready] { + + CoInitializeEx (NULL, COINIT_MULTITHREADED); + + { + std::lock_guard lock (mutex); + ready = true; + } + + condition.notify_one (); + Sleep (INFINITE); + + }).detach (); + + std::unique_lock lock (mutex); + condition.wait (lock, [&ready] { return ready; }); + + }); + + } + + + static std::wstring GetURL (const char* path) { + + int length = MultiByteToWideChar (CP_UTF8, 0, path, -1, NULL, 0); + if (length <= 0) return std::wstring (); + + std::wstring wide (length, 0); + MultiByteToWideChar (CP_UTF8, 0, path, -1, &wide[0], length); + wide.resize (length - 1); + + if (wide.find (L"://") != std::wstring::npos) return wide; + + DWORD fullLength = GetFullPathNameW (wide.c_str (), 0, NULL, NULL); + if (fullLength == 0) return wide; + + std::wstring full (fullLength, 0); + fullLength = GetFullPathNameW (wide.c_str (), fullLength, &full[0], NULL); + full.resize (fullLength); + return full; + + } + + + // The connection a reader reads through, which closing and seeking give up + // when it waits on a stalled network source + struct MFConnection { + + MFConnection () : busy (false), byteStream (NULL), cancelCookie (NULL), cancelled (false), reopen (false), reopenPosition (0), resolver (NULL) {} + + // Reading or opening, under the stream mutex + bool busy; + + IMFByteStream* byteStream; + IUnknown* cancelCookie; + std::atomic cancelled; + + // Given up by a seek, so the reader opens again from reopenPosition + std::atomic reopen; + LONGLONG reopenPosition; + + IMFSourceResolver* resolver; + + }; + + + // Receives the byte stream a source resolver opens, which a cancelled open + // may still deliver after the reader stopped waiting for it + class MFResolveCallback : public IMFAsyncCallback { + + + public: + + MFResolveCallback (IMFSourceResolver* resolver) : object (NULL), references (1), resolver (resolver), result (E_PENDING) { + + resolver->AddRef (); + done = CreateEventW (NULL, TRUE, FALSE, NULL); + + } + + ~MFResolveCallback () { + + if (object) object->Release (); + if (done) CloseHandle (done); + resolver->Release (); + + } + + STDMETHODIMP QueryInterface (REFIID riid, void** value) { + + if (riid == __uuidof (IMFAsyncCallback) || riid == __uuidof (IUnknown)) { + + *value = static_cast (this); + AddRef (); + return S_OK; + + } + + *value = NULL; + return E_NOINTERFACE; + + } + + STDMETHODIMP_(ULONG) AddRef () { return InterlockedIncrement (&references); } + + STDMETHODIMP_(ULONG) Release () { + + ULONG count = InterlockedDecrement (&references); + if (count == 0) delete this; + return count; + + } + + STDMETHODIMP GetParameters (DWORD* flags, DWORD* queue) { return E_NOTIMPL; } + + STDMETHODIMP Invoke (IMFAsyncResult* asyncResult) { + + MF_OBJECT_TYPE type = MF_OBJECT_INVALID; + result = resolver->EndCreateObjectFromURL (asyncResult, &type, &object); + SetEvent (done); + return S_OK; + + } + + // Once done is set + IUnknown* TakeObject () { + + IUnknown* value = SUCCEEDED (result) ? object : NULL; + if (!value && object) object->Release (); + object = NULL; + return value; + + } + + HANDLE done; + + + private: + + IUnknown* object; + LONG references; + IMFSourceResolver* resolver; + HRESULT result; + + + }; + + + class MFVideoBackend : public VideoBackend { + + + public: + + MFVideoBackend (); + ~MFVideoBackend (); + + virtual void Close (); + virtual VideoDecodeResult DecodeAudio (std::vector* pcm, double* time); + virtual VideoDecodeResult DecodeVideo (VideoPlanes* planes, double* time, double* duration); + virtual void Interrupt (); + virtual bool Open (const char* path, bool hardwareDecoding, VideoStreamInfo* info); + virtual void PrepareSeek (); + virtual bool Seek (double time); + + private: + + void ClearStaging (); + bool CopyToStaging (IMFSample* sample, LONGLONG timestamp, LONGLONG sampleDuration); + bool CreateDeviceManager (); + bool CreateProcessor (UINT inputWidth, UINT inputHeight); + IMFSourceReader* CreateReader (IMFAttributes* attributes, MFConnection* connection); + void GetPlanes (VideoPlanes* planes, BYTE* data, int pitch, int lumaRows); + void GiveUp (MFConnection* connection); + VideoDecodeResult MapStaging (VideoPlanes* planes, double* time, double* duration); + bool OpenAudio (); + bool OpenVideo (bool hardware); + void ReadAudioFormat (); + bool OpenVideoReader (bool hardware, bool advanced, bool keepDevice = false); + HRESULT ReadSample (IMFSourceReader* reader, DWORD stream, MFConnection* connection, DWORD* flags, LONGLONG* timestamp, IMFSample** sample); + void ReadVideoFormat (); + void ReleaseDeviceManager (); + void ReleaseProcessor (); + VideoDecodeResult ReopenAudio (); + VideoDecodeResult ReopenVideo (); + IUnknown* ResolveURL (IMFSourceResolver* resolver, MFConnection* connection); + bool SetBusy (MFConnection* connection, bool busy); + void SetByteStream (MFConnection* connection, IMFByteStream* value); + bool SwitchToSoftware (); + void UnlockVideo (); + + virtual VideoDecodeResult DecodeVideoTexture (VideoTextureFrame** frame, double* time, double* duration); + virtual unsigned int LockTexture (VideoTextureFrame* frame); + virtual void ReleaseTextureFrame (VideoTextureFrame* frame); + virtual void ReleaseOrphans (); + virtual void ReleaseTextures (); + virtual bool SupportsTextures (); + virtual void UnlockTexture (VideoTextureFrame* frame); + + int audioChannels; + VideoAudioConverter audioConverter; + int audioPacketChannels; + int audioPacketRate; + MFConnection audioConnection; + IMFSourceReader* audioReader; + int audioSampleRate; + int audioSourceChannels; + bool audioStreamFound; + int bufferHeight; + int bufferWidth; + bool canSeek; + VideoColorMatrix colorMatrix; + int cropHeight; + int cropWidth; + int cropX; + int cropY; + ID3D11DeviceContext* context; + LONG defaultStride; + ID3D11Device* device; + std::vector freeOutputs; + MFTextureFrame outputFrames[OUTPUT_TEXTURES]; + std::condition_variable outputCondition; + std::mutex outputMutex; + ID3D11Query* outputQuery; + MFOutputTexture outputs[OUTPUT_TEXTURES]; + ID3D11VideoProcessor* processor; + ID3D11VideoProcessorEnumerator* processorEnumerator; + UINT processorHeight; + UINT processorWidth; + IMFDXGIDeviceManager* deviceManager; + double frameRate; + VideoColorTransfer frameTransfer; + bool fullRange; + bool hardwareActive; + std::map, ID3D11VideoProcessorInputView*> inputViews; + std::atomic interrupted; + HANDLE interopDevice; + void* interopContext; + bool interopFailed; + LONGLONG lastFrameTime; + IMF2DBuffer* locked2D; + IMFMediaBuffer* lockedBuffer; + double pixelAspectRatio; + int rotation; + LONGLONG skipUntil; + ID3D11Texture2D* staging[STAGING_TEXTURES]; + LONGLONG stagingDurations[STAGING_TEXTURES]; + bool stagingFailed; + bool texturesFailed; + UINT stagingHeight; + int stagingMapped; + bool stagingPrimed; + std::deque stagingQueue; + LONGLONG stagingTimes[STAGING_TEXTURES]; + UINT stagingWidth; + bool network; + std::condition_variable readCondition; + std::atomic seeking; + bool started; + std::mutex streamMutex; + std::wstring url; + bool videoAdvanced; + bool videoEnded; + ID3D11VideoContext* videoContext; + ID3D11VideoDevice* videoDevice; + LONGLONG videoPosition; + MFConnection videoConnection; + IMFSourceReader* videoReader; + bool videoStreamFound; + + + }; + + + MFVideoBackend::MFVideoBackend () { + + audioChannels = 0; + audioPacketChannels = 0; + audioPacketRate = 0; + audioReader = NULL; + audioSampleRate = 0; + audioSourceChannels = 0; + audioStreamFound = false; + bufferHeight = 0; + bufferWidth = 0; + canSeek = true; + colorMatrix = VIDEO_COLOR_MATRIX_BT709; + cropHeight = 0; + cropWidth = 0; + cropX = 0; + cropY = 0; + context = NULL; + defaultStride = 0; + device = NULL; + deviceManager = NULL; + frameRate = 0; + fullRange = false; + hardwareActive = false; + interopContext = NULL; + interopDevice = NULL; + interopFailed = false; + interrupted = false; + lastFrameTime = -1; + locked2D = NULL; + lockedBuffer = NULL; + outputQuery = NULL; + pixelAspectRatio = 0; + processor = NULL; + processorEnumerator = NULL; + processorHeight = 0; + processorWidth = 0; + rotation = 0; + skipUntil = -1; + stagingFailed = false; + stagingHeight = 0; + stagingMapped = -1; + stagingPrimed = false; + stagingWidth = 0; + texturesFailed = false; + videoContext = NULL; + videoDevice = NULL; + videoEnded = false; + videoPosition = 0; + frameTransfer = VIDEO_COLOR_TRANSFER_SDR; + network = false; + seeking = false; + videoAdvanced = false; + videoReader = NULL; + videoStreamFound = false; + + for (int i = 0; i < STAGING_TEXTURES; i++) staging[i] = NULL; + + for (int i = 0; i < OUTPUT_TEXTURES; i++) { + + outputs[i].glTexture = 0; + outputs[i].interop = NULL; + outputs[i].locked = false; + outputs[i].texture = NULL; + outputs[i].view = NULL; + outputFrames[i].index = i; + + } + + UseMultithreadedApartment (); + started = SUCCEEDED (_MFStartup (MF_VERSION, MFSTARTUP_FULL)); + + } + + + #ifdef LIME_VIDEO_WGL_INTEROP + // Textures shared with OpenGL by a backend closed away from the GL thread. + // The device is kept so the interop objects can still be released. + struct MFOrphan { + + void* context; + ID3D11Device* device; + HANDLE interopDevice; + std::vector interops; + std::vector glTextures; + std::vector locked; + std::vector textures; + + }; + + // Never destroyed, like the other video code's, as threads can still be + // closing videos while static destructors run + static std::mutex* orphanMutex = new std::mutex (); + static std::vector* orphans = new std::vector (); + #endif + + + void MFVideoBackend::ReleaseOrphans () { + + #ifdef LIME_VIDEO_WGL_INTEROP + std::vector list; + + { + std::lock_guard lock (*orphanMutex); + if (orphans->empty ()) return; + list.swap (*orphans); + } + + void* context = (void*)wglGetCurrentContext (); + + for (size_t i = 0; i < list.size (); i++) { + + MFOrphan& orphan = list[i]; + + // Objects from a context that is no longer current are left with it + bool current = orphan.interopDevice && orphan.context == context; + + for (size_t j = 0; j < orphan.interops.size (); j++) { + + // The frame shown when the backend closed is still locked, and is + // unlocked first, as closing on the GL thread does + if (current && orphan.interops[j] && orphan.locked[j]) _wglDXUnlockObjectsNV (orphan.interopDevice, 1, &orphan.interops[j]); + if (current && orphan.interops[j]) _wglDXUnregisterObjectNV (orphan.interopDevice, orphan.interops[j]); + if (current && orphan.glTextures[j]) glDeleteTextures (1, &orphan.glTextures[j]); + if (orphan.textures[j]) orphan.textures[j]->Release (); + + } + + if (current) _wglDXCloseDeviceNV (orphan.interopDevice); + if (orphan.device) orphan.device->Release (); + + } + #endif + + } + + + MFVideoBackend::~MFVideoBackend () { + + Close (); + + if (started) _MFShutdown (); + + } + + + void MFVideoBackend::Close () { + + // Closing away from the GL thread queues the textures it shares with + // OpenGL, rather than using them with the next device + #ifdef LIME_VIDEO_WGL_INTEROP + MFOrphan orphan; + orphan.context = interopContext; + orphan.device = NULL; + orphan.interopDevice = interopDevice; + #endif + + for (int i = 0; i < OUTPUT_TEXTURES; i++) { + + if (outputs[i].interop) { + + #ifdef LIME_VIDEO_WGL_INTEROP + orphan.interops.push_back (outputs[i].interop); + orphan.glTextures.push_back (outputs[i].glTexture); + orphan.locked.push_back (outputs[i].locked); + orphan.textures.push_back (outputs[i].texture); + #endif + + outputs[i].glTexture = 0; + outputs[i].interop = NULL; + outputs[i].locked = false; + outputs[i].texture = NULL; + + } + + } + + #ifdef LIME_VIDEO_WGL_INTEROP + if (orphan.interopDevice || !orphan.interops.empty ()) { + + if (device) { + + device->AddRef (); + orphan.device = device; + + } + + std::lock_guard lock (*orphanMutex); + orphans->push_back (orphan); + + } + #endif + + interopContext = NULL; + interopDevice = NULL; + interopFailed = false; + + UnlockVideo (); + ReleaseReader (&videoReader, network); + ReleaseReader (&audioReader, network); + + SetByteStream (&audioConnection, NULL); + SetByteStream (&videoConnection, NULL); + audioConnection.cancelled = false; + audioConnection.reopen = false; + videoConnection.cancelled = false; + videoConnection.reopen = false; + frameTransfer = VIDEO_COLOR_TRANSFER_SDR; + interrupted = false; + network = false; + seeking = false; + + ReleaseDeviceManager (); + + audioChannels = 0; + audioConverter.Reset (); + audioPacketChannels = 0; + audioPacketRate = 0; + audioSampleRate = 0; + audioSourceChannels = 0; + audioStreamFound = false; + canSeek = true; + frameRate = 0; + hardwareActive = false; + lastFrameTime = -1; + pixelAspectRatio = 0; + rotation = 0; + skipUntil = -1; + stagingFailed = false; + stagingPrimed = false; + texturesFailed = false; + videoEnded = false; + videoPosition = 0; + videoStreamFound = false; + + } + + + void MFVideoBackend::ClearStaging () { + + if (stagingMapped >= 0) { + + context->Unmap (staging[stagingMapped], 0); + stagingMapped = -1; + + } + + stagingQueue.clear (); + + } + + + bool MFVideoBackend::CopyToStaging (IMFSample* sample, LONGLONG timestamp, LONGLONG sampleDuration) { + + if (!context || stagingFailed) return false; + + IMFMediaBuffer* buffer = NULL; + IMFDXGIBuffer* dxgiBuffer = NULL; + ID3D11Texture2D* texture = NULL; + UINT subresource = 0; + bool copied = false; + + if (SUCCEEDED (sample->GetBufferByIndex (0, &buffer)) && SUCCEEDED (buffer->QueryInterface (IID_PPV_ARGS (&dxgiBuffer))) + && SUCCEEDED (dxgiBuffer->GetResource (IID_PPV_ARGS (&texture))) && SUCCEEDED (dxgiBuffer->GetSubresourceIndex (&subresource))) { + + D3D11_TEXTURE2D_DESC desc; + texture->GetDesc (&desc); + + if (desc.Format == DXGI_FORMAT_NV12 && (desc.Width != stagingWidth || desc.Height != stagingHeight)) { + + ClearStaging (); + + for (int i = 0; i < STAGING_TEXTURES; i++) { + + if (staging[i]) staging[i]->Release (); + staging[i] = NULL; + + } + + D3D11_TEXTURE2D_DESC stagingDesc = {}; + stagingDesc.Width = desc.Width; + stagingDesc.Height = desc.Height; + stagingDesc.MipLevels = 1; + stagingDesc.ArraySize = 1; + stagingDesc.Format = DXGI_FORMAT_NV12; + stagingDesc.SampleDesc.Count = 1; + stagingDesc.Usage = D3D11_USAGE_STAGING; + stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + + bool created = true; + + for (int i = 0; i < STAGING_TEXTURES && created; i++) { + + created = SUCCEEDED (device->CreateTexture2D (&stagingDesc, NULL, &staging[i])); + + } + + stagingWidth = created ? desc.Width : 0; + stagingHeight = created ? desc.Height : 0; + + } + + if (desc.Format == DXGI_FORMAT_NV12 && stagingWidth == desc.Width && stagingHeight == desc.Height) { + + // Use a texture that is neither mapped nor waiting to be + int index = -1; + + for (int i = 0; i < STAGING_TEXTURES && index < 0; i++) { + + bool used = (i == stagingMapped); + for (size_t j = 0; j < stagingQueue.size () && !used; j++) used = (stagingQueue[j] == i); + if (!used) index = i; + + } + + if (index >= 0) { + + context->CopySubresourceRegion (staging[index], 0, 0, 0, 0, texture, subresource, NULL); + stagingDurations[index] = sampleDuration; + stagingTimes[index] = timestamp; + stagingQueue.push_back (index); + copied = true; + + } + + } + + } + + if (texture) texture->Release (); + if (dxgiBuffer) dxgiBuffer->Release (); + if (buffer) buffer->Release (); + + // The frames already queued would come out of order, so they are + // dropped if copying stops working + if (!copied) { + + stagingFailed = true; + ClearStaging (); + + } + + return copied; + + } + + + IMFSourceReader* MFVideoBackend::CreateReader (IMFAttributes* attributes, MFConnection* connection) { + + // Each reader reads its own byte stream, which is kept so Interrupt and + // PrepareSeek can close it. The resolver opens files and URLs as Media + // Foundation does. + IMFByteStream* byteStream = NULL; + IMFSourceResolver* resolver = NULL; + + if (_MFCreateSourceResolver && _MFCreateSourceReaderFromByteStream && SUCCEEDED (_MFCreateSourceResolver (&resolver))) { + + IUnknown* object = ResolveURL (resolver, connection); + + if (object) { + + object->QueryInterface (IID_PPV_ARGS (&byteStream)); + object->Release (); + + } + + resolver->Release (); + + } + + IMFSourceReader* reader = NULL; + + if (byteStream) { + + // The byte stream is kept first, so a source that stalls while the + // reader opens can be given up too + SetByteStream (connection, byteStream); + + if (FAILED (_MFCreateSourceReaderFromByteStream (byteStream, attributes, &reader))) { + + reader = NULL; + SetByteStream (connection, NULL); + + } + + } + + if (!reader && (connection->cancelled || FAILED (_MFCreateSourceReaderFromURL (url.c_str (), attributes, &reader)))) { + + return NULL; + + } + + return reader; + + } + + + static bool GetSharedDevice (ID3D11Device** device, IMFDXGIDeviceManager** deviceManager) { + + // One device serves every video for the life of the process. Creating one + // for each video takes tens of milliseconds, and releasing one ends the + // driver's threads, which Neko's collector, as it tracks every thread in + // the process, can abort on when it happens during a collection. + static std::mutex mutex; + static ID3D11Device* sharedDevice = NULL; + static IMFDXGIDeviceManager* sharedManager = NULL; + + std::lock_guard lock (mutex); + + if (sharedDevice && sharedDevice->GetDeviceRemovedReason () != S_OK) { + + // A device lost to a driver reset or update is replaced. Videos using + // it keep their references until they close. + sharedManager->Release (); + sharedDevice->Release (); + sharedManager = NULL; + sharedDevice = NULL; + + } + + if (!sharedDevice) { + + if (!_D3D11CreateDevice || !_MFCreateDXGIDeviceManager) return false; + + static const D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3 }; + + UINT flags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT | D3D11_CREATE_DEVICE_BGRA_SUPPORT; + ID3D11Device* created = NULL; + HRESULT hr = _D3D11CreateDevice (NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, flags, levels, ARRAYSIZE (levels), D3D11_SDK_VERSION, &created, NULL, NULL); + + if (hr == E_INVALIDARG) { + + // Windows 7 without the platform update does not know 11.1 + hr = _D3D11CreateDevice (NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, flags, levels + 1, ARRAYSIZE (levels) - 1, D3D11_SDK_VERSION, &created, NULL, NULL); + + } + + if (FAILED (hr) || !created) return false; + + // Media Foundation and every video use the device from their own threads + ID3D10Multithread* multithread = NULL; + + if (SUCCEEDED (created->QueryInterface (IID_PPV_ARGS (&multithread)))) { + + multithread->SetMultithreadProtected (TRUE); + multithread->Release (); + + } + + UINT token = 0; + IMFDXGIDeviceManager* manager = NULL; + + if (FAILED (_MFCreateDXGIDeviceManager (&token, &manager)) || FAILED (manager->ResetDevice (created, token))) { + + if (manager) manager->Release (); + created->Release (); + return false; + + } + + sharedDevice = created; + sharedManager = manager; + + } + + sharedDevice->AddRef (); + sharedManager->AddRef (); + *device = sharedDevice; + *deviceManager = sharedManager; + return true; + + } + + + bool MFVideoBackend::CreateDeviceManager () { + + if (deviceManager) return true; + if (!GetSharedDevice (&device, &deviceManager)) return false; + + device->GetImmediateContext (&context); + return true; + + } + + + VideoDecodeResult MFVideoBackend::DecodeAudio (std::vector* pcm, double* time) { + + InitializeCOM (); + + if (audioConnection.reopen) { + + VideoDecodeResult result = ReopenAudio (); + if (result != VIDEO_DECODE_OK) return result; + + } + + if (!audioReader) return VIDEO_DECODE_END; + + while (true) { + + DWORD flags = 0; + LONGLONG timestamp = 0; + IMFSample* sample = NULL; + + HRESULT hr = ReadSample (audioReader, MF_SOURCE_READER_FIRST_AUDIO_STREAM, &audioConnection, &flags, ×tamp, &sample); + + if (FAILED (hr) || (flags & MF_SOURCE_READERF_ERROR)) { + + if (sample) sample->Release (); + + // A seek gave up on this read, or it waits for one, and reading + // carries on after it + return audioConnection.reopen || seeking ? VIDEO_DECODE_AGAIN : VIDEO_DECODE_ERROR; + + } + + if (flags & MF_SOURCE_READERF_ENDOFSTREAM) { + + if (sample) sample->Release (); + return VIDEO_DECODE_END; + + } + + // A stream can change its rate or channels partway, such as HE-AAC + // that only shows its full rate once decoding starts + if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) ReadAudioFormat (); + + if (!sample) continue; + + IMFMediaBuffer* buffer = NULL; + hr = sample->ConvertToContiguousBuffer (&buffer); + sample->Release (); + + if (FAILED (hr)) return VIDEO_DECODE_ERROR; + + BYTE* data = NULL; + DWORD length = 0; + + if (FAILED (buffer->Lock (&data, NULL, &length))) { + + buffer->Release (); + return VIDEO_DECODE_ERROR; + + } + + *time = timestamp / 10000000.0; + + // Audio is given in the format it opened with + if (audioPacketChannels == audioChannels && audioPacketRate == audioSampleRate) { + + pcm->insert (pcm->end (), data, data + length); + + } else if (audioPacketChannels > 0 && audioPacketRate > 0) { + + ConvertAudio ((const short*)data, length / (audioPacketChannels * 2), audioPacketChannels, audioPacketRate, audioChannels, audioSampleRate, &audioConverter, pcm); + + } + + buffer->Unlock (); + buffer->Release (); + + return VIDEO_DECODE_OK; + + } + + } + + + VideoDecodeResult MFVideoBackend::DecodeVideo (VideoPlanes* planes, double* time, double* duration) { + + UnlockVideo (); + InitializeCOM (); + + if (videoConnection.reopen) { + + VideoDecodeResult result = ReopenVideo (); + if (result != VIDEO_DECODE_OK) return result; + + } + + if (!videoReader) return VIDEO_DECODE_END; + + while (true) { + + // The first frame after opening or seeking is mapped straight away, so + // only frames that follow it wait for the pipeline + if (!stagingQueue.empty () && (stagingQueue.size () >= (size_t)STAGING_DELAY || videoEnded || !stagingPrimed)) { + + stagingPrimed = true; + VideoDecodeResult result = MapStaging (planes, time, duration); + + // A GPU that is lost, as when its driver restarts, cannot give back + // the frames it decoded, so decoding carries on in software + if (result == VIDEO_DECODE_ERROR && hardwareActive && !interrupted && SwitchToSoftware ()) continue; + + return result; + + } + + if (videoEnded) return VIDEO_DECODE_END; + + DWORD flags = 0; + LONGLONG timestamp = 0; + IMFSample* sample = NULL; + + HRESULT hr = ReadSample (videoReader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, &videoConnection, &flags, ×tamp, &sample); + + if (FAILED (hr) || (flags & MF_SOURCE_READERF_ERROR)) { + + if (sample) sample->Release (); + + // A seek gave up on this read, or it waits for one, and reading + // carries on after it + if (videoConnection.reopen || seeking) return VIDEO_DECODE_AGAIN; + + if (hardwareActive && !interrupted && SwitchToSoftware ()) continue; + + return videoConnection.reopen || seeking ? VIDEO_DECODE_AGAIN : VIDEO_DECODE_ERROR; + + } + + if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) ReadVideoFormat (); + + if (flags & MF_SOURCE_READERF_ENDOFSTREAM) { + + if (sample) sample->Release (); + videoEnded = true; + continue; + + } + + if (!sample) continue; + + // After switching to software, decoding restarts from a keyframe, and + // the frames that were already returned are skipped + if (skipUntil >= 0) { + + if (timestamp <= skipUntil) { + + sample->Release (); + continue; + + } + + skipUntil = -1; + + } + + LONGLONG sampleDuration = 0; + sample->GetSampleDuration (&sampleDuration); + + if (hardwareActive && CopyToStaging (sample, timestamp, sampleDuration)) { + + sample->Release (); + continue; + + } + + IMFMediaBuffer* buffer = NULL; + DWORD bufferCount = 0; + sample->GetBufferCount (&bufferCount); + + if (bufferCount == 1) { + + hr = sample->GetBufferByIndex (0, &buffer); + + } else { + + hr = sample->ConvertToContiguousBuffer (&buffer); + + } + + sample->Release (); + + if (FAILED (hr)) return VIDEO_DECODE_ERROR; + + BYTE* data = NULL; + LONG pitch = 0; + DWORD length = 0; + IMF2DBuffer2* buffer2D2 = NULL; + IMF2DBuffer* buffer2D = NULL; + + if (SUCCEEDED (buffer->QueryInterface (IID_PPV_ARGS (&buffer2D2)))) { + + BYTE* start = NULL; + + if (SUCCEEDED (buffer2D2->Lock2DSize (MF2DBuffer_LockFlags_Read, &data, &pitch, &start, &length))) { + + buffer2D = buffer2D2; + + } else { + + buffer2D2->Release (); + + } + + } + + if (!buffer2D && SUCCEEDED (buffer->QueryInterface (IID_PPV_ARGS (&buffer2D)))) { + + if (FAILED (buffer2D->Lock2D (&data, &pitch))) { + + buffer2D->Release (); + buffer2D = NULL; + + } + + } + + if (!buffer2D) { + + if (FAILED (buffer->Lock (&data, NULL, &length))) { + + buffer->Release (); + return VIDEO_DECODE_ERROR; + + } + + pitch = defaultStride > 0 ? defaultStride : bufferWidth; + + } + + lockedBuffer = buffer; + locked2D = buffer2D; + + if (pitch <= 0 || !data) { + + UnlockVideo (); + return VIDEO_DECODE_ERROR; + + } + + // The luma plane is normally the frame size from the media type, but + // some decoders align the surface further + int lumaRows = bufferHeight; + + if (length > 0) { + + int rows = (int)(length / pitch); + int alignedRows = (rows * 2) / 3; + if (alignedRows > lumaRows && alignedRows <= lumaRows + 64) lumaRows = alignedRows; + + if ((DWORD)pitch * (lumaRows + (lumaRows + 1) / 2) > length) { + + UnlockVideo (); + return VIDEO_DECODE_ERROR; + + } + + } + + GetPlanes (planes, data, pitch, lumaRows); + + lastFrameTime = timestamp; + videoPosition = timestamp; + *time = timestamp / 10000000.0; + *duration = sampleDuration > 0 ? sampleDuration / 10000000.0 : (frameRate > 0 ? 1.0 / frameRate : 0); + + return VIDEO_DECODE_OK; + + } + + } + + + void MFVideoBackend::GiveUp (MFConnection* connection) { + + // Closing the byte stream makes a read waiting on a stalled network + // source fail, and connecting is cancelled + connection->cancelled = true; + if (connection->byteStream) connection->byteStream->Close (); + if (connection->resolver && connection->cancelCookie) connection->resolver->CancelObjectCreation (connection->cancelCookie); + + } + + + void MFVideoBackend::Interrupt () { + + // A stream waiting on a stalled network source would wait until the + // connection times out, half a minute later + interrupted = true; + + std::lock_guard lock (streamMutex); + GiveUp (&audioConnection); + GiveUp (&videoConnection); + + } + + + void MFVideoBackend::GetPlanes (VideoPlanes* planes, BYTE* data, int pitch, int lumaRows) { + + planes->colorMatrix = colorMatrix; + planes->fullRange = fullRange; + planes->transfer = frameTransfer; + planes->width = cropWidth; + planes->height = cropHeight; + planes->y = data + cropY * pitch + cropX; + planes->yStride = pitch; + planes->u = data + lumaRows * pitch + (cropY / 2) * pitch + (cropX & ~1); + planes->v = planes->u + 1; + planes->uvStride = pitch; + planes->uvPixelStride = 2; + + } + + + VideoDecodeResult MFVideoBackend::MapStaging (VideoPlanes* planes, double* time, double* duration) { + + int index = stagingQueue.front (); + stagingQueue.pop_front (); + + D3D11_MAPPED_SUBRESOURCE mapped; + if (FAILED (context->Map (staging[index], 0, D3D11_MAP_READ, 0, &mapped))) return VIDEO_DECODE_ERROR; + + stagingMapped = index; + + // The chroma plane of a mapped NV12 texture follows its luma rows + GetPlanes (planes, (BYTE*)mapped.pData, (int)mapped.RowPitch, (int)stagingHeight); + + lastFrameTime = stagingTimes[index]; + videoPosition = stagingTimes[index]; + *time = stagingTimes[index] / 10000000.0; + *duration = stagingDurations[index] > 0 ? stagingDurations[index] / 10000000.0 : (frameRate > 0 ? 1.0 / frameRate : 0); + + return VIDEO_DECODE_OK; + + } + + + bool MFVideoBackend::Open (const char* path, bool hardwareDecoding, VideoStreamInfo* info) { + + Close (); + + if (!started || !path || !path[0]) return false; + + UseMultithreadedApartment (); + + url = GetURL (path); + if (url.empty ()) return false; + + network = url.find (L"://") != std::wstring::npos && url.compare (0, 5, L"file:") != 0; + + info->hasVideo = OpenVideo (hardwareDecoding) || (hardwareDecoding && OpenVideo (false)); + info->hasAudio = OpenAudio (); + + // A stream that was found but cannot be decoded, such as one in a codec + // that is not installed + info->videoUnsupported = !info->hasVideo && videoStreamFound; + info->audioUnsupported = !info->hasAudio && audioStreamFound; + + if (!info->hasVideo && !info->hasAudio) return false; + + IMFSourceReader* reader = videoReader ? videoReader : audioReader; + PROPVARIANT duration; + PropVariantInit (&duration); + + if (SUCCEEDED (reader->GetPresentationAttribute (MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &duration)) && duration.vt == VT_UI8) { + + info->duration = duration.uhVal.QuadPart / 10000000.0; + + } + + PropVariantClear (&duration); + + PROPVARIANT characteristics; + PropVariantInit (&characteristics); + + if (SUCCEEDED (reader->GetPresentationAttribute (MF_SOURCE_READER_MEDIASOURCE, MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS, &characteristics)) && characteristics.vt == VT_UI4) { + + canSeek = (characteristics.ulVal & MFMEDIASOURCE_CAN_SEEK) != 0; + + } + + PropVariantClear (&characteristics); + + info->audioChannels = audioChannels; + info->audioSampleRate = audioSampleRate; + info->frameRate = frameRate; + info->pixelAspectRatio = info->hasVideo ? pixelAspectRatio : 0; + info->rotation = info->hasVideo ? rotation : 0; + info->width = info->hasVideo ? cropWidth : 0; + info->height = info->hasVideo ? cropHeight : 0; + + return true; + + } + + + bool MFVideoBackend::OpenAudio () { + + IMFSourceReader* reader = NULL; + + reader = CreateReader (NULL, &audioConnection); + if (!reader) return false; + + reader->SetStreamSelection (MF_SOURCE_READER_ALL_STREAMS, FALSE); + + IMFMediaType* nativeType = NULL; + + if (FAILED (reader->SetStreamSelection (MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE)) || FAILED (reader->GetNativeMediaType (MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, &nativeType))) { + + reader->Release (); + return false; + + } + + audioStreamFound = true; + UINT32 sourceChannels = MFGetAttributeUINT32 (nativeType, MF_MT_AUDIO_NUM_CHANNELS, 0); + nativeType->Release (); + + IMFMediaType* type = NULL; + bool configured = false; + + // Ask the decoder to downmix first, and downmix here if it cannot + for (int attempt = 0; attempt < 2 && !configured; attempt++) { + + bool downmix = (attempt == 0 && sourceChannels > 2); + if (attempt == 1 && sourceChannels <= 2) break; + if (FAILED (_MFCreateMediaType (&type))) break; + + type->SetGUID (MF_MT_MAJOR_TYPE, MFMediaType_Audio); + type->SetGUID (MF_MT_SUBTYPE, MFAudioFormat_PCM); + type->SetUINT32 (MF_MT_AUDIO_BITS_PER_SAMPLE, 16); + if (downmix) type->SetUINT32 (MF_MT_AUDIO_NUM_CHANNELS, 2); + + configured = SUCCEEDED (reader->SetCurrentMediaType (MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, type)); + type->Release (); + type = NULL; + + } + + IMFMediaType* currentType = NULL; + + if (!configured || FAILED (reader->GetCurrentMediaType (MF_SOURCE_READER_FIRST_AUDIO_STREAM, ¤tType))) { + + reader->Release (); + return false; + + } + + UINT32 bits = MFGetAttributeUINT32 (currentType, MF_MT_AUDIO_BITS_PER_SAMPLE, 0); + UINT32 channels = MFGetAttributeUINT32 (currentType, MF_MT_AUDIO_NUM_CHANNELS, 0); + UINT32 sampleRate = MFGetAttributeUINT32 (currentType, MF_MT_AUDIO_SAMPLES_PER_SECOND, 0); + currentType->Release (); + + if (bits != 16 || channels == 0 || channels > 8 || sampleRate == 0) { + + reader->Release (); + return false; + + } + + audioReader = reader; + audioSampleRate = (int)sampleRate; + audioSourceChannels = (int)channels; + audioChannels = channels > 2 ? 2 : (int)channels; + audioConverter.Reset (); + audioPacketChannels = audioSourceChannels; + audioPacketRate = audioSampleRate; + + return true; + + } + + + void MFVideoBackend::ReadAudioFormat () { + + IMFMediaType* type = NULL; + if (FAILED (audioReader->GetCurrentMediaType (MF_SOURCE_READER_FIRST_AUDIO_STREAM, &type))) return; + + UINT32 bits = MFGetAttributeUINT32 (type, MF_MT_AUDIO_BITS_PER_SAMPLE, 0); + UINT32 channels = MFGetAttributeUINT32 (type, MF_MT_AUDIO_NUM_CHANNELS, 0); + UINT32 sampleRate = MFGetAttributeUINT32 (type, MF_MT_AUDIO_SAMPLES_PER_SECOND, 0); + type->Release (); + + // A format that cannot be read is dropped, rather than played as noise + bool readable = (bits == 16 && channels > 0 && channels <= 8 && sampleRate > 0); + audioPacketChannels = readable ? (int)channels : 0; + audioPacketRate = readable ? (int)sampleRate : 0; + + } + + + bool MFVideoBackend::OpenVideo (bool hardware) { + + if (hardware) return OpenVideoReader (true, true); + + // Software decoding converts frames with the basic video processing, or + // the advanced processing of Windows 8 and later for what that cannot, + // such as 10-bit video + return OpenVideoReader (false, false) || OpenVideoReader (false, true); + + } + + + bool MFVideoBackend::OpenVideoReader (bool hardware, bool advanced, bool keepDevice) { + + IMFAttributes* attributes = NULL; + if (FAILED (_MFCreateAttributes (&attributes, 4))) return false; + + if (hardware && CreateDeviceManager ()) { + + attributes->SetUnknown (MF_SOURCE_READER_D3D_MANAGER, deviceManager); + attributes->SetUINT32 (MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE); + attributes->SetUINT32 (MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, TRUE); + + } else { + + hardware = false; + attributes->SetUINT32 (advanced ? MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING : MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE); + + } + + IMFSourceReader* reader = CreateReader (attributes, &videoConnection); + attributes->Release (); + + if (!reader) { + + if (hardware && !keepDevice) ReleaseDeviceManager (); + return false; + + } + + reader->SetStreamSelection (MF_SOURCE_READER_ALL_STREAMS, FALSE); + + bool configured = false; + + if (SUCCEEDED (reader->SetStreamSelection (MF_SOURCE_READER_FIRST_VIDEO_STREAM, TRUE))) { + + videoStreamFound = true; + + // The advanced video processing turns and stretches frames to show + // them upright with square pixels, which other platforms leave to the + // application, so frames are asked for with the stream's own rotation + // and pixel shape to keep them as they are stored + UINT32 streamRotation = 0; + UINT64 streamPixelShape = 0; + VideoColorTransfer streamTransfer = VIDEO_COLOR_TRANSFER_SDR; + IMFMediaType* nativeType = NULL; + + if (SUCCEEDED (reader->GetNativeMediaType (MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &nativeType))) { + + streamRotation = MFGetAttributeUINT32 (nativeType, MF_MT_VIDEO_ROTATION, 0); + nativeType->GetUINT64 (MF_MT_PIXEL_ASPECT_RATIO, &streamPixelShape); + streamTransfer = ReadTransfer (nativeType); + nativeType->Release (); + + } + + // HDR is shown in standard range. The hardware video processing tone + // maps PQ well, and is asked to. HLG, which it leaves far too bright, + // and HDR decoded in software, which is not tone mapped, are asked for + // as they are, and VideoDecoder tone maps them. + bool hdr = streamTransfer != VIDEO_COLOR_TRANSFER_SDR; + bool convertHDR = hardware && streamTransfer == VIDEO_COLOR_TRANSFER_PQ; + + // Frames are asked for progressive, so the video processing + // deinterlaces interlaced video, unless it cannot + for (int attempt = 0; attempt < (hdr ? 4 : 2) && !configured; attempt++) { + + IMFMediaType* type = NULL; + if (FAILED (_MFCreateMediaType (&type))) break; + + bool describeHDR = hdr && attempt < 2; + + type->SetGUID (MF_MT_MAJOR_TYPE, MFMediaType_Video); + type->SetGUID (MF_MT_SUBTYPE, MFVideoFormat_NV12); + type->SetUINT32 (MF_MT_VIDEO_ROTATION, streamRotation); + if (streamPixelShape) type->SetUINT64 (MF_MT_PIXEL_ASPECT_RATIO, streamPixelShape); + if (attempt % 2 == 0) type->SetUINT32 (MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); + + if (describeHDR) { + + type->SetUINT32 (MF_MT_TRANSFER_FUNCTION, convertHDR ? MFVideoTransFunc_709 : (streamTransfer == VIDEO_COLOR_TRANSFER_PQ ? TRANSFER_PQ : TRANSFER_HLG)); + type->SetUINT32 (MF_MT_VIDEO_PRIMARIES, convertHDR ? MFVideoPrimaries_BT709 : PRIMARIES_BT2020); + type->SetUINT32 (MF_MT_YUV_MATRIX, convertHDR ? MFVideoTransferMatrix_BT709 : MATRIX_BT2020); + + } + + configured = SUCCEEDED (reader->SetCurrentMediaType (MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, type)); + type->Release (); + + // Without asking, hardware video processing converts HDR itself + if (configured) frameTransfer = (describeHDR ? convertHDR : hardware) ? VIDEO_COLOR_TRANSFER_SDR : streamTransfer; + + } + + } + + if (!configured) { + + reader->Release (); + if (hardware && !keepDevice) ReleaseDeviceManager (); + return false; + + } + + videoReader = reader; + videoAdvanced = advanced; + hardwareActive = hardware; + ReadVideoFormat (); + + if (cropWidth <= 0 || cropHeight <= 0) { + + videoReader->Release (); + videoReader = NULL; + if (hardware && !keepDevice) ReleaseDeviceManager (); + return false; + + } + + return true; + + } + + + void MFVideoBackend::PrepareSeek () { + + // A stream waiting on a stalled network source would hold up the seek + // until the connection times out, half a minute later. Reads from a + // source that is still sending finish soon, and those that have not by + // then are given up. Their readers open again once the seek is done. + if (!network || !canSeek) return; + + std::unique_lock lock (streamMutex); + seeking = true; + readCondition.wait_for (lock, std::chrono::milliseconds (SEEK_WAIT_MS), [this] { return !audioConnection.busy && !videoConnection.busy; }); + + if (audioConnection.busy) { + + audioConnection.reopen = true; + GiveUp (&audioConnection); + + } + + if (videoConnection.busy) { + + videoConnection.reopen = true; + GiveUp (&videoConnection); + + } + + } + + + HRESULT MFVideoBackend::ReadSample (IMFSourceReader* reader, DWORD stream, MFConnection* connection, DWORD* flags, LONGLONG* timestamp, IMFSample** sample) { + + if (!SetBusy (connection, true)) return E_ABORT; + HRESULT hr = reader->ReadSample (stream, 0, NULL, flags, timestamp, sample); + SetBusy (connection, false); + return hr; + + } + + + VideoDecodeResult MFVideoBackend::ReopenAudio () { + + // A seek gave up on a read that waited on a stalled network source, so + // the reader opens again, at the time seeked to, and plays on in the + // format it opened with + audioConnection.reopen = false; + + if (!SetBusy (&audioConnection, true)) { + + audioConnection.reopen = true; + return VIDEO_DECODE_AGAIN; + + } + + ReleaseReader (&audioReader, true); + SetByteStream (&audioConnection, NULL); + + int channels = audioChannels; + int sampleRate = audioSampleRate; + bool opened = OpenAudio (); + SetBusy (&audioConnection, false); + + if (!opened || audioConnection.reopen) { + + // One that cannot open is tried again at the next seek + bool again = audioConnection.reopen; + audioConnection.reopen = true; + + if (audioReader) { + + audioReader->Release (); + audioReader = NULL; + + } + + return again ? VIDEO_DECODE_AGAIN : VIDEO_DECODE_ERROR; + + } + + audioChannels = channels; + audioSampleRate = sampleRate; + + PROPVARIANT position; + PropVariantInit (&position); + position.vt = VT_I8; + position.hVal.QuadPart = audioConnection.reopenPosition; + audioReader->SetCurrentPosition (TIME_FORMAT_100NS, position); + + return VIDEO_DECODE_OK; + + } + + + VideoDecodeResult MFVideoBackend::ReopenVideo () { + + // A seek gave up on a read that waited on a stalled network source, so + // the reader opens again as it was, at the time seeked to. The device + // stays, as OpenGL can still be showing frames from its textures. + videoConnection.reopen = false; + + if (!SetBusy (&videoConnection, true)) { + + videoConnection.reopen = true; + return VIDEO_DECODE_AGAIN; + + } + + ReleaseProcessor (); + ReleaseReader (&videoReader, true); + SetByteStream (&videoConnection, NULL); + bool opened = OpenVideoReader (hardwareActive, videoAdvanced, true); + SetBusy (&videoConnection, false); + + if (!opened || videoConnection.reopen) { + + // One that cannot open is tried again at the next seek + bool again = videoConnection.reopen; + videoConnection.reopen = true; + + if (videoReader) { + + videoReader->Release (); + videoReader = NULL; + + } + + return again ? VIDEO_DECODE_AGAIN : VIDEO_DECODE_ERROR; + + } + + PROPVARIANT position; + PropVariantInit (&position); + position.vt = VT_I8; + position.hVal.QuadPart = videoConnection.reopenPosition; + videoReader->SetCurrentPosition (TIME_FORMAT_100NS, position); + + return VIDEO_DECODE_OK; + + } + + + IUnknown* MFVideoBackend::ResolveURL (IMFSourceResolver* resolver, MFConnection* connection) { + + // Connecting waits for the server, which Interrupt and PrepareSeek can + // cancel, so it is made asynchronously. The wait ends when it is + // cancelled, whether or not Media Foundation calls back. + MFResolveCallback* callback = new MFResolveCallback (resolver); + IUnknown* cookie = NULL; + IUnknown* object = NULL; + + if (callback->done && SUCCEEDED (resolver->BeginCreateObjectFromURL (url.c_str (), MF_RESOLUTION_BYTESTREAM, NULL, &cookie, callback, NULL))) { + + { + std::lock_guard lock (streamMutex); + connection->cancelCookie = cookie; + connection->resolver = resolver; + if (connection->cancelled && cookie) resolver->CancelObjectCreation (cookie); + } + + DWORD wait = WAIT_TIMEOUT; + + while (!connection->cancelled && (wait = WaitForSingleObject (callback->done, 50)) == WAIT_TIMEOUT) {} + + { + std::lock_guard lock (streamMutex); + connection->cancelCookie = NULL; + connection->resolver = NULL; + } + + if (cookie) cookie->Release (); + if (wait == WAIT_OBJECT_0) object = callback->TakeObject (); + + } + + callback->Release (); + return object; + + } + + + void MFVideoBackend::ReadVideoFormat () { + + IMFMediaType* type = NULL; + if (FAILED (videoReader->GetCurrentMediaType (MF_SOURCE_READER_FIRST_VIDEO_STREAM, &type))) return; + + UINT32 width = 0; + UINT32 height = 0; + MFGetAttributeSize (type, MF_MT_FRAME_SIZE, &width, &height); + + bufferWidth = (int)width; + bufferHeight = (int)height; + cropX = 0; + cropY = 0; + cropWidth = (int)width; + cropHeight = (int)height; + + MFVideoArea area; + + if (SUCCEEDED (type->GetBlob (MF_MT_MINIMUM_DISPLAY_APERTURE, (UINT8*)&area, sizeof (area), NULL))) { + + int x = area.OffsetX.value; + int y = area.OffsetY.value; + + if (x >= 0 && y >= 0 && area.Area.cx > 0 && area.Area.cy > 0 && x + area.Area.cx <= (int)width && y + area.Area.cy <= (int)height) { + + cropX = x; + cropY = y; + cropWidth = area.Area.cx; + cropHeight = area.Area.cy; + + } + + } + + UINT32 stride = 0; + defaultStride = SUCCEEDED (type->GetUINT32 (MF_MT_DEFAULT_STRIDE, &stride)) ? (LONG)stride : (LONG)width; + + UINT32 numerator = 0; + UINT32 denominator = 0; + + if (SUCCEEDED (MFGetAttributeRatio (type, MF_MT_FRAME_RATE, &numerator, &denominator)) && denominator > 0) { + + frameRate = (double)numerator / denominator; + + } + + // Video processing can drop the color attributes, so fall back to the + // stream's own media type + IMFMediaType* nativeType = NULL; + videoReader->GetNativeMediaType (MF_SOURCE_READER_FIRST_VIDEO_STREAM, MF_SOURCE_READER_CURRENT_TYPE_INDEX, &nativeType); + + UINT32 matrix = MFGetAttributeUINT32 (type, MF_MT_YUV_MATRIX, MFVideoTransferMatrix_Unknown); + if (matrix == MFVideoTransferMatrix_Unknown && nativeType) matrix = MFGetAttributeUINT32 (nativeType, MF_MT_YUV_MATRIX, MFVideoTransferMatrix_Unknown); + + UINT32 range = MFGetAttributeUINT32 (type, MF_MT_VIDEO_NOMINAL_RANGE, MFNominalRange_Unknown); + if (range == MFNominalRange_Unknown && nativeType) range = MFGetAttributeUINT32 (nativeType, MF_MT_VIDEO_NOMINAL_RANGE, MFNominalRange_Unknown); + + // Media Foundation gives the rotation that was applied to the picture, + // counterclockwise, which is undone by turning it as far clockwise. The + // basic video processing keeps frames as they are stored, and says so. + // The advanced video processing turns and stretches them to show them + // upright with square pixels, whatever is asked for, and leaves the + // rotation out, so only what it leaves undone is reported. + IMFMediaType* streamType = nativeType ? nativeType : type; + int streamRotation = (int)MFGetAttributeUINT32 (streamType, MF_MT_VIDEO_ROTATION, 0); + UINT32 streamWidth = 0; + UINT32 streamHeight = 0; + MFGetAttributeSize (streamType, MF_MT_FRAME_SIZE, &streamWidth, &streamHeight); + + UINT32 frameRotation = 0; + bool quarterTurn = (streamRotation == 90 || streamRotation == 270); + bool sidesSwapped = cropWidth != cropHeight && abs (cropWidth - (int)streamHeight) <= 16 && abs (cropHeight - (int)streamWidth) <= 16; + + if (SUCCEEDED (type->GetUINT32 (MF_MT_VIDEO_ROTATION, &frameRotation))) { + + rotation = (int)frameRotation; + + } else { + + rotation = quarterTurn && !sidesSwapped ? streamRotation : 0; + + } + + // Frames stretched to square pixels no longer match the stream's size + bool turned = quarterTurn && rotation == 0; + int storedWidth = turned ? cropHeight : cropWidth; + int storedHeight = turned ? cropWidth : cropHeight; + bool stretched = streamWidth > 0 && streamHeight > 0 && (abs (storedWidth - (int)streamWidth) > 2 || abs (storedHeight - (int)streamHeight) > 16); + + UINT32 pixelWidth = 0; + UINT32 pixelHeight = 0; + pixelAspectRatio = !stretched && SUCCEEDED (MFGetAttributeRatio (streamType, MF_MT_PIXEL_ASPECT_RATIO, &pixelWidth, &pixelHeight)) && pixelWidth > 0 && pixelHeight > 0 ? (double)pixelWidth / pixelHeight : 0; + + if (nativeType) nativeType->Release (); + type->Release (); + + switch (matrix) { + + case MFVideoTransferMatrix_BT601: colorMatrix = VIDEO_COLOR_MATRIX_BT601; break; + case MFVideoTransferMatrix_BT709: colorMatrix = VIDEO_COLOR_MATRIX_BT709; break; + case 4: case 5: colorMatrix = VIDEO_COLOR_MATRIX_BT2020; break; + default: colorMatrix = GetDefaultColorMatrix (cropWidth, cropHeight); break; + + } + + fullRange = (range == MFNominalRange_0_255); + + } + + + void MFVideoBackend::ReleaseDeviceManager () { + + ReleaseProcessor (); + + if (outputQuery) { + + outputQuery->Release (); + outputQuery = NULL; + + } + + for (int i = 0; i < OUTPUT_TEXTURES; i++) { + + // Textures still shared with OpenGL are left for ReleaseTextures + if (outputs[i].interop) continue; + if (outputs[i].texture) outputs[i].texture->Release (); + outputs[i].texture = NULL; + + } + + if (videoContext) { + + videoContext->Release (); + videoContext = NULL; + + } + + if (videoDevice) { + + videoDevice->Release (); + videoDevice = NULL; + + } + + if (context) ClearStaging (); + + for (int i = 0; i < STAGING_TEXTURES; i++) { + + if (staging[i]) staging[i]->Release (); + staging[i] = NULL; + + } + + stagingHeight = 0; + stagingWidth = 0; + + if (context) { + + context->Release (); + context = NULL; + + } + + if (deviceManager) { + + deviceManager->Release (); + deviceManager = NULL; + + } + + if (device) { + + device->Release (); + device = NULL; + + } + + } + + + bool MFVideoBackend::Seek (double time) { + + seeking = false; + + // A source that cannot seek, such as a live stream, is left playing + if (!canSeek) return false; + + UnlockVideo (); + lastFrameTime = -1; + skipUntil = -1; + stagingPrimed = false; + stagingQueue.clear (); + videoEnded = false; + + PROPVARIANT position; + PropVariantInit (&position); + position.vt = VT_I8; + position.hVal.QuadPart = (LONGLONG)(time * 10000000.0); + + bool success = true; + + // A reader that a seek gave up on opens again from the new position + if (videoConnection.reopen) { + + videoConnection.reopenPosition = position.hVal.QuadPart; + videoPosition = position.hVal.QuadPart; + + } else if (videoReader) { + + success = SUCCEEDED (videoReader->SetCurrentPosition (TIME_FORMAT_100NS, position)) && success; + videoPosition = position.hVal.QuadPart; + + } + + if (audioConnection.reopen) { + + audioConnection.reopenPosition = position.hVal.QuadPart; + + } else if (audioReader) { + + success = SUCCEEDED (audioReader->SetCurrentPosition (TIME_FORMAT_100NS, position)) && success; + + } + + audioConverter.Reset (); + return success; + + } + + + bool MFVideoBackend::CreateProcessor (UINT inputWidth, UINT inputHeight) { + + ReleaseProcessor (); + + if (!videoDevice && (FAILED (device->QueryInterface (IID_PPV_ARGS (&videoDevice))) || FAILED (context->QueryInterface (IID_PPV_ARGS (&videoContext))))) return false; + + // The output keeps the size the video opened with, so the textures stay + // shared with OpenGL if the stream changes size + if (!outputs[0].texture) { + + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = cropWidth; + desc.Height = cropHeight; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + + for (int i = 0; i < OUTPUT_TEXTURES; i++) { + + if (FAILED (device->CreateTexture2D (&desc, NULL, &outputs[i].texture))) return false; + + } + + std::lock_guard lock (outputMutex); + freeOutputs.clear (); + for (int i = 0; i < OUTPUT_TEXTURES; i++) freeOutputs.push_back (i); + + } + + D3D11_TEXTURE2D_DESC outputDesc; + outputs[0].texture->GetDesc (&outputDesc); + + D3D11_VIDEO_PROCESSOR_CONTENT_DESC contentDesc = {}; + contentDesc.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE; + contentDesc.InputWidth = inputWidth; + contentDesc.InputHeight = inputHeight; + contentDesc.OutputWidth = outputDesc.Width; + contentDesc.OutputHeight = outputDesc.Height; + contentDesc.Usage = D3D11_VIDEO_USAGE_PLAYBACK_NORMAL; + + UINT support = 0; + + if (FAILED (videoDevice->CreateVideoProcessorEnumerator (&contentDesc, &processorEnumerator)) + || FAILED (processorEnumerator->CheckVideoProcessorFormat (DXGI_FORMAT_B8G8R8A8_UNORM, &support)) + || !(support & D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_OUTPUT) + || FAILED (videoDevice->CreateVideoProcessor (processorEnumerator, 0, &processor))) { + + ReleaseProcessor (); + return false; + + } + + D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC viewDesc = {}; + viewDesc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D; + + for (int i = 0; i < OUTPUT_TEXTURES; i++) { + + if (FAILED (videoDevice->CreateVideoProcessorOutputView (outputs[i].texture, processorEnumerator, &viewDesc, &outputs[i].view))) { + + ReleaseProcessor (); + return false; + + } + + } + + RECT source = { cropX, cropY, cropX + cropWidth, cropY + cropHeight }; + RECT target = { 0, 0, (LONG)outputDesc.Width, (LONG)outputDesc.Height }; + + videoContext->VideoProcessorSetStreamFrameFormat (processor, 0, D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE); + videoContext->VideoProcessorSetStreamAutoProcessingMode (processor, 0, FALSE); + videoContext->VideoProcessorSetStreamSourceRect (processor, 0, TRUE, &source); + videoContext->VideoProcessorSetStreamDestRect (processor, 0, TRUE, &target); + videoContext->VideoProcessorSetOutputTargetRect (processor, TRUE, &target); + + ID3D11VideoContext1* videoContext1 = NULL; + + if (colorMatrix == VIDEO_COLOR_MATRIX_BT2020 && SUCCEEDED (videoContext->QueryInterface (IID_PPV_ARGS (&videoContext1)))) { + + // BT.2020 needs the DXGI color spaces from Windows 10 + videoContext1->VideoProcessorSetStreamColorSpace1 (processor, 0, fullRange ? DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020 : DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020); + videoContext1->VideoProcessorSetOutputColorSpace1 (processor, DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709); + videoContext1->Release (); + + } else { + + D3D11_VIDEO_PROCESSOR_COLOR_SPACE inputSpace = {}; + inputSpace.YCbCr_Matrix = (colorMatrix == VIDEO_COLOR_MATRIX_BT601) ? 0 : 1; + inputSpace.Nominal_Range = fullRange ? D3D11_VIDEO_PROCESSOR_NOMINAL_RANGE_0_255 : D3D11_VIDEO_PROCESSOR_NOMINAL_RANGE_16_235; + videoContext->VideoProcessorSetStreamColorSpace (processor, 0, &inputSpace); + + D3D11_VIDEO_PROCESSOR_COLOR_SPACE outputSpace = {}; + outputSpace.RGB_Range = 0; + videoContext->VideoProcessorSetOutputColorSpace (processor, &outputSpace); + + } + + processorWidth = inputWidth; + processorHeight = inputHeight; + return true; + + } + + + VideoDecodeResult MFVideoBackend::DecodeVideoTexture (VideoTextureFrame** frame, double* time, double* duration) { + + *frame = NULL; + UnlockVideo (); + InitializeCOM (); + + if (videoConnection.reopen) { + + VideoDecodeResult result = ReopenVideo (); + if (result != VIDEO_DECODE_OK) return result; + + } + + if (!videoReader) return VIDEO_DECODE_END; + + while (true) { + + DWORD flags = 0; + LONGLONG timestamp = 0; + IMFSample* sample = NULL; + + HRESULT hr = ReadSample (videoReader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, &videoConnection, &flags, ×tamp, &sample); + + if (FAILED (hr) || (flags & MF_SOURCE_READERF_ERROR)) { + + if (sample) sample->Release (); + + // A seek gave up on this read, or it waits for one, and reading + // carries on after it + if (videoConnection.reopen || seeking) return VIDEO_DECODE_AGAIN; + + // Continuing in software means decoding into memory from here on + if (hardwareActive && !interrupted && SwitchToSoftware ()) return VIDEO_DECODE_OK; + + return videoConnection.reopen || seeking ? VIDEO_DECODE_AGAIN : VIDEO_DECODE_ERROR; + + } + + if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) ReadVideoFormat (); + + if (flags & MF_SOURCE_READERF_ENDOFSTREAM) { + + if (sample) sample->Release (); + return VIDEO_DECODE_END; + + } + + if (!sample) continue; + + LONGLONG sampleDuration = 0; + sample->GetSampleDuration (&sampleDuration); + + IMFMediaBuffer* buffer = NULL; + IMFDXGIBuffer* dxgiBuffer = NULL; + ID3D11Texture2D* texture = NULL; + UINT subresource = 0; + int index = -1; + + if (SUCCEEDED (sample->GetBufferByIndex (0, &buffer)) && SUCCEEDED (buffer->QueryInterface (IID_PPV_ARGS (&dxgiBuffer))) + && SUCCEEDED (dxgiBuffer->GetResource (IID_PPV_ARGS (&texture))) && SUCCEEDED (dxgiBuffer->GetSubresourceIndex (&subresource))) { + + D3D11_TEXTURE2D_DESC desc; + texture->GetDesc (&desc); + + if (processor || CreateProcessor (desc.Width, desc.Height)) { + + if (desc.Width != processorWidth || desc.Height != processorHeight) CreateProcessor (desc.Width, desc.Height); + + } + + std::pair key (texture, subresource); + ID3D11VideoProcessorInputView* inputView = NULL; + + if (processor) { + + std::map, ID3D11VideoProcessorInputView*>::iterator found = inputViews.find (key); + + if (found != inputViews.end ()) { + + inputView = found->second; + + } else { + + D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputDesc = {}; + inputDesc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D; + inputDesc.Texture2D.ArraySlice = subresource; + + if (SUCCEEDED (videoDevice->CreateVideoProcessorInputView (texture, processorEnumerator, &inputDesc, &inputView))) { + + inputViews[key] = inputView; + + } + + } + + } + + if (inputView) { + + // Wait for the reader to give back a texture + std::unique_lock lock (outputMutex); + outputCondition.wait_for (lock, std::chrono::seconds (5), [this] { return !freeOutputs.empty (); }); + + if (!freeOutputs.empty ()) { + + index = freeOutputs.back (); + freeOutputs.pop_back (); + + } + + } + + if (index >= 0) { + + D3D11_VIDEO_PROCESSOR_STREAM stream = {}; + stream.Enable = TRUE; + stream.pInputSurface = inputView; + + if (FAILED (videoContext->VideoProcessorBlt (processor, outputs[index].view, 0, 1, &stream))) { + + ReleaseTextureFrame (&outputFrames[index]); + index = -1; + + } else { + + // OpenGL samples the shared texture through another API, so wait + // for the GPU to finish writing it before the frame is queued + if (!outputQuery) { + + D3D11_QUERY_DESC queryDesc = {}; + queryDesc.Query = D3D11_QUERY_EVENT; + device->CreateQuery (&queryDesc, &outputQuery); + + } + + if (outputQuery) { + + context->End (outputQuery); + context->Flush (); + BOOL done = FALSE; + while (context->GetData (outputQuery, &done, sizeof (done), 0) == S_FALSE) std::this_thread::yield (); + + } else { + + context->Flush (); + + } + + } + + } + + } + + if (texture) texture->Release (); + if (dxgiBuffer) dxgiBuffer->Release (); + if (buffer) buffer->Release (); + sample->Release (); + + if (index < 0) { + + // Frames that cannot be converted here are decoded into memory + texturesFailed = true; + return VIDEO_DECODE_OK; + + } + + outputFrames[index].width = cropWidth; + outputFrames[index].height = cropHeight; + *frame = &outputFrames[index]; + + lastFrameTime = timestamp; + videoPosition = timestamp; + *time = timestamp / 10000000.0; + *duration = sampleDuration > 0 ? sampleDuration / 10000000.0 : (frameRate > 0 ? 1.0 / frameRate : 0); + + return VIDEO_DECODE_OK; + + } + + } + + + unsigned int MFVideoBackend::LockTexture (VideoTextureFrame* frame) { + + #ifdef LIME_VIDEO_WGL_INTEROP + MFOutputTexture& output = outputs[((MFTextureFrame*)frame)->index]; + HGLRC glContext = wglGetCurrentContext (); + + if (interopDevice && glContext != interopContext) { + + // Another context is current, such as another window's, where the + // textures shared with the first one do not exist, so they are shared + // again and the first context keeps its own until it is deleted + for (int i = 0; i < OUTPUT_TEXTURES; i++) { + + outputs[i].glTexture = 0; + outputs[i].interop = NULL; + outputs[i].locked = false; + + } + + interopDevice = NULL; + interopFailed = false; + + } + + if (!interopDevice) { + + if (interopFailed || !glContext) return 0; + + _wglDXOpenDeviceNV = (WGLDXOpenDeviceFunc)wglGetProcAddress ("wglDXOpenDeviceNV"); + _wglDXCloseDeviceNV = (WGLDXCloseDeviceFunc)wglGetProcAddress ("wglDXCloseDeviceNV"); + _wglDXRegisterObjectNV = (WGLDXRegisterObjectFunc)wglGetProcAddress ("wglDXRegisterObjectNV"); + _wglDXUnregisterObjectNV = (WGLDXUnregisterObjectFunc)wglGetProcAddress ("wglDXUnregisterObjectNV"); + _wglDXLockObjectsNV = (WGLDXLockObjectsFunc)wglGetProcAddress ("wglDXLockObjectsNV"); + _wglDXUnlockObjectsNV = (WGLDXUnlockObjectsFunc)wglGetProcAddress ("wglDXUnlockObjectsNV"); + + if (_wglDXOpenDeviceNV && _wglDXCloseDeviceNV && _wglDXRegisterObjectNV && _wglDXUnregisterObjectNV && _wglDXLockObjectsNV && _wglDXUnlockObjectsNV) { + + interopDevice = _wglDXOpenDeviceNV (device); + interopContext = glContext; + + } + + if (!interopDevice) { + + interopFailed = true; + return 0; + + } + + } + + if (!output.interop) { + + glGenTextures (1, &output.glTexture); + output.interop = _wglDXRegisterObjectNV (interopDevice, output.texture, output.glTexture, GL_TEXTURE_2D, WGL_ACCESS_READ_ONLY); + + if (!output.interop) { + + glDeleteTextures (1, &output.glTexture); + output.glTexture = 0; + interopFailed = true; + return 0; + + } + + if (!_wglDXLockObjectsNV (interopDevice, 1, &output.interop)) return 0; + output.locked = true; + + // Without mipmaps, the default minification filter would leave the + // texture incomplete + GLint previousTexture = 0; + glGetIntegerv (GL_TEXTURE_BINDING_2D, &previousTexture); + glBindTexture (GL_TEXTURE_2D, output.glTexture); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0x812F); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 0x812F); + glBindTexture (GL_TEXTURE_2D, previousTexture); + + return output.glTexture; + + } + + if (!_wglDXLockObjectsNV (interopDevice, 1, &output.interop)) return 0; + output.locked = true; + return output.glTexture; + #else + return 0; + #endif + + } + + + void MFVideoBackend::ReleaseProcessor () { + + for (std::map, ID3D11VideoProcessorInputView*>::iterator it = inputViews.begin (); it != inputViews.end (); ++it) { + + it->second->Release (); + + } + + inputViews.clear (); + + for (int i = 0; i < OUTPUT_TEXTURES; i++) { + + if (outputs[i].view) outputs[i].view->Release (); + outputs[i].view = NULL; + + } + + if (processor) { + + processor->Release (); + processor = NULL; + + } + + if (processorEnumerator) { + + processorEnumerator->Release (); + processorEnumerator = NULL; + + } + + processorHeight = 0; + processorWidth = 0; + + } + + + void MFVideoBackend::ReleaseTextureFrame (VideoTextureFrame* frame) { + + { + std::lock_guard lock (outputMutex); + freeOutputs.push_back (((MFTextureFrame*)frame)->index); + } + + outputCondition.notify_one (); + + } + + + void MFVideoBackend::ReleaseTextures () { + + #ifdef LIME_VIDEO_WGL_INTEROP + bool current = interopDevice && wglGetCurrentContext () == interopContext; + + for (int i = 0; i < OUTPUT_TEXTURES; i++) { + + if (outputs[i].interop) { + + if (current && outputs[i].locked) _wglDXUnlockObjectsNV (interopDevice, 1, &outputs[i].interop); + if (current) _wglDXUnregisterObjectNV (interopDevice, outputs[i].interop); + outputs[i].interop = NULL; + outputs[i].locked = false; + + } + + if (outputs[i].glTexture) { + + if (current) glDeleteTextures (1, &outputs[i].glTexture); + outputs[i].glTexture = 0; + + } + + } + + // Objects from a context that is no longer current are left with it + if (interopDevice && wglGetCurrentContext () == interopContext) { + + _wglDXCloseDeviceNV (interopDevice); + + } + + interopContext = NULL; + interopDevice = NULL; + #endif + + interopFailed = false; + + } + + + bool MFVideoBackend::SetBusy (MFConnection* connection, bool busy) { + + // PrepareSeek waits for reads in progress, and gives up those that wait + // too long. Reads do not start while a seek waits, and a read that + // starts after closing has begun is given up. + std::lock_guard lock (streamMutex); + + if (busy) { + + if (seeking) return false; + connection->busy = true; + connection->cancelled = interrupted.load (); + + } else { + + connection->busy = false; + readCondition.notify_all (); + + } + + return true; + + } + + + void MFVideoBackend::SetByteStream (MFConnection* connection, IMFByteStream* value) { + + // Interrupt and PrepareSeek close the streams from another thread, so a + // stream that opened after they gave up on its connection is closed now + std::lock_guard lock (streamMutex); + if (connection->byteStream) connection->byteStream->Release (); + connection->byteStream = value; + if (value && connection->cancelled) value->Close (); + + } + + + bool MFVideoBackend::SupportsTextures () { + + // HDR frames that VideoDecoder tone maps are decoded into memory + return hardwareActive && device && !texturesFailed && frameTransfer == VIDEO_COLOR_TRANSFER_SDR; + + } + + + bool MFVideoBackend::SwitchToSoftware () { + + // Some drivers fail to decode a stream they accepted, and a GPU can be + // lost, so decoding continues from the same position in software. The + // device and the textures shared with OpenGL stay until closing, as the + // GL thread can still be showing frames from them. + ClearStaging (); + ReleaseProcessor (); + videoReader->Release (); + videoReader = NULL; + + // The advanced video processing turns and stretches frames as the + // hardware decoding did, so they keep the shape they opened with. A + // network source is opened again, which a seek can give up. + if (!SetBusy (&videoConnection, true)) return false; + bool opened = OpenVideoReader (false, true) || OpenVideoReader (false, false); + SetBusy (&videoConnection, false); + + if (!opened) return false; + + PROPVARIANT position; + PropVariantInit (&position); + position.vt = VT_I8; + position.hVal.QuadPart = videoPosition; + videoReader->SetCurrentPosition (TIME_FORMAT_100NS, position); + + // Decoding resumes from the keyframe before, so the frames up to the + // last one returned are skipped + skipUntil = lastFrameTime; + return true; + + } + + + void MFVideoBackend::UnlockTexture (VideoTextureFrame* frame) { + + #ifdef LIME_VIDEO_WGL_INTEROP + MFOutputTexture& output = outputs[((MFTextureFrame*)frame)->index]; + if (interopDevice && output.interop && output.locked) _wglDXUnlockObjectsNV (interopDevice, 1, &output.interop); + output.locked = false; + #endif + + } + + + void MFVideoBackend::UnlockVideo () { + + if (stagingMapped >= 0) { + + context->Unmap (staging[stagingMapped], 0); + stagingMapped = -1; + + } + + if (locked2D) { + + locked2D->Unlock2D (); + locked2D->Release (); + locked2D = NULL; + + } else if (lockedBuffer) { + + lockedBuffer->Unlock (); + + } + + if (lockedBuffer) { + + lockedBuffer->Release (); + lockedBuffer = NULL; + + } + + } + + + VideoBackend* VideoBackend::Create () { + + return LoadMediaFoundation () ? new MFVideoBackend () : NULL; + + } + + + bool VideoBackend::IsSupported () { + + return LoadMediaFoundation (); + + } + + +} + + +#endif diff --git a/project/src/media/video/MediaCodecVideoBackend.cpp b/project/src/media/video/MediaCodecVideoBackend.cpp new file mode 100644 index 0000000000..c34ca68bb6 --- /dev/null +++ b/project/src/media/video/MediaCodecVideoBackend.cpp @@ -0,0 +1,3017 @@ +#include + + +#ifdef LIME_VIDEO_MEDIACODEC + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef LIME_VIDEO_STANDALONE +#include +#include +#include +#endif + +#ifndef EGL_NATIVE_BUFFER_ANDROID +#define EGL_NATIVE_BUFFER_ANDROID 0x3140 +#endif +#ifndef EGL_SYNC_NATIVE_FENCE_ANDROID +#define EGL_SYNC_NATIVE_FENCE_ANDROID 0x3144 +#endif +#ifndef GL_PIXEL_UNPACK_BUFFER +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#endif +#ifndef GL_PIXEL_UNPACK_BUFFER_BINDING +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#endif +#ifndef GL_RASTERIZER_DISCARD +#define GL_RASTERIZER_DISCARD 0x8C89 +#endif +#ifndef GL_TEXTURE_BINDING_EXTERNAL_OES +#define GL_TEXTURE_BINDING_EXTERNAL_OES 0x8D67 +#endif +#ifndef GL_TEXTURE_EXTERNAL_OES +#define GL_TEXTURE_EXTERNAL_OES 0x8D65 +#endif +#ifndef GL_VERTEX_ARRAY_BINDING +#define GL_VERTEX_ARRAY_BINDING 0x85B5 +#endif + + +struct AHardwareBuffer; +struct AMediaDataSource; + + +namespace lime { + + + // AHardwareBuffer_Desc, which older NDK headers only declare from API level 26 + struct HardwareBufferDesc { + + uint32_t width; + uint32_t height; + uint32_t layers; + uint32_t format; + uint64_t usage; + uint32_t stride; + uint32_t rfu0; + uint64_t rfu1; + + }; + + + // AImageReader_ImageListener, which older NDK headers only declare from + // API level 24 + struct ImageListener { + + void* context; + void (*onImageAvailable) (void* context, AImageReader* reader); + + }; + + + // The NDK media library is loaded at runtime, so Lime does not link it. + // The image functions are newer than the rest, and only texture output + // needs them. + struct MediaNDK { + + int (*AHardwareBuffer_describe) (const AHardwareBuffer*, HardwareBufferDesc*); + void (*AImage_delete) (AImage*); + void (*AImage_deleteAsync) (AImage*, int); + media_status_t (*AImage_getCropRect) (const AImage*, AImageCropRect*); + media_status_t (*AImage_getHardwareBuffer) (const AImage*, AHardwareBuffer**); + media_status_t (*AImage_getTimestamp) (const AImage*, int64_t*); + media_status_t (*AImageReader_acquireNextImage) (AImageReader*, AImage**); + void (*AImageReader_delete) (AImageReader*); + media_status_t (*AImageReader_getWindow) (AImageReader*, ANativeWindow**); + media_status_t (*AImageReader_newWithUsage) (int32_t, int32_t, int32_t, uint64_t, int32_t, AImageReader**); + media_status_t (*AImageReader_setImageListener) (AImageReader*, ImageListener*); + media_status_t (*AMediaCodec_configure) (AMediaCodec*, const AMediaFormat*, ANativeWindow*, AMediaCrypto*, uint32_t); + AMediaCodec* (*AMediaCodec_createCodecByName) (const char*); + AMediaCodec* (*AMediaCodec_createDecoderByType) (const char*); + media_status_t (*AMediaCodec_delete) (AMediaCodec*); + ssize_t (*AMediaCodec_dequeueInputBuffer) (AMediaCodec*, int64_t); + ssize_t (*AMediaCodec_dequeueOutputBuffer) (AMediaCodec*, AMediaCodecBufferInfo*, int64_t); + media_status_t (*AMediaCodec_flush) (AMediaCodec*); + uint8_t* (*AMediaCodec_getInputBuffer) (AMediaCodec*, size_t, size_t*); + uint8_t* (*AMediaCodec_getOutputBuffer) (AMediaCodec*, size_t, size_t*); + AMediaFormat* (*AMediaCodec_getOutputFormat) (AMediaCodec*); + media_status_t (*AMediaCodec_queueInputBuffer) (AMediaCodec*, size_t, long, size_t, uint64_t, uint32_t); + media_status_t (*AMediaCodec_releaseOutputBuffer) (AMediaCodec*, size_t, bool); + media_status_t (*AMediaCodec_start) (AMediaCodec*); + media_status_t (*AMediaCodec_stop) (AMediaCodec*); + bool (*AMediaExtractor_advance) (AMediaExtractor*); + media_status_t (*AMediaExtractor_delete) (AMediaExtractor*); + int64_t (*AMediaExtractor_getSampleTime) (AMediaExtractor*); + size_t (*AMediaExtractor_getTrackCount) (AMediaExtractor*); + AMediaFormat* (*AMediaExtractor_getTrackFormat) (AMediaExtractor*, size_t); + AMediaExtractor* (*AMediaExtractor_new) (); + ssize_t (*AMediaExtractor_readSampleData) (AMediaExtractor*, uint8_t*, size_t); + media_status_t (*AMediaExtractor_seekTo) (AMediaExtractor*, int64_t, SeekMode); + media_status_t (*AMediaExtractor_selectTrack) (AMediaExtractor*, size_t); + media_status_t (*AMediaExtractor_setDataSource) (AMediaExtractor*, const char*); + media_status_t (*AMediaExtractor_setDataSourceCustom) (AMediaExtractor*, AMediaDataSource*); + media_status_t (*AMediaExtractor_setDataSourceFd) (AMediaExtractor*, int, off64_t, off64_t); + void (*AMediaDataSource_close) (AMediaDataSource*); + void (*AMediaDataSource_delete) (AMediaDataSource*); + AMediaDataSource* (*AMediaDataSource_newUri) (const char*, int, const char* const*); + media_status_t (*AMediaFormat_delete) (AMediaFormat*); + bool (*AMediaFormat_getBuffer) (AMediaFormat*, const char*, void**, size_t*); + bool (*AMediaFormat_getFloat) (AMediaFormat*, const char*, float*); + bool (*AMediaFormat_getInt32) (AMediaFormat*, const char*, int32_t*); + bool (*AMediaFormat_getInt64) (AMediaFormat*, const char*, int64_t*); + bool (*AMediaFormat_getRect) (AMediaFormat*, const char*, int32_t*, int32_t*, int32_t*, int32_t*); + bool (*AMediaFormat_getString) (AMediaFormat*, const char*, const char**); + void (*AMediaFormat_setInt32) (AMediaFormat*, const char*, int32_t); + + }; + + + // A plane of MediaImage2 from the platform's VideoAPI.h, which describes a + // decoder's output as "image-data". The MediaImage of API level 23 lacks + // mBitDepthAllocated, so its planes start 4 bytes sooner. + struct MediaImagePlane { + + uint32_t offset; + int32_t colInc; + int32_t rowInc; + uint32_t horizSubsampling; + uint32_t vertSubsampling; + + }; + + + // The GL state that LockTexture changes and restores, since OpenFL caches + // GL state + struct SavedGLState { + + GLint activeTexture; + GLint arrayBuffer; + GLint attribBuffer; + GLint attribEnabled; + GLint attribNormalized; + void* attribPointer; + GLint attribSize; + GLint attribStride; + GLint attribType; + GLboolean blend; + GLboolean colorMask[4]; + GLboolean cullFace; + GLboolean depthTest; + GLint externalTexture; + GLint framebuffer; + GLint program; + GLboolean rasterizerDiscard; + GLboolean scissorTest; + GLboolean stencilTest; + GLint texture; + GLint unpackBuffer; + GLint vertexArray; + GLint viewport[4]; + + }; + + + struct SoftwareDecoder { + + const char* mime; + const char* codec2; + const char* omx; + + }; + + + class MediaCodecTextureFrame : public VideoTextureFrame { + + + public: + + int cropX; + int cropY; + int fence; + AImage* image; + + + }; + + + // Values from MediaFormat, MediaCodecInfo and VideoAPI.h + enum { + + COLOR_FORMAT_YUV420_PLANAR = 19, + COLOR_FORMAT_YUV420_PACKED_PLANAR = 20, + COLOR_FORMAT_YUV420_SEMI_PLANAR = 21, + COLOR_FORMAT_YUV420_PACKED_SEMI_PLANAR = 39, + COLOR_FORMAT_YUV420_FLEXIBLE = 0x7F420888, + COLOR_FORMAT_QCOM_YUV420_SEMI_PLANAR = 0x7FA30C00, + COLOR_FORMAT_QCOM_YUV420_SEMI_PLANAR_32M = 0x7FA30C04, + COLOR_RANGE_FULL = 1, + COLOR_STANDARD_BT709 = 1, + COLOR_STANDARD_BT601_PAL = 2, + COLOR_STANDARD_BT601_NTSC = 4, + COLOR_STANDARD_BT2020 = 6, + COLOR_TRANSFER_SDR_VIDEO = 3, + COLOR_TRANSFER_ST2084 = 6, + COLOR_TRANSFER_HLG = 7, + MEDIA_IMAGE_SIZE = 80, + MEDIA_IMAGE_TYPE_YUV = 1, + MEDIA_IMAGE2_SIZE = 104, + PCM_ENCODING_16BIT = 2, + PCM_ENCODING_8BIT = 3, + PCM_ENCODING_FLOAT = 4, + PCM_ENCODING_24BIT_PACKED = 21, + PCM_ENCODING_32BIT = 22 + + }; + + + // Google's software decoders, by their Codec 2.0 and older OMX names + static const SoftwareDecoder SOFTWARE_DECODERS[] = { + + { "audio/3gpp", "c2.android.amrnb.decoder", "OMX.google.amrnb.decoder" }, + { "audio/amr-wb", "c2.android.amrwb.decoder", "OMX.google.amrwb.decoder" }, + { "audio/flac", "c2.android.flac.decoder", "OMX.google.flac.decoder" }, + { "audio/g711-alaw", "c2.android.g711.alaw.decoder", "OMX.google.g711.alaw.decoder" }, + { "audio/g711-mlaw", "c2.android.g711.mlaw.decoder", "OMX.google.g711.mlaw.decoder" }, + { "audio/mp4a-latm", "c2.android.aac.decoder", "OMX.google.aac.decoder" }, + { "audio/mpeg", "c2.android.mp3.decoder", "OMX.google.mp3.decoder" }, + { "audio/opus", "c2.android.opus.decoder", "OMX.google.opus.decoder" }, + { "audio/raw", "c2.android.raw.decoder", "OMX.google.raw.decoder" }, + { "audio/vorbis", "c2.android.vorbis.decoder", "OMX.google.vorbis.decoder" }, + { "video/3gpp", "c2.android.h263.decoder", "OMX.google.h263.decoder" }, + { "video/av01", "c2.android.av1.decoder", NULL }, + { "video/avc", "c2.android.avc.decoder", "OMX.google.h264.decoder" }, + { "video/hevc", "c2.android.hevc.decoder", "OMX.google.hevc.decoder" }, + { "video/mp4v-es", "c2.android.mpeg4.decoder", "OMX.google.mpeg4.decoder" }, + { "video/mpeg2", "c2.android.mpeg2.decoder", "OMX.google.mpeg2.decoder" }, + { "video/x-vnd.on2.vp8", "c2.android.vp8.decoder", "OMX.google.vp8.decoder" }, + { "video/x-vnd.on2.vp9", "c2.android.vp9.decoder", "OMX.google.vp9.decoder" } + + }; + + // AIMAGE_FORMAT_PRIVATE and AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE + static const int32_t IMAGE_FORMAT_PRIVATE = 0x22; + static const uint64_t IMAGE_USAGE_GPU_SAMPLED = 1ULL << 8; + + // Texture output keeps decoded frames in an image reader, which holds + // enough for the decoder's queue, the frame on screen and one being read + static const int OUTPUT_IMAGES = 8; + + // Decoding gives up after about 3 seconds without progress + static const int MAX_IDLE_WAITS = 300; + static const int64_t OUTPUT_TIMEOUT_US = 10000; + + // Buffer indices are small, so a larger one is an error code, such as the + // system reclaiming the codec for another app + static const ssize_t MAX_BUFFER_INDEX = 1000; + + // A codec that fails is replaced this many times in a row, such as when + // the system reclaims it while the app is in the background + static const int MAX_CODEC_RESTARTS = 3; + + // How long a seek waits for reads in progress before giving up on them, as + // reads from a source that is still sending finish well within it + static const int SEEK_WAIT_MS = 250; + + // How long decoding waits at a time for a connection being made again + static const int CONNECT_WAIT_MS = 50; + + static const char* COPY_FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + "#ifdef GL_FRAGMENT_PRECISION_HIGH\n" + "precision highp float;\n" + "#else\n" + "precision mediump float;\n" + "#endif\n" + "uniform samplerExternalOES image;\n" + "varying vec2 coord;\n" + "void main () { gl_FragColor = texture2D (image, coord); }\n"; + + // The crop is the offset and size of the picture in the buffer, in texture + // coordinates. Row 0 of the copy samples the buffer's first row, the top. + static const char* COPY_VERTEX_SHADER = + "attribute vec2 position;\n" + "uniform vec4 crop;\n" + "varying vec2 coord;\n" + "void main () {\n" + " coord = crop.xy + (position * 0.5 + 0.5) * crop.zw;\n" + " gl_Position = vec4 (position, 0.0, 1.0);\n" + "}\n"; + + typedef void (*GLBindVertexArrayFunc) (GLuint); + typedef void (*GLDeleteVertexArraysFunc) (GLsizei, const GLuint*); + typedef void (*GLEGLImageTargetTexture2DFunc) (GLenum, void*); + typedef void (*GLGenVertexArraysFunc) (GLsizei, GLuint*); + typedef EGLImageKHR (*EGLCreateImageFunc) (EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint*); + typedef EGLSyncKHR (*EGLCreateSyncFunc) (EGLDisplay, EGLenum, const EGLint*); + typedef EGLBoolean (*EGLDestroyImageFunc) (EGLDisplay, EGLImageKHR); + typedef EGLBoolean (*EGLDestroySyncFunc) (EGLDisplay, EGLSyncKHR); + typedef EGLint (*EGLDupNativeFenceFDFunc) (EGLDisplay, EGLSyncKHR); + typedef EGLClientBuffer (*EGLGetNativeClientBufferFunc) (const AHardwareBuffer*); + + static GLBindVertexArrayFunc _glBindVertexArray = 0; + static GLDeleteVertexArraysFunc _glDeleteVertexArrays = 0; + static GLEGLImageTargetTexture2DFunc _glEGLImageTargetTexture2DOES = 0; + static GLGenVertexArraysFunc _glGenVertexArrays = 0; + static EGLCreateImageFunc _eglCreateImageKHR = 0; + static EGLCreateSyncFunc _eglCreateSyncKHR = 0; + static EGLDestroyImageFunc _eglDestroyImageKHR = 0; + static EGLDestroySyncFunc _eglDestroySyncKHR = 0; + static EGLDupNativeFenceFDFunc _eglDupNativeFenceFDANDROID = 0; + static EGLGetNativeClientBufferFunc _eglGetNativeClientBufferANDROID = 0; + + static std::once_flag extensionsFlag; + static std::once_flag loadFlag; + static bool loaded = false; + static MediaNDK ndk; + + + static bool LoadMediaNDK () { + + std::call_once (loadFlag, [] { + + void* library = dlopen ("libmediandk.so", RTLD_NOW | RTLD_LOCAL); + if (!library) return; + + #define LIME_MEDIANDK_REQUIRE(name) ndk.name = (decltype (ndk.name))dlsym (library, #name); if (!ndk.name) return; + #define LIME_MEDIANDK_OPTIONAL(library, name) ndk.name = (decltype (ndk.name))dlsym (library, #name); + + LIME_MEDIANDK_REQUIRE (AMediaCodec_configure); + LIME_MEDIANDK_REQUIRE (AMediaCodec_createCodecByName); + LIME_MEDIANDK_REQUIRE (AMediaCodec_createDecoderByType); + LIME_MEDIANDK_REQUIRE (AMediaCodec_delete); + LIME_MEDIANDK_REQUIRE (AMediaCodec_dequeueInputBuffer); + LIME_MEDIANDK_REQUIRE (AMediaCodec_dequeueOutputBuffer); + LIME_MEDIANDK_REQUIRE (AMediaCodec_flush); + LIME_MEDIANDK_REQUIRE (AMediaCodec_getInputBuffer); + LIME_MEDIANDK_REQUIRE (AMediaCodec_getOutputBuffer); + LIME_MEDIANDK_REQUIRE (AMediaCodec_getOutputFormat); + LIME_MEDIANDK_REQUIRE (AMediaCodec_queueInputBuffer); + LIME_MEDIANDK_REQUIRE (AMediaCodec_releaseOutputBuffer); + LIME_MEDIANDK_REQUIRE (AMediaCodec_start); + LIME_MEDIANDK_REQUIRE (AMediaCodec_stop); + LIME_MEDIANDK_REQUIRE (AMediaExtractor_advance); + LIME_MEDIANDK_REQUIRE (AMediaExtractor_delete); + LIME_MEDIANDK_REQUIRE (AMediaExtractor_getSampleTime); + LIME_MEDIANDK_REQUIRE (AMediaExtractor_getTrackCount); + LIME_MEDIANDK_REQUIRE (AMediaExtractor_getTrackFormat); + LIME_MEDIANDK_REQUIRE (AMediaExtractor_new); + LIME_MEDIANDK_REQUIRE (AMediaExtractor_readSampleData); + LIME_MEDIANDK_REQUIRE (AMediaExtractor_seekTo); + LIME_MEDIANDK_REQUIRE (AMediaExtractor_selectTrack); + LIME_MEDIANDK_REQUIRE (AMediaExtractor_setDataSource); + LIME_MEDIANDK_REQUIRE (AMediaExtractor_setDataSourceFd); + LIME_MEDIANDK_REQUIRE (AMediaFormat_delete); + LIME_MEDIANDK_REQUIRE (AMediaFormat_getBuffer); + LIME_MEDIANDK_REQUIRE (AMediaFormat_getFloat); + LIME_MEDIANDK_REQUIRE (AMediaFormat_getInt32); + LIME_MEDIANDK_REQUIRE (AMediaFormat_getInt64); + LIME_MEDIANDK_REQUIRE (AMediaFormat_getString); + + // API level 24 to 28 + LIME_MEDIANDK_OPTIONAL (library, AImage_delete); + LIME_MEDIANDK_OPTIONAL (library, AImage_deleteAsync); + LIME_MEDIANDK_OPTIONAL (library, AImage_getCropRect); + LIME_MEDIANDK_OPTIONAL (library, AImage_getHardwareBuffer); + LIME_MEDIANDK_OPTIONAL (library, AImage_getTimestamp); + LIME_MEDIANDK_OPTIONAL (library, AImageReader_acquireNextImage); + LIME_MEDIANDK_OPTIONAL (library, AImageReader_delete); + LIME_MEDIANDK_OPTIONAL (library, AImageReader_getWindow); + LIME_MEDIANDK_OPTIONAL (library, AImageReader_newWithUsage); + LIME_MEDIANDK_OPTIONAL (library, AImageReader_setImageListener); + LIME_MEDIANDK_OPTIONAL (library, AMediaFormat_getRect); + LIME_MEDIANDK_OPTIONAL (library, AMediaFormat_setInt32); + + // API level 28 and 29 + LIME_MEDIANDK_OPTIONAL (library, AMediaDataSource_close); + LIME_MEDIANDK_OPTIONAL (library, AMediaDataSource_delete); + LIME_MEDIANDK_OPTIONAL (library, AMediaDataSource_newUri); + LIME_MEDIANDK_OPTIONAL (library, AMediaExtractor_setDataSourceCustom); + + void* android = dlopen ("libandroid.so", RTLD_NOW | RTLD_LOCAL); + if (android) LIME_MEDIANDK_OPTIONAL (android, AHardwareBuffer_describe); + + #undef LIME_MEDIANDK_REQUIRE + #undef LIME_MEDIANDK_OPTIONAL + + loaded = true; + + }); + + return loaded; + + } + + + static bool CanUseImageReader () { + + return ndk.AHardwareBuffer_describe && ndk.AImage_delete && ndk.AImage_deleteAsync && ndk.AImage_getCropRect && ndk.AImage_getHardwareBuffer && ndk.AImage_getTimestamp + && ndk.AImageReader_acquireNextImage && ndk.AImageReader_delete && ndk.AImageReader_getWindow && ndk.AImageReader_newWithUsage && ndk.AImageReader_setImageListener; + + } + + + static GLuint CompileShader (GLenum type, const char* source) { + + GLuint shader = glCreateShader (type); + glShaderSource (shader, 1, &source, NULL); + glCompileShader (shader); + + GLint compiled = 0; + glGetShaderiv (shader, GL_COMPILE_STATUS, &compiled); + + if (!compiled) { + + glDeleteShader (shader); + return 0; + + } + + return shader; + + } + + + static void ConvertSamples (const uint8_t* data, size_t count, int encoding, std::vector* out) { + + // Decoders that cannot give 16-bit samples give these instead + out->resize (count); + short* samples = out->data (); + + for (size_t i = 0; i < count; i++) { + + switch (encoding) { + + case PCM_ENCODING_FLOAT: { + + float value; + memcpy (&value, data + i * 4, 4); + value *= 32768.0f; + samples[i] = value >= 32767.0f ? 32767 : (value <= -32768.0f ? -32768 : (short)value); + break; + + } + + case PCM_ENCODING_8BIT: samples[i] = (short)((data[i] - 128) << 8); break; + case PCM_ENCODING_24BIT_PACKED: samples[i] = (short)(data[i * 3 + 1] | (data[i * 3 + 2] << 8)); break; + + case PCM_ENCODING_32BIT: { + + int32_t value; + memcpy (&value, data + i * 4, 4); + samples[i] = (short)(value >> 16); + break; + + } + + default: samples[i] = 0; break; + + } + + } + + } + + + static int GetSampleBytes (int encoding) { + + switch (encoding) { + + case PCM_ENCODING_16BIT: return 2; + case PCM_ENCODING_8BIT: return 1; + case PCM_ENCODING_24BIT_PACKED: return 3; + case PCM_ENCODING_FLOAT: case PCM_ENCODING_32BIT: return 4; + default: return 0; + + } + + } + + + #ifndef LIME_VIDEO_STANDALONE + static int OpenContentURI (const char* uri) { + + // A content URI, as file pickers and the media store give, is opened + // through the content resolver, which hands out a file descriptor + JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv (); + jobject activity = env ? (jobject)SDL_AndroidGetActivity () : NULL; + if (!activity) return -1; + + int fd = -1; + + if (env->PushLocalFrame (16) == 0) { + + jclass contextClass = env->GetObjectClass (activity); + jmethodID getContentResolver = env->GetMethodID (contextClass, "getContentResolver", "()Landroid/content/ContentResolver;"); + jobject resolver = getContentResolver ? env->CallObjectMethod (activity, getContentResolver) : NULL; + jclass uriClass = env->FindClass ("android/net/Uri"); + jmethodID parse = uriClass ? env->GetStaticMethodID (uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;") : NULL; + jobject uriObject = parse ? env->CallStaticObjectMethod (uriClass, parse, env->NewStringUTF (uri)) : NULL; + + if (env->ExceptionCheck ()) { + + env->ExceptionClear (); + resolver = NULL; + + } + + if (resolver && uriObject) { + + jmethodID openFileDescriptor = env->GetMethodID (env->GetObjectClass (resolver), "openFileDescriptor", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;"); + jobject descriptor = openFileDescriptor ? env->CallObjectMethod (resolver, openFileDescriptor, uriObject, env->NewStringUTF ("r")) : NULL; + + if (env->ExceptionCheck ()) { + + // Such as a URI that does not exist or is not permitted + env->ExceptionClear (); + descriptor = NULL; + + } + + if (descriptor) { + + jclass descriptorClass = env->GetObjectClass (descriptor); + jmethodID detachFd = env->GetMethodID (descriptorClass, "detachFd", "()I"); + jmethodID close = env->GetMethodID (descriptorClass, "close", "()V"); + + if (detachFd) fd = env->CallIntMethod (descriptor, detachFd); + if (env->ExceptionCheck ()) { env->ExceptionClear (); fd = -1; } + if (close) env->CallVoidMethod (descriptor, close); + if (env->ExceptionCheck ()) env->ExceptionClear (); + + } + + } + + if (env->ExceptionCheck ()) env->ExceptionClear (); + env->PopLocalFrame (NULL); + + } + + env->DeleteLocalRef (activity); + return fd; + + } + #endif + + + static AMediaCodec* CreateDecoder (AMediaFormat* format, bool hardware, ANativeWindow* surface) { + + const char* mime = NULL; + if (!ndk.AMediaFormat_getString (format, "mime", &mime) || !mime) return NULL; + + std::string type = mime; + const char* names[] = { NULL, NULL }; + + if (!hardware) { + + for (size_t i = 0; i < sizeof (SOFTWARE_DECODERS) / sizeof (SOFTWARE_DECODERS[0]); i++) { + + if (type == SOFTWARE_DECODERS[i].mime) { + + names[0] = SOFTWARE_DECODERS[i].codec2; + names[1] = SOFTWARE_DECODERS[i].omx; + break; + + } + + } + + } + + // The software decoders by name, then the platform's choice for the type + for (int i = 0; i < 3; i++) { + + AMediaCodec* codec = NULL; + + if (i < 2) { + + if (names[i]) codec = ndk.AMediaCodec_createCodecByName (names[i]); + + } else { + + codec = ndk.AMediaCodec_createDecoderByType (type.c_str ()); + + } + + if (!codec) continue; + + if (ndk.AMediaCodec_configure (codec, format, surface, NULL, 0) == AMEDIA_OK && ndk.AMediaCodec_start (codec) == AMEDIA_OK) { + + return codec; + + } + + ndk.AMediaCodec_delete (codec); + + } + + return NULL; + + } + + + static double EstimateFrameRate (AMediaExtractor* extractor) { + + // Samples arrive in decode order, so sort their times and measure the + // evenly spaced run from the first frame + std::vector times; + + while (times.size () < 16) { + + int64_t time = ndk.AMediaExtractor_getSampleTime (extractor); + if (time < 0) break; + + times.push_back (time); + if (!ndk.AMediaExtractor_advance (extractor)) break; + + } + + ndk.AMediaExtractor_seekTo (extractor, 0, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC); + + if (times.size () < 3) return 0; + + std::sort (times.begin (), times.end ()); + + std::vector deltas; + for (size_t i = 1; i < times.size (); i++) deltas.push_back (times[i] - times[i - 1]); + + std::nth_element (deltas.begin (), deltas.begin () + deltas.size () / 2, deltas.end ()); + int64_t median = deltas[deltas.size () / 2]; + + if (median <= 0) return 0; + + size_t count = 1; + + while (count < times.size ()) { + + int64_t delta = times[count] - times[count - 1]; + if (delta < median - median / 4 || delta > median + median / 4) break; + count++; + + } + + return count > 1 ? (count - 1) * 1000000.0 / (times[count - 1] - times[0]) : 1000000.0 / median; + + } + + + static bool HasExtension (const char* extensions, const char* name) { + + if (!extensions) return false; + + size_t length = strlen (name); + const char* found = extensions; + + while ((found = strstr (found, name)) != NULL) { + + if ((found == extensions || found[-1] == ' ') && (found[length] == ' ' || found[length] == '\0')) return true; + found += length; + + } + + return false; + + } + + + static bool LoadEGLExtensions () { + + std::call_once (extensionsFlag, [] { + + _eglCreateImageKHR = (EGLCreateImageFunc)eglGetProcAddress ("eglCreateImageKHR"); + _eglCreateSyncKHR = (EGLCreateSyncFunc)eglGetProcAddress ("eglCreateSyncKHR"); + _eglDestroyImageKHR = (EGLDestroyImageFunc)eglGetProcAddress ("eglDestroyImageKHR"); + _eglDestroySyncKHR = (EGLDestroySyncFunc)eglGetProcAddress ("eglDestroySyncKHR"); + _eglDupNativeFenceFDANDROID = (EGLDupNativeFenceFDFunc)eglGetProcAddress ("eglDupNativeFenceFDANDROID"); + _eglGetNativeClientBufferANDROID = (EGLGetNativeClientBufferFunc)eglGetProcAddress ("eglGetNativeClientBufferANDROID"); + _glBindVertexArray = (GLBindVertexArrayFunc)eglGetProcAddress ("glBindVertexArray"); + _glDeleteVertexArrays = (GLDeleteVertexArraysFunc)eglGetProcAddress ("glDeleteVertexArrays"); + _glEGLImageTargetTexture2DOES = (GLEGLImageTargetTexture2DFunc)eglGetProcAddress ("glEGLImageTargetTexture2DOES"); + _glGenVertexArrays = (GLGenVertexArraysFunc)eglGetProcAddress ("glGenVertexArrays"); + + }); + + return _eglCreateImageKHR && _eglDestroyImageKHR && _eglGetNativeClientBufferANDROID && _glEGLImageTargetTexture2DOES; + + } + + + // GL objects of a backend closed away from the GL thread, deleted the next + // time a decoder reads a texture frame where their context is current + struct MediaCodecOrphan { + + EGLContext context; + GLuint copyBuffer; + GLuint copyFramebuffer; + GLuint copyProgram; + GLuint copyTexture; + GLuint copyVertexArray; + GLuint imageTexture; + + }; + + // Never destroyed, as exiting runs static destructors while threads can + // still be closing videos + static std::mutex* orphanMutex = new std::mutex (); + static std::vector* orphans = new std::vector (); + + + static void RestoreGLState (const SavedGLState* state, bool gles3) { + + glBindFramebuffer (GL_FRAMEBUFFER, state->framebuffer); + glUseProgram (state->program); + glViewport (state->viewport[0], state->viewport[1], state->viewport[2], state->viewport[3]); + glColorMask (state->colorMask[0], state->colorMask[1], state->colorMask[2], state->colorMask[3]); + + if (state->blend) glEnable (GL_BLEND); + if (state->cullFace) glEnable (GL_CULL_FACE); + if (state->depthTest) glEnable (GL_DEPTH_TEST); + if (state->scissorTest) glEnable (GL_SCISSOR_TEST); + if (state->stencilTest) glEnable (GL_STENCIL_TEST); + + glActiveTexture (GL_TEXTURE0); + glBindTexture (GL_TEXTURE_2D, state->texture); + glBindTexture (GL_TEXTURE_EXTERNAL_OES, state->externalTexture); + glActiveTexture (state->activeTexture); + + if (gles3) { + + if (state->rasterizerDiscard) glEnable (GL_RASTERIZER_DISCARD); + glBindBuffer (GL_PIXEL_UNPACK_BUFFER, state->unpackBuffer); + _glBindVertexArray (state->vertexArray); + + } else { + + glBindBuffer (GL_ARRAY_BUFFER, state->attribBuffer); + glVertexAttribPointer (0, state->attribSize, state->attribType, state->attribNormalized, state->attribStride, state->attribPointer); + + if (state->attribEnabled) { + + glEnableVertexAttribArray (0); + + } else { + + glDisableVertexAttribArray (0); + + } + + } + + glBindBuffer (GL_ARRAY_BUFFER, state->arrayBuffer); + + } + + + static void SaveGLState (SavedGLState* state, bool gles3) { + + glGetIntegerv (GL_ACTIVE_TEXTURE, &state->activeTexture); + glGetIntegerv (GL_ARRAY_BUFFER_BINDING, &state->arrayBuffer); + glGetIntegerv (GL_CURRENT_PROGRAM, &state->program); + glGetIntegerv (GL_FRAMEBUFFER_BINDING, &state->framebuffer); + glGetIntegerv (GL_VIEWPORT, state->viewport); + glGetBooleanv (GL_COLOR_WRITEMASK, state->colorMask); + state->blend = glIsEnabled (GL_BLEND); + state->cullFace = glIsEnabled (GL_CULL_FACE); + state->depthTest = glIsEnabled (GL_DEPTH_TEST); + state->scissorTest = glIsEnabled (GL_SCISSOR_TEST); + state->stencilTest = glIsEnabled (GL_STENCIL_TEST); + + glActiveTexture (GL_TEXTURE0); + glGetIntegerv (GL_TEXTURE_BINDING_2D, &state->texture); + glGetIntegerv (GL_TEXTURE_BINDING_EXTERNAL_OES, &state->externalTexture); + + if (gles3) { + + // The copy draws with its own vertex array, so the application's + // vertex attributes are left alone + glGetIntegerv (GL_PIXEL_UNPACK_BUFFER_BINDING, &state->unpackBuffer); + glGetIntegerv (GL_VERTEX_ARRAY_BINDING, &state->vertexArray); + state->rasterizerDiscard = glIsEnabled (GL_RASTERIZER_DISCARD); + + } else { + + glGetVertexAttribiv (0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &state->attribBuffer); + glGetVertexAttribiv (0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &state->attribEnabled); + glGetVertexAttribiv (0, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &state->attribNormalized); + glGetVertexAttribiv (0, GL_VERTEX_ATTRIB_ARRAY_SIZE, &state->attribSize); + glGetVertexAttribiv (0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &state->attribStride); + glGetVertexAttribiv (0, GL_VERTEX_ATTRIB_ARRAY_TYPE, &state->attribType); + glGetVertexAttribPointerv (0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &state->attribPointer); + + } + + } + + + static int GetSystemVersion () { + + // The API level, which the NDK only gives from Android 10 + static int version = -1; + + if (version < 0) { + + char value[PROP_VALUE_MAX] = {}; + version = __system_property_get ("ro.build.version.sdk", value) > 0 ? atoi (value) : 0; + + } + + return version; + + } + + + static AMediaFormat* SelectTrack (AMediaExtractor* extractor, const char* type, size_t* track) { + + size_t count = ndk.AMediaExtractor_getTrackCount (extractor); + + for (size_t i = 0; i < count; i++) { + + AMediaFormat* format = ndk.AMediaExtractor_getTrackFormat (extractor, i); + if (!format) continue; + + const char* mime = NULL; + + if (ndk.AMediaFormat_getString (format, "mime", &mime) && mime && strncmp (mime, type, strlen (type)) == 0) { + + *track = i; + if (ndk.AMediaExtractor_selectTrack (extractor, i) == AMEDIA_OK) return format; + + ndk.AMediaFormat_delete (format); + return NULL; + + } + + ndk.AMediaFormat_delete (format); + + } + + return NULL; + + } + + + // Whether a seek waits for the streams, and which of them are reading, + // under the mutex, so a read does not start while a seek waits + struct MediaCodecReads { + + MediaCodecReads () : seeking (false) {} + + std::condition_variable condition; + std::mutex mutex; + std::atomic seeking; + + }; + + + // Connecting to a server, which Android cannot cancel, so it happens on a + // thread of its own that seeking and closing can leave behind + struct MediaCodecConnection { + + MediaCodecConnection () : abandoned (false), dataSource (NULL), done (false) {} + + bool abandoned; + std::condition_variable condition; + AMediaDataSource* dataSource; + bool done; + std::mutex mutex; + + }; + + + static std::shared_ptr Connect (const std::string& path) { + + std::shared_ptr connection = std::make_shared (); + + std::thread ([connection, path] { + + #ifndef LIME_VIDEO_STANDALONE + // The data source reads through Java, so the thread joins the Java VM + SDL_AndroidGetJNIEnv (); + #endif + + AMediaDataSource* dataSource = ndk.AMediaDataSource_newUri (path.c_str (), 0, NULL); + + std::lock_guard lock (connection->mutex); + + if (connection->abandoned && dataSource) { + + ndk.AMediaDataSource_delete (dataSource); + dataSource = NULL; + + } + + connection->dataSource = dataSource; + connection->done = true; + connection->condition.notify_all (); + + }).detach (); + + return connection; + + } + + + // One stream's extractor and decoder, which no other thread touches while + // it decodes + class MediaCodecStream { + + + public: + + MediaCodecStream (); + + bool BeginRead (); + void Close (); + void EndRead (); + void GiveUp (); + bool Prime (); + VideoDecodeResult Read (bool* formatChanged); + void Release (); + bool Seek (int64_t position, bool later); + void SetDataSource (AMediaDataSource* value); + + // Reading, under the reads mutex + bool busy; + + std::atomic cancelled; + AMediaCodec* codec; + std::shared_ptr connection; + AMediaDataSource* dataSource; + bool ended; + AMediaExtractor* extractor; + ssize_t index; + AMediaCodecBufferInfo info; + bool inputEnded; + bool inputError; + std::atomic interrupted; + bool pending; + MediaCodecReads* reads; + + // Given up by a seek, so the stream connects again + std::atomic reopen; + + // Where the extractor seeks before reading next, or -1 + int64_t seekPosition; + + private: + + bool QueueInput (); + + + }; + + + MediaCodecStream::MediaCodecStream () { + + busy = false; + cancelled = false; + codec = NULL; + dataSource = NULL; + ended = false; + extractor = NULL; + index = -1; + inputEnded = false; + inputError = false; + interrupted = false; + memset (&info, 0, sizeof (info)); + pending = false; + reads = NULL; + reopen = false; + seekPosition = -1; + + } + + + bool MediaCodecStream::BeginRead () { + + // Reading a URL can wait on a stalled network source, which a seek gives + // up, and reads do not start while a seek waits + std::lock_guard lock (reads->mutex); + if (reads->seeking) return false; + + busy = true; + cancelled = false; + return true; + + } + + + void MediaCodecStream::Close () { + + Release (); + + if (connection) { + + std::lock_guard lock (connection->mutex); + connection->abandoned = true; + + if (connection->dataSource) { + + ndk.AMediaDataSource_delete (connection->dataSource); + connection->dataSource = NULL; + + } + + } + + connection.reset (); + + if (codec) { + + ndk.AMediaCodec_stop (codec); + ndk.AMediaCodec_delete (codec); + codec = NULL; + + } + + if (extractor) { + + ndk.AMediaExtractor_delete (extractor); + extractor = NULL; + + } + + // The extractor reads from the data source until it is deleted + SetDataSource (NULL); + + cancelled = false; + ended = false; + inputEnded = false; + inputError = false; + interrupted = false; + reopen = false; + seekPosition = -1; + + } + + + void MediaCodecStream::EndRead () { + + std::lock_guard lock (reads->mutex); + busy = false; + reads->condition.notify_all (); + + } + + + void MediaCodecStream::GiveUp () { + + // With the reads mutex held. Closing the data source makes a read waiting + // on a stalled network source fail, and the stream connects again. + cancelled = true; + reopen = true; + if (dataSource) ndk.AMediaDataSource_close (dataSource); + + } + + + bool MediaCodecStream::Prime () { + + // Decode ahead to the first output, so the output format is the real one + bool formatChanged = false; + pending = (Read (&formatChanged) == VIDEO_DECODE_OK); + return pending; + + } + + + bool MediaCodecStream::QueueInput () { + + if (inputEnded) return false; + + ssize_t inputIndex = ndk.AMediaCodec_dequeueInputBuffer (codec, 0); + if (inputIndex < 0) return false; + + if (inputIndex >= MAX_BUFFER_INDEX) { + + inputError = true; + return false; + + } + + size_t capacity = 0; + uint8_t* buffer = ndk.AMediaCodec_getInputBuffer (codec, inputIndex, &capacity); + + // The input buffer goes back to the codec when the seek flushes it + if (!BeginRead ()) { + + inputError = true; + return false; + + } + + ssize_t size = buffer ? ndk.AMediaExtractor_readSampleData (extractor, buffer, capacity) : -1; + int64_t time = ndk.AMediaExtractor_getSampleTime (extractor); + EndRead (); + + if (cancelled || (time >= 0 && size < 0)) { + + // A sample that is there but cannot be read, such as one larger than + // the input buffer or one lost to a network error, is not the end + inputError = true; + return false; + + } + + if (time < 0) { + + ndk.AMediaCodec_queueInputBuffer (codec, inputIndex, 0, 0, 0, AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM); + inputEnded = true; + + } else { + + ndk.AMediaCodec_queueInputBuffer (codec, inputIndex, 0, size, time, 0); + + // Advancing reads the next sample + if (BeginRead ()) { + + ndk.AMediaExtractor_advance (extractor); + EndRead (); + if (cancelled) inputError = true; + + } else { + + inputError = true; + + } + + } + + return true; + + } + + + VideoDecodeResult MediaCodecStream::Read (bool* formatChanged) { + + if (pending) { + + pending = false; + return VIDEO_DECODE_OK; + + } + + Release (); + + if (ended) return VIDEO_DECODE_END; + if (!extractor) return reopen ? VIDEO_DECODE_AGAIN : VIDEO_DECODE_ERROR; + + if (seekPosition >= 0) { + + if (!BeginRead ()) return VIDEO_DECODE_AGAIN; + media_status_t status = ndk.AMediaExtractor_seekTo (extractor, seekPosition, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC); + EndRead (); + + if (cancelled) return VIDEO_DECODE_AGAIN; + + seekPosition = -1; + if (status != AMEDIA_OK) return VIDEO_DECODE_ERROR; + + } + + int idle = 0; + + while (!interrupted) { + + bool queued = QueueInput (); + + // A seek gave up on this read, or it waits for one, and reading + // carries on after it + if (inputError) return cancelled || reads->seeking ? VIDEO_DECODE_AGAIN : VIDEO_DECODE_ERROR; + if (queued) idle = 0; + + ssize_t result = ndk.AMediaCodec_dequeueOutputBuffer (codec, &info, queued ? 0 : OUTPUT_TIMEOUT_US); + + if (result >= MAX_BUFFER_INDEX) { + + return VIDEO_DECODE_ERROR; + + } else if (result >= 0) { + + if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) ended = true; + + if (info.size > 0) { + + index = result; + return VIDEO_DECODE_OK; + + } + + ndk.AMediaCodec_releaseOutputBuffer (codec, result, false); + if (ended) return VIDEO_DECODE_END; + + } else if (result == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) { + + *formatChanged = true; + + } else if (result == AMEDIACODEC_INFO_TRY_AGAIN_LATER) { + + // Some decoders never flag the end of the stream after the last input + if (!queued && ++idle >= MAX_IDLE_WAITS) return inputEnded ? VIDEO_DECODE_END : VIDEO_DECODE_ERROR; + + } else if (result != AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) { + + return VIDEO_DECODE_ERROR; + + } + + } + + return VIDEO_DECODE_ERROR; + + } + + + void MediaCodecStream::Release () { + + if (index >= 0) { + + ndk.AMediaCodec_releaseOutputBuffer (codec, index, false); + index = -1; + + } + + pending = false; + + } + + + bool MediaCodecStream::Seek (int64_t position, bool later) { + + Release (); + + // Seeking a URL reads from the network, so the extractor seeks later, + // on the thread that decodes, where a seek can give up on it + bool success = true; + + if (later || !extractor) { + + seekPosition = position; + + } else { + + seekPosition = -1; + success = (ndk.AMediaExtractor_seekTo (extractor, position, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC) == AMEDIA_OK); + + } + + success = (ndk.AMediaCodec_flush (codec) == AMEDIA_OK) && success; + + ended = false; + inputEnded = false; + inputError = false; + + return success; + + } + + + void MediaCodecStream::SetDataSource (AMediaDataSource* value) { + + AMediaDataSource* previous = NULL; + + { + // Interrupt and PrepareSeek close it from another thread + std::lock_guard lock (reads->mutex); + previous = dataSource; + dataSource = value; + } + + if (previous) ndk.AMediaDataSource_delete (previous); + + } + + + class MediaCodecVideoBackend : public VideoBackend { + + + public: + + MediaCodecVideoBackend (); + ~MediaCodecVideoBackend (); + + virtual void Close (); + virtual VideoDecodeResult DecodeAudio (std::vector* pcm, double* time); + virtual VideoDecodeResult DecodeVideo (VideoPlanes* planes, double* time, double* duration); + virtual VideoDecodeResult DecodeVideoTexture (VideoTextureFrame** frame, double* time, double* duration); + virtual void Interrupt (); + virtual unsigned int LockTexture (VideoTextureFrame* frame); + virtual bool Open (const char* path, bool hardwareDecoding, VideoStreamInfo* info); + virtual void PrepareSeek (); + virtual void ReleaseTextureFrame (VideoTextureFrame* frame); + virtual void ReleaseOrphans (); + virtual void ReleaseTextures (); + virtual bool Seek (double time); + virtual void SetTextureOutput (bool enabled); + virtual bool SupportsTextures (); + + private: + + AImage* AcquireImage (); + AMediaExtractor* CreateExtractor (AMediaDataSource** dataSource); + ANativeWindow* CreateImageReader (int width, int height); + bool CreateTextures (); + AMediaCodec* CreateVideoDecoder (bool hardware, bool surface); + void ForgetTextures (); + bool GetPlanes (VideoPlanes* planes); + bool OpenAudio (); + bool OpenVideo (bool hardware); + bool ReadAudioFormat (); + bool ReadPlaneLayout (AMediaFormat* format, int width, int height); + bool ReadVideoFormat (); + void ReleaseImage (AImage* image, int fence); + VideoDecodeResult ReopenStream (MediaCodecStream* stream, size_t track); + bool ReplaceVideoCodec (bool hardware, bool surface, int64_t position); + bool RestartAudio (); + bool RestartVideo (); + + static void OnImageAvailable (void* context, AImageReader* reader); + + MediaCodecStream audio; + int audioChannels; + VideoAudioConverter audioConverter; + int audioEncoding; + int64_t audioPosition; + int audioRestarts; + std::vector audioSamples; + int audioSampleRate; + int64_t audioSkipTime; + int audioSourceChannels; + int audioSourceRate; + int64_t audioTime; + size_t audioTrack; + bool audioTrackFound; + VideoColorMatrix colorMatrix; + VideoColorTransfer colorTransfer; + GLuint copyBuffer; + GLuint copyFramebuffer; + GLuint copyProgram; + GLuint copyTexture; + int copyTextureHeight; + int copyTextureWidth; + GLuint copyVertexArray; + GLint cropLocation; + int cropHeight; + int cropWidth; + int cropX; + int cropY; + double duration; + double frameRate; + bool fullRange; + bool hardwareActive; + std::condition_variable imageCondition; + std::mutex imageMutex; + AImageReader* imageReader; + int imageSignals; + int imagesHeld; + GLuint imageTexture; + ANativeWindow* imageWindow; + bool nativeFences; + bool network; + std::string path; + double pixelAspectRatio; + MediaCodecReads reads; + int rotation; + EGLContext textureContext; + bool textureGLES3; + std::atomic textureOutput; + bool texturesFailed; + size_t uOffset; + int uvPixelStride; + int uvStride; + size_t vOffset; + MediaCodecStream video; + AMediaFormat* videoFormat; + int64_t videoPosition; + int videoRestarts; + int64_t videoSkipTime; + bool videoSurface; + int64_t videoTime; + size_t videoTrack; + bool videoTrackFound; + size_t yOffset; + int yStride; + + + }; + + + MediaCodecVideoBackend::MediaCodecVideoBackend () { + + audio.reads = &reads; + video.reads = &reads; + audioChannels = 0; + audioEncoding = PCM_ENCODING_16BIT; + audioPosition = 0; + audioRestarts = 0; + audioSampleRate = 0; + audioSkipTime = INT64_MIN; + audioSourceChannels = 0; + audioSourceRate = 0; + audioTime = INT64_MIN; + audioTrack = 0; + audioTrackFound = false; + colorMatrix = VIDEO_COLOR_MATRIX_BT709; + colorTransfer = VIDEO_COLOR_TRANSFER_SDR; + copyBuffer = 0; + copyFramebuffer = 0; + copyProgram = 0; + copyTexture = 0; + copyTextureHeight = 0; + copyTextureWidth = 0; + copyVertexArray = 0; + cropLocation = -1; + cropHeight = 0; + cropWidth = 0; + cropX = 0; + cropY = 0; + duration = 0; + frameRate = 0; + fullRange = false; + hardwareActive = false; + imageReader = NULL; + imageSignals = 0; + imagesHeld = 0; + imageTexture = 0; + imageWindow = NULL; + nativeFences = false; + network = false; + pixelAspectRatio = 0; + rotation = 0; + textureContext = EGL_NO_CONTEXT; + textureGLES3 = false; + textureOutput = false; + texturesFailed = false; + uOffset = 0; + uvPixelStride = 0; + uvStride = 0; + vOffset = 0; + videoFormat = NULL; + videoPosition = 0; + videoRestarts = 0; + videoSkipTime = INT64_MIN; + videoSurface = false; + videoTime = INT64_MIN; + videoTrack = 0; + videoTrackFound = false; + yOffset = 0; + yStride = 0; + + } + + + MediaCodecVideoBackend::~MediaCodecVideoBackend () { + + Close (); + + } + + + AImage* MediaCodecVideoBackend::AcquireImage () { + + // Frames reach the reader one at a time, so the codec never queues more + // than it holds, and each image is matched to its output buffer + { + std::unique_lock lock (imageMutex); + imageCondition.wait_for (lock, std::chrono::seconds (5), [this] { return imagesHeld < OUTPUT_IMAGES || video.interrupted; }); + if (imagesHeld >= OUTPUT_IMAGES) return NULL; + } + + int64_t time = video.info.presentationTimeUs; + ndk.AMediaCodec_releaseOutputBuffer (video.codec, video.index, true); + video.index = -1; + + std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now () + std::chrono::seconds (1); + + while (!video.interrupted) { + + int signals = 0; + + { + std::lock_guard lock (imageMutex); + signals = imageSignals; + } + + AImage* image = NULL; + media_status_t status = ndk.AImageReader_acquireNextImage (imageReader, &image); + + if (status == AMEDIA_OK && image) { + + int64_t timestamp = -1; + ndk.AImage_getTimestamp (image, ×tamp); + + if (timestamp / 1000 == time) { + + std::lock_guard lock (imageMutex); + imagesHeld++; + return image; + + } + + // A frame rendered before a flush, or a blank one from a codec + // that was released + ndk.AImage_delete (image); + continue; + + } + + if (status != AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE) return NULL; + + std::unique_lock lock (imageMutex); + if (!imageCondition.wait_until (lock, deadline, [this, signals] { return imageSignals != signals || video.interrupted; })) return NULL; + + } + + return NULL; + + } + + + void MediaCodecVideoBackend::Close () { + + // GL objects still held were left by closing away from the GL thread, as + // closing there releases them first + if (textureContext != EGL_NO_CONTEXT && (copyBuffer || copyFramebuffer || copyProgram || copyTexture || copyVertexArray || imageTexture)) { + + MediaCodecOrphan orphan = { textureContext, copyBuffer, copyFramebuffer, copyProgram, copyTexture, copyVertexArray, imageTexture }; + + { + std::lock_guard lock (*orphanMutex); + orphans->push_back (orphan); + } + + ForgetTextures (); + + } + + video.Close (); + audio.Close (); + + // VideoDecoder gives back every frame before closing, so no image + // outlives the reader + if (imageReader) { + + ndk.AImageReader_delete (imageReader); + imageReader = NULL; + imageWindow = NULL; + + } + + if (videoFormat) { + + ndk.AMediaFormat_delete (videoFormat); + videoFormat = NULL; + + } + + audioChannels = 0; + audioConverter.Reset (); + audioPosition = 0; + audioRestarts = 0; + audioSampleRate = 0; + audioSkipTime = INT64_MIN; + audioSourceChannels = 0; + audioSourceRate = 0; + audioTime = INT64_MIN; + audioTrackFound = false; + duration = 0; + frameRate = 0; + hardwareActive = false; + imagesHeld = 0; + network = false; + pixelAspectRatio = 0; + reads.seeking = false; + rotation = 0; + texturesFailed = false; + videoPosition = 0; + videoRestarts = 0; + videoSkipTime = INT64_MIN; + videoSurface = false; + videoTime = INT64_MIN; + videoTrackFound = false; + + } + + + AMediaExtractor* MediaCodecVideoBackend::CreateExtractor (AMediaDataSource** dataSource) { + + *dataSource = NULL; + + AMediaExtractor* extractor = ndk.AMediaExtractor_new (); + if (!extractor) return NULL; + + media_status_t status = AMEDIA_ERROR_UNKNOWN; + bool url = path.find ("://") != std::string::npos; + + #ifndef LIME_VIDEO_STANDALONE + // The extractor opens URLs through Java, so a thread that opens one, such + // as one VideoSource.loadFromFile uses, joins the Java VM, which SDL + // does for any thread that asks for its environment + if (url) SDL_AndroidGetJNIEnv (); + #endif + + #ifndef LIME_VIDEO_STANDALONE + if (path.compare (0, 10, "content://") == 0) { + + int fd = OpenContentURI (path.c_str ()); + + if (fd >= 0) { + + // A provider that streams through a pipe has no length + struct stat fileInfo; + off64_t length = (fstat (fd, &fileInfo) == 0 && fileInfo.st_size > 0) ? fileInfo.st_size : INT64_MAX; + status = ndk.AMediaExtractor_setDataSourceFd (extractor, fd, 0, length); + ::close (fd); + + } + + } else + #endif + if (url) { + + // From Android 10, a URL is read through a data source, which + // Interrupt can close, so a stream waiting on a stalled network + // returns rather than waiting for the connection to time out + if (ndk.AMediaDataSource_newUri && ndk.AMediaDataSource_close && ndk.AMediaDataSource_delete && ndk.AMediaExtractor_setDataSourceCustom) { + + *dataSource = ndk.AMediaDataSource_newUri (path.c_str (), 0, NULL); + + if (*dataSource) { + + status = ndk.AMediaExtractor_setDataSourceCustom (extractor, *dataSource); + + if (status != AMEDIA_OK) { + + ndk.AMediaExtractor_delete (extractor); + ndk.AMediaDataSource_delete (*dataSource); + *dataSource = NULL; + extractor = ndk.AMediaExtractor_new (); + if (!extractor) return NULL; + + } + + } + + } + + if (status != AMEDIA_OK) status = ndk.AMediaExtractor_setDataSource (extractor, path.c_str ()); + + } else { + + // Each extractor opens the file itself, since descriptors that share + // an open file also share its read position + int fd = ::open (path.c_str (), O_RDONLY | O_CLOEXEC); + off64_t offset = 0; + off64_t length = 0; + struct stat fileInfo; + + if (fd >= 0 && fstat (fd, &fileInfo) == 0) length = fileInfo.st_size; + + #ifndef LIME_VIDEO_STANDALONE + if (fd < 0) { + + // An asset in the APK opens as the APK file, at the asset's offset + FILE_HANDLE* handle = lime::fopen (path.c_str (), "rb"); + + if (handle) { + + FILE* file = handle->getFile (); + + if (file) { + + fd = dup (fileno (file)); + offset = ::ftell (file); + length = handle->getLength (); + + // getFile opens a new stream for an asset + if (!handle->isFile ()) ::fclose (file); + + } + + lime::fclose (handle); + + } + + } + #endif + + // The extractor keeps its own duplicate of the descriptor + if (fd >= 0) { + + status = ndk.AMediaExtractor_setDataSourceFd (extractor, fd, offset, length); + ::close (fd); + + } + + } + + if (status != AMEDIA_OK) { + + ndk.AMediaExtractor_delete (extractor); + + if (*dataSource) { + + ndk.AMediaDataSource_delete (*dataSource); + *dataSource = NULL; + + } + + return NULL; + + } + + return extractor; + + } + + + ANativeWindow* MediaCodecVideoBackend::CreateImageReader (int width, int height) { + + if (imageWindow) return imageWindow; + if (!CanUseImageReader ()) return NULL; + + AImageReader* reader = NULL; + + if (ndk.AImageReader_newWithUsage (std::max (width, 1), std::max (height, 1), IMAGE_FORMAT_PRIVATE, IMAGE_USAGE_GPU_SAMPLED, OUTPUT_IMAGES, &reader) != AMEDIA_OK || !reader) { + + return NULL; + + } + + ImageListener listener = { this, OnImageAvailable }; + ANativeWindow* window = NULL; + + if (ndk.AImageReader_setImageListener (reader, &listener) != AMEDIA_OK || ndk.AImageReader_getWindow (reader, &window) != AMEDIA_OK || !window) { + + ndk.AImageReader_delete (reader); + return NULL; + + } + + imageReader = reader; + imageWindow = window; + + return window; + + } + + + bool MediaCodecVideoBackend::CreateTextures () { + + GLuint vertexShader = CompileShader (GL_VERTEX_SHADER, COPY_VERTEX_SHADER); + GLuint fragmentShader = CompileShader (GL_FRAGMENT_SHADER, COPY_FRAGMENT_SHADER); + GLuint program = 0; + + if (vertexShader && fragmentShader) { + + program = glCreateProgram (); + glAttachShader (program, vertexShader); + glAttachShader (program, fragmentShader); + glBindAttribLocation (program, 0, "position"); + glLinkProgram (program); + + GLint linked = 0; + glGetProgramiv (program, GL_LINK_STATUS, &linked); + + if (!linked) { + + glDeleteProgram (program); + program = 0; + + } + + } + + if (vertexShader) glDeleteShader (vertexShader); + if (fragmentShader) glDeleteShader (fragmentShader); + if (!program) return false; + + copyProgram = program; + cropLocation = glGetUniformLocation (program, "crop"); + glUseProgram (program); + glUniform1i (glGetUniformLocation (program, "image"), 0); + + static const GLfloat positions[] = { -1, -1, 1, -1, -1, 1, 1, 1 }; + glGenBuffers (1, ©Buffer); + glBindBuffer (GL_ARRAY_BUFFER, copyBuffer); + glBufferData (GL_ARRAY_BUFFER, sizeof (positions), positions, GL_STATIC_DRAW); + + if (textureGLES3) { + + _glGenVertexArrays (1, ©VertexArray); + _glBindVertexArray (copyVertexArray); + glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE, 0, NULL); + glEnableVertexAttribArray (0); + + } + + glGenTextures (1, &imageTexture); + glBindTexture (GL_TEXTURE_EXTERNAL_OES, imageTexture); + glTexParameteri (GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri (GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glGenTextures (1, ©Texture); + glBindTexture (GL_TEXTURE_2D, copyTexture); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glGenFramebuffers (1, ©Framebuffer); + copyTextureWidth = 0; + copyTextureHeight = 0; + + return true; + + } + + + AMediaCodec* MediaCodecVideoBackend::CreateVideoDecoder (bool hardware, bool surface) { + + // HDR video is tone mapped to standard range, in 8-bit frames, by + // decoders that can from Android 13, rather than shown washed out. A + // decoder that refuses the request is configured without it. + for (int attempt = 0; attempt < 2; attempt++) { + + // Configuring with a surface stores it in the format, so each codec + // gets its own copy of the track format + AMediaFormat* format = ndk.AMediaExtractor_getTrackFormat (video.extractor, videoTrack); + if (!format) return NULL; + + int32_t transfer = 0; + bool hdr = ndk.AMediaFormat_getInt32 (format, "color-transfer", &transfer) && (transfer == COLOR_TRANSFER_ST2084 || transfer == COLOR_TRANSFER_HLG); + + if (ndk.AMediaFormat_setInt32) { + + // Containers without an index, such as MPEG-TS, do not say how + // large a sample can be, and a keyframe larger than the decoder's + // default buffer could not be read + int32_t maxInputSize = 0; + int32_t width = 0; + int32_t height = 0; + + if (!ndk.AMediaFormat_getInt32 (format, "max-input-size", &maxInputSize) && ndk.AMediaFormat_getInt32 (format, "width", &width) && ndk.AMediaFormat_getInt32 (format, "height", &height)) { + + int64_t estimate = (int64_t)width * height * 3 / 4; + ndk.AMediaFormat_setInt32 (format, "max-input-size", (int32_t)std::min (std::max (estimate, 1 << 20), 16 << 20)); + + } + + if (hdr && attempt == 0) { + + ndk.AMediaFormat_setInt32 (format, "color-transfer-request", COLOR_TRANSFER_SDR_VIDEO); + if (!surface) ndk.AMediaFormat_setInt32 (format, "color-format", COLOR_FORMAT_YUV420_FLEXIBLE); + + } + + } + + AMediaCodec* codec = CreateDecoder (format, hardware, surface ? imageWindow : NULL); + ndk.AMediaFormat_delete (format); + + if (codec || !hdr || !ndk.AMediaFormat_setInt32) return codec; + + } + + return NULL; + + } + + + VideoDecodeResult MediaCodecVideoBackend::DecodeAudio (std::vector* pcm, double* time) { + + if (audio.reopen && audio.codec) { + + VideoDecodeResult result = ReopenStream (&audio, audioTrack); + if (result != VIDEO_DECODE_OK) return result; + + } + + while (audio.codec) { + + bool formatChanged = false; + VideoDecodeResult result = audio.Read (&formatChanged); + + if (formatChanged && !ReadAudioFormat ()) result = VIDEO_DECODE_ERROR; + + // After restarting, the audio up to the last packet returned is skipped + if (result == VIDEO_DECODE_OK && audio.info.presentationTimeUs <= audioSkipTime) { + + audio.Release (); + continue; + + } + + size_t capacity = 0; + uint8_t* data = result == VIDEO_DECODE_OK ? ndk.AMediaCodec_getOutputBuffer (audio.codec, audio.index, &capacity) : NULL; + size_t offset = (size_t)audio.info.offset; + size_t length = (size_t)audio.info.size; + int sampleBytes = GetSampleBytes (audioEncoding); + + if (result == VIDEO_DECODE_OK && (!data || offset > capacity || length > capacity - offset || sampleBytes <= 0 || audioSourceChannels <= 0)) { + + result = VIDEO_DECODE_ERROR; + + } + + if (result != VIDEO_DECODE_OK) { + + audio.Release (); + + // A codec that fails, or that the system reclaims for another app, is + // replaced, carrying on after the last audio returned + if (result == VIDEO_DECODE_ERROR && RestartAudio ()) continue; + + return result; + + } + + int frames = (int)(length / (audioSourceChannels * sampleBytes)); + const short* samples = (const short*)(data + offset); + + if (audioEncoding != PCM_ENCODING_16BIT) { + + ConvertSamples (data + offset, (size_t)frames * audioSourceChannels, audioEncoding, &audioSamples); + samples = audioSamples.data (); + + } + + // Audio is given in the format it opened with, downmixing surround and + // following a stream that changes its rate or channels + if (audioSourceChannels == audioChannels && audioSourceRate == audioSampleRate) { + + pcm->insert (pcm->end (), (const unsigned char*)samples, (const unsigned char*)(samples + frames * audioChannels)); + + } else { + + ConvertAudio (samples, frames, audioSourceChannels, audioSourceRate, audioChannels, audioSampleRate, &audioConverter, pcm); + + } + + audioRestarts = 0; + audioSkipTime = INT64_MIN; + audioTime = audio.info.presentationTimeUs; + *time = audioTime / 1000000.0; + audio.Release (); + + return VIDEO_DECODE_OK; + + } + + return VIDEO_DECODE_END; + + } + + + VideoDecodeResult MediaCodecVideoBackend::DecodeVideo (VideoPlanes* planes, double* time, double* duration) { + + if (video.reopen && video.codec) { + + VideoDecodeResult result = ReopenStream (&video, videoTrack); + if (result != VIDEO_DECODE_OK) return result; + + } + + // Texture output stopped, and VideoDecoder reads in memory until the + // seek that follows, so continue after the last frame in memory + if (videoSurface && video.codec) { + + // A decoder that cannot give the same video in memory, as with a + // layout that cannot be read, is replaced in software + videoSkipTime = videoTime; + if (!ReplaceVideoCodec (hardwareActive, false, std::max (videoPosition, videoTime)) && !RestartVideo ()) return VIDEO_DECODE_ERROR; + + } + + while (video.codec) { + + bool formatChanged = false; + VideoDecodeResult result = video.Read (&formatChanged); + + if (formatChanged) ReadVideoFormat (); + if (result == VIDEO_DECODE_OK && video.info.presentationTimeUs <= videoSkipTime) continue; + if (result == VIDEO_DECODE_OK && !GetPlanes (planes)) result = VIDEO_DECODE_ERROR; + + // Some hardware decoders fail on a stream they accepted, or lose it to + // another app, so continue from the same frame + if (result == VIDEO_DECODE_ERROR && RestartVideo ()) continue; + if (result != VIDEO_DECODE_OK) return result; + + videoRestarts = 0; + videoSkipTime = INT64_MIN; + videoTime = video.info.presentationTimeUs; + *time = videoTime / 1000000.0; + *duration = frameRate > 0 ? 1.0 / frameRate : 0; + + return VIDEO_DECODE_OK; + + } + + return VIDEO_DECODE_END; + + } + + + VideoDecodeResult MediaCodecVideoBackend::DecodeVideoTexture (VideoTextureFrame** frame, double* time, double* duration) { + + *frame = NULL; + + if (video.reopen && video.codec) { + + VideoDecodeResult result = ReopenStream (&video, videoTrack); + if (result != VIDEO_DECODE_OK) return result; + + } + + while (video.codec) { + + // VideoDecoder reads the next frame with DecodeVideo instead + if (!SupportsTextures ()) return VIDEO_DECODE_OK; + + bool formatChanged = false; + VideoDecodeResult result = video.Read (&formatChanged); + + if (formatChanged) ReadVideoFormat (); + if (result == VIDEO_DECODE_OK && video.info.presentationTimeUs <= videoSkipTime) continue; + if (result == VIDEO_DECODE_ERROR && RestartVideo ()) continue; + if (result != VIDEO_DECODE_OK) return result; + + AImage* image = AcquireImage (); + AImageCropRect crop; + + if (!image || ndk.AImage_getCropRect (image, &crop) != AMEDIA_OK || crop.left < 0 || crop.top < 0 || crop.right <= crop.left || crop.bottom <= crop.top) { + + // Frames that do not reach the reader are decoded into memory + if (image) ReleaseImage (image, -1); + texturesFailed = true; + return VIDEO_DECODE_OK; + + } + + MediaCodecTextureFrame* textureFrame = new MediaCodecTextureFrame (); + textureFrame->cropX = crop.left; + textureFrame->cropY = crop.top; + textureFrame->fence = -1; + textureFrame->height = crop.bottom - crop.top; + textureFrame->image = image; + textureFrame->width = crop.right - crop.left; + *frame = textureFrame; + + videoRestarts = 0; + videoSkipTime = INT64_MIN; + videoTime = video.info.presentationTimeUs; + *time = videoTime / 1000000.0; + *duration = frameRate > 0 ? 1.0 / frameRate : 0; + + return VIDEO_DECODE_OK; + + } + + return VIDEO_DECODE_END; + + } + + + void MediaCodecVideoBackend::ForgetTextures () { + + // Leaves the GL objects, which belong to a context that may be gone + copyBuffer = 0; + copyFramebuffer = 0; + copyProgram = 0; + copyTexture = 0; + copyTextureHeight = 0; + copyTextureWidth = 0; + copyVertexArray = 0; + cropLocation = -1; + imageTexture = 0; + nativeFences = false; + textureContext = EGL_NO_CONTEXT; + textureGLES3 = false; + + } + + + bool MediaCodecVideoBackend::GetPlanes (VideoPlanes* planes) { + + size_t capacity = 0; + uint8_t* data = ndk.AMediaCodec_getOutputBuffer (video.codec, video.index, &capacity); + size_t offset = (size_t)video.info.offset; + + if (!data || yStride <= 0 || cropWidth <= 0 || cropHeight <= 0 || offset > capacity) return false; + + int chromaX = cropX / 2; + int chromaY = cropY / 2; + size_t size = capacity - offset; + size_t lumaEnd = yOffset + (size_t)(cropY + cropHeight - 1) * yStride + cropX + cropWidth; + size_t chromaEnd = (size_t)(chromaY + (cropHeight + 1) / 2 - 1) * uvStride + (size_t)(chromaX + (cropWidth + 1) / 2 - 1) * uvPixelStride + 1; + + if (lumaEnd > size || uOffset + chromaEnd > size || vOffset + chromaEnd > size) return false; + + data += offset; + + planes->colorMatrix = colorMatrix; + planes->fullRange = fullRange; + planes->transfer = colorTransfer; + planes->width = cropWidth; + planes->height = cropHeight; + planes->y = data + yOffset + (size_t)cropY * yStride + cropX; + planes->yStride = yStride; + planes->u = data + uOffset + (size_t)chromaY * uvStride + chromaX * uvPixelStride; + planes->v = data + vOffset + (size_t)chromaY * uvStride + chromaX * uvPixelStride; + planes->uvStride = uvStride; + planes->uvPixelStride = uvPixelStride; + + return true; + + } + + + void MediaCodecVideoBackend::Interrupt () { + + audio.interrupted = true; + video.interrupted = true; + + { + // Reads waiting on a stalled network fail at once + std::lock_guard lock (reads.mutex); + if (audio.dataSource) ndk.AMediaDataSource_close (audio.dataSource); + if (video.dataSource) ndk.AMediaDataSource_close (video.dataSource); + } + + std::lock_guard lock (imageMutex); + imageCondition.notify_all (); + + } + + + unsigned int MediaCodecVideoBackend::LockTexture (VideoTextureFrame* frame) { + + MediaCodecTextureFrame* textureFrame = (MediaCodecTextureFrame*)frame; + EGLDisplay display = eglGetCurrentDisplay (); + EGLContext context = eglGetCurrentContext (); + + if (display == EGL_NO_DISPLAY || context == EGL_NO_CONTEXT || !LoadEGLExtensions ()) return 0; + + if (context != textureContext) ForgetTextures (); + textureContext = context; + + if (!copyProgram) { + + const char* eglExtensions = eglQueryString (display, EGL_EXTENSIONS); + const char* glExtensions = (const char*)glGetString (GL_EXTENSIONS); + const char* version = (const char*)glGetString (GL_VERSION); + + if (!HasExtension (eglExtensions, "EGL_ANDROID_image_native_buffer") || !HasExtension (glExtensions, "GL_OES_EGL_image_external")) return 0; + + // A GLES 3 context draws the copy with its own vertex array + textureGLES3 = version && strncmp (version, "OpenGL ES ", 10) == 0 && atoi (version + 10) >= 3 && _glBindVertexArray && _glDeleteVertexArrays && _glGenVertexArrays; + nativeFences = HasExtension (eglExtensions, "EGL_ANDROID_native_fence_sync") && _eglCreateSyncKHR && _eglDestroySyncKHR && _eglDupNativeFenceFDANDROID; + + } + + AHardwareBuffer* buffer = NULL; + HardwareBufferDesc desc; + memset (&desc, 0, sizeof (desc)); + + if (ndk.AImage_getHardwareBuffer (textureFrame->image, &buffer) != AMEDIA_OK || !buffer) return 0; + + ndk.AHardwareBuffer_describe (buffer, &desc); + + int width = textureFrame->width; + int height = textureFrame->height; + + if (desc.width == 0 || desc.height == 0 || textureFrame->cropX + width > (int)desc.width || textureFrame->cropY + height > (int)desc.height) return 0; + + EGLClientBuffer clientBuffer = _eglGetNativeClientBufferANDROID (buffer); + EGLint attributes[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; + EGLImageKHR image = clientBuffer ? _eglCreateImageKHR (display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attributes) : EGL_NO_IMAGE_KHR; + + if (image == EGL_NO_IMAGE_KHR) return 0; + + SavedGLState state; + SaveGLState (&state, textureGLES3); + + bool copied = false; + + if (copyProgram || CreateTextures ()) { + + glActiveTexture (GL_TEXTURE0); + glBindTexture (GL_TEXTURE_EXTERNAL_OES, imageTexture); + _glEGLImageTargetTexture2DOES (GL_TEXTURE_EXTERNAL_OES, image); + + if (copyTextureWidth != width || copyTextureHeight != height) { + + // A bound unpack buffer would be read as the initial contents + if (textureGLES3) glBindBuffer (GL_PIXEL_UNPACK_BUFFER, 0); + + glBindTexture (GL_TEXTURE_2D, copyTexture); + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glBindFramebuffer (GL_FRAMEBUFFER, copyFramebuffer); + glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, copyTexture, 0); + + copyTextureWidth = width; + copyTextureHeight = height; + + } + + glBindFramebuffer (GL_FRAMEBUFFER, copyFramebuffer); + + if (glCheckFramebufferStatus (GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { + + glDisable (GL_BLEND); + glDisable (GL_CULL_FACE); + glDisable (GL_DEPTH_TEST); + glDisable (GL_SCISSOR_TEST); + glDisable (GL_STENCIL_TEST); + if (textureGLES3) glDisable (GL_RASTERIZER_DISCARD); + glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glViewport (0, 0, width, height); + + glUseProgram (copyProgram); + glUniform4f (cropLocation, (float)textureFrame->cropX / desc.width, (float)textureFrame->cropY / desc.height, (float)width / desc.width, (float)height / desc.height); + + if (textureGLES3) { + + _glBindVertexArray (copyVertexArray); + + } else { + + glBindBuffer (GL_ARRAY_BUFFER, copyBuffer); + glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE, 0, NULL); + glEnableVertexAttribArray (0); + + } + + // The driver converts YUV to RGB as it samples the external texture + glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); + copied = true; + + } + + } + + RestoreGLState (&state, textureGLES3); + + // The texture keeps the buffer it was drawn from, so the image is not needed + _eglDestroyImageKHR (display, image); + + // The decoder can reuse the buffer once the copy has read it, which a + // native fence tells it without waiting here + if (copied && nativeFences) { + + EGLSyncKHR sync = _eglCreateSyncKHR (display, EGL_SYNC_NATIVE_FENCE_ANDROID, NULL); + + if (sync != EGL_NO_SYNC_KHR) { + + glFlush (); + if (textureFrame->fence >= 0) ::close (textureFrame->fence); + textureFrame->fence = _eglDupNativeFenceFDANDROID (display, sync); + _eglDestroySyncKHR (display, sync); + + } else { + + glFlush (); + + } + + } else { + + glFlush (); + + } + + return copied ? copyTexture : 0; + + } + + + void MediaCodecVideoBackend::OnImageAvailable (void* context, AImageReader* reader) { + + MediaCodecVideoBackend* backend = (MediaCodecVideoBackend*)context; + + std::lock_guard lock (backend->imageMutex); + backend->imageSignals++; + backend->imageCondition.notify_all (); + + } + + + bool MediaCodecVideoBackend::Open (const char* path, bool hardwareDecoding, VideoStreamInfo* info) { + + Close (); + + if (!path || !path[0]) return false; + + this->path = path; + + // A seek can give up on a URL read through a data source, from Android 10 + std::string source = path; + network = source.find ("://") != std::string::npos && source.compare (0, 10, "content://") != 0 && source.compare (0, 7, "file://") != 0 + && ndk.AMediaDataSource_newUri && ndk.AMediaDataSource_close && ndk.AMediaDataSource_delete && ndk.AMediaExtractor_setDataSourceCustom; + + info->hasVideo = OpenVideo (hardwareDecoding) || (hardwareDecoding && OpenVideo (false)); + info->hasAudio = OpenAudio (); + + // A track that was found but cannot be decoded, such as one in a codec the + // device lacks + info->videoUnsupported = !info->hasVideo && videoTrackFound; + info->audioUnsupported = !info->hasAudio && audioTrackFound; + + if (!info->hasVideo && !info->hasAudio) return false; + + info->audioChannels = audioChannels; + info->audioSampleRate = audioSampleRate; + info->duration = duration; + info->frameRate = info->hasVideo ? frameRate : 0; + info->pixelAspectRatio = info->hasVideo ? pixelAspectRatio : 0; + info->rotation = info->hasVideo ? rotation : 0; + info->width = info->hasVideo ? cropWidth : 0; + info->height = info->hasVideo ? cropHeight : 0; + + return true; + + } + + + bool MediaCodecVideoBackend::OpenAudio () { + + AMediaDataSource* dataSource = NULL; + AMediaExtractor* extractor = CreateExtractor (&dataSource); + if (!extractor) return false; + + AMediaFormat* format = SelectTrack (extractor, "audio/", &audioTrack); + + if (!format) { + + ndk.AMediaExtractor_delete (extractor); + if (dataSource) ndk.AMediaDataSource_delete (dataSource); + return false; + + } + + audioTrackFound = true; + + int64_t durationUs = 0; + ndk.AMediaFormat_getInt64 (format, "durationUs", &durationUs); + + // Decoders that can choose give 16-bit samples, which need no conversion + if (ndk.AMediaFormat_setInt32) ndk.AMediaFormat_setInt32 (format, "pcm-encoding", PCM_ENCODING_16BIT); + + // Hardware decoding only applies to video + audio.codec = CreateDecoder (format, false, NULL); + audio.dataSource = dataSource; + audio.extractor = extractor; + ndk.AMediaFormat_delete (format); + + if (!audio.codec || !audio.Prime () || !ReadAudioFormat ()) { + + audio.Close (); + return false; + + } + + audioChannels = audioSourceChannels > 2 ? 2 : audioSourceChannels; + audioSampleRate = audioSourceRate; + audioConverter.Reset (); + if (durationUs / 1000000.0 > duration) duration = durationUs / 1000000.0; + + return true; + + } + + + bool MediaCodecVideoBackend::OpenVideo (bool hardware) { + + AMediaDataSource* dataSource = NULL; + AMediaExtractor* extractor = CreateExtractor (&dataSource); + if (!extractor) return false; + + AMediaFormat* format = SelectTrack (extractor, "video/", &videoTrack); + + if (!format) { + + ndk.AMediaExtractor_delete (extractor); + if (dataSource) ndk.AMediaDataSource_delete (dataSource); + return false; + + } + + videoTrackFound = true; + + // Phones store video recorded upright sideways, with the turn that shows + // it upright, and some video has pixels that are not square. Decoding + // keeps frames as they are stored. + int32_t degrees = 0; + int32_t sarWidth = 0; + int32_t sarHeight = 0; + rotation = ndk.AMediaFormat_getInt32 (format, "rotation-degrees", °rees) ? degrees : 0; + pixelAspectRatio = ndk.AMediaFormat_getInt32 (format, "sar-width", &sarWidth) && ndk.AMediaFormat_getInt32 (format, "sar-height", &sarHeight) && sarWidth > 0 && sarHeight > 0 ? (double)sarWidth / sarHeight : 0; + + float floatRate = 0; + int32_t intRate = 0; + double formatRate = ndk.AMediaFormat_getFloat (format, "frame-rate", &floatRate) ? floatRate : (ndk.AMediaFormat_getInt32 (format, "frame-rate", &intRate) ? intRate : 0); + + // Containers may round the frame rate, so prefer the spacing of the + // first frames when it agrees + frameRate = EstimateFrameRate (extractor); + if (formatRate > 0 && !(fabs (frameRate - formatRate) < 0.5)) frameRate = formatRate; + + int64_t durationUs = 0; + int32_t trackWidth = 0; + int32_t trackHeight = 0; + ndk.AMediaFormat_getInt64 (format, "durationUs", &durationUs); + ndk.AMediaFormat_getInt32 (format, "width", &trackWidth); + ndk.AMediaFormat_getInt32 (format, "height", &trackHeight); + + // Containers also give the size to show the picture at, which for pixels + // that are not square is wider or taller than the picture + int32_t displayWidth = 0; + int32_t displayHeight = 0; + + if (pixelAspectRatio <= 0 && trackWidth > 0 && trackHeight > 0 && ndk.AMediaFormat_getInt32 (format, "display-width", &displayWidth) && ndk.AMediaFormat_getInt32 (format, "display-height", &displayHeight) && displayWidth > 0 && displayHeight > 0) { + + double shape = ((double)displayWidth / trackWidth) / ((double)displayHeight / trackHeight); + if (fabs (shape - 1.0) > 0.01) pixelAspectRatio = shape; + + } + + // Texture output decodes into an image reader, falling back to memory + // when the decoder cannot use one + bool surface = textureOutput && CreateImageReader (trackWidth, trackHeight); + + video.dataSource = dataSource; + video.extractor = extractor; + videoFormat = format; + video.codec = surface ? CreateVideoDecoder (hardware, true) : NULL; + videoSurface = (video.codec != NULL); + if (!video.codec) video.codec = CreateVideoDecoder (hardware, false); + hardwareActive = hardware; + + if (!video.codec || !video.Prime () || !ReadVideoFormat () || cropWidth <= 0 || cropHeight <= 0) { + + video.Close (); + ndk.AMediaFormat_delete (videoFormat); + videoFormat = NULL; + hardwareActive = false; + videoSurface = false; + return false; + + } + + if (durationUs / 1000000.0 > duration) duration = durationUs / 1000000.0; + + return true; + + } + + + bool MediaCodecVideoBackend::ReadAudioFormat () { + + AMediaFormat* format = ndk.AMediaCodec_getOutputFormat (audio.codec); + if (!format) return false; + + int32_t channels = 0; + int32_t encoding = PCM_ENCODING_16BIT; + int32_t sampleRate = 0; + + ndk.AMediaFormat_getInt32 (format, "channel-count", &channels); + ndk.AMediaFormat_getInt32 (format, "pcm-encoding", &encoding); + ndk.AMediaFormat_getInt32 (format, "sample-rate", &sampleRate); + ndk.AMediaFormat_delete (format); + + // The format of the audio being decoded now, which can change partway + audioEncoding = encoding; + audioSourceChannels = channels; + audioSourceRate = sampleRate; + + return (channels > 0 && channels <= 8 && sampleRate > 0 && GetSampleBytes (encoding) > 0); + + } + + + bool MediaCodecVideoBackend::ReadPlaneLayout (AMediaFormat* format, int width, int height) { + + void* image = NULL; + size_t imageSize = 0; + + if (ndk.AMediaFormat_getBuffer (format, "image-data", &image, &imageSize) && imageSize >= MEDIA_IMAGE_SIZE) { + + uint32_t header[6] = {}; + MediaImagePlane plane[3]; + size_t headerSize = imageSize >= MEDIA_IMAGE2_SIZE ? 24 : 20; + + memcpy (header, image, headerSize); + memcpy (plane, (const uint8_t*)image + headerSize, sizeof (plane)); + + bool eightBit = (header[4] == 8 && (headerSize == 20 || header[5] == 8)); + bool luma = (plane[0].colInc == 1 && plane[0].rowInc > 0 && plane[0].horizSubsampling == 1 && plane[0].vertSubsampling == 1); + bool chroma = (plane[1].colInc >= 1 && plane[1].colInc <= 2 && plane[1].rowInc > 0 && plane[1].colInc == plane[2].colInc && plane[1].rowInc == plane[2].rowInc); + bool subsampled = (plane[1].horizSubsampling == 2 && plane[1].vertSubsampling == 2 && plane[2].horizSubsampling == 2 && plane[2].vertSubsampling == 2); + + if (header[0] == MEDIA_IMAGE_TYPE_YUV && header[1] == 3 && eightBit && luma && chroma && subsampled) { + + yOffset = plane[0].offset; + yStride = plane[0].rowInc; + uOffset = plane[1].offset; + vOffset = plane[2].offset; + uvStride = plane[1].rowInc; + uvPixelStride = plane[1].colInc; + return true; + + } + + } + + int32_t colorFormat = 0; + int32_t sliceHeight = 0; + int32_t stride = 0; + + ndk.AMediaFormat_getInt32 (format, "color-format", &colorFormat); + ndk.AMediaFormat_getInt32 (format, "slice-height", &sliceHeight); + ndk.AMediaFormat_getInt32 (format, "stride", &stride); + + // Some decoders report no stride or a slice height of 0 + if (stride < width) stride = width; + if (sliceHeight < height) sliceHeight = height; + + if (colorFormat == COLOR_FORMAT_QCOM_YUV420_SEMI_PLANAR_32M) { + + // Venus buffers align the stride to 128 and the height to 32 + stride = std::max (stride, (width + 127) & ~127); + sliceHeight = std::max (sliceHeight, (height + 31) & ~31); + + } + + yOffset = 0; + yStride = stride; + uOffset = (size_t)stride * sliceHeight; + + switch (colorFormat) { + + case COLOR_FORMAT_YUV420_PLANAR: + case COLOR_FORMAT_YUV420_PACKED_PLANAR: + case COLOR_FORMAT_YUV420_FLEXIBLE: + + uvStride = (stride + 1) / 2; + vOffset = uOffset + (size_t)uvStride * ((sliceHeight + 1) / 2); + uvPixelStride = 1; + return true; + + case COLOR_FORMAT_YUV420_SEMI_PLANAR: + case COLOR_FORMAT_YUV420_PACKED_SEMI_PLANAR: + case COLOR_FORMAT_QCOM_YUV420_SEMI_PLANAR: + case COLOR_FORMAT_QCOM_YUV420_SEMI_PLANAR_32M: + + uvStride = stride; + vOffset = uOffset + 1; + uvPixelStride = 2; + return true; + + default: + + return false; + + } + + } + + + bool MediaCodecVideoBackend::ReadVideoFormat () { + + AMediaFormat* format = ndk.AMediaCodec_getOutputFormat (video.codec); + if (!format) return false; + + int32_t width = 0; + int32_t height = 0; + int32_t trackWidth = 0; + int32_t trackHeight = 0; + + ndk.AMediaFormat_getInt32 (format, "width", &width); + ndk.AMediaFormat_getInt32 (format, "height", &height); + ndk.AMediaFormat_getInt32 (videoFormat, "width", &trackWidth); + ndk.AMediaFormat_getInt32 (videoFormat, "height", &trackHeight); + + if (width <= 0 || height <= 0) { + + width = trackWidth; + height = trackHeight; + + } + + int32_t left = 0; + int32_t top = 0; + int32_t right = -1; + int32_t bottom = -1; + + bool cropped = (ndk.AMediaFormat_getRect && ndk.AMediaFormat_getRect (format, "crop", &left, &top, &right, &bottom)); + + if (!cropped || left < 0 || top < 0 || right < left || bottom < top || right >= width || bottom >= height) { + + // The crop rectangle cannot be read before API level 28, so use the + // track's picture size + left = 0; + top = 0; + right = (trackWidth > 0 && trackWidth < width ? trackWidth : width) - 1; + bottom = (trackHeight > 0 && trackHeight < height ? trackHeight : height) - 1; + + } + + cropX = left; + cropY = top; + cropWidth = right - left + 1; + cropHeight = bottom - top + 1; + + // Decoders give the pixel shape from the stream itself where the + // container does not + int32_t sarWidth = 0; + int32_t sarHeight = 0; + + if (pixelAspectRatio <= 0 && ndk.AMediaFormat_getInt32 (format, "sar-width", &sarWidth) && ndk.AMediaFormat_getInt32 (format, "sar-height", &sarHeight) && sarWidth > 0 && sarHeight > 0) { + + pixelAspectRatio = (double)sarWidth / sarHeight; + + } + + // Frames decoded into the image reader have no memory layout to read + bool supported = videoSurface || ReadPlaneLayout (format, width, height); + + // Decoders only report the color aspects that the stream signals + int32_t range = 0; + int32_t standard = 0; + + if (!ndk.AMediaFormat_getInt32 (format, "color-range", &range) || range == 0) ndk.AMediaFormat_getInt32 (videoFormat, "color-range", &range); + if (!ndk.AMediaFormat_getInt32 (format, "color-standard", &standard) || standard == 0) ndk.AMediaFormat_getInt32 (videoFormat, "color-standard", &standard); + + // Decoders that tone map HDR, which they can from Android 13, say their + // frames are standard range. HDR that is not is tone mapped by + // VideoDecoder. + int32_t transfer = 0; + if ((!ndk.AMediaFormat_getInt32 (format, "color-transfer", &transfer) || transfer == 0) && GetSystemVersion () < 33) ndk.AMediaFormat_getInt32 (videoFormat, "color-transfer", &transfer); + colorTransfer = transfer == COLOR_TRANSFER_ST2084 ? VIDEO_COLOR_TRANSFER_PQ : (transfer == COLOR_TRANSFER_HLG ? VIDEO_COLOR_TRANSFER_HLG : VIDEO_COLOR_TRANSFER_SDR); + + ndk.AMediaFormat_delete (format); + + switch (standard) { + + case COLOR_STANDARD_BT709: colorMatrix = VIDEO_COLOR_MATRIX_BT709; break; + case COLOR_STANDARD_BT601_PAL: case COLOR_STANDARD_BT601_NTSC: colorMatrix = VIDEO_COLOR_MATRIX_BT601; break; + case COLOR_STANDARD_BT2020: colorMatrix = VIDEO_COLOR_MATRIX_BT2020; break; + default: colorMatrix = GetDefaultColorMatrix (cropWidth, cropHeight); break; + + } + + fullRange = (range == COLOR_RANGE_FULL); + + // DecodeVideo fails on a layout it cannot read + if (!supported) yStride = 0; + + return supported; + + } + + + void MediaCodecVideoBackend::ReleaseImage (AImage* image, int fence) { + + // The reader waits for the fence before the decoder writes the buffer again + if (fence >= 0) { + + ndk.AImage_deleteAsync (image, fence); + + } else { + + ndk.AImage_delete (image); + + } + + std::lock_guard lock (imageMutex); + imagesHeld--; + imageCondition.notify_all (); + + } + + + void MediaCodecVideoBackend::ReleaseTextureFrame (VideoTextureFrame* frame) { + + MediaCodecTextureFrame* textureFrame = (MediaCodecTextureFrame*)frame; + ReleaseImage (textureFrame->image, textureFrame->fence); + delete textureFrame; + + } + + + void MediaCodecVideoBackend::ReleaseOrphans () { + + std::vector list; + + { + std::lock_guard lock (*orphanMutex); + if (orphans->empty ()) return; + list.swap (*orphans); + } + + EGLContext context = eglGetCurrentContext (); + + for (size_t i = 0; i < list.size (); i++) { + + // Objects from a context that is no longer current are left with it + MediaCodecOrphan& orphan = list[i]; + if (orphan.context != context) continue; + + if (orphan.copyBuffer) glDeleteBuffers (1, &orphan.copyBuffer); + if (orphan.copyFramebuffer) glDeleteFramebuffers (1, &orphan.copyFramebuffer); + if (orphan.copyProgram) glDeleteProgram (orphan.copyProgram); + if (orphan.copyTexture) glDeleteTextures (1, &orphan.copyTexture); + if (orphan.copyVertexArray && _glDeleteVertexArrays) _glDeleteVertexArrays (1, &orphan.copyVertexArray); + if (orphan.imageTexture) glDeleteTextures (1, &orphan.imageTexture); + + } + + } + + + void MediaCodecVideoBackend::ReleaseTextures () { + + // GL names only mean these objects in the context that made them + if (textureContext != EGL_NO_CONTEXT && eglGetCurrentContext () == textureContext) { + + if (copyBuffer) glDeleteBuffers (1, ©Buffer); + if (copyFramebuffer) glDeleteFramebuffers (1, ©Framebuffer); + if (copyProgram) glDeleteProgram (copyProgram); + if (copyTexture) glDeleteTextures (1, ©Texture); + if (copyVertexArray) _glDeleteVertexArrays (1, ©VertexArray); + if (imageTexture) glDeleteTextures (1, &imageTexture); + + } + + ForgetTextures (); + + } + + + void MediaCodecVideoBackend::PrepareSeek () { + + // A stream waiting on a stalled network source would hold up the seek + // until the connection gives up, which takes minutes. Reads from a + // source that is still sending finish soon, and those that have not by + // then are given up. Their streams connect again once the seek is done. + if (!network) return; + + std::unique_lock lock (reads.mutex); + reads.seeking = true; + reads.condition.wait_for (lock, std::chrono::milliseconds (SEEK_WAIT_MS), [this] { return !audio.busy && !video.busy; }); + + if (audio.busy) audio.GiveUp (); + if (video.busy) video.GiveUp (); + + } + + + VideoDecodeResult MediaCodecVideoBackend::ReopenStream (MediaCodecStream* stream, size_t track) { + + // A seek gave up on a read that waited on a stalled network source, so + // the stream reads through a new connection, from where the seek went. + // The codec carries on, as the seek flushed it. Connecting cannot be + // cancelled, so decoding waits for it a little at a time, and seeking + // and closing do not wait for it. + #ifndef LIME_VIDEO_STANDALONE + // A URL is read and closed through Java, so this thread joins the Java VM + SDL_AndroidGetJNIEnv (); + #endif + + if (!stream->connection) { + + if (stream->extractor) { + + ndk.AMediaExtractor_delete (stream->extractor); + stream->extractor = NULL; + + } + + stream->SetDataSource (NULL); + stream->connection = Connect (path); + + } + + std::shared_ptr connection = stream->connection; + AMediaDataSource* dataSource = NULL; + + { + std::unique_lock lock (connection->mutex); + if (!connection->condition.wait_for (lock, std::chrono::milliseconds (CONNECT_WAIT_MS), [&connection] { return connection->done; })) return VIDEO_DECODE_AGAIN; + + dataSource = connection->dataSource; + connection->dataSource = NULL; + } + + stream->connection.reset (); + + // A server that cannot be reached is tried again at the next seek + if (!dataSource) return VIDEO_DECODE_ERROR; + + // Opening reads from the source, which a seek or closing can give up + stream->SetDataSource (dataSource); + if (!stream->BeginRead ()) return VIDEO_DECODE_AGAIN; + + AMediaExtractor* extractor = ndk.AMediaExtractor_new (); + bool opened = extractor && ndk.AMediaExtractor_setDataSourceCustom (extractor, dataSource) == AMEDIA_OK && ndk.AMediaExtractor_selectTrack (extractor, track) == AMEDIA_OK; + stream->EndRead (); + + if (!opened) { + + if (extractor) ndk.AMediaExtractor_delete (extractor); + return stream->cancelled ? VIDEO_DECODE_AGAIN : VIDEO_DECODE_ERROR; + + } + + // The extractor seeks to where the seek went before reading next + stream->extractor = extractor; + stream->reopen = false; + return VIDEO_DECODE_OK; + + } + + + bool MediaCodecVideoBackend::ReplaceVideoCodec (bool hardware, bool surface, int64_t position) { + + video.Release (); + + if (video.codec) { + + ndk.AMediaCodec_stop (video.codec); + ndk.AMediaCodec_delete (video.codec); + + } + + video.codec = CreateVideoDecoder (hardware, surface); + video.ended = false; + video.inputEnded = false; + videoSurface = surface && video.codec; + + if (!video.codec) return false; + + video.seekPosition = position; + + return video.Prime () && ReadVideoFormat (); + + } + + + bool MediaCodecVideoBackend::RestartAudio () { + + // Closing interrupts decoding, which is not a failure + if (audio.interrupted || !audio.extractor || audioRestarts >= MAX_CODEC_RESTARTS) return false; + + audioRestarts++; + audio.Release (); + + if (audio.codec) { + + ndk.AMediaCodec_stop (audio.codec); + ndk.AMediaCodec_delete (audio.codec); + audio.codec = NULL; + + } + + AMediaFormat* format = ndk.AMediaExtractor_getTrackFormat (audio.extractor, audioTrack); + if (!format) return false; + + if (ndk.AMediaFormat_setInt32) ndk.AMediaFormat_setInt32 (format, "pcm-encoding", PCM_ENCODING_16BIT); + audio.codec = CreateDecoder (format, false, NULL); + ndk.AMediaFormat_delete (format); + + if (!audio.codec) return false; + + audio.ended = false; + audio.inputEnded = false; + audio.inputError = false; + + // Resume from the packet before the last one returned, and skip to it + audio.seekPosition = audioTime != INT64_MIN ? audioTime : audioPosition; + audioSkipTime = audioTime; + return true; + + } + + + bool MediaCodecVideoBackend::RestartVideo () { + + // Closing interrupts decoding, which is not a failure + if (video.interrupted || videoRestarts >= MAX_CODEC_RESTARTS) return false; + + videoRestarts++; + + // Resume from the keyframe before the last frame returned, and skip to it + int64_t position = std::max (videoPosition, videoTime); + videoSkipTime = videoTime; + + // A hardware codec that the system reclaimed for another app is replaced + // by another the first time, and one that fails again by software + if (hardwareActive && videoRestarts == 1 && ReplaceVideoCodec (true, videoSurface, position)) return true; + + hardwareActive = false; + return ReplaceVideoCodec (false, videoSurface, position); + + } + + + bool MediaCodecVideoBackend::Seek (double time) { + + reads.seeking = false; + + int64_t position = (int64_t)(time * 1000000.0); + bool success = true; + + if (video.codec) { + + videoPosition = position; + videoSkipTime = INT64_MIN; + videoTime = INT64_MIN; + + // Texture output stopped, so decode into memory from here on, which + // a stream that connects again does once it has + if (videoSurface && !textureOutput && !video.reopen) { + + success = ReplaceVideoCodec (hardwareActive, false, position) && success; + + } else { + + success = video.Seek (position, network) && success; + + } + + } + + if (audio.codec) { + + audioConverter.Reset (); + audioPosition = position; + audioSkipTime = INT64_MIN; + audioTime = INT64_MIN; + success = audio.Seek (position, network) && success; + + } + + return success; + + } + + + void MediaCodecVideoBackend::SetTextureOutput (bool enabled) { + + // This can be called while the video thread decodes, so the codec only + // changes output at the next Open, Seek or DecodeVideo + textureOutput = enabled; + + } + + + bool MediaCodecVideoBackend::SupportsTextures () { + + // HDR frames that VideoDecoder tone maps are decoded into memory + return videoSurface && textureOutput && !texturesFailed && colorTransfer == VIDEO_COLOR_TRANSFER_SDR; + + } + + + VideoBackend* VideoBackend::Create () { + + return LoadMediaNDK () ? new MediaCodecVideoBackend () : NULL; + + } + + + bool VideoBackend::IsSupported () { + + return LoadMediaNDK (); + + } + + +} + + +#endif diff --git a/project/src/media/video/VideoDecoder.cpp b/project/src/media/video/VideoDecoder.cpp new file mode 100644 index 0000000000..19efcf0777 --- /dev/null +++ b/project/src/media/video/VideoDecoder.cpp @@ -0,0 +1,1536 @@ +#include +#include +#include +#include +#include +#include + +#if defined (LIME_OPENGL) && !defined (LIME_VIDEO_STANDALONE) +#define LIME_VIDEO_TEXTURES +#include "../../graphics/opengl/OpenGL.h" +#include "../../graphics/opengl/OpenGLBindings.h" +#ifdef NEED_EXTENSIONS +#define DECLARE_EXTENSION +#include "../../graphics/opengl/OpenGLExtensions.h" +#undef DECLARE_EXTENSION +#endif +#ifdef LIME_SDL +#include +#endif +#endif + +#ifndef GL_PIXEL_UNPACK_BUFFER +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#endif +#ifndef GL_PIXEL_UNPACK_BUFFER_BINDING +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#endif +#ifndef GL_UNPACK_ROW_LENGTH +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#endif + + +namespace lime { + + + // Textures of decoders closed away from the GL thread, deleted the next + // time any decoder reads a texture frame where their context is current + struct OrphanTexture { + + unsigned int name; + void* context; + + }; + + // Never destroyed, as exiting on Android and Linux runs static destructors + // while threads can still be closing videos + static std::mutex* orphanMutex = new std::mutex (); + static std::vector* orphanTextures = new std::vector (); + + + static void ReleaseOrphans (VideoBackend* backend) { + + #ifdef LIME_VIDEO_TEXTURES + std::vector textures; + + { + std::lock_guard lock (*orphanMutex); + textures.swap (*orphanTextures); + } + + void* context = VideoBackend::GetCurrentGLContext (); + + for (size_t i = 0; i < textures.size (); i++) { + + // A texture made in a context that has since been replaced went with it + if (textures[i].context == context) glDeleteTextures (1, &textures[i].name); + + } + #endif + + backend->ReleaseOrphans (); + + } + + + // Decoding stops once this many frames or seconds of audio are queued + static const size_t MAX_QUEUED_FRAMES = 4; + static const double MAX_QUEUED_AUDIO = 0.75; + + // HDR is shown as ITU-R BT.2408 suggests, with its reference white, 203 + // nits, as standard range white, and highlights up to 1000 nits rolled off + // into it by the curve of BT.2390. HLG is taken as shown on a 1000 nit + // display. The tone map table holds every luma for chroma in steps of 4. + static const double TONE_MAP_PEAK = 1000.0; + static const double TONE_MAP_WHITE = 203.0; + static const int TONE_MAP_CHROMA_STEP = 4; + static const int TONE_MAP_CHROMA_NODES = 256 / TONE_MAP_CHROMA_STEP + 1; + + + static inline unsigned char ClampByte (int value) { + + return value < 0 ? 0 : (value > 255 ? 255 : (unsigned char)value); + + } + + + void VideoBackend::ConvertAudio (const short* in, int frames, int channels, int rate, int outChannels, int outRate, VideoAudioConverter* converter, std::vector* pcm) { + + if (frames <= 0 || channels <= 0 || rate <= 0 || outChannels < 1 || outChannels > 2 || outRate <= 0) return; + + // Mix the channels first, folding surround into stereo + std::vector stereo; + std::vector mixed (frames * outChannels); + + if (channels > 2) { + + DownmixToStereo (in, frames, channels, &stereo); + in = (const short*)stereo.data (); + channels = 2; + + } + + for (int i = 0; i < frames; i++) { + + if (channels == outChannels) { + + for (int c = 0; c < outChannels; c++) mixed[i * outChannels + c] = in[i * channels + c]; + + } else if (channels == 1) { + + mixed[i * 2] = mixed[i * 2 + 1] = in[i]; + + } else { + + mixed[i] = (short)(((int)in[i * 2] + in[i * 2 + 1]) / 2); + + } + + } + + if (converter->previousChannels != outChannels) { + + // Nothing comes before the first packet, so it starts on its first frame + converter->position = 0; + converter->previousChannels = outChannels; + for (int c = 0; c < outChannels; c++) converter->previous[c] = mixed[c]; + + } + + if (rate == outRate) { + + converter->position = 0; + for (int c = 0; c < outChannels; c++) converter->previous[c] = mixed[(frames - 1) * outChannels + c]; + pcm->insert (pcm->end (), (const unsigned char*)mixed.data (), (const unsigned char*)(mixed.data () + mixed.size ())); + return; + + } + + // Linear interpolation, where a position below 0 lies between the last + // frame of the previous packet and the first of this one + double step = (double)rate / outRate; + double position = converter->position; + size_t offset = pcm->size (); + size_t count = position < frames - 1 ? (size_t)((frames - 1 - position) / step) + 1 : 0; + pcm->resize (offset + count * outChannels * 2); + short* out = (short*)(pcm->data () + offset); + size_t written = 0; + + while (position < frames - 1 && written < count) { + + int index = (int)floor (position); + double t = position - index; + + for (int c = 0; c < outChannels; c++) { + + double a = index < 0 ? converter->previous[c] : mixed[index * outChannels + c]; + double b = mixed[(index + 1) * outChannels + c]; + out[written * outChannels + c] = (short)(a + (b - a) * t); + + } + + written++; + position += step; + + } + + pcm->resize (offset + written * outChannels * 2); + converter->position = position - frames; + for (int c = 0; c < outChannels; c++) converter->previous[c] = mixed[(frames - 1) * outChannels + c]; + + } + + + // Folds the WAVE channel orders (3: FL FR FC, 4: FL FR BL BR, 5: FL FR FC BL + // BR, 6: FL FR FC LFE BL BR, 7.1 adds SL SR) into stereo, keeping the level + void VideoBackend::DownmixToStereo (const short* in, int frames, int channels, std::vector* pcm) { + + int center = (channels == 3 || channels == 5 || channels >= 6) ? 2 : -1; + int backLeft = channels == 4 ? 2 : (channels == 5 ? 3 : (channels >= 6 ? 4 : -1)); + int sideLeft = channels >= 8 ? 6 : -1; + float mix = 0.7071f; + float scale = 1.0f / (1.0f + (center >= 0 ? mix : 0) + (backLeft >= 0 ? mix : 0) + (sideLeft >= 0 ? mix : 0)); + + size_t offset = pcm->size (); + pcm->resize (offset + frames * 4); + short* out = (short*)(pcm->data () + offset); + + for (int i = 0; i < frames; i++) { + + const short* frame = in + i * channels; + float left = frame[0]; + float right = frame[1]; + + if (center >= 0) { left += frame[center] * mix; right += frame[center] * mix; } + if (backLeft >= 0) { left += frame[backLeft] * mix; right += frame[backLeft + 1] * mix; } + if (sideLeft >= 0) { left += frame[sideLeft] * mix; right += frame[sideLeft + 1] * mix; } + + out[i * 2] = (short)(left * scale); + out[i * 2 + 1] = (short)(right * scale); + + } + + } + + + void* VideoBackend::GetCurrentGLContext () { + + // Lime makes its contexts current through SDL, which keeps track of them + #if defined (LIME_VIDEO_TEXTURES) && defined (LIME_SDL) + return SDL_GL_GetCurrentContext (); + #else + return 0; + #endif + + } + + + VideoColorMatrix VideoBackend::GetDefaultColorMatrix (int width, int height) { + + // Streams without color information use BT.601 for SD and BT.709 for HD + return (width > 1024 || height >= 600) ? VIDEO_COLOR_MATRIX_BT709 : VIDEO_COLOR_MATRIX_BT601; + + } + + + int VideoBackend::NormalizeRotation (int degrees) { + + // Rounded to the nearest quarter turn + degrees = ((degrees % 360) + 360) % 360; + return ((degrees + 45) / 90) % 4 * 90; + + } + + + #if !defined (LIME_VIDEO_MEDIA_FOUNDATION) && !defined (LIME_VIDEO_AVFOUNDATION) && !defined (LIME_VIDEO_MEDIACODEC) && !defined (LIME_VIDEO_GSTREAMER) + + VideoBackend* VideoBackend::Create () { + + return 0; + + } + + + bool VideoBackend::IsSupported () { + + return false; + + } + + #endif + + + VideoDecoder::VideoDecoder (VideoBackend* backend) : backend (backend) { + + runningThreads = 0; + + audioEnded = true; + audioError = false; + audioQueued = 0; + audioQueueEnd = 0; + audioSeekTime = 0; + audioTime = 0; + currentFrame = 0; + dropLateFrames = true; + droppedFrames = 0; + format = VIDEO_FRAME_FORMAT_NV12; + frameColorMatrix = VIDEO_COLOR_MATRIX_BT709; + frameDuration = 0; + frameFullRange = false; + frameHeight = 0; + frameLength = 0; + frameTexture = 0; + frameTime = 0; + frameWidth = 0; + memset (&info, 0, sizeof (info)); + open = false; + presentationTime = -1; + quit = false; + seekAccurate = false; + seekPending = false; + texture = 0; + textureContext = 0; + textureHeight = 0; + texturesFailed = false; + textureThreadKnown = false; + textureWidth = 0; + videoEnded = true; + videoError = false; + videoSeekTime = 0; + + } + + + VideoDecoder::~VideoDecoder () { + + Close (); + delete backend; + + } + + + void VideoDecoder::AudioThread () { + + std::vector pcm; + int blockAlign = info.audioChannels * 2; + double bytesPerSecond = (double)info.audioSampleRate * blockAlign; + size_t maxQueued = (size_t)(bytesPerSecond * MAX_QUEUED_AUDIO); + + while (true) { + + { + std::unique_lock lock (mutex); + audioCondition.wait (lock, [this, maxQueued] { return quit || (!seekPending && !audioEnded && audioQueued < maxQueued); }); + if (quit) break; + } + + std::lock_guard decodeLock (audioDecodeMutex); + + pcm.clear (); + double time = 0; + VideoDecodeResult result = backend->DecodeAudio (&pcm, &time); + + if (result == VIDEO_DECODE_AGAIN) continue; + + if (result != VIDEO_DECODE_OK) { + + std::lock_guard lock (mutex); + audioEnded = true; + audioError = (result == VIDEO_DECODE_ERROR); + continue; + + } + + size_t offset = 0; + size_t length = pcm.size () - (pcm.size () % blockAlign); + + if (seekAccurate && time < audioSeekTime) { + + // Drop the audio before an accurate seek target + size_t skip = (size_t)((audioSeekTime - time) * info.audioSampleRate + 0.5) * blockAlign; + if (skip >= length) continue; + offset = skip; + time = audioSeekTime; + + } + + if (offset >= length) continue; + + std::lock_guard lock (mutex); + + AudioChunk* chunk; + + if (freeChunks.empty ()) { + + chunk = new AudioChunk (); + + } else { + + chunk = freeChunks.back (); + freeChunks.pop_back (); + + } + + chunk->data.assign (pcm.begin () + offset, pcm.begin () + length); + chunk->offset = 0; + chunk->time = time; + + audioChunks.push_back (chunk); + audioQueued += chunk->data.size (); + audioQueueEnd = time + chunk->data.size () / bytesPerSecond; + + } + + } + + + void VideoDecoder::Close () { + + if (!open) return; + + StopThreads (); + + if (currentFrame) { + + if (currentFrame->texture && IsTextureThread ()) backend->UnlockTexture (currentFrame->texture); + + std::lock_guard lock (mutex); + ReleaseFrame (currentFrame); + currentFrame = 0; + + } + + { + std::lock_guard lock (mutex); + Flush (); + } + + // GL objects can only be deleted where their context is current, so + // closing on another thread queues them for the next texture read there + if (IsTextureThread ()) { + + ReleaseTextures (); + + } else { + + #ifdef LIME_VIDEO_TEXTURES + if (texture) { + + std::lock_guard lock (*orphanMutex); + OrphanTexture orphan = { texture, textureContext }; + orphanTextures->push_back (orphan); + + } + #endif + + texture = 0; + textureContext = 0; + frameTexture = 0; + textureHeight = 0; + textureThreadKnown = false; + textureWidth = 0; + + } + + backend->Close (); + + std::lock_guard lock (mutex); + + for (size_t i = 0; i < freeChunks.size (); i++) delete freeChunks[i]; + for (size_t i = 0; i < freeFrames.size (); i++) delete freeFrames[i]; + freeChunks.clear (); + freeFrames.clear (); + + open = false; + audioEnded = true; + videoEnded = true; + memset (&info, 0, sizeof (info)); + + } + + + void VideoDecoder::Flush () { + + for (size_t i = 0; i < audioChunks.size (); i++) freeChunks.push_back (audioChunks[i]); + for (size_t i = 0; i < frames.size (); i++) ReleaseFrame (frames[i]); + + audioChunks.clear (); + audioQueued = 0; + frames.clear (); + + } + + + int VideoDecoder::GetFrameLength (VideoFrameFormat format, int width, int height) { + + if (width <= 0 || height <= 0) return 0; + + switch (format) { + + case VIDEO_FRAME_FORMAT_NV12: return width * height + ((width + 1) / 2) * 2 * ((height + 1) / 2); + case VIDEO_FRAME_FORMAT_RGBA: + case VIDEO_FRAME_FORMAT_BGRA: + case VIDEO_FRAME_FORMAT_ARGB: + case VIDEO_FRAME_FORMAT_TEXTURE: return width * height * 4; + default: return 0; + + } + + } + + + bool VideoDecoder::Open (const char* path, bool hardwareDecoding, VideoFrameFormat format) { + + Close (); + + if (!backend || !path) return false; + + VideoStreamInfo streamInfo; + memset (&streamInfo, 0, sizeof (streamInfo)); + + texturesFailed = false; + backend->SetTextureOutput (format == VIDEO_FRAME_FORMAT_TEXTURE); + + if (!backend->Open (path, hardwareDecoding, &streamInfo)) { + + backend->Close (); + return false; + + } + + if (streamInfo.audioChannels < 1 || streamInfo.audioChannels > 2 || streamInfo.audioSampleRate <= 0) { + + streamInfo.hasAudio = false; + + } + + if (!streamInfo.hasAudio) { + + streamInfo.audioChannels = 0; + streamInfo.audioSampleRate = 0; + + } + + if (!streamInfo.hasAudio && !streamInfo.hasVideo) { + + backend->Close (); + return false; + + } + + if (!streamInfo.hasVideo || !(streamInfo.pixelAspectRatio > 0.0 && streamInfo.pixelAspectRatio < 100.0)) streamInfo.pixelAspectRatio = 1.0; + streamInfo.rotation = streamInfo.hasVideo ? VideoBackend::NormalizeRotation (streamInfo.rotation) : 0; + + info = streamInfo; + this->format = format; + + audioEnded = !info.hasAudio; + audioError = false; + audioQueueEnd = 0; + audioTime = 0; + frameColorMatrix = VideoBackend::GetDefaultColorMatrix (info.width, info.height); + frameDuration = info.frameRate > 0 ? 1.0 / info.frameRate : 0; + frameFullRange = false; + frameHeight = info.height; + frameLength = format == VIDEO_FRAME_FORMAT_TEXTURE ? 0 : GetFrameLength (format, info.width, info.height); + frameTexture = 0; + frameTime = 0; + frameWidth = info.width; + droppedFrames = 0; + lastQueued = std::chrono::steady_clock::now (); + presentationTime = -1; + audioSeekTime = 0; + seekAccurate = false; + seekPending = false; + videoEnded = !info.hasVideo; + videoError = false; + videoSeekTime = 0; + + open = true; + StartThreads (); + return true; + + } + + + int VideoDecoder::ReadAudio (unsigned char* data, int length) { + + std::lock_guard lock (mutex); + + if (!open || !info.hasAudio) return VIDEO_READ_END; + + int blockAlign = info.audioChannels * 2; + double bytesPerSecond = (double)info.audioSampleRate * blockAlign; + length -= length % blockAlign; + + int written = 0; + + while (written < length && !audioChunks.empty ()) { + + AudioChunk* chunk = audioChunks.front (); + size_t available = chunk->data.size () - chunk->offset; + size_t count = (size_t)(length - written) < available ? (size_t)(length - written) : available; + + memcpy (data + written, chunk->data.data () + chunk->offset, count); + chunk->offset += count; + written += (int)count; + audioQueued -= count; + + if (chunk->offset >= chunk->data.size ()) { + + audioTime = chunk->time + chunk->data.size () / bytesPerSecond; + audioChunks.pop_front (); + freeChunks.push_back (chunk); + + } else { + + audioTime = chunk->time + chunk->offset / bytesPerSecond; + + } + + } + + if (written > 0) { + + audioCondition.notify_one (); + return written; + + } + + if (audioEnded) return audioError ? VIDEO_READ_ERROR : VIDEO_READ_END; + return VIDEO_READ_NOT_READY; + + } + + + int VideoDecoder::ReadFrame (unsigned char* data, int length, double time) { + + if (format == VIDEO_FRAME_FORMAT_TEXTURE) return ReadTexture (time); + + std::lock_guard lock (mutex); + + if (!open || !info.hasVideo) return VIDEO_READ_END; + + bool dropped = false; + + if (time >= 0) { + + presentationTime = time; + + // Drop frames that a later queued frame replaces by time + while (dropLateFrames && frames.size () > 1 && frames[1]->time <= time) { + + ReleaseFrame (frames.front ()); + frames.pop_front (); + droppedFrames++; + dropped = true; + + } + + } + + if (dropped) videoCondition.notify_one (); + + if (frames.empty ()) { + + if (videoEnded) return videoError ? VIDEO_READ_ERROR : VIDEO_READ_END; + return VIDEO_READ_NOT_READY; + + } + + Frame* frame = frames.front (); + + if (time >= 0 && frame->time > time) return VIDEO_READ_NOT_READY; + + frameColorMatrix = frame->colorMatrix; + frameDuration = frame->duration; + frameFullRange = frame->fullRange; + frameHeight = frame->height; + frameLength = frame->length; + frameTime = frame->time; + frameWidth = frame->width; + + if (!data || length < frame->length) return VIDEO_READ_BUFFER_TOO_SMALL; + + memcpy (data, frame->data.data (), frame->length); + + frames.pop_front (); + ReleaseFrame (frame); + videoCondition.notify_one (); + + return frameLength; + + } + + + bool VideoDecoder::IsTextureThread () { + + return textureThreadKnown && textureThread == std::this_thread::get_id (); + + } + + + int VideoDecoder::ReadTexture (double time) { + + Frame* frame = 0; + + { + std::lock_guard lock (mutex); + + if (!open || !info.hasVideo) return VIDEO_READ_END; + + bool dropped = false; + + if (time >= 0) { + + presentationTime = time; + + while (dropLateFrames && frames.size () > 1 && frames[1]->time <= time) { + + ReleaseFrame (frames.front ()); + frames.pop_front (); + droppedFrames++; + dropped = true; + + } + + } + + if (dropped) videoCondition.notify_one (); + + if (frames.empty ()) { + + if (videoEnded) return videoError ? VIDEO_READ_ERROR : VIDEO_READ_END; + return VIDEO_READ_NOT_READY; + + } + + if (time >= 0 && frames.front ()->time > time) return VIDEO_READ_NOT_READY; + + frame = frames.front (); + frames.pop_front (); + videoCondition.notify_one (); + } + + textureThread = std::this_thread::get_id (); + textureThreadKnown = true; + ReleaseOrphans (backend); + + unsigned int name = frame->texture ? backend->LockTexture (frame->texture) : UploadTexture (frame); + + if (!name) { + + bool fallBack = (frame->texture != 0 && !texturesFailed); + double resume = frame->time; + + { + std::lock_guard lock (mutex); + ReleaseFrame (frame); + } + + if (!fallBack) return VIDEO_READ_ERROR; + + // This context cannot use the backend's textures, so decode into + // memory and upload from here on. The audio carries on from where it + // was read to, as it has already been played. + texturesFailed = true; + backend->SetTextureOutput (false); + SeekStreams (resume, true, true); + return VIDEO_READ_NOT_READY; + + } + + if (currentFrame) { + + if (currentFrame->texture) backend->UnlockTexture (currentFrame->texture); + + std::lock_guard lock (mutex); + ReleaseFrame (currentFrame); + + } + + currentFrame = frame; + + frameColorMatrix = frame->colorMatrix; + frameDuration = frame->duration; + frameFullRange = frame->fullRange; + frameHeight = frame->height; + frameLength = 0; + frameTexture = name; + frameTime = frame->time; + frameWidth = frame->width; + + return 1; + + } + + + void VideoDecoder::ReleaseFrame (Frame* frame) { + + if (frame->texture) { + + backend->ReleaseTextureFrame (frame->texture); + frame->texture = 0; + + } + + freeFrames.push_back (frame); + + } + + + void VideoDecoder::ReleaseTextures () { + + backend->ReleaseTextures (); + + #ifdef LIME_VIDEO_TEXTURES + // A texture made in a context that has since been replaced went with it + if (texture && textureContext == VideoBackend::GetCurrentGLContext ()) { + + glDeleteTextures (1, &texture); + + } + #endif + + texture = 0; + textureContext = 0; + frameTexture = 0; + textureHeight = 0; + textureThreadKnown = false; + textureWidth = 0; + + } + + + bool VideoDecoder::Seek (double time, bool accurate) { + + return SeekStreams (time, accurate, false); + + } + + + bool VideoDecoder::SeekStreams (double time, bool accurate, bool keepAudio) { + + if (!open) return false; + if (time < 0) time = 0; + + { + std::lock_guard lock (mutex); + seekPending = true; + } + + audioCondition.notify_all (); + videoCondition.notify_all (); + + // A stream waiting on a stalled source gives up waiting, so the seek does + // not wait for it + backend->PrepareSeek (); + + // Wait for the frame or packet in progress, then seek with both streams idle + std::lock_guard videoLock (videoDecodeMutex); + std::lock_guard audioLock (audioDecodeMutex); + + // Audio that is kept carries on from what has been read, and the backend + // seeks to whichever stream starts first + double audioTarget = time; + + if (keepAudio) { + + std::lock_guard lock (mutex); + audioTarget = audioTime; + + } + + // There is nothing to decode from the end on, and some backends cannot + // seek there + bool ended = !keepAudio && info.duration > 0 && time >= info.duration; + + if (!ended && !backend->Seek (audioTarget < time ? audioTarget : time)) { + + // Nothing has moved, so what is queued still follows on + std::lock_guard lock (mutex); + seekPending = false; + audioCondition.notify_all (); + videoCondition.notify_all (); + return false; + + } + + if (ended) time = audioTarget = info.duration; + + std::lock_guard lock (mutex); + Flush (); + + audioEnded = ended || !info.hasAudio; + audioError = false; + audioQueueEnd = audioTarget; + audioSeekTime = audioTarget; + audioTime = audioTarget; + frameTime = time; + lastQueued = std::chrono::steady_clock::now (); + presentationTime = -1; + seekAccurate = accurate || keepAudio; + seekPending = false; + videoEnded = ended || !info.hasVideo; + videoError = false; + videoSeekTime = time; + + audioCondition.notify_all (); + videoCondition.notify_all (); + + return true; + + } + + + void VideoDecoder::StartThreads () { + + quit = false; + runningThreads = (info.hasAudio ? 1 : 0) + (info.hasVideo ? 1 : 0); + + if (info.hasAudio) audioThread = std::thread ([this] { AudioThread (); ThreadStopped (); }); + if (info.hasVideo) videoThread = std::thread ([this] { VideoThread (); ThreadStopped (); }); + + } + + + void VideoDecoder::StopThreads () { + + { + std::lock_guard lock (mutex); + quit = true; + Flush (); + } + + audioCondition.notify_all (); + videoCondition.notify_all (); + + backend->Interrupt (); + + { + // Media Foundation now and then leaves a read waiting on a stalled + // network source after its byte stream is closed, so the streams are + // interrupted again until they finish + std::unique_lock lock (mutex); + + while (!stoppedCondition.wait_for (lock, std::chrono::milliseconds (200), [this] { return runningThreads == 0; })) { + + lock.unlock (); + backend->Interrupt (); + lock.lock (); + + } + } + + if (audioThread.joinable ()) audioThread.join (); + if (videoThread.joinable ()) videoThread.join (); + + } + + + void VideoDecoder::ThreadStopped () { + + { + std::lock_guard lock (mutex); + runningThreads--; + } + + stoppedCondition.notify_all (); + + } + + + void VideoDecoder::VideoThread () { + + while (true) { + + { + std::unique_lock lock (mutex); + videoCondition.wait (lock, [this] { return quit || (!seekPending && !videoEnded && frames.size () < MAX_QUEUED_FRAMES); }); + if (quit) break; + } + + std::lock_guard decodeLock (videoDecodeMutex); + + VideoPlanes planes; + planes.transfer = VIDEO_COLOR_TRANSFER_SDR; + VideoTextureFrame* textureFrame = 0; + double time = 0; + double duration = 0; + VideoDecodeResult result; + + if (format == VIDEO_FRAME_FORMAT_TEXTURE && !texturesFailed && backend->SupportsTextures ()) { + + result = backend->DecodeVideoTexture (&textureFrame, &time, &duration); + + // A backend that stops supporting textures returns no frame, and the + // next one is decoded into memory + if (result == VIDEO_DECODE_OK && !textureFrame) continue; + + } else { + + result = backend->DecodeVideo (&planes, &time, &duration); + + } + + if (result == VIDEO_DECODE_AGAIN) continue; + + if (result != VIDEO_DECODE_OK) { + + std::lock_guard lock (mutex); + videoEnded = true; + videoError = (result == VIDEO_DECODE_ERROR); + continue; + + } + + if (duration <= 0) duration = info.frameRate > 0 ? 1.0 / info.frameRate : 0; + + // Drop the frames that end before an accurate seek target + if (seekAccurate && time + duration <= videoSeekTime + 0.0001) { + + if (textureFrame) backend->ReleaseTextureFrame (textureFrame); + continue; + + } + + // A frame that ends before the time the application last asked for is + // late, and is dropped before it is converted or copied, most of the + // work for each frame, so a decoder that falls behind catches up. One + // is still kept every quarter of a second, so a decoder that cannot keep + // up at all still shows something. + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now (); + Frame* frame = 0; + + { + std::lock_guard lock (mutex); + + if (dropLateFrames && presentationTime >= 0 && time + duration <= presentationTime && now - lastQueued < std::chrono::milliseconds (250)) { + + if (textureFrame) backend->ReleaseTextureFrame (textureFrame); + droppedFrames++; + continue; + + } + + if (!freeFrames.empty ()) { + + frame = freeFrames.back (); + freeFrames.pop_back (); + + } + } + + if (!frame) frame = new Frame (); + + frame->duration = duration; + frame->texture = textureFrame; + frame->time = time; + + if (textureFrame) { + + // The backend converts to RGB on the GPU + frame->colorMatrix = VIDEO_COLOR_MATRIX_BT709; + frame->fullRange = true; + frame->height = textureFrame->height; + frame->length = 1; + frame->width = textureFrame->width; + + } else { + + if (planes.transfer != VIDEO_COLOR_TRANSFER_SDR) toneMapper.Map (&planes); + + // Texture output without GPU support converts to RGBA here and uploads + VideoFrameFormat outputFormat = format == VIDEO_FRAME_FORMAT_TEXTURE ? VIDEO_FRAME_FORMAT_RGBA : format; + int length = GetFrameLength (outputFormat, planes.width, planes.height); + if ((int)frame->data.size () < length) frame->data.resize (length); + + frame->colorMatrix = planes.colorMatrix; + frame->fullRange = planes.fullRange; + frame->height = planes.height; + frame->length = length > 0 ? WriteFrame (&planes, outputFormat, frame->data.data (), (int)frame->data.size ()) : 0; + frame->width = planes.width; + + } + + std::lock_guard lock (mutex); + + if (frame->length > 0) { + + frames.push_back (frame); + lastQueued = now; + + } else { + + ReleaseFrame (frame); + + } + + } + + } + + + unsigned int VideoDecoder::UploadTexture (Frame* frame) { + + #ifdef LIME_VIDEO_TEXTURES + if (!OpenGLBindings::Init ()) return 0; + + // Unpack buffers and row lengths exist from OpenGL 3 and OpenGL ES 3. The + // version is only known once a context is current. + static int glVersion = -1; + + if (glVersion < 0) { + + const char* version = (const char*)glGetString (GL_VERSION); + if (!version) return 0; + if (strncmp (version, "OpenGL ES ", 10) == 0) version += 10; + glVersion = atoi (version); + + } + + // A texture from a context that has since been replaced, as Android does + // after losing it, went with that context, and its name may now belong to + // one of the application's textures + void* context = VideoBackend::GetCurrentGLContext (); + + if (texture && context != textureContext) { + + texture = 0; + + } + + // Leave the application's GL state as it was + GLint previousTexture = 0; + GLint previousUnpackBuffer = 0; + GLint previousRowLength = 0; + glGetIntegerv (GL_TEXTURE_BINDING_2D, &previousTexture); + + if (glVersion >= 3) { + + glGetIntegerv (GL_PIXEL_UNPACK_BUFFER_BINDING, &previousUnpackBuffer); + glGetIntegerv (GL_UNPACK_ROW_LENGTH, &previousRowLength); + if (previousUnpackBuffer) glBindBuffer (GL_PIXEL_UNPACK_BUFFER, 0); + if (previousRowLength) glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); + + } + + if (!texture) { + + glGenTextures (1, &texture); + textureContext = context; + glBindTexture (GL_TEXTURE_2D, texture); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + textureWidth = 0; + textureHeight = 0; + + } else { + + glBindTexture (GL_TEXTURE_2D, texture); + + } + + // RGBA rows are always four-byte aligned, so the unpack alignment does not matter + if (frame->width != textureWidth || frame->height != textureHeight) { + + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, frame->width, frame->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame->data.data ()); + textureWidth = frame->width; + textureHeight = frame->height; + + } else { + + glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, frame->width, frame->height, GL_RGBA, GL_UNSIGNED_BYTE, frame->data.data ()); + + } + + glBindTexture (GL_TEXTURE_2D, previousTexture); + + if (glVersion >= 3) { + + if (previousUnpackBuffer) glBindBuffer (GL_PIXEL_UNPACK_BUFFER, previousUnpackBuffer); + if (previousRowLength) glPixelStorei (GL_UNPACK_ROW_LENGTH, previousRowLength); + + } + + return texture; + #else + return 0; + #endif + + } + + + static double DecodeHLG (double value) { + + // Scene light from 0 to 1 + value = (std::max) (value, 0.0); + return value <= 0.5 ? value * value / 3.0 : (exp ((value - 0.55991073) / 0.17883277) + 0.28466892) / 12.0; + + } + + + static double DecodePQ (double value) { + + // Nits + double p = pow ((std::max) (value, 0.0), 1.0 / 78.84375); + return 10000.0 * pow ((std::max) (p - 0.8359375, 0.0) / (18.8515625 - 18.6875 * p), 1.0 / 0.1593017578125); + + } + + + static double EncodePQ (double nits) { + + double y = pow ((std::max) (nits, 0.0) / 10000.0, 0.1593017578125); + return pow ((0.8359375 + 18.8515625 * y) / (1.0 + 18.6875 * y), 78.84375); + + } + + + // The transfer curves are sampled, and looked up between samples, as + // building a table calls them a million times + static const int TONE_MAP_CURVE_SAMPLES = 4096; + + struct ToneMapCurves { + + double gamma[TONE_MAP_CURVE_SAMPLES + 1]; + double hlg[TONE_MAP_CURVE_SAMPLES + 1]; + double peak; + double pq[TONE_MAP_CURVE_SAMPLES + 1]; + double white; + + }; + + + static double LookUpCurve (const double* curve, double value) { + + // Values from 0 to 1 + double position = (std::min) ((std::max) (value, 0.0), 1.0) * TONE_MAP_CURVE_SAMPLES; + int index = (int)position; + if (index >= TONE_MAP_CURVE_SAMPLES) return curve[TONE_MAP_CURVE_SAMPLES]; + return curve[index] + (curve[index + 1] - curve[index]) * (position - index); + + } + + + static double ToneMapLuminance (const ToneMapCurves& curves, double nits) { + + // BT.2390's curve, in PQ, from 0 to TONE_MAP_PEAK nits into 0 to + // TONE_MAP_WHITE, which passes darker light through as it is + double peak = curves.peak; + double value = (std::min) (EncodePQ (nits) / peak, 1.0); + double white = curves.white / peak; + double knee = 1.5 * white - 0.5; + + if (value > knee) { + + double t = (value - knee) / (1.0 - knee); + double t2 = t * t; + double t3 = t2 * t; + value = (2 * t3 - 3 * t2 + 1) * knee + (t3 - 2 * t2 + t) * (1 - knee) + (-2 * t3 + 3 * t2) * white; + + } + + return LookUpCurve (curves.pq, value * peak); + + } + + + static const unsigned char* GetToneMapTable (VideoColorTransfer transfer, VideoColorMatrix matrix, bool fullRange) { + + // Each kind of frame's table is built once, a few megabytes, and kept. + // They are never destroyed, as exiting on Android and Linux runs static + // destructors while decoding threads can still be using them. + static std::mutex* mutex = new std::mutex (); + static std::map >* tables = new std::map > (); + + std::lock_guard lock (*mutex); + std::vector& table = (*tables)[(int)transfer * 16 + (int)matrix * 2 + (fullRange ? 1 : 0)]; + if (!table.empty ()) return table.data (); + + double kr, kb; + + switch (matrix) { + + case VIDEO_COLOR_MATRIX_BT601: kr = 0.299; kb = 0.114; break; + case VIDEO_COLOR_MATRIX_BT709: kr = 0.2126; kb = 0.0722; break; + default: kr = 0.2627; kb = 0.0593; break; + + } + + static ToneMapCurves* curves = NULL; + + if (!curves) { + + curves = new ToneMapCurves (); + curves->peak = EncodePQ (TONE_MAP_PEAK); + curves->white = EncodePQ (TONE_MAP_WHITE); + + for (int i = 0; i <= TONE_MAP_CURVE_SAMPLES; i++) { + + double value = (double)i / TONE_MAP_CURVE_SAMPLES; + curves->gamma[i] = pow (value, 1.0 / 2.4); + curves->hlg[i] = DecodeHLG (value); + curves->pq[i] = DecodePQ (value); + + } + + } + + double kg = 1.0 - kr - kb; + table.resize ((size_t)TONE_MAP_CHROMA_NODES * TONE_MAP_CHROMA_NODES * 256 * 3); + unsigned char* entry = table.data (); + + for (int u = 0; u < TONE_MAP_CHROMA_NODES; u++) { + + for (int v = 0; v < TONE_MAP_CHROMA_NODES; v++) { + + double cb = ((std::min) (u * TONE_MAP_CHROMA_STEP, 255) - 128) / (fullRange ? 255.0 : 224.0); + double cr = ((std::min) (v * TONE_MAP_CHROMA_STEP, 255) - 128) / (fullRange ? 255.0 : 224.0); + + for (int y = 0; y < 256; y++) { + + double luma = fullRange ? y / 255.0 : (y - 16) / 219.0; + double rgb[3]; + rgb[0] = luma + 2.0 * (1.0 - kr) * cr; + rgb[2] = luma + 2.0 * (1.0 - kb) * cb; + rgb[1] = (luma - kr * rgb[0] - kb * rgb[2]) / kg; + + for (int i = 0; i < 3; i++) rgb[i] = (std::min) ((std::max) (rgb[i], 0.0), 1.0); + + // Light in nits, as shown on an HDR display + if (transfer == VIDEO_COLOR_TRANSFER_HLG) { + + for (int i = 0; i < 3; i++) rgb[i] = LookUpCurve (curves->hlg, rgb[i]); + double scene = 0.2627 * rgb[0] + 0.6780 * rgb[1] + 0.0593 * rgb[2]; + double gain = scene > 0 ? TONE_MAP_PEAK * pow (scene, 0.2) : 0; + for (int i = 0; i < 3; i++) rgb[i] *= gain; + + } else { + + for (int i = 0; i < 3; i++) rgb[i] = LookUpCurve (curves->pq, rgb[i]); + + } + + // The brightness is tone mapped, keeping the color's hue, and white + // becomes 1 + double luminance = 0.2627 * rgb[0] + 0.6780 * rgb[1] + 0.0593 * rgb[2]; + double scale = luminance > 0 ? ToneMapLuminance (*curves, luminance) / luminance / TONE_MAP_WHITE : 0; + + // BT.2020 primaries to BT.709, clipping colors outside it + double r = (1.6605 * rgb[0] - 0.5876 * rgb[1] - 0.0728 * rgb[2]) * scale; + double g = (-0.1246 * rgb[0] + 1.1329 * rgb[1] - 0.0083 * rgb[2]) * scale; + double b = (-0.0182 * rgb[0] - 0.1006 * rgb[1] + 1.1187 * rgb[2]) * scale; + + // Standard range video is made for BT.1886's display gamma + r = LookUpCurve (curves->gamma, r); + g = LookUpCurve (curves->gamma, g); + b = LookUpCurve (curves->gamma, b); + + double outLuma = 0.2126 * r + 0.7152 * g + 0.0722 * b; + entry[0] = ClampByte ((int)(16 + 219 * outLuma + 0.5)); + entry[1] = ClampByte ((int)(128 + 224 * (b - outLuma) / 1.8556 + 0.5)); + entry[2] = ClampByte ((int)(128 + 224 * (r - outLuma) / 1.5748 + 0.5)); + entry += 3; + + } + + } + + } + + return table.data (); + + } + + + void VideoToneMapper::Map (VideoPlanes* planes) { + + const unsigned char* table = GetToneMapTable (planes->transfer, planes->colorMatrix, planes->fullRange); + + int width = planes->width; + int height = planes->height; + int chromaWidth = (width + 1) / 2; + int chromaHeight = (height + 1) / 2; + int pixelStride = planes->uvPixelStride; + + buffer.resize ((size_t)width * height + (size_t)chromaWidth * chromaHeight * 2); + unsigned char* yOut = buffer.data (); + unsigned char* uvOut = yOut + (size_t)width * height; + + for (int row = 0; row < chromaHeight; row++) { + + const unsigned char* u = planes->u + row * planes->uvStride; + const unsigned char* v = planes->v + row * planes->uvStride; + const unsigned char* top = planes->y + (row * 2) * planes->yStride; + const unsigned char* bottom = row * 2 + 1 < height ? top + planes->yStride : top; + unsigned char* topOut = yOut + (size_t)(row * 2) * width; + unsigned char* bottomOut = row * 2 + 1 < height ? topOut + width : NULL; + unsigned char* uv = uvOut + (size_t)row * chromaWidth * 2; + + for (int x = 0; x < chromaWidth; x++) { + + // The pixels sharing this chroma, each mapped with its own luma + int node = ((u[x * pixelStride] + TONE_MAP_CHROMA_STEP / 2) / TONE_MAP_CHROMA_STEP) * TONE_MAP_CHROMA_NODES + (v[x * pixelStride] + TONE_MAP_CHROMA_STEP / 2) / TONE_MAP_CHROMA_STEP; + const unsigned char* entries = table + (size_t)node * 256 * 3; + int left = x * 2; + int right = left + 1 < width ? left + 1 : left; + + const unsigned char* a = entries + top[left] * 3; + const unsigned char* b = entries + top[right] * 3; + const unsigned char* c = entries + bottom[left] * 3; + const unsigned char* d = entries + bottom[right] * 3; + + topOut[left] = a[0]; + topOut[right] = b[0]; + + if (bottomOut) { + + bottomOut[left] = c[0]; + bottomOut[right] = d[0]; + + } + + uv[x * 2] = (unsigned char)((a[1] + b[1] + c[1] + d[1] + 2) >> 2); + uv[x * 2 + 1] = (unsigned char)((a[2] + b[2] + c[2] + d[2] + 2) >> 2); + + } + + } + + planes->colorMatrix = VIDEO_COLOR_MATRIX_BT709; + planes->fullRange = false; + planes->transfer = VIDEO_COLOR_TRANSFER_SDR; + planes->u = uvOut; + planes->uvPixelStride = 2; + planes->uvStride = chromaWidth * 2; + planes->v = uvOut + 1; + planes->y = yOut; + planes->yStride = width; + + } + + + int VideoDecoder::WriteFrame (const VideoPlanes* planes, VideoFrameFormat format, unsigned char* data, int length) { + + int frameLength = GetFrameLength (format, planes->width, planes->height); + + if (frameLength <= 0 || !data) return VIDEO_READ_ERROR; + if (length < frameLength) return VIDEO_READ_BUFFER_TOO_SMALL; + + int width = planes->width; + int height = planes->height; + int chromaWidth = (width + 1) / 2; + int chromaHeight = (height + 1) / 2; + int pixelStride = planes->uvPixelStride; + + if (format == VIDEO_FRAME_FORMAT_NV12) { + + for (int row = 0; row < height; row++) { + + memcpy (data + row * width, planes->y + row * planes->yStride, width); + + } + + unsigned char* uv = data + width * height; + bool interleaved = (pixelStride == 2 && planes->v == planes->u + 1); + + for (int row = 0; row < chromaHeight; row++) { + + unsigned char* out = uv + row * chromaWidth * 2; + const unsigned char* u = planes->u + row * planes->uvStride; + + if (interleaved) { + + memcpy (out, u, chromaWidth * 2); + + } else { + + const unsigned char* v = planes->v + row * planes->uvStride; + + for (int x = 0; x < chromaWidth; x++) { + + out[x * 2] = u[x * pixelStride]; + out[x * 2 + 1] = v[x * pixelStride]; + + } + + } + + } + + return frameLength; + + } + + // Kr and Kb from ITU-R BT.601, BT.709 and BT.2020 + + double kr, kb; + + switch (planes->colorMatrix) { + + case VIDEO_COLOR_MATRIX_BT601: kr = 0.299; kb = 0.114; break; + case VIDEO_COLOR_MATRIX_BT2020: kr = 0.2627; kb = 0.0593; break; + default: kr = 0.2126; kb = 0.0722; break; + + } + + double kg = 1.0 - kr - kb; + double yScale = planes->fullRange ? 1.0 : 255.0 / 219.0; + double cScale = planes->fullRange ? 1.0 : 255.0 / 224.0; + int yOffset = planes->fullRange ? 0 : 16; + + // Where each channel goes in the four bytes of a pixel + int red = 0, green = 1, blue = 2, alpha = 3; + + if (format == VIDEO_FRAME_FORMAT_BGRA) { + + red = 2; + blue = 0; + + } else if (format == VIDEO_FRAME_FORMAT_ARGB) { + + alpha = 0; + red = 1; + green = 2; + blue = 3; + + } + + int yFactor = (int)(yScale * 65536.0 + 0.5); + int rv = (int)(2.0 * (1.0 - kr) * cScale * 65536.0 + 0.5); + int gu = (int)(2.0 * kb * (1.0 - kb) / kg * cScale * 65536.0 + 0.5); + int gv = (int)(2.0 * kr * (1.0 - kr) / kg * cScale * 65536.0 + 0.5); + int bu = (int)(2.0 * (1.0 - kb) * cScale * 65536.0 + 0.5); + + for (int row = 0; row < height; row++) { + + const unsigned char* y = planes->y + row * planes->yStride; + const unsigned char* u = planes->u + (row / 2) * planes->uvStride; + const unsigned char* v = planes->v + (row / 2) * planes->uvStride; + unsigned char* out = data + row * width * 4; + + for (int x = 0; x < width; x++) { + + int cu = u[(x / 2) * pixelStride] - 128; + int cv = v[(x / 2) * pixelStride] - 128; + int luma = (y[x] - yOffset) * yFactor + 32768; + + out[red] = ClampByte ((luma + rv * cv) >> 16); + out[green] = ClampByte ((luma - gu * cu - gv * cv) >> 16); + out[blue] = ClampByte ((luma + bu * cu) >> 16); + out[alpha] = 255; + out += 4; + + } + + } + + return frameLength; + + } + + +} diff --git a/project/src/media/video/VideoDecoderBindings.cpp b/project/src/media/video/VideoDecoderBindings.cpp new file mode 100644 index 0000000000..d723c8e6f0 --- /dev/null +++ b/project/src/media/video/VideoDecoderBindings.cpp @@ -0,0 +1,434 @@ +#include +#include +#include +#include +#include +#include + + +namespace lime { + + + static int id_audioChannels; + static int id_audioSampleRate; + static int id_audioUnsupported; + static int id_colorMatrix; + static int id_droppedFrames; + static int id_duration; + static int id_frameRate; + static int id_fullRange; + static int id_hasAudio; + static int id_hasVideo; + static int id_height; + static int id_length; + static int id_pixelAspectRatio; + static int id_rotation; + static int id_texture; + static int id_time; + static int id_videoUnsupported; + static int id_width; + static bool init = false; + static bool hl_init = false; + + + inline void _initializeVideo () { + + if (!init) { + + id_audioChannels = val_id ("audioChannels"); + id_audioSampleRate = val_id ("audioSampleRate"); + id_audioUnsupported = val_id ("audioUnsupported"); + id_colorMatrix = val_id ("colorMatrix"); + id_droppedFrames = val_id ("droppedFrames"); + id_duration = val_id ("duration"); + id_frameRate = val_id ("frameRate"); + id_fullRange = val_id ("fullRange"); + id_hasAudio = val_id ("hasAudio"); + id_hasVideo = val_id ("hasVideo"); + id_height = val_id ("height"); + id_length = val_id ("length"); + id_pixelAspectRatio = val_id ("pixelAspectRatio"); + id_rotation = val_id ("rotation"); + id_texture = val_id ("texture"); + id_time = val_id ("time"); + id_videoUnsupported = val_id ("videoUnsupported"); + id_width = val_id ("width"); + init = true; + + } + + } + + + inline void _hl_initializeVideo () { + + if (!hl_init) { + + id_audioChannels = hl_hash_utf8 ("audioChannels"); + id_audioSampleRate = hl_hash_utf8 ("audioSampleRate"); + id_audioUnsupported = hl_hash_utf8 ("audioUnsupported"); + id_colorMatrix = hl_hash_utf8 ("colorMatrix"); + id_droppedFrames = hl_hash_utf8 ("droppedFrames"); + id_duration = hl_hash_utf8 ("duration"); + id_frameRate = hl_hash_utf8 ("frameRate"); + id_fullRange = hl_hash_utf8 ("fullRange"); + id_hasAudio = hl_hash_utf8 ("hasAudio"); + id_hasVideo = hl_hash_utf8 ("hasVideo"); + id_height = hl_hash_utf8 ("height"); + id_length = hl_hash_utf8 ("length"); + id_pixelAspectRatio = hl_hash_utf8 ("pixelAspectRatio"); + id_rotation = hl_hash_utf8 ("rotation"); + id_texture = hl_hash_utf8 ("texture"); + id_time = hl_hash_utf8 ("time"); + id_videoUnsupported = hl_hash_utf8 ("videoUnsupported"); + id_width = hl_hash_utf8 ("width"); + hl_init = true; + + } + + } + + + void gc_video_decoder (value handle) { + + VideoDecoder* decoder = (VideoDecoder*)val_data (handle); + delete decoder; + + } + + + void hl_gc_video_decoder (HL_CFFIPointer* handle) { + + VideoDecoder* decoder = (VideoDecoder*)handle->ptr; + delete decoder; + + } + + + void lime_video_decoder_close (value handle) { + + VideoDecoder* decoder = (VideoDecoder*)val_data (handle); + + System::GCEnterBlocking (); + decoder->Close (); + System::GCExitBlocking (); + + } + + + HL_PRIM void HL_NAME(hl_video_decoder_close) (HL_CFFIPointer* handle) { + + VideoDecoder* decoder = (VideoDecoder*)handle->ptr; + + hl_blocking (true); + decoder->Close (); + hl_blocking (false); + + } + + + value lime_video_decoder_create () { + + // Loading the platform's media framework the first time can take a while, + // such as GStreamer scanning its plugins + System::GCEnterBlocking (); + VideoBackend* backend = VideoBackend::Create (); + System::GCExitBlocking (); + + if (!backend) return alloc_null (); + + VideoDecoder* decoder = new VideoDecoder (backend); + return CFFIPointer (decoder, gc_video_decoder); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_video_decoder_create) () { + + hl_blocking (true); + VideoBackend* backend = VideoBackend::Create (); + hl_blocking (false); + + if (!backend) return NULL; + + VideoDecoder* decoder = new VideoDecoder (backend); + return HLCFFIPointer (decoder, (hl_finalizer)hl_gc_video_decoder); + + } + + + double lime_video_decoder_get_audio_time (value handle) { + + VideoDecoder* decoder = (VideoDecoder*)val_data (handle); + return decoder->audioTime; + + } + + + HL_PRIM double HL_NAME(hl_video_decoder_get_audio_time) (HL_CFFIPointer* handle) { + + VideoDecoder* decoder = (VideoDecoder*)handle->ptr; + return decoder->audioTime; + + } + + + value lime_video_decoder_get_frame_info (value handle) { + + VideoDecoder* decoder = (VideoDecoder*)val_data (handle); + + _initializeVideo (); + + value info = alloc_empty_object (); + alloc_field (info, id_colorMatrix, alloc_int (decoder->frameColorMatrix)); + alloc_field (info, id_droppedFrames, alloc_int (decoder->droppedFrames)); + alloc_field (info, id_duration, alloc_float (decoder->frameDuration)); + alloc_field (info, id_fullRange, alloc_bool (decoder->frameFullRange)); + alloc_field (info, id_height, alloc_int (decoder->frameHeight)); + alloc_field (info, id_length, alloc_int (decoder->frameLength)); + alloc_field (info, id_texture, alloc_int (decoder->frameTexture)); + alloc_field (info, id_time, alloc_float (decoder->frameTime)); + alloc_field (info, id_width, alloc_int (decoder->frameWidth)); + return info; + + } + + + HL_PRIM vdynamic* HL_NAME(hl_video_decoder_get_frame_info) (HL_CFFIPointer* handle) { + + VideoDecoder* decoder = (VideoDecoder*)handle->ptr; + + _hl_initializeVideo (); + + vdynamic* info = (vdynamic*)hl_alloc_dynobj (); + hl_dyn_seti (info, id_colorMatrix, &hlt_i32, decoder->frameColorMatrix); + hl_dyn_seti (info, id_droppedFrames, &hlt_i32, decoder->droppedFrames); + hl_dyn_setd (info, id_duration, decoder->frameDuration); + hl_dyn_seti (info, id_fullRange, &hlt_bool, decoder->frameFullRange); + hl_dyn_seti (info, id_height, &hlt_i32, decoder->frameHeight); + hl_dyn_seti (info, id_length, &hlt_i32, decoder->frameLength); + hl_dyn_seti (info, id_texture, &hlt_i32, decoder->frameTexture); + hl_dyn_setd (info, id_time, decoder->frameTime); + hl_dyn_seti (info, id_width, &hlt_i32, decoder->frameWidth); + return info; + + } + + + bool lime_video_decoder_is_supported () { + + System::GCEnterBlocking (); + bool supported = VideoBackend::IsSupported (); + System::GCExitBlocking (); + + return supported; + + } + + + HL_PRIM bool HL_NAME(hl_video_decoder_is_supported) () { + + hl_blocking (true); + bool supported = VideoBackend::IsSupported (); + hl_blocking (false); + + return supported; + + } + + + value lime_video_decoder_open (value handle, HxString path, bool hardwareDecoding, int format) { + + // hxcpp keeps strings with non-ASCII characters as UTF-16 + VideoDecoder* decoder = (VideoDecoder*)val_data (handle); + const char* utf8 = hxs_utf8 (path, nullptr); + std::string source = utf8 ? utf8 : ""; + + System::GCEnterBlocking (); + bool success = decoder->Open (source.c_str (), hardwareDecoding, (VideoFrameFormat)format); + System::GCExitBlocking (); + + if (!success) return alloc_null (); + + _initializeVideo (); + + value info = alloc_empty_object (); + alloc_field (info, id_audioChannels, alloc_int (decoder->info.audioChannels)); + alloc_field (info, id_audioSampleRate, alloc_int (decoder->info.audioSampleRate)); + alloc_field (info, id_audioUnsupported, alloc_bool (decoder->info.audioUnsupported)); + alloc_field (info, id_duration, alloc_float (decoder->info.duration)); + alloc_field (info, id_frameRate, alloc_float (decoder->info.frameRate)); + alloc_field (info, id_hasAudio, alloc_bool (decoder->info.hasAudio)); + alloc_field (info, id_hasVideo, alloc_bool (decoder->info.hasVideo)); + alloc_field (info, id_height, alloc_int (decoder->info.height)); + alloc_field (info, id_pixelAspectRatio, alloc_float (decoder->info.pixelAspectRatio)); + alloc_field (info, id_rotation, alloc_int (decoder->info.rotation)); + alloc_field (info, id_videoUnsupported, alloc_bool (decoder->info.videoUnsupported)); + alloc_field (info, id_width, alloc_int (decoder->info.width)); + return info; + + } + + + HL_PRIM vdynamic* HL_NAME(hl_video_decoder_open) (HL_CFFIPointer* handle, hl_vstring* path, bool hardwareDecoding, int format) { + + VideoDecoder* decoder = (VideoDecoder*)handle->ptr; + std::string source = path ? hl_to_utf8 (path->bytes) : ""; + + hl_blocking (true); + bool success = decoder->Open (source.c_str (), hardwareDecoding, (VideoFrameFormat)format); + hl_blocking (false); + + if (!success) return NULL; + + _hl_initializeVideo (); + + vdynamic* info = (vdynamic*)hl_alloc_dynobj (); + hl_dyn_seti (info, id_audioChannels, &hlt_i32, decoder->info.audioChannels); + hl_dyn_seti (info, id_audioSampleRate, &hlt_i32, decoder->info.audioSampleRate); + hl_dyn_seti (info, id_audioUnsupported, &hlt_bool, decoder->info.audioUnsupported); + hl_dyn_setd (info, id_duration, decoder->info.duration); + hl_dyn_setd (info, id_frameRate, decoder->info.frameRate); + hl_dyn_seti (info, id_hasAudio, &hlt_bool, decoder->info.hasAudio); + hl_dyn_seti (info, id_hasVideo, &hlt_bool, decoder->info.hasVideo); + hl_dyn_seti (info, id_height, &hlt_i32, decoder->info.height); + hl_dyn_setd (info, id_pixelAspectRatio, decoder->info.pixelAspectRatio); + hl_dyn_seti (info, id_rotation, &hlt_i32, decoder->info.rotation); + hl_dyn_seti (info, id_videoUnsupported, &hlt_bool, decoder->info.videoUnsupported); + hl_dyn_seti (info, id_width, &hlt_i32, decoder->info.width); + return info; + + } + + + int lime_video_decoder_read_audio (value handle, value buffer, int position, int length) { + + VideoDecoder* decoder = (VideoDecoder*)val_data (handle); + Bytes bytes (buffer); + + if (!bytes.b || position < 0 || length < 0 || position > bytes.length || length > bytes.length - position) return VIDEO_READ_ERROR; + + return decoder->ReadAudio (bytes.b + position, length); + + } + + + HL_PRIM int HL_NAME(hl_video_decoder_read_audio) (HL_CFFIPointer* handle, Bytes* buffer, int position, int length) { + + VideoDecoder* decoder = (VideoDecoder*)handle->ptr; + + if (!buffer || !buffer->b || position < 0 || length < 0 || position > buffer->length || length > buffer->length - position) return VIDEO_READ_ERROR; + + return decoder->ReadAudio (buffer->b + position, length); + + } + + + int lime_video_decoder_read_frame (value handle, value buffer, int position, int length, double time) { + + VideoDecoder* decoder = (VideoDecoder*)val_data (handle); + Bytes bytes (buffer); + + if (!bytes.b || position < 0 || length < 0 || position > bytes.length || length > bytes.length - position) { + + return decoder->ReadFrame (NULL, 0, time); + + } + + return decoder->ReadFrame (bytes.b + position, length, time); + + } + + + HL_PRIM int HL_NAME(hl_video_decoder_read_frame) (HL_CFFIPointer* handle, Bytes* buffer, int position, int length, double time) { + + VideoDecoder* decoder = (VideoDecoder*)handle->ptr; + + if (!buffer || !buffer->b || position < 0 || length < 0 || position > buffer->length || length > buffer->length - position) { + + return decoder->ReadFrame (NULL, 0, time); + + } + + return decoder->ReadFrame (buffer->b + position, length, time); + + } + + + bool lime_video_decoder_seek (value handle, double time, bool accurate) { + + VideoDecoder* decoder = (VideoDecoder*)val_data (handle); + + System::GCEnterBlocking (); + bool success = decoder->Seek (time, accurate); + System::GCExitBlocking (); + + return success; + + } + + + void lime_video_decoder_set_drop_late_frames (value handle, bool value) { + + VideoDecoder* decoder = (VideoDecoder*)val_data (handle); + decoder->dropLateFrames = value; + + } + + + HL_PRIM void HL_NAME(hl_video_decoder_set_drop_late_frames) (HL_CFFIPointer* handle, bool value) { + + VideoDecoder* decoder = (VideoDecoder*)handle->ptr; + decoder->dropLateFrames = value; + + } + + + HL_PRIM bool HL_NAME(hl_video_decoder_seek) (HL_CFFIPointer* handle, double time, bool accurate) { + + VideoDecoder* decoder = (VideoDecoder*)handle->ptr; + + hl_blocking (true); + bool success = decoder->Seek (time, accurate); + hl_blocking (false); + + return success; + + } + + + DEFINE_PRIME1v (lime_video_decoder_close); + DEFINE_PRIME0 (lime_video_decoder_create); + DEFINE_PRIME1 (lime_video_decoder_get_audio_time); + DEFINE_PRIME1 (lime_video_decoder_get_frame_info); + DEFINE_PRIME0 (lime_video_decoder_is_supported); + DEFINE_PRIME4 (lime_video_decoder_open); + DEFINE_PRIME4 (lime_video_decoder_read_audio); + DEFINE_PRIME5 (lime_video_decoder_read_frame); + DEFINE_PRIME3 (lime_video_decoder_seek); + DEFINE_PRIME2v (lime_video_decoder_set_drop_late_frames); + + + #define _TBYTES _OBJ (_I32 _BYTES) + #define _TCFFIPOINTER _DYN + + DEFINE_HL_PRIM (_VOID, hl_video_decoder_close, _TCFFIPOINTER); + DEFINE_HL_PRIM (_TCFFIPOINTER, hl_video_decoder_create, _NO_ARG); + DEFINE_HL_PRIM (_F64, hl_video_decoder_get_audio_time, _TCFFIPOINTER); + DEFINE_HL_PRIM (_DYN, hl_video_decoder_get_frame_info, _TCFFIPOINTER); + DEFINE_HL_PRIM (_BOOL, hl_video_decoder_is_supported, _NO_ARG); + DEFINE_HL_PRIM (_DYN, hl_video_decoder_open, _TCFFIPOINTER _STRING _BOOL _I32); + DEFINE_HL_PRIM (_I32, hl_video_decoder_read_audio, _TCFFIPOINTER _TBYTES _I32 _I32); + DEFINE_HL_PRIM (_I32, hl_video_decoder_read_frame, _TCFFIPOINTER _TBYTES _I32 _I32 _F64); + DEFINE_HL_PRIM (_BOOL, hl_video_decoder_seek, _TCFFIPOINTER _F64 _BOOL); + DEFINE_HL_PRIM (_VOID, hl_video_decoder_set_drop_late_frames, _TCFFIPOINTER _BOOL); + + +} + + +extern "C" int lime_video_register_prims () { + + return 0; + +} diff --git a/project/src/net/curl/CURLBindings.cpp b/project/src/net/curl/CURLBindings.cpp index f6cb9f6e77..f55670a970 100644 --- a/project/src/net/curl/CURLBindings.cpp +++ b/project/src/net/curl/CURLBindings.cpp @@ -2532,10 +2532,19 @@ namespace lime { int lime_curl_multi_perform (value multi_handle) { + CURLM* multi = (CURLM*)val_data (multi_handle); + int runningHandles = 0; + + // Transfers, and name lookups where they can't run on a thread of + // their own, may take a while, so the collector is let run meanwhile. + // The lock is let go before leaving, as finalizers take it too. + System::GCEnterBlocking (); curl_gc_mutex.Lock (); + CURLMcode result = curl_multi_perform (multi, &runningHandles); + curl_gc_mutex.Unlock (); + System::GCExitBlocking (); - int runningHandles = 0; - CURLMcode result = curl_multi_perform ((CURLM*)val_data (multi_handle), &runningHandles); + curl_gc_mutex.Lock (); std::vector* handles = curlMultiHandles[multi_handle]; @@ -2558,10 +2567,17 @@ namespace lime { HL_PRIM int HL_NAME(hl_curl_multi_perform) (HL_CFFIPointer* multi_handle) { + CURLM* multi = (CURLM*)multi_handle->ptr; + int runningHandles = 0; + + // See lime_curl_multi_perform + System::GCEnterBlocking (); curl_gc_mutex.Lock (); + CURLMcode result = curl_multi_perform (multi, &runningHandles); + curl_gc_mutex.Unlock (); + System::GCExitBlocking (); - int runningHandles = 0; - CURLMcode result = curl_multi_perform ((CURLM*)multi_handle->ptr, &runningHandles); + curl_gc_mutex.Lock (); std::vector* handles = curlMultiHandles[multi_handle]; @@ -2830,9 +2846,17 @@ namespace lime { curl_version_info_data* data = curl_version_info ((CURLversion)type); - // TODO + if (!data) { - return alloc_null (); + return alloc_null (); + + } + + value info = alloc_empty_object (); + alloc_field (info, val_id ("age"), alloc_int (data->age)); + alloc_field (info, val_id ("features"), alloc_int (data->features)); + alloc_field (info, val_id ("versionNum"), alloc_int (data->version_num)); + return info; } @@ -2841,9 +2865,17 @@ namespace lime { curl_version_info_data* data = curl_version_info ((CURLversion)type); - // TODO + if (!data) { - return NULL; + return NULL; + + } + + vdynamic* info = (vdynamic*)hl_alloc_dynobj (); + hl_dyn_seti (info, hl_hash_utf8 ("age"), &hlt_i32, data->age); + hl_dyn_seti (info, hl_hash_utf8 ("features"), &hlt_i32, data->features); + hl_dyn_seti (info, hl_hash_utf8 ("versionNum"), &hlt_i32, data->version_num); + return info; } diff --git a/project/src/system/System.cpp b/project/src/system/System.cpp index 06caca2ca3..37551d6d0f 100644 --- a/project/src/system/System.cpp +++ b/project/src/system/System.cpp @@ -20,9 +20,25 @@ namespace lime { #endif + // Blocking calls nest, such as opening a file while a video opens, but + // hxcpp tracks one level, so only the outermost pair reaches it + static thread_local int gcBlockingDepth = 0; + + + // Both collectors wait for every thread they know of to stop, unless it + // has said it is blocked in native code, where it can't stop void System::GCEnterBlocking () { - if (!_isHL) { + if (gcBlockingDepth++ == 0) { + + #ifdef LIME_HASHLINK + if (_isHL) { + + hl_blocking (true); + return; + + } + #endif gc_enter_blocking (); @@ -33,7 +49,16 @@ namespace lime { void System::GCExitBlocking () { - if (!_isHL) { + if (--gcBlockingDepth == 0) { + + #ifdef LIME_HASHLINK + if (_isHL) { + + hl_blocking (false); + return; + + } + #endif gc_exit_blocking (); diff --git a/project/src/text/Font.cpp b/project/src/text/Font.cpp index a57be11df6..ad165664fe 100644 --- a/project/src/text/Font.cpp +++ b/project/src/text/Font.cpp @@ -15,6 +15,7 @@ #include FT_TRUETYPE_TABLES_H #include FT_GLYPH_H #include FT_OUTLINE_H +#include FT_MODULE_H #endif #ifdef GetGlyphIndices @@ -1313,6 +1314,20 @@ namespace lime { } + bool Font::IsBold () { + + return ((FT_Face)face)->style_flags & FT_STYLE_FLAG_BOLD; + + } + + + bool Font::IsItalic () { + + return ((FT_Face)face)->style_flags & FT_STYLE_FLAG_ITALIC; + + } + + int Font::RenderGlyph(int index, Bytes *bytes, int offset) { if (FT_Load_Glyph((FT_Face)face, index, FT_LOAD_FORCE_AUTOHINT | FT_LOAD_DEFAULT) == 0) @@ -1443,15 +1458,36 @@ namespace lime { int Font::RenderGlyphs (value indices, Bytes *bytes) { + int numIndices = val_array_size (indices); + std::vector indexList (numIndices); + + for (int i = 0; i < numIndices; i++) { + + indexList[i] = val_int (val_array_i (indices, i)); + + } + + return RenderGlyphs (indexList.data (), numIndices, bytes); + + } + + + int Font::RenderGlyphs (hl_varray* indices, Bytes *bytes) { + + return RenderGlyphs (hl_aptr (indices, int), indices->size, bytes); + + } + + + int Font::RenderGlyphs (const int* indices, int numIndices, Bytes *bytes) { + int offset = 0; int totalOffset = 4; uint32_t count = 0; - int numIndices = val_array_size (indices); - for (int i = 0; i < numIndices; i++) { - offset = RenderGlyph (val_int (val_array_i (indices, i)), bytes, totalOffset); + offset = RenderGlyph (indices[i], bytes, totalOffset); if (offset > 0) { @@ -1492,4 +1528,28 @@ namespace lime { } + void Font::SetStemDarkening (bool enable) + { + + FT_Bool no_darkening = enable ? 0 : 1; + + FT_Property_Set((FT_Library)library, "cff", "no-stem-darkening", &no_darkening); + FT_Property_Set((FT_Library)library, "autofitter", "no-stem-darkening", &no_darkening); + FT_Property_Set((FT_Library)library, "t1cid", "no-stem-darkening", &no_darkening); + FT_Property_Set((FT_Library)library, "type1", "no-stem-darkening", &no_darkening); + + } + + + void Font::SetStemDarkeningParameters (int params[]) + { + + FT_Property_Set((FT_Library)library, "cff", "darkening-parameters", params); + FT_Property_Set((FT_Library)library, "autofitter", "darkening-parameters", params); + FT_Property_Set((FT_Library)library, "t1cid", "darkening-parameters", params); + FT_Property_Set((FT_Library)library, "type1", "darkening-parameters", params); + + } + + } diff --git a/project/src/ui/FileDialog.cpp b/project/src/ui/FileDialog.cpp index b4ce57fdd1..ec561973b1 100644 --- a/project/src/ui/FileDialog.cpp +++ b/project/src/ui/FileDialog.cpp @@ -6,298 +6,164 @@ #include +#ifdef HX_WINDOWS +#define tinyfd_selectFolderDialog tinyfd_selectFolderDialogW +#define tinyfd_openFileDialog tinyfd_openFileDialogW +#define tinyfd_saveFileDialog tinyfd_saveFileDialogW -namespace lime { - - - std::string* wstring_to_string (std::wstring* source) { +#define FD_STR(str) L##str +#else +#define FD_STR(str) str +#endif - if (!source) return NULL; +using char_t = lime::FileDialog::char_t; +using string_t = std::basic_string; - int size = std::wcslen (source->c_str ()); - char* temp = (char*)malloc (size + 1); - std::wcstombs (temp, source->c_str (), size); - temp[size] = '\0'; - - std::string* data = new std::string (temp); - free (temp); - return data; +namespace lime { + static bool is_null_or_empty (const char_t* str) { + return str == nullptr || str[0] == 0; } + static string_t normalize_filter (const string_t& filter) { - std::wstring* FileDialog::OpenDirectory (std::wstring* title, std::wstring* filter, std::wstring* defaultPath) { - - // TODO: Filter? - - #ifdef HX_WINDOWS - - const wchar_t* path = tinyfd_selectFolderDialogW (title ? title->c_str () : 0, defaultPath ? defaultPath->c_str () : 0); + if (filter.empty () || filter.find (FD_STR('*')) != string_t::npos || filter.find (FD_STR('?')) != string_t::npos) { - if (path && std::wcslen(path) > 0) { - - std::wstring* _path = new std::wstring (path); - return _path; + return filter; } - #else - - std::string* _title = wstring_to_string (title); - //std::string* _filter = wstring_to_string (filter); - std::string* _defaultPath = wstring_to_string (defaultPath); - - const char* path = tinyfd_selectFolderDialog (_title ? _title->c_str () : NULL, _defaultPath ? _defaultPath->c_str () : NULL); - - if (_title) delete _title; - //if (_filter) delete _filter; - if (_defaultPath) delete _defaultPath; - - if (path && std::strlen(path) > 0) { + if (filter[0] == FD_STR('.')) { - std::string _path = std::string (path); - std::wstring* __path = new std::wstring (_path.begin (), _path.end ()); - return __path; + return string_t (FD_STR("*")) + filter; } - #endif - - return 0; + return string_t (FD_STR("*.")) + filter; } + static std::vector normalize_filters (const char_t* filter) { - std::wstring* FileDialog::OpenFile (std::wstring* title, std::wstring* filter, std::wstring* defaultPath) { + std::vector filters; - #ifdef HX_WINDOWS - - std::vector filters_vec; if (filter) { - std::wstring temp (L"*."); - std::wstring line; - std::wstringstream ss(*filter); - while(std::getline(ss, line, L',')) { - filters_vec.push_back(temp + line); - } - } - const int numFilters = filter ? filters_vec.size() : 1; - const wchar_t **filters = new const wchar_t*[numFilters]; - if (filter && numFilters > 0) { - for (int index = 0; index < numFilters; index++) { - filters[index] = const_cast(filters_vec[index].c_str()); - } - } else { - filters[0] = NULL; - } + string_t line; + std::basic_stringstream ss (filter); - const wchar_t* path = tinyfd_openFileDialogW (title ? title->c_str () : 0, defaultPath ? defaultPath->c_str () : 0, filter ? numFilters : 0, filter ? filters : NULL, NULL, 0); + while (std::getline (ss, line, FD_STR(','))) { - delete[] filters; + filters.push_back (normalize_filter (line)); - if (path && std::wcslen(path) > 0) { - - std::wstring* _path = new std::wstring (path); - return _path; + } } - #else + return filters; - std::string* _title = wstring_to_string (title); - std::string* _filter = wstring_to_string (filter); - std::string* _defaultPath = wstring_to_string (defaultPath); - - std::vector filters_vec; - if (_filter) { - std::string line; - std::stringstream ss(*_filter); - while(std::getline(ss, line, ',')) { - line.insert (0, "*."); - filters_vec.push_back(line); - } - } - - const int numFilters = _filter ? filters_vec.size() : 1; - const char **filters = new const char*[numFilters]; - if (_filter && numFilters > 0) { - for (int index = 0; index < numFilters; index++) { - filters[index] = const_cast(filters_vec[index].c_str()); - } - } else { - filters[0] = NULL; - } + } - const char* path = tinyfd_openFileDialog (_title ? _title->c_str () : NULL, _defaultPath ? _defaultPath->c_str () : NULL, _filter ? numFilters : 0, _filter ? filters : NULL, NULL, 0); + const char_t* FileDialog::OpenDirectory (const char_t* title, const char_t* filter, const char_t* defaultPath) { - delete[] filters; + // TODO: Filter? - if (_title) delete _title; - if (_filter) delete _filter; - if (_defaultPath) delete _defaultPath; + const char_t* path = tinyfd_selectFolderDialog(title, defaultPath); - if (path && std::strlen(path) > 0) { + if (!is_null_or_empty(path)) { - std::string _path = std::string (path); - std::wstring* __path = new std::wstring (_path.begin (), _path.end ()); - return __path; + return path; } - #endif - return 0; } - void FileDialog::OpenFiles (std::vector* files, std::wstring* title, std::wstring* filter, std::wstring* defaultPath) { - - std::wstring* __paths = 0; + const char_t* FileDialog::OpenFile (const char_t* title, const char_t* filter, const char_t* defaultPath) { - #ifdef HX_WINDOWS + std::vector filters_vec = normalize_filters (filter); - std::vector filters_vec; - if (filter) { - std::wstring temp (L"*."); - std::wstring line; - std::wstringstream ss(*filter); - while(std::getline(ss, line, L',')) { - filters_vec.push_back(temp + line); - } - } - - const int numFilters = filter ? filters_vec.size() : 1; - const wchar_t **filters = new const wchar_t*[numFilters]; - if (filter && numFilters > 0) { + const int numFilters = filters_vec.size (); + const char_t **filters = numFilters > 0 ? new const char_t*[numFilters] : NULL; + if (numFilters > 0) { for (int index = 0; index < numFilters; index++) { - filters[index] = const_cast(filters_vec[index].c_str()); + filters[index] = filters_vec[index].c_str(); } - } else { - filters[0] = NULL; } - const wchar_t* paths = tinyfd_openFileDialogW (title ? title->c_str () : 0, defaultPath ? defaultPath->c_str () : 0, filter ? numFilters : 0, filter ? filters : NULL, NULL, 1); + const char_t* path = tinyfd_openFileDialog (title, defaultPath, numFilters, filters, NULL, 0); delete[] filters; - if (paths) { + if (!is_null_or_empty(path)) { - __paths = new std::wstring (paths); + return path; } - #else + return 0; - std::string* _title = wstring_to_string (title); - std::string* _filter = wstring_to_string (filter); - std::string* _defaultPath = wstring_to_string (defaultPath); + } - std::vector filters_vec; - if (_filter) { - std::string line; - std::stringstream ss(*_filter); - while(std::getline(ss, line, ',')) { - line.insert (0, "*."); - filters_vec.push_back(line); - } - } - const int numFilters = _filter ? filters_vec.size() : 1; - const char **filters = new const char*[numFilters]; - if (_filter && numFilters > 0) { + std::vector FileDialog::OpenFiles (const char_t* title, const char_t* filter, const char_t* defaultPath) { + std::vector files; + + std::vector filters_vec = normalize_filters (filter); + + const int numFilters = filters_vec.size (); + const char_t **filters = numFilters > 0 ? new const char_t*[numFilters] : NULL; + if (numFilters > 0) { for (int index = 0; index < numFilters; index++) { - filters[index] = const_cast(filters_vec[index].c_str()); + filters[index] = filters_vec[index].c_str(); } - } else { - filters[0] = NULL; } - const char* paths = tinyfd_openFileDialog (_title ? _title->c_str () : NULL, _defaultPath ? _defaultPath->c_str () : NULL, _filter ? numFilters : 0, _filter ? filters : NULL, NULL, 1); + const char_t* paths = tinyfd_openFileDialog (title, defaultPath, numFilters, filters, NULL, 1); delete[] filters; - if (_title) delete _title; - if (_filter) delete _filter; - if (_defaultPath) delete _defaultPath; - - if (paths) { - - std::string _paths = std::string (paths); - __paths = new std::wstring (_paths.begin (), _paths.end ()); - - } - - #endif - - if (__paths) { + if (!is_null_or_empty(paths)) { - std::wstring sep = L"|"; + char_t sep = FD_STR('|'); - std::size_t start = 0, end = 0; + const char_t* start = paths, + * cur = paths; - while ((end = __paths->find (sep, start)) != std::wstring::npos) { - - files->push_back (new std::wstring (__paths->substr (start, end - start).c_str ())); - start = end + 1; + while (*cur) { + if (*cur == sep) { + files.emplace_back(start, cur - start); + start = cur + 1; + } + cur++; } - files->push_back (new std::wstring (__paths->substr (start).c_str ())); + files.emplace_back(start, cur - start); } - } - - - std::wstring* FileDialog::SaveFile (std::wstring* title, std::wstring* filter, std::wstring* defaultPath) { - - #ifdef HX_WINDOWS - - std::wstring temp (L"*."); - const wchar_t* filters[] = { filter ? (temp + *filter).c_str () : NULL }; - - const wchar_t* path = tinyfd_saveFileDialogW (title ? title->c_str () : 0, defaultPath ? defaultPath->c_str () : 0, filter ? 1 : 0, filter ? filters : NULL, NULL); - - if (path && std::wcslen(path) > 0) { - - std::wstring* _path = new std::wstring (path); - return _path; + return files; - } - - #else - - std::string* _title = wstring_to_string (title); - std::string* _filter = wstring_to_string (filter); - std::string* _defaultPath = wstring_to_string (defaultPath); - - const char* filters[] = { NULL }; - - if (_filter) { + } - _filter->insert (0, "*."); - filters[0] = _filter->c_str (); - } + const char_t* FileDialog::SaveFile (const char_t* title, const char_t* filter, const char_t* defaultPath) { - const char* path = tinyfd_saveFileDialog (_title ? _title->c_str () : NULL, _defaultPath ? _defaultPath->c_str () : NULL, _filter ? 1 : 0, _filter ? filters : NULL, NULL); + std::vector filters_vec = normalize_filters (filter); + const int numFilters = filters_vec.size () > 0 ? 1 : 0; + const char_t* filters[] = { numFilters > 0 ? filters_vec[0].c_str () : NULL }; - if (_title) delete _title; - if (_filter) delete _filter; - if (_defaultPath) delete _defaultPath; + const char_t* path = tinyfd_saveFileDialog (title, defaultPath, numFilters, numFilters > 0 ? filters : NULL, NULL); - if (path && std::strlen(path) > 0) { + if (!is_null_or_empty(path)) { - std::string _path = std::string (path); - std::wstring* __path = new std::wstring (_path.begin (), _path.end ()); - return __path; + return path; } - #endif - return 0; } diff --git a/project/src/ui/GamepadEvent.cpp b/project/src/ui/GamepadEvent.cpp index 64fa1c5384..2ea41ba5a4 100644 --- a/project/src/ui/GamepadEvent.cpp +++ b/project/src/ui/GamepadEvent.cpp @@ -11,8 +11,12 @@ namespace lime { static double id_axis; static int id_button; static int id_id; + static int id_timestamp; static int id_type; static int id_value; + static int id_x; + static int id_y; + static int id_z; static bool init = false; @@ -22,7 +26,11 @@ namespace lime { axisValue = 0; button = 0; id = 0; + timestamp = 0; type = GAMEPAD_AXIS_MOVE; + x = 0; + y = 0; + z = 0; } @@ -38,8 +46,12 @@ namespace lime { id_axis = val_id ("axis"); id_button = val_id ("button"); id_id = val_id ("id"); + id_timestamp = val_id ("timestamp"); id_type = val_id ("type"); id_value = val_id ("axisValue"); + id_x = val_id ("x"); + id_y = val_id ("y"); + id_z = val_id ("z"); init = true; } @@ -49,8 +61,12 @@ namespace lime { alloc_field (object, id_axis, alloc_int (event->axis)); alloc_field (object, id_button, alloc_int (event->button)); alloc_field (object, id_id, alloc_int (event->id)); + alloc_field (object, id_timestamp, alloc_int (event->timestamp)); alloc_field (object, id_type, alloc_int (event->type)); alloc_field (object, id_value, alloc_float (event->axisValue)); + alloc_field (object, id_x, alloc_float (event->x)); + alloc_field (object, id_y, alloc_float (event->y)); + alloc_field (object, id_z, alloc_float (event->z)); } else { @@ -59,8 +75,12 @@ namespace lime { eventObject->axis = event->axis; eventObject->button = event->button; eventObject->id = event->id; + eventObject->timestamp = event->timestamp; eventObject->type = event->type; eventObject->axisValue = event->axisValue; + eventObject->x = event->x; + eventObject->y = event->y; + eventObject->z = event->z; } @@ -71,4 +91,4 @@ namespace lime { } -} \ No newline at end of file +} diff --git a/project/src/ui/KeyEvent.cpp b/project/src/ui/KeyEvent.cpp index 42acd15560..871d8d1146 100644 --- a/project/src/ui/KeyEvent.cpp +++ b/project/src/ui/KeyEvent.cpp @@ -10,6 +10,7 @@ namespace lime { static double id_keyCode; static int id_modifier; + static int id_timestamp; static int id_type; static int id_windowID; static bool init = false; @@ -19,6 +20,7 @@ namespace lime { keyCode = 0; modifier = 0; + timestamp = 0; type = KEY_DOWN; windowID = 0; @@ -35,6 +37,7 @@ namespace lime { id_keyCode = val_id ("keyCode"); id_modifier = val_id ("modifier"); + id_timestamp = val_id ("timestamp"); id_type = val_id ("type"); id_windowID = val_id ("windowID"); init = true; @@ -45,6 +48,7 @@ namespace lime { alloc_field (object, id_keyCode, alloc_float (event->keyCode)); alloc_field (object, id_modifier, alloc_int (event->modifier)); + alloc_field (object, id_timestamp, alloc_int (event->timestamp)); alloc_field (object, id_type, alloc_int (event->type)); alloc_field (object, id_windowID, alloc_int (event->windowID)); @@ -54,6 +58,7 @@ namespace lime { eventObject->keyCode = event->keyCode; eventObject->modifier = event->modifier; + eventObject->timestamp = event->timestamp; eventObject->type = event->type; eventObject->windowID = event->windowID; @@ -66,4 +71,4 @@ namespace lime { } -} \ No newline at end of file +} diff --git a/project/src/ui/MenuEvent.cpp b/project/src/ui/MenuEvent.cpp new file mode 100644 index 0000000000..9c00ad83df --- /dev/null +++ b/project/src/ui/MenuEvent.cpp @@ -0,0 +1,64 @@ +#include +#include + + +namespace lime { + + + ValuePointer* MenuEvent::callback = 0; + ValuePointer* MenuEvent::eventObject = 0; + + static int id_id; + static int id_type; + static int id_windowID; + static bool init = false; + + + MenuEvent::MenuEvent () { + + id = 0; + type = MENU_SELECT; + windowID = 0; + + } + + + void MenuEvent::Dispatch (MenuEvent* event) { + + if (MenuEvent::callback) { + + if (MenuEvent::eventObject->IsCFFIValue ()) { + + if (!init) { + + id_id = val_id ("id"); + id_type = val_id ("type"); + id_windowID = val_id ("windowID"); + init = true; + + } + + value object = (value)MenuEvent::eventObject->Get (); + + alloc_field (object, id_id, alloc_int (event->id)); + alloc_field (object, id_type, alloc_int (event->type)); + alloc_field (object, id_windowID, alloc_int (event->windowID)); + + } else { + + MenuEvent* eventObject = (MenuEvent*)MenuEvent::eventObject->Get (); + + eventObject->id = event->id; + eventObject->type = event->type; + eventObject->windowID = event->windowID; + + } + + MenuEvent::callback->Call (); + + } + + } + + +} diff --git a/project/src/ui/TrayIconEvent.cpp b/project/src/ui/TrayIconEvent.cpp new file mode 100644 index 0000000000..5c8e2b3e77 --- /dev/null +++ b/project/src/ui/TrayIconEvent.cpp @@ -0,0 +1,64 @@ +#include +#include + + +namespace lime { + + + ValuePointer* TrayIconEvent::callback = 0; + ValuePointer* TrayIconEvent::eventObject = 0; + + static int id_id; + static int id_itemID; + static int id_type; + static bool init = false; + + + TrayIconEvent::TrayIconEvent () { + + id = 0; + itemID = 0; + type = TRAY_ICON_CLICK; + + } + + + void TrayIconEvent::Dispatch (TrayIconEvent* event) { + + if (TrayIconEvent::callback) { + + if (TrayIconEvent::eventObject->IsCFFIValue ()) { + + if (!init) { + + id_id = val_id ("id"); + id_itemID = val_id ("itemID"); + id_type = val_id ("type"); + init = true; + + } + + value object = (value)TrayIconEvent::eventObject->Get (); + + alloc_field (object, id_id, alloc_int (event->id)); + alloc_field (object, id_itemID, alloc_int (event->itemID)); + alloc_field (object, id_type, alloc_int (event->type)); + + } else { + + TrayIconEvent* eventObject = (TrayIconEvent*)TrayIconEvent::eventObject->Get (); + + eventObject->id = event->id; + eventObject->itemID = event->itemID; + eventObject->type = event->type; + + } + + TrayIconEvent::callback->Call (); + + } + + } + + +} diff --git a/project/src/utils/Bytes.cpp b/project/src/utils/Bytes.cpp index 83f028f2dd..9cf89dbd40 100644 --- a/project/src/utils/Bytes.cpp +++ b/project/src/utils/Bytes.cpp @@ -40,7 +40,9 @@ namespace lime { Bytes::Bytes () { + #ifndef LIME_HASHLINK _initializeBytes (); + #endif b = 0; length = 0; @@ -122,73 +124,51 @@ namespace lime { if (size != length || (length > 0 && !b)) { - mutex.Lock (); - - if (size <= 0) { - - if (b) { - - if (usingValue.find (this) != usingValue.end ()) { - - usingValue.erase (this); - - } else { - - free (b); - - } + unsigned char* data = 0; + unsigned char* oldB = b; + bool freeOldB = false; - } else if (usingValue.find (this) != usingValue.end ()) { + if (size > 0) { - usingValue.erase (this); - - } - - b = 0; - length = 0; - - } else { - - unsigned char* data = (unsigned char*)malloc (sizeof (char) * size); + data = (unsigned char*)malloc (sizeof (char) * size); if (!data) { - mutex.Unlock (); return false; } - if (b) { + if (b && length) { - if (length) { + memcpy (data, b, length < size ? length : size); + + } - memcpy (data, b, length < size ? length : size); + } - } + mutex.Lock (); - if (usingValue.find (this) != usingValue.end ()) { + if (usingValue.find (this) != usingValue.end ()) { - usingValue.erase (this); + usingValue.erase (this); - } else { + } else if (oldB) { - free (b); + freeOldB = true; - } + } - } else if (usingValue.find (this) != usingValue.end ()) { + b = data; + length = size > 0 ? size : 0; - usingValue.erase (this); + mutex.Unlock (); - } + if (freeOldB) { - b = data; - length = size; + free (oldB); } - mutex.Unlock (); - } return (size == length || (size <= 0 && length == 0)); @@ -203,22 +183,22 @@ namespace lime { } - void Bytes::Set(value bytes) { - + void Bytes::Set(value bytes) { + int newLength = 0; unsigned char* newB = 0; bool isNull = val_is_null(bytes); - + if (!isNull) { - + //here we can extract the values before calling our mutex to avoid potential deadlock or contention value lengthVal = val_field(bytes, id_length); value bVal = val_field(bytes, id_b); - + newLength = val_int(lengthVal); - + if (newLength > 0) { - + if (val_is_string(bVal)) { newB = (unsigned char*)val_string(bVal); } else { @@ -226,7 +206,7 @@ namespace lime { } } } - + //and now it should be save to lock mutex.Lock(); @@ -251,7 +231,7 @@ namespace lime { usingHaxeValue = false; } - + if (isNull) { length = 0; b = 0; @@ -261,7 +241,7 @@ namespace lime { length = newLength; b = newB; } - + mutex.Unlock(); } diff --git a/run.n b/run.n index deae46fd3a..1436727e31 100644 Binary files a/run.n and b/run.n differ diff --git a/src/haxe/Timer.hx b/src/haxe/Timer.hx index 425eab35fd..9afc7986c1 100644 --- a/src/haxe/Timer.hx +++ b/src/haxe/Timer.hx @@ -218,6 +218,7 @@ private class TimerTask extends java.util.TimerTask #else import lime.system.System; +@:access(lime.system.System) class Timer { private static var sRunningTimers:Array = []; @@ -249,7 +250,8 @@ class Timer private static function getMS():Float { - return System.getTimer(); + // System.getTimer wraps after 24.8 days, which would stop every timer + return System.__getTimer(); } public static function measure(f:Void->T, ?pos:PosInfos):T @@ -264,7 +266,7 @@ class Timer public static inline function stamp():Float { - var timer = System.getTimer(); + var timer = getMS(); return (timer > 0 ? timer / 1000 : 0); } diff --git a/src/lime/_internal/backend/air/AIRWindow.hx b/src/lime/_internal/backend/air/AIRWindow.hx index 0c5e022fda..3413d2f20e 100644 --- a/src/lime/_internal/backend/air/AIRWindow.hx +++ b/src/lime/_internal/backend/air/AIRWindow.hx @@ -187,7 +187,8 @@ class AIRWindow extends FlashWindow if (parent.application.window == parent) { var newDeviceOrientation:Orientation = UNKNOWN; - switch (parent.stage.deviceOrientation) { + switch (parent.stage.deviceOrientation) + { case DEFAULT: newDeviceOrientation = PORTRAIT; case UPSIDE_DOWN: @@ -202,7 +203,8 @@ class AIRWindow extends FlashWindow parent.application.onDeviceOrientationChange.dispatch(newDeviceOrientation); } var newDisplayOrientation:Orientation = UNKNOWN; - switch (event.afterOrientation) { + switch (event.afterOrientation) + { case DEFAULT: newDisplayOrientation = PORTRAIT; case UPSIDE_DOWN: @@ -331,6 +333,15 @@ class AIRWindow extends FlashWindow return value; } + public override function flash(mode:Int):Bool + { + // AIR's attention requests end on their own once the window is focused + if (nativeWindow == null || !NativeWindow.supportsNotification || mode == 0) return false; + + nativeWindow.notifyUser(mode == 2 ? NotificationType.CRITICAL : NotificationType.INFORMATIONAL); + return true; + } + public override function setAlwaysOnTop(value:Bool):Bool { if (nativeWindow != null) diff --git a/src/lime/_internal/backend/emscripten/EmscriptenFetch.hx b/src/lime/_internal/backend/emscripten/EmscriptenFetch.hx index 91fc89814e..8777ae7134 100644 --- a/src/lime/_internal/backend/emscripten/EmscriptenFetch.hx +++ b/src/lime/_internal/backend/emscripten/EmscriptenFetch.hx @@ -7,44 +7,46 @@ import cpp.ConstCharStar; @:include("emscripten/fetch.h") @:native -extern class EmscriptenFetch { +extern class EmscriptenFetch +{ + @:native("emscripten_fetch_attr_init") + public static function emscripten_fetch_attr_init(attr:Pointer):Void; - @:native("emscripten_fetch_attr_init") - public static function emscripten_fetch_attr_init(attr:Pointer):Void; + @:native("emscripten_fetch") + public static function emscripten_fetch(attr:Pointer, url:ConstCharStar):Pointer; - @:native("emscripten_fetch") - public static function emscripten_fetch(attr:Pointer, url:ConstCharStar):Pointer; - - @:native("emscripten_fetch_close") - public static function emscripten_fetch_close(fetch:Pointer):Void; + @:native("emscripten_fetch_close") + public static function emscripten_fetch_close(fetch:Pointer):Void; } @:include("emscripten/fetch.h") @:native("emscripten_fetch_attr_t") @:structAccess @:unreflective -extern class EmscriptenFetchAttrType { - public var requestMethod:ConstCharStar; - public var onsuccess:Star->Void; - public var onprogress:Star->Void; - public var onerror:Star->Void; - public var attributes:Int; - public var userData:Star; +extern class EmscriptenFetchAttrType +{ + public var requestMethod:ConstCharStar; + public var onsuccess:Star->Void; + public var onprogress:Star->Void; + public var onerror:Star->Void; + public var attributes:Int; + public var userData:Star; } @:include("emscripten/fetch.h") @:native("emscripten_fetch_t") @:structAccess -extern class EmscriptenFetchType { - public var id:Int; - public var numBytes:Int; - public var dataOffset:Int; - public var totalBytes:Int; - public var url:ConstCharStar; - public var readyState:Int; - public var status:Int; - public var statusText:ConstCharStar; - public var data:Pointer; - public var userData:Star; +extern class EmscriptenFetchType +{ + public var id:Int; + public var numBytes:Int; + public var dataOffset:Int; + public var totalBytes:Int; + public var url:ConstCharStar; + public var readyState:Int; + public var status:Int; + public var statusText:ConstCharStar; + public var data:Pointer; + public var userData:Star; } -#end \ No newline at end of file +#end diff --git a/src/lime/_internal/backend/emscripten/EmscriptenHTTPRequest.hx b/src/lime/_internal/backend/emscripten/EmscriptenHTTPRequest.hx index 435afeff94..bb726fa0a8 100644 --- a/src/lime/_internal/backend/emscripten/EmscriptenHTTPRequest.hx +++ b/src/lime/_internal/backend/emscripten/EmscriptenHTTPRequest.hx @@ -16,238 +16,237 @@ import lime.net.HTTPRequest._HTTPRequestErrorResponse; @:access(lime.graphics.Image) class EmscriptenHTTPRequest { - private static var fetchId:Int = 0; - private static var activeFetches = new Map(); - - private var binary:Bool; - private var parent:_IHTTPRequest; - private var currentFetchId:Int = -1; - - public function new() - { - } - - public function cancel():Void - { - if (currentFetchId >= 0 && activeFetches.exists(currentFetchId)) - { - var context = activeFetches.get(currentFetchId); - if (context != null && context.fetchPtr != null) - { - EmscriptenFetch.emscripten_fetch_close(context.fetchPtr); - } - activeFetches.remove(currentFetchId); - currentFetchId = -1; - } - } - - public function init(parent:_IHTTPRequest):Void - { - this.parent = parent; - } - - public function loadData(uri:String):Future - { - var promise = new Promise(); - binary = true; - __load(uri, promise, LoadType.BINARY); - return promise.future; - } - - public function loadText(uri:String):Future - { - var promise = new Promise(); - binary = false; - __load(uri, promise, LoadType.TEXT); - return promise.future; - } - - private function __load(uri:String, promise:Dynamic, loadType:LoadType):Void - { - var id = fetchId++; - currentFetchId = id; - - var context:FetchContext = { - id: id, - promise: promise, - parent: parent, - loadType: loadType, - binary: binary, - fetchPtr: null - }; - - activeFetches.set(id, context); - - var attr:EmscriptenFetchAttrType = untyped __cpp__("{}"); - var attrPtr:Pointer = Pointer.addressOf(attr); - EmscriptenFetch.emscripten_fetch_attr_init(attrPtr); - - // Set request method - var method = parent.method != null ? Std.string(parent.method) : "GET"; - untyped __cpp__('strcpy({0}, {1})', attr.requestMethod, method); - - // Set callbacks - attr.onsuccess = untyped __cpp__("onFetchSuccess"); - attr.onprogress = untyped __cpp__("onFetchProgress"); - attr.onerror = untyped __cpp__("onFetchError"); - - // Store fetch ID in userData - var idPointer:Star = untyped __cpp__('(int*)malloc(sizeof(int))'); - untyped __cpp__('*{0} = {1}', idPointer, id); - attr.userData = cast idPointer; - - // Set EMSCRIPTEN_FETCH_LOAD_TO_MEMORY - attr.attributes = 1; - - // Start fetch - var fetchPtr = EmscriptenFetch.emscripten_fetch(attrPtr, uri); - context.fetchPtr = fetchPtr; - } - - @:keep - private static function onFetchSuccess(fetchPtr:Star):Void - { - var fetch:EmscriptenFetchType = Pointer.fromStar(fetchPtr).value; - var idPointer:Star = cast fetch.userData; - var id:Int = untyped __cpp__('*(int*){0}', idPointer); - - cpp.Native.free(idPointer); - fetch.userData = null; - - if (!activeFetches.exists(id)) - { - EmscriptenFetch.emscripten_fetch_close(Pointer.fromStar(fetchPtr)); - return; - } - - var context = activeFetches.get(id); - activeFetches.remove(id); - if(context == null) - trace("EmscriptenHTTPRequest.onFetchSuccess: context is null"); - - // Process response - if (context.parent.enableResponseHeaders) - { - context.parent.responseHeaders = []; - // Note: Emscripten Fetch API has limited header access - // You may need to parse headers from fetch.statusText if available - } - - context.parent.responseStatus = fetch.status; - - // Create bytes from fetch data - var bytes:Bytes = null; - if (fetch.numBytes > 0 && fetch.data != null) - { - bytes = Bytes.ofData(cast fetch.data.toUnmanagedArray(fetch.numBytes).copy()); - } - else - { - bytes = Bytes.alloc(0); - } - - EmscriptenFetch.emscripten_fetch_close(Pointer.fromStar(fetchPtr)); - - // Complete promise based on load type - if (fetch.status >= 200 && fetch.status < 400) - { - switch (context.loadType) - { - case LoadType.BINARY: - cast(context.promise, Promise).complete(bytes); - - case LoadType.TEXT: - var text = bytes.getString(0, bytes.length, UTF8); - cast(context.promise, Promise).complete(text); - - case LoadType.IMAGE: - // For images, we need to decode the bytes - var img = new Image(); - img.__fromBytes(bytes, function(image) - { - cast(context.promise, Promise).complete(image); - }); - } - } - else - { - var errorResponse:_HTTPRequestErrorResponse; - if(context.loadType == LoadType.BINARY) { - errorResponse = new _HTTPRequestErrorResponse(fetch.status, bytes); - cast(context.promise, Promise).error(errorResponse); - }else { - errorResponse = new _HTTPRequestErrorResponse(fetch.status, bytes.getString(0, bytes.length, UTF8)); - cast(context.promise, Promise).error(errorResponse); - } - } - } - - @:keep - private static function onFetchProgress(fetchPtr:Star):Void - { - var fetch:EmscriptenFetchType = Pointer.fromStar(fetchPtr).value; - var idPointer:Star = cast fetch.userData; - var id:Int = untyped __cpp__('*(int*){0}', idPointer); - - if (activeFetches.exists(id)) - { - var context = activeFetches.get(id); - if (fetch.totalBytes > 0) - { - if(context.loadType == LoadType.BINARY) - cast(context.promise, Promise).progress(fetch.dataOffset + fetch.numBytes, fetch.totalBytes); - else if(context.loadType == LoadType.TEXT) - cast(context.promise, Promise).progress(fetch.dataOffset + fetch.numBytes, fetch.totalBytes); - } - } - } - - @:keep - private static function onFetchError(fetchPtr:Star):Void - { - var fetch:EmscriptenFetchType = Pointer.fromStar(fetchPtr).value; - var idPointer:Star = cast fetch.userData; - var id:Int = untyped __cpp__('*(int*){0}', idPointer); - - cpp.Native.free(idPointer); - - if (!activeFetches.exists(id)) - { - EmscriptenFetch.emscripten_fetch_close(Pointer.fromStar(fetchPtr)); - return; - } - - var context = activeFetches.get(id); - activeFetches.remove(id); - - context.parent.responseStatus = fetch.status; - - EmscriptenFetch.emscripten_fetch_close(Pointer.fromStar(fetchPtr)); - - var errorResponse = new _HTTPRequestErrorResponse(fetch.status, null); - if(context.loadType == LoadType.BINARY) - cast(context.promise, Promise).error(errorResponse); - else - cast(context.promise, Promise).error(errorResponse); - } + private static var fetchId:Int = 0; + private static var activeFetches = new Map(); + + private var binary:Bool; + private var parent:_IHTTPRequest; + private var currentFetchId:Int = -1; + + public function new() {} + + public function cancel():Void + { + if (currentFetchId >= 0 && activeFetches.exists(currentFetchId)) + { + var context = activeFetches.get(currentFetchId); + if (context != null && context.fetchPtr != null) + { + EmscriptenFetch.emscripten_fetch_close(context.fetchPtr); + } + activeFetches.remove(currentFetchId); + currentFetchId = -1; + } + } + + public function init(parent:_IHTTPRequest):Void + { + this.parent = parent; + } + + public function loadData(uri:String):Future + { + var promise = new Promise(); + binary = true; + __load(uri, promise, LoadType.BINARY); + return promise.future; + } + + public function loadText(uri:String):Future + { + var promise = new Promise(); + binary = false; + __load(uri, promise, LoadType.TEXT); + return promise.future; + } + + private function __load(uri:String, promise:Dynamic, loadType:LoadType):Void + { + var id = fetchId++; + currentFetchId = id; + + var context:FetchContext = + { + id: id, + promise: promise, + parent: parent, + loadType: loadType, + binary: binary, + fetchPtr: null + }; + + activeFetches.set(id, context); + + var attr:EmscriptenFetchAttrType = untyped __cpp__("{}"); + var attrPtr:Pointer = Pointer.addressOf(attr); + EmscriptenFetch.emscripten_fetch_attr_init(attrPtr); + + // Set request method + var method = parent.method != null ? Std.string(parent.method) : "GET"; + untyped __cpp__('strcpy({0}, {1})', attr.requestMethod, method); + + // Set callbacks + attr.onsuccess = untyped __cpp__("onFetchSuccess"); + attr.onprogress = untyped __cpp__("onFetchProgress"); + attr.onerror = untyped __cpp__("onFetchError"); + + // Store fetch ID in userData + var idPointer:Star = untyped __cpp__('(int*)malloc(sizeof(int))'); + untyped __cpp__('*{0} = {1}', idPointer, id); + attr.userData = cast idPointer; + + // Set EMSCRIPTEN_FETCH_LOAD_TO_MEMORY + attr.attributes = 1; + + // Start fetch + var fetchPtr = EmscriptenFetch.emscripten_fetch(attrPtr, uri); + context.fetchPtr = fetchPtr; + } + + @:keep + private static function onFetchSuccess(fetchPtr:Star):Void + { + var fetch:EmscriptenFetchType = Pointer.fromStar(fetchPtr).value; + var idPointer:Star = cast fetch.userData; + var id:Int = untyped __cpp__('*(int*){0}', idPointer); + + cpp.Native.free(idPointer); + fetch.userData = null; + + if (!activeFetches.exists(id)) + { + EmscriptenFetch.emscripten_fetch_close(Pointer.fromStar(fetchPtr)); + return; + } + + var context = activeFetches.get(id); + activeFetches.remove(id); + if (context == null) trace("EmscriptenHTTPRequest.onFetchSuccess: context is null"); + + // Process response + if (context.parent.enableResponseHeaders) + { + context.parent.responseHeaders = []; + // Note: Emscripten Fetch API has limited header access + // You may need to parse headers from fetch.statusText if available + } + + context.parent.responseStatus = fetch.status; + + // Create bytes from fetch data + var bytes:Bytes = null; + if (fetch.numBytes > 0 && fetch.data != null) + { + bytes = Bytes.ofData(cast fetch.data.toUnmanagedArray(fetch.numBytes).copy()); + } + else + { + bytes = Bytes.alloc(0); + } + + EmscriptenFetch.emscripten_fetch_close(Pointer.fromStar(fetchPtr)); + + // Complete promise based on load type + if (fetch.status >= 200 && fetch.status < 400) + { + switch (context.loadType) + { + case LoadType.BINARY: + cast(context.promise, Promise).complete(bytes); + + case LoadType.TEXT: + var text = bytes.getString(0, bytes.length, UTF8); + cast(context.promise, Promise).complete(text); + + case LoadType.IMAGE: + // For images, we need to decode the bytes + var img = new Image(); + img.__fromBytes(bytes, function(image) + { + cast(context.promise, Promise).complete(image); + }); + } + } + else + { + var errorResponse:_HTTPRequestErrorResponse; + if (context.loadType == LoadType.BINARY) + { + errorResponse = new _HTTPRequestErrorResponse(fetch.status, bytes); + cast(context.promise, Promise).error(errorResponse); + } + else + { + errorResponse = new _HTTPRequestErrorResponse(fetch.status, bytes.getString(0, bytes.length, UTF8)); + cast(context.promise, Promise).error(errorResponse); + } + } + } + + @:keep + private static function onFetchProgress(fetchPtr:Star):Void + { + var fetch:EmscriptenFetchType = Pointer.fromStar(fetchPtr).value; + var idPointer:Star = cast fetch.userData; + var id:Int = untyped __cpp__('*(int*){0}', idPointer); + + if (activeFetches.exists(id)) + { + var context = activeFetches.get(id); + if (fetch.totalBytes > 0) + { + if (context.loadType == LoadType.BINARY) cast(context.promise, Promise).progress(fetch.dataOffset + fetch.numBytes, fetch.totalBytes); + else if (context.loadType == LoadType.TEXT) cast(context.promise, Promise).progress(fetch.dataOffset + fetch.numBytes, + fetch.totalBytes); + } + } + } + + @:keep + private static function onFetchError(fetchPtr:Star):Void + { + var fetch:EmscriptenFetchType = Pointer.fromStar(fetchPtr).value; + var idPointer:Star = cast fetch.userData; + var id:Int = untyped __cpp__('*(int*){0}', idPointer); + + cpp.Native.free(idPointer); + + if (!activeFetches.exists(id)) + { + EmscriptenFetch.emscripten_fetch_close(Pointer.fromStar(fetchPtr)); + return; + } + + var context = activeFetches.get(id); + activeFetches.remove(id); + + context.parent.responseStatus = fetch.status; + + EmscriptenFetch.emscripten_fetch_close(Pointer.fromStar(fetchPtr)); + + var errorResponse = new _HTTPRequestErrorResponse(fetch.status, null); + if (context.loadType == LoadType.BINARY) cast(context.promise, Promise).error(errorResponse); + else + cast(context.promise, Promise).error(errorResponse); + } } @:dox(hide) enum LoadType { - BINARY; - TEXT; - IMAGE; + BINARY; + TEXT; + IMAGE; } @:dox(hide) typedef FetchContext = { - var id:Int; - var promise:Dynamic; - var parent:_IHTTPRequest; - var loadType:LoadType; - var binary:Bool; - var fetchPtr:Pointer; + var id:Int; + var promise:Dynamic; + var parent:_IHTTPRequest; + var loadType:LoadType; + var binary:Bool; + var fetchPtr:Pointer; } -#end \ No newline at end of file +#end diff --git a/src/lime/_internal/backend/flash/FlashApplication.hx b/src/lime/_internal/backend/flash/FlashApplication.hx index bc1d5b90d6..b63a23ed48 100644 --- a/src/lime/_internal/backend/flash/FlashApplication.hx +++ b/src/lime/_internal/backend/flash/FlashApplication.hx @@ -8,6 +8,7 @@ import lime.app.FrameProfile; import lime.app.VSyncMode; import lime.media.AudioManager; import lime.system.Orientation; +import lime.ui.Menu; import lime.ui.Window; @:access(lime.app.Application) @@ -46,5 +47,7 @@ class FlashApplication return UNKNOWN; } + public function setMenu(menu:Menu):Void {} + public function setVSyncMode(mode:VSyncMode):Void {} } diff --git a/src/lime/_internal/backend/flash/FlashAudioInput.hx b/src/lime/_internal/backend/flash/FlashAudioInput.hx new file mode 100644 index 0000000000..59f0012252 --- /dev/null +++ b/src/lime/_internal/backend/flash/FlashAudioInput.hx @@ -0,0 +1,288 @@ +package lime._internal.backend.flash; + +import flash.events.SampleDataEvent; +import flash.media.Microphone; +import haxe.io.Bytes; +import lime.media.AudioInput; +import lime.media.AudioInputDevice; + +private typedef AudioInputData = +{ + var bytes:Bytes; + var offset:Int; +} + +@:access(lime.media.AudioInput) +class FlashAudioInput +{ + private static inline var MAX_QUEUED_MS = 10000; + + private var buffers:Array; + private var frameSize:Int; + private var microphone:Microphone; + private var parent:AudioInput; + private var queuedBytes:Int; + + public static function getDefaultDevice():AudioInputDevice + { + if (!isSupported()) + { + return null; + } + + var microphone = Microphone.getMicrophone(); + + if (microphone != null) + { + return new AudioInputDevice(microphone.name, Std.string(microphone.index), true); + } + + var devices = getDevices(); + return devices.length > 0 ? devices[0] : null; + } + + public static function getDevices():Array + { + var devices:Array = []; + + if (!isSupported() || Microphone.names == null) + { + return devices; + } + + for (i in 0...Microphone.names.length) + { + var name = Std.string(Microphone.names[i]); + devices.push(new AudioInputDevice(name, Std.string(i), i == 0)); + } + + return devices; + } + + public static function isSupported():Bool + { + return Microphone.isSupported; + } + + public function new(parent:AudioInput) + { + this.parent = parent; + + buffers = []; + frameSize = getFrameSize(); + queuedBytes = 0; + } + + public function dispose():Void + { + stop(); + buffers.resize(0); + queuedBytes = 0; + } + + public function getSamplesAvailable():Int + { + if (frameSize <= 0) + { + return 0; + } + + return Std.int(queuedBytes / frameSize); + } + + public function read(buffer:Bytes, samples:Int):Int + { + if (buffer == null || samples <= 0 || frameSize <= 0) + { + return 0; + } + + var samplesToRead = samples; + var available = getSamplesAvailable(); + var maxSamples = Std.int(buffer.length / frameSize); + + if (samplesToRead > available) + { + samplesToRead = available; + } + + if (samplesToRead > maxSamples) + { + samplesToRead = maxSamples; + } + + if (samplesToRead <= 0) + { + return 0; + } + + var bytesToRead = samplesToRead * frameSize; + var remaining = bytesToRead; + var writePosition = 0; + + while (remaining > 0 && buffers.length > 0) + { + var data = buffers[0]; + var availableBytes = data.bytes.length - data.offset; + var copyBytes = availableBytes < remaining ? availableBytes : remaining; + + buffer.blit(writePosition, data.bytes, data.offset, copyBytes); + + data.offset += copyBytes; + writePosition += copyBytes; + remaining -= copyBytes; + queuedBytes -= copyBytes; + + if (data.offset >= data.bytes.length) + { + buffers.shift(); + } + } + + return Std.int((bytesToRead - remaining) / frameSize); + } + + public function start():Bool + { + if (parent.running || !isSupported() || !isFormatSupported()) + { + return parent.running; + } + + var index = -1; + + if (parent.device != null && !parent.device.isDefault && parent.device.id != null) + { + var parsedIndex = Std.parseInt(parent.device.id); + + if (parsedIndex != null) + { + index = parsedIndex; + } + } + + microphone = Microphone.getMicrophone(index); + + if (microphone == null) + { + return false; + } + + microphone.rate = getMicrophoneRate(parent.sampleRate); + parent.sampleRate = microphone.rate * 1000; + + microphone.setLoopBack(false); + microphone.setSilenceLevel(0, -1); + microphone.setUseEchoSuppression(false); + microphone.addEventListener(SampleDataEvent.SAMPLE_DATA, microphone_onSampleData); + + return true; + } + + public function stop():Void + { + parent.running = false; + + if (microphone != null) + { + microphone.removeEventListener(SampleDataEvent.SAMPLE_DATA, microphone_onSampleData); + microphone = null; + } + } + + private function appendSample(bytes:Bytes, position:Int, sample:Float):Int + { + if (sample > 1) + { + sample = 1; + } + else if (sample < -1) + { + sample = -1; + } + + if (parent.bitsPerSample == 8) + { + bytes.set(position++, Std.int((sample * 0.5 + 0.5) * 255) & 0xFF); + } + else + { + var value = sample < 0 ? Std.int(sample * 32768) : Std.int(sample * 32767); + bytes.set(position++, value & 0xFF); + bytes.set(position++, (value >> 8) & 0xFF); + } + + return position; + } + + private function getFrameSize():Int + { + return Std.int((parent.channels * parent.bitsPerSample) / 8); + } + + private function getMicrophoneRate(sampleRate:Int):Int + { + if (sampleRate >= 44100) + { + return 44; + } + else if (sampleRate >= 22050) + { + return 22; + } + else if (sampleRate >= 11025) + { + return 11; + } + else if (sampleRate >= 8000) + { + return 8; + } + + return 5; + } + + private function isFormatSupported():Bool + { + return (parent.channels == 1 || parent.channels == 2) && (parent.bitsPerSample == 8 || parent.bitsPerSample == 16); + } + + private function microphone_onSampleData(event:SampleDataEvent):Void + { + if (!parent.running || frameSize <= 0) + { + return; + } + + event.data.position = 0; + + var samples = Std.int(event.data.bytesAvailable / 4); + var bytes = Bytes.alloc(samples * frameSize); + var position = 0; + + for (i in 0...samples) + { + var sample = event.data.readFloat(); + + for (channel in 0...parent.channels) + { + position = appendSample(bytes, position, sample); + } + } + + buffers.push({bytes: bytes, offset: 0}); + queuedBytes += bytes.length; + trimQueue(); + } + + private function trimQueue():Void + { + var maxQueuedBytes = Std.int((parent.sampleRate * MAX_QUEUED_MS / 1000) * frameSize); + + while (queuedBytes > maxQueuedBytes && buffers.length > 0) + { + var data = buffers.shift(); + queuedBytes -= data.bytes.length - data.offset; + } + } +} diff --git a/src/lime/_internal/backend/flash/FlashDockIcon.hx b/src/lime/_internal/backend/flash/FlashDockIcon.hx new file mode 100644 index 0000000000..d31b84fe7b --- /dev/null +++ b/src/lime/_internal/backend/flash/FlashDockIcon.hx @@ -0,0 +1,18 @@ +package lime._internal.backend.flash; + +import lime.graphics.Image; +import lime.ui.Menu; + +class FlashDockIcon +{ + public static function bounce(critical:Bool):Void {} + + public static function isSupported():Bool + { + return false; + } + + public static function setIcon(image:Image):Void {} + + public static function setMenu(menu:Menu):Void {} +} diff --git a/src/lime/_internal/backend/flash/FlashHTTPRequest.hx b/src/lime/_internal/backend/flash/FlashHTTPRequest.hx index 67861d810a..e44164755f 100644 --- a/src/lime/_internal/backend/flash/FlashHTTPRequest.hx +++ b/src/lime/_internal/backend/flash/FlashHTTPRequest.hx @@ -174,4 +174,4 @@ class FlashHTTPRequest urlLoader.load(urlRequest); return promise.future; } -} \ No newline at end of file +} diff --git a/src/lime/_internal/backend/flash/FlashTrayIcon.hx b/src/lime/_internal/backend/flash/FlashTrayIcon.hx new file mode 100644 index 0000000000..c5071ef3ba --- /dev/null +++ b/src/lime/_internal/backend/flash/FlashTrayIcon.hx @@ -0,0 +1,23 @@ +package lime._internal.backend.flash; + +import lime.graphics.Image; +import lime.ui.Menu; +import lime.ui.TrayIcon; + +class FlashTrayIcon +{ + public function new(parent:TrayIcon) {} + + public function close():Void {} + + public static function isSupported():Bool + { + return false; + } + + public function setIcon(image:Image):Void {} + + public function setMenu(menu:Menu):Void {} + + public function setTooltip(value:String):Void {} +} diff --git a/src/lime/_internal/backend/flash/FlashVideoSource.hx b/src/lime/_internal/backend/flash/FlashVideoSource.hx new file mode 100644 index 0000000000..6a5cca1068 --- /dev/null +++ b/src/lime/_internal/backend/flash/FlashVideoSource.hx @@ -0,0 +1,155 @@ +package lime._internal.backend.flash; + +import lime.app.Future; +import lime.media.VideoClockMode; +import lime.media.VideoFrame; +import lime.media.VideoFrameFormat; +import lime.media.VideoSource; + +class FlashVideoSource +{ + public function new(parent:VideoSource) {} + + public function dispose():Void {} + + public function getClockMode():VideoClockMode + { + return SMOOTH; + } + + public function getCurrentTime():Int + { + return 0; + } + + public function getDisplayHeight():Int + { + return 0; + } + + public function getDisplayWidth():Int + { + return 0; + } + + public function getDroppedFrames():Int + { + return 0; + } + + public function getDropLateFrames():Bool + { + return true; + } + + public function getGain():Float + { + return 1; + } + + public function getHasAudio():Bool + { + return false; + } + + public function getHasVideo():Bool + { + return false; + } + + public function getHeight():Int + { + return 0; + } + + public function getLength():Int + { + return 0; + } + + public function getLoops():Int + { + return 0; + } + + public function getPan():Float + { + return 0; + } + + public function getPixelAspectRatio():Float + { + return 1; + } + + public function getPlaying():Bool + { + return false; + } + + public function getRotation():Int + { + return 0; + } + + public function getWidth():Int + { + return 0; + } + + public static function isSupported():Bool + { + return false; + } + + public static function loadFromFile(path:String, format:VideoFrameFormat):Future + { + return cast Future.withError("Video is not supported on this target"); + } + + public function open(path:String, format:VideoFrameFormat):Bool + { + return false; + } + + public function pause():Void {} + + public function play():Void {} + + public function readFrame(frame:VideoFrame):Bool + { + return false; + } + + public function setClockMode(value:VideoClockMode):VideoClockMode + { + return value; + } + + public function setCurrentTime(value:Int):Int + { + return value; + } + + public function setDropLateFrames(value:Bool):Bool + { + return value; + } + + public function setGain(value:Float):Float + { + return value; + } + + public function setLoops(value:Int):Int + { + return value; + } + + public function setPan(value:Float):Float + { + return value; + } + + public function stop():Void {} +} diff --git a/src/lime/_internal/backend/flash/FlashWindow.hx b/src/lime/_internal/backend/flash/FlashWindow.hx index 1935a11208..90b3e26847 100644 --- a/src/lime/_internal/backend/flash/FlashWindow.hx +++ b/src/lime/_internal/backend/flash/FlashWindow.hx @@ -21,6 +21,7 @@ import lime.graphics.RenderContextAttributes; import lime.math.Rectangle; import lime.ui.KeyCode; import lime.ui.KeyModifier; +import lime.ui.Menu; import lime.ui.MouseButton; import lime.ui.MouseCursor; import lime.ui.MouseWheelMode; @@ -225,8 +226,7 @@ class FlashWindow "middleMouseMove", "middleMouseUp" #if ((!openfl && !disable_flash_right_click) - || enable_flash_right_click), "rightMouseDown", "rightMouseMove", "rightMouseUp" - #end + || enable_flash_right_click), "rightMouseDown", "rightMouseMove", "rightMouseUp" #end ]; for (event in events) @@ -567,11 +567,26 @@ class FlashWindow return frameRate; } + public function flash(mode:Int):Bool + { + return false; + } + + public function getKeyboardGrab():Bool + { + return false; + } + public function getMouseLock():Bool { return false; } + public function getMouseRect():Rectangle + { + return null; + } + public function getOpacity():Float { return 1.0; @@ -584,6 +599,8 @@ class FlashWindow public function move(x:Int, y:Int):Void {} + public function popupMenu(menu:Menu, x:Null, y:Null):Void {} + public function resize(width:Int, height:Int):Void {} public function setMinSize(width:Int, height:Int):Void {} @@ -615,13 +632,22 @@ class FlashWindow return false; } + public function setMenu(menu:Menu):Void {} + public function setMinimized(value:Bool):Bool { return false; } + public function setKeyboardGrab(value:Bool):Bool + { + return false; + } + public function setMouseLock(value:Bool):Void {} + public function setMouseRect(value:Rectangle):Void {} + public function setOpacity(value:Float):Void {} public function setResizable(value:Bool):Bool diff --git a/src/lime/_internal/backend/html5/HTML5Application.hx b/src/lime/_internal/backend/html5/HTML5Application.hx index e451eb3545..59ba437d6c 100644 --- a/src/lime/_internal/backend/html5/HTML5Application.hx +++ b/src/lime/_internal/backend/html5/HTML5Application.hx @@ -11,12 +11,14 @@ import lime.media.AudioManager; import lime.system.Orientation; import lime.system.Sensor; import lime.system.SensorType; +import lime.system.System; import lime.ui.GamepadAxis; import lime.ui.KeyCode; import lime.ui.KeyModifier; import lime.ui.Gamepad; import lime.ui.GamepadButton; import lime.ui.Joystick; +import lime.ui.Menu; import lime.ui.Window; @:access(lime._internal.backend.html5.HTML5Window) @@ -36,6 +38,7 @@ class HTML5Application private var lastUpdate:Float; private var nextUpdate:Float; private var parent:Application; + private var preferVisibilityChange:Bool; #if stats private var stats:Dynamic; #end @@ -49,6 +52,36 @@ class HTML5Application nextUpdate = 0; framePeriod = -1; + // mobile Safari sometimes dispatches the blur event, but completely + // skips the matching focus event. for instance, when opening the tab + // switcher and returning to the exact same tab. it may depend on which + // type of HTML element has focus, though. + // + // sometimes, mobile Safari doesn't dispatch the blur and focus events + // at all! however, it always dispatches the visibilitychange event when + // switching to a different tab, or to a different app, so we prefer to + // use visibilitychange instead, but only on iOS. + // + // on desktop, we should continue to use the blur and focus events + // because the page will be considered to remain visible when losing + // focus to another app. the visibilitychange event seems to be + // dispatched on desktop when changing tabs only. desktop Safari behaves + // like other desktop browsers. + // + // Note: Android behaves more similarly to desktop than to iOS. + + #if haxe4 + preferVisibilityChange = js.Lib.typeof(Browser.document.visibilityState) == "string" + && ~/(iPad|iPhone|iPod).*OS/gi.match(Browser.window.navigator.userAgent); + #else + preferVisibilityChange = untyped __js__ ('typeof document.visibilityState === "string"') + && ~/(iPad|iPhone|iPod).*OS/gi.match(Browser.window.navigator.userAgent); + #end + if (preferVisibilityChange) + { + hidden = Browser.document.hidden; + } + AudioManager.init(); accelerometer = Sensor.registerSensor(SensorType.ACCELEROMETER, 0); } @@ -275,7 +308,18 @@ class HTML5Application public function configureFrameTiming(profile:FrameProfile, frameRate:Float, options:FrameOptions):Void { - framePeriod = frameRate > 0 ? (1000.0 / frameRate) : -1; + if (frameRate == 0 || frameRate >= 60) + { + framePeriod = -1; + } + else if (frameRate > 0) + { + framePeriod = 1000.0 / frameRate; + } + else + { + framePeriod = 1000; + } } public function exec():Int @@ -286,6 +330,7 @@ class HTML5Application Browser.window.addEventListener("blur", handleWindowEvent, false); Browser.window.addEventListener("resize", handleWindowEvent, false); Browser.window.addEventListener("beforeunload", handleWindowEvent, false); + Browser.document.addEventListener("visibilitychange", handleWindowEvent, false); if (Reflect.hasField(Browser.window, "Accelerometer")) { @@ -426,6 +471,8 @@ class HTML5Application Browser.window.requestAnimationFrame(cast handleApplicationEvent); } + public function setMenu(menu:Menu):Void {} + public function setVSyncMode(mode:VSyncMode):Void {} private function handleKeyEvent(event:KeyboardEvent):Void @@ -444,10 +491,11 @@ class HTML5Application var keyCode = cast convertKeyCode(event.keyCode != null ? event.keyCode : event.which); var modifier = (event.shiftKey ? (KeyModifier.SHIFT) : 0) | (event.ctrlKey ? (KeyModifier.CTRL) : 0) | (event.altKey ? (KeyModifier.ALT) : 0) | (event.metaKey ? (KeyModifier.META) : 0); + var timestamp = Std.int(event.timeStamp); if (event.type == "keydown") { - parent.window.onKeyDown.dispatch(keyCode, modifier); + @:privateAccess parent.window.onKeyDown.__dispatchWithTimestamp(timestamp, keyCode, modifier); if (parent.window.onKeyDown.canceled && event.cancelable) { @@ -456,7 +504,7 @@ class HTML5Application } else { - parent.window.onKeyUp.dispatch(keyCode, modifier); + @:privateAccess parent.window.onKeyUp.__dispatchWithTimestamp(timestamp, keyCode, modifier); if (parent.window.onKeyUp.canceled && event.cancelable) { @@ -478,7 +526,7 @@ class HTML5Application switch (event.type) { case "focus": - if (hidden) + if (!preferVisibilityChange && hidden) { parent.window.onFocusIn.dispatch(); parent.window.onActivate.dispatch(); @@ -486,7 +534,7 @@ class HTML5Application } case "blur": - if (!hidden) + if (!preferVisibilityChange && !hidden) { parent.window.onFocusOut.dispatch(); parent.window.onDeactivate.dispatch(); @@ -494,23 +542,17 @@ class HTML5Application } case "visibilitychange": - if (Browser.document.hidden) + if (Browser.document.hidden && !hidden) { - if (!hidden) - { - parent.window.onFocusOut.dispatch(); - parent.window.onDeactivate.dispatch(); - hidden = true; - } + parent.window.onFocusOut.dispatch(); + parent.window.onDeactivate.dispatch(); + hidden = true; } - else + else if (!Browser.document.hidden && hidden) { - if (hidden) - { - parent.window.onFocusIn.dispatch(); - parent.window.onActivate.dispatch(); - hidden = false; - } + parent.window.onFocusIn.dispatch(); + parent.window.onActivate.dispatch(); + hidden = false; } case "resize": @@ -601,18 +643,19 @@ class HTML5Application for (i in 0...data.buttons.length) { value = data.buttons[i].value; + var timestamp = System.getTimer(); if (value != cache.buttons[i]) { if (i == 6) { joystick.onAxisMove.dispatch(data.axes.length, value); - if (gamepad != null) gamepad.onAxisMove.dispatch(GamepadAxis.TRIGGER_LEFT, value); + if (gamepad != null) @:privateAccess gamepad.onAxisMove.__dispatchWithTimestamp(timestamp, GamepadAxis.TRIGGER_LEFT, value); } else if (i == 7) { joystick.onAxisMove.dispatch(data.axes.length + 1, value); - if (gamepad != null) gamepad.onAxisMove.dispatch(GamepadAxis.TRIGGER_RIGHT, value); + if (gamepad != null) @:privateAccess gamepad.onAxisMove.__dispatchWithTimestamp(timestamp, GamepadAxis.TRIGGER_RIGHT, value); } else { @@ -649,11 +692,11 @@ class HTML5Application if (value > 0) { - gamepad.onButtonDown.dispatch(button); + @:privateAccess gamepad.onButtonDown.__dispatchWithTimestamp(timestamp, button); } else { - gamepad.onButtonUp.dispatch(button); + @:privateAccess gamepad.onButtonUp.__dispatchWithTimestamp(timestamp, button); } } } @@ -667,7 +710,7 @@ class HTML5Application if (data.axes[i] != cache.axes[i]) { joystick.onAxisMove.dispatch(i, data.axes[i]); - if (gamepad != null) gamepad.onAxisMove.dispatch(i, data.axes[i]); + if (gamepad != null) @:privateAccess gamepad.onAxisMove.__dispatchWithTimestamp(System.getTimer(), i, data.axes[i]); cache.axes[i] = data.axes[i]; } } diff --git a/src/lime/_internal/backend/html5/HTML5AudioInput.hx b/src/lime/_internal/backend/html5/HTML5AudioInput.hx new file mode 100644 index 0000000000..856ddd8aad --- /dev/null +++ b/src/lime/_internal/backend/html5/HTML5AudioInput.hx @@ -0,0 +1,444 @@ +package lime._internal.backend.html5; + +import haxe.io.Bytes; +import js.Browser; +import lime.media.AudioInput; +import lime.media.AudioInputDevice; + +private typedef AudioInputData = +{ + var bytes:Bytes; + var offset:Int; +} + +@:access(lime.media.AudioInput) +class HTML5AudioInput +{ + private static inline var MAX_QUEUED_MS = 10000; + + private static var cachedDevices:Array; + private static var enumeratingDevices:Bool; + + private var audioContext:Dynamic; + private var buffers:Array; + private var disposed:Bool; + private var frameSize:Int; + private var mediaStream:Dynamic; + private var parent:AudioInput; + private var processorNode:Dynamic; + private var queuedBytes:Int; + private var sourceNode:Dynamic; + private var starting:Bool; + + public static function getDefaultDevice():AudioInputDevice + { + if (!isSupported()) + { + return null; + } + + return new AudioInputDevice("Default Audio Input", null, true); + } + + public static function getDevices():Array + { + refreshDevices(); + + if (cachedDevices != null) + { + return cachedDevices.copy(); + } + + var defaultDevice = getDefaultDevice(); + return defaultDevice != null ? [defaultDevice] : []; + } + + public static function isSupported():Bool + { + var navigator:Dynamic = Browser.navigator; + return navigator != null && navigator.mediaDevices != null && navigator.mediaDevices.getUserMedia != null; + } + + public function new(parent:AudioInput) + { + this.parent = parent; + + buffers = []; + frameSize = getFrameSize(); + queuedBytes = 0; + } + + public function dispose():Void + { + disposed = true; + stop(); + buffers.resize(0); + queuedBytes = 0; + } + + public function getSamplesAvailable():Int + { + if (frameSize <= 0) + { + return 0; + } + + return Std.int(queuedBytes / frameSize); + } + + public function read(buffer:Bytes, samples:Int):Int + { + if (buffer == null || samples <= 0 || frameSize <= 0) + { + return 0; + } + + var samplesToRead = samples; + var available = getSamplesAvailable(); + var maxSamples = Std.int(buffer.length / frameSize); + + if (samplesToRead > available) + { + samplesToRead = available; + } + + if (samplesToRead > maxSamples) + { + samplesToRead = maxSamples; + } + + if (samplesToRead <= 0) + { + return 0; + } + + var bytesToRead = samplesToRead * frameSize; + var remaining = bytesToRead; + var writePosition = 0; + + while (remaining > 0 && buffers.length > 0) + { + var data = buffers[0]; + var availableBytes = data.bytes.length - data.offset; + var copyBytes = availableBytes < remaining ? availableBytes : remaining; + + buffer.blit(writePosition, data.bytes, data.offset, copyBytes); + + data.offset += copyBytes; + writePosition += copyBytes; + remaining -= copyBytes; + queuedBytes -= copyBytes; + + if (data.offset >= data.bytes.length) + { + buffers.shift(); + } + } + + return Std.int((bytesToRead - remaining) / frameSize); + } + + public function start():Bool + { + if (disposed || starting || parent.running || !isSupported() || !isFormatSupported()) + { + return parent.running; + } + + starting = true; + audioContext = createAudioContext(); + + if (audioContext == null) + { + starting = false; + return false; + } + + if (audioContext.resume != null) + { + audioContext.resume(); + } + + var constraints:Dynamic = + { + audio: + { + channelCount: parent.channels, + echoCancellation: false, + noiseSuppression: false, + autoGainControl: false + } + }; + + if (parent.sampleRate > 0) + { + constraints.audio.sampleRate = parent.sampleRate; + } + + if (parent.device != null && !parent.device.isDefault && parent.device.id != null && parent.device.id != "") + { + constraints.audio.deviceId = {exact: parent.device.id}; + } + + var navigator:Dynamic = Browser.navigator; + var promise:Dynamic = navigator.mediaDevices.getUserMedia(constraints); + + promise.then(function(stream) + { + if (disposed || !starting) + { + stopMediaStream(stream); + return; + } + + open(stream); + }, function(error) + { + starting = false; + parent.running = false; + }); + + return false; + } + + public function stop():Void + { + starting = false; + parent.running = false; + + if (sourceNode != null) + { + sourceNode.disconnect(); + sourceNode = null; + } + + if (processorNode != null) + { + processorNode.disconnect(); + processorNode.onaudioprocess = null; + processorNode = null; + } + + if (audioContext != null && audioContext.close != null) + { + audioContext.close(); + audioContext = null; + } + + if (mediaStream != null) + { + stopMediaStream(mediaStream); + mediaStream = null; + } + } + + private function appendSamples(event:Dynamic):Void + { + var outputBuffer:Dynamic = event.outputBuffer; + + if (outputBuffer != null) + { + for (channel in 0...outputBuffer.numberOfChannels) + { + var output = outputBuffer.getChannelData(channel); + + for (frame in 0...outputBuffer.length) + { + output[frame] = 0; + } + } + } + + if (!parent.running || disposed || frameSize <= 0) + { + return; + } + + var inputBuffer:Dynamic = event.inputBuffer; + var frames:Int = inputBuffer.length; + var inputChannels:Int = inputBuffer.numberOfChannels; + var channelData:Array = []; + + for (channel in 0...parent.channels) + { + var sourceChannel = channel < inputChannels ? channel : 0; + channelData.push(inputBuffer.getChannelData(sourceChannel)); + } + + var bytes = Bytes.alloc(frames * frameSize); + var position = 0; + + for (frame in 0...frames) + { + for (channel in 0...parent.channels) + { + var sample:Float = channelData[channel][frame]; + + if (sample > 1) + { + sample = 1; + } + else if (sample < -1) + { + sample = -1; + } + + if (parent.bitsPerSample == 8) + { + bytes.set(position++, Std.int((sample * 0.5 + 0.5) * 255) & 0xFF); + } + else + { + var value = sample < 0 ? Std.int(sample * 32768) : Std.int(sample * 32767); + bytes.set(position++, value & 0xFF); + bytes.set(position++, (value >> 8) & 0xFF); + } + } + } + + buffers.push({bytes: bytes, offset: 0}); + queuedBytes += bytes.length; + trimQueue(); + } + + private function createAudioContext():Dynamic + { + var window:Dynamic = Browser.window; + var audioContextClass:Dynamic = window.AudioContext != null ? window.AudioContext : window.webkitAudioContext; + + if (audioContextClass == null) + { + return null; + } + + try + { + return untyped #if haxe4 js.Syntax.code #else __js__ #end ("new audioContextClass({ sampleRate: {0} })", parent.sampleRate); + } + catch (e:Dynamic) + { + return untyped #if haxe4 js.Syntax.code #else __js__ #end ("new audioContextClass()"); + } + } + + private function getFrameSize():Int + { + return Std.int((parent.channels * parent.bitsPerSample) / 8); + } + + private function getProcessorBufferSize():Int + { + var size = 256; + + while (size < parent.bufferSize && size < 16384) + { + size *= 2; + } + + return size; + } + + private function isFormatSupported():Bool + { + return (parent.channels == 1 || parent.channels == 2) && (parent.bitsPerSample == 8 || parent.bitsPerSample == 16); + } + + private function open(stream:Dynamic):Void + { + if (audioContext == null) + { + starting = false; + stopMediaStream(stream); + return; + } + + if (audioContext.sampleRate != null) + { + parent.sampleRate = Std.int(audioContext.sampleRate); + } + + mediaStream = stream; + sourceNode = audioContext.createMediaStreamSource(stream); + processorNode = audioContext.createScriptProcessor(getProcessorBufferSize(), parent.channels, parent.channels); + + processorNode.onaudioprocess = appendSamples; + sourceNode.connect(processorNode); + processorNode.connect(audioContext.destination); + + if (audioContext.resume != null) + { + audioContext.resume(); + } + + starting = false; + parent.running = true; + refreshDevices(); + } + + private static function refreshDevices():Void + { + if (enumeratingDevices || !isSupported()) + { + return; + } + + enumeratingDevices = true; + + var navigator:Dynamic = Browser.navigator; + var promise:Dynamic = navigator.mediaDevices.enumerateDevices(); + + promise.then(function(devices) + { + var result:Array = []; + var length:Int = devices.length; + + for (i in 0...length) + { + var device:Dynamic = devices[i]; + + if (device.kind == "audioinput") + { + var label:String = device.label != null && device.label != "" ? device.label : "Audio Input"; + result.push(new AudioInputDevice(label, device.deviceId, result.length == 0)); + } + } + + if (result.length > 0) + { + cachedDevices = result; + } + + enumeratingDevices = false; + }, function(error) + { + enumeratingDevices = false; + }); + } + + private function stopMediaStream(stream:Dynamic):Void + { + if (stream == null || stream.getTracks == null) + { + return; + } + + var tracks:Dynamic = stream.getTracks(); + var length:Int = tracks.length; + + for (i in 0...length) + { + tracks[i].stop(); + } + } + + private function trimQueue():Void + { + var maxQueuedBytes = Std.int((parent.sampleRate * MAX_QUEUED_MS / 1000) * frameSize); + + while (queuedBytes > maxQueuedBytes && buffers.length > 0) + { + var data = buffers.shift(); + queuedBytes -= data.bytes.length - data.offset; + } + } +} diff --git a/src/lime/_internal/backend/html5/HTML5AudioSource.hx b/src/lime/_internal/backend/html5/HTML5AudioSource.hx index f2b9fe80b2..a898c7ad16 100644 --- a/src/lime/_internal/backend/html5/HTML5AudioSource.hx +++ b/src/lime/_internal/backend/html5/HTML5AudioSource.hx @@ -261,7 +261,8 @@ class HTML5AudioSource position.w = value.w; #if lime_howlerjs - if (parent.buffer != null && parent.buffer.__srcHowl != null && parent.buffer.__srcHowl.pos != null) parent.buffer.__srcHowl.pos(position.x, position.y, position.z, id); + if (parent.buffer != null && parent.buffer.__srcHowl != null && parent.buffer.__srcHowl.pos != null) parent.buffer.__srcHowl.pos(position.x, + position.y, position.z, id); // There are more settings to the position of the sound on the "pannerAttr()" function of howler. Maybe somebody who understands sound should look into it? #end diff --git a/src/lime/_internal/backend/html5/HTML5DockIcon.hx b/src/lime/_internal/backend/html5/HTML5DockIcon.hx new file mode 100644 index 0000000000..1ffb1f03c5 --- /dev/null +++ b/src/lime/_internal/backend/html5/HTML5DockIcon.hx @@ -0,0 +1,18 @@ +package lime._internal.backend.html5; + +import lime.graphics.Image; +import lime.ui.Menu; + +class HTML5DockIcon +{ + public static function bounce(critical:Bool):Void {} + + public static function isSupported():Bool + { + return false; + } + + public static function setIcon(image:Image):Void {} + + public static function setMenu(menu:Menu):Void {} +} diff --git a/src/lime/_internal/backend/html5/HTML5HTTPRequest.hx b/src/lime/_internal/backend/html5/HTML5HTTPRequest.hx index 4b94560e66..547d2f348e 100644 --- a/src/lime/_internal/backend/html5/HTML5HTTPRequest.hx +++ b/src/lime/_internal/backend/html5/HTML5HTTPRequest.hx @@ -64,8 +64,7 @@ class HTML5HTTPRequest if (parent.method == POST) { - if(request.upload != null) - request.upload.addEventListener("progress", progress, false); + if (request.upload != null) request.upload.addEventListener("progress", progress, false); } else { diff --git a/src/lime/_internal/backend/html5/HTML5Thread.hx b/src/lime/_internal/backend/html5/HTML5Thread.hx index 9f744e59e1..02caf2ca0c 100644 --- a/src/lime/_internal/backend/html5/HTML5Thread.hx +++ b/src/lime/_internal/backend/html5/HTML5Thread.hx @@ -1,7 +1,6 @@ package lime._internal.backend.html5; import lime.app.Event; - #if macro import haxe.macro.Context; import haxe.macro.Expr; @@ -30,7 +29,8 @@ import lime.app.Application; /** Emulates much of the `sys.thread.Thread` API using web workers. **/ -class HTML5Thread { +class HTML5Thread +{ private static var __current:HTML5Thread = new HTML5Thread(Lib.global.location.href); private static var __isWorker:Bool #if !macro = #if !haxe4 untyped __js__ #else Syntax.code #end ('typeof window == "undefined"') #end; private static var __messages:List = new List(); @@ -80,8 +80,7 @@ class HTML5Thread { #if !macro // Find the URL of the primary JS file. var url:URL = new URL(__current.__href); - url.pathname = url.pathname.substr(0, url.pathname.lastIndexOf("/") + 1) - + Application.current.meta["file"] + ".js"; + url.pathname = url.pathname.substr(0, url.pathname.lastIndexOf("/") + 1) + Application.current.meta["file"] + ".js"; // Use the hash to distinguish workers. if (url.hash.length > 0) url.hash += "_"; @@ -108,9 +107,9 @@ class HTML5Thread { private static inline function zeroDelay():Promise { return new Promise(function(resolve, _):Void - { - js.Lib.global.setTimeout(resolve); - }); + { + js.Lib.global.setTimeout(resolve); + }); } #end @@ -128,8 +127,8 @@ class HTML5Thread { // suspended, so we must insert `await` even if `block` is false. // TODO: find a more efficient way to read messages. var zeroDelayExpr:Expr = macro @:privateAccess - $jsCode("await {0}", lime._internal.backend.html5.HTML5Thread.zeroDelay()) - .then(function(_) return lime._internal.backend.html5.HTML5Thread.__messages.pop()); + $jsCode("await {0}", lime._internal.backend.html5.HTML5Thread.zeroDelay()).then(function(_) return + lime._internal.backend.html5.HTML5Thread.__messages.pop()); switch (block.expr) { @@ -138,15 +137,18 @@ class HTML5Thread { default: return macro if ($block && @:privateAccess lime._internal.backend.html5.HTML5Thread.__messages.isEmpty()) { - $jsCode("await {0}", new #if haxe4 js.lib.Promise #else js.Promise #end + $jsCode("await {0}", new + #if haxe4 + js.lib.Promise + #else + js.Promise + #end (function(resolve, _):Void { @:privateAccess lime._internal.backend.html5.HTML5Thread.__resolveMethods.add(resolve); - } - )); + })); } - else - $zeroDelayExpr; + else $zeroDelayExpr; } } @@ -294,18 +296,18 @@ abstract WorkFunction(WorkFunctionData) from Wor as the `initialExpr`. For instance, `array[i].foo.bar` will result in `chain == ["foo", "bar"]` and `initialExpr == array[i]`. **/ - private static function parseFieldChain(chain:Expr):{ chain:Array, ?initialExpr:Expr } + private static function parseFieldChain(chain:Expr):{chain:Array, ?initialExpr:Expr} { - switch(chain.expr) + switch (chain.expr) { case EConst(CIdent(ident)): - return { chain: [ident] }; + return {chain: [ident]}; case EField(e, field): var out = parseFieldChain(e); out.chain.push(field); return out; default: - return { chain: [], initialExpr: chain }; + return {chain: [], initialExpr: chain}; } } #end @@ -314,9 +316,10 @@ abstract WorkFunction(WorkFunctionData) from Wor @:noCompletion @:dox(hide) #if !macro @:from #end public static #if !macro macro #end function fromFunction(func:ExprOf) { - var defaultOutput:Expr = macro { - func: $func - }; + var defaultOutput:Expr = macro + { + func: $func + }; if (!Context.defined("lime-threads")) { @@ -343,11 +346,12 @@ abstract WorkFunction(WorkFunctionData) from Wor var classPath:String = matcher.matched(1); var functionName:String = matcher.matched(2); - return macro { - func: $func, - classPath: $v{classPath}, - functionName: $v{functionName} - }; + return macro + { + func: $func, + classPath: $v{classPath}, + functionName: $v{functionName} + }; } } @@ -416,7 +420,7 @@ abstract WorkFunction(WorkFunctionData) from Wor else { #if !macro - this.sourceCode = (cast this.func #if haxe4 :Function #end).toString(); + this.sourceCode = (cast this.func #if haxe4 : Function #end).toString(); if (this.sourceCode.indexOf("[native code]") < 0) { // All set. @@ -456,7 +460,8 @@ abstract WorkFunction(WorkFunctionData) from Wor Stores the class path and function name of a function, so that it can be found again in the background thread. **/ -typedef WorkFunctionData = { +typedef WorkFunctionData = +{ @:optional var classPath:String; @:optional var functionName:String; @:optional var sourceCode:String; @@ -475,17 +480,15 @@ abstract Message(Dynamic) from Dynamic to Dynamic private static inline function skip(object:Dynamic):Bool { // Skip `null` for obvious reasons. - return object == null - // No need to preserve a primitive type. - || !#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (object, Object) - // Objects with this field have been deliberately excluded. - || Reflect.field(object, SKIP_FIELD) == true - // A `Uint8Array` (the type used by `haxe.io.Bytes`) can have - // thousands or millions of fields, which can take entire seconds to - // enumerate. This also applies to `Int8Array`, `Float64Array`, etc. - || object.byteLength != null && object.byteOffset != null - && object.buffer != null - && #if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (object.buffer, #if haxe4 js.lib.ArrayBuffer #else js.html.ArrayBuffer #end); + return object == null // No need to preserve a primitive type. + || !#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (object, Object) // Objects with this field have been deliberately excluded. + || Reflect.field(object, SKIP_FIELD) == true // A `Uint8Array` (the type used by `haxe.io.Bytes`) can have + // thousands or millions of fields, which can take entire seconds to + // enumerate. This also applies to `Int8Array`, `Float64Array`, etc. + || object.byteLength != null + && object.byteOffset != null + && object.buffer != null + && #if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (object.buffer, #if haxe4 js.lib.ArrayBuffer #else js.html.ArrayBuffer #end); } #end @@ -556,7 +559,7 @@ abstract Message(Dynamic) from Dynamic to Dynamic // Recurse. for (child in Object.values(this)) { - (child:Message).preserveClasses(); + (child : Message).preserveClasses(); } #end } @@ -600,9 +603,8 @@ abstract Message(Dynamic) from Dynamic to Dynamic { try { - Object.setPrototypeOf(this, - #if haxe4 Syntax.code #else untyped __js__ #end - ("$hxClasses[{0}].prototype", Reflect.field(this, PROTOTYPE_FIELD))); + Object.setPrototypeOf(this, #if haxe4 Syntax.code #else untyped __js__ #end ("$hxClasses[{0}].prototype", Reflect.field(this, + PROTOTYPE_FIELD))); } catch (e:Dynamic) {} } @@ -610,7 +612,7 @@ abstract Message(Dynamic) from Dynamic to Dynamic // Recurse. for (child in Object.values(this)) { - (child:Message).restoreClasses(flag); + (child : Message).restoreClasses(flag); } #end } @@ -620,13 +622,15 @@ abstract Message(Dynamic) from Dynamic to Dynamic typedef Worker = Dynamic; typedef URL = Dynamic; class Object {} + class Browser { public static var window:Dynamic; } + class Lib { - public static var global:Dynamic = { location: {} }; + public static var global:Dynamic = {location: {}}; } #end @@ -641,15 +645,15 @@ class Lib **/ // Mozilla uses "transferable" and "transferrable" interchangeably, but the HTML // specification only uses the former. + @:forward -abstract Transferable(Dynamic) #if macro from Dynamic - #else from lime.utils.ArrayBuffer from js.html.MessagePort from js.html.ImageBitmap #end -{ -} +abstract Transferable(Dynamic) #if macro from Dynamic #else from lime.utils.ArrayBuffer from js.html.MessagePort from js.html.ImageBitmap #end +{} #if (!haxe4 && !macro) @:native("Object") -extern class Object { +extern class Object +{ static function setPrototypeOf(obj:T, prototype:Null<{}>):T; @:pure static function values(obj:{}):Array; static var prototype(default, never):Dynamic; diff --git a/src/lime/_internal/backend/html5/HTML5TrayIcon.hx b/src/lime/_internal/backend/html5/HTML5TrayIcon.hx new file mode 100644 index 0000000000..d9ad16f78f --- /dev/null +++ b/src/lime/_internal/backend/html5/HTML5TrayIcon.hx @@ -0,0 +1,23 @@ +package lime._internal.backend.html5; + +import lime.graphics.Image; +import lime.ui.Menu; +import lime.ui.TrayIcon; + +class HTML5TrayIcon +{ + public function new(parent:TrayIcon) {} + + public function close():Void {} + + public static function isSupported():Bool + { + return false; + } + + public function setIcon(image:Image):Void {} + + public function setMenu(menu:Menu):Void {} + + public function setTooltip(value:String):Void {} +} diff --git a/src/lime/_internal/backend/html5/HTML5VideoSource.hx b/src/lime/_internal/backend/html5/HTML5VideoSource.hx new file mode 100644 index 0000000000..ddd1cade66 --- /dev/null +++ b/src/lime/_internal/backend/html5/HTML5VideoSource.hx @@ -0,0 +1,371 @@ +package lime._internal.backend.html5; + +import js.html.VideoElement; +import js.Browser; +import lime.app.Future; +import lime.app.Promise; +import lime.media.VideoClockMode; +import lime.media.VideoFrame; +import lime.media.VideoFrameFormat; +import lime.media.VideoSource; +import lime.utils.Assets; + +@:access(lime.media.VideoSource) +class HTML5VideoSource +{ + private var clockMode:VideoClockMode; + private var completed:Bool; + private var dropLateFrames:Bool; + private var element:VideoElement; + private var frameTime:Float; + private var gain:Float; + private var loops:Int; + private var pan:Float; + private var parent:VideoSource; + private var path:String; + private var playing:Bool; + + public function new(parent:VideoSource) + { + this.parent = parent; + + clockMode = SMOOTH; + completed = false; + dropLateFrames = true; + frameTime = -1; + gain = 1; + loops = 0; + pan = 0; + playing = false; + } + + public function dispose():Void + { + close(); + } + + public function getClockMode():VideoClockMode + { + return clockMode; + } + + public function getCurrentTime():Int + { + return element != null ? Std.int(element.currentTime * 1000) : 0; + } + + public function getDisplayHeight():Int + { + // The browser sizes and turns the picture itself + return getHeight(); + } + + public function getDisplayWidth():Int + { + return getWidth(); + } + + public function getDroppedFrames():Int + { + // Browsers that count dropped frames report them in the playback quality + if (element == null || untyped element.getVideoPlaybackQuality == null) return 0; + var quality:Dynamic = untyped element.getVideoPlaybackQuality(); + return quality != null ? quality.droppedVideoFrames : 0; + } + + public function getDropLateFrames():Bool + { + return dropLateFrames; + } + + public function getGain():Float + { + return gain; + } + + public function getHasAudio():Bool + { + // Browsers do not report whether a video has sound + return element != null; + } + + public function getHasVideo():Bool + { + // Known once the size is, and a picture the browser cannot decode has none + return element != null && element.videoWidth > 0; + } + + public function getHeight():Int + { + return element != null ? element.videoHeight : 0; + } + + public function getLength():Int + { + if (element == null || Math.isNaN(element.duration) || !Math.isFinite(element.duration)) return 0; + return Std.int(element.duration * 1000); + } + + public function getLoops():Int + { + return loops; + } + + public function getPan():Float + { + return pan; + } + + public function getPixelAspectRatio():Float + { + return 1; + } + + public function getPlaying():Bool + { + return playing; + } + + public function getRotation():Int + { + return 0; + } + + public function getWidth():Int + { + return element != null ? element.videoWidth : 0; + } + + public static function isSupported():Bool + { + return true; + } + + public static function loadFromFile(path:String, format:VideoFrameFormat):Future + { + var promise = new Promise(); + var source = new VideoSource(); + + if (!source.open(path, format)) + { + promise.error("Cannot play " + path); + return promise.future; + } + + var element = source.__backend.element; + + if (element.readyState >= 1) + { + promise.complete(source); + } + else + { + var onMetadata:Dynamic = null; + var onError:Dynamic = null; + + onMetadata = function(_) + { + element.removeEventListener("loadedmetadata", onMetadata); + element.removeEventListener("error", onError); + promise.complete(source); + }; + + onError = function(_) + { + element.removeEventListener("loadedmetadata", onMetadata); + element.removeEventListener("error", onError); + promise.error("Cannot play " + path); + }; + + element.addEventListener("loadedmetadata", onMetadata); + element.addEventListener("error", onError); + } + + return promise.future; + } + + public function open(path:String, format:VideoFrameFormat):Bool + { + close(); + + if (path == null || path == "") return false; + + var url = path; + + if (Assets.exists(path)) + { + var assetPath = Assets.getPath(path); + if (assetPath != null) url = assetPath; + } + + element = cast Browser.document.createElement("video"); + element.crossOrigin = "anonymous"; + element.preload = "auto"; + element.volume = Math.max(0, Math.min(1, gain)); + // Play inline on iOS rather than in the full screen player + element.setAttribute("playsinline", ""); + element.addEventListener("ended", element_onEnded); + element.addEventListener("error", element_onError); + element.src = url; + element.load(); + + completed = false; + frameTime = -1; + this.path = path; + return true; + } + + public function pause():Void + { + playing = false; + + if (element != null) + { + element.pause(); + } + } + + public function play():Void + { + if (element == null || playing) return; + + if (completed) + { + element.currentTime = 0; + completed = false; + } + + playing = true; + + // Haxe 3 types play as Void, from before browsers returned a promise + var result:Dynamic = untyped element.play(); + + if (result != null) + { + // Browsers refuse to start sound before the user interacts with the page + result.then(null, function(_) + { + playing = false; + }); + } + } + + public function readFrame(frame:VideoFrame):Bool + { + // HAVE_CURRENT_DATA, so the element has a picture to upload + if (element == null || frame == null || element.readyState < 2) return false; + if (element.currentTime == frameTime) return false; + + frameTime = element.currentTime; + + frame.data = null; + frame.duration = 0; + frame.format = RGBA; + frame.fullRange = true; + frame.height = element.videoHeight; + frame.length = 0; + frame.src = element; + frame.time = element.currentTime; + frame.width = element.videoWidth; + + return true; + } + + public function setClockMode(value:VideoClockMode):VideoClockMode + { + // The browser keeps a