mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 11:17:16 -04:00
cleanup (#764)
This commit is contained in:
1 parent
4163b31b02
commit
83ec5708ed
852 files changed
+4844
-46144
No files matched your search
@@ -1,3 +1,2 @@
|
||||
# Vendored files preserve upstream license spacing and unified-diff context lines.
|
||||
tools/comet/LICENSE whitespace=-blank-at-eof
|
||||
tools/comet/droiddeck.patch whitespace=-blank-at-eol,-blank-at-eof
|
||||
@@ -1,7 +1,5 @@
|
||||
name: Build AAudio sink module
|
||||
|
||||
# A quick check of tools/aaudio-sink on its own; the APK build compiles the same module into the
|
||||
# audio bundle. Runs on pushes that touch the module.
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
name: Build desktop package (KDE Plasma)
|
||||
|
||||
# The desktop the app installs into its Linux runtime on first use: KDE Plasma as SteamOS's desktop
|
||||
# mode has it, the closure of tools/desktop-kde/seeds.txt over Arch Linux ARM minus what the runtime
|
||||
# already carries, plus tools/desktop-kde/overlay (tools/desktop-kde/build.sh), every package
|
||||
# signature-checked. Published as a release in Droid-Deck/DroidDeck-Components with DroidDeck's
|
||||
# other components; the apk pins it in tools/desktop-kde/release.env (tag, sha256, size). A
|
||||
# published tag is never rebuilt: the packages are Arch Linux ARM's current ones, so a rebuild
|
||||
# would not match the pinned sha256. A changed package gets a new tag (desktop-kde-r2, ...) and a
|
||||
# release.env update.
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
@@ -72,7 +64,6 @@ jobs:
|
||||
KDE_KWIN_OVERRIDE="$PWD/out/kwin" tools/desktop-kde/build.sh out
|
||||
cp out/kwin/desktop-kde.kwin.txt out/
|
||||
echo "size $(stat -c %s out/desktop-kde.tar.zst) sha256 $(cat out/desktop-kde.sha256)"
|
||||
# The package itself too, briefly: enough to try a build on a device before publishing it.
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: desktop-kde
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
name: Build gamescope (patched)
|
||||
|
||||
# The runtime's own gamescope (Arch Linux ARM's 3.16.29 package) rebuilt with the patches under
|
||||
# tools/gamescope/patches, in an Arch Linux ARM container on an arm64 runner, then published as a
|
||||
# release asset the apk build fetches (tools/gamescope/release.env names the tag and sha). The
|
||||
# hosted runtime image is never changed: the apk stages the binary over /usr/local/bin.
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
@@ -43,6 +39,4 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh release create "${{ inputs.tag }}" --latest=false --prerelease=false --title "${{ inputs.tag }}" --notes "gamescope 3.16.29 with tools/gamescope/patches; staged by the apk over /usr/local/bin/gamescope. sha256 $(cut -d' ' -f1 gamescope.tzst.sha256)" gamescope.tzst gamescope.tzst.sha256 || gh release upload "${{ inputs.tag }}" gamescope.tzst gamescope.tzst.sha256 --clobber
|
||||
# A component the apk fetches by tag, never something users install: it must not
|
||||
# become the repo's Latest release or appear as a pre-release, also when re-uploaded.
|
||||
gh release edit "${{ inputs.tag }}" --latest=false --prerelease=false
|
||||
@@ -22,8 +22,6 @@ permissions:
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
# Replace older builds only for the same PR. Each push/dispatch gets its own group so one main
|
||||
# build cannot cancel another before it signs and publishes.
|
||||
group: build-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
@@ -211,14 +209,12 @@ jobs:
|
||||
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -nostdlib -ffreestanding -fno-stack-protector \
|
||||
-fno-tree-loop-distribute-patterns -Wl,-z,defs -o "$D/libblaudit.so" tools/proot/fastpath/audit.c
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/libblaudit.so"
|
||||
# Force-SSBS adapter, preloaded into a game's Wine only when the game asks for it.
|
||||
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
|
||||
-o "$D/libssbs.so" tools/linuxfs/ssbs/ssbs_adapter.c -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/libssbs.so"
|
||||
mkdir -p "$D/usr/local/bin"
|
||||
aarch64-linux-gnu-gcc -O2 -Wall -Wextra -o "$D/usr/local/bin/droiddeck-clipboard" tools/linuxfs/clipboard/clipboard.c -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/usr/local/bin/droiddeck-clipboard"
|
||||
# Cardboard mode's SteamVR driver and Vulkan layer (tools/vr), Vulkan headers from the runner's NDK.
|
||||
VK_INCLUDE=$(ls -d "${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"/toolchains/llvm/prebuilt/*/sysroot/usr/include | head -1) \
|
||||
tools/vr/build.sh --native "$D/usr/local/lib/droiddeck-vr"
|
||||
for script in tools/linuxfs/overlay/usr/local/bin/droiddeck-* tools/linuxfs/overlay/usr/local/bin/steam-compatibility; do
|
||||
@@ -254,14 +250,12 @@ jobs:
|
||||
for so in libfmt.so.10 libspdlog.so.1.13 libglfw.so.3 libtraceevent.so.1; do
|
||||
cp -L "mango-pkgs/usr/lib/$so" "$M/$so"
|
||||
done
|
||||
# Ours, not the package's: GPU memory without tracefs (tools/mangoapp/libtracefs-shim.c).
|
||||
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -Wl,-soname,libtracefs.so.1 \
|
||||
-o "$M/libtracefs.so.1" tools/mangoapp/libtracefs-shim.c
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$M/libtracefs.so.1"
|
||||
install -Dm644 tools/mangoapp/mangoapp "$D/usr/local/bin/mangoapp"
|
||||
rm -rf mango-pkgs
|
||||
ls -l "$M"
|
||||
# The Windows components installer's tools (tools/msitools/packages.txt).
|
||||
T="$D/usr/local/lib/droiddeck-msitools"
|
||||
mkdir -p "$T" msi-pkgs
|
||||
grep -v '^#' tools/msitools/packages.txt | while read -r sum url; do
|
||||
@@ -273,7 +267,6 @@ jobs:
|
||||
done
|
||||
install -m644 msi-pkgs/usr/bin/msiinfo "$T/msiinfo"
|
||||
install -m644 msi-pkgs/usr/bin/cabextract "$T/cabextract"
|
||||
# 7-Zip (the full build with its format plugin) opens installer .exe wrappers.
|
||||
install -m644 msi-pkgs/usr/lib/7zip/7z "$T/7z"
|
||||
install -m644 msi-pkgs/usr/lib/7zip/7z.so "$T/7z.so"
|
||||
for so in libmsi-1.0.so.0 libgsf-1.so.114 libgcab-1.0.so.0; do
|
||||
@@ -291,10 +284,6 @@ jobs:
|
||||
install -Dm644 tools/linuxfs/desktop/firefox-droiddeck.js "$D/usr/lib/firefox/defaults/pref/droiddeck.js"
|
||||
ls -lR "$D"
|
||||
|
||||
# The Debian 11 image the preloads are compiled in comes from a release on this repo
|
||||
# (published once by ci-debian-image.yml, pinned in tools/linuxfs/debian-image.env): the
|
||||
# public registry mirrors rate-limit the shared runner addresses and failed builds on a
|
||||
# busy evening. Only if that download fails is the image pulled, with retries.
|
||||
- name: Fetch the Debian image the preloads build in
|
||||
if: steps.preloads.outputs.cache-hit != 'true'
|
||||
env:
|
||||
@@ -370,9 +359,6 @@ jobs:
|
||||
set -euo pipefail
|
||||
curl -fsSL -o pa.tar.gz https://github.com/pulseaudio/pulseaudio/archive/refs/tags/v13.0.tar.gz
|
||||
mkdir -p pa13 && tar -xzf pa.tar.gz -C pa13 --strip-components=1 && rm pa.tar.gz
|
||||
# DirectAudio - the game driver (one set per Wine audio interface), the Android-side helper and
|
||||
# the Steam client's sinks - comes from a pinned release of The412Banner/directaudio, checksummed,
|
||||
# exactly like gamescope; nothing compiled for audio is kept in this repo.
|
||||
- name: Fetch DirectAudio (tools/directaudio/release.env)
|
||||
run: tools/directaudio/fetch.sh "$PWD" "$PWD/sink-out"
|
||||
- name: Build the AAudio sink into the audio bundle
|
||||
@@ -410,12 +396,6 @@ jobs:
|
||||
set -euo pipefail
|
||||
bash tools/linuxfs/check-preloads.sh app/src/main/assets/linuxfs
|
||||
|
||||
# The store download engines (app/src/main/rust/stores) are Rust, built by Gradle's
|
||||
# buildRustStores task through cargo-ndk. rustup and cargo are on the runner image; the Android
|
||||
# target and cargo-ndk are added here. Cache = cargo registry + cargo-ndk + the crate's target
|
||||
# tree, keyed on the rustc version and Cargo.lock, so a source-only change rebuilds this crate
|
||||
# on top of the cached dependency objects, and a lockfile change still starts from the
|
||||
# previous objects (restore-keys).
|
||||
- name: Rust toolchain for the store engines (aarch64-linux-android)
|
||||
id: rust
|
||||
run: |
|
||||
@@ -474,9 +454,6 @@ jobs:
|
||||
python3 tools/release/runtime_inputs.py apk app/build/outputs/apk/release/app-release.apk
|
||||
|
||||
- name: Check the store engines' library
|
||||
# The exports Kotlin binds must be in the apk's libdroiddeckstores.so, or the stores fail on
|
||||
# the device with an UnsatisfiedLinkError instead of here. The lockfile check is the
|
||||
# reproducibility guard: cargo must not have had to re-resolve anything.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
|
||||
@@ -487,11 +464,6 @@ jobs:
|
||||
|| { echo "::error::Cargo.lock changed during the build; commit the regenerated lockfile (uploaded as stores-cargo-lock)"; exit 1; }
|
||||
|
||||
- name: Store engines host unit tests
|
||||
# The crate's #[test] functions are pure host-side logic (manifest parsing, chunk plans,
|
||||
# assembly from a cache, hashes), so a plain `cargo test` on the runner's own architecture
|
||||
# runs them; the Android build above compiles none of them. Informational on this first
|
||||
# run - the point is to learn whether they pass - and bounded so a hang can never hold
|
||||
# the apk. Make it blocking once it is known green.
|
||||
timeout-minutes: 10
|
||||
continue-on-error: true
|
||||
run: |
|
||||
@@ -626,13 +598,6 @@ jobs:
|
||||
variants: ${{ needs.build.outputs.variants }}
|
||||
secrets: inherit
|
||||
|
||||
# Each signed main build as a prerelease of the public Droid-Deck/DroidDeck-CI (main-<sha>, the
|
||||
# last KEEP kept), where anyone can download it without a GitHub account, then a post in the
|
||||
# Discord's #preview-builds channel linking to it. The release is made with a token of the org's
|
||||
# "DroidDeck CI Publisher" app (installed on DroidDeck-CI only, Contents: write): its ID in the
|
||||
# DROIDDECK_CI_APP_ID variable, its private key in the DROIDDECK_CI_APP_KEY secret.
|
||||
# DISCORD_BUILDS_WEBHOOK is the channel's webhook URL. Keep this secret name when renaming
|
||||
# the channel: Discord routes the existing webhook by channel ID, not its display name.
|
||||
announce:
|
||||
needs: [build, sign]
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
||||
@@ -659,8 +624,6 @@ jobs:
|
||||
owner: Droid-Deck
|
||||
repositories: DroidDeck-CI
|
||||
|
||||
# What this build is, for the top of the release notes: the merged PR's title and the first
|
||||
# paragraph of its description, or the commit's subject for a push made without a PR.
|
||||
- name: Describe the change
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -710,15 +673,12 @@ jobs:
|
||||
gh release delete "$tag" -R "$CI_REPO" --yes --cleanup-tag 2>/dev/null || true
|
||||
gh release create "$tag" -R "$CI_REPO" --target main --prerelease \
|
||||
--title "main ${GITHUB_SHA::7}" --notes "$notes" signed/*
|
||||
# Keep the newest main builds by publication time. GitHub App releases can share createdAt.
|
||||
gh release list -R "$CI_REPO" -L 200 --json tagName,publishedAt \
|
||||
--jq '[.[] | select(.tagName | startswith("main-"))] | sort_by(.publishedAt) | reverse | .['"$KEEP"':][] | .tagName' |
|
||||
while IFS= read -r old; do
|
||||
# Never remove this run's release, even if the API returns an unexpected ordering.
|
||||
if [ "$old" = "$tag" ]; then
|
||||
continue
|
||||
fi
|
||||
# Another main publish may have removed the same old release concurrently.
|
||||
if ! gh release delete "$old" -R "$CI_REPO" --yes --cleanup-tag 2>/dev/null; then
|
||||
if gh release view "$old" -R "$CI_REPO" >/dev/null 2>&1; then
|
||||
echo "::error::Could not delete old release $old"
|
||||
@@ -748,8 +708,6 @@ jobs:
|
||||
fi
|
||||
commit=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA")
|
||||
subject=$(jq -r '.commit.message | split("\n")[0]' <<<"$commit")
|
||||
# A merge commit's subject is "Merge pull request #N from ..."; its body's first line is
|
||||
# the PR's title, which says more.
|
||||
title=$(jq -r '.commit.message | split("\n") | map(select(length > 0)) | .[1] // ""' <<<"$commit")
|
||||
pr=$(sed -n 's/^Merge pull request #\([0-9]*\).*/\1/p' <<<"$subject")
|
||||
if [ -n "$pr" ]; then
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
name: Publish the CI Debian image
|
||||
|
||||
# Pulls the Debian 11 image the x86 preloads build in, once, and publishes it as a release asset
|
||||
# on this repo so build.yml never depends on a registry mirror's rate limit. Run it again (with a
|
||||
# new tag) only to refresh the image; tools/linuxfs/debian-image.env names the one builds use.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -32,7 +29,6 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
image=public.ecr.aws/docker/library/debian:bullseye
|
||||
# A published image is pinned by checksum in the build; never replace one by accident.
|
||||
if [ "$REPLACE" != true ] && gh release view "$TAG" -R "$GITHUB_REPOSITORY" --json assets -q '.assets[].name' 2>/dev/null | grep -qx debian-bullseye.tar.zst; then
|
||||
echo "::error::$TAG already carries debian-bullseye.tar.zst; publish to a new tag, or set replace and re-pin tools/linuxfs/debian-image.env"
|
||||
exit 1
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
name: PR Release Cleanup
|
||||
run-name: remove PR #${{ github.event.pull_request.number }}'s test build
|
||||
run-name: remove PR
|
||||
|
||||
|
||||
|
||||
|
||||
# A merged or closed PR's test build leaves DroidDeck-CI, so the app's Updates page stops offering it
|
||||
# (a merged fix is in the next Preview). pull_request_target for the publisher app's secrets; nothing
|
||||
# from the PR is checked out or run.
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ closed ]
|
||||
@@ -32,7 +32,6 @@ jobs:
|
||||
set -euo pipefail
|
||||
state=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq .state)
|
||||
echo "closed=$([ "$state" = closed ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||||
# A token of the org's "DroidDeck CI Publisher" app, as build.yml's announce job uses.
|
||||
- uses: actions/create-github-app-token@v2
|
||||
if: ${{ steps.state.outputs.closed == 'true' }}
|
||||
id: ci-token
|
||||
|
||||
@@ -11,8 +11,6 @@ on:
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
branches: [main]
|
||||
# A PR from a branch of this repo is signed on its own after each green build: only people who
|
||||
# can push here can change it. A fork's PR waits for a maintainer's publish-test label.
|
||||
workflow_run:
|
||||
workflows: [Build APK]
|
||||
types: [completed]
|
||||
@@ -53,7 +51,6 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
# Every variant a main build ships, so a PR build can be tried under any of them.
|
||||
- name: The packages a signed build ships as
|
||||
id: variants
|
||||
run: echo "list=$(awk '!/^#/ && NF { print $1 }' tools/release/variants.txt | jq -R . | jq -sc .)" >> "$GITHUB_OUTPUT"
|
||||
@@ -68,8 +65,6 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] || { echo "::error::Invalid PR number '$PR_NUMBER'"; exit 1; }
|
||||
# The automatic path stands down quietly, never failing: a draft, a PR labelled
|
||||
# no-test-build, or a build that a later push has already overtaken.
|
||||
skip() { echo "::notice::Not publishing PR #$PR_NUMBER: $1"; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; }
|
||||
if [ "$EVENT_NAME" = workflow_run ]; then
|
||||
echo "automatic=true" >> "$GITHUB_OUTPUT"
|
||||
@@ -94,9 +89,6 @@ jobs:
|
||||
head=$(jq -r '.head.sha' <<<"$pr")
|
||||
base=$(jq -r '.base.sha' <<<"$pr")
|
||||
|
||||
# The label is often added while the PR's build is still running, or just after a push
|
||||
# before it has started: wait for the latest build of the head commit to finish (a PR
|
||||
# build takes about 4 minutes) rather than fail and need the label added again.
|
||||
run_id=
|
||||
for attempt in $(seq 1 60); do
|
||||
latest=$(gh run list -R "$GITHUB_REPOSITORY" --workflow build.yml \
|
||||
@@ -116,8 +108,6 @@ jobs:
|
||||
sleep 30
|
||||
done
|
||||
[[ -n "$run_id" ]] || { echo '::error::The PR build did not finish within 30 minutes'; exit 1; }
|
||||
# GitHub leaves a run's pull_requests empty when its head is in a fork, so the build is
|
||||
# tied to this PR by its head repository, branch and commit instead.
|
||||
run=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id")
|
||||
jq -e --arg head "$head" --arg repo "$(jq -r .head.repo.full_name <<<"$pr")" --arg ref "$(jq -r .head.ref <<<"$pr")" '
|
||||
.event == "pull_request" and .head_sha == $head and .conclusion == "success" and
|
||||
@@ -127,8 +117,6 @@ jobs:
|
||||
|
||||
sdk=${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}
|
||||
tools=$(find "$sdk/build-tools" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -1)
|
||||
# A PR build is made from the PR merged onto main, so it must install over main's own
|
||||
# build. main is what this job checked out; no release has to exist or be marked latest.
|
||||
main_code=$(sed -n 's/^[[:space:]]*versionCode[[:space:]][[:space:]]*\([0-9][0-9]*\)[[:space:]]*$/\1/p' app/build.gradle)
|
||||
[[ "$main_code" =~ ^[0-9]+$ ]] || { echo "::error::No numeric versionCode in main's app/build.gradle"; exit 1; }
|
||||
mkdir -p "$RUNNER_TEMP/pr"
|
||||
@@ -188,8 +176,6 @@ jobs:
|
||||
pattern: droiddeck-pr-signed-*
|
||||
merge-multiple: true
|
||||
path: signed
|
||||
# What this build is, for the top of the release notes: the PR's title and the first
|
||||
# paragraph of its description.
|
||||
- name: Describe the change
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -208,7 +194,6 @@ jobs:
|
||||
fi
|
||||
} > notes-head.md
|
||||
cat notes-head.md
|
||||
# A token of the org's "DroidDeck CI Publisher" app, as build.yml's announce job uses.
|
||||
- uses: actions/create-github-app-token@v2
|
||||
id: ci-token
|
||||
with:
|
||||
@@ -250,7 +235,6 @@ jobs:
|
||||
echo "first=$first" >> "$GITHUB_OUTPUT"
|
||||
gh release upload "$tag" signed/* -R "$repo" --clobber
|
||||
gh release edit "$tag" -R "$repo" --title "PR #$PR_NUMBER" --notes "$notes" --prerelease
|
||||
# Drop the previous build's files: anything this build did not just upload.
|
||||
keep=$(cd signed && ls | jq -R . | jq -sc .)
|
||||
gh api "repos/$repo/releases/tags/$tag" |
|
||||
jq -r --argjson keep "$keep" '.assets[] | select(.name as $n | $keep | index($n) | not) | .id' |
|
||||
|
||||
@@ -63,7 +63,6 @@ jobs:
|
||||
artifact: droiddeck-release
|
||||
secrets: inherit
|
||||
|
||||
# The release is made here, from the apks this run signed, so it traces back to its Build APK run.
|
||||
publish:
|
||||
name: publish the release
|
||||
needs: [find, sign]
|
||||
@@ -94,7 +93,6 @@ jobs:
|
||||
if gh release view "$VERSION" -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
echo "::error::release $VERSION already exists"; exit 1
|
||||
fi
|
||||
# The notes' first line is the title; media paths become raw URLs at this release's tag.
|
||||
title=$(sed -n '1s/^# *//p' "$notes")
|
||||
[ -n "$title" ] || title="DroidDeck $VERSION"
|
||||
raw="https://raw.githubusercontent.com/$GITHUB_REPOSITORY/$VERSION/docs/releases/media/"
|
||||
@@ -110,7 +108,6 @@ jobs:
|
||||
--target "$GITHUB_SHA" --title "$title" --notes-file "$RUNNER_TEMP/body.md" --latest
|
||||
echo "Published [$title]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/$VERSION)" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# A release made with this run's token fires no release event, so the in-app catalog is refreshed here.
|
||||
catalog:
|
||||
needs: publish
|
||||
uses: ./.github/workflows/update-catalog.yml
|
||||
|
||||
@@ -20,7 +20,6 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
# Always use the current trusted generator, even when a release event points at an older tag.
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
|
||||
@@ -8,24 +8,17 @@ captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
.kotlin/
|
||||
# Built in CI from tools/linuxfs and app/src/main/cpp/fakeinput.cpp, never committed, so the two
|
||||
# copies cannot drift.
|
||||
app/src/main/assets/linuxfs/
|
||||
app/src/main/jniLibs/arm64-v8a/libproot.so
|
||||
app/src/main/jniLibs/arm64-v8a/.proot-inputs
|
||||
app/src/main/jniLibs/arm64-v8a/.proot-outputs
|
||||
app/src/main/jniLibs/arm64-v8a/libproot-loader.so
|
||||
app/src/main/jniLibs/arm64-v8a/libdirectaudiorelay.so
|
||||
# The store download engines (app/src/main/rust/stores) are cross-compiled by Gradle into
|
||||
# app/build/rustJniLibs; cargo's own build tree stays next to the crate. Neither is source.
|
||||
app/src/main/rust/stores/target/
|
||||
app/src/main/assets/droiddeck-esync/
|
||||
|
||||
# Kotlin compiler session files (build-tool state, never source).
|
||||
.kotlin/
|
||||
|
||||
# Signing keys never enter the repo (DroidDeck's lives in GitHub environment secrets; see
|
||||
# tools/release/make-release-key.sh). keystore/testkey.p12 is the public AOSP testkey.
|
||||
*.jks
|
||||
*.keystore
|
||||
*.p12
|
||||
@@ -37,7 +30,6 @@ droiddeck-release-key/
|
||||
password.txt
|
||||
__pycache__/
|
||||
|
||||
# Local signing with the release key (tools/build_local.sh); holds a password.
|
||||
.signing.env
|
||||
app/src/main/assets/directaudio/
|
||||
/tools/vr/out/
|
||||
@@ -49,19 +49,12 @@ if (!(treeState in ['clean', 'dirty'])) {
|
||||
buildLabel = "${buildOrigin} ${treeState} · ${buildLabel}"
|
||||
def escapedBuildLabel = buildLabel.replace('\\', '\\\\').replace('"', '\\"')
|
||||
|
||||
// What the in-app updater compares against the published builds: the commit this build is of (a PR
|
||||
// build's head, not the merge CI makes of it), its PR, and whether CI made it at all.
|
||||
def sourceCommit = providers.environmentVariable('DROIDDECK_SOURCE_COMMIT').getOrNull()
|
||||
?: providers.environmentVariable('GITHUB_SHA').getOrNull() ?: gitValue(['rev-parse', 'HEAD']) ?: ''
|
||||
def prNumber = (providers.environmentVariable('DROIDDECK_PR_NUMBER').getOrNull() ?: '0').with { it.isInteger() ? it : '0' }
|
||||
def ciBuild = providers.environmentVariable('GITHUB_ACTIONS').getOrNull() == 'true'
|
||||
// The release key's certificate: a build signed with it, from CI or a computer, updates in place.
|
||||
def releaseSigner = rootProject.file('keystore/release-signer.sha256').text.trim()
|
||||
// When the source was committed, for "built 3 hours ago": fixed per commit, so it costs no rebuilds.
|
||||
def commitTime = (gitValue(['show', '-s', '--format=%ct', 'HEAD']) ?: '0').with { it.isLong() ? it : '0' }
|
||||
// The desktop package this apk installs (tools/desktop-kde/release.env): published once per tag in
|
||||
// DroidDeck-Components, pinned here with its checksum like gamescope and the esync packs, so the
|
||||
// desktop scripts in the apk always get the package they were made for.
|
||||
def desktopKde = rootProject.file('tools/desktop-kde/release.env').readLines()
|
||||
.findAll { it.contains('=') && !it.startsWith('#') }
|
||||
.collectEntries { def kv = it.split('=', 2); [(kv[0].trim()): kv[1].trim()] }
|
||||
@@ -72,7 +65,6 @@ if (desktopRemovalSources.archives[desktopKde.DESKTOP_KDE_TAG] != desktopKde.DES
|
||||
if (!file('src/main/assets/desktop-removal/versions.txt').readLines().contains(desktopKde.DESKTOP_KDE_TAG)) {
|
||||
throw new GradleException('Regenerate desktop removal inventories for ' + desktopKde.DESKTOP_KDE_TAG)
|
||||
}
|
||||
// tools/release/variants.txt as "package=suffix;...", so the updater picks the apk of the package it runs as.
|
||||
def variantSuffixes = rootProject.file('tools/release/variants.txt').readLines()
|
||||
.findAll { it.trim() && !it.startsWith('#') }
|
||||
.collect { def f = it.trim().split(/\s+/); "${f[1]}=${f[2] == '-' ? '' : f[2]}" }
|
||||
@@ -81,16 +73,11 @@ def variantSuffixes = rootProject.file('tools/release/variants.txt').readLines()
|
||||
android {
|
||||
namespace 'com.droiddeck.launcher'
|
||||
compileSdk 34
|
||||
// The NDK is pinned by property so CI can hand in whatever the runner already has rather than
|
||||
// downloading a second copy; see gradle.properties.
|
||||
ndkVersion project.findProperty('ndkVersion') ?: '27.3.13750724'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.droiddeck.launcher"
|
||||
minSdkVersion 26
|
||||
// 28 is not a leftover. An app on targetSdk 29+ runs in a domain that may not execute a
|
||||
// file out of its own data directory, and this app's entire job is to exec a rootfs:
|
||||
// proot, gamescope, the Steam client and everything it launches all live under files/.
|
||||
targetSdkVersion 28
|
||||
versionCode 13
|
||||
versionName "0.4.0"
|
||||
@@ -125,10 +112,6 @@ android {
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
// AOSP testkey ([email protected]), the same key Bannerlator ships with. It is publicly
|
||||
// known, so it proves nothing about who built the apk - what it buys is ONE stable
|
||||
// signature: every build installs over the last one instead of asking the user to
|
||||
// uninstall first. v1 (JAR) + v2 + v3 so every installer and Android version accepts it.
|
||||
testkey {
|
||||
storeFile file("${rootDir}/keystore/testkey.p12")
|
||||
storeType "PKCS12"
|
||||
@@ -153,24 +136,15 @@ android {
|
||||
}
|
||||
|
||||
lint {
|
||||
// targetSdk 28 is deliberate and structural: an app on 29+ may not execute a file out of
|
||||
// its own data directory, and executing a rootfs is the whole of what this app does. Lint
|
||||
// fails a release build over it because Google Play will not accept an sdk that old -
|
||||
// true, and not something this app can trade away. Every other check stays on.
|
||||
disable 'ExpiredTargetSdkVersion'
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources {
|
||||
// Kadb's Bouncy Castle artifacts repeat the same multi-release JAR manifest.
|
||||
excludes += ['META-INF/versions/9/OSGI-INF/MANIFEST.MF']
|
||||
}
|
||||
jniLibs {
|
||||
// The PulseAudio daemon is executed from the native library directory and its
|
||||
// libraries are read out of the apk by the class loader: both want the old,
|
||||
// uncompressed-and-extracted layout.
|
||||
useLegacyPackaging = true
|
||||
// Prefer the PTY helper built by this app's NDK target over Termux's prebuilt copy.
|
||||
pickFirsts += ['**/libtermux.so']
|
||||
}
|
||||
}
|
||||
@@ -181,9 +155,7 @@ android {
|
||||
}
|
||||
|
||||
testOptions {
|
||||
// Plain JVM tests: a stray android.util.Log call returns a default instead of throwing.
|
||||
unitTests.returnDefaultValues = true
|
||||
// Robolectric tests start activities whose text is now all string resources.
|
||||
unitTests.includeAndroidResources = true
|
||||
}
|
||||
|
||||
@@ -196,8 +168,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
// Session scripts must follow this checkout even when the native asset bundle is cached.
|
||||
// A stale local bundle can omit a newly required helper on a fresh runtime install.
|
||||
def stageSessionScripts = tasks.register('stageSessionScripts', Copy) {
|
||||
from(rootProject.file('tools/linuxfs/overlay')) {
|
||||
include 'usr/local/bin/droiddeck-*', 'usr/local/bin/steam-compatibility', 'usr/bin/**'
|
||||
@@ -206,8 +176,6 @@ def stageSessionScripts = tasks.register('stageSessionScripts', Copy) {
|
||||
}
|
||||
tasks.named('preBuild').configure { dependsOn(stageSessionScripts) }
|
||||
|
||||
// Prepared audio lives under build/, so native builds never rewrite the tracked base bundle.
|
||||
// Copying into one asset root also prevents an old base bundle winning a duplicate asset merge.
|
||||
def runtimeAssetsDir = layout.buildDirectory.dir('runtimeAssets')
|
||||
def stageRuntimeAssets = tasks.register('stageRuntimeAssets', Sync) {
|
||||
dependsOn(stageSessionScripts)
|
||||
@@ -218,12 +186,7 @@ def stageRuntimeAssets = tasks.register('stageRuntimeAssets', Sync) {
|
||||
android.sourceSets.main.assets.setSrcDirs([runtimeAssetsDir])
|
||||
tasks.named('preBuild').configure { dependsOn(stageRuntimeAssets) }
|
||||
|
||||
// Unit tests and previews remain usable without Docker. Packaging an APK requires the complete
|
||||
// runtime payload prepared by the local helper or CI, with matching sources and output hashes.
|
||||
def verifyRuntimeInputs = tasks.register('verifyRuntimeInputs', Exec) {
|
||||
// The session scripts are re-copied into the asset tree by stageSessionScripts; with parallel
|
||||
// task execution this check once hashed a file mid-copy and failed a main build. Hash only
|
||||
// once the staging has finished.
|
||||
mustRunAfter(stageSessionScripts, stageRuntimeAssets)
|
||||
workingDir rootProject.projectDir
|
||||
commandLine 'python3', 'tools/release/runtime_inputs.py', 'verify'
|
||||
@@ -232,14 +195,6 @@ tasks.configureEach { task ->
|
||||
if (task.name in ['packageDebug', 'packageRelease']) task.dependsOn(verifyRuntimeInputs)
|
||||
}
|
||||
|
||||
// The GOG / Epic / Amazon download engines are Rust (app/src/main/rust/stores, ported from
|
||||
// Bannerlator's bl-steam-client crate). cargo-ndk cross-compiles them for arm64-v8a into the build
|
||||
// directory, which is one more jniLibs source below: nothing compiled is committed, the same rule
|
||||
// the preloads, proot and the DirectAudio relay follow. cargo-ndk exports the NDK clang/llvm-ar as
|
||||
// the C compiler for the one C-compiling crate (ring, under rustls) - the step Bannerlator's CI
|
||||
// does by hand - and reads the NDK from ANDROID_NDK_HOME, which is set to the NDK this build pins.
|
||||
// -PskipRust=true builds without the library, for a checkout with no Rust toolchain: the stores
|
||||
// then report themselves unavailable instead of the build failing.
|
||||
def rustCrateDir = layout.projectDirectory.dir('src/main/rust/stores')
|
||||
def rustJniLibsDir = layout.buildDirectory.dir('rustJniLibs')
|
||||
def skipRust = (project.findProperty('skipRust') ?: 'false').toString().toBoolean()
|
||||
@@ -255,9 +210,6 @@ def buildRustStores = tasks.register('buildRustStores', Exec) {
|
||||
}.standardOutput.asText)
|
||||
outputs.dir(rustJniLibsDir)
|
||||
workingDir rustCrateDir
|
||||
// The configuration cache serialises the task, so everything it needs is resolved here, not
|
||||
// in doFirst. ~/.cargo/bin is where rustup puts cargo and cargo-ndk; an IDE-launched Gradle
|
||||
// does not always have it on PATH.
|
||||
def cargoBin = "${System.getProperty('user.home')}/.cargo/bin"
|
||||
environment 'PATH', "${cargoBin}${File.pathSeparator}${System.getenv('PATH')}"
|
||||
environment 'ANDROID_NDK_HOME', android.ndkDirectory.absolutePath
|
||||
@@ -265,47 +217,32 @@ def buildRustStores = tasks.register('buildRustStores', Exec) {
|
||||
if (!skipRust) environment 'RUSTC', providers.exec {
|
||||
commandLine 'rustup', 'which', '--toolchain', 'stable', 'rustc'
|
||||
}.standardOutput.asText.get().trim()
|
||||
// --platform 26 = minSdkVersion: the engines link against the API 26 libc, like the C++ code.
|
||||
// Homebrew's cargo/rustc can precede rustup on macOS and use a different sysroot, despite
|
||||
// rustup reporting the Android target installed. Run both through the selected toolchain.
|
||||
commandLine 'rustup', 'run', 'stable', 'cargo', 'ndk', '--target', 'arm64-v8a', '--platform', '26',
|
||||
'--output-dir', rustJniLibsDir.get().asFile.absolutePath,
|
||||
'build', '--release', '--locked'
|
||||
}
|
||||
// Skipping Rust must omit its library even if another build left a cached .so behind.
|
||||
if (!skipRust) android.sourceSets.main.jniLibs.srcDir(rustJniLibsDir)
|
||||
tasks.named('preBuild').configure { dependsOn(buildRustStores) }
|
||||
|
||||
dependencies {
|
||||
// The runtime tarball is a zstd-compressed tar.
|
||||
implementation 'com.github.luben:zstd-jni:1.5.2-3@aar'
|
||||
implementation 'org.apache.commons:commons-compress:1.27.1'
|
||||
// Bannerlator's Windows components come as .tar.xz (WinComponents).
|
||||
implementation 'org.tukaani:xz:1.10'
|
||||
// The UI: every menu, dialog and overlay is Compose. The session's SurfaceView and the
|
||||
// on-screen pad are Views underneath it, which is what they are in every Compose app.
|
||||
implementation platform('androidx.compose:compose-bom:2024.02.00')
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
// @Preview for the composables; the tooling that renders them is a debug-only dependency.
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||
implementation 'androidx.activity:activity-compose:1.8.2'
|
||||
// The File Manager, Bannerlator's: its icon set, image thumbnails and the coroutines it
|
||||
// copies on. Same versions as Bannerlator so the screen stays a straight copy.
|
||||
implementation 'androidx.compose.material:material-icons-extended'
|
||||
implementation 'io.coil-kt:coil-compose:2.6.0'
|
||||
implementation 'com.caverock:androidsvg-aar:1.4'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
||||
// ADB-over-Wi-Fi pairing and authenticated shell client. Users still approve pairing from
|
||||
// Android's Wireless debugging screen; DroidDeck only runs its one fixed setting command.
|
||||
implementation 'com.flyfishxu:kadb-android:1.2.1'
|
||||
// Android terminal view/emulator used by the in-session secondary-display Linux shell.
|
||||
implementation 'com.termux.termux-app:terminal-view:0.118.0'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.robolectric:robolectric:4.16.1'
|
||||
testImplementation 'androidx.compose.ui:ui-test-junit4'
|
||||
// The Android archive does not include the host natives used by JVM archive tests.
|
||||
testRuntimeOnly 'com.github.luben:zstd-jni:1.5.2-3@jar'
|
||||
}
|
||||
@@ -4,34 +4,19 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<!-- Session logs go to Downloads so a failed run can be handed over as one folder. -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<!-- Store download progress and "installed" / "failed" notices (Android 13+). -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<!-- The session keeps running while the app is not on screen: a foreground service holds the
|
||||
process at perceptible priority, and a high-performance WiFi lock keeps a backgrounded
|
||||
download from being throttled to nothing by the radio's power-save. -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<!-- Optional Wi-Fi discovery: requested only from Steam settings after an explanation. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<!-- Ends background processes of another app that holds the Steam login. GameHub starts
|
||||
itself with the phone via boot receivers and signs this client out seconds after every
|
||||
login; a normal permission, and it cannot touch anything in the foreground. -->
|
||||
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
|
||||
<!-- The microphone for the Linux session, only when the user turns it on: the DirectAudio
|
||||
relay helper opens the input stream under this uid, and PulseAudio exposes it to the
|
||||
Steam client as DirectAudioMic. -->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<!-- Installs DroidDeck's own updates (Updates page) over itself, after the user allows it. -->
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<!-- A paired Steam Controller, read over Bluetooth LE out of lizard mode (SteamControllerBle).
|
||||
Granted at install below targetSdk 31; no scan, so no location. -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
|
||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
|
||||
@@ -50,11 +35,6 @@
|
||||
android:appCategory="game"
|
||||
android:isGame="true">
|
||||
|
||||
<!-- A game, to the OS: Android 12+ routes the category through GameManager and the OEM
|
||||
performance frameworks (Game Booster, Game Turbo, Game Space) key off it. Without it
|
||||
a regular flagship runs the session under its "app" power profile - a conservative
|
||||
governor and GPU DVFS - which is why the same chipset feels slower here than in a
|
||||
gaming phone whose mode is on for everything. The config says what the OS may do. -->
|
||||
<meta-data android:name="android.game_mode_config" android:resource="@xml/game_mode_config" />
|
||||
|
||||
<activity
|
||||
@@ -82,10 +62,6 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Android owns the Home key. This small translucent Home handler routes the resulting
|
||||
Home launch either back into a running Steam session or to the regular front end.
|
||||
Off until the user turns on "Use as a Home screen" (Setup): while it is disabled the
|
||||
app is not a Home app at all - not offered, not in the "choose Home" list. -->
|
||||
<activity
|
||||
android:name=".HomeActivity"
|
||||
android:enabled="false"
|
||||
@@ -102,16 +78,7 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!--
|
||||
A session keeps a Surface, a compositor thread and a proot tree alive; a configuration
|
||||
change must not tear that down and rebuild it, so the activity handles them itself.
|
||||
Landscape only: gamescope is told one output size when it starts.
|
||||
|
||||
Resizable, deliberately: a foldable opening or closing changes the window, and the
|
||||
compositor re-fits the picture with bars. Declared non-resizable, the system keeps a
|
||||
fixed-shape window instead and STRETCHES it onto the other panel - the app never sees a
|
||||
size change, so nothing it does can help.
|
||||
-->
|
||||
<activity
|
||||
android:name=".input.ControllerEditorActivity"
|
||||
android:exported="false"
|
||||
@@ -132,7 +99,6 @@
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<!-- A store's own sign-in page (GOG, Epic Games, Amazon Games) in a WebView. -->
|
||||
<activity
|
||||
android:name=".stores.StoreLoginActivity"
|
||||
android:exported="false"
|
||||
@@ -158,29 +124,20 @@
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
|
||||
|
||||
<!--
|
||||
Owns the session - gamescope, the Steam client, audio - so it survives the activity
|
||||
going away. Without it Android demotes the process as soon as the last visible
|
||||
activity goes, and the low-memory killer reaps the guest: leaving Big Picture to
|
||||
answer a message would come back to a dead Steam and a half-finished download.
|
||||
-->
|
||||
<service
|
||||
android:name=".session.SessionService"
|
||||
android:exported="false"
|
||||
android:stopWithTask="false" />
|
||||
<!-- Holds the process in the foreground while the Linux runtime downloads and unpacks. -->
|
||||
<service
|
||||
android:name=".runtime.RuntimeInstallService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".core.WirelessAdbPairingService"
|
||||
android:exported="false" />
|
||||
<!-- Keeps the process alive while a store (GOG, Epic, Amazon) game downloads. -->
|
||||
<service
|
||||
android:name=".stores.download.StoreDownloadService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
<!-- Hands a zipped session folder to the share sheet; nothing else is exposed. -->
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.logs"
|
||||
@@ -190,8 +147,6 @@
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/log_share_paths" />
|
||||
</provider>
|
||||
<!-- The app's data folder in Android's file picker ("Open from" > DroidDeck). Only the
|
||||
system's documents UI may bind it; apps get a file only when the user picks it. -->
|
||||
<provider
|
||||
android:name=".files.AppFilesProvider"
|
||||
android:authorities="${applicationId}.documents"
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
# The app's native side: the Wayland compositor the session presents into, the adrenotools loader
|
||||
# it opens Turnip with, and one small library for the fake-input ring's memory fence.
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
project(droiddeck C CXX)
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -Wno-unused-function -Wimplicit-function-declaration")
|
||||
|
||||
# Every library the app builds is linked with 16 KB-aligned LOAD segments. A 16 KB-page kernel
|
||||
# (Android 15+ devices) refuses to load a 4 KB-only ELF; a 4 KB kernel loads a 16 KB-aligned one
|
||||
# unchanged (the alignment is a multiple of its page), so this costs nothing on today's devices
|
||||
# but a little padding in the file. Prebuilt libraries under jniLibs/ carry their own alignment.
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||
|
||||
add_subdirectory(adrenotools)
|
||||
|
||||
# DXVK's DXBC->SPIR-V translator (zlib licence), vendored under thirdparty/dxbc from the same
|
||||
# subset upstream lsfg-vk uses. Native LSFG frame generation needs it: the Lossless Scaling
|
||||
# shader chain is DXBC in every build downloadable from Steam.
|
||||
# DXVK's DXBC->SPIR-V translator is zlib licensed (vendored under thirdparty/dxbc).
|
||||
add_subdirectory(thirdparty/dxbc)
|
||||
|
||||
# The Win-FG chain self-identifies by two halves: the upstream tag it was copied from (hand-set,
|
||||
# UPDATE IT when re-copying) and a hash over the sources themselves, computed here, which cannot
|
||||
# rot. The compositor's frame-generation leg (waylandcomp/CMakeLists.txt) stamps both in.
|
||||
set(WINFG_UPSTREAM "v0.3.0")
|
||||
set(WINFG_CHAIN_SRC
|
||||
framegen/winfg/framegen.cpp
|
||||
@@ -44,11 +33,7 @@ add_subdirectory(waylandcomp)
|
||||
add_library(sdnative SHARED sdnative/sdnative.c)
|
||||
target_link_libraries(sdnative log)
|
||||
|
||||
# The device info window's Vulkan section: the phone's own driver, asked once through the system
|
||||
# loader (opened with dlopen, so a phone without Vulkan still loads the app).
|
||||
add_library(deviceinfo SHARED deviceinfo/vkinfo.c)
|
||||
target_link_libraries(deviceinfo dl)
|
||||
|
||||
# Rebuild Termux terminal-emulator's PTY JNI helper with the app's NDK instead of carrying its
|
||||
# prebuilt libtermux.so with a redundant libstdc++.so dependency.
|
||||
add_library(termux SHARED termux/termux.c)
|
||||
@@ -3,7 +3,6 @@
|
||||
aarch64-linux-gnu-gcc -c src/bcenabler_patch.s -o /tmp/patch.o
|
||||
aarch64-linux-gnu-objcopy -O binary -j .text /tmp/patch.o /tmp/patch.bin
|
||||
|
||||
# Must be smaller than a single page
|
||||
bin2c -o gen/bcenabler_patch.h -n PatchRawData /tmp/patch.bin
|
||||
|
||||
rm /tmp/patch.o
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/* Generated by bin2c, do not edit manually */
|
||||
|
||||
/* Contents of file /tmp/patch.bin */
|
||||
const long int PatchRawData_size = 264;
|
||||
const unsigned char PatchRawData[264] = {
|
||||
0x1F, 0x0C, 0x02, 0x71, 0x61, 0x00, 0x00, 0x54, 0xE0, 0x08, 0x80, 0x52, 0x3E, 0x00, 0x00, 0x14,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -11,29 +12,14 @@ extern "C" {
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Describes the level of BCeNabler support for a driver
|
||||
*/
|
||||
enum adrenotools_bcn_type {
|
||||
ADRENOTOOLS_BCN_INCOMPATIBLE, //!< Driver isn't supported by BCeNabler
|
||||
ADRENOTOOLS_BCN_BLOB, //!< Driver already supports BCn textures so BCeNabler isn't necessary
|
||||
ADRENOTOOLS_BCN_PATCH //!< Driver can be patched with BCeNabler to support BCn textures
|
||||
ADRENOTOOLS_BCN_INCOMPATIBLE,
|
||||
ADRENOTOOLS_BCN_BLOB,
|
||||
ADRENOTOOLS_BCN_PATCH
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Checks the status of BCn support in the supplied driver
|
||||
* @param major The major part of VkPhysicalDeviceProperties::driverVersion
|
||||
* @param minor The minor part of VkPhysicalDeviceProperties::driverVersion
|
||||
* @param vendorId VkPhysicalDeviceProperties::vendorId
|
||||
*/
|
||||
enum adrenotools_bcn_type adrenotools_get_bcn_type(uint32_t major, uint32_t minor, uint32_t vendorId);
|
||||
|
||||
/**
|
||||
* @brief Patches the Adreno graphics driver to enable support for BCn compressed formats
|
||||
* @note adrenotools_get_bcn_type MUST be checked to equal ADRENOTOOLS_BCN_PATCH before calling this
|
||||
* @param vkGetPhysicalDeviceFormatPropertiesFn A pointer to vkGetPhysicalDeviceFormatProperties obtained through vkGetInstanceProcAddr. This is used to find the correct function to patch
|
||||
* @return If the patching succeeded, if false the driver will be in an undefined state
|
||||
*/
|
||||
bool adrenotools_patch_bcn(void *vkGetPhysicalDeviceFormatPropertiesFn);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -10,60 +11,16 @@ extern "C" {
|
||||
#include <stdbool.h>
|
||||
#include "priv.h"
|
||||
|
||||
/**
|
||||
* @brief Opens a new libvulkan.so instance according to `flags`
|
||||
* @remark IMPORTANT: The app MUST be packaged w/ useLegacyPackaging = true. The reason for this is
|
||||
* that we install the hook in hookLibDir; but this folder won't be set correctly when useLegacyPackaging == false
|
||||
* because Android expects to read the *.so directly from the apk (instead of decompressing them into nativeLibraryDir).
|
||||
* @param dlopenMode The dlopen mode to use when opening libvulkan
|
||||
* @param featureFlags Which adrenotools driver features to enable
|
||||
* @param tmpLibDir A writable directory to hold patched libraries, only used on api < 29 due to the lack of memfd support. If nullptr is passed and the API version is < 29 memfd usage will be attempted and if unsupported nullptr will be returned
|
||||
* @param hookLibDir The directory holding the built hooks.
|
||||
* IMPORTANT: This path MUST point to whatever `getApplicationInfo().nativeLibraryDir` (Kotlin code) returns.
|
||||
* Failing to do so will result in this function returning a valid ptr but then the hook will fail and either the original driver is loaded as fallback, or vkEnumeratePhysicalDevices returns 0 devices.
|
||||
* @param customDriverDir The directory to load a custom GPU driver named according to `customDriverName` from. Only used if ADRENOTOOLS_DRIVER_CUSTOM is set in `featureFlags`.
|
||||
* IMPORTANT: This path MUST NOT be on sdcard/storage. i.e. use ANativeActivity::internalDataPath and often looks like "/data/user/0/com.example.app/files/".
|
||||
* Otherwise you will get permission denied because dlopen() can't be done on *.so libraries that any random user or application can manipulate (which would be a security risk).
|
||||
* @param customDriverName The soname of the custom driver to load. Only used if ADRENOTOOLS_DRIVER_CUSTOM is set in `featureFlags`
|
||||
* @param fileRedirectDir The directory which to redirect all file accesses performed by the driver to. Only used if ADRENOTOOLS_DRIVER_FILE_REDIRECT is set in `featureFlags`
|
||||
* @param userMappingHandle A pointer to a void* which will be set to the mapping handle if ADRENOTOOLS_DRIVER_GPU_MAPPING_IMPORT is set in `featureFlags`
|
||||
*/
|
||||
void *adrenotools_open_libvulkan(int dlopenMode, int featureFlags, const char *tmpLibDir, const char *hookLibDir, const char *customDriverDir, const char *customDriverName, const char *fileRedirectDir, void **userMappingHandle);
|
||||
|
||||
/**
|
||||
* @brief Imports the given CPU mapped memory range into the GSL allocator. This should then be followed by a call to vkAllocateMemory with a matching size which will return a VkDeviceMemory view over the input region
|
||||
* @param handle Mapping handle that was returned by adrenotools_open_libvulkan
|
||||
* @param hostPtr The host pointer to import
|
||||
* @param size The size of the region to import
|
||||
* @return True on success
|
||||
*/
|
||||
bool adrenotools_import_user_mem(void *handle, void *hostPtr, uint64_t size);
|
||||
|
||||
/**
|
||||
* @brief Maps GPU memory and imports it into the GSL allocator. This should then be followed by a call to adrenotools_mem_cpu_map to map the memory on the CPU, then finally vkAllocateMemory with a matching size
|
||||
* @param handle Mapping handle that was returned by adrenotools_open_libvulkan
|
||||
* @param size Pointer to a variable containing the size of the region to import, will be updated to contain the required size of the region to allocate CPU side
|
||||
* @return true on success
|
||||
*/
|
||||
bool adrenotools_mem_gpu_allocate(void *handle, uint64_t *size);
|
||||
|
||||
/**
|
||||
* @brief Maps the last mapping allocated using adrenotools_mem_gpu_allocate into the given host memory region, such that vkAllocateMemory can then be called
|
||||
* @param handle Mapping handle that was returned by adrenotools_open_libvulkan
|
||||
* @param hostPtr A pointer to where the mapping should be mapped
|
||||
* @param size The size of the mapping. MUST be equal to the size returned by adrenotools_mem_gpu_allocate
|
||||
*/
|
||||
bool adrenotools_mem_cpu_map(void *handle, void *hostPtr, uint64_t size);
|
||||
|
||||
/**
|
||||
* @note This function should be called after adrenotools_open_libvulkan and Vulkan driver init to check if the mapping import hook loaded successfully
|
||||
* @return True if the mapping was successfully imported (or initialization succeeded)
|
||||
*/
|
||||
bool adrenotools_validate_gpu_mapping(void *handle);
|
||||
|
||||
/**
|
||||
* @brief Provides a way to force the GPU to run at the maximum possible clocks (thermal constraints will still be applied)
|
||||
*/
|
||||
void adrenotools_set_turbo(bool turbo);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Bitfield enum of additional driver features that can be used with adrenotools_open_libvulkan
|
||||
*/
|
||||
enum {
|
||||
ADRENOTOOLS_DRIVER_CUSTOM = 1 << 0,
|
||||
ADRENOTOOLS_DRIVER_FILE_REDIRECT = 1 << 1,
|
||||
@@ -16,12 +14,9 @@ enum {
|
||||
|
||||
#define ADRENOTOOLS_GPU_MAPPING_SUCCEEDED_MAGIC 0xDEADBEEF
|
||||
|
||||
/**
|
||||
* @brief A replacement GPU memory mapping for use with ADRENOTOOLS_DRIVER_GPU_MAPPING_IMPORT
|
||||
*/
|
||||
struct adrenotools_gpu_mapping {
|
||||
void *host_ptr;
|
||||
uint64_t gpu_addr; //!< The GPU address of the mapping to import, if mapping import/init succeeds this will be set to ADRENOTOOLS_GPU_MAPPING_SUCCEEDED_MAGIC
|
||||
uint64_t gpu_addr;
|
||||
uint64_t size;
|
||||
uint64_t flags;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -21,7 +22,6 @@ static loader_android_create_namespace_t loader_android_create_namespace;
|
||||
static bool lib_loaded;
|
||||
|
||||
|
||||
/* Public API */
|
||||
bool linkernsbypass_load_status() {
|
||||
return lib_loaded;
|
||||
}
|
||||
@@ -55,8 +55,6 @@ android_link_namespaces_all_libs_t android_link_namespaces_all_libs;
|
||||
android_link_namespaces_t android_link_namespaces;
|
||||
|
||||
bool linkernsbypass_link_namespace_to_default_all_libs(android_namespace_t *to) {
|
||||
// Creating a shared namespace with the default parent will give a copy of the default namespace that we can actually access
|
||||
// This is needed since there is no way to access a direct handle to the default namespace as it's not exported
|
||||
static auto defaultNs{android_create_namespace_escape("default_copy", nullptr, nullptr, ANDROID_NAMESPACE_TYPE_SHARED, nullptr, nullptr)};
|
||||
return android_link_namespaces_all_libs(to, defaultNs);
|
||||
}
|
||||
@@ -81,7 +79,6 @@ void *linkernsbypass_namespace_dlopen(const char *filename, int flags, android_n
|
||||
void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *libTargetDir, int flags, android_namespace_t *ns) {
|
||||
static std::array<char, PATH_MAX> PathBuf{};
|
||||
|
||||
// Used as a unique ID for overwriting soname and creating target lib files
|
||||
static uint16_t TargetId{};
|
||||
|
||||
int libTargetFd{[&] () {
|
||||
@@ -89,7 +86,6 @@ void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *li
|
||||
snprintf(PathBuf.data(), PathBuf.size(), "%s/%d_patched.so", libTargetDir, TargetId);
|
||||
return open(PathBuf.data(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
|
||||
} else {
|
||||
// If memfd isn't supported errno will contain ENOSYS after calling
|
||||
errno = 0;
|
||||
int fd{static_cast<int>(syscall(__NR_memfd_create, libPath, 0))};
|
||||
if (errno == ENOSYS || fd < 0)
|
||||
@@ -101,21 +97,18 @@ void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *li
|
||||
if (libTargetFd == -1)
|
||||
return nullptr;
|
||||
|
||||
// Partially overwrite soname with 3 digits (replacing lib...) with to make sure a cached so isn't loaded
|
||||
std::array<char, 3> sonameOverwrite{};
|
||||
snprintf(sonameOverwrite.data(), sonameOverwrite.size(), "%03u", TargetId++);
|
||||
|
||||
if (!elf_soname_patch(libPath, libTargetFd, sonameOverwrite.data()))
|
||||
return nullptr;
|
||||
|
||||
// Load our patched library into the hook namespace
|
||||
android_dlextinfo hookExtInfo{
|
||||
.flags = ANDROID_DLEXT_USE_NAMESPACE | ANDROID_DLEXT_USE_LIBRARY_FD,
|
||||
.library_fd = libTargetFd,
|
||||
.library_namespace = ns
|
||||
};
|
||||
|
||||
// Make a path that looks about right
|
||||
snprintf(PathBuf.data(), PathBuf.size(), "/proc/self/fd/%d", libTargetFd);
|
||||
|
||||
return android_dlopen_ext(PathBuf.data(), flags, &hookExtInfo);
|
||||
@@ -125,21 +118,19 @@ static void *align_ptr(void *ptr) {
|
||||
return reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(ptr) & ~(getpagesize() - 1));
|
||||
}
|
||||
|
||||
/* Private */
|
||||
__attribute__((constructor)) static void resolve_linker_symbols() {
|
||||
using loader_dlopen_t = void *(*)(const char *, int, const void *);
|
||||
|
||||
if (android_get_device_api_level() < 28)
|
||||
return;
|
||||
|
||||
// ARM64 specific function walking to locate the internal dlopen handler
|
||||
auto loader_dlopen{[]() {
|
||||
union BranchLinked {
|
||||
uint32_t raw;
|
||||
|
||||
struct {
|
||||
int32_t offset : 26; //!< 26-bit branch offset
|
||||
uint8_t sig : 6; //!< 6-bit signature
|
||||
int32_t offset : 26;
|
||||
uint8_t sig : 6;
|
||||
};
|
||||
|
||||
bool Verify() {
|
||||
@@ -148,10 +139,8 @@ __attribute__((constructor)) static void resolve_linker_symbols() {
|
||||
};
|
||||
static_assert(sizeof(BranchLinked) == 4, "BranchLinked is wrong size");
|
||||
|
||||
// Some devices ship with --X mapping for exexecutables so work around that
|
||||
mprotect(align_ptr(reinterpret_cast<void *>(&dlopen)), getpagesize(), PROT_WRITE | PROT_READ | PROT_EXEC);
|
||||
|
||||
// dlopen is just a wrapper for __loader_dlopen that passes the return address as the third arg hence we can just walk it to find __loader_dlopen
|
||||
auto blInstr{reinterpret_cast<BranchLinked *>(&dlopen)};
|
||||
while (!blInstr->Verify())
|
||||
blInstr++;
|
||||
@@ -159,10 +148,8 @@ __attribute__((constructor)) static void resolve_linker_symbols() {
|
||||
return reinterpret_cast<loader_dlopen_t>(blInstr + blInstr->offset);
|
||||
}()};
|
||||
|
||||
// Protect the loader_dlopen function to remove the BTI attribute (since this is an internal function that isn't intended to be jumped indirectly to)
|
||||
mprotect(align_ptr(reinterpret_cast<void *>(&loader_dlopen)), getpagesize(), PROT_WRITE | PROT_READ | PROT_EXEC);
|
||||
|
||||
// Passing dlopen as a caller address tricks the linker into using the internal unrestricted namespace letting us access libraries that are normally forbidden in the classloader namespace imposed on apps
|
||||
auto ldHandle{loader_dlopen("ld-android.so", RTLD_LAZY, reinterpret_cast<void *>(&dlopen))};
|
||||
if (!ldHandle)
|
||||
return;
|
||||
@@ -187,6 +174,5 @@ __attribute__((constructor)) static void resolve_linker_symbols() {
|
||||
if (!android_get_exported_namespace)
|
||||
return;
|
||||
|
||||
// Lib is now safe to use
|
||||
lib_loaded = true;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -9,7 +10,6 @@ extern "C" {
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/+/0a492a4685377d41fef2b12e9af4ebfa6feef9c2:art/libnativeloader/include/nativeloader/dlext_namespaces.h;l=25;bpv=1;bpt=1
|
||||
enum {
|
||||
ANDROID_NAMESPACE_TYPE_REGULAR = 0,
|
||||
ANDROID_NAMESPACE_TYPE_ISOLATED = 1,
|
||||
@@ -19,14 +19,8 @@ enum {
|
||||
ANDROID_NAMESPACE_TYPE_SHARED_ISOLATED = ANDROID_NAMESPACE_TYPE_SHARED | ANDROID_NAMESPACE_TYPE_ISOLATED,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Checks if linkernsbypass loaded successfully and is safe to use
|
||||
* @note IMPORTANT: This should be called before any calls to the rest of the library are made
|
||||
* @return true if loading succeeded
|
||||
*/
|
||||
bool linkernsbypass_load_status();
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/+/0a492a4685377d41fef2b12e9af4ebfa6feef9c2:art/libnativeloader/include/nativeloader/dlext_namespaces.h;l=86;bpv=1;bpt=1
|
||||
struct android_namespace_t *android_create_namespace(const char *name,
|
||||
const char *ld_library_path,
|
||||
const char *default_library_path,
|
||||
@@ -41,39 +35,19 @@ struct android_namespace_t *android_create_namespace_escape(const char *name,
|
||||
const char *permitted_when_isolated_path,
|
||||
struct android_namespace_t *parent_namespace);
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/+/dcb01ef31026b3b8aeb72dada3370af63fe66bbd:bionic/linker/linker.cpp;l=3554
|
||||
typedef struct android_namespace_t *(*android_get_exported_namespace_t)(const char *);
|
||||
extern android_get_exported_namespace_t android_get_exported_namespace;
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/+/dcb01ef31026b3b8aeb72dada3370af63fe66bbd:bionic/linker/linker.cpp;l=2499
|
||||
typedef bool (*android_link_namespaces_all_libs_t)(struct android_namespace_t *, struct android_namespace_t *);
|
||||
extern android_link_namespaces_all_libs_t android_link_namespaces_all_libs;
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/+/dcb01ef31026b3b8aeb72dada3370af63fe66bbd:bionic/linker/linker.cpp;l=2473
|
||||
typedef bool (*android_link_namespaces_t)(struct android_namespace_t *, struct android_namespace_t *, const char *);
|
||||
extern android_link_namespaces_t android_link_namespaces;
|
||||
|
||||
/**
|
||||
* @brief Like android_link_namespaces_all_libs but links from the default namespace
|
||||
*/
|
||||
bool linkernsbypass_link_namespace_to_default_all_libs(struct android_namespace_t *to);
|
||||
|
||||
/**
|
||||
* @brief Loads a library into a namespace
|
||||
* @note IMPORTANT: If `filename` is compiled with the '-z global' linker flag and RTLD_GLOBAL is supplied in `flags` the library will be added to the namespace's LD_PRELOAD list
|
||||
* @param filename The name of the library to load
|
||||
* @param flags The rtld flags for `filename`
|
||||
* @param ns The namespace to dlopen into
|
||||
*/
|
||||
void *linkernsbypass_namespace_dlopen(const char *filename, int flags, struct android_namespace_t *ns);
|
||||
|
||||
/**
|
||||
* @brief Force loads a unique instance of a library into a namespace
|
||||
* @param libPath The path to the library to load with hooks applied
|
||||
* @param libTargetDir A temporary directory to hold the soname patched library at `libPath`, will attempt to use memfd if nullptr
|
||||
* @param flags The rtld flags for `libName`
|
||||
* @param ns The namespace to dlopen into
|
||||
*/
|
||||
void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *libTargetDir, int flags, struct android_namespace_t *ns);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#include <initializer_list>
|
||||
#include <cstdint>
|
||||
#include <errno.h>
|
||||
@@ -20,7 +21,6 @@ bool elf_soname_patch(const char *libPath, int targetFd, const char *sonamePatch
|
||||
if (ftruncate(targetFd, libStat.st_size) == -1)
|
||||
return false;
|
||||
|
||||
// Map the memory so we can read our elf into it
|
||||
auto mappedLib{reinterpret_cast<uint8_t *>(mmap(nullptr, libStat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, targetFd, 0))};
|
||||
if (!mappedLib)
|
||||
return false;
|
||||
@@ -29,30 +29,25 @@ bool elf_soname_patch(const char *libPath, int targetFd, const char *sonamePatch
|
||||
if (libFd == false)
|
||||
return false;
|
||||
|
||||
// Read lib elf into target file
|
||||
if (read(libFd, mappedLib, libStat.st_size) != libStat.st_size)
|
||||
return false;
|
||||
|
||||
// No longer needed
|
||||
close(libFd);
|
||||
|
||||
auto eHdr{reinterpret_cast<ElfW(Ehdr) *>(mappedLib)};
|
||||
auto sHdrEntries{reinterpret_cast<ElfW(Shdr) *>(mappedLib + eHdr->e_shoff)};
|
||||
|
||||
// Iterate over section headers to find the .dynamic section
|
||||
for (ElfW(Half) i{}; i < eHdr->e_shnum; i++) {
|
||||
auto &sHdr{sHdrEntries[i]};
|
||||
if (sHdr.sh_type == SHT_DYNAMIC) {
|
||||
auto strTab{reinterpret_cast<char *>(mappedLib + sHdrEntries[sHdr.sh_link].sh_offset)};
|
||||
auto dynHdrEntries{reinterpret_cast<ElfW(Dyn) *>(mappedLib + sHdr.sh_offset)};
|
||||
|
||||
// Iterate over .dynamic entries to find DT_SONAME
|
||||
for (ElfW(Xword) k{}; k < (sHdr.sh_size / sHdr.sh_entsize); k++) {
|
||||
auto &dynHdrEntry{dynHdrEntries[k]};
|
||||
if (dynHdrEntry.d_tag == DT_SONAME) {
|
||||
char *soname{strTab + dynHdrEntry.d_un.d_val};
|
||||
|
||||
// Partially replace the old soname with the soname patch
|
||||
size_t charIdx{};
|
||||
for (; soname[charIdx] != 0 && sonamePatch[charIdx] != 0; charIdx++)
|
||||
soname[charIdx] = sonamePatch[charIdx];
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Overwrites a portion of the soname in an elf by loading it into shared memory and modifying .dynstr
|
||||
* @note IMPORTANT: The supplied soname patch will overwrite the first strlen(sonamePatch) chars of the soname
|
||||
* @param elfPath Full path to the elf to patch
|
||||
* @param targetFd FD to use for storing the patched library
|
||||
* @return True on success
|
||||
*/
|
||||
bool elf_soname_patch(const char *elfPath, int targetFd, const char *newSoname);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -11,8 +11,3 @@ linkernsbypass_lib = static_library (
|
||||
include_directories: ([linkernsbypass_inc, adrenotools_inc]),
|
||||
dependencies: ([libandroid_dep, libdl_dep])
|
||||
)
|
||||
|
||||
# linkernsbypass_include = static_library('linkernsbypass', sources)
|
||||
# target_compile_options(['linkernsbypass', 'PRIVATE', '-Wall', '-Wextra'])
|
||||
# target_link_libraries(['linkernsbypass', 'android', 'dl'])
|
||||
# target_include_directories(['linkernsbypass', 'PUBLIC', '.'])
|
||||
@@ -33,18 +33,3 @@ adrenotools_lib = shared_library(
|
||||
)
|
||||
|
||||
subdir('src/hook')
|
||||
|
||||
# target_include_directories(['adrenotools', 'PUBLIC', 'include'])
|
||||
# target_include_directories(['adrenotools', 'PRIVATE', '.'])
|
||||
# target_compile_options(['adrenotools', 'PRIVATE', '-Wall', '-Wextra'])
|
||||
# target_link_libraries(['adrenotools', 'android', 'linkernsbypass'])
|
||||
# hooks_deb = subproject('src/hook')
|
||||
# if build_shared_libs
|
||||
# target_link_options(['adrenotools', 'PRIVATE', '-Wl,-s,--exclude-libs,liblinkernsbypass.a'])
|
||||
# endif
|
||||
# if gen_install_target
|
||||
# configure_file(['adrenotools.pc.in', 'adrenotools.pc', '@ONLY'])
|
||||
# install(['FILES', cmake_binary_dir, '/adrenotools.pc', 'DESTINATION', 'lib/pkgconfig'])
|
||||
# install(['TARGETS', 'adrenotools', 'main_hook', 'file_redirect_hook', 'gsl_alloc_hook', 'hook_impl'])
|
||||
# install(['DIRECTORY', 'include/', 'DESTINATION', 'include', 'FILES_MATCHING', 'PATTERN', '*.h*'])
|
||||
# endif
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
@@ -19,7 +20,6 @@ enum adrenotools_bcn_type adrenotools_get_bcn_type(uint32_t major, uint32_t mino
|
||||
return ADRENOTOOLS_BCN_PATCH;
|
||||
}
|
||||
|
||||
// Searches /proc/self/maps for the first free page after the given address
|
||||
static void *find_free_page(uintptr_t address) {
|
||||
std::ifstream procMaps("/proc/self/maps");
|
||||
|
||||
@@ -45,62 +45,48 @@ static void *align_ptr(void *ptr) {
|
||||
bool adrenotools_patch_bcn(void *vkGetPhysicalDeviceFormatPropertiesFn) {
|
||||
union Branch {
|
||||
struct {
|
||||
int32_t offset : 26; //!< 26-bit branch offset
|
||||
uint8_t sig : 6; //!< 6-bit signature (0x25 for linked, 0x5 for jump)
|
||||
int32_t offset : 26;
|
||||
uint8_t sig : 6;
|
||||
};
|
||||
|
||||
uint32_t raw{};
|
||||
};
|
||||
static_assert(sizeof(Branch) == 4, "Branch size is invalid");
|
||||
|
||||
// Find the nearest unmapped page where we can place patch code
|
||||
void *patchPage{find_free_page(reinterpret_cast<uintptr_t>(vkGetPhysicalDeviceFormatPropertiesFn))};
|
||||
if (!patchPage)
|
||||
return false;
|
||||
|
||||
// Map patch region
|
||||
void *ptr{mmap(patchPage, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0)};
|
||||
if (ptr != patchPage)
|
||||
return false;
|
||||
|
||||
// Allow reading from the blob's .text section since some devices enable ---X
|
||||
// Protect two pages just in case we happen to land on a page boundary
|
||||
if (mprotect(align_ptr(vkGetPhysicalDeviceFormatPropertiesFn), getpagesize() * 2, PROT_WRITE | PROT_READ | PROT_EXEC))
|
||||
return false;
|
||||
|
||||
// First branch in this function is targeted at the function we want to patch
|
||||
Branch *blInst{reinterpret_cast<Branch *>(vkGetPhysicalDeviceFormatPropertiesFn)};
|
||||
|
||||
constexpr uint8_t BranchLinkSignature{0x25};
|
||||
|
||||
// Search for first instruction with the BL signature
|
||||
while (blInst->sig != BranchLinkSignature)
|
||||
blInst++;
|
||||
|
||||
// Internal QGL format conversion function that we need to patch
|
||||
uint32_t *convFormatFn{reinterpret_cast<uint32_t *>(blInst) + blInst->offset};
|
||||
|
||||
// See mprotect call above
|
||||
// This time we also set PROT_WRITE so we can write our patch to the page
|
||||
if (mprotect(align_ptr(convFormatFn), getpagesize() * 2, PROT_WRITE | PROT_READ | PROT_EXEC))
|
||||
return false;
|
||||
|
||||
// This would normally set the default result to 0 (error) in the format not found case
|
||||
constexpr uint32_t ClearResultSignature{0x2a1f03e0};
|
||||
|
||||
// We replace it with a branch to our own extended if statement which adds in the extra things for BCn
|
||||
uint32_t *clearResultPtr{convFormatFn};
|
||||
while (*clearResultPtr != ClearResultSignature)
|
||||
clearResultPtr++;
|
||||
|
||||
// Ensure we don't write out of bounds
|
||||
if (PatchRawData_size > getpagesize())
|
||||
return false;
|
||||
|
||||
// Copy the patch function to our mapped page
|
||||
memcpy(patchPage, PatchRawData, PatchRawData_size);
|
||||
|
||||
// Fixup the patch code so it correctly returns back to the driver after running
|
||||
constexpr uint32_t PatchReturnFixupMagic{0xffffffff};
|
||||
constexpr uint8_t BranchSignature{0x5};
|
||||
|
||||
@@ -130,6 +116,5 @@ bool adrenotools_patch_bcn(void *vkGetPhysicalDeviceFormatPropertiesFn) {
|
||||
|
||||
asm volatile("ISB");
|
||||
|
||||
// Done!
|
||||
return true;
|
||||
}
|
||||
@@ -80,4 +80,4 @@
|
||||
.L18:
|
||||
mov w0, 0
|
||||
.L3:
|
||||
.word 0xffffffff // Branch fixup
|
||||
.word 0xffffffff
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/stat.h>
|
||||
@@ -17,17 +18,14 @@
|
||||
#include <unistd.h>
|
||||
|
||||
void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *tmpLibDir, const char *hookLibDir, const char *customDriverDir, const char *customDriverName, const char *fileRedirectDir, void **userMappingHandle) {
|
||||
// Bail out if linkernsbypass failed to load, this probably means we're on api < 28
|
||||
if (!linkernsbypass_load_status()) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Could not load linkernsbypass\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Use memfd on Q+ since it's guaranteed to work, but only if tmpLibDir wasn't set
|
||||
if (android_get_device_api_level() >= 29 && !tmpLibDir)
|
||||
tmpLibDir = nullptr;
|
||||
|
||||
// Verify that params for specific features are only passed if they are enabled
|
||||
if (!(featureFlags & ADRENOTOOLS_DRIVER_FILE_REDIRECT) && fileRedirectDir) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "ADRENOTOOLS_DRIVER_FILE_REDIRECT present but no file redirect folder found\n");
|
||||
return nullptr;
|
||||
@@ -43,7 +41,6 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Verify that params for enabled features are correct
|
||||
struct stat buf{};
|
||||
|
||||
if (featureFlags & ADRENOTOOLS_DRIVER_CUSTOM) {
|
||||
@@ -58,7 +55,6 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that params for enabled features are correct
|
||||
if (featureFlags & ADRENOTOOLS_DRIVER_FILE_REDIRECT) {
|
||||
if (!fileRedirectDir) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "ADRENOTOOLS_DRIVER_REDIRECT_DIR present but no folder parameter was found\n");
|
||||
@@ -71,22 +67,18 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
|
||||
}
|
||||
}
|
||||
|
||||
// Create a namespace that can isolate our hook from the classloader namespace
|
||||
auto hookNs{android_create_namespace("adrenotools-libvulkan", hookLibDir, nullptr, ANDROID_NAMESPACE_TYPE_SHARED, nullptr, nullptr)};
|
||||
|
||||
// Link it to the default namespace so the hook can use libandroid etc
|
||||
if (!linkernsbypass_link_namespace_to_default_all_libs(hookNs)) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Failed to hook our namespace to the default one\n");
|
||||
return nullptr;
|
||||
}
|
||||
// Preload the hook implementation, otherwise we get a weird issue where despite being in NEEDED of the hook lib the hook's symbols will overwrite ours and cause an infinite loop
|
||||
auto hookImpl{linkernsbypass_namespace_dlopen("libhook_impl.so", RTLD_NOW, hookNs)};
|
||||
if (!hookImpl) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Couldn't preload the hook implementation\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Pass parameters to the hook implementation
|
||||
auto initHookParam{reinterpret_cast<void (*)(const void *)>(dlsym(hookImpl, "init_hook_param"))};
|
||||
if (!initHookParam) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Couldn't init hook params\n");
|
||||
@@ -95,7 +87,6 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
|
||||
|
||||
auto importMapping{[&]() -> adrenotools_gpu_mapping * {
|
||||
if (featureFlags & ADRENOTOOLS_DRIVER_GPU_MAPPING_IMPORT) {
|
||||
// This will be leaked, but it's not a big deal since it's only a few bytes
|
||||
adrenotools_gpu_mapping *mapping{new adrenotools_gpu_mapping{}};
|
||||
*userMappingHandle = mapping;
|
||||
return mapping;
|
||||
@@ -107,7 +98,6 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
|
||||
|
||||
initHookParam(new HookImplParams(featureFlags, tmpLibDir, hookLibDir, customDriverDir, customDriverName, fileRedirectDir, importMapping));
|
||||
|
||||
// Load the libvulkan hook into the isolated namespace
|
||||
if (!linkernsbypass_namespace_dlopen("libmain_hook.so", RTLD_GLOBAL, hookNs)) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Failed to load libvulkan into the isolated namespace\n");
|
||||
return nullptr;
|
||||
@@ -150,7 +140,7 @@ bool adrenotools_import_user_mem(void *handle, void *hostPtr, uint64_t size) {
|
||||
importMapping->host_ptr = hostPtr;
|
||||
importMapping->gpu_addr = info.gpuaddr;
|
||||
importMapping->size = size;
|
||||
importMapping->flags = 0xc2600; //!< Unknown flags, but they are required for the mapping to work
|
||||
importMapping->flags = 0xc2600;
|
||||
|
||||
close(kgslFd);
|
||||
return true;
|
||||
@@ -189,7 +179,7 @@ bool adrenotools_mem_gpu_allocate(void *handle, uint64_t *size) {
|
||||
mapping->host_ptr = nullptr;
|
||||
mapping->gpu_addr = info.gpuaddr;
|
||||
mapping->size = *size;
|
||||
mapping->flags = 0xc2600; //!< Unknown flags, but they are required for the mapping to work
|
||||
mapping->flags = 0xc2600;
|
||||
|
||||
close(kgslFd);
|
||||
return true;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#define TAG "hook_impl"
|
||||
#define LOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##__VA_ARGS__)
|
||||
|
||||
const HookImplParams *hook_params; //!< Bunch of info needed to load/patch the driver
|
||||
const HookImplParams *hook_params;
|
||||
int (*gsl_memory_alloc_pure_sym)(uint32_t, uint32_t, void *);
|
||||
int (*gsl_memory_alloc_pure_64_sym)(uint64_t, uint32_t, void *);
|
||||
int (*gsl_memory_free_pure_sym)(void *);
|
||||
@@ -43,7 +43,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
|
||||
|
||||
LOGI("hook_android_dlopen_ext: filename: %s", filename);
|
||||
|
||||
// Ignore non-vulkan libraries
|
||||
if (!strstr(filename, "vulkan."))
|
||||
return android_dlopen_ext(filename, flags, extinfo);
|
||||
|
||||
@@ -52,7 +51,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
|
||||
return fallback();
|
||||
}
|
||||
|
||||
// customDriverDir will be empty if ADRENOTOOLS_DRIVER_CUSTOM isn't set therefore it's fine to have either way
|
||||
auto driverNs{android_create_namespace(filename, hook_params->customDriverDir.c_str(),
|
||||
hook_params->hookLibDir.c_str(), ANDROID_NAMESPACE_TYPE_SHARED,
|
||||
nullptr, extinfo->library_namespace)};
|
||||
@@ -61,16 +59,12 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
|
||||
return fallback();
|
||||
}
|
||||
|
||||
// We depend on libandroid which is unlikely to be in the supplied driver namespace so we have to link it over
|
||||
android_link_namespaces(driverNs, nullptr, "libandroid.so");
|
||||
|
||||
// Preload ourself, a new instance will be created since we have different linker ancestory
|
||||
// If we don't preload we get a weird issue where despite being in NEEDED of the hook lib the hook's symbols will overwrite ours and cause an infinite loop
|
||||
auto hookImpl{linkernsbypass_namespace_dlopen("libhook_impl.so", RTLD_NOW, driverNs)};
|
||||
if (!hookImpl)
|
||||
return nullptr;
|
||||
|
||||
// Pass parameters to ourself
|
||||
auto initHookParam{reinterpret_cast<void (*)(const void *)>(dlsym(hookImpl, "init_hook_param"))};
|
||||
if (!initHookParam)
|
||||
return nullptr;
|
||||
@@ -86,7 +80,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
|
||||
LOGI("hook_android_dlopen_ext: applied libfile_redirect_hook");
|
||||
}
|
||||
|
||||
// Use our new namespace to load the vulkan driver
|
||||
auto newExtinfo{*extinfo};
|
||||
newExtinfo.library_namespace = driverNs;
|
||||
|
||||
@@ -122,8 +115,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
|
||||
LOGI("hook_android_dlopen_ext: hook failed: failed to apply libgsl_alloc_hook!");
|
||||
}
|
||||
|
||||
// TODO: If there is already an instance of a Vulkan driver loaded hooks won't be applied, this will only be the case for skiavk generally
|
||||
// To fix this we would need to search /proc/self/maps for the file to a loaded instance of the library in order to read it to patch the soname and load it uniquely
|
||||
if (hook_params->featureFlags & ADRENOTOOLS_DRIVER_CUSTOM) {
|
||||
LOGI("hook_android_dlopen_ext: loading custom driver: %s%s", hook_params->customDriverDir.c_str(), hook_params->customDriverName.c_str());
|
||||
void *handle{android_dlopen_ext(hook_params->customDriverName.c_str(), flags, &newExtinfo)};
|
||||
@@ -142,7 +133,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
|
||||
__attribute__((visibility("default"))) void *hook_android_load_sphal_library(const char *filename, int flags) {
|
||||
LOGI("hook_android_load_sphal_library: filename: %s", filename);
|
||||
|
||||
// https://android.googlesource.com/platform/system/core/+/master/libvndksupport/linker.cpp
|
||||
for (const char *name : {"sphal", "vendor", "default"}) {
|
||||
if (auto vendorNs{android_get_exported_namespace(name)}) {
|
||||
android_dlextinfo dlextinfo{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <adrenotools/priv.h>
|
||||
|
||||
/**
|
||||
* @brief Holds the parameters needed for all hooks
|
||||
* @note See comments for adrenotools_open_libvulkan as a reference for member variables
|
||||
*/
|
||||
struct HookImplParams {
|
||||
int featureFlags;
|
||||
std::string tmpLibDir;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,32 +1,12 @@
|
||||
hook_impl_files = files(['hook_impl.cpp', 'hook_impl.h', 'hook_impl_params.h'])
|
||||
hook_impl_inc = include_directories('../../include')
|
||||
|
||||
# hook_impl_lib = shared_library('hook_impl', 'hook_impl.cpp', 'hook_impl.h', 'hook_impl_params.h')
|
||||
# target_compile_options(['hook_impl', 'PRIVATE', '-Wall', '-Wextra'])
|
||||
# target_link_libraries(['hook_impl', 'linkernsbypass', 'log'])
|
||||
# target_include_directories(['hook_impl', 'PRIVATE', '../../include'])
|
||||
# set_target_properties(['hook_impl', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
|
||||
|
||||
main_hook_files = files(['main_hook.c'])
|
||||
# main_hook_lib = shared_library('main_hook', 'main_hook.c')
|
||||
# target_compile_options(['main_hook', 'PRIVATE', '-Wall', '-Wextra'])
|
||||
# target_link_options(['main_hook', 'PRIVATE', '-z', 'global'])
|
||||
# target_link_libraries(['main_hook', 'hook_impl'])
|
||||
# set_target_properties(['main_hook', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
|
||||
|
||||
file_redirect_hook_files = files(['file_redirect_hook.c'])
|
||||
# file_redirect_hook_lib = shared_library('file_redirect_hook', 'file_redirect_hook.c')
|
||||
# target_compile_options(['file_redirect_hook', 'PRIVATE', '-Wall', '-Wextra'])
|
||||
# target_link_options(['file_redirect_hook', 'PRIVATE', '-z', 'global'])
|
||||
# target_link_libraries(['file_redirect_hook', 'hook_impl'])
|
||||
# set_target_properties(['file_redirect_hook', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
|
||||
|
||||
gsl_alloc_hook_files = files(['gsl_alloc_hook.c'])
|
||||
# gsl_alloc_hook_lib = shared_library('gsl_alloc_hook', 'gsl_alloc_hook.c')
|
||||
# target_compile_options(['gsl_alloc_hook', 'PRIVATE', '-Wall', '-Wextra'])
|
||||
# target_link_options(['gsl_alloc_hook', 'PRIVATE', '-z', 'global'])
|
||||
# target_link_libraries(['gsl_alloc_hook', 'hook_impl'])
|
||||
# set_target_properties(['gsl_alloc_hook', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
|
||||
|
||||
hook_impl_lib = shared_library(
|
||||
'hook_impl',
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
thread_local int satisfy_driver_emutls;
|
||||
|
||||
// Check libllvm-glnext.so in older drivers for a full list of these, newer ones are slightly more awkward due to the split
|
||||
enum class OptionType : uint32_t {
|
||||
ArgsStringList = 1,
|
||||
QcArgsString = 4,
|
||||
@@ -21,7 +20,6 @@ struct StringListVal {
|
||||
struct Option
|
||||
{
|
||||
OptionType type;
|
||||
// PAD
|
||||
union {
|
||||
bool boolVal;
|
||||
int intVal;
|
||||
@@ -49,7 +47,6 @@ int CompileHook(void *state, OptionsSet *opts) {
|
||||
Option replacementOpts[ReplacementOptsNum];
|
||||
uint32_t patchedOptNum{};
|
||||
|
||||
// Insert your desired args here - results will be written to stderr
|
||||
const char *argv[] = {"-help", "-help-hidden", "", NULL };
|
||||
StringListVal listVal{
|
||||
.len = 3,
|
||||
@@ -83,7 +80,6 @@ int CompileHook(void *state, OptionsSet *opts) {
|
||||
|
||||
struct LlvmInterface {
|
||||
uint32_t unk0[3];
|
||||
// PAD
|
||||
void *makeContext;
|
||||
void *delContext;
|
||||
decltype(CompileHook) *compile;
|
||||
|
||||
@@ -30,53 +30,31 @@
|
||||
#ifndef __QTIGRALLOCDEFS_H__
|
||||
#define __QTIGRALLOCDEFS_H__
|
||||
|
||||
// From gralloc_priv.h
|
||||
|
||||
/* Gralloc usage bits indicating the type of allocation that should be used */
|
||||
/* Refer to BufferUsage in hardware/interfaces/graphics/common/<ver>/types.hal */
|
||||
|
||||
/* The bits below are in officially defined vendor space
|
||||
* i.e bits 28-31 and 48-63*/
|
||||
/* Non linear, Universal Bandwidth Compression */
|
||||
#define GRALLOC_USAGE_PRIVATE_ALLOC_UBWC (UINT32_C(1) << 28)
|
||||
|
||||
/* Set this for allocating uncached memory (using O_DSYNC),
|
||||
* cannot be used with noncontiguous heaps */
|
||||
#define GRALLOC_USAGE_PRIVATE_UNCACHED (UINT32_C(1) << 29)
|
||||
|
||||
/* This flag is used to indicate 10 bit format.
|
||||
* When both GRALLOC_USAGE_PRIVATE_ALLOC_UBWC & GRALLOC_USAGE_PRIVATE_10BIT
|
||||
* are set then it will indicate UBWC_TP10 format.
|
||||
* When only GRALLOC_USAGE_PRIVATE_10BIT is set it will indicate linear P010 format.
|
||||
*/
|
||||
#define GRALLOC_USAGE_PRIVATE_10BIT (UINT32_C(1) << 30)
|
||||
|
||||
/* This flag is used for SECURE display usecase */
|
||||
#define GRALLOC_USAGE_PRIVATE_SECURE_DISPLAY (UINT32_C(1) << 31)
|
||||
|
||||
/* This flag is used to indicate video NV21 format */
|
||||
#define GRALLOC_USAGE_PRIVATE_VIDEO_NV21_ENCODER 1ULL << 48
|
||||
|
||||
/* unused legacy flags */
|
||||
#define GRALLOC_USAGE_PRIVATE_MM_HEAP 0
|
||||
#define GRALLOC_USAGE_PRIVATE_IOMMU_HEAP 0
|
||||
|
||||
/* This flag is set for WFD usecase */
|
||||
#define GRALLOC_USAGE_PRIVATE_WFD 1ULL << 51
|
||||
|
||||
/* This flag is set for HEIF usecase */
|
||||
#define GRALLOC_USAGE_PRIVATE_HEIF (UINT32_C(1) << 27)
|
||||
|
||||
/* TODO(user): Remove when clients stop referencing this flag */
|
||||
#define GRALLOC_USAGE_PRIVATE_10BIT_TP 0
|
||||
|
||||
/* This flag indicates PI format is being used */
|
||||
#define GRALLOC_USAGE_PRIVATE_ALLOC_UBWC_PI 1ULL << 49
|
||||
|
||||
/* This flag is set while CDSP accesses the buffer */
|
||||
#define GRALLOC_USAGE_PRIVATE_CDSP 1ULL << 50
|
||||
|
||||
// OEM specific HAL formats
|
||||
#define HAL_PIXEL_FORMAT_RGBA_5551 6
|
||||
#define HAL_PIXEL_FORMAT_RGBA_4444 7
|
||||
#define HAL_PIXEL_FORMAT_NV12_ENCODEABLE 0x102
|
||||
@@ -98,7 +76,6 @@
|
||||
#define HAL_PIXEL_FORMAT_RAW8 0x123
|
||||
#define HAL_PIXEL_FORMAT_NV12_HEIF 0x116
|
||||
|
||||
// 10 bit
|
||||
#define HAL_PIXEL_FORMAT_ARGB_2101010 0x117
|
||||
#define HAL_PIXEL_FORMAT_RGBX_1010102 0x118
|
||||
#define HAL_PIXEL_FORMAT_XRGB_2101010 0x119
|
||||
@@ -106,7 +83,7 @@
|
||||
#define HAL_PIXEL_FORMAT_ABGR_2101010 0x11B
|
||||
#define HAL_PIXEL_FORMAT_BGRX_1010102 0x11C
|
||||
#define HAL_PIXEL_FORMAT_XBGR_2101010 0x11D
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_420_P010 0x36 // HAL_PIXEL_FORMAT_YCBCR_P010
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_420_P010 0x36
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_420_P010_UBWC 0x124
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_420_P010_VENUS 0x7FA30C0A
|
||||
|
||||
@@ -115,7 +92,6 @@
|
||||
|
||||
#define HAL_PIXEL_FORMAT_INTERLACE 0x180
|
||||
|
||||
// Camera utils format
|
||||
#define HAL_PIXEL_FORMAT_NV12_LINEAR_FLEX 0x125
|
||||
#define HAL_PIXEL_FORMAT_NV12_UBWC_FLEX 0x126
|
||||
#define HAL_PIXEL_FORMAT_MULTIPLANAR_FLEX 0x127
|
||||
@@ -123,22 +99,16 @@
|
||||
#define HAL_PIXEL_FORMAT_NV12_UBWC_FLEX_4_BATCH 0x129
|
||||
#define HAL_PIXEL_FORMAT_NV12_UBWC_FLEX_8_BATCH 0x130
|
||||
|
||||
// FSC Formats
|
||||
#define HAL_PIXEL_FORMAT_RGB888_UBWC_FSC 0x131
|
||||
#define HAL_PIXEL_FORMAT_RGB101010_UBWC_FSC 0x132
|
||||
|
||||
// v4l2_fourcc('Y', 'U', 'Y', 'L'). 24 bpp YUYV 4:2:2 10 bit per component
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_422_I_10BIT 0x4C595559
|
||||
|
||||
// v4l2_fourcc('Y', 'B', 'W', 'C'). 10 bit per component. This compressed
|
||||
// format reduces the memory access bandwidth
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_422_I_10BIT_COMPRESSED 0x43574259
|
||||
|
||||
// UBWC aligned Venus format
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC 0x7FA30C06
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_420_TP10_UBWC 0x7FA30C09
|
||||
|
||||
// Khronos ASTC formats
|
||||
#define HAL_PIXEL_FORMAT_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0
|
||||
#define HAL_PIXEL_FORMAT_COMPRESSED_RGBA_ASTC_5x4_KHR 0x93B1
|
||||
#define HAL_PIXEL_FORMAT_COMPRESSED_RGBA_ASTC_5x5_KHR 0x93B2
|
||||
@@ -170,4 +140,4 @@
|
||||
|
||||
enum { BUFFER_TYPE_UI = 0, BUFFER_TYPE_VIDEO };
|
||||
|
||||
#endif //__QTIGRALLOCDEFS_H__
|
||||
#endif
|
||||
@@ -26,7 +26,6 @@
|
||||
using namespace android::hardware;
|
||||
using namespace android;
|
||||
|
||||
/* Simplified version of hidl::base::V1_0::IBase */
|
||||
struct IBase : virtual public RefBase {
|
||||
virtual bool isRemote() const;
|
||||
|
||||
|
||||
@@ -80,8 +80,6 @@ AdrenoMemInfo::AdrenoMemInfo() {
|
||||
ALOGE(" Failed to load libadreno_utils.so");
|
||||
}
|
||||
|
||||
// Check if the overriding property debug.gralloc.gfx_ubwc_disable
|
||||
// that disables UBWC allocations for the graphics stack is set
|
||||
char property[PROPERTY_VALUE_MAX];
|
||||
property_get(DISABLE_UBWC_PROP, property, "0");
|
||||
if (!(strncmp(property, "1", PROPERTY_VALUE_MAX)) ||
|
||||
@@ -123,16 +121,13 @@ void AdrenoMemInfo::AlignUnCompressedRGB(int width, int height, int format, int
|
||||
break;
|
||||
}
|
||||
|
||||
int raster_mode = 0; // Adreno unknown raster mode.
|
||||
int padding_threshold = 512; // Threshold for padding surfaces.
|
||||
// the function below computes aligned width and aligned height
|
||||
// based on linear or macro tile mode selected.
|
||||
int raster_mode = 0;
|
||||
int padding_threshold = 512;
|
||||
|
||||
|
||||
if (LINK_adreno_compute_fmt_aligned_width_and_height) {
|
||||
// We call into adreno_utils only for RGB formats. So plane_id is 0 and
|
||||
// num_samples is 1 always. We may have to add uitility function to
|
||||
// find out these if there is a need to call this API for YUV formats.
|
||||
LINK_adreno_compute_fmt_aligned_width_and_height(
|
||||
width, height, 0 /*plane_id*/, GetGpuPixelFormat(format), 1 /*num_samples*/, tile_enabled,
|
||||
width, height, 0 , GetGpuPixelFormat(format), 1 , tile_enabled,
|
||||
raster_mode, padding_threshold, reinterpret_cast<int*>(aligned_w),
|
||||
reinterpret_cast<int*>(aligned_h));
|
||||
} else if (LINK_adreno_compute_aligned_width_and_height) {
|
||||
@@ -140,7 +135,7 @@ void AdrenoMemInfo::AlignUnCompressedRGB(int width, int height, int format, int
|
||||
width, height, bpp, tile_enabled, raster_mode, padding_threshold,
|
||||
reinterpret_cast<int*>(aligned_w), reinterpret_cast<int*>(aligned_h));
|
||||
} else if (LINK_adreno_compute_padding) {
|
||||
int surface_tile_height = 1; // Linear surface
|
||||
int surface_tile_height = 1;
|
||||
*aligned_w = UINT(LINK_adreno_compute_padding(width, bpp, surface_tile_height, raster_mode,
|
||||
padding_threshold));
|
||||
ALOGW("%s: Warning!! Old GFX API is used to calculate stride", __FUNCTION__);
|
||||
@@ -157,9 +152,9 @@ void AdrenoMemInfo::AlignCompressedRGB(int width, int height, int format, unsign
|
||||
unsigned int* aligned_h) {
|
||||
if (LINK_adreno_compute_compressedfmt_aligned_width_and_height) {
|
||||
int bytesPerPixel = 0;
|
||||
int raster_mode = 0; // Adreno unknown raster mode.
|
||||
int padding_threshold = 512; // Threshold for padding
|
||||
// surfaces.
|
||||
int raster_mode = 0;
|
||||
int padding_threshold = 512;
|
||||
|
||||
|
||||
LINK_adreno_compute_compressedfmt_aligned_width_and_height(
|
||||
width, height, format, 0, raster_mode, padding_threshold, reinterpret_cast<int*>(aligned_w),
|
||||
@@ -341,4 +336,4 @@ bool AdrenoMemInfo::IsPISupportedByGPU(int format, uint64_t usage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace gralloc
|
||||
}
|
||||
@@ -35,11 +35,10 @@
|
||||
|
||||
namespace gralloc {
|
||||
|
||||
// Adreno Pixel Formats
|
||||
typedef enum {
|
||||
ADRENO_PIXELFORMAT_UNKNOWN = 0,
|
||||
ADRENO_PIXELFORMAT_R16G16B16A16_FLOAT = 10,
|
||||
ADRENO_PIXELFORMAT_R10G10B10A2_UNORM = 24, // Vertex, Normalized GL_UNSIGNED_INT_10_10_10_2_OES
|
||||
ADRENO_PIXELFORMAT_R10G10B10A2_UNORM = 24,
|
||||
ADRENO_PIXELFORMAT_R8G8B8A8 = 28,
|
||||
ADRENO_PIXELFORMAT_R8G8B8A8_SRGB = 29,
|
||||
ADRENO_PIXELFORMAT_D32_FLOAT = 40,
|
||||
@@ -55,140 +54,87 @@ typedef enum {
|
||||
ADRENO_PIXELFORMAT_P010 = 104,
|
||||
ADRENO_PIXELFORMAT_YUY2 = 107,
|
||||
ADRENO_PIXELFORMAT_B4G4R4A4 = 115,
|
||||
ADRENO_PIXELFORMAT_NV12_EXT = 506, // NV12 with non-std alignment and offsets
|
||||
ADRENO_PIXELFORMAT_R8G8B8X8 = 507, // GL_RGB8 (Internal)
|
||||
ADRENO_PIXELFORMAT_R8G8B8 = 508, // GL_RGB8
|
||||
ADRENO_PIXELFORMAT_A1B5G5R5 = 519, // GL_RGB5_A1
|
||||
ADRENO_PIXELFORMAT_R8G8B8X8_SRGB = 520, // GL_SRGB8
|
||||
ADRENO_PIXELFORMAT_R8G8B8_SRGB = 521, // GL_SRGB8
|
||||
ADRENO_PIXELFORMAT_NV12_EXT = 506,
|
||||
ADRENO_PIXELFORMAT_R8G8B8X8 = 507,
|
||||
ADRENO_PIXELFORMAT_R8G8B8 = 508,
|
||||
ADRENO_PIXELFORMAT_A1B5G5R5 = 519,
|
||||
ADRENO_PIXELFORMAT_R8G8B8X8_SRGB = 520,
|
||||
ADRENO_PIXELFORMAT_R8G8B8_SRGB = 521,
|
||||
ADRENO_PIXELFORMAT_A2B10G10R10_UNORM = 532,
|
||||
// Vertex, Normalized GL_UNSIGNED_INT_10_10_10_2_OES
|
||||
ADRENO_PIXELFORMAT_R10G10B10X2_UNORM = 537,
|
||||
ADRENO_PIXELFORMAT_D24_UNORM_X8_UINT = 548,
|
||||
ADRENO_PIXELFORMAT_D24_UNORM = 549,
|
||||
ADRENO_PIXELFORMAT_D32_FLOAT_X24S8_UINT = 551,
|
||||
ADRENO_PIXELFORMAT_S8_UINT = 552,
|
||||
ADRENO_PIXELFORMAT_ASTC_4X4 = 568, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_5X4 = 569, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_5X5 = 570, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_6X5 = 571, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_6X6 = 572, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_8X5 = 573, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_8X6 = 574, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_8X8 = 575, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X5 = 576, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X6 = 577, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X8 = 578, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X10 = 579, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_12X10 = 580, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_12X12 = 581, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_4X4_SRGB = 582, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_5X4_SRGB = 583, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_5X5_SRGB = 584, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_6X5_SRGB = 585, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_6X6_SRGB = 586, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_8X5_SRGB = 587, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_8X6_SRGB = 588, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_8X8_SRGB = 589, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X5_SRGB = 590, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X6_SRGB = 591, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X8_SRGB = 592, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X10_SRGB = 593, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_12X10_SRGB = 594, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_12X12_SRGB = 595, // ASTC Compressed
|
||||
// Vertex, Normalized GL_UNSIGNED_INT_10_10_10_2_OES
|
||||
ADRENO_PIXELFORMAT_R5G6B5 = 610, // RGBA version of B5G6R5
|
||||
ADRENO_PIXELFORMAT_R5G5B5A1 = 611, // RGBA version of B5G5R5A1
|
||||
ADRENO_PIXELFORMAT_R4G4B4A4 = 612, // RGBA version of B4G4R4A4
|
||||
ADRENO_PIXELFORMAT_UYVY = 614, // YUV 4:2:2 packed progressive (1 plane)
|
||||
ADRENO_PIXELFORMAT_ASTC_4X4 = 568,
|
||||
ADRENO_PIXELFORMAT_ASTC_5X4 = 569,
|
||||
ADRENO_PIXELFORMAT_ASTC_5X5 = 570,
|
||||
ADRENO_PIXELFORMAT_ASTC_6X5 = 571,
|
||||
ADRENO_PIXELFORMAT_ASTC_6X6 = 572,
|
||||
ADRENO_PIXELFORMAT_ASTC_8X5 = 573,
|
||||
ADRENO_PIXELFORMAT_ASTC_8X6 = 574,
|
||||
ADRENO_PIXELFORMAT_ASTC_8X8 = 575,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X5 = 576,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X6 = 577,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X8 = 578,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X10 = 579,
|
||||
ADRENO_PIXELFORMAT_ASTC_12X10 = 580,
|
||||
ADRENO_PIXELFORMAT_ASTC_12X12 = 581,
|
||||
ADRENO_PIXELFORMAT_ASTC_4X4_SRGB = 582,
|
||||
ADRENO_PIXELFORMAT_ASTC_5X4_SRGB = 583,
|
||||
ADRENO_PIXELFORMAT_ASTC_5X5_SRGB = 584,
|
||||
ADRENO_PIXELFORMAT_ASTC_6X5_SRGB = 585,
|
||||
ADRENO_PIXELFORMAT_ASTC_6X6_SRGB = 586,
|
||||
ADRENO_PIXELFORMAT_ASTC_8X5_SRGB = 587,
|
||||
ADRENO_PIXELFORMAT_ASTC_8X6_SRGB = 588,
|
||||
ADRENO_PIXELFORMAT_ASTC_8X8_SRGB = 589,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X5_SRGB = 590,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X6_SRGB = 591,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X8_SRGB = 592,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X10_SRGB = 593,
|
||||
ADRENO_PIXELFORMAT_ASTC_12X10_SRGB = 594,
|
||||
ADRENO_PIXELFORMAT_ASTC_12X12_SRGB = 595,
|
||||
|
||||
ADRENO_PIXELFORMAT_R5G6B5 = 610,
|
||||
ADRENO_PIXELFORMAT_R5G5B5A1 = 611,
|
||||
ADRENO_PIXELFORMAT_R4G4B4A4 = 612,
|
||||
ADRENO_PIXELFORMAT_UYVY = 614,
|
||||
ADRENO_PIXELFORMAT_NV21 = 619,
|
||||
ADRENO_PIXELFORMAT_Y8U8V8A8 = 620, // YUV 4:4:4 packed (1 plane)
|
||||
ADRENO_PIXELFORMAT_Y8 = 625, // Single 8-bit luma only channel YUV format
|
||||
ADRENO_PIXELFORMAT_TP10 = 654, // YUV 4:2:0 planar 10 bits/comp (2 planes)
|
||||
ADRENO_PIXELFORMAT_Y8U8V8A8 = 620,
|
||||
ADRENO_PIXELFORMAT_Y8 = 625,
|
||||
ADRENO_PIXELFORMAT_TP10 = 654,
|
||||
} ADRENOPIXELFORMAT;
|
||||
|
||||
typedef enum {
|
||||
SURFACE_TILE_MODE_DISABLE = 0x0, // used for linear surface
|
||||
SURFACE_TILE_MODE_ENABLE = 0x1 // used for tiled surface
|
||||
SURFACE_TILE_MODE_DISABLE = 0x0,
|
||||
SURFACE_TILE_MODE_ENABLE = 0x1
|
||||
} surface_tile_mode_t;
|
||||
|
||||
class AdrenoMemInfo {
|
||||
public:
|
||||
/*
|
||||
* Function to compute aligned width and aligned height based on
|
||||
* width, height, format and usage flags.
|
||||
*
|
||||
* @return aligned width, aligned height
|
||||
*/
|
||||
void GetAlignedWidthAndHeight(int width, int height, int format, int usage,
|
||||
unsigned int* aligned_w, unsigned int* aligned_h, bool ubwc_enabled,
|
||||
bool tile_enabled);
|
||||
|
||||
/*
|
||||
* Function to compute the adreno aligned width and aligned height
|
||||
* based on the width and format.
|
||||
*
|
||||
* @return aligned width, aligned height
|
||||
*/
|
||||
void AlignUnCompressedRGB(int width, int height, int format, int tileEnabled,
|
||||
unsigned int* aligned_w, unsigned int* aligned_h);
|
||||
|
||||
/*
|
||||
* Function to compute the adreno aligned width and aligned height
|
||||
* based on the width and format.
|
||||
*
|
||||
* @return aligned width, aligned height
|
||||
*/
|
||||
void AlignCompressedRGB(int width, int height, int format, unsigned int* aligned_w,
|
||||
unsigned int* aligned_h);
|
||||
|
||||
/*
|
||||
* Function to compute the pixel alignment requirement.
|
||||
*
|
||||
* @return alignment
|
||||
*/
|
||||
uint32_t GetGpuPixelAlignment();
|
||||
|
||||
/*
|
||||
* Function to query whether GPU supports UBWC for given HAL format
|
||||
* @return > 0 : supported
|
||||
* 0 : not supported
|
||||
*/
|
||||
bool IsUBWCSupportedByGPU(int format);
|
||||
|
||||
/*
|
||||
* Function to check if GPU supports PI or not
|
||||
*/
|
||||
bool IsPISupportedByGPU(int format, uint64_t usage);
|
||||
/*
|
||||
* Function to get the corresponding Adreno format for given HAL format
|
||||
*/
|
||||
ADRENOPIXELFORMAT GetGpuPixelFormat(int hal_format);
|
||||
|
||||
/*
|
||||
* Function to get graphics metadata blob size
|
||||
* @return graphics metadata size
|
||||
*/
|
||||
uint32_t AdrenoGetMetadataBlobSize();
|
||||
|
||||
/*
|
||||
* Function to populate the graphics metadata blob
|
||||
* @return 1 : Successful
|
||||
* 2 : Unsuccessful
|
||||
*/
|
||||
int AdrenoInitMemoryLayout(void* metadata_blob, int width, int height, int depth, int format,
|
||||
int num_samples, int isUBWC, uint64_t usage, uint32_t num_planes);
|
||||
/*
|
||||
* Function to get buffer size for based on graphcis metadata
|
||||
* @return buffer size
|
||||
*/
|
||||
uint32_t AdrenoGetAlignedGpuBufferSize(void* metadata_blob);
|
||||
|
||||
/*
|
||||
* Function to check if adreno size calculation APIs are avaliable
|
||||
* @return true : Avaliable
|
||||
* false : Unavaliable
|
||||
*/
|
||||
bool AdrenoSizeAPIAvaliable();
|
||||
|
||||
static AdrenoMemInfo* GetInstance();
|
||||
@@ -196,7 +142,6 @@ class AdrenoMemInfo {
|
||||
private:
|
||||
AdrenoMemInfo();
|
||||
~AdrenoMemInfo();
|
||||
// link(s)to adreno surface padding library.
|
||||
int (*LINK_adreno_compute_padding)(int width, int bpp, int surface_tile_height,
|
||||
int screen_tile_height, int padding_threshold) = NULL;
|
||||
void (*LINK_adreno_compute_aligned_width_and_height)(int width, int height, int bpp,
|
||||
@@ -229,6 +174,6 @@ class AdrenoMemInfo {
|
||||
static AdrenoMemInfo* s_instance;
|
||||
};
|
||||
|
||||
} // namespace gralloc
|
||||
}
|
||||
|
||||
#endif // __GR_ADRENO_INFO_H__
|
||||
#endif
|
||||
@@ -57,29 +57,27 @@ struct private_handle_t {
|
||||
PRIV_FLAGS_CAMERA_READ = 0x00040000,
|
||||
PRIV_FLAGS_HW_COMPOSER = 0x00080000,
|
||||
PRIV_FLAGS_HW_TEXTURE = 0x00100000,
|
||||
PRIV_FLAGS_ITU_R_601 = 0x00200000, // Unused from display
|
||||
PRIV_FLAGS_ITU_R_601_FR = 0x00400000, // Unused from display
|
||||
PRIV_FLAGS_ITU_R_709 = 0x00800000, // Unused from display
|
||||
PRIV_FLAGS_ITU_R_601 = 0x00200000,
|
||||
PRIV_FLAGS_ITU_R_601_FR = 0x00400000,
|
||||
PRIV_FLAGS_ITU_R_709 = 0x00800000,
|
||||
PRIV_FLAGS_SECURE_DISPLAY = 0x01000000,
|
||||
PRIV_FLAGS_TILE_RENDERED = 0x02000000,
|
||||
PRIV_FLAGS_CPU_RENDERED = 0x04000000,
|
||||
PRIV_FLAGS_UBWC_ALIGNED = 0x08000000,
|
||||
PRIV_FLAGS_DISP_CONSUMER = 0x10000000,
|
||||
PRIV_FLAGS_CLIENT_ALLOCATED = 0x20000000, // Ion buffer allocated outside of gralloc
|
||||
PRIV_FLAGS_UBWC_ALIGNED_PI = 0x40000000, // PI format
|
||||
PRIV_FLAGS_CLIENT_ALLOCATED = 0x20000000,
|
||||
PRIV_FLAGS_UBWC_ALIGNED_PI = 0x40000000,
|
||||
};
|
||||
|
||||
// file-descriptors dup'd over IPC
|
||||
int fd;
|
||||
int fd_metadata;
|
||||
|
||||
// values sent over IPC
|
||||
int magic;
|
||||
int flags;
|
||||
int width; // holds width of the actual buffer allocated
|
||||
int height; // holds height of the actual buffer allocated
|
||||
int unaligned_width; // holds width client asked to allocate
|
||||
int unaligned_height; // holds height client asked to allocate
|
||||
int width;
|
||||
int height;
|
||||
int unaligned_width;
|
||||
int unaligned_height;
|
||||
int format;
|
||||
int buffer_type;
|
||||
unsigned int layer_count;
|
||||
@@ -126,7 +124,6 @@ struct private_handle_t {
|
||||
numFds = kNumFds;
|
||||
}
|
||||
|
||||
// Legacy constructor used by some clients
|
||||
private_handle_t(int fd, unsigned int size, int usage, int buf_type, int format, int w, int h)
|
||||
: private_handle_t(fd, -1, PRIV_FLAGS_CLIENT_ALLOCATED, w, h, 0, 0, format, buf_type, size,
|
||||
static_cast<uint64_t>(usage)) {}
|
||||
@@ -176,7 +173,6 @@ struct private_handle_t {
|
||||
unsigned int GetLayerCount() const { return layer_count; }
|
||||
|
||||
int GetStride() const {
|
||||
// In handle we currently store aligned width after allocation.
|
||||
return width;
|
||||
}
|
||||
|
||||
@@ -187,4 +183,4 @@ struct private_handle_t {
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif // __GR_PRIV_HANDLE_H__
|
||||
#endif
|
||||
@@ -162,7 +162,6 @@ bool CpuCanRead(uint64_t usage) {
|
||||
|
||||
bool CpuCanWrite(uint64_t usage) {
|
||||
if (usage & BufferUsage::CPU_WRITE_MASK) {
|
||||
// Application intends to use CPU for rendering
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -177,7 +176,6 @@ uint32_t GetDataAlignment(int format, uint64_t usage) {
|
||||
|
||||
if (usage & BufferUsage::PROTECTED) {
|
||||
if ((usage & BufferUsage::CAMERA_OUTPUT) || (usage & GRALLOC_USAGE_PRIVATE_SECURE_DISPLAY)) {
|
||||
// The alignment here reflects qsee mmu V7L/V8L requirement
|
||||
align = SZ_2M;
|
||||
} else {
|
||||
align = SECURE_ALIGN;
|
||||
@@ -202,7 +200,6 @@ bool IsGPUFlagSupported(uint64_t usage) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Returns the final buffer size meant to be allocated with ion
|
||||
unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int alignedh) {
|
||||
unsigned int size = 0;
|
||||
int format = info.format;
|
||||
@@ -223,7 +220,6 @@ unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int
|
||||
} else if (IsCompressedRGBFormat(format)) {
|
||||
size = alignedw * alignedh * ASTC_BLOCK_SIZE;
|
||||
} else {
|
||||
// Below switch should be for only YUV/custom formats
|
||||
switch (format) {
|
||||
case HAL_PIXEL_FORMAT_RAW16:
|
||||
case HAL_PIXEL_FORMAT_Y16:
|
||||
@@ -237,15 +233,15 @@ unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int
|
||||
case HAL_PIXEL_FORMAT_Y8:
|
||||
size = alignedw * alignedh * 1;
|
||||
break;
|
||||
// adreno formats
|
||||
case HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO: // NV21
|
||||
|
||||
case HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO:
|
||||
size = ALIGN(alignedw * alignedh, SIZE_4K);
|
||||
size += (unsigned int)ALIGN(2 * ALIGN(width / 2, 32) * ALIGN(height / 2, 32), SIZE_4K);
|
||||
break;
|
||||
case HAL_PIXEL_FORMAT_YCbCr_420_SP_TILED: // NV12
|
||||
// The chroma plane is subsampled,
|
||||
// but the pitch in bytes is unchanged
|
||||
// The GPU needs 4K alignment, but the video decoder needs 8K
|
||||
case HAL_PIXEL_FORMAT_YCbCr_420_SP_TILED:
|
||||
|
||||
|
||||
|
||||
size = ALIGN(alignedw * alignedh, SIZE_8K);
|
||||
size += ALIGN(alignedw * (unsigned int)ALIGN(height / 2, 32), SIZE_8K);
|
||||
break;
|
||||
@@ -296,7 +292,6 @@ unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int
|
||||
return size;
|
||||
}
|
||||
|
||||
// Explicitly defined UBWC formats
|
||||
bool IsUBwcFormat(int format) {
|
||||
switch (format) {
|
||||
case HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC:
|
||||
@@ -309,7 +304,6 @@ bool IsUBwcFormat(int format) {
|
||||
}
|
||||
|
||||
bool IsUBwcSupported(int format) {
|
||||
// Existing HAL formats with UBWC support
|
||||
switch (format) {
|
||||
case HAL_PIXEL_FORMAT_BGR_565:
|
||||
case HAL_PIXEL_FORMAT_RGBA_8888:
|
||||
@@ -336,7 +330,6 @@ bool IsUBwcPISupported(int format, uint64_t usage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// As of now only two formats
|
||||
switch (format) {
|
||||
case HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC:
|
||||
case HAL_PIXEL_FORMAT_YCbCr_420_TP10_UBWC: {
|
||||
@@ -354,27 +347,21 @@ bool IsUBwcPISupported(int format, uint64_t usage) {
|
||||
}
|
||||
|
||||
bool IsUBwcEnabled(int format, uint64_t usage) {
|
||||
// Allow UBWC, if client is using an explicitly defined UBWC pixel format.
|
||||
if (IsUBwcFormat(format)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allow UBWC, if an OpenGL client sets UBWC usage flag and GPU plus MDP
|
||||
// support the format. OR if a non-OpenGL client like Rotator, sets UBWC
|
||||
// usage flag and MDP supports the format.
|
||||
if (((usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC) ||
|
||||
(usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC_PI) ||
|
||||
(usage & BufferUsage::COMPOSER_CLIENT_TARGET)) &&
|
||||
IsUBwcSupported(format)) {
|
||||
bool enable = true;
|
||||
// Query GPU for UBWC only if buffer is intended to be used by GPU.
|
||||
if ((usage & BufferUsage::GPU_TEXTURE) || (usage & BufferUsage::GPU_RENDER_TARGET)) {
|
||||
if (AdrenoMemInfo::GetInstance()) {
|
||||
enable = AdrenoMemInfo::GetInstance()->IsUBWCSupportedByGPU(format);
|
||||
}
|
||||
}
|
||||
|
||||
// Allow UBWC, only if CPU usage flags are not set
|
||||
if (enable && !(CpuCanAccess(usage))) {
|
||||
return true;
|
||||
}
|
||||
@@ -418,13 +405,10 @@ unsigned int GetRgbUBwcMetaBufferSize(int width, int height, uint32_t bpp) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Align meta buffer height to 16 blocks
|
||||
meta_height = ALIGN(((height + block_height - 1) / block_height), 16);
|
||||
|
||||
// Align meta buffer width to 64 blocks
|
||||
meta_width = ALIGN(((width + block_width - 1) / block_width), 64);
|
||||
|
||||
// Align meta buffer size to 4K
|
||||
size = (unsigned int)ALIGN((meta_width * meta_height), 4096);
|
||||
|
||||
return size;
|
||||
@@ -500,12 +484,10 @@ bool UseUncached(int format, uint64_t usage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// CPU read rarely
|
||||
if ((usage & BufferUsage::CPU_READ_MASK) == static_cast<uint64_t>(BufferUsage::CPU_READ_RARELY)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// CPU write rarely
|
||||
if ((usage & BufferUsage::CPU_WRITE_MASK) ==
|
||||
static_cast<uint64_t>(BufferUsage::CPU_WRITE_RARELY)) {
|
||||
return true;
|
||||
@@ -576,8 +558,6 @@ uint64_t GetHandleFlags(int format, uint64_t usage) {
|
||||
int GetImplDefinedFormat(uint64_t usage, int format) {
|
||||
int gr_format = format;
|
||||
|
||||
// If input format is HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED then based on
|
||||
// the usage bits, gralloc assigns a format.
|
||||
if (format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED ||
|
||||
format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
|
||||
if ((usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC || usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC_PI) &&
|
||||
@@ -585,33 +565,30 @@ int GetImplDefinedFormat(uint64_t usage, int format) {
|
||||
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC;
|
||||
} else if (usage & BufferUsage::VIDEO_ENCODER) {
|
||||
if (usage & GRALLOC_USAGE_PRIVATE_VIDEO_NV21_ENCODER) {
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ENCODEABLE; // NV21
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ENCODEABLE;
|
||||
} else if (usage & GRALLOC_USAGE_PRIVATE_HEIF) {
|
||||
gr_format = HAL_PIXEL_FORMAT_NV12_HEIF;
|
||||
} else if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
|
||||
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS;
|
||||
} else {
|
||||
gr_format = HAL_PIXEL_FORMAT_NV12_ENCODEABLE; // NV12
|
||||
gr_format = HAL_PIXEL_FORMAT_NV12_ENCODEABLE;
|
||||
}
|
||||
} else if (usage & BufferUsage::CAMERA_INPUT) {
|
||||
if (usage & BufferUsage::CAMERA_OUTPUT) {
|
||||
// Assumed ZSL if both producer and consumer camera flags set
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL; // NV21
|
||||
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL;
|
||||
} else {
|
||||
gr_format = HAL_PIXEL_FORMAT_YCrCb_420_SP; // NV21
|
||||
gr_format = HAL_PIXEL_FORMAT_YCrCb_420_SP;
|
||||
}
|
||||
} else if (usage & BufferUsage::CAMERA_OUTPUT) {
|
||||
if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL; // NV21
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL;
|
||||
} else {
|
||||
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS; // NV12 preview
|
||||
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS;
|
||||
}
|
||||
} else if (usage & BufferUsage::COMPOSER_OVERLAY) {
|
||||
// XXX: If we still haven't set a format, default to RGBA8888
|
||||
gr_format = HAL_PIXEL_FORMAT_RGBA_8888;
|
||||
} else if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
|
||||
// If no other usage flags are detected, default the
|
||||
// flexible YUV format to NV21_ZSL
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL;
|
||||
}
|
||||
}
|
||||
@@ -637,7 +614,7 @@ bool HasAlphaComponent(int32_t format) {
|
||||
}
|
||||
|
||||
void GetRGBPlaneInfo(const BufferInfo& info, int32_t format, int32_t width, int32_t height,
|
||||
int32_t /* flags */, int* plane_count, PlaneLayoutInfo* plane_info) {
|
||||
int32_t , int* plane_count, PlaneLayoutInfo* plane_info) {
|
||||
uint64_t usage = info.usage;
|
||||
*plane_count = 1;
|
||||
uint32_t bpp = 0;
|
||||
@@ -659,4 +636,4 @@ void GetRGBPlaneInfo(const BufferInfo& info, int32_t format, int32_t width, int3
|
||||
plane_info->scanlines = height;
|
||||
}
|
||||
|
||||
} // namespace gralloc
|
||||
}
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
#ifdef SLAVE_SIDE_CP
|
||||
#define SECURE_ALIGN SZ_1M
|
||||
#else // MASTER_SIDE_CP
|
||||
#else
|
||||
#define SECURE_ALIGN SZ_4K
|
||||
#endif
|
||||
|
||||
@@ -68,59 +68,38 @@ inline Type1 ALIGN(Type1 x, Type2 align) {
|
||||
}
|
||||
|
||||
enum PlaneComponent {
|
||||
/* luma */
|
||||
PLANE_COMPONENT_Y = 1 << 0,
|
||||
/* chroma blue */
|
||||
PLANE_COMPONENT_Cb = 1 << 1,
|
||||
/* chroma red */
|
||||
PLANE_COMPONENT_Cr = 1 << 2,
|
||||
|
||||
/* red */
|
||||
PLANE_COMPONENT_R = 1 << 10,
|
||||
/* green */
|
||||
PLANE_COMPONENT_G = 1 << 11,
|
||||
/* blue */
|
||||
PLANE_COMPONENT_B = 1 << 12,
|
||||
|
||||
/* alpha */
|
||||
PLANE_COMPONENT_A = 1 << 20,
|
||||
|
||||
/* raw data plane */
|
||||
PLANE_COMPONENT_RAW = 1 << 30,
|
||||
|
||||
/* meta information plane */
|
||||
PLANE_COMPONENT_META = 1 << 31,
|
||||
};
|
||||
|
||||
struct PlaneLayoutInfo {
|
||||
/** Components represented the type of plane. */
|
||||
PlaneComponent component;
|
||||
|
||||
/** horizontal subsampling. Must be a positive power of 2. */
|
||||
uint32_t h_subsampling;
|
||||
|
||||
/** vertical subsampling. Must be a positive power of 2. */
|
||||
uint32_t v_subsampling;
|
||||
|
||||
/** offset to the first byte of the top-left pixel of the plane
|
||||
* and it is calculated from the start of the buffer.
|
||||
* Add base of the handle with offset to get the first byte of the plane.
|
||||
*/
|
||||
uint32_t offset;
|
||||
|
||||
/** step is the distance in bytes from one pixel value to the next. */
|
||||
int32_t step;
|
||||
|
||||
/** stride of the plane in pixels */
|
||||
int32_t stride;
|
||||
|
||||
/** stride of the plane in in bytes */
|
||||
int32_t stride_bytes;
|
||||
|
||||
/** plane height or vertical stride */
|
||||
int32_t scanlines;
|
||||
|
||||
/** size of the plane in bytes */
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
@@ -151,6 +130,6 @@ int GetImplDefinedFormat(uint64_t usage, int format);
|
||||
int GetCustomFormatFlags(int format, uint64_t usage, int* custom_format, uint64_t* priv_flags);
|
||||
bool IsGPUFlagSupported(uint64_t usage);
|
||||
bool HasAlphaComponent(int32_t format);
|
||||
} // namespace gralloc
|
||||
}
|
||||
|
||||
#endif // __GR_UTILS_H__
|
||||
#endif
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "QtiGrallocDefs.h"
|
||||
#include "gr_priv_handle.h"
|
||||
|
||||
//#pragma message "Warning: gralloc_priv.h is deprecated"
|
||||
|
||||
#define GRALLOC_PROP_PREFIX "vendor.gralloc."
|
||||
#define GRALLOC_PROP(prop_name) GRALLOC_PROP_PREFIX prop_name
|
||||
@@ -42,8 +41,6 @@ inline int roundUpToPageSize(int x) {
|
||||
return (x + (getpagesize() - 1)) & ~(getpagesize() - 1);
|
||||
}
|
||||
|
||||
/* Legacy gralloc1 definitions */
|
||||
/* Some clients may still be using the old flags */
|
||||
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_ADSP_HEAP GRALLOC_USAGE_PRIVATE_ADSP_HEAP
|
||||
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_ALLOC_UBWC GRALLOC_USAGE_PRIVATE_ALLOC_UBWC
|
||||
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_UNCACHED GRALLOC_USAGE_PRIVATE_UNCACHED
|
||||
@@ -54,7 +51,6 @@ inline int roundUpToPageSize(int x) {
|
||||
#define GRALLOC1_CONSUMER_USAGE_PRIVATE_10BIT_TP GRALLOC_USAGE_PRIVATE_10BIT_TP
|
||||
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_VIDEO_NV21_ENCODER GRALLOC_USAGE_PRIVATE_VIDEO_NV21_ENCODER
|
||||
|
||||
// for PERFORM API :
|
||||
#define GRALLOC_MODULE_PERFORM_CREATE_HANDLE_FROM_BUFFER 1
|
||||
#define GRALLOC_MODULE_PERFORM_GET_STRIDE 2
|
||||
#define GRALLOC_MODULE_PERFORM_GET_CUSTOM_STRIDE_FROM_HANDLE 3
|
||||
@@ -72,11 +68,9 @@ inline int roundUpToPageSize(int x) {
|
||||
#define GRALLOC1_MODULE_PERFORM_GET_INTERLACE_FLAG 15
|
||||
#define GRALLOC_MODULE_PERFORM_GET_GRAPHICS_METADATA 16
|
||||
|
||||
/* possible values for inverse gamma correction */
|
||||
#define HAL_IGC_NOT_SPECIFIED 0
|
||||
#define HAL_IGC_s_RGB 1
|
||||
|
||||
/* Color Space: Values maps to ColorSpace_t in qdMetadata.h */
|
||||
#define HAL_CSC_ITU_R_601 0
|
||||
#define HAL_CSC_ITU_R_601_FR 1
|
||||
#define HAL_CSC_ITU_R_709 2
|
||||
@@ -84,20 +78,16 @@ inline int roundUpToPageSize(int x) {
|
||||
#define HAL_CSC_ITU_R_2020 4
|
||||
#define HAL_CSC_ITU_R_2020_FR 5
|
||||
|
||||
/* possible formats for 3D content*/
|
||||
enum {
|
||||
HAL_NO_3D = 0x0,
|
||||
HAL_3D_SIDE_BY_SIDE_L_R = 0x1,
|
||||
HAL_3D_SIDE_BY_SIDE_R_L = 0x2,
|
||||
HAL_3D_TOP_BOTTOM = 0x4,
|
||||
HAL_3D_IN_SIDE_BY_SIDE_L_R = 0x10000, // unused legacy format
|
||||
HAL_3D_IN_SIDE_BY_SIDE_L_R = 0x10000,
|
||||
};
|
||||
|
||||
/* Flag to determine interlaced content
|
||||
* Value maps to Flags presents in types.hal of QtiMapperextensions
|
||||
*/
|
||||
enum {
|
||||
LAYOUT_INTERLACED_FLAG = 1 << 0,
|
||||
};
|
||||
|
||||
#endif // __GRALLOC_PRIV_H__
|
||||
#endif
|
||||
@@ -60,43 +60,43 @@ struct IMapper : public IBase {
|
||||
int32_t height __attribute__((aligned(4)));
|
||||
};
|
||||
|
||||
static sp<IMapper> getService(const std::string& /* serviceName */, const bool /* getStub */);
|
||||
static sp<IMapper> getService(const std::string& , const bool );
|
||||
};
|
||||
|
||||
} // namespace V2_0
|
||||
}
|
||||
namespace V3_0 {
|
||||
|
||||
struct IMapper : public IBase {
|
||||
static sp<IMapper> getService(const std::string& /* serviceName */, const bool /* getStub */);
|
||||
static sp<IMapper> getService(const std::string& , const bool );
|
||||
};
|
||||
|
||||
} // namespace V3_0
|
||||
}
|
||||
namespace V4_0 {
|
||||
|
||||
struct IMapper : public IBase {
|
||||
static sp<IMapper> getService(const std::string& /* serviceName */, const bool /* getStub */);
|
||||
static sp<IMapper> getService(const std::string& , const bool );
|
||||
};
|
||||
|
||||
} // namespace V4_0
|
||||
} // namespace android::hardware::graphics::mapper
|
||||
}
|
||||
}
|
||||
|
||||
namespace vendor::qti::hardware::display::mapper {
|
||||
namespace V4_0 {
|
||||
|
||||
struct IQtiMapper : public IBase {
|
||||
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V4_0::IMapper>& /* parent */,
|
||||
bool /* emitError */);
|
||||
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V4_0::IMapper>& ,
|
||||
bool );
|
||||
};
|
||||
|
||||
} // namespace V4_0
|
||||
}
|
||||
namespace V3_0 {
|
||||
|
||||
struct IQtiMapper : public IBase {
|
||||
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V3_0::IMapper>& /* parent */,
|
||||
bool /* emitError */);
|
||||
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V3_0::IMapper>& ,
|
||||
bool );
|
||||
};
|
||||
|
||||
} // namespace V3_0
|
||||
}
|
||||
namespace V2_0 {
|
||||
|
||||
struct IQtiMapper : public IBase {
|
||||
@@ -175,9 +175,9 @@ struct IQtiMapper : public IBase {
|
||||
V1_0::IQtiMapperExtensions>& extensions)>;
|
||||
virtual Return<void> getMapperExtensions(getMapperExtensions_cb _hidl_cb);
|
||||
|
||||
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V2_0::IMapper>& /* parent */,
|
||||
bool /* emitError */);
|
||||
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V2_0::IMapper>& ,
|
||||
bool );
|
||||
};
|
||||
|
||||
} // namespace V2_0
|
||||
} // namespace vendor::qti::hardware::display::mapper
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ struct IQtiMapperExtensions : public IBase {
|
||||
IQtiMapperExtensions(){};
|
||||
};
|
||||
|
||||
} // namespace V1_0
|
||||
}
|
||||
namespace V1_1 {
|
||||
using Error = V1_0::Error;
|
||||
using PlaneLayout = V1_0::PlaneLayout;
|
||||
@@ -64,7 +64,6 @@ struct IQtiMapperExtensions : public IBase {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Used, passthrough
|
||||
using getInterlacedFlag_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, bool interlaced)>;
|
||||
virtual ::android::hardware::Return<void> getInterlacedFlag(void* buffer,
|
||||
@@ -79,7 +78,6 @@ struct IQtiMapperExtensions : public IBase {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Used, passthrough
|
||||
using getRgbDataAddress_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, void* rgb_data)>;
|
||||
virtual ::android::hardware::Return<void> getRgbDataAddress(void* buffer,
|
||||
@@ -95,7 +93,6 @@ struct IQtiMapperExtensions : public IBase {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Used, passhthrough
|
||||
using getColorSpace_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t color_space)>;
|
||||
virtual ::android::hardware::Return<void> getColorSpace(void* buffer, getColorSpace_cb _hidl_cb) {
|
||||
@@ -103,7 +100,6 @@ struct IQtiMapperExtensions : public IBase {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Used, passthrough with hidl fixups
|
||||
using getYuvPlaneInfo_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error,
|
||||
const ::android::hardware::hidl_vec<
|
||||
@@ -130,27 +126,22 @@ struct IQtiMapperExtensions : public IBase {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getFd_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t fd)>;
|
||||
virtual ::android::hardware::Return<void> getFd(void* buffer, getFd_cb _hidl_cb);
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getWidth_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t width)>;
|
||||
virtual ::android::hardware::Return<void> getWidth(void* buffer, getWidth_cb _hidl_cb);
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getHeight_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t height)>;
|
||||
virtual ::android::hardware::Return<void> getHeight(void* buffer, getHeight_cb _hidl_cb);
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getOffset_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t offset)>;
|
||||
virtual ::android::hardware::Return<void> getOffset(void* buffer, getOffset_cb _hidl_cb);
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getSize_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t size)>;
|
||||
virtual ::android::hardware::Return<void> getSize(void* buffer, getSize_cb _hidl_cb);
|
||||
@@ -179,12 +170,10 @@ struct IQtiMapperExtensions : public IBase {
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t usage)>;
|
||||
virtual ::android::hardware::Return<void> getUsageFlags(void* buffer, getUsageFlags_cb _hidl_cb);
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getFormat_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t format)>;
|
||||
virtual ::android::hardware::Return<void> getFormat(void* buffer, getFormat_cb _hidl_cb);
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getPrivateFlags_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t flags)>;
|
||||
virtual ::android::hardware::Return<void> getPrivateFlags(void* buffer,
|
||||
@@ -198,7 +187,6 @@ struct IQtiMapperExtensions : public IBase {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Used, PAIN
|
||||
using getFormatLayout_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t size,
|
||||
const ::android::hardware::hidl_vec<
|
||||
@@ -208,7 +196,6 @@ struct IQtiMapperExtensions : public IBase {
|
||||
int32_t height,
|
||||
getFormatLayout_cb _hidl_cb);
|
||||
|
||||
// V1.1, Used, passthrough
|
||||
virtual ::android::hardware::Return<
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error>
|
||||
getSurfaceMetadata_V1(void* buffer, void* metadata) {
|
||||
@@ -217,8 +204,8 @@ struct IQtiMapperExtensions : public IBase {
|
||||
}
|
||||
|
||||
static Return<sp<IQtiMapperExtensions>> castFrom(
|
||||
android::sp<V1_0::IQtiMapperExtensions> const& /* parent */, bool /* emitError */);
|
||||
android::sp<V1_0::IQtiMapperExtensions> const& , bool );
|
||||
};
|
||||
|
||||
} // namespace V1_1
|
||||
} // namespace vendor::qti::hardware::display::mapperextensions
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -12,7 +12,6 @@ namespace graphics {
|
||||
namespace mapper {
|
||||
namespace V2_0 {
|
||||
|
||||
// Forward declaration for forward reference support:
|
||||
enum class Error : int32_t;
|
||||
struct YCbCrLayout;
|
||||
|
||||
@@ -25,34 +24,8 @@ enum class Error : int32_t {
|
||||
UNSUPPORTED = 7,
|
||||
};
|
||||
|
||||
/**
|
||||
* A buffer descriptor is an implementation-defined opaque data returned by
|
||||
* createDescriptor. It describes the properties of a buffer and is consumed
|
||||
* by the allocator.
|
||||
*/
|
||||
typedef ::android::hardware::hidl_vec<uint32_t> BufferDescriptor;
|
||||
|
||||
/**
|
||||
* Structure for describing YCbCr formats for consumption by applications.
|
||||
* This is used with PixelFormat::YCBCR_*_888.
|
||||
*
|
||||
* Buffer chroma subsampling is defined in the format.
|
||||
* e.g. PixelFormat::YCBCR_420_888 has subsampling 4:2:0.
|
||||
*
|
||||
* Buffers must have a 8 bit depth.
|
||||
*
|
||||
* y, cb, and cr point to the first byte of their respective planes.
|
||||
*
|
||||
* Stride describes the distance in bytes from the first value of one row of
|
||||
* the image to the first value of the next row. It includes the width of the
|
||||
* image plus padding.
|
||||
* yStride is the stride of the luma plane.
|
||||
* cStride is the stride of the chroma planes.
|
||||
*
|
||||
* chromaStep is the distance in bytes from one chroma pixel value to the
|
||||
* next. This is 2 bytes for semiplanar (because chroma values are interleaved
|
||||
* and each chroma value is one byte) and 1 for planar.
|
||||
*/
|
||||
struct YCbCrLayout final {
|
||||
void* y;
|
||||
void* cb;
|
||||
@@ -62,9 +35,6 @@ struct YCbCrLayout final {
|
||||
uint32_t chromaStep;
|
||||
};
|
||||
|
||||
//
|
||||
// type declarations for package
|
||||
//
|
||||
|
||||
template <typename>
|
||||
static inline std::string toString(int32_t o);
|
||||
@@ -107,11 +77,7 @@ constexpr int32_t& operator&=(int32_t& v,
|
||||
|
||||
static inline std::string toString(
|
||||
const ::android::hardware::graphics::mapper::V2_0::YCbCrLayout& o);
|
||||
// operator== and operator!= are not generated for YCbCrLayout
|
||||
|
||||
//
|
||||
// type header definitions for package
|
||||
//
|
||||
|
||||
template <>
|
||||
inline std::string toString<::android::hardware::graphics::mapper::V2_0::Error>(int32_t o) {
|
||||
@@ -218,17 +184,17 @@ static inline std::string toString(
|
||||
return os;
|
||||
}
|
||||
|
||||
// operator== and operator!= are not generated for YCbCrLayout
|
||||
|
||||
} // namespace V2_0
|
||||
} // namespace mapper
|
||||
} // namespace graphics
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
|
||||
//
|
||||
// global type declarations for package
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
@@ -243,8 +209,8 @@ constexpr std::array<::android::hardware::graphics::mapper::V2_0::Error, 6>
|
||||
::android::hardware::graphics::mapper::V2_0::Error::NO_RESOURCES,
|
||||
::android::hardware::graphics::mapper::V2_0::Error::UNSUPPORTED,
|
||||
};
|
||||
} // namespace details
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HIDL_GENERATED_ANDROID_HARDWARE_GRAPHICS_MAPPER_V2_0_TYPES_H
|
||||
#endif
|
||||
@@ -13,7 +13,6 @@ namespace display {
|
||||
namespace mapperextensions {
|
||||
namespace V1_0 {
|
||||
|
||||
// Forward declaration for forward reference support:
|
||||
enum class Error : int32_t;
|
||||
struct YCbCrLayout;
|
||||
enum class PlaneComponent : uint32_t;
|
||||
@@ -36,61 +35,22 @@ struct YCbCrLayout final {
|
||||
uint32_t chromaStep;
|
||||
};
|
||||
|
||||
/**
|
||||
* Plane Component type definitions.
|
||||
*/
|
||||
enum class PlaneComponent : uint32_t {
|
||||
PLANE_COMPONENT_Y = 1u /* (1 << 0) */,
|
||||
PLANE_COMPONENT_Cb = 2u /* (1 << 1) */,
|
||||
PLANE_COMPONENT_Cr = 4u /* (1 << 2) */,
|
||||
PLANE_COMPONENT_R = 1024u /* (1 << 10) */,
|
||||
PLANE_COMPONENT_G = 2048u /* (1 << 11) */,
|
||||
PLANE_COMPONENT_B = 4096u /* (1 << 12) */,
|
||||
PLANE_COMPONENT_A = 1048576u /* (1 << 20) */,
|
||||
PLANE_COMPONENT_RAW = 1073741824u /* (1 << 30) */,
|
||||
PLANE_COMPONENT_META = 2147483648u /* (1 << 31) */,
|
||||
PLANE_COMPONENT_Y = 1u ,
|
||||
PLANE_COMPONENT_Cb = 2u ,
|
||||
PLANE_COMPONENT_Cr = 4u ,
|
||||
PLANE_COMPONENT_R = 1024u ,
|
||||
PLANE_COMPONENT_G = 2048u ,
|
||||
PLANE_COMPONENT_B = 4096u ,
|
||||
PLANE_COMPONENT_A = 1048576u ,
|
||||
PLANE_COMPONENT_RAW = 1073741824u ,
|
||||
PLANE_COMPONENT_META = 2147483648u ,
|
||||
};
|
||||
|
||||
/**
|
||||
* Flags to send with getFormatLayout for extra information for e.g. interlaced,
|
||||
* otherwise send 0.
|
||||
*/
|
||||
enum class Flags : int32_t {
|
||||
LAYOUT_INTERLACED_FLAG = 1 /* (1 << 0) */,
|
||||
LAYOUT_INTERLACED_FLAG = 1 ,
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure for describing plane layout information for YUVA/RGBA/RAW formats
|
||||
* YUV formats can be planar or semi-planar.
|
||||
*
|
||||
* This layout structure is used with any pixel format that can be
|
||||
* represented by it, such as:
|
||||
* - HAL_PIXEL_FORMAT_YCbCr_*
|
||||
* - HAL_PIXEL_FORMAT_RGB*_*
|
||||
* - even implementation defined formats that can be represented by
|
||||
* the structures
|
||||
*
|
||||
* Each plane can be made up of one or more PlaneComponents depending on
|
||||
* the format.
|
||||
*
|
||||
* Each plane can be subsampled either vertically or horizontally by
|
||||
* a power-of-two factor.
|
||||
*
|
||||
* Pixel increment describes the distance in bytes from one pixel to the
|
||||
* next pixel (to the right) on the same row for the component plane.
|
||||
* This can be negative.
|
||||
*
|
||||
* Stride is provided in both pixels and bytes.
|
||||
* Stride_bytes describes the distance in bytes from the first pixel of
|
||||
* one row to the first pixel of the next row (below) for the component plane.
|
||||
* This can be negative.
|
||||
*
|
||||
* Scanlines describes the height of the plane in terms of number of rows.
|
||||
*
|
||||
* Size of plane is stride_bytes * scanlines + any padding required for
|
||||
* alignment as per format specification.
|
||||
*
|
||||
*/
|
||||
struct PlaneLayout final {
|
||||
::android::hardware::hidl_bitfield<
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneComponent>
|
||||
@@ -137,9 +97,6 @@ static_assert(sizeof(::vendor::qti::hardware::display::mapperextensions::V1_0::P
|
||||
static_assert(__alignof(::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneLayout) == 4,
|
||||
"wrong alignment");
|
||||
|
||||
//
|
||||
// type declarations for package
|
||||
//
|
||||
|
||||
template <typename>
|
||||
static inline std::string toString(int32_t o);
|
||||
@@ -185,7 +142,6 @@ constexpr int32_t& operator&=(
|
||||
|
||||
static inline std::string toString(
|
||||
const ::vendor::qti::hardware::display::mapperextensions::V1_0::YCbCrLayout& o);
|
||||
// operator== and operator!= are not generated for YCbCrLayout
|
||||
|
||||
template <typename>
|
||||
static inline std::string toString(uint32_t o);
|
||||
@@ -284,9 +240,6 @@ static inline bool operator!=(
|
||||
const ::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneLayout& lhs,
|
||||
const ::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneLayout& rhs);
|
||||
|
||||
//
|
||||
// type header definitions for package
|
||||
//
|
||||
|
||||
template <>
|
||||
inline std::string toString<::vendor::qti::hardware::display::mapperextensions::V1_0::Error>(
|
||||
@@ -379,7 +332,6 @@ static inline std::string toString(
|
||||
return os;
|
||||
}
|
||||
|
||||
// operator== and operator!= are not generated for YCbCrLayout
|
||||
|
||||
template <>
|
||||
inline std::string
|
||||
@@ -642,16 +594,13 @@ static inline bool operator!=(
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace V1_0
|
||||
} // namespace mapperextensions
|
||||
} // namespace display
|
||||
} // namespace hardware
|
||||
} // namespace qti
|
||||
} // namespace vendor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// global type declarations for package
|
||||
//
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
@@ -664,9 +613,9 @@ constexpr std::array<::vendor::qti::hardware::display::mapperextensions::V1_0::E
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error::NO_RESOURCES,
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error::UNSUPPORTED,
|
||||
};
|
||||
} // namespace details
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
@@ -688,9 +637,9 @@ constexpr std::array<::vendor::qti::hardware::display::mapperextensions::V1_0::P
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneComponent::
|
||||
PLANE_COMPONENT_META,
|
||||
};
|
||||
} // namespace details
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
@@ -700,8 +649,8 @@ constexpr std::array<::vendor::qti::hardware::display::mapperextensions::V1_0::F
|
||||
hidl_enum_values<::vendor::qti::hardware::display::mapperextensions::V1_0::Flags> = {
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Flags::LAYOUT_INTERLACED_FLAG,
|
||||
};
|
||||
} // namespace details
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HIDL_GENERATED_VENDOR_QTI_HARDWARE_DISPLAY_MAPPEREXTENSIONS_V1_0_TYPES_H
|
||||
#endif
|
||||
@@ -46,14 +46,13 @@
|
||||
using namespace android::hardware;
|
||||
using namespace android;
|
||||
|
||||
// These two functions are used by the blob and aren't present on Q so weakly stub them
|
||||
extern "C" uint64_t __attribute__((weak)) atrace_get_enabled_tags() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace android::hardware::details {
|
||||
void __attribute__((weak)) return_status::onValueRetrieval() const {}
|
||||
} // namespace android::hardware::details
|
||||
}
|
||||
|
||||
bool IBase::isRemote() const {
|
||||
return false;
|
||||
@@ -146,7 +145,7 @@ sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getSt
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace V2_0
|
||||
}
|
||||
namespace V3_0 {
|
||||
|
||||
sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getStub) {
|
||||
@@ -154,7 +153,7 @@ sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getSt
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace V3_0
|
||||
}
|
||||
namespace V4_0 {
|
||||
|
||||
sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getStub) {
|
||||
@@ -162,8 +161,8 @@ sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getSt
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace V4_0
|
||||
} // namespace android::hardware::graphics::mapper
|
||||
}
|
||||
}
|
||||
namespace vendor::qti::hardware::display {
|
||||
namespace mapper {
|
||||
namespace V4_0 {
|
||||
@@ -174,7 +173,7 @@ Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V4_0::IMa
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace V4_0
|
||||
}
|
||||
namespace V3_0 {
|
||||
|
||||
Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V3_0::IMapper>& parent,
|
||||
@@ -183,7 +182,7 @@ Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V3_0::IMa
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace V3_0
|
||||
}
|
||||
namespace V2_0 {
|
||||
|
||||
Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V2_0::IMapper>& parent,
|
||||
@@ -200,8 +199,8 @@ Return<void> IQtiMapper::getMapperExtensions(getMapperExtensions_cb _hidl_cb) {
|
||||
return Void();
|
||||
}
|
||||
|
||||
} // namespace V2_0
|
||||
} // namespace mapper
|
||||
}
|
||||
}
|
||||
namespace mapperextensions {
|
||||
namespace V1_1 {
|
||||
|
||||
@@ -467,6 +466,6 @@ Return<void> IQtiMapperExtensions::getFormatLayout(int32_t format, uint64_t usag
|
||||
return Void();
|
||||
}
|
||||
|
||||
} // namespace V1_1
|
||||
} // namespace mapperextensions
|
||||
} // namespace vendor::qti::hardware::display
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
// What the phone's own Vulkan driver says about its GPU, for the app's device info window: one
|
||||
// instance on the system loader, the first physical device's properties, a few limits and the
|
||||
// extensions DXVK and VKD3D-Proton lean on. Returned as "key=value" lines; nothing is kept open.
|
||||
#include <dlfcn.h>
|
||||
#include <jni.h>
|
||||
#include <stdio.h>
|
||||
@@ -62,12 +59,10 @@ Java_com_droiddeck_launcher_gpu_VulkanInfo_nativeQuery(JNIEnv *env, jclass clazz
|
||||
LINE("device=%s\n", p.deviceName);
|
||||
LINE("type=%s\n", type_name(p.deviceType));
|
||||
LINE("api=%u.%u.%u\n", VK_API_VERSION_MAJOR(p.apiVersion), VK_API_VERSION_MINOR(p.apiVersion), VK_API_VERSION_PATCH(p.apiVersion));
|
||||
// Qualcomm packs its driver version as 10.10.12 bits, the same layout as VK_MAKE_VERSION.
|
||||
LINE("driver_version=%u.%u.%u\n", p.driverVersion >> 22, (p.driverVersion >> 12) & 0x3ff, p.driverVersion & 0xfff);
|
||||
LINE("vendor_id=0x%04x\ndevice_id=0x%08x\n", p.vendorID, p.deviceID);
|
||||
LINE("max_image_2d=%u\n", p.limits.maxImageDimension2D);
|
||||
|
||||
// Driver name and build string (Vulkan 1.2 or VK_KHR_driver_properties).
|
||||
uint32_t extCount = 0;
|
||||
if (vkEnumerateDeviceExtensionProperties) vkEnumerateDeviceExtensionProperties(device, NULL, &extCount, NULL);
|
||||
static VkExtensionProperties exts[512];
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -84,12 +84,11 @@ struct VkTable {
|
||||
PFN_vkCmdSetScissor CmdSetScissor;
|
||||
PFN_vkCmdPipelineBarrier CmdPipelineBarrier;
|
||||
PFN_vkCmdCopyImage CmdCopyImage;
|
||||
PFN_vkCmdBlitImage CmdBlitImage; // frame-gen capture resolution: composite -> swapchain upscale
|
||||
// Compute: the native LSFG chain is 25 compute dispatches.
|
||||
PFN_vkCmdBlitImage CmdBlitImage;
|
||||
|
||||
PFN_vkCmdDispatch CmdDispatch;
|
||||
PFN_vkCreateComputePipelines CreateComputePipelines;
|
||||
PFN_vkUnmapMemory UnmapMemory;
|
||||
// win-fg native: clears its flow scratch, resets its scratch pool on resize.
|
||||
PFN_vkCmdClearColorImage CmdClearColorImage;
|
||||
PFN_vkResetDescriptorPool ResetDescriptorPool;
|
||||
PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage;
|
||||
@@ -125,11 +124,8 @@ struct VkTable {
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
|
||||
// Renderer-neutral direct-scanout impl (owns the SurfaceControl/AHB state).
|
||||
// Included after SCANOUT_LOG is defined above; its own definition is guarded.
|
||||
#include "../scanout/ScanoutContext.h"
|
||||
|
||||
// Native (compositor-side) LSFG frame generation - capability gate.
|
||||
#include "lsfg/lsfg_probe.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -139,89 +135,83 @@ namespace winfg { class Engine; }
|
||||
|
||||
static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2;
|
||||
|
||||
// Push-constant layouts for the spatial-upscaler post passes. The leading vec4
|
||||
// `ndc` matches upscale.vert; the remaining members are read only by the
|
||||
// fragment stage. std430 offsets line up with these C structs.
|
||||
struct SgsrPushConstants { // 36 bytes
|
||||
|
||||
|
||||
|
||||
struct SgsrPushConstants {
|
||||
float ndc[4];
|
||||
float viewportInfo[4]; // xy = 1/inputSize, zw = inputSize px
|
||||
float edgeSharpness; // SGSR EdgeSharpness, from the slider
|
||||
float viewportInfo[4];
|
||||
float edgeSharpness;
|
||||
};
|
||||
struct NisPushConstants { // 36 bytes
|
||||
struct NisPushConstants {
|
||||
float ndc[4];
|
||||
float viewportInfo[4]; // xy = 1/inputSize, zw = inputSize px
|
||||
float sharpness; // NIS sharpness [0..1], from the slider
|
||||
float viewportInfo[4];
|
||||
float sharpness;
|
||||
};
|
||||
struct EasuPushConstants { // 88 bytes
|
||||
struct EasuPushConstants {
|
||||
float ndc[4];
|
||||
uint32_t con0[4], con1[4], con2[4], con3[4];
|
||||
float outW, outH;
|
||||
};
|
||||
struct RcasPushConstants { // 40 bytes
|
||||
struct RcasPushConstants {
|
||||
float ndc[4];
|
||||
uint32_t con[4]; // con.x = bit-packed sharpness
|
||||
uint32_t con[4];
|
||||
float outW, outH;
|
||||
};
|
||||
// Composable post effects (AMD CAS sharpen + fake-HDR). Both lead with vec4 ndc
|
||||
// (offset 0) like the upscaler PCs and stay well under the 88-byte PC range.
|
||||
struct CasPushConstants { // 32 bytes
|
||||
|
||||
|
||||
struct CasPushConstants {
|
||||
float ndc[4];
|
||||
float resolution[2]; // input texture size in px
|
||||
float sharpness; // CAS SHARPNESS term [0..1]
|
||||
float resolution[2];
|
||||
float sharpness;
|
||||
float _pad;
|
||||
};
|
||||
struct HdrPushConstants { // 24 bytes
|
||||
struct HdrPushConstants {
|
||||
float ndc[4];
|
||||
float resolution[2]; // input texture size in px
|
||||
float resolution[2];
|
||||
};
|
||||
// Phase 2 screen effects ported from the GL EffectComposer. Each leads with vec4
|
||||
// ndc (offset 0) like the upscaler PCs and stays well under the 88-byte PC range.
|
||||
struct FxaaPushConstants { // 24 bytes
|
||||
|
||||
|
||||
struct FxaaPushConstants {
|
||||
float ndc[4];
|
||||
float resolution[2]; // input texture size in px
|
||||
float resolution[2];
|
||||
};
|
||||
struct ToonPushConstants { // 24 bytes
|
||||
struct ToonPushConstants {
|
||||
float ndc[4];
|
||||
float resolution[2]; // input texture size in px
|
||||
float resolution[2];
|
||||
};
|
||||
struct ColorPushConstants { // 32 bytes
|
||||
struct ColorPushConstants {
|
||||
float ndc[4];
|
||||
float brightness; // additive [-1,1]
|
||||
float contrast; // [0,2]
|
||||
float gamma; // [0.1,5]
|
||||
float saturation; // [0,2]; 1 = unchanged, 0 = greyscale.
|
||||
// APPENDED last so the three offsets above
|
||||
// stay where color.frag already had them.
|
||||
float brightness;
|
||||
float contrast;
|
||||
float gamma;
|
||||
float saturation;
|
||||
|
||||
|
||||
};
|
||||
struct NtscPushConstants { // 28 bytes
|
||||
struct NtscPushConstants {
|
||||
float ndc[4];
|
||||
float resolution[2]; // screen size in px (TextureSize/resolution)
|
||||
float frameCount; // animated chroma-phase counter
|
||||
float resolution[2];
|
||||
float frameCount;
|
||||
};
|
||||
struct CrtPushConstants { // 24 bytes
|
||||
struct CrtPushConstants {
|
||||
float ndc[4];
|
||||
float resolution[2]; // input texture size in px (unused by shader)
|
||||
float resolution[2];
|
||||
};
|
||||
struct DebandPushConstants { // 28 bytes
|
||||
struct DebandPushConstants {
|
||||
float ndc[4];
|
||||
float resolution[2]; // input texture size in px (layout parity)
|
||||
float strength; // dither amplitude in LSBs (1.0 = +/-1/255)
|
||||
float resolution[2];
|
||||
float strength;
|
||||
};
|
||||
|
||||
class VulkanRendererContext {
|
||||
public:
|
||||
// `lsfgVk11Compat` (experimental) lets the LSFG capability probe accept a
|
||||
// Vulkan 1.1/1.2 device that offers the extensions the chain needs; it has
|
||||
// to be known before vkCreateDevice, hence a constructor argument.
|
||||
VulkanRendererContext(ANativeWindow* window, int cWidth, int cHeight, void* adrenotoolsHandle = nullptr,
|
||||
bool lsfgVk11Compat = false);
|
||||
~VulkanRendererContext();
|
||||
|
||||
void onSurfaceResized(int width, int height);
|
||||
void setTransform(float ox, float oy, float sx, float sy);
|
||||
// #413: game-region clip rect (surface px). Stored and applied as the swapchain scissor in
|
||||
// recordCmdBuf so a FILL/STRETCH game confined to a half (TOP/BOTTOM alignment) can't bleed into
|
||||
// the on-screen-controls half. A full-surface (or w<=0) rect means "no clip" (CENTER).
|
||||
void setClipRegion(int x, int y, int w, int h);
|
||||
void updatePointerPosition(short x, short y);
|
||||
void updateWindowContent(int64_t id, void* pixels, short w, short h, short stride, int x, int y);
|
||||
@@ -241,13 +231,7 @@ public:
|
||||
void scanoutSetBuffer(AHardwareBuffer* ahb, int x, int y, int w, int h, int fenceFd = -1);
|
||||
void scanoutSetCursorImage(void* pixels, short w, short h, short stride);
|
||||
void scanoutSetCursorPos(short x, short y, short hotX, short hotY);
|
||||
// The renderer-neutral scanout implementation. The scanout methods above are
|
||||
// thin forwarders to this (see VulkanRendererScanout.cpp); the cursor render-
|
||||
// thread signalling stays in the forwarders, not in ScanoutContext.
|
||||
ScanoutContext scanout;
|
||||
// Bound to scanout's atomics so the existing JNI ABI (r->scanoutActive /
|
||||
// r->gameFrameDelivered direct member access) keeps compiling against a
|
||||
// single source of truth. Must be declared after `scanout`.
|
||||
std::atomic<bool>& scanoutActive = scanout.scanoutActive;
|
||||
std::atomic<bool>& gameFrameDelivered = scanout.gameFrameDelivered;
|
||||
std::atomic<bool> surfaceDetached{false};
|
||||
@@ -286,47 +270,16 @@ public:
|
||||
void setPresentMode(VkPresentModeKHR mode);
|
||||
std::vector<int> getSupportedPresentModes() const;
|
||||
|
||||
// --- Native LSFG frame generation -------------------------------------
|
||||
// Capability verdict for the compositor-side LSFG engine. Filled at device
|
||||
// creation and completed once the swapchain format is known; read by the
|
||||
// UI (through JNI) to grey the engine out with a reason.
|
||||
const lsfg::Caps& lsfgCaps() const { return lsfgCaps_; }
|
||||
|
||||
// Arm/disarm native LSFG frame generation for this session. Changing the
|
||||
// armed state recreates the swapchain, because the composite path needs
|
||||
// TRANSFER_DST usage and a deeper image queue that a normal session does
|
||||
// not pay for.
|
||||
void setFrameGenArmed(bool armed, int multiplier);
|
||||
bool frameGenArmed() const { return fgArmed_.load(std::memory_order_relaxed); }
|
||||
// Live frame-gen telemetry for the in-game readout:
|
||||
// [0] generations the governor currently trusts
|
||||
// [1] generations actually planned for the last source frame
|
||||
// [2] measured source (real) frames per second
|
||||
// [3] measured presented frames per second
|
||||
// [4] thermal status, -1 when the device gives no signal
|
||||
// [5] GPU milliseconds the chain spends per generated frame, -1 unknown
|
||||
void frameGenStats(float out[6]) const;
|
||||
// Why the selected native frame-gen engine cannot run here, for the UI:
|
||||
// -1 not known yet (caps not probed), 0 fine, 1 this driver lacks what the
|
||||
// engine needs (lsfgCaps().reason says which gate), 2 the engine failed to start.
|
||||
int frameGenProblem() const;
|
||||
// Path to the SPIR-V cache built from the user's Lossless.dll. Setting it
|
||||
// drops any existing engine so the next armed frame rebuilds from it.
|
||||
void setLsfgCachePath(const char* path);
|
||||
// Flow scale (0.25-1.0) and the panel's real refresh rate. The pacer never
|
||||
// generates above the refresh rate.
|
||||
// Experimental: `captureHeight` sizes the composite ring the chain runs on,
|
||||
// the width following the swapchain's aspect, so a phone GPU generates at
|
||||
// game-like resolution and the result is blitted up. 0 = panel;
|
||||
// kFgCaptureGame = the X screen's height (containerHeight), which already
|
||||
// carries the per-game override and render scale; otherwise a pixel height.
|
||||
static constexpr int32_t kFgCaptureGame = -1;
|
||||
void setFrameGenTuning(float flowScale, float refreshHz, int32_t captureHeight = 0);
|
||||
// Which native engine generates: 0 = LSFG (needs the cache built from the
|
||||
// user's Lossless.dll), 1 = win-fg (our own chain, embedded, needs nothing).
|
||||
// Switching drops the other engine so only one ever holds GPU resources.
|
||||
void setFrameGenEngine(int kind);
|
||||
// win-fg only: interpolation model (3/4) and performance preset (0..2).
|
||||
void setWinFgTuning(int model, int perfPreset);
|
||||
|
||||
private:
|
||||
@@ -359,11 +312,9 @@ private:
|
||||
|
||||
ANativeWindow* window;
|
||||
int surfaceWidth, surfaceHeight, containerWidth, containerHeight;
|
||||
// #413 compositor clip rect (surface px). clipRegionW<=0 => disabled (whole swapchain). Written from
|
||||
// the renderer thread (setClipRegion), read from the render thread (recordCmdBuf) -> atomic.
|
||||
std::atomic<int> clipRegionX{0}, clipRegionY{0}, clipRegionW{0}, clipRegionH{0};
|
||||
void* adrenotoolsHandle = nullptr;
|
||||
bool lsfgVk11Compat_ = false; // see the constructor
|
||||
bool lsfgVk11Compat_ = false;
|
||||
int filterMode = 0;
|
||||
bool swapRB = false;
|
||||
float maxAnisotropy = 1.0f;
|
||||
@@ -386,9 +337,6 @@ private:
|
||||
std::vector<VkImageMemoryBarrier> framePreUpload;
|
||||
std::vector<VkImageMemoryBarrier> framePostUpload;
|
||||
|
||||
// [P0] All SurfaceControl / AHardwareBuffer scanout state moved to
|
||||
// ScanoutContext (member `scanout` above). Dead members (lastDst*,
|
||||
// ScanoutPending/scanoutPending*, fnSTSetBackPressure) were dropped.
|
||||
|
||||
std::atomic<int> pointerX{0}, pointerY{0};
|
||||
float sceneOffsetX=0.f, sceneOffsetY=0.f, sceneScaleX=1.f, sceneScaleY=1.f;
|
||||
@@ -428,138 +376,69 @@ private:
|
||||
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
|
||||
// ---- Spatial upscaler (SGSR / FSR1) ----
|
||||
// upscalerMode enum (mirrored by JNI nativeSetUpscaler):
|
||||
// 0 = none (passthrough; current sampler governs scaling)
|
||||
// 1 = linear (rebuild base sampler LINEAR; no shader pass)
|
||||
// 2 = nearest (rebuild base sampler NEAREST; no shader pass)
|
||||
// 3 = sgsr (Snapdragon GSR 1.0; single pass; aspect-fit / letterbox)
|
||||
// 4 = fsr (AMD FSR1 EASU+RCAS; two passes; fill / stretch)
|
||||
// 5 = fsr_fit (AMD FSR1 EASU+RCAS; two passes; aspect-fit / letterbox)
|
||||
// 6 = sharpen (RCAS-only; any resolution; aspect-fit / letterbox)
|
||||
// 7 = nis (NVIDIA Image Scaling NVScaler; single pass; aspect-fit)
|
||||
// 8 = sgsr_quality (SGSR 1 edge-direction variant; single pass; aspect-fit)
|
||||
// Shader upscaling only engages for modes 3-8 AND when the game render
|
||||
// resolution (container) is smaller than the swapchain. Otherwise the
|
||||
// existing direct-to-swapchain path is used unchanged.
|
||||
int upscalerMode = 0;
|
||||
// Independent of upscalerMode: high-quality Lanczos downscale for the
|
||||
// supersampling case (game render res > display res). When enabled and the
|
||||
// render res exceeds the swapchain, replaces the bilinear minify with a
|
||||
// proper downscale pass. Supersampling and the upscale modes are mutually
|
||||
// exclusive in practice (one is render>display, the other render<display).
|
||||
bool hqDownscale = false;
|
||||
|
||||
// Composable post effects, independent of the scaling mode. CAS layers a sharpen
|
||||
// on top of any mode (incl. native res); HDR is a binary fake-HDR. The upscaler
|
||||
// sharpness (RCAS lobe scale / SGSR EdgeSharpness) is driven by its own slider as a
|
||||
// linear 0..1 value: 0 = neutral (no sharpening; the upscale still runs), 1 = max.
|
||||
bool casEnabled = false;
|
||||
int casSharpness = 60; // slider 0..100 -> CAS SHARPNESS
|
||||
int casSharpness = 60;
|
||||
bool hdrEnabled = false;
|
||||
float upscaleSharpness01 = 0.75f; // slider/100; RCAS lobe scale, SGSR edge derived
|
||||
// Debanding: terminal TPDF/IGN dither pass before the 8-bit swapchain quantize.
|
||||
float upscaleSharpness01 = 0.75f;
|
||||
|
||||
bool debandEnabled = false;
|
||||
int debandStrength = 100; // slider 0..200 -> strength/100 LSBs
|
||||
int debandStrength = 100;
|
||||
|
||||
// Phase 2 screen effects (GL EffectComposer parity). FXAA/Toon/CRT/NTSC are
|
||||
// binary; Color is the brightness/contrast/gamma grade (always-applied via the
|
||||
// sliders, no toggle) and is "enabled" only when not at neutral (0,0,1).
|
||||
bool fxaaEnabled = false;
|
||||
bool toonEnabled = false;
|
||||
bool crtEnabled = false;
|
||||
bool ntscEnabled = false;
|
||||
bool colorEnabled = false; // derived: !neutral grade
|
||||
float colorBrightness = 0.0f; // [-1,1] (slider/100, clamped)
|
||||
float colorContrast = 0.0f; // [0,2] (slider/100, clamped)
|
||||
float colorGamma = 1.0f; // [0.1,5]
|
||||
float colorSaturation = 1.0f; // [0,2] (slider/100, clamped)
|
||||
uint32_t ntscFrameCounter = 0; // animates NTSC chroma phase
|
||||
bool colorEnabled = false;
|
||||
float colorBrightness = 0.0f;
|
||||
float colorContrast = 0.0f;
|
||||
float colorGamma = 1.0f;
|
||||
float colorSaturation = 1.0f;
|
||||
uint32_t ntscFrameCounter = 0;
|
||||
|
||||
VkSampler upscaleSampler = VK_NULL_HANDLE; // linear clamp; offscreen/mid input
|
||||
VkSampler upscaleSampler = VK_NULL_HANDLE;
|
||||
|
||||
// === Native LSFG: composite target ring ===================================
|
||||
// Frame generation cannot composite straight into a swapchain image: the
|
||||
// finished frame has to be READABLE (it becomes the next frame's LSFG
|
||||
// input) and generated frames have to be STORAGE-WRITABLE by a compute
|
||||
// dispatch. Android swapchain images are COLOR_ATTACHMENT only, and
|
||||
// storage support on a swapchain format is not something a driver owes us.
|
||||
//
|
||||
// So when frame gen is armed the whole existing recording is redirected at
|
||||
// a composite image we own - format-identical to the swapchain, so every
|
||||
// existing pipeline stays render-pass compatible - and a copy moves it into
|
||||
// the acquired swapchain image at the end. With frame gen off, not one of
|
||||
// these objects is created and the direct-to-swapchain path is untouched.
|
||||
struct CompositeTarget {
|
||||
VkImage img = VK_NULL_HANDLE;
|
||||
VkDeviceMemory mem = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE; // colour attachment + sampled
|
||||
VkImageView storageView = VK_NULL_HANDLE; // compute writes (generate)
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VkImageView storageView = VK_NULL_HANDLE;
|
||||
VkFramebuffer fb = VK_NULL_HANDLE;
|
||||
VkDescriptorSet ds = VK_NULL_HANDLE; // sampled, for later passes
|
||||
VkDescriptorSet ds = VK_NULL_HANDLE;
|
||||
};
|
||||
// Hard ceiling: (max generations + 1) presentable frames per source frame,
|
||||
// times a queue depth of 2, capped so the footprint stays bounded.
|
||||
static constexpr uint32_t kMaxCompositeTargets = 7;
|
||||
|
||||
std::vector<CompositeTarget> compositeTargets;
|
||||
VkRenderPass compositeRenderPass = VK_NULL_HANDLE; // CLEAR -> GENERAL
|
||||
VkRenderPass compositeRenderPass = VK_NULL_HANDLE;
|
||||
uint32_t compositeW = 0, compositeH = 0;
|
||||
uint32_t compositeIndex = 0; // rotates per composite; gives history for free
|
||||
bool compositeArmed = false; // targets exist AND this frame uses them
|
||||
bool swapchainTransferDst = false; // swapchain was created with TRANSFER_DST
|
||||
uint32_t compositeIndex = 0;
|
||||
bool compositeArmed = false;
|
||||
bool swapchainTransferDst = false;
|
||||
|
||||
// Set from the app when the native LSFG engine is selected for this
|
||||
// session. Read on the render thread; false keeps every path as it was.
|
||||
std::atomic<bool> fgArmed_{false};
|
||||
std::atomic<int> fgMultiplier_{0};
|
||||
// Tuning is written by the UI thread and consumed by the render thread, so
|
||||
// it is published through atomics and applied to the engine at the top of
|
||||
// the frame-gen block. The UI thread never touches the engine itself.
|
||||
std::atomic<float> fgFlowScale_{1.0f};
|
||||
std::atomic<float> fgRefreshHz_{0.0f};
|
||||
std::atomic<bool> fgConfigDirty_{true};
|
||||
// Experimental (see setFrameGenTuning): capture height of the composite
|
||||
// ring. 0 = panel resolution, kFgCaptureGame = the X screen's height.
|
||||
std::atomic<int32_t> fgCaptureHeight_{0};
|
||||
// Extent the composite ring should have for the current swapchain and
|
||||
// capture height (width follows the swapchain aspect, both even).
|
||||
void compositeExtentFor(uint32_t& w, uint32_t& h) const;
|
||||
// The extent the scene is rendered at this frame: the composite ring while
|
||||
// frame gen runs on a ring smaller than the swapchain (experimental capture
|
||||
// resolution), otherwise the swapchain. Every full-target pass (the effect
|
||||
// chain, the spatial upscalers, the render-scale downscale) sizes itself by
|
||||
// this, so those keep working below panel resolution and their
|
||||
// intermediates shrink with the ring.
|
||||
VkExtent2D renderExtent() const {
|
||||
if (compositeActive() && (compositeW != swapchainExt.width || compositeH != swapchainExt.height))
|
||||
return VkExtent2D{compositeW, compositeH};
|
||||
return swapchainExt;
|
||||
}
|
||||
// Copy (same extent) or blit (composite smaller than the swapchain) a
|
||||
// composite-ring image, already in TRANSFER_SRC, into a swapchain image
|
||||
// already in TRANSFER_DST.
|
||||
void recordCompositeToSwapchainTransfer(VkCommandBuffer cb, VkImage src, uint32_t imgIdx);
|
||||
|
||||
bool createCompositeRenderPass();
|
||||
bool ensureCompositeTargets(uint32_t w, uint32_t h, uint32_t count);
|
||||
void destroyCompositeTargets();
|
||||
// True when this frame should composite off-swapchain. Call on the render
|
||||
// thread; every gate must hold or we fall back to the untouched path.
|
||||
bool compositeActive() const;
|
||||
// Render pass / framebuffer this frame draws its FINAL pass into.
|
||||
VkRenderPass targetRenderPass() const;
|
||||
VkFramebuffer targetFramebuffer(uint32_t imgIdx) const;
|
||||
// Copy the finished composite into the acquired swapchain image and leave
|
||||
// it in PRESENT_SRC. No-op when the composite path is not active.
|
||||
void copyCompositeToSwapchain(VkCommandBuffer cb, uint32_t imgIdx);
|
||||
|
||||
// === Native LSFG: the software cursor ====================================
|
||||
// LSFG interpolates whatever it is given, so a cursor composited into the
|
||||
// frame gets warped along the flow field and smears. It is therefore
|
||||
// excluded from the composite while frame gen is armed and drawn once into
|
||||
// EVERY presented image instead - real and generated alike - through a
|
||||
// load-op render pass that leaves the image ready to present.
|
||||
VkRenderPass cursorOverlayRenderPass = VK_NULL_HANDLE;
|
||||
struct CursorOverlay {
|
||||
bool draw = false;
|
||||
@@ -569,19 +448,16 @@ private:
|
||||
CursorOverlay cursorOverlay_{};
|
||||
|
||||
bool createCursorOverlayRenderPass();
|
||||
// Draw the cursor into an already-copied swapchain image and leave it in
|
||||
// PRESENT_SRC. Takes over the final transition from the copy helpers.
|
||||
void recordCursorOverlay(VkCommandBuffer cb, uint32_t imgIdx);
|
||||
// True when the cursor is being drawn per present rather than composited.
|
||||
bool cursorDrawnPerPresent() const;
|
||||
|
||||
// === Native LSFG: the per-source-frame present plan =======================
|
||||
// Interpolation produces frames that belong BETWEEN N-1 and N, so the
|
||||
// generated frames are presented FIRST and real frame N is held back one
|
||||
// slot. All presents for one source frame are queued together: with FIFO
|
||||
// the driver then shows them on consecutive vblanks, so the pacing falls
|
||||
// out of the present mode for free - no sleeps, no render-mode change.
|
||||
static constexpr uint32_t kMaxPresentsPerFrame = 4; // 1 real + up to 3 generated
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static constexpr uint32_t kMaxPresentsPerFrame = 4;
|
||||
struct FrameGenPlan {
|
||||
uint32_t generations = 0;
|
||||
uint32_t presents = 1;
|
||||
@@ -594,30 +470,17 @@ private:
|
||||
bool lsfgEngineTried_ = false;
|
||||
std::unique_ptr<winfg::Engine> winfgEngine_;
|
||||
bool winfgEngineTried_ = false;
|
||||
std::atomic<int> fgEngineKind_{0}; // 0 = lsfg, 1 = win-fg
|
||||
std::atomic<int> fgEngineKind_{0};
|
||||
std::atomic<int> fgModel_{4};
|
||||
std::atomic<int> fgPerfPreset_{1};
|
||||
bool ensureWinFgEngine();
|
||||
// Capability gate for the SELECTED engine. win-fg's shaders need only a
|
||||
// storage-capable swapchain format; the LSFG chain also needs the fp16 /
|
||||
// memory-model feature set, which some otherwise capable GPUs lack.
|
||||
bool fgCapsOk() const;
|
||||
|
||||
// Sync objects are indexed per PRESENT, not per composite: each pending
|
||||
// present needs its own image-available and render-finished semaphore.
|
||||
uint32_t syncSlot(uint32_t k) const { return currentFrame * kMaxPresentsPerFrame + k; }
|
||||
// Destroy and rebuild every acquire/present semaphore. Called with the
|
||||
// swapchain, so no stale pending signal can survive into the new one.
|
||||
void recreateSyncObjects();
|
||||
uint32_t fgAcquireFailLog_ = 0;
|
||||
|
||||
// Measured PRESENTS per second - the rate that actually reaches the panel,
|
||||
// counting generated frames. The pacer's own "loop rate" cannot serve this
|
||||
// purpose: it is sampled once per SOURCE frame, so it always equals the
|
||||
// guest rate and contains no evidence that generation happened at all.
|
||||
float fgPresentedRate_ = 0.0f;
|
||||
// Measured SOURCE frames per second over the same window, for engines that
|
||||
// do not measure it themselves (win-fg).
|
||||
float fgSourceRate_ = 0.0f;
|
||||
uint32_t fgSourceAccum_ = 0;
|
||||
uint32_t fgPresentAccum_ = 0;
|
||||
@@ -626,22 +489,10 @@ private:
|
||||
void trackPresentedRate(uint32_t presents);
|
||||
|
||||
bool ensureLsfgEngine();
|
||||
// One command buffer per pending present: slot 0 carries the composite,
|
||||
// the chain's shared passes and generated frame 0; each later generated
|
||||
// frame and the real frame are recorded and submitted on their own, so a
|
||||
// finished frame reaches the presentation engine without waiting for the
|
||||
// rest of the chain.
|
||||
uint32_t cmdSlot(uint32_t k) const { return currentFrame * kMaxPresentsPerFrame + k; }
|
||||
// Shared chain passes (the 24 shaders every generated frame depends on).
|
||||
void recordFrameGenProcess(VkCommandBuffer cb);
|
||||
// Generated frame g: synthesise into a spare composite target and copy it
|
||||
// into the swapchain image reserved for it.
|
||||
void recordFrameGenGeneration(VkCommandBuffer cb, uint32_t g);
|
||||
|
||||
// Chain cost: a timestamp pair per frame slot, start before the shared
|
||||
// passes, end after the last generation's compute (before its copy, so a
|
||||
// vblank wait on the swapchain image is not counted). Read back after the
|
||||
// slot's fence wait, one frame later.
|
||||
VkQueryPool fgQueryPool_ = VK_NULL_HANDLE;
|
||||
bool fgTimestampsOk_ = false;
|
||||
float fgTimestampPeriodNs_ = 0.0f;
|
||||
@@ -654,22 +505,21 @@ private:
|
||||
void readFgQueryResult();
|
||||
|
||||
VkFormat offscreenFmt = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
VkRenderPass offscreenRenderPass = VK_NULL_HANDLE; // CLEAR -> SHADER_READ_ONLY
|
||||
VkRenderPass offscreenRenderPass = VK_NULL_HANDLE;
|
||||
VkPipelineLayout postPipeLayout = VK_NULL_HANDLE;
|
||||
VkPipeline sgsrPipeline = VK_NULL_HANDLE;
|
||||
VkPipeline nisPipeline = VK_NULL_HANDLE; // NVIDIA Image Scaling (mode 7)
|
||||
VkPipeline sgsrQualityPipeline = VK_NULL_HANDLE; // SGSR 1 edge-direction (mode 8)
|
||||
VkPipeline nisPipeline = VK_NULL_HANDLE;
|
||||
VkPipeline sgsrQualityPipeline = VK_NULL_HANDLE;
|
||||
VkPipeline easuPipeline = VK_NULL_HANDLE;
|
||||
VkPipeline rcasPipeline = VK_NULL_HANDLE;
|
||||
VkPipeline downscalePipeline = VK_NULL_HANDLE;
|
||||
// CAS/HDR can be either a non-final pass (writes an fx target via
|
||||
// offscreenRenderPass) or the final pass (writes the swapchain via renderPass);
|
||||
// a pipeline is render-pass-specific, so keep one variant of each.
|
||||
VkPipeline casPipelineOff = VK_NULL_HANDLE; // -> fx target (offscreenRenderPass)
|
||||
VkPipeline casPipelineSwap = VK_NULL_HANDLE; // -> swapchain (renderPass)
|
||||
|
||||
|
||||
|
||||
VkPipeline casPipelineOff = VK_NULL_HANDLE;
|
||||
VkPipeline casPipelineSwap = VK_NULL_HANDLE;
|
||||
VkPipeline hdrPipelineOff = VK_NULL_HANDLE;
|
||||
VkPipeline hdrPipelineSwap = VK_NULL_HANDLE;
|
||||
// Phase 2 effects: same Off (-> fx target) / Swap (-> swapchain) variant pair.
|
||||
VkPipeline fxaaPipelineOff = VK_NULL_HANDLE;
|
||||
VkPipeline fxaaPipelineSwap = VK_NULL_HANDLE;
|
||||
VkPipeline toonPipelineOff = VK_NULL_HANDLE;
|
||||
@@ -680,10 +530,8 @@ private:
|
||||
VkPipeline ntscPipelineSwap = VK_NULL_HANDLE;
|
||||
VkPipeline crtPipelineOff = VK_NULL_HANDLE;
|
||||
VkPipeline crtPipelineSwap = VK_NULL_HANDLE;
|
||||
// Debanding is always the LAST effect (terminal) -> only a swapchain variant.
|
||||
VkPipeline debandPipelineSwap = VK_NULL_HANDLE;
|
||||
|
||||
// offscreen composite target @ game/container resolution
|
||||
VkImage offscreenImg = VK_NULL_HANDLE;
|
||||
VkDeviceMemory offscreenMem = VK_NULL_HANDLE;
|
||||
VkImageView offscreenView = VK_NULL_HANDLE;
|
||||
@@ -691,7 +539,6 @@ private:
|
||||
VkDescriptorSet offscreenDS = VK_NULL_HANDLE;
|
||||
int offscreenW = 0, offscreenH = 0;
|
||||
|
||||
// FSR intermediate (EASU output) @ upscale output resolution
|
||||
VkImage midImg = VK_NULL_HANDLE;
|
||||
VkDeviceMemory midMem = VK_NULL_HANDLE;
|
||||
VkImageView midView = VK_NULL_HANDLE;
|
||||
@@ -699,8 +546,6 @@ private:
|
||||
VkDescriptorSet midDS = VK_NULL_HANDLE;
|
||||
int midW = 0, midH = 0;
|
||||
|
||||
// Effect-chain intermediates @ swapchain resolution. fx1 holds the scaled (or
|
||||
// composited) image fed into CAS/HDR; fx2 ping-pongs when both effects run.
|
||||
VkImage fx1Img = VK_NULL_HANDLE;
|
||||
VkDeviceMemory fx1Mem = VK_NULL_HANDLE;
|
||||
VkImageView fx1View = VK_NULL_HANDLE;
|
||||
@@ -715,21 +560,18 @@ private:
|
||||
VkDescriptorSet fx2DS = VK_NULL_HANDLE;
|
||||
int fx2W = 0, fx2H = 0;
|
||||
|
||||
// Per-frame upscale plan, computed in renderFrame, consumed by recordCmdBuf.
|
||||
// upFrame.mode reuses the upscalerMode enum (3=sgsr,4=fsr,5=fsr_fit,6=sharpen,7=nis,8=sgsr_quality)
|
||||
// plus an internal sentinel (UPMODE_DOWNSCALE) for the supersampling path.
|
||||
struct UpscaleFrame {
|
||||
bool active = false;
|
||||
int mode = 0;
|
||||
int outX = 0, outY = 0, outW = 0, outH = 0;
|
||||
bool cas = false; // run CAS this frame (snapshot of casEnabled)
|
||||
bool hdr = false; // run HDR this frame (snapshot of hdrEnabled)
|
||||
bool fxaa = false; // snapshot of fxaaEnabled
|
||||
bool toon = false; // snapshot of toonEnabled
|
||||
bool color = false; // snapshot of colorEnabled (non-neutral grade)
|
||||
bool ntsc = false; // snapshot of ntscEnabled
|
||||
bool crt = false; // snapshot of crtEnabled
|
||||
bool deband = false;// snapshot of debandEnabled (terminal dither)
|
||||
bool cas = false;
|
||||
bool hdr = false;
|
||||
bool fxaa = false;
|
||||
bool toon = false;
|
||||
bool color = false;
|
||||
bool ntsc = false;
|
||||
bool crt = false;
|
||||
bool deband = false;
|
||||
SgsrPushConstants sgsrPC{};
|
||||
NisPushConstants nisPC{};
|
||||
EasuPushConstants easuPC{};
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_alpha.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_beta.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_chain.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_common.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_delta.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
// See lsfg_dll.h. PE resource walk + on-device SPIR-V cache for the Lossless
|
||||
// Scaling frame-generation chain.
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk (PancakeTAS). Ported to WinNative (lsfg_dll.c) and DroidDeck
|
||||
// by @maxjivi05. GPL-3.0-or-later.
|
||||
// LSFG from Camille LaVey / the Eden Emulator Project, following upstream lsfg-vk (PancakeTAS).
|
||||
// Ported to WinNative and DroidDeck by @maxjivi05. GPL-3.0-or-later.
|
||||
|
||||
#include "lsfg_dll.h"
|
||||
#include "lsfg_dxbc.h"
|
||||
@@ -25,9 +21,9 @@
|
||||
namespace lsfg {
|
||||
namespace {
|
||||
|
||||
// ---- PE constants ---------------------------------------------------------
|
||||
constexpr uint16_t kDosMagic = 0x5A4Du; // "MZ"
|
||||
constexpr uint32_t kPeSignature = 0x00004550u; // "PE\0\0"
|
||||
|
||||
constexpr uint16_t kDosMagic = 0x5A4Du;
|
||||
constexpr uint32_t kPeSignature = 0x00004550u;
|
||||
constexpr uint16_t kPe32Magic = 0x010Bu;
|
||||
constexpr uint16_t kPe32PlusMagic = 0x020Bu;
|
||||
|
||||
@@ -47,7 +43,6 @@ constexpr size_t kResourceEntrySize = 8u;
|
||||
constexpr uint32_t kResourceSubdirectoryBit = 0x80000000u;
|
||||
constexpr uint32_t kResourceTypeRcData = 10u;
|
||||
|
||||
// ---- SPIR-V constants -----------------------------------------------------
|
||||
constexpr uint32_t kSpirvMagic = 0x07230203u;
|
||||
constexpr size_t kSpirvHeaderWords = 5u;
|
||||
constexpr uint32_t kSpirvWordCountShift = 16u;
|
||||
@@ -64,7 +59,7 @@ constexpr uint32_t kVariantFp32Offset = 98u;
|
||||
constexpr uint32_t kMaxResourceId = 512u;
|
||||
constexpr size_t kMaxSpirvWords = 16u * 1024u * 1024u;
|
||||
|
||||
constexpr uint32_t kCacheMagic = 0x4C534642u; // "BFSL" little-endian
|
||||
constexpr uint32_t kCacheMagic = 0x4C534642u;
|
||||
constexpr uint32_t kCacheVersion = 1u;
|
||||
|
||||
struct CacheHeader {
|
||||
@@ -83,7 +78,6 @@ struct PeSection {
|
||||
uint32_t rawSize = 0;
|
||||
};
|
||||
|
||||
// Read-only mapping of the DLL. Closes itself; the DLL is never executed.
|
||||
class PeImage {
|
||||
public:
|
||||
~PeImage() { close(); }
|
||||
@@ -168,7 +162,6 @@ private:
|
||||
std::vector<PeSection> sections_;
|
||||
};
|
||||
|
||||
// RCDATA id -> blob, for the ids we care about.
|
||||
struct ResourceTable {
|
||||
const uint8_t* data[kMaxResourceId] = {};
|
||||
uint32_t size[kMaxResourceId] = {};
|
||||
@@ -217,7 +210,6 @@ bool resourceReadLeaf(const PeImage& img, size_t leafOffset,
|
||||
return true;
|
||||
}
|
||||
|
||||
// Walk type -> name -> language and keep the first language of every RCDATA id.
|
||||
bool collectRcData(const PeImage& img, size_t resourceBase, ResourceTable& out) {
|
||||
size_t typeTotal = 0;
|
||||
if (!resourceEntryCount(img, resourceBase, typeTotal)) return false;
|
||||
@@ -256,7 +248,7 @@ bool collectRcData(const PeImage& img, size_t resourceBase, ResourceTable& out)
|
||||
if (!resourceReadLeaf(img, resourceBase + langOffset, blob, blobSize)) continue;
|
||||
out.data[nameId] = blob;
|
||||
out.size[nameId] = blobSize;
|
||||
break; // first language wins
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,7 +281,6 @@ DllStatus parseResources(PeImage& img, ResourceTable& table) {
|
||||
return DllStatus::Ok;
|
||||
}
|
||||
|
||||
// ---- SPIR-V adoption (precompiled-variant path) ---------------------------
|
||||
|
||||
bool isSpirvModule(const uint8_t* blob, uint32_t size) {
|
||||
if (!blob || size < kSpirvHeaderWords * sizeof(uint32_t)) return false;
|
||||
@@ -315,9 +306,6 @@ uint32_t lookupDescriptorSet(const std::vector<uint32_t>& w, uint32_t targetId)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Precompiled blobs are renumbered into set/binding order - that is the
|
||||
// convention they were built with. The DXBC path uses encounter order instead
|
||||
// (see lsfg_dxbc), which is what DXVK's output pairs with. Do not merge them.
|
||||
bool renumberBindingsSetOrder(std::vector<uint32_t>& w) {
|
||||
struct Slot { uint32_t set; uint32_t binding; size_t literalOffset; };
|
||||
std::vector<Slot> slots;
|
||||
@@ -399,8 +387,6 @@ bool writeCache(const std::string& cachePath, const CacheHeader& header, const M
|
||||
if (ok) ok = fsync(fileno(f)) == 0;
|
||||
fclose(f);
|
||||
|
||||
// Temp file + rename, so a failed or interrupted build can never leave a
|
||||
// half-written cache that would later be loaded as if it were complete.
|
||||
if (!ok || rename(tempPath.c_str(), cachePath.c_str()) != 0) {
|
||||
unlink(tempPath.c_str());
|
||||
return false;
|
||||
@@ -416,9 +402,8 @@ bool readCacheHeader(const std::string& cachePath, CacheHeader& out) {
|
||||
return ok && out.magic == kCacheMagic && out.version == kCacheVersion;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
}
|
||||
|
||||
// ---- public API -----------------------------------------------------------
|
||||
|
||||
const std::vector<uint32_t>& shaderIds() {
|
||||
static const std::vector<uint32_t> ids = [] {
|
||||
@@ -584,7 +569,6 @@ DllStatus loadModules(const std::string& cachePath, ModuleSet& outSet) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// ---- SPIR-V version lowering (Vulkan 1.1/1.2 compat) ----------------------
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -593,14 +577,14 @@ constexpr uint32_t kSpirvOpExtInstImport= 11u;
|
||||
constexpr uint32_t kSpirvOpMemoryModel = 14u;
|
||||
constexpr uint32_t kSpirvOpCapability = 17u;
|
||||
|
||||
// Capabilities that are core in the version the translator emits but not in
|
||||
// the versions we lower to.
|
||||
constexpr uint32_t kCapVulkanMemoryModel = 5345u; // core from 1.5
|
||||
constexpr uint32_t kCapVulkanMemoryModelDeviceScope = 5346u; // core from 1.5
|
||||
constexpr uint32_t kCapDemoteToHelperInvocation = 5379u; // core from 1.6
|
||||
|
||||
|
||||
constexpr uint32_t kCapVulkanMemoryModel = 5345u;
|
||||
constexpr uint32_t kCapVulkanMemoryModelDeviceScope = 5346u;
|
||||
constexpr uint32_t kCapDemoteToHelperInvocation = 5379u;
|
||||
|
||||
std::vector<uint32_t> encodeOpExtension(const char* name) {
|
||||
const size_t len = strlen(name) + 1; // incl. NUL
|
||||
const size_t len = strlen(name) + 1;
|
||||
const size_t strWords = (len + 3) / 4;
|
||||
std::vector<uint32_t> ins(1 + strWords, 0u);
|
||||
ins[0] = ((uint32_t)(1 + strWords) << kSpirvWordCountShift) | kSpirvOpExtension;
|
||||
@@ -624,17 +608,13 @@ bool hasOpExtension(const std::vector<uint32_t>& w, const char* name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
}
|
||||
|
||||
bool downgradeSpirv(std::vector<uint32_t>& w, uint32_t targetVersion) {
|
||||
if (w.size() < kSpirvHeaderWords || w[0] != kSpirvMagic) return false;
|
||||
if (targetVersion < kSpirv14) return false;
|
||||
if (w[1] <= targetVersion) return true; // already low enough
|
||||
if (w[1] <= targetVersion) return true;
|
||||
|
||||
// Walk the preamble first and only touch the module once it is known to
|
||||
// be lowerable: note which extension-gated capabilities are declared and
|
||||
// where the capability block ends (OpExtension must follow every
|
||||
// OpCapability and precede OpExtInstImport / OpMemoryModel).
|
||||
bool needMemoryModel = false;
|
||||
size_t insertAt = kSpirvHeaderWords;
|
||||
size_t off = kSpirvHeaderWords;
|
||||
@@ -645,9 +625,6 @@ bool downgradeSpirv(std::vector<uint32_t>& w, uint32_t targetVersion) {
|
||||
if (opcode == kSpirvOpCapability && length >= 2) {
|
||||
const uint32_t cap = w[off + 1];
|
||||
if (cap == kCapVulkanMemoryModel || cap == kCapVulkanMemoryModelDeviceScope) needMemoryModel = true;
|
||||
// Core only in 1.6, and VK_EXT_shader_demote_to_helper_invocation
|
||||
// is never enabled on the compat path: refuse rather than hand the
|
||||
// driver a module that is not legal at the lowered version.
|
||||
if (cap == kCapDemoteToHelperInvocation) return false;
|
||||
insertAt = off + length;
|
||||
} else if (opcode == kSpirvOpExtension) {
|
||||
@@ -659,7 +636,6 @@ bool downgradeSpirv(std::vector<uint32_t>& w, uint32_t targetVersion) {
|
||||
off += length;
|
||||
}
|
||||
|
||||
// 1.5 has the memory model as core; only 1.4 needs it declared.
|
||||
if (targetVersion < kSpirv15 && needMemoryModel
|
||||
&& !hasOpExtension(w, "SPV_KHR_vulkan_memory_model")) {
|
||||
const auto e = encodeOpExtension("SPV_KHR_vulkan_memory_model");
|
||||
@@ -669,4 +645,4 @@ bool downgradeSpirv(std::vector<uint32_t>& w, uint32_t targetVersion) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace lsfg
|
||||
}
|
||||
@@ -1,35 +1,7 @@
|
||||
// LSFG from Camille LaVey / the Eden Emulator Project, following upstream lsfg-vk (PancakeTAS).
|
||||
// Ported to WinNative and DroidDeck by @maxjivi05. GPL-3.0-or-later.
|
||||
|
||||
#pragma once
|
||||
// ============================================================================
|
||||
// lsfg_dll - extract the Lossless Scaling frame-generation shader chain from
|
||||
// the user's own Lossless.dll, and cache it on device as SPIR-V.
|
||||
//
|
||||
// The 25 compute shaders that make up LSFG 3.1 are NOT redistributable. They
|
||||
// ship as RCDATA resources inside Lossless.dll from the user's own paid copy
|
||||
// of Lossless Scaling (Steam app 993090). Every implementation - upstream
|
||||
// lsfg-vk, LSFG-Android, eden, WinNative - requires the user to supply their
|
||||
// own DLL and extracts the resources on device. DroidDeck does the same, and
|
||||
// bundles nothing.
|
||||
//
|
||||
// The DLL is mmap'd READ-ONLY and parsed as data. It is never loaded as a
|
||||
// library and never executed.
|
||||
//
|
||||
// Resource layout, measured against real installs:
|
||||
// * The base chain lives at RCDATA ids 255, 256 and 280-302 (25 modules),
|
||||
// and is DXBC. It has always been DXBC.
|
||||
// * Lossless Scaling MAY additionally carry precompiled SPIR-V copies at
|
||||
// base+49 (native fp16) and base+98 (native fp32). No build currently
|
||||
// downloadable from Steam carries them - public buildId 19655272 was
|
||||
// measured with zero SPIR-V anywhere in the 311 MB install - so the DXBC
|
||||
// path is the normal path and the translator is not optional. The SPIR-V
|
||||
// path is kept because it costs nothing and a future build may ship them.
|
||||
//
|
||||
// Both producers fill the same shader-id -> SPIR-V words map, so everything
|
||||
// downstream of this file is source-agnostic.
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk (PancakeTAS). Ported to WinNative (lsfg_dll.c) and DroidDeck
|
||||
// by @maxjivi05. GPL-3.0-or-later.
|
||||
// ============================================================================
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -39,44 +11,37 @@ namespace lsfg {
|
||||
|
||||
enum class DllStatus {
|
||||
Ok = 0,
|
||||
NotInstalled, // no such file / cannot open
|
||||
UnreadableFile, // opens but cannot be mapped or is empty
|
||||
NotInstalled,
|
||||
UnreadableFile,
|
||||
NotPortableExecutable,
|
||||
MissingShaders, // parses, but the base chain is not all there
|
||||
TranslationFailed, // a shader failed DXBC->SPIR-V or SPIR-V adoption
|
||||
CacheUnusable // cache could not be written or read back
|
||||
MissingShaders,
|
||||
TranslationFailed,
|
||||
CacheUnusable
|
||||
};
|
||||
|
||||
// SPIR-V version words. The translator emits 1.6; a Vulkan 1.1 device takes
|
||||
// 1.4 (via VK_KHR_spirv_1_4) and a 1.2 device 1.5. One definition for the
|
||||
// probe, the engine and the shader loader.
|
||||
constexpr uint32_t kSpirv14 = 0x00010400u;
|
||||
constexpr uint32_t kSpirv15 = 0x00010500u;
|
||||
constexpr uint32_t kSpirv16 = 0x00010600u;
|
||||
|
||||
enum class Variant {
|
||||
None = 0,
|
||||
SpirvFp16, // precompiled fp16 blobs at base+49
|
||||
SpirvFp32, // precompiled fp32 blobs at base+98
|
||||
DxbcTranslated // base chain, translated on device (the normal case)
|
||||
SpirvFp16,
|
||||
SpirvFp32,
|
||||
DxbcTranslated
|
||||
};
|
||||
|
||||
// The 25 shader ids that make up the chain.
|
||||
constexpr uint32_t kShaderMipmaps = 255u;
|
||||
constexpr uint32_t kShaderGenerate = 256u;
|
||||
constexpr uint32_t kShaderChainFirst= 280u;
|
||||
constexpr uint32_t kShaderChainLast = 302u;
|
||||
constexpr uint32_t kShaderCount = 25u;
|
||||
|
||||
// Upstream spelling for the two ids the ported chain files refer to by name.
|
||||
// Kept so those files stay byte-close to their upstream form; these alias the
|
||||
// constants above rather than being a second source of truth.
|
||||
constexpr uint32_t LSFG_SHADER_MIPMAPS = kShaderMipmaps;
|
||||
constexpr uint32_t LSFG_SHADER_GENERATE = kShaderGenerate;
|
||||
|
||||
struct Module {
|
||||
uint32_t id = 0;
|
||||
std::vector<uint32_t> words; // SPIR-V
|
||||
std::vector<uint32_t> words;
|
||||
};
|
||||
|
||||
struct ModuleSet {
|
||||
@@ -87,41 +52,19 @@ struct ModuleSet {
|
||||
bool complete() const;
|
||||
};
|
||||
|
||||
// The canonical shader-id list, in dispatch-independent order.
|
||||
const std::vector<uint32_t>& shaderIds();
|
||||
|
||||
const char* statusName(DllStatus s);
|
||||
const char* variantName(Variant v);
|
||||
|
||||
// Does this file look like a usable Lossless.dll? Checks the base chain only,
|
||||
// so a DLL without the SPIR-V variants (i.e. every build on Steam today) is
|
||||
// reported as valid - the translator handles it.
|
||||
DllStatus validateDll(const std::string& dllPath);
|
||||
|
||||
// Parse the DLL, produce all 25 SPIR-V modules, and write them to cachePath
|
||||
// (via temp file + rename, so a failed build cannot leave a half-written
|
||||
// cache behind). Records which producer ran in the cache header.
|
||||
DllStatus buildCache(const std::string& dllPath, const std::string& cachePath, bool preferFp16);
|
||||
|
||||
// Load a previously built cache.
|
||||
DllStatus loadModules(const std::string& cachePath, ModuleSet& outSet);
|
||||
|
||||
// Which producer built an existing cache, without loading the modules.
|
||||
DllStatus cacheVariant(const std::string& cachePath, Variant& outVariant);
|
||||
|
||||
// Experimental Vulkan 1.1 / 1.2 compat: lower a DXBC-translated module's
|
||||
// declared SPIR-V version to `targetVersion` (kSpirv14 or kSpirv15). Below 1.5
|
||||
// the Vulkan memory model is not core, so SPV_KHR_vulkan_memory_model is
|
||||
// declared for it (the device side enables VK_KHR_vulkan_memory_model). The
|
||||
// cache is built without a device in hand, always at the translator's native
|
||||
// 1.6, so this runs at load time against whatever the device can take. A
|
||||
// module already at or below the target is left untouched. Returns false,
|
||||
// leaving the module untouched, for a malformed module, a target below 1.4, or
|
||||
// a module declaring DemoteToHelperInvocation: that capability is core only in
|
||||
// 1.6 and its extension is never enabled on the compat path, so such a module
|
||||
// cannot be made legal here (DXVK emits it for pixel-shader discard only, so
|
||||
// the 25 compute shaders never carry it). Whether the device then accepts the
|
||||
// result is vkCreateShaderModule's / vkCreateComputePipelines' verdict.
|
||||
bool downgradeSpirv(std::vector<uint32_t>& words, uint32_t targetVersion);
|
||||
|
||||
} // namespace lsfg
|
||||
}
|
||||
@@ -1,10 +1,5 @@
|
||||
// See lsfg_dxbc.h. DXBC -> SPIR-V via DXVK's vendored translator.
|
||||
//
|
||||
// Follows upstream lsfg-vk's src/extract/trans.cpp, including its
|
||||
// encounter-order binding renumber. Ported to WinNative (lsfg_dxbc.cpp) and
|
||||
// DroidDeck by @maxjivi05 from the work of Camille LaVey / the Eden Emulator
|
||||
// Project (GPL-3.0-or-later); the translator itself is DXVK's, zlib licensed,
|
||||
// vendored under cpp/thirdparty/dxbc.
|
||||
// Ported to WinNative and DroidDeck by @maxjivi05 from Camille LaVey / the Eden Emulator Project (GPL-3.0-or-later).
|
||||
// DXVK's translator is zlib licensed; vendored under cpp/thirdparty/dxbc.
|
||||
|
||||
#include "lsfg_dxbc.h"
|
||||
|
||||
@@ -21,12 +16,12 @@ namespace {
|
||||
|
||||
struct BindingSlot {
|
||||
uint32_t bindingIndex = 0;
|
||||
uint32_t bindingOffset = 0; // word offset of the Binding literal
|
||||
uint32_t bindingOffset = 0;
|
||||
uint32_t setIndex = 0;
|
||||
uint32_t setOffset = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
}
|
||||
|
||||
bool translateDxbc(const uint8_t* bytecode, uint32_t size, std::vector<uint32_t>& outWords) {
|
||||
outWords.clear();
|
||||
@@ -38,9 +33,6 @@ bool translateDxbc(const uint8_t* bytecode, uint32_t size, std::vector<uint32_t>
|
||||
const dxvk::DxbcModuleInfo info{};
|
||||
auto code = module.compile(info, "CS");
|
||||
|
||||
// Collect the descriptor decorations in the order the Binding
|
||||
// decorations appear. Everything before the first OpFunction is the
|
||||
// decoration block, so the scan stops there.
|
||||
std::vector<BindingSlot> slotsByVarId;
|
||||
std::vector<uint32_t> varIdsInOrder;
|
||||
for (auto ins : code) {
|
||||
@@ -60,7 +52,6 @@ bool translateDxbc(const uint8_t* bytecode, uint32_t size, std::vector<uint32_t>
|
||||
if (ins.opCode() == spv::OpFunction) break;
|
||||
}
|
||||
|
||||
// Renumber to a dense 0..n range, in encounter order.
|
||||
uint32_t next = 0;
|
||||
for (const uint32_t varId : varIdsInOrder) {
|
||||
const BindingSlot& slot = slotsByVarId[varId];
|
||||
@@ -75,11 +66,9 @@ bool translateDxbc(const uint8_t* bytecode, uint32_t size, std::vector<uint32_t>
|
||||
std::memcpy(outWords.data(), code.data(), byteSize);
|
||||
return true;
|
||||
} catch (...) {
|
||||
// DXVK's compiler signals malformed bytecode by throwing. A bad
|
||||
// resource must degrade to "this DLL is unusable", never to a crash.
|
||||
outWords.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lsfg
|
||||
}
|
||||
@@ -1,39 +1,11 @@
|
||||
#pragma once
|
||||
// ============================================================================
|
||||
// lsfg_dxbc - DXBC -> SPIR-V for the Lossless Scaling shader chain.
|
||||
//
|
||||
// Why this exists at all: the base chain inside Lossless.dll is DXBC, and has
|
||||
// always been DXBC. Lossless Scaling 3.2.2's release note ("Added shaders
|
||||
// intended for use by the lsfg-vk project") describes precompiled SPIR-V
|
||||
// copies at RCDATA base+49 / base+98 - but no build downloadable from Steam
|
||||
// carries them. Measured against the current public build (buildId 19655272,
|
||||
// 2025-08-19; linux_testing byte-identical on depot 993091): the whole 311 MB
|
||||
// install contains zero occurrences of the SPIR-V magic word, and the DLL's
|
||||
// 202 RCDATA entries are all DXBC.
|
||||
//
|
||||
// So a translator is required, not optional. Upstream lsfg-vk reaches the same
|
||||
// conclusion and links DXVK's `dxbc` in src/extract/trans.cpp; we vendor the
|
||||
// same subset (zlib licence) under cpp/thirdparty/dxbc and follow its
|
||||
// trans.cpp step for step.
|
||||
//
|
||||
// The binding renumber here is ENCOUNTER ORDER - the order in which Binding
|
||||
// decorations appear in DXVK's output - which is what DXVK's own layout pairs
|
||||
// with. The precompiled-SPIR-V path in lsfg_dll.cpp uses a set/binding sort
|
||||
// instead, because those blobs were built with that convention. The two must
|
||||
// not be merged.
|
||||
// ============================================================================
|
||||
// DXVK's dxbc subset is zlib licensed; vendored under cpp/thirdparty/dxbc.
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace lsfg {
|
||||
|
||||
// Translate one DXBC compute shader to SPIR-V, renumbering descriptor bindings
|
||||
// into a dense 0..n range in encounter order. Returns false (leaving outWords
|
||||
// empty) on any malformed input - DXVK's compiler throws, and everything is
|
||||
// caught here.
|
||||
// Emits DXVK's native SPIR-V 1.6; lowering for a Vulkan 1.1/1.2 device happens
|
||||
// at load time (lsfg_dll.h downgradeSpirv).
|
||||
bool translateDxbc(const uint8_t* bytecode, uint32_t size, std::vector<uint32_t>& outWords);
|
||||
|
||||
} // namespace lsfg
|
||||
}
|
||||
@@ -1,8 +1,3 @@
|
||||
// See lsfg_engine.h.
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative (vkr_lsfg.cpp) and DroidDeck by
|
||||
// @maxjivi05, where it runs on gamescope's output in the compositor.
|
||||
|
||||
#include "lsfg_engine.h"
|
||||
|
||||
@@ -21,9 +16,6 @@
|
||||
namespace lsfg {
|
||||
namespace {
|
||||
|
||||
// The chain needs two real frames in its history before anything it produces
|
||||
// is meaningful, and we require the plan to stay warm for a couple of frames
|
||||
// so a single settled sample cannot start generation from garbage.
|
||||
constexpr uint64_t kRequiredFrames = 2;
|
||||
constexpr uint32_t kRecurrenceFrames = 2;
|
||||
constexpr uint64_t kTelemetryInterval = 120;
|
||||
@@ -50,8 +42,6 @@ VkImageMemoryBarrier makeTransition(VkImage image, VkAccessFlags srcAccess,
|
||||
return b;
|
||||
}
|
||||
|
||||
// Copy the just-composited frame into the chain's input ring. `source` is the
|
||||
// composite target, which the composite render pass leaves in GENERAL.
|
||||
void copyPresentedFrame(VkCommandBuffer cmd, VkImage source, LsfgImage& destination,
|
||||
VkExtent2D extent) {
|
||||
const VkImageMemoryBarrier before[] = {
|
||||
@@ -88,7 +78,7 @@ void copyPresentedFrame(VkCommandBuffer cmd, VkImage source, LsfgImage& destinat
|
||||
destination.SetLayout(VK_IMAGE_LAYOUT_GENERAL);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
}
|
||||
|
||||
Engine::Engine() = default;
|
||||
|
||||
@@ -142,9 +132,6 @@ void Engine::setGuestExtent(uint32_t width, uint32_t height) {
|
||||
peakGuestExtent_.height = std::max(peakGuestExtent_.height, height);
|
||||
}
|
||||
|
||||
// The flow pyramid is the expensive part, so its resolution is scaled relative
|
||||
// to the largest guest surface seen: a game running well below panel
|
||||
// resolution does not need a panel-resolution flow field.
|
||||
float Engine::effectiveFlowScale(uint32_t width) const {
|
||||
if (width == 0 || peakGuestExtent_.width == 0) return flowScale_;
|
||||
const float ratio = (float)peakGuestExtent_.width / (float)width;
|
||||
@@ -171,8 +158,6 @@ bool Engine::prepare(uint32_t width, uint32_t height, VkFormat format) {
|
||||
chain_ = std::make_unique<LsfgChain>(device_, *shaders_, VkExtent2D{width, height}, format,
|
||||
scale);
|
||||
if (!chain_->Valid()) {
|
||||
// A device that cannot build the chain reports unsupported once and
|
||||
// stops trying, rather than thrashing allocations every frame.
|
||||
LSFG_LOGW("chain build failed at %ux%u; frame generation unavailable", width, height);
|
||||
chain_.reset();
|
||||
unavailable_ = true;
|
||||
@@ -204,17 +189,9 @@ uint32_t Engine::plan(uint32_t capacity, uint64_t sourceFrames) {
|
||||
|
||||
plan_ = pacer_.Plan(std::min<size_t>(capacity, kMaxGenerations), sourceFrames);
|
||||
|
||||
// The pacer says how many frames FIT in the panel's budget. The governor
|
||||
// says how many this device can actually afford right now - on a handheld
|
||||
// the chain competes with the game for one GPU, so an extra generation has
|
||||
// to prove it improves total output without collapsing the real frame rate.
|
||||
if (governorEnabled_) {
|
||||
const LsfgPacerStats s = pacer_.Stats();
|
||||
governor_.configure((uint32_t)std::min<size_t>(pacer_.MaxGenerations(), kMaxGenerations));
|
||||
// presentedRate_, NOT the pacer's loop rate. The pacer samples its loop
|
||||
// once per SOURCE frame, so its "loop rate" is the guest rate by
|
||||
// construction and can never show that generation added anything - the
|
||||
// governor could not accept a probe on any hardware.
|
||||
plan_.generations = governor_.cap((uint32_t)plan_.generations, s.source_rate,
|
||||
presentedRate_);
|
||||
}
|
||||
@@ -223,12 +200,6 @@ uint32_t Engine::plan(uint32_t capacity, uint64_t sourceFrames) {
|
||||
warmStreak_ = warm_ ? warmStreak_ + 1 : 0;
|
||||
generating_ = warm_ && warmStreak_ >= kRecurrenceFrames && plan_.generations > 0;
|
||||
|
||||
// The ring is only seeded while generating, so the first frame after any
|
||||
// gap - arming, or a pacer discontinuity after a hitch - would interpolate
|
||||
// between this frame and whatever the other slot held when generation last
|
||||
// stopped. That is a visible warp, and it happens every time the engine
|
||||
// starts. Spend this frame filling the ring instead and generate from the
|
||||
// next one, when both slots are genuinely consecutive.
|
||||
primeHistory_ = false;
|
||||
if (generating_ && !historyFresh()) {
|
||||
primeHistory_ = true;
|
||||
@@ -265,11 +236,6 @@ void Engine::process(VkCommandBuffer cmd, VkImage source, uint32_t width, uint32
|
||||
lastCount_ = count;
|
||||
lastGenerations_ = generations;
|
||||
|
||||
// Seeding the input ring is a full-resolution image copy every frame, so it
|
||||
// is NOT free at 100 fps - it was costing real frame rate while producing
|
||||
// nothing. Copy only when generating, on the one priming frame that
|
||||
// precedes generation, or while the governor is warming up towards a
|
||||
// probe - so the ring is populated by the time it is needed.
|
||||
const bool needHistory = generations > 0 || primeHistory_ || followEveryFrame()
|
||||
|| (governorEnabled_ && governor_.wantsHistory());
|
||||
if (needHistory) {
|
||||
@@ -278,13 +244,6 @@ void Engine::process(VkCommandBuffer cmd, VkImage source, uint32_t width, uint32
|
||||
haveCopied_ = true;
|
||||
}
|
||||
|
||||
// The SHARED chain is 24 of the 25 shaders - the whole flow pyramid - and
|
||||
// only `generate` runs per generated frame. Running it while producing
|
||||
// nothing spends almost the entire cost of frame generation for no frames
|
||||
// at all, every frame. That is what pinned the GPU at 100% on device
|
||||
// regardless of the game's own settings, and it fed straight back into the
|
||||
// governor: the chain made the source rate collapse, the governor saw the
|
||||
// collapse and refused to generate, and refusing did not stop the chain.
|
||||
if (warm_ && (generations > 0 || followEveryFrame())) chain_->DispatchShared(cmd, count);
|
||||
}
|
||||
|
||||
@@ -318,4 +277,4 @@ void Engine::reset() {
|
||||
plan_ = {};
|
||||
}
|
||||
|
||||
} // namespace lsfg
|
||||
}
|
||||
@@ -1,25 +1,4 @@
|
||||
#pragma once
|
||||
// ============================================================================
|
||||
// lsfg_engine - the compositor's handle on native LSFG frame generation.
|
||||
//
|
||||
// Owns the shader modules, the interpolation chain and the pacer, and exposes
|
||||
// the small contract the render loop needs:
|
||||
//
|
||||
// prepare(w, h, format) build/rebuild the chain for this size
|
||||
// plan(capacity, sourceFrames) how many frames to generate this time
|
||||
// process(cmd, source, w, h, gens) take frame N in, run the shared chain
|
||||
// generateInto(cmd, g, i, img, view) synthesise generated frame g
|
||||
//
|
||||
// Ordering matters and is not obvious: interpolation produces frames that
|
||||
// belong BETWEEN N-1 and N, so the generated frames are presented FIRST and
|
||||
// the real frame N is held back one slot. That one output interval is the
|
||||
// unavoidable cost of interpolating rather than extrapolating; no placement of
|
||||
// this code changes it.
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative (vkr_lsfg.cpp) and DroidDeck by
|
||||
// @maxjivi05, where it runs on gamescope's output in the compositor.
|
||||
// ============================================================================
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -28,7 +7,7 @@
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include "lsfg_common.hpp"
|
||||
#include "lsfg_dll.h" // kSpirv16
|
||||
#include "lsfg_dll.h"
|
||||
#include "lsfg_governor.h"
|
||||
#include "lsfg_pacer.hpp"
|
||||
|
||||
@@ -37,27 +16,16 @@ namespace lsfg {
|
||||
class LsfgShaders;
|
||||
class LsfgChain;
|
||||
|
||||
// Hard ceiling on generated frames per source frame (2x..4x -> 1..3).
|
||||
constexpr uint32_t kMaxGenerations = 3;
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
// Both the constructor and destructor are out-of-line ON PURPOSE. The
|
||||
// shader set and the chain are held by unique_ptr to forward-declared
|
||||
// types, and an inline constructor would make the compiler emit the
|
||||
// exception-cleanup path for those members at every construction site -
|
||||
// which needs the complete types there. Keeping both here confines that to
|
||||
// lsfg_engine.cpp, where the types are complete.
|
||||
Engine();
|
||||
~Engine();
|
||||
|
||||
Engine(const Engine&) = delete;
|
||||
Engine& operator=(const Engine&) = delete;
|
||||
|
||||
// Build the shader modules from a cache produced by lsfg_dll::buildCache.
|
||||
// Returns false if the cache does not yield all 25 modules. `spirvTarget`
|
||||
// is the highest SPIR-V version the device accepts (lsfg_probe.h); the
|
||||
// cached 1.6 modules are lowered to it when it is below that.
|
||||
bool init(VkDevice device, VkPhysicalDevice physicalDevice, const std::string& cachePath,
|
||||
uint32_t spirvTarget = kSpirv16);
|
||||
|
||||
@@ -71,28 +39,18 @@ public:
|
||||
bool needsRebuild(uint32_t width, uint32_t height, VkFormat format) const;
|
||||
bool prepare(uint32_t width, uint32_t height, VkFormat format);
|
||||
|
||||
// How many frames to generate for this source frame. Returns 0 until the
|
||||
// history slots are valid and the rates have settled, so the first frames
|
||||
// after a start or a resize are never interpolated from garbage.
|
||||
uint32_t plan(uint32_t capacity, uint64_t sourceFrames);
|
||||
|
||||
// Take the just-composited frame as LSFG input and run the shared part of
|
||||
// the chain (everything except `generate`). `source` must be in GENERAL.
|
||||
void process(VkCommandBuffer cmd, VkImage source, uint32_t width, uint32_t height,
|
||||
uint32_t generations);
|
||||
|
||||
// Synthesise generated frame `generation` into the given storage image.
|
||||
void generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t targetIndex,
|
||||
VkImage targetImage, VkImageView targetView,
|
||||
uint32_t width, uint32_t height);
|
||||
|
||||
// What the probe governor currently trusts, and the latest thermal
|
||||
// reading (-1 = no signal). Surfaced for the HUD and diagnostics.
|
||||
uint32_t acceptedGenerations() const { return governor_.accepted(); }
|
||||
int thermalStatus() const { return governor_.thermalStatus(); }
|
||||
float sourceRate() const;
|
||||
// The rate that actually reaches the panel, generated frames included.
|
||||
// Supplied by the renderer, which is the only thing that counts presents.
|
||||
float loopRate() const { return presentedRate_; }
|
||||
void setPresentedRate(float fps) { presentedRate_ = fps; }
|
||||
|
||||
@@ -102,16 +60,8 @@ public:
|
||||
private:
|
||||
float effectiveFlowScale(uint32_t width) const;
|
||||
|
||||
// A generated frame is a blend of the last two REAL frames, so the input
|
||||
// ring - which is two images deep - only holds usable history when the
|
||||
// other slot was filled by the frame immediately before this one. It is
|
||||
// not, every time generation resumes after a pause: the ring is seeded
|
||||
// only while generating, so the slot still carries whatever was there when
|
||||
// generation last stopped.
|
||||
bool historyFresh() const { return haveCopied_ && lastCopiedCount_ + 1 == frameCount_; }
|
||||
|
||||
// With a target rate the pacer plans 0..3 generations frame by frame, so the ring and the
|
||||
// shared chain have to follow every frame it keeps warm, not just the ones that generate.
|
||||
bool followEveryFrame() const { return warm_ && pacer_.Config().target_rate != 0; }
|
||||
|
||||
Device device_{};
|
||||
@@ -120,12 +70,6 @@ private:
|
||||
std::unique_ptr<LsfgChain> chain_;
|
||||
LsfgPacer pacer_;
|
||||
ProbeGovernor governor_;
|
||||
// The governor is OFF by default: the device's own thermal management is
|
||||
// the authority, and a probe loop that can veto the user's setting is not
|
||||
// wanted here. The multiplier is then what was asked for, still clamped by
|
||||
// what the panel can show. Kept behind a flag rather than deleted because
|
||||
// the probe/backoff logic is the right answer for a device WITHOUT decent
|
||||
// native throttling, and this is the only thing that would have to change.
|
||||
bool governorEnabled_ = false;
|
||||
LsfgPlan plan_{};
|
||||
|
||||
@@ -151,4 +95,4 @@ private:
|
||||
bool unavailable_{};
|
||||
};
|
||||
|
||||
} // namespace lsfg
|
||||
}
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_gamma.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_generate.hpp"
|
||||
#include "lsfg_dll.h"
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// See lsfg_governor.h.
|
||||
|
||||
#include "lsfg_governor.h"
|
||||
|
||||
@@ -12,13 +11,10 @@
|
||||
namespace lsfg {
|
||||
namespace {
|
||||
|
||||
// A probe has to run long enough to survive a single unlucky frame, and the
|
||||
// baseline long enough to be a fair comparison.
|
||||
constexpr float kBaselineSeconds = 2.0f;
|
||||
constexpr float kProbeSeconds = 2.0f;
|
||||
constexpr uint32_t kMinSamples = 30;
|
||||
|
||||
// Keep the extra generation only if it actually paid off.
|
||||
constexpr float kOutputGainRequired = 1.15f;
|
||||
constexpr float kSourceFloor = 0.70f;
|
||||
|
||||
@@ -28,20 +24,20 @@ constexpr uint32_t kBackoffSteps = 4;
|
||||
constexpr float kThermalPollSeconds = 2.0f;
|
||||
constexpr float kThermalDecaySeconds = 1.0f;
|
||||
|
||||
// ADEVICE_THERMAL_STATUS_* from <android/thermal.h>, which is API 30. Resolved
|
||||
// by dlsym so the build keeps working below that and on devices that do not
|
||||
// ship the service; -1 simply means "no thermal signal", never "hot".
|
||||
// Android reports a STATUS, not a temperature - NONE 0, LIGHT 1, MODERATE 2,
|
||||
// SEVERE 3, CRITICAL 4, EMERGENCY 5, SHUTDOWN 6 - so there is no degree value
|
||||
// here to raise. The equivalent of "let it run hotter" is to act on a higher
|
||||
// status, which is what these now do.
|
||||
//
|
||||
// A handheld under a real 3D load sits at SEVERE routinely; blocking there
|
||||
// made the governor refuse on a device that was working exactly as intended.
|
||||
// It now only stops GROWING at SEVERE and only gives frames back at CRITICAL,
|
||||
// leaving the device's own thermal management as the authority.
|
||||
constexpr int kThermalBlock = 4; // CRITICAL: give generations back
|
||||
constexpr int kThermalNoGrowth = 3; // SEVERE: keep what we have, stop probing
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
constexpr int kThermalBlock = 4;
|
||||
constexpr int kThermalNoGrowth = 3;
|
||||
|
||||
using PFN_AThermal_acquireManager = void* (*)();
|
||||
using PFN_AThermal_getCurrentThermalStatus = int (*)(void*);
|
||||
@@ -72,17 +68,12 @@ float phaseSeconds(std::chrono::steady_clock::time_point a,
|
||||
return std::chrono::duration<float>(b - a).count();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
}
|
||||
|
||||
void ProbeGovernor::configure(uint32_t maxGenerations) {
|
||||
if (maxGenerations_ == maxGenerations) return;
|
||||
// Raising the ceiling must NOT throw away what we already know works -
|
||||
// going 2x -> 4x used to reset the accepted level to zero and restart the
|
||||
// whole baseline/probe cycle, so a user raising the multiplier got FEWER
|
||||
// frames. Only clamp down to a ceiling that has actually been lowered.
|
||||
maxGenerations_ = maxGenerations;
|
||||
if (accepted_ > maxGenerations_) accepted_ = maxGenerations_;
|
||||
// Re-measure against the new ceiling, but keep the accepted level.
|
||||
phase_ = Phase::Baseline;
|
||||
havePhaseStart_ = false;
|
||||
sourceAccum_ = loopAccum_ = 0.0f;
|
||||
@@ -132,19 +123,8 @@ uint32_t ProbeGovernor::cap(uint32_t requested, float sourceRate, float loopRate
|
||||
if (!havePhaseStart_) enterBaseline(now);
|
||||
pollThermal(now);
|
||||
|
||||
// Thermal override: at SEVERE and above we stop asking for more and give
|
||||
// generations back, but SLOWLY.
|
||||
//
|
||||
// This runs once per source frame - about forty times a second - so the
|
||||
// previous version's unconditional accepted_-- was not "give one back", it
|
||||
// was "slam to zero in three frames", and its unconditional enterBackoff()
|
||||
// reset the backoff clock every single call, so the backoff could never
|
||||
// expire and the governor could never measure or recover. One decay per
|
||||
// second, and no churn at all once there is nothing left to give back.
|
||||
thermalBlocked_ = thermal_ >= kThermalBlock;
|
||||
if (thermal_ >= kThermalBlock) {
|
||||
// Logged, sparingly. This is a REFUSAL TO EVEN MEASURE, and it was
|
||||
// silent: on device it looked identical to "measured and declined".
|
||||
if ((refusalLog_++ % 600u) == 0u)
|
||||
GOV_LOGI("thermal status %d - not probing (accepted=%u)", thermal_, accepted_);
|
||||
const bool decayDue = !haveThermalDecay_
|
||||
@@ -183,12 +163,10 @@ uint32_t ProbeGovernor::cap(uint32_t requested, float sourceRate, float loopRate
|
||||
sourceAccum_ = loopAccum_ = 0.0f;
|
||||
samples_ = 0;
|
||||
} else {
|
||||
// Also previously silent. "Baseline completed but we did not
|
||||
// probe" is a decision worth seeing in a log.
|
||||
if ((refusalLog_++ % 600u) == 0u)
|
||||
GOV_LOGI("not probing: accepted=%u max=%u requested=%u thermal=%d",
|
||||
accepted_, maxGenerations_, requested, thermal_);
|
||||
enterBaseline(now); // nothing to try; measure again later
|
||||
enterBaseline(now);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -226,12 +204,10 @@ uint32_t ProbeGovernor::cap(uint32_t requested, float sourceRate, float loopRate
|
||||
break;
|
||||
}
|
||||
|
||||
// While probing we allow one more than is currently accepted; otherwise the
|
||||
// accepted level is the ceiling, and the pacer's own answer still applies.
|
||||
const uint32_t allowed = phase_ == Phase::Probing
|
||||
? std::min(accepted_ + 1, maxGenerations_)
|
||||
: accepted_;
|
||||
return std::min(requested, allowed);
|
||||
}
|
||||
|
||||
} // namespace lsfg
|
||||
}
|
||||
@@ -1,29 +1,4 @@
|
||||
#pragma once
|
||||
// ============================================================================
|
||||
// lsfg_governor - decides whether an extra generated frame is actually WORTH
|
||||
// it on this device, right now.
|
||||
//
|
||||
// The ported pacer answers "how many frames fit in the panel's budget". That
|
||||
// is necessary but not sufficient on a handheld: the LSFG chain competes with
|
||||
// the game for the same GPU, so a naive fixed 3x on a GPU-bound title lowers
|
||||
// the real frame rate by more than the generated frames add. The user sees a
|
||||
// higher HUD number and a worse-looking game.
|
||||
//
|
||||
// So the multiplier is treated as a ceiling to earn, not a setting to obey.
|
||||
// The governor starts at 0 generations, periodically PROBES one more, and
|
||||
// keeps it only if the probe measurably paid off:
|
||||
//
|
||||
// * total output rate must improve by at least 1.15x, and
|
||||
// * the real (source) frame rate must not collapse below 0.70x baseline.
|
||||
//
|
||||
// A failed probe reverts immediately and backs off 5s, 15s, 30s, then 60s, so
|
||||
// a device that cannot afford another generation stops being asked.
|
||||
//
|
||||
// Thermals are the second input a phone needs and a desktop does not.
|
||||
// Sustained frame generation is a thermal decision as much as a performance
|
||||
// one, so the governor gives back generations as the device heats and refuses
|
||||
// to probe while it is throttling.
|
||||
// ============================================================================
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
@@ -34,22 +9,15 @@ class ProbeGovernor {
|
||||
public:
|
||||
void configure(uint32_t maxGenerations);
|
||||
|
||||
// Cap the pacer's answer. Called once per source frame with the measured
|
||||
// rates; returns the number of generations actually permitted.
|
||||
uint32_t cap(uint32_t requested, float sourceRate, float loopRate);
|
||||
|
||||
void reset();
|
||||
|
||||
uint32_t accepted() const { return accepted_; }
|
||||
bool probing() const { return phase_ == Phase::Probing; }
|
||||
// Whether the chain's input ring needs to be kept fed. Seeding it is a
|
||||
// full-resolution copy every frame, so it is skipped during the long idle
|
||||
// stretches - a backoff, or a thermal block - and resumed during Baseline,
|
||||
// which gives it a couple of seconds to fill before a probe can start.
|
||||
bool wantsHistory() const {
|
||||
return !thermalBlocked_ && phase_ != Phase::Backoff;
|
||||
}
|
||||
// Latest thermal reading, 0 (none) .. 6 (shutdown); -1 when unavailable.
|
||||
int thermalStatus() const { return thermal_; }
|
||||
|
||||
private:
|
||||
@@ -68,11 +36,9 @@ private:
|
||||
Clock::time_point phaseStart_{};
|
||||
bool havePhaseStart_ = false;
|
||||
|
||||
// Rolling means over the current phase window.
|
||||
float sourceAccum_ = 0.0f, loopAccum_ = 0.0f;
|
||||
uint32_t samples_ = 0;
|
||||
|
||||
// What the accepted level achieves, measured during Baseline.
|
||||
float baselineSource_ = 0.0f, baselineLoop_ = 0.0f;
|
||||
bool haveBaseline_ = false;
|
||||
|
||||
@@ -87,4 +53,4 @@ private:
|
||||
bool haveThermalPoll_ = false;
|
||||
};
|
||||
|
||||
} // namespace lsfg
|
||||
}
|
||||
@@ -1,8 +1,3 @@
|
||||
// JNI surface for native LSFG shader-cache management.
|
||||
//
|
||||
// Deliberately free of any renderer handle: extracting the shader chain from
|
||||
// Lossless.dll and caching it is a slow, once-per-DLL operation that the app
|
||||
// runs off the render thread, long before a game launches.
|
||||
|
||||
#include "lsfg_dll.h"
|
||||
|
||||
@@ -20,7 +15,7 @@ std::string toStdString(JNIEnv* env, jstring s) {
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_droiddeck_launcher_gpu_LsfgNative_nativeValidateDll(JNIEnv* env, jclass, jstring dllPath) {
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_mipmaps.hpp"
|
||||
#include "lsfg_dll.h"
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_pacer.hpp"
|
||||
|
||||
@@ -21,7 +17,6 @@ constexpr float SOURCE_SMOOTHING = 0.15f;
|
||||
constexpr float SOURCE_STALE_SECONDS = 0.5f;
|
||||
constexpr float DISCONTINUITY_SECONDS = 0.25f;
|
||||
constexpr float HEADROOM_EPSILON = 0.02f;
|
||||
// A target is aimed just under, so rounding never puts output past the panel's refresh rate.
|
||||
constexpr float TARGET_MARGIN = 0.1f;
|
||||
constexpr float CREDIT_EPSILON = 1.0e-4f;
|
||||
constexpr float SOURCE_ACCUM_FLOOR = 0.01f;
|
||||
@@ -121,17 +116,6 @@ LsfgPlan LsfgPacer::Plan(size_t capacity, uint64_t source_frames) {
|
||||
if (target_rate > 0.0f) target_rate -= TARGET_MARGIN;
|
||||
|
||||
if (target_rate == 0.0f) {
|
||||
// No explicit target rate: the multiplier is the multiplier, full stop.
|
||||
// The panel-headroom clamp was here briefly and was removed at the
|
||||
// user's decision; this is also upstream's behaviour on this branch.
|
||||
//
|
||||
// What that means above the panel's refresh rate, for the record: the
|
||||
// display cannot show more frames than it refreshes, so under FIFO the
|
||||
// surplus presents queue and the render thread waits on acquire. Nothing
|
||||
// is displayed faster, and the compositor falls behind on guest
|
||||
// deliveries instead. Below the refresh rate - every source at or under
|
||||
// panel/4 - this branch behaves identically with or without the clamp.
|
||||
// To restore it: limit = std::min(ceiling, HeadroomLimit()).
|
||||
output_credit = 0.0f;
|
||||
limit = ceiling;
|
||||
return LsfgPlan{limit, true};
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// See lsfg_probe.h. Capability gate for native compositor-side LSFG.
|
||||
|
||||
#include "lsfg_probe.h"
|
||||
#include "../VulkanRendererContext.h"
|
||||
@@ -16,7 +15,7 @@ bool hasExtension(const std::vector<VkExtensionProperties>& exts, const char* na
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
}
|
||||
|
||||
FeatureSupport queryFeatures(const VkTable& vk, VkPhysicalDevice pd,
|
||||
const std::vector<VkExtensionProperties>& deviceExtensions,
|
||||
@@ -33,33 +32,21 @@ FeatureSupport queryFeatures(const VkTable& vk, VkPhysicalDevice pd,
|
||||
|
||||
const bool below12 = props.apiVersion < VK_API_VERSION_1_2;
|
||||
if (below12) {
|
||||
// Below 1.2 there is no VkPhysicalDeviceVulkan12Features to chain, and
|
||||
// the SPIR-V 1.6 modules will not load anyway. Unless the experimental
|
||||
// compat path is on AND the driver offers the extensions that make the
|
||||
// chain loadable as SPIR-V 1.4, don't touch the device further:
|
||||
// device creation stays exactly as it is today.
|
||||
if (!allowVk11) return fs;
|
||||
fs.hasSpirv14 = hasExtension(deviceExtensions, VK_KHR_SPIRV_1_4_EXTENSION_NAME);
|
||||
fs.hasFloatControls = hasExtension(deviceExtensions, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
|
||||
fs.hasMemoryModelExt = hasExtension(deviceExtensions, VK_KHR_VULKAN_MEMORY_MODEL_EXTENSION_NAME);
|
||||
if (!fs.hasSpirv14 || !fs.hasFloatControls || !fs.hasMemoryModelExt) {
|
||||
// Report what was looked at so explain() can name the gap.
|
||||
fs.queried = true;
|
||||
return fs;
|
||||
}
|
||||
fs.extensionPath = true;
|
||||
fs.spirvTarget = kSpirv14;
|
||||
} else if (!fs.apiAtLeast13 && allowVk11) {
|
||||
// 1.2: SPIR-V 1.5 is core, VulkanMemoryModel is core. Nothing to
|
||||
// enable beyond the features; the modules only need their header
|
||||
// lowered from 1.6 to 1.5. Same experimental gate as the 1.1 path so
|
||||
// a production build keeps rejecting 1.2 exactly as before.
|
||||
fs.extensionPath = true;
|
||||
fs.spirvTarget = kSpirv15;
|
||||
}
|
||||
|
||||
// On 1.2+ the Vulkan12Features struct carries the memory model; below
|
||||
// that the KHR struct does (same sType value, same layout).
|
||||
VkPhysicalDeviceVulkan12Features v12{};
|
||||
v12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
|
||||
VkPhysicalDeviceVulkanMemoryModelFeaturesKHR mm{};
|
||||
@@ -87,7 +74,7 @@ FeatureSupport queryFeatures(const VkTable& vk, VkPhysicalDevice pd,
|
||||
std::vector<const char*> extensionNames(const FeatureSupport& f) {
|
||||
std::vector<const char*> out;
|
||||
if (!f.extensionPath || f.deviceApiVersion >= VK_API_VERSION_1_2) return out;
|
||||
out.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); // dependency of spirv_1_4
|
||||
out.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
|
||||
out.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME);
|
||||
out.push_back(VK_KHR_VULKAN_MEMORY_MODEL_EXTENSION_NAME);
|
||||
return out;
|
||||
@@ -108,10 +95,6 @@ bool probeStorageFormat(const VkTable& vk, VkPhysicalDevice pd, VkFormat fmt) {
|
||||
VkFormatProperties fp{};
|
||||
vk.GetPhysicalDeviceFormatProperties(pd, fmt, &fp);
|
||||
|
||||
// The composite target is COLOR_ATTACHMENT (effect chain writes it),
|
||||
// SAMPLED (next frame's LSFG input reads it), STORAGE (generate writes it)
|
||||
// and both blit ends (it is copied - or, below panel resolution, blitted -
|
||||
// into the swapchain image).
|
||||
const VkFormatFeatureFlags need =
|
||||
VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT
|
||||
| VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT
|
||||
@@ -163,4 +146,4 @@ void explain(Caps& caps) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lsfg
|
||||
}
|
||||
@@ -1,45 +1,17 @@
|
||||
#pragma once
|
||||
// ============================================================================
|
||||
// lsfg_probe - capability gate for native (compositor-side) LSFG frame
|
||||
// generation.
|
||||
//
|
||||
// The Lossless Scaling chain is 25 compute shaders that DXVK's DXBC translator
|
||||
// emits as SPIR-V 1.6 with OpCapability VulkanMemoryModel and
|
||||
// StorageImageWriteWithoutFormat. Three consequences, all checked here:
|
||||
//
|
||||
// * SPIR-V 1.6 will not load on a Vulkan 1.1 device, so the DEVICE (not just
|
||||
// the instance) must report 1.3+ ... OR, on the experimental Vulkan 1.1
|
||||
// compat path, offer VK_KHR_spirv_1_4 + VK_KHR_vulkan_memory_model so the
|
||||
// same modules can be handed over as SPIR-V 1.4 (see downgradeSpirv in
|
||||
// lsfg_dll.h). The stock Qualcomm driver on Adreno 7xx phones reports
|
||||
// 1.1.128 yet carries every one of those extensions (measured on an
|
||||
// Adreno 710 / HyperOS device with `cmd gpu vkjson`).
|
||||
// * vulkanMemoryModel, shaderStorageImageWriteWithoutFormat and
|
||||
// shaderStorageImageExtendedFormats must be ENABLED at device creation.
|
||||
// Today the renderer enables no features at all, so all three are off.
|
||||
// * `generate` writes into a storage image, and Android swapchain formats
|
||||
// are frequently not storage-capable - so the format is probed separately
|
||||
// once the swapchain has picked one.
|
||||
//
|
||||
// A device failing any gate reports unsupported UP FRONT, with a reason, so
|
||||
// the UI can grey the engine out instead of failing later inside
|
||||
// vkCreateShaderModule or vkCreateComputePipelines.
|
||||
// ============================================================================
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "lsfg_dll.h" // kSpirv14/15/16
|
||||
#include "lsfg_dll.h"
|
||||
|
||||
struct VkTable;
|
||||
|
||||
namespace lsfg {
|
||||
|
||||
// Which of the three required features the physical device OFFERS. Queried
|
||||
// before vkCreateDevice; what we actually enable is recorded in Caps below.
|
||||
struct FeatureSupport {
|
||||
bool queried = false; // vkGetPhysicalDeviceFeatures2 resolved
|
||||
bool queried = false;
|
||||
bool apiAtLeast13 = false;
|
||||
bool vulkanMemoryModel = false;
|
||||
bool vulkanMemoryModelDeviceScope= false;
|
||||
@@ -47,65 +19,41 @@ struct FeatureSupport {
|
||||
bool storageImageExtendedFormats = false;
|
||||
uint32_t deviceApiVersion = 0;
|
||||
|
||||
// Experimental Vulkan 1.1 compat: the device is below 1.3 but offers the
|
||||
// extensions that make the chain loadable. When set, the renderer must
|
||||
// enable those extensions at device creation (see extensionNames) and the
|
||||
// modules are downgraded to `spirvTarget` at load.
|
||||
bool extensionPath = false;
|
||||
bool hasSpirv14 = false; // VK_KHR_spirv_1_4
|
||||
bool hasFloatControls = false; // VK_KHR_shader_float_controls (spirv_1_4 depends on it)
|
||||
bool hasMemoryModelExt = false; // VK_KHR_vulkan_memory_model
|
||||
bool hasSpirv14 = false;
|
||||
bool hasFloatControls = false;
|
||||
bool hasMemoryModelExt = false;
|
||||
uint32_t spirvTarget = kSpirv16;
|
||||
|
||||
// Every hard device-level gate passes (format is checked separately).
|
||||
bool deviceGatesPass() const {
|
||||
return queried && (apiAtLeast13 || extensionPath) && vulkanMemoryModel
|
||||
&& storageImageWriteWithoutFormat && storageImageExtendedFormats;
|
||||
}
|
||||
};
|
||||
|
||||
// What was actually enabled + the running verdict. Owned by the renderer.
|
||||
struct Caps {
|
||||
FeatureSupport features;
|
||||
bool featuresEnabled = false; // the chain was passed to vkCreateDevice
|
||||
bool featuresEnabled = false;
|
||||
bool storageOnSwapchainFormat = false;
|
||||
// The swapchain format blits with VK_FILTER_LINEAR (needs
|
||||
// SAMPLED_IMAGE_FILTER_LINEAR on the source). Not a gate: without it the
|
||||
// capture-resolution upscale falls back to NEAREST.
|
||||
bool linearBlitOnSwapchainFormat = false;
|
||||
VkFormat probedFormat = VK_FORMAT_UNDEFINED;
|
||||
char reason[160] = "not probed";
|
||||
|
||||
// The single question the UI and the render path ask.
|
||||
bool supported() const {
|
||||
return featuresEnabled && features.deviceGatesPass() && storageOnSwapchainFormat;
|
||||
}
|
||||
};
|
||||
|
||||
// Ask the physical device which of the required features it offers.
|
||||
// Safe on any driver: if vkGetPhysicalDeviceFeatures2 cannot be resolved, or
|
||||
// the device reports below Vulkan 1.2 and `allowVk11` is false, nothing is
|
||||
// chained and `queried` is left false - the caller then creates the device
|
||||
// exactly as it always has. With `allowVk11`, a 1.1 device is accepted when
|
||||
// `deviceExtensions` lists VK_KHR_spirv_1_4, VK_KHR_shader_float_controls and
|
||||
// VK_KHR_vulkan_memory_model, and the memory-model features are queried
|
||||
// through the KHR struct instead of VkPhysicalDeviceVulkan12Features.
|
||||
FeatureSupport queryFeatures(const VkTable& vk, VkPhysicalDevice pd,
|
||||
const std::vector<VkExtensionProperties>& deviceExtensions,
|
||||
bool allowVk11);
|
||||
|
||||
// The device extensions the extension path needs enabled, in the order they
|
||||
// should be pushed onto VkDeviceCreateInfo. Empty unless extensionPath.
|
||||
std::vector<const char*> extensionNames(const FeatureSupport& f);
|
||||
|
||||
// Probe VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT on the live swapchain format.
|
||||
// `generate` writes into an image of this format via a compute dispatch.
|
||||
bool probeStorageFormat(const VkTable& vk, VkPhysicalDevice pd, VkFormat fmt);
|
||||
|
||||
// Whether the format may be the source of a VK_FILTER_LINEAR blit.
|
||||
bool probeLinearBlit(const VkTable& vk, VkPhysicalDevice pd, VkFormat fmt);
|
||||
|
||||
// Fill caps.reason with the FIRST gate that failed (or "supported").
|
||||
void explain(Caps& caps);
|
||||
|
||||
} // namespace lsfg
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_shaders.hpp"
|
||||
#include "lsfg_common.hpp"
|
||||
@@ -27,12 +23,6 @@ LsfgShaders::LsfgShaders(const Device& device_, const std::string& cache_path,
|
||||
return;
|
||||
}
|
||||
|
||||
// Vulkan 1.1/1.2 compat: the cache is always built at the translator's
|
||||
// native SPIR-V 1.6 (no device exists at import time), so lower each module
|
||||
// to what THIS device accepts. A no-op on 1.3+ devices. Only the
|
||||
// DXBC-translated chain is lowered: downgradeSpirv knows the capabilities
|
||||
// DXVK emits, not whatever a precompiled vendor SPIR-V variant may carry,
|
||||
// and relabelling one of those could hand the driver invalid SPIR-V.
|
||||
uint32_t lowered = 0;
|
||||
for (Module& module : set.modules) {
|
||||
if (module.words.size() > 1 && module.words[1] > spirv_target) {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -20,8 +16,6 @@ class Device;
|
||||
class LsfgShaders {
|
||||
public:
|
||||
LsfgShaders() = default;
|
||||
// `spirv_target` is the highest SPIR-V version the device accepts (see
|
||||
// lsfg_probe.h); cached modules above it are lowered before creation.
|
||||
LsfgShaders(const Device& device, const std::string& cache_path,
|
||||
uint32_t spirv_target = kSpirv16);
|
||||
~LsfgShaders();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// See lsfg_vkd.h. Fills the chain's dispatch from the renderer's own table.
|
||||
|
||||
#include "lsfg_vkd.h"
|
||||
#include "../VulkanRendererContext.h"
|
||||
|
||||
@@ -1,24 +1,4 @@
|
||||
#pragma once
|
||||
// ============================================================================
|
||||
// lsfg_vkd - the Vulkan entry points the LSFG chain calls, as a single global.
|
||||
//
|
||||
// Two dispatch styles meet here. DroidDeck's compositor resolves Vulkan
|
||||
// through a per-context VkTable (`vk_`), because an adrenotools-loaded driver
|
||||
// lives in an isolated linker namespace and shares no global symbols with the
|
||||
// system loader. The LSFG chain, ported from Eden by way of WinNative, calls
|
||||
// through a file-scope `vkd`. Rather than rewrite ~2,300 lines of proven chain
|
||||
// code to thread a table through every call, this shim exposes exactly the 34
|
||||
// entry points the chain uses and is filled from the renderer's own table when
|
||||
// the chain is created.
|
||||
//
|
||||
// That keeps the ported files close to their upstream form, which matters:
|
||||
// they are the part of this feature that is hardest to review by eye and
|
||||
// easiest to break with a well-meaning edit.
|
||||
//
|
||||
// Single-renderer assumption: there is one Vulkan compositor per process, so
|
||||
// one global table is correct. lsfgVkdInit() is idempotent and simply
|
||||
// overwrites; lsfgVkdReady() reports whether every pointer resolved.
|
||||
// ============================================================================
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
@@ -63,8 +43,5 @@ struct LsfgVkDispatch {
|
||||
|
||||
extern LsfgVkDispatch vkd;
|
||||
|
||||
// Fill `vkd` from the renderer's table. Returns false (and leaves the chain
|
||||
// unusable) if any entry point failed to resolve - better to report the engine
|
||||
// unsupported than to call through a null pointer mid-frame.
|
||||
bool lsfgVkdInit(const VkTable& table);
|
||||
bool lsfgVkdReady();
|
||||
@@ -1,6 +1,3 @@
|
||||
// win-fg - runtime config: enable gate, model, multiplier, and the synthesis
|
||||
// tuning parameters. Sourced from environment variables and an optional
|
||||
// conf.toml (a config file, when present, wins over the env defaults).
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
@@ -12,143 +9,110 @@
|
||||
namespace winfg {
|
||||
|
||||
struct Config {
|
||||
bool enabled = false; // gate; loader also honours the manifest enable var
|
||||
// GRANULAR PRESENT-PATH DEBUG TRACE. Default OFF. When ON, winfg_QueuePresentKHR
|
||||
// emits a per-frame, step-by-step trail (logcat tag "win-fg") so a freeze/
|
||||
// crash-on-enable leaves an obvious LAST line at the exact stage that hung or
|
||||
// faulted (acquire-spare, compute-submit, present-generated/real, fence wait,
|
||||
// fallback). Zero behaviour change and effectively zero cost when off - one
|
||||
// predicted-not-taken bool test per step, no logcat writes.
|
||||
// WIN_FG_DEBUG=1|0 / conf.toml debug=on|off.
|
||||
bool enabled = false;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool debug = false;
|
||||
// ── EVEN-CADENCE FRAME PACING ─────────────────────────────────────────────
|
||||
// Default ON. The 2x insert path presents the GENERATED frame then the REAL
|
||||
// frame back-to-back in one vkQueuePresentKHR hook call, so on Mailbox/
|
||||
// Immediate both land in ~one instant followed by a full frame-interval gap
|
||||
// until the next pair - uneven in time → judder ("slows down to catch up").
|
||||
// With pacing on, the layer measures the real-frame interval (EMA of a
|
||||
// monotonic clock) and holds the REAL present to its scheduled beat so the
|
||||
// generated frame lands near the temporal MIDPOINT between consecutive real
|
||||
// frames (even gen,real,gen,real cadence). Pure bounded CPU sleep - no GPU
|
||||
// wait added; if a frame is already behind schedule the wait is SKIPPED
|
||||
// (present immediately) so worst case is one un-paced pair, never a hitch.
|
||||
// FIFO self-paces so the waits collapse to ~0 there. Off ⇒ byte-identical
|
||||
// back-to-back behaviour. WIN_FG_PACING=on|off / conf.toml pacing=on|off.
|
||||
// ISOLATION BUILD: default flipped to OFF to A/B the AYANEO-750 post-setting-change
|
||||
// flicker regression against the pacing feature (everything else byte-identical).
|
||||
bool pacing = false;
|
||||
int model = 4; // 3 = symmetric flow, 4 = bidir + occlusion gate
|
||||
int multiplier = 2; // generated presents per real present + 1
|
||||
// EXTRA SWAPCHAIN IMAGE HEADROOM (device-freeze fix). On top of the +1 spare the
|
||||
// insert path needs, request this many ADDITIONAL images at swapchain creation:
|
||||
// requested minImageCount = app_min + 1 (spare) + extra_images,
|
||||
// clamped to surfaceCaps.maxImageCount (0 = unlimited -> no clamp), never below
|
||||
// app_min+1. The extra headroom stops the guest's NEXT vkAcquireNextImageKHR from
|
||||
// starving while win-fg holds a spare + a real frame in flight to the AHB-backed
|
||||
// host compositor. Some SoCs (Adreno 840 / Turnip) recycle displayed AHBs too
|
||||
// slowly to tolerate the extra present with only the +1 spare and freeze on the
|
||||
// first inserted frame; the AYANEO (Adreno 750) recycles fast enough to tolerate
|
||||
// it. Default 2 (a few MB VRAM), applied on all devices. On a device that already
|
||||
// works this only grows the pool - no FPS/quality change; the pool-headroom guard
|
||||
// in QueuePresentKHR stays a no-op when headroom is adequate.
|
||||
// WIN_FG_EXTRA_IMAGES / conf.toml extra_images.
|
||||
int model = 4;
|
||||
int multiplier = 2;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int extraImages = 2;
|
||||
// ── FLOW-RESOLUTION PERFORMANCE PRESET ────────────────────────────────────
|
||||
// Trades base-FPS-drop against optical-flow quality by selecting the FINEST
|
||||
// pyramid level the dense flow is solved at (FrameGen::kFlowFinest):
|
||||
// 0 = Quality -> kFlowFinest = 1 (~1/2-res flow; best motion, biggest FG cost)
|
||||
// 1 = Balanced -> kFlowFinest = 2 (~1/4-res; TODAY's behaviour) [DEFAULT]
|
||||
// 2 = Performance -> kFlowFinest = 3 (~1/8-res; cheapest -> smallest base-FPS drop)
|
||||
// Out-of-range clamps to Balanced (1), so an unset/older conf.toml is byte-
|
||||
// identical to today. Changing this LIVE rebuilds the affected per-size flow
|
||||
// images (like a resize) inside the layer and resets the flow predictor, so a
|
||||
// preset change is fully hot - no FG toggle needed. See FrameGen::configure /
|
||||
// FrameGen::flowFinestForPreset. WIN_FG_PERF_PRESET / conf.toml perf_preset.
|
||||
int perfPreset = 1; // 0 quality, 1 balanced (default), 2 performance
|
||||
float flowScale = 1.0f; // scales solved flow magnitude
|
||||
// C1 GLOBAL-MOTION PRE-WARP. Estimate the per-frame camera affine (LK) and
|
||||
// remove it before the dense SAD flow search so the search only sees coherent
|
||||
// object-only residual motion (kills fast-pan "melt"). 0 = auto (engage only
|
||||
// when the LK solve is stable), 1 = on (force-engage once a solve exists),
|
||||
// 2 = off (never pre-warp; identity affine → pipeline == pre-C1 exactly).
|
||||
// WIN_FG_GM=auto|on|off / conf.toml global_motion=auto|on|off.
|
||||
int gmMode = 0; // 0 auto, 1 on, 2 off
|
||||
// C2 FLOW REGULARIZATION (TV-L1 smoothness prior). After C1 removes the camera
|
||||
// motion, the dense flow (flowLvl_ at kFlowFinest, 1/4-res) is the OBJECT-only
|
||||
// residual; it still carries spurious per-block vectors. C2 runs N semi-implicit
|
||||
// TV-L1 denoising iterations on that field - an L1 data term + edge-aware Total-
|
||||
// Variation regularizer - to kill incoherent vectors while KEEPING true motion
|
||||
// discontinuities (object edges) sharp. Solved in-place; expand's median + C1
|
||||
// add-back consume the cleaned field unchanged. 0 iterations ⇒ byte-identical to
|
||||
// pre-C2. See shaders/of3_flowreg.comp.
|
||||
// WIN_FG_FLOWREG=auto|on|off / conf.toml flow_reg=auto|on|off.
|
||||
int frMode = 0; // 0 auto (engaged), 1 on (engaged), 2 off
|
||||
int frIters = 4; // TV-L1 iterations at kFlowFinest (0 ⇒ off)
|
||||
float frLambda = 2.0f; // L1 data-fidelity weight (higher ⇒ trust SAD flow)
|
||||
float frDt = 0.25f; // semi-implicit smoothing step (higher ⇒ smoother)
|
||||
float frEdge = 8.0f; // luma-gradient edge sensitivity for the TV weight g
|
||||
float frEps = 0.05f; // Charbonnier epsilon (px) for the TV diffusivity
|
||||
// synthesis (wfg_synth) tuning
|
||||
float beta = 8.0f; // softmax sharpness on importance Z
|
||||
float lambda = 0.6f; // FB-consistency vs photometric weight
|
||||
// Tightened 2026-08-17 to prioritise "no ghost" over max sharpness - the
|
||||
// remaining single-frame ghosts came from pixels that the previous defaults
|
||||
// still trusted with warp when they should have cross-faded. Raising both
|
||||
// pushes borderline pixels toward the safe cross-fade path (softer, but no
|
||||
// smear). Prewarp will lift the ceiling further; this is the "safety net"
|
||||
// knob change until then.
|
||||
// Walked back from (0.10, 9.0) tighten after user reported softening. The
|
||||
// real fix for the residual ghosts is the 3a-fallback path change in
|
||||
// layer.cpp (present real curr on fallback instead of blitting synth over);
|
||||
// the gate can stay closer to the original values now.
|
||||
float epsilon = 0.07f; // disocclusion floor (0.05 -> 0.10 -> 0.07)
|
||||
float photoScale = 7.5f; // photometric residual scale into Z (6.0 -> 9.0 -> 7.5)
|
||||
// HUD exclusion rect in pixel coords (x0,y0,x1,y1). Fragments inside are
|
||||
// passed through as the real current frame (no warp, no synth) so text /
|
||||
// overlays don't ghost. Disabled when x0 >= x1 or y0 >= y1 (the default).
|
||||
// Sourced from WIN_FG_HUD_RECT="x0,y0,x1,y1" env var or conf.toml
|
||||
// (hudRect="x0,y0,x1,y1"). Pattern is Isygold's Vegas DXVK framegen -
|
||||
// host knows where the HUD is, tell the shader to skip it.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int perfPreset = 1;
|
||||
float flowScale = 1.0f;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int gmMode = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int frMode = 0;
|
||||
int frIters = 4;
|
||||
float frLambda = 2.0f;
|
||||
float frDt = 0.25f;
|
||||
float frEdge = 8.0f;
|
||||
float frEps = 0.05f;
|
||||
|
||||
float beta = 8.0f;
|
||||
float lambda = 0.6f;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
float epsilon = 0.07f;
|
||||
float photoScale = 7.5f;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
float hudX0 = 0.0f;
|
||||
float hudY0 = 0.0f;
|
||||
float hudX1 = 0.0f;
|
||||
float hudY1 = 0.0f;
|
||||
|
||||
// ── TRAINING-DATA CAPTURE MODE (Route-B VFI dataset collection) ───────────
|
||||
// A dev-only gate. When OFF (default) the present path is byte-identical to a
|
||||
// build without this feature - a single bool test and nothing else. When ON,
|
||||
// every real present is downscaled on the GPU, read back async, and written to
|
||||
// disk as lossless QOI (motion-rich 256² triplet patches by default, or full
|
||||
// downscaled frames) plus a JSONL manifest, for offline (i-1,i+1)->i training.
|
||||
// NEVER captures win-fg's generated frames - capture reads the real swapchain
|
||||
// image at the TOP of QueuePresentKHR, before any interpolation.
|
||||
// WIN_FG_CAPTURE=on|off / conf.toml capture=on|off.
|
||||
bool capture = false;
|
||||
// Downscale target box (aspect-preserving, never upscales). WIN_FG_CAPTURE_W/H,
|
||||
// conf capture_width/capture_height.
|
||||
int captureW = 1280;
|
||||
int captureH = 720;
|
||||
// 0 = patch (self-contained aligned triplets, DEFAULT), 1 = frame (full
|
||||
// downscaled frames in order). WIN_FG_CAPTURE_MODE=patch|frame.
|
||||
int capMode = 0;
|
||||
int capPatches = 3; // patches per triplet in patch mode (WIN_FG_CAPTURE_PATCHES)
|
||||
int capPatchSize = 256; // patch edge in downscaled px (WIN_FG_CAPTURE_PATCH)
|
||||
// Skip a unit whose inter-frame motion (mean per-pixel luma abs-diff, 0..255)
|
||||
// is below this. WIN_FG_CAPTURE_MOTION / conf capture_motion.
|
||||
int capPatches = 3;
|
||||
int capPatchSize = 256;
|
||||
|
||||
|
||||
float capMotion = 2.0f;
|
||||
// Output dir; empty => $HOME/.cache/winfg-capture. WIN_FG_CAPTURE_DIR /
|
||||
// conf capture_dir. A per-run session subdir is created under it.
|
||||
std::string capDir;
|
||||
// Rolling-shard size cap (MiB). Output is a small number of large packed
|
||||
// .wfgcap containers, not thousands of loose files; a new shard opens when the
|
||||
// current one passes this. WIN_FG_CAPTURE_SHARD_MB / conf capture_shard_mb.
|
||||
int capShardMB = 1024;
|
||||
|
||||
void sanitize() {
|
||||
if (model < 3) model = 3; if (model > 4) model = 4;
|
||||
if (multiplier < 2) multiplier = 2; if (multiplier > 4) multiplier = 4;
|
||||
if (extraImages < 0) extraImages = 0; if (extraImages > 8) extraImages = 8;
|
||||
if (perfPreset < 0 || perfPreset > 2) perfPreset = 1; // clamp out-of-range -> Balanced
|
||||
if (perfPreset < 0 || perfPreset > 2) perfPreset = 1;
|
||||
if (gmMode < 0) gmMode = 0; if (gmMode > 2) gmMode = 2;
|
||||
if (frMode < 0) frMode = 0; if (frMode > 2) frMode = 2;
|
||||
if (frIters < 0) frIters = 0; if (frIters > 16) frIters = 16;
|
||||
@@ -158,7 +122,6 @@ struct Config {
|
||||
if (frEps < 1e-3f) frEps = 1e-3f;
|
||||
if (flowScale < 0.05f) flowScale = 0.05f; if (flowScale > 4.0f) flowScale = 4.0f;
|
||||
if (beta < 0.0f) beta = 0.0f; if (lambda < 0.0f) lambda = 0.0f;
|
||||
// capture knobs
|
||||
if (captureW < 16) captureW = 16; if (captureW > 7680) captureW = 7680;
|
||||
if (captureH < 16) captureH = 16; if (captureH > 4320) captureH = 4320;
|
||||
if (capMode < 0) capMode = 0; if (capMode > 1) capMode = 1;
|
||||
@@ -176,7 +139,6 @@ static inline int envi(const char* k, int d) {
|
||||
const char* v = std::getenv(k); return v ? std::atoi(v) : d;
|
||||
}
|
||||
|
||||
// "auto"/"on"/"off" (also true/false/on/off numerics) -> {0 auto, 1 on, 2 off}.
|
||||
static inline int parse_tristate(const std::string& v, int dflt) {
|
||||
if (v.empty()) return dflt;
|
||||
if (v == "auto") return 0;
|
||||
@@ -185,19 +147,16 @@ static inline int parse_tristate(const std::string& v, int dflt) {
|
||||
return dflt;
|
||||
}
|
||||
|
||||
// "on"/"off"/"1"/"true"/... -> bool.
|
||||
static inline bool parse_bool(const std::string& v, bool dflt) {
|
||||
int t = parse_tristate(v, dflt ? 1 : 2);
|
||||
return t == 1;
|
||||
}
|
||||
// "patch"|"frame" -> {0 patch, 1 frame}.
|
||||
static inline int parse_capmode(const std::string& v, int dflt) {
|
||||
if (v == "patch" || v == "patches" || v == "0") return 0;
|
||||
if (v == "frame" || v == "frames" || v == "1") return 1;
|
||||
return dflt;
|
||||
}
|
||||
|
||||
// key=value TOML-lite reader (flat keys, '#' comments) - enough for our knobs.
|
||||
static inline void apply_toml(Config& c, const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
if (!f) return;
|
||||
@@ -229,7 +188,6 @@ static inline void apply_toml(Config& c, const std::string& path) {
|
||||
else if (k == "lambda") c.lambda = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "epsilon") c.epsilon = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "photoScale") c.photoScale = std::strtof(v.c_str(), nullptr);
|
||||
// ── capture-mode knobs ────────────────────────────────────────────────
|
||||
else if (k == "capture") c.capture = parse_bool(v, c.capture);
|
||||
else if (k == "capture_dir") c.capDir = v;
|
||||
else if (k == "capture_width") c.captureW = std::atoi(v.c_str());
|
||||
@@ -240,7 +198,6 @@ static inline void apply_toml(Config& c, const std::string& path) {
|
||||
else if (k == "capture_motion") c.capMotion = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "capture_shard_mb") c.capShardMB = std::atoi(v.c_str());
|
||||
else if (k == "hudRect") {
|
||||
// "x0,y0,x1,y1" in pixel coords
|
||||
float r[4] = {0,0,0,0}; int n = 0;
|
||||
size_t start = 0; std::string s = v;
|
||||
for (; n < 4; ++n) {
|
||||
@@ -255,22 +212,18 @@ static inline void apply_toml(Config& c, const std::string& path) {
|
||||
}
|
||||
}
|
||||
|
||||
// Path of the conf.toml the app writes (used for hot-reload mtime checks).
|
||||
static inline std::string conf_path() {
|
||||
if (const char* p = std::getenv("WIN_FG_CONF")) return p;
|
||||
const char* home = std::getenv("HOME");
|
||||
return home ? std::string(home) + "/.config/win-fg/conf.toml" : std::string();
|
||||
}
|
||||
|
||||
// Resolve the capture output root: explicit override, else $HOME/.cache/winfg-capture,
|
||||
// else a /tmp fallback so a HOME-less prefix still writes somewhere pullable.
|
||||
static inline std::string capture_root(const Config& c) {
|
||||
if (!c.capDir.empty()) return c.capDir;
|
||||
if (const char* home = std::getenv("HOME")) return std::string(home) + "/.cache/winfg-capture";
|
||||
return std::string("/tmp/winfg-capture");
|
||||
}
|
||||
|
||||
// env defaults first, then conf.toml overrides (file wins when present).
|
||||
static inline Config load_config() {
|
||||
Config c;
|
||||
c.enabled = envi("WIN_FG_ENABLE", 0) != 0;
|
||||
@@ -292,7 +245,6 @@ static inline Config load_config() {
|
||||
c.lambda = envf("WIN_FG_LAMBDA", c.lambda);
|
||||
c.epsilon = envf("WIN_FG_EPSILON", c.epsilon);
|
||||
c.photoScale = envf("WIN_FG_PHOTOSCALE", c.photoScale);
|
||||
// ── capture-mode env (conf.toml still wins below) ─────────────────────────
|
||||
if (const char* cc = std::getenv("WIN_FG_CAPTURE")) c.capture = parse_bool(cc, c.capture);
|
||||
c.captureW = envi("WIN_FG_CAPTURE_W", c.captureW);
|
||||
c.captureH = envi("WIN_FG_CAPTURE_H", c.captureH);
|
||||
@@ -303,7 +255,6 @@ static inline Config load_config() {
|
||||
c.capShardMB = envi("WIN_FG_CAPTURE_SHARD_MB", c.capShardMB);
|
||||
if (const char* cd = std::getenv("WIN_FG_CAPTURE_DIR")) c.capDir = cd;
|
||||
if (const char* h = std::getenv("WIN_FG_HUD_RECT")) {
|
||||
// "x0,y0,x1,y1" - pixel coords; disabled when x0>=x1 or y0>=y1
|
||||
float r[4] = {0,0,0,0}; int n = 0;
|
||||
std::string s = h; size_t start = 0;
|
||||
for (; n < 4; ++n) {
|
||||
@@ -321,37 +272,24 @@ static inline Config load_config() {
|
||||
return c;
|
||||
}
|
||||
|
||||
// UBO layout - MUST match wfg_synth.comp binding 0.
|
||||
// The vec4 hudRect is 16-byte aligned in std140, sits at offset 32 (after the
|
||||
// 8 leading floats which naturally occupy 32B). Disabled when hudX0>=hudX1.
|
||||
struct SynthUBO {
|
||||
float flowScale, alpha, beta, lambda, epsilon, photoScale, pad0, pad1;
|
||||
float hudX0, hudY0, hudX1, hudY1;
|
||||
};
|
||||
// UBO layout - MUST match of3_flow / of3_expand_m4 binding 0 (Model3UBO).
|
||||
struct FlowUBO { float flowScale; uint32_t level; float occlLo; float occlHi; };
|
||||
|
||||
// C1 global-motion UBO - MUST match binding 1 in of3_gm_reduce / of3_gm_prewarp /
|
||||
// of3_expand(_m4). std140: four vec4 = 64 bytes. The affine is in centered-UV
|
||||
// space: P = [[p1,p3],[p2,p4]], translation (tx,ty); warp uvSrc = uv + P*(uv-0.5)
|
||||
// + t maps a prev(template) coord to the curr(image) coord it samples (prev→curr).
|
||||
// lin[] = running LK estimate (linearization point fed to of3_gm_reduce)
|
||||
// app[] = APPLIED affine (of3_gm_prewarp + expand add-back); identity when
|
||||
// disengaged so the pipeline is byte-identical to pre-C1.
|
||||
struct GMUBO {
|
||||
float lin[4]; // p1,p2,p3,p4
|
||||
float linT[4]; // tx,ty, engaged, pad
|
||||
float app[4]; // p1,p2,p3,p4
|
||||
float appT[4]; // tx,ty, pad, pad
|
||||
float lin[4];
|
||||
float linT[4];
|
||||
float app[4];
|
||||
float appT[4];
|
||||
};
|
||||
|
||||
// C2 flow-regularization UBO - MUST match binding 0 (vec4 params) in
|
||||
// of3_flowreg.comp. std140: one vec4 = 16 bytes. See Config::fr* fields.
|
||||
struct FlowRegUBO {
|
||||
float dt; // params.x - semi-implicit smoothing step
|
||||
float lambda; // params.y - L1 data-fidelity weight (θ = dt·lambda)
|
||||
float edgeAlpha; // params.z - luma-gradient edge sensitivity for g
|
||||
float epsTV; // params.w - Charbonnier epsilon (px) for the TV diffusivity
|
||||
float dt;
|
||||
float lambda;
|
||||
float edgeAlpha;
|
||||
float epsTV;
|
||||
};
|
||||
|
||||
} // namespace winfg
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// GENERATED by tools/embed_spv.py - do not edit by hand.
|
||||
// win-fg embedded SPIR-V: our optical-flow front end + synthesis only.
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@@ -41,4 +39,4 @@ static inline Blob blob(Shader s) {
|
||||
default: return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
} } // namespace winfg::embedded
|
||||
} }
|
||||
@@ -1,4 +1,3 @@
|
||||
// win-fg - compute frame-generation engine implementation.
|
||||
#include "framegen.hpp"
|
||||
#include "embedded_shaders.hpp"
|
||||
#include "log.hpp"
|
||||
@@ -49,10 +48,6 @@ void FrameGen::destroyImage(Img& i) {
|
||||
i = Img{};
|
||||
}
|
||||
|
||||
// C1: allocate the per-slot host-visible SSBOs the LK reduce writes partials into.
|
||||
// Host-visible + coherent so the CPU can read them back after the owning frame's
|
||||
// fence signals (no staging copy needed; the buffers are tiny - kGmThreads*kGmStride
|
||||
// floats each, ~64 KB). Persistently mapped.
|
||||
bool FrameGen::makeGmBuffers() {
|
||||
const VkDeviceSize sz = (VkDeviceSize)kGmThreads * kGmStride * sizeof(float);
|
||||
const VkMemoryPropertyFlags want = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
@@ -75,8 +70,6 @@ bool FrameGen::makeGmBuffers() {
|
||||
|
||||
bool FrameGen::makePipe(Pipe& p, int shaderId,
|
||||
const std::vector<VkDescriptorType>&) {
|
||||
// binding layout is derived per shader below in init(); here we only build
|
||||
// the module + a generic layout supplied by caller via setLayout already set.
|
||||
embedded::Blob b = embedded::blob((embedded::Shader)shaderId);
|
||||
VkShaderModuleCreateInfo mi{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
mi.codeSize = b.words * 4; mi.pCode = b.code;
|
||||
@@ -101,7 +94,6 @@ void FrameGen::destroyPipe(Pipe& p) {
|
||||
p = Pipe{};
|
||||
}
|
||||
|
||||
// Build a descriptor-set layout from an explicit (binding, type) list.
|
||||
static bool buildSetLayout(const DeviceDispatch* dd, VkDevice dev,
|
||||
const std::vector<std::pair<uint32_t, VkDescriptorType>>& binds,
|
||||
VkDescriptorSetLayout& out) {
|
||||
@@ -123,12 +115,11 @@ bool FrameGen::init(const DeviceDispatch* dd, const InstanceDispatch* id,
|
||||
si.addressModeU = si.addressModeV = si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
VKOK(dd_->CreateSampler(device_, &si, nullptr, &linear_));
|
||||
|
||||
// descriptor pool sized generously for our per-frame sets.
|
||||
VkDescriptorPoolSize sizes[] = {
|
||||
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 256},
|
||||
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 256},
|
||||
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 64},
|
||||
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16}, // C1 LK-reduce SSBOs
|
||||
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16},
|
||||
};
|
||||
VkDescriptorPoolCreateInfo pci{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
||||
pci.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
|
||||
@@ -141,19 +132,15 @@ bool FrameGen::init(const DeviceDispatch* dd, const InstanceDispatch* id,
|
||||
const auto U = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
const auto SB = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
|
||||
// per-shader binding sets (match the GLSL layout(binding=...) declarations)
|
||||
if (!buildSetLayout(dd_, dev, {B{32,S},B{48,W}}, pLuma_.setLayout)) return false;
|
||||
if (!buildSetLayout(dd_, dev, {B{32,S},B{48,W}}, pDown_.setLayout)) return false;
|
||||
if (!buildSetLayout(dd_, dev, {B{0,U},B{32,S},B{33,S},B{34,S},B{48,W}}, pFlow_.setLayout)) return false;
|
||||
if (!buildSetLayout(dd_, dev, {B{0,U},B{32,S},B{33,S},B{34,S},B{48,W}}, pFlowM4_.setLayout)) return false;
|
||||
// C1: expand sets gain a second UBO (binding 1 = GMUBO) for the global-motion add-back.
|
||||
if (!buildSetLayout(dd_, dev, {B{0,U},B{1,U},B{32,S},B{48,W},B{49,W}}, pExpand_.setLayout)) return false;
|
||||
if (!buildSetLayout(dd_, dev, {B{0,U},B{1,U},B{32,S},B{48,W},B{49,W}}, pExpandM4_.setLayout)) return false;
|
||||
if (!buildSetLayout(dd_, dev, {B{0,U},B{32,S},B{33,S},B{34,S},B{35,S},B{48,W}}, pSynth_.setLayout)) return false;
|
||||
// C1: LK-reduce (UBO, SSBO, prev, curr) and curr-luma stabilization prewarp (UBO, src, dst).
|
||||
if (!buildSetLayout(dd_, dev, {B{1,U},B{16,SB},B{32,S},B{33,S}}, pGmReduce_.setLayout)) return false;
|
||||
if (!buildSetLayout(dd_, dev, {B{1,U},B{32,S},B{48,W}}, pGmPrewarp_.setLayout)) return false;
|
||||
// C2: flow-reg (UBO, u_in, f0_obs, luma, out).
|
||||
if (!buildSetLayout(dd_, dev, {B{0,U},B{32,S},B{33,S},B{34,S},B{48,W}}, pFlowReg_.setLayout)) return false;
|
||||
|
||||
std::vector<VkDescriptorType> unused;
|
||||
@@ -177,23 +164,19 @@ static VkExtent2D levelExtent(VkExtent2D e, int l) {
|
||||
return { e.width >> l ? e.width >> l : 1u, e.height >> l ? e.height >> l : 1u };
|
||||
}
|
||||
|
||||
bool FrameGen::onResize(VkExtent2D extent, VkFormat /*colorFormat*/, bool force) {
|
||||
// force skips the same-extent early-out so a perf_preset change (which alters
|
||||
// flowFinest_ and therefore the per-size flow-image resolutions) rebuilds them.
|
||||
bool FrameGen::onResize(VkExtent2D extent, VkFormat , bool force) {
|
||||
|
||||
|
||||
if (!force && ready_ && extent.width == extent_.width && extent.height == extent_.height) return true;
|
||||
// tear down previous size
|
||||
for (auto& i : pyrA_) destroyImage(i); pyrA_.clear();
|
||||
for (auto& i : pyrB_) destroyImage(i); pyrB_.clear();
|
||||
for (auto& i : pyrAs_) destroyImage(i); pyrAs_.clear();
|
||||
for (auto& i : flowLvl_) destroyImage(i); flowLvl_.clear();
|
||||
destroyImage(flowExpA_); destroyImage(flowExpB_);
|
||||
destroyImage(flowRegA_); destroyImage(flowRegB_); // C2
|
||||
destroyImage(flowRegA_); destroyImage(flowRegB_);
|
||||
extent_ = extent; ready_ = false;
|
||||
|
||||
const VkImageUsageFlags lumaUsage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
// flowLvl_ is cleared every frame (per-frame reset) and C2 copies the cleaned
|
||||
// finest level back into it - both need TRANSFER_DST. The C2 ping-pong images
|
||||
// are the copy SOURCE (TRANSFER_SRC).
|
||||
const VkImageUsageFlags flowUsage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
const VkImageUsageFlags regUsage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
pyrA_.resize(kLevels); pyrB_.resize(kLevels); flowLvl_.resize(kLevels);
|
||||
@@ -203,13 +186,11 @@ bool FrameGen::onResize(VkExtent2D extent, VkFormat /*colorFormat*/, bool force)
|
||||
if (!makeImage(pyrB_[l], e, VK_FORMAT_R32_SFLOAT, lumaUsage)) return false;
|
||||
if (!makeImage(flowLvl_[l], e, VK_FORMAT_R16G16B16A16_SFLOAT, flowUsage)) return false;
|
||||
}
|
||||
// C1: stabilized curr luma - only the levels the dense flow reads (finest..coarsest).
|
||||
pyrAs_.resize(kLevels);
|
||||
for (int l = flowFinest_; l < kLevels; ++l)
|
||||
if (!makeImage(pyrAs_[l], levelExtent(extent, l), VK_FORMAT_R32_SFLOAT, lumaUsage)) return false;
|
||||
if (!makeImage(flowExpA_, extent, VK_FORMAT_R16G16B16A16_SFLOAT, flowUsage)) return false;
|
||||
if (!makeImage(flowExpB_, extent, VK_FORMAT_R16G16B16A16_SFLOAT, flowUsage)) return false;
|
||||
// C2: TV-L1 ping-pong scratch at the finest solved flow level (perf_preset-driven res).
|
||||
if (!makeImage(flowRegA_, levelExtent(extent, flowFinest_), VK_FORMAT_R16G16B16A16_SFLOAT, regUsage)) return false;
|
||||
if (!makeImage(flowRegB_, levelExtent(extent, flowFinest_), VK_FORMAT_R16G16B16A16_SFLOAT, regUsage)) return false;
|
||||
ready_ = true;
|
||||
@@ -229,13 +210,6 @@ void FrameGen::barrier(VkCommandBuffer cmd, VkImage img, VkImageLayout from, VkI
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
|
||||
}
|
||||
|
||||
// NOTE: descriptor-set allocation/update per record() and the exact per-level
|
||||
// flow wiring are implemented in record_impl.inc for readability; the graph is:
|
||||
// luma(curr)->pyrA[0], luma(prev)->pyrB[0]
|
||||
// downsample pyrA/pyrB to kLevels
|
||||
// coarse->fine flow (per level) into flowLvl
|
||||
// expand flowLvl[0] -> flowExpA/flowExpB
|
||||
// synth(prev,curr,flowExpA,flowExpB, alpha) -> out
|
||||
|
||||
void FrameGen::destroy() {
|
||||
if (!device_) return;
|
||||
@@ -245,11 +219,11 @@ void FrameGen::destroy() {
|
||||
for (auto& i : pyrAs_) destroyImage(i); pyrAs_.clear();
|
||||
for (auto& i : flowLvl_) destroyImage(i); flowLvl_.clear();
|
||||
destroyImage(flowExpA_); destroyImage(flowExpB_);
|
||||
destroyImage(flowRegA_); destroyImage(flowRegB_); // C2
|
||||
destroyImage(flowRegA_); destroyImage(flowRegB_);
|
||||
destroyPipe(pLuma_); destroyPipe(pDown_); destroyPipe(pFlow_); destroyPipe(pFlowM4_);
|
||||
destroyPipe(pExpand_); destroyPipe(pExpandM4_); destroyPipe(pSynth_);
|
||||
destroyPipe(pGmReduce_); destroyPipe(pGmPrewarp_);
|
||||
destroyPipe(pFlowReg_); // C2
|
||||
destroyPipe(pFlowReg_);
|
||||
for (int s = 0; s < kGmSlots; ++s) {
|
||||
if (gmSsboMem_[s]) dd_->UnmapMemory(device_, gmSsboMem_[s]);
|
||||
if (gmSsbo_[s]) dd_->DestroyBuffer(device_, gmSsbo_[s], nullptr);
|
||||
@@ -262,8 +236,6 @@ void FrameGen::destroy() {
|
||||
descPool_ = VK_NULL_HANDLE; linear_ = VK_NULL_HANDLE; device_ = VK_NULL_HANDLE; ready_ = false;
|
||||
}
|
||||
|
||||
} // namespace winfg
|
||||
}
|
||||
|
||||
// record() + its scratch helpers reopen namespace winfg themselves, so include
|
||||
// this OUTSIDE the namespace block above (keeps FrameGen:: members attached).
|
||||
#include "record_impl.inc"
|
||||
@@ -1,8 +1,3 @@
|
||||
// win-fg - compute frame-generation engine.
|
||||
// Owns the compute pipelines (our of3_* optical flow + wfg_synth) and the
|
||||
// per-resolution intermediate images, and records the flow+synth graph that
|
||||
// turns (prev, curr) into an interpolated frame. Written for win-fg; no code
|
||||
// from bionic-fg / lsfg / GameScope.
|
||||
#pragma once
|
||||
#include "vk_dispatch.hpp"
|
||||
#include "config.hpp"
|
||||
@@ -14,70 +9,53 @@ class FrameGen {
|
||||
public:
|
||||
bool init(const DeviceDispatch* dd, const InstanceDispatch* id,
|
||||
VkPhysicalDevice phys, VkDevice dev, uint32_t queueFamily, VkQueue queue);
|
||||
// Applies a new config. Recomputes the active finest flow level from
|
||||
// cfg_.perfPreset; if it changed while resources are live, safely rebuilds the
|
||||
// affected per-size flow images (like a resize) and resets the flow predictor,
|
||||
// so a perf_preset change is FULLY LIVE - no FG toggle needed. Defined in
|
||||
// record_impl.inc (needs the scratch-state teardown). See flowFinestForPreset.
|
||||
void configure(const Config& c);
|
||||
// (Re)build per-resolution resources for a swapchain of the given size/format.
|
||||
// force=true rebuilds even when the extent is unchanged (used by configure()
|
||||
// when a perf_preset change alters the finest solved flow level).
|
||||
bool onResize(VkExtent2D extent, VkFormat colorFormat, bool force = false);
|
||||
// Record the whole flow+synth graph into cmd. prevView/currView are SAMPLED
|
||||
// views of the two real frames; outView is a STORAGE view of the target
|
||||
// image the generated frame is written into. alpha in (0,1).
|
||||
// gmSlot selects which C1 global-motion SSBO to reduce into / read back; the
|
||||
// caller MUST pass the ring-slot index of the FrameCtx it just fence-waited
|
||||
// (that guarantees this slot's PREVIOUS reduce has completed and is safe to
|
||||
// read on the host - see the decoupled-LK note in record_impl.inc).
|
||||
void record(VkCommandBuffer cmd, VkImageView prevView, VkImageView currView,
|
||||
VkImageView outView, float alpha, uint32_t gmSlot = 0);
|
||||
void destroy();
|
||||
bool valid() const { return device_ != VK_NULL_HANDLE && ready_; }
|
||||
const Config& config() const { return cfg_; }
|
||||
|
||||
// public so the record_impl.inc free helpers can reference them
|
||||
struct Img { VkImage image = VK_NULL_HANDLE; VkDeviceMemory mem = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE; VkExtent2D extent{}; VkFormat fmt{}; };
|
||||
struct Pipe { VkPipeline pipeline = VK_NULL_HANDLE; VkPipelineLayout layout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout setLayout = VK_NULL_HANDLE; VkShaderModule module = VK_NULL_HANDLE; };
|
||||
|
||||
// ── C1 GLOBAL-MOTION PRE-WARP constants (public so record_impl.inc's free
|
||||
// helpers can size/index the SSBO pool). See of3_gm_reduce/of3_gm_prewarp. ──
|
||||
static constexpr int kGmSlots = 3; // >= present ring size; one SSBO per in-flight ctx
|
||||
static constexpr int kGmLevel = 3; // 1/8-res luma pyramid level for the LK estimate
|
||||
static constexpr int kGmThreads = 512; // fixed reduce threads (8 groups * local_size_x 64)
|
||||
static constexpr int kGmStride = 32; // floats/thread: 21 H + 6 b + coverage + resid + pad
|
||||
// Finest pyramid level the dense flow is solved at (0 = full res). This is now
|
||||
// RUNTIME-SELECTABLE via conf.toml perf_preset (0 Quality=1 / 1 Balanced=2 /
|
||||
// 2 Performance=3). kFlowFinestDefault is the Balanced value (today's behaviour);
|
||||
// the ACTIVE level lives in flowFinest_ and is read through flowFinest(). onResize
|
||||
// sizes the stabilized-curr pyramid + C2 scratch from it, and record_impl.inc
|
||||
// wires the flow/expand inputs to it - both must agree, so they all read
|
||||
// flowFinest()/flowFinest_ (never a hard-coded 2). (Was a static constexpr = 2.)
|
||||
static constexpr int kFlowFinestDefault = 2; // Balanced (perf_preset=1)
|
||||
// Map perf_preset -> finest solved level. Out-of-range -> Balanced. A higher
|
||||
// level = coarser (cheaper) flow = smaller base-FPS drop. configure() clamps the
|
||||
// result against kLevels so at least two solved levels always remain.
|
||||
|
||||
|
||||
static constexpr int kGmSlots = 3;
|
||||
static constexpr int kGmLevel = 3;
|
||||
static constexpr int kGmThreads = 512;
|
||||
static constexpr int kGmStride = 32;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static constexpr int kFlowFinestDefault = 2;
|
||||
|
||||
|
||||
|
||||
static int flowFinestForPreset(int preset) {
|
||||
switch (preset) {
|
||||
case 0: return 1; // Quality ~1/2-res
|
||||
case 2: return 3; // Performance ~1/8-res
|
||||
case 1: default: return kFlowFinestDefault; // Balanced ~1/4-res (today)
|
||||
case 0: return 1;
|
||||
case 2: return 3;
|
||||
case 1: default: return kFlowFinestDefault;
|
||||
}
|
||||
}
|
||||
// The ACTIVE finest solved flow level (set by configure() from perf_preset).
|
||||
int flowFinest() const { return flowFinest_; }
|
||||
|
||||
private:
|
||||
uint32_t findMemType(uint32_t bits, VkMemoryPropertyFlags props) const;
|
||||
bool makeImage(Img& out, VkExtent2D ext, VkFormat fmt, VkImageUsageFlags usage);
|
||||
void destroyImage(Img& i);
|
||||
bool makeGmBuffers(); // allocate the kGmSlots host-visible LK-reduce SSBOs
|
||||
// Read back one reduce SSBO, solve the 6x6 inverse-compositional LK step, and
|
||||
// update the running/applied global affine. Returns true if it engaged this
|
||||
// frame. Implemented in record_impl.inc (has the ScratchState/UBO context).
|
||||
bool makeGmBuffers();
|
||||
|
||||
|
||||
|
||||
bool updateGlobalMotion(uint32_t gmSlot, VkExtent2D fullExtent);
|
||||
bool makePipe(Pipe& p, int shaderId,
|
||||
const std::vector<VkDescriptorType>& bindings);
|
||||
@@ -97,55 +75,36 @@ private:
|
||||
Config cfg_;
|
||||
VkExtent2D extent_{};
|
||||
bool ready_ = false;
|
||||
// ACTIVE finest solved flow level (perf_preset). Default = Balanced (today's 2)
|
||||
// so an unconfigured engine and an unset conf.toml are byte-identical to before.
|
||||
int flowFinest_ = kFlowFinestDefault;
|
||||
|
||||
// pipelines
|
||||
Pipe pLuma_, pDown_, pFlow_, pFlowM4_, pExpand_, pExpandM4_, pSynth_;
|
||||
// C1: LK normal-equation reduce + curr-luma stabilization pre-warp
|
||||
Pipe pGmReduce_, pGmPrewarp_;
|
||||
// C2: TV-L1 flow-regularization iteration (one dispatch per iteration)
|
||||
Pipe pFlowReg_;
|
||||
|
||||
// per-resolution resources
|
||||
// 7 levels (was 5): the coarsest levels set the max motion the solver can track.
|
||||
// At 5 levels the reach was only ~±84px/frame at 720p, so fast camera motion
|
||||
// (150-300px/frame at the periphery) saturated the search -> incoherent vectors
|
||||
// -> oil-paint melt. Two more (tiny) coarse levels push the reach to ~±300px so
|
||||
// large global motion is captured coherently instead of smeared. Near-zero cost:
|
||||
// the extra levels are 40x22 and 20x11 images.
|
||||
static const int kLevels = 7;
|
||||
std::vector<Img> pyrA_, pyrB_; // luma pyramids for curr / prev
|
||||
std::vector<Img> pyrAs_; // C1: STABILIZED curr luma (flow levels only)
|
||||
std::vector<Img> flowLvl_; // per-level flow scratch
|
||||
Img flowExpA_, flowExpB_; // fwd / bwd expanded flow (+conf)
|
||||
// C2: TV-L1 ping-pong scratch at kFlowFinest res. flowLvl_[kFlowFinest] holds
|
||||
// the fixed observation f0 (never written during the iterations); u ping-pongs
|
||||
// between these two; the final iterate is copied back into flowLvl_[kFlowFinest]
|
||||
// so of3_expand consumes the cleaned field with no binding change (⇒ skipping
|
||||
// C2 leaves flowLvl_ byte-identical to pre-C2).
|
||||
std::vector<Img> pyrA_, pyrB_;
|
||||
std::vector<Img> pyrAs_;
|
||||
std::vector<Img> flowLvl_;
|
||||
Img flowExpA_, flowExpB_;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Img flowRegA_, flowRegB_;
|
||||
|
||||
// ── C1 global-motion state ────────────────────────────────────────────────
|
||||
// Host-visible SSBOs the reduce shader writes per-thread partials into; the
|
||||
// CPU sums + solves them. One per ring slot so a readback never races a live
|
||||
// submission (the caller fence-waited its slot before calling record()).
|
||||
VkBuffer gmSsbo_[kGmSlots] {};
|
||||
VkDeviceMemory gmSsboMem_[kGmSlots]{};
|
||||
void* gmSsboPtr_[kGmSlots]{};
|
||||
bool gmBuffersReady_ = false;
|
||||
// running affine params p1..p6 in centered-UV space (see GMUBO in config.hpp);
|
||||
// identity = all zero. gmLinAt_ records the linearization params in effect when
|
||||
// each slot's reduce was dispatched (the correct IC-update base for that slot).
|
||||
double gmRun_[6] {};
|
||||
double gmLinAt_[kGmSlots][6] {};
|
||||
bool gmSlotValid_[kGmSlots]{};
|
||||
bool gmHaveEst_ = false; // at least one good solve so far
|
||||
int gmStableCount_ = 0; // consecutive good solves (auto-engage threshold)
|
||||
bool gmEngaged_ = false; // is the affine being APPLIED this frame
|
||||
float gmLastTransPx_ = 0.0f; // estimated global translation magnitude (full-res px), for logging
|
||||
bool gmHaveEst_ = false;
|
||||
int gmStableCount_ = 0;
|
||||
bool gmEngaged_ = false;
|
||||
float gmLastTransPx_ = 0.0f;
|
||||
unsigned long long gmFrames_ = 0;
|
||||
};
|
||||
|
||||
} // namespace winfg
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// win-fg - logging. On Android goes to logcat (tag "win-fg") so device bring-up
|
||||
// can trace the layer; elsewhere falls back to stderr.
|
||||
#pragma once
|
||||
#if defined(__ANDROID__)
|
||||
#include <android/log.h>
|
||||
@@ -11,10 +9,4 @@
|
||||
#define WFG_LOGE(...) do { std::fprintf(stderr, "[win-fg][E] " __VA_ARGS__); std::fprintf(stderr, "\n"); } while(0)
|
||||
#endif
|
||||
|
||||
// Runtime-gated granular debug trace. The gate is a RUNTIME bool (Config::debug),
|
||||
// not a compile-time switch - a shipping .so carries the full trail but pays only
|
||||
// a predicted-not-taken bool test per step when debug is off (no logcat write, no
|
||||
// arg evaluation of the message). Same "win-fg" tag so the app captures it under
|
||||
// its own UID. Used to step the present path so a freeze leaves an obvious last
|
||||
// line at the stage that hung. Usage: WFG_LOGD(dbg, "P#%llu step ...", n);
|
||||
#define WFG_LOGD(cond, ...) do { if (cond) WFG_LOGI(__VA_ARGS__); } while(0)
|
||||
@@ -1,57 +1,28 @@
|
||||
// win-fg - FrameGen::record() and its lazy per-size descriptor/UBO setup.
|
||||
// Included from framegen.cpp (has access to FrameGen privates). Written for
|
||||
// win-fg; standard Vulkan compute wiring, no third-party code.
|
||||
//
|
||||
// ── C1 GLOBAL-MOTION PRE-WARP (this file wires it end to end) ─────────────────
|
||||
// The dense SAD flow search melts on fast camera pans because the global motion
|
||||
// saturates its window. C1 estimates the per-frame camera affine with a cheap
|
||||
// inverse-compositional Lucas-Kanade solve (of3_gm_reduce accumulates the 6x6
|
||||
// normal equations at 1/8 res → CPU 6x6 Cholesky here), then of3_gm_prewarp
|
||||
// removes it from the curr luma pyramid BEFORE the search, so the search only
|
||||
// sees small, coherent, object-only residual motion. of3_expand(_m4) composes
|
||||
// the global affine back so wfg_synth is unchanged and the final warp is correct.
|
||||
//
|
||||
// DECOUPLED (stall-free) SCHEDULING: a true in-frame CPU solve would need a
|
||||
// mid-frame GPU submit+readback stall - unacceptable on a GPU-bound title. So we
|
||||
// read back the reduce SSBO the caller ALREADY fence-waited (its ring slot from a
|
||||
// couple of presents ago) and run ONE Gauss-Newton step per frame, warm-started
|
||||
// from the running estimate. Camera motion is temporally smooth, so a ~1-2 frame
|
||||
// old affine is an excellent predictor; and because the SAME affine is removed
|
||||
// (prewarp) and added back (expand), the result is EXACT for any affine we pick -
|
||||
// an approximate/stale one only changes how much residual the search must absorb,
|
||||
// never correctness. Identity affine ⇒ byte-identical to the pre-C1 pipeline, so
|
||||
// the bail-to-identity safety path can never be worse than "no pre-warp".
|
||||
|
||||
namespace {
|
||||
struct ScratchState {
|
||||
// host-visible UBO buffers
|
||||
VkBuffer synthUbo = VK_NULL_HANDLE; VkDeviceMemory synthUboMem = VK_NULL_HANDLE; void* synthUboPtr = nullptr;
|
||||
std::vector<VkBuffer> flowUbo; std::vector<VkDeviceMemory> flowUboMem; std::vector<void*> flowUboPtr;
|
||||
VkBuffer expandUbo = VK_NULL_HANDLE; VkDeviceMemory expandUboMem = VK_NULL_HANDLE; void* expandUboPtr = nullptr;
|
||||
// C2: flow-regularization UBO (shared by all iterations; refreshed per frame).
|
||||
VkBuffer flowRegUbo = VK_NULL_HANDLE; VkDeviceMemory flowRegUboMem = VK_NULL_HANDLE; void* flowRegUboPtr = nullptr;
|
||||
// C1: per-slot global-motion UBO (double-buffered by ring slot so a live GPU
|
||||
// read is never overwritten by the next frame - see the header note).
|
||||
VkBuffer gmUbo[winfg::FrameGen::kGmSlots]{};
|
||||
VkDeviceMemory gmUboMem[winfg::FrameGen::kGmSlots]{};
|
||||
void* gmUboPtr[winfg::FrameGen::kGmSlots]{};
|
||||
// persistent descriptor sets
|
||||
VkDescriptorSet sLumaCurr = VK_NULL_HANDLE, sLumaPrev = VK_NULL_HANDLE;
|
||||
std::vector<VkDescriptorSet> sDownA, sDownB, sFlow;
|
||||
VkDescriptorSet sExpand[winfg::FrameGen::kGmSlots]{}; // per-slot (binds gmUbo[slot])
|
||||
// C2: three flow-reg sets covering every (u_in → u_out) pairing of the
|
||||
// ping-pong. sFlowReg0: in=flowLvl_[F] (=f0), out=A. sFlowRegAB: in=A, out=B.
|
||||
// sFlowRegBA: in=B, out=A. f0 (binding 33) = flowLvl_[F] in all three.
|
||||
VkDescriptorSet sExpand[winfg::FrameGen::kGmSlots]{};
|
||||
|
||||
|
||||
|
||||
VkDescriptorSet sFlowReg0 = VK_NULL_HANDLE, sFlowRegAB = VK_NULL_HANDLE, sFlowRegBA = VK_NULL_HANDLE;
|
||||
VkDescriptorSet sSynth = VK_NULL_HANDLE;
|
||||
// C1: per-slot reduce set + per-slot per-level prewarp sets
|
||||
VkDescriptorSet sGmReduce[winfg::FrameGen::kGmSlots]{};
|
||||
std::vector<VkDescriptorSet> sGmPrewarp[winfg::FrameGen::kGmSlots]; // sized kLevels; [flowFinest()..) used
|
||||
std::vector<VkDescriptorSet> sGmPrewarp[winfg::FrameGen::kGmSlots];
|
||||
VkExtent2D builtFor{0,0};
|
||||
bool built = false;
|
||||
bool flowInited = false; // flow images laid out once, then persist (temporal predictor)
|
||||
bool flowInited = false;
|
||||
};
|
||||
ScratchState g_scratch; // one swapchain in flight in this first-draft engine
|
||||
ScratchState g_scratch;
|
||||
}
|
||||
|
||||
static bool makeUbo(const winfg::DeviceDispatch* dd, VkDevice dev,
|
||||
@@ -77,7 +48,6 @@ static VkDescriptorSet allocSet(const winfg::DeviceDispatch* dd, VkDevice dev,
|
||||
VkDescriptorSet s = VK_NULL_HANDLE; dd->AllocateDescriptorSets(dev, &ai, &s); return s;
|
||||
}
|
||||
|
||||
// write helpers
|
||||
static void wImg(const winfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
|
||||
VkDescriptorType type, VkImageView view, VkSampler samp, VkImageLayout layout) {
|
||||
VkDescriptorImageInfo ii{}; ii.imageView = view; ii.sampler = samp; ii.imageLayout = layout;
|
||||
@@ -102,9 +72,6 @@ static void wSsbo(const winfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet
|
||||
dd->UpdateDescriptorSets(dev, 1, &w, 0, nullptr);
|
||||
}
|
||||
|
||||
// ── C1 CPU-side linear algebra (clean-room, textbook) ────────────────────────
|
||||
// 6x6 symmetric positive-definite solve via Cholesky. Returns false if not PD
|
||||
// (a bad/ill-conditioned frame → caller bails to identity). Damped by the caller.
|
||||
static bool cholSolve6(double A[6][6], const double b[6], double x[6]) {
|
||||
double L[6][6] = {{0}};
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
@@ -112,7 +79,7 @@ static bool cholSolve6(double A[6][6], const double b[6], double x[6]) {
|
||||
double s = A[i][j];
|
||||
for (int k = 0; k < j; ++k) s -= L[i][k] * L[j][k];
|
||||
if (i == j) {
|
||||
if (s <= 1e-20) return false; // not positive-definite
|
||||
if (s <= 1e-20) return false;
|
||||
L[i][j] = std::sqrt(s);
|
||||
} else {
|
||||
L[i][j] = s / L[j][j];
|
||||
@@ -124,7 +91,6 @@ static bool cholSolve6(double A[6][6], const double b[6], double x[6]) {
|
||||
for (int i = 5; i >= 0; --i) { double s = y[i]; for (int k = i+1; k < 6; ++k) s -= L[k][i]*x[k]; x[i] = s / L[i][i]; }
|
||||
return true;
|
||||
}
|
||||
// affine <-> params. params p=(p1,p2,p3,p4,p5,p6); M = I + [[p1,p3,p5],[p2,p4,p6]].
|
||||
static void affFromParams(const double p[6], double M[3][3]) {
|
||||
M[0][0]=1.0+p[0]; M[0][1]=p[2]; M[0][2]=p[4];
|
||||
M[1][0]=p[1]; M[1][1]=1.0+p[3]; M[1][2]=p[5];
|
||||
@@ -153,8 +119,6 @@ static void mulAff(const double A[3][3], const double B[3][3], double out[3][3])
|
||||
|
||||
namespace winfg {
|
||||
|
||||
// Lazily allocate per-size UBOs + descriptor sets and wire the fixed (internal-
|
||||
// resource) bindings once. External views (prev/curr/out) are wired per frame.
|
||||
static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
const VkPhysicalDeviceMemoryProperties& mp, VkDescriptorPool pool,
|
||||
VkSampler samp, VkExtent2D ext,
|
||||
@@ -168,11 +132,6 @@ static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
FrameGen::Img& flowRegA, FrameGen::Img& flowRegB, VkBuffer* gmSsbo,
|
||||
int kLevels, const Config& cfg);
|
||||
|
||||
// Tear down the lazily-built scratch (all UBO buffers + every descriptor set) so a
|
||||
// live perf_preset rebuild neither leaks them nor exhausts the descriptor pool.
|
||||
// The CALLER must have made the device idle first - no in-flight command buffer may
|
||||
// still reference these. Resetting the pool frees every set FrameGen allocated (all
|
||||
// sets come from this pool); the mapped UBO memory is unmapped + freed explicitly.
|
||||
static void destroyScratch(const DeviceDispatch* dd, VkDevice dev, VkDescriptorPool pool) {
|
||||
auto freeUbo = [&](VkBuffer& b, VkDeviceMemory& m, void*& p) {
|
||||
if (m) dd->UnmapMemory(dev, m);
|
||||
@@ -186,29 +145,20 @@ static void destroyScratch(const DeviceDispatch* dd, VkDevice dev, VkDescriptorP
|
||||
freeUbo(S.flowRegUbo, S.flowRegUboMem, S.flowRegUboPtr);
|
||||
for (size_t i = 0; i < S.flowUbo.size(); ++i) freeUbo(S.flowUbo[i], S.flowUboMem[i], S.flowUboPtr[i]);
|
||||
for (int s = 0; s < FrameGen::kGmSlots; ++s) freeUbo(S.gmUbo[s], S.gmUboMem[s], S.gmUboPtr[s]);
|
||||
if (pool && dd->ResetDescriptorPool) dd->ResetDescriptorPool(dev, pool, 0); // reclaim all sets at once
|
||||
S = ScratchState{}; // built=false -> next record() rebuilds via ensureScratch
|
||||
if (pool && dd->ResetDescriptorPool) dd->ResetDescriptorPool(dev, pool, 0);
|
||||
S = ScratchState{};
|
||||
}
|
||||
|
||||
// ── perf_preset apply / live rebuild ─────────────────────────────────────────
|
||||
// Sets the config, resolves the perf_preset -> finest-solved-level, and - when
|
||||
// that level changed while resources are already live - safely rebuilds the
|
||||
// affected per-size flow images (a resize) + scratch descriptor sets and resets
|
||||
// the flow predictor. This makes a perf_preset change FULLY LIVE per-present (the
|
||||
// conf.toml hot-reload path calls this), so no FG toggle / bg-fg cycle is needed.
|
||||
void FrameGen::configure(const Config& c) {
|
||||
cfg_ = c; cfg_.sanitize();
|
||||
// Map preset -> finest level, clamped so at least TWO solved levels always
|
||||
// remain (coarse->fine needs a range). With kLevels=7 the presets (1/2/3) never
|
||||
// hit the clamp; the guard is defensive for future level/preset tuning.
|
||||
int want = flowFinestForPreset(cfg_.perfPreset);
|
||||
const int maxFinest = kLevels - 2; // keep >= 2 solved levels
|
||||
const int maxFinest = kLevels - 2;
|
||||
if (want > maxFinest) {
|
||||
WFG_LOGE("perf-preset=%d wants kFlowFinest=%d but only %d pyramid levels exist "
|
||||
"(need >=2 solved) - clamping to %d", cfg_.perfPreset, want, kLevels, maxFinest);
|
||||
want = maxFinest;
|
||||
}
|
||||
if (want < 1) want = 1; // never solve full-res (level 0)
|
||||
if (want < 1) want = 1;
|
||||
|
||||
const int prevFinest = flowFinest_;
|
||||
flowFinest_ = want;
|
||||
@@ -216,35 +166,27 @@ void FrameGen::configure(const Config& c) {
|
||||
WFG_LOGI("perf-preset=%d -> kFlowFinest=%d (was %d) live-rebuild=%d ready=%d",
|
||||
cfg_.perfPreset, flowFinest_, prevFinest, live ? 1 : 0, ready_ ? 1 : 0);
|
||||
|
||||
if (!live) return; // unchanged, or not yet built (initial configure before onResize)
|
||||
if (!live) return;
|
||||
|
||||
// The per-size flow images (pyrAs_/flowReg*/flowLvl_ wiring) and the scratch
|
||||
// descriptor sets were built for the OLD finest level. Rebuild them so record()
|
||||
// never reads/writes mismatched-size images.
|
||||
dd_->DeviceWaitIdle(device_); // no in-flight cmd buf may touch these
|
||||
destroyScratch(dd_, device_, descPool_); // free old scratch (UBOs + all sets)
|
||||
if (!onResize(extent_, VK_FORMAT_UNDEFINED, /*force=*/true))
|
||||
|
||||
|
||||
|
||||
dd_->DeviceWaitIdle(device_);
|
||||
destroyScratch(dd_, device_, descPool_);
|
||||
if (!onResize(extent_, VK_FORMAT_UNDEFINED, true))
|
||||
WFG_LOGE("perf-preset live rebuild: onResize FAILED at %ux%u - FG disabled until next resize",
|
||||
extent_.width, extent_.height);
|
||||
// Reset the persistent flow predictor so stale, differently-scaled state never
|
||||
// feeds the new resolution (avoids a melt/garbage frame on switch). The per-frame
|
||||
// flow field (flowLvl_) is already cleared every frame; the cross-frame state is
|
||||
// the C1 global-motion estimate + its per-slot reduce partials (dispatched against
|
||||
// the old pyramid) - drop both back to identity.
|
||||
for (int i = 0; i < 6; ++i) gmRun_[i] = 0.0;
|
||||
for (int s = 0; s < kGmSlots; ++s) { gmSlotValid_[s] = false; for (int i = 0; i < 6; ++i) gmLinAt_[s][i] = 0.0; }
|
||||
gmHaveEst_ = false; gmEngaged_ = false; gmStableCount_ = 0; gmLastTransPx_ = 0.0f;
|
||||
WFG_LOGI("perf-preset live rebuild DONE (kFlowFinest=%d, flow predictor reset)", flowFinest_);
|
||||
}
|
||||
|
||||
// ── C1: read back one reduce SSBO, run one inverse-compositional LK step ──────
|
||||
// Updates the running/applied global affine. Returns whether GM engages this frame.
|
||||
bool FrameGen::updateGlobalMotion(uint32_t gmSlot, VkExtent2D fullExtent) {
|
||||
++gmFrames_;
|
||||
if (!gmBuffersReady_ || cfg_.gmMode == 2 /*off*/) {
|
||||
// off: keep identity applied; keep estimating cheaply is pointless when off.
|
||||
if (!gmBuffersReady_ || cfg_.gmMode == 2 ) {
|
||||
|
||||
gmEngaged_ = false; gmStableCount_ = 0;
|
||||
// still refresh the linearization record so a live switch to auto starts clean
|
||||
for (int i = 0; i < 6; ++i) gmRun_[i] = 0.0;
|
||||
for (int i = 0; i < 6; ++i) gmLinAt_[gmSlot][i] = gmRun_[i];
|
||||
gmLastTransPx_ = 0.0f;
|
||||
@@ -260,31 +202,25 @@ bool FrameGen::updateGlobalMotion(uint32_t gmSlot, VkExtent2D fullExtent) {
|
||||
for (int k = 0; k < kGmStride; ++k) sums[k] += (double)row[k];
|
||||
}
|
||||
double coverage = sums[27];
|
||||
// require a few percent of the level's pixels as inliers
|
||||
double lvlpx = double((fullExtent.width >> kGmLevel) * (fullExtent.height >> kGmLevel));
|
||||
double minCov = 0.03 * (lvlpx > 0 ? lvlpx : 1.0);
|
||||
if (coverage > minCov && coverage > 64.0) {
|
||||
double H[6][6]; int idx = 0;
|
||||
for (int r = 0; r < 6; ++r) for (int c = r; c < 6; ++c) { H[r][c] = H[c][r] = sums[idx]; ++idx; }
|
||||
double b[6]; for (int i = 0; i < 6; ++i) b[i] = sums[21 + i];
|
||||
// Levenberg damping for conditioning (relative + tiny absolute).
|
||||
for (int i = 0; i < 6; ++i) H[i][i] += 1e-3 * H[i][i] + 1e-9;
|
||||
double dp[6];
|
||||
if (cholSolve6(H, b, dp)) {
|
||||
// reject wild steps (something non-affine / a scene cut)
|
||||
bool bounded = std::fabs(dp[0]) < 0.5 && std::fabs(dp[1]) < 0.5 &&
|
||||
std::fabs(dp[2]) < 0.5 && std::fabs(dp[3]) < 0.5 &&
|
||||
std::fabs(dp[4]) < 0.25 && std::fabs(dp[5]) < 0.25;
|
||||
if (bounded) {
|
||||
// IC update: M_new = M_lin ∘ W(dp)^-1 (compose onto the SLOT's
|
||||
// linearization base, which is what this reduce linearized around).
|
||||
double Mlin[3][3], Md[3][3], Mdi[3][3], Mnew[3][3];
|
||||
affFromParams(gmLinAt_[gmSlot], Mlin);
|
||||
affFromParams(dp, Md);
|
||||
if (invAff(Md, Mdi)) {
|
||||
mulAff(Mlin, Mdi, Mnew);
|
||||
double pnew[6]; paramsFromAff(Mnew, pnew);
|
||||
// final sanity clamp on the composed estimate
|
||||
bool sane = std::fabs(pnew[0]) < 0.6 && std::fabs(pnew[1]) < 0.6 &&
|
||||
std::fabs(pnew[2]) < 0.6 && std::fabs(pnew[3]) < 0.6 &&
|
||||
std::fabs(pnew[4]) < 0.5 && std::fabs(pnew[5]) < 0.5;
|
||||
@@ -297,17 +233,14 @@ bool FrameGen::updateGlobalMotion(uint32_t gmSlot, VkExtent2D fullExtent) {
|
||||
|
||||
if (good) ++gmStableCount_; else gmStableCount_ = 0;
|
||||
|
||||
// engage policy
|
||||
switch (cfg_.gmMode) {
|
||||
case 1: gmEngaged_ = gmHaveEst_; break; // on
|
||||
case 2: gmEngaged_ = false; break; // off (unreachable; handled above)
|
||||
default: gmEngaged_ = (gmStableCount_ >= 3); break; // auto: 3 consecutive good solves
|
||||
case 1: gmEngaged_ = gmHaveEst_; break;
|
||||
case 2: gmEngaged_ = false; break;
|
||||
default: gmEngaged_ = (gmStableCount_ >= 3); break;
|
||||
}
|
||||
|
||||
// record the linearization base for the reduce we are about to dispatch into
|
||||
// this slot this frame (i.e. the current running estimate).
|
||||
for (int i = 0; i < 6; ++i) gmLinAt_[gmSlot][i] = gmRun_[i];
|
||||
gmSlotValid_[gmSlot] = true; // this frame's reduce will (re)write it
|
||||
gmSlotValid_[gmSlot] = true;
|
||||
|
||||
gmLastTransPx_ = (float)std::sqrt(
|
||||
(gmRun_[4]*fullExtent.width) * (gmRun_[4]*fullExtent.width) +
|
||||
@@ -330,9 +263,7 @@ void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView cur
|
||||
pFlowReg_, flowRegA_, flowRegB_, gmSsbo_, kLevels, cfg_);
|
||||
}
|
||||
|
||||
// ── C1: solve the global-motion affine (reads the SSBO the caller fence-waited) ──
|
||||
bool gmEngaged = updateGlobalMotion(gmSlot, extent_);
|
||||
// Build this slot's GMUBO from the running / applied affine.
|
||||
if (g_scratch.gmUboPtr[gmSlot]) {
|
||||
GMUBO g{};
|
||||
g.lin[0]=(float)gmRun_[0]; g.lin[1]=(float)gmRun_[1]; g.lin[2]=(float)gmRun_[2]; g.lin[3]=(float)gmRun_[3];
|
||||
@@ -340,10 +271,9 @@ void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView cur
|
||||
if (gmEngaged) {
|
||||
g.app[0]=(float)gmRun_[0]; g.app[1]=(float)gmRun_[1]; g.app[2]=(float)gmRun_[2]; g.app[3]=(float)gmRun_[3];
|
||||
g.appT[0]=(float)gmRun_[4]; g.appT[1]=(float)gmRun_[5];
|
||||
} // else app/appT stay 0 -> identity -> prewarp/expand are pre-C1 no-ops
|
||||
}
|
||||
std::memcpy(g_scratch.gmUboPtr[gmSlot], &g, sizeof(g));
|
||||
}
|
||||
// Log engage state + estimated global translation periodically (tag win-fg).
|
||||
{
|
||||
static int lastEng = -1;
|
||||
if (gmEngaged != (lastEng == 1) || (gmFrames_ % 300ull) == 0) {
|
||||
@@ -353,12 +283,10 @@ void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView cur
|
||||
}
|
||||
}
|
||||
|
||||
// update UBOs for this frame
|
||||
SynthUBO su{cfg_.flowScale, alpha, cfg_.beta, cfg_.lambda, cfg_.epsilon, cfg_.photoScale, 0, 0,
|
||||
cfg_.hudX0, cfg_.hudY0, cfg_.hudX1, cfg_.hudY1};
|
||||
if (g_scratch.synthUboPtr) std::memcpy(g_scratch.synthUboPtr, &su, sizeof(su));
|
||||
|
||||
// wire per-frame external views
|
||||
wImg(dd_, device_, g_scratch.sLumaCurr, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, currView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
wImg(dd_, device_, g_scratch.sLumaPrev, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, prevView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
wImg(dd_, device_, g_scratch.sSynth, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, prevView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
@@ -372,23 +300,13 @@ void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView cur
|
||||
};
|
||||
auto bar = [&](FrameGen::Img& i){ barrier(cmd, i.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL); };
|
||||
|
||||
// Internal images are created UNDEFINED and fully recomputed every frame, so
|
||||
// discard-transition them all to GENERAL up front (all descriptors use GENERAL
|
||||
// for these). This also fixes the first-frame layout (storage writes require
|
||||
// GENERAL, not UNDEFINED).
|
||||
for (auto& i : pyrA_) barrier(cmd, i.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
for (auto& i : pyrB_) barrier(cmd, i.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
for (int l = flowFinest_; l < kLevels; ++l) barrier(cmd, pyrAs_[l].image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowExpA_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowExpB_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
// C2: ping-pong scratch to GENERAL (storage writes require it; contents discarded).
|
||||
barrier(cmd, flowRegA_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowRegB_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
// Reset the flow field to zero every frame. The coarsest level uses it as its
|
||||
// search predictor; carrying it across frames lets a spurious vector feed
|
||||
// forward until it diverges and melts the image (the bg/fg pause was resetting
|
||||
// it - that's why artifacts vanished on resume). Fresh zero every frame =
|
||||
// stable, standard pyramid flow, no feedback loop.
|
||||
{
|
||||
VkClearColorValue zero{};
|
||||
VkImageSubresourceRange rng{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
@@ -406,20 +324,14 @@ void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView cur
|
||||
}
|
||||
}
|
||||
|
||||
// 1) luma of curr/prev at full res
|
||||
disp(pLuma_, g_scratch.sLumaCurr, levelExtent(extent_, 0));
|
||||
disp(pLuma_, g_scratch.sLumaPrev, levelExtent(extent_, 0));
|
||||
bar(pyrA_[0]); bar(pyrB_[0]);
|
||||
// 2) downsample pyramids
|
||||
for (int l = 1; l < kLevels; ++l) {
|
||||
disp(pDown_, g_scratch.sDownA[l - 1], levelExtent(extent_, l));
|
||||
disp(pDown_, g_scratch.sDownB[l - 1], levelExtent(extent_, l));
|
||||
bar(pyrA_[l]); bar(pyrB_[l]);
|
||||
}
|
||||
// 2b) C1: LK normal-equation reduce (fixed grid, 1/8-res) → this slot's SSBO.
|
||||
// Reads raw prev/curr luma at kGmLevel; writes per-thread partials. A
|
||||
// compute→host barrier makes the write visible to the CPU readback that
|
||||
// happens when this slot comes round again (~1-2 frames later).
|
||||
if (gmBuffersReady_ && g_scratch.sGmReduce[gmSlot]) {
|
||||
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pGmReduce_.pipeline);
|
||||
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pGmReduce_.layout, 0, 1, &g_scratch.sGmReduce[gmSlot], 0, nullptr);
|
||||
@@ -429,28 +341,15 @@ void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView cur
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT,
|
||||
0, 1, &mb, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
// 2c) C1: pre-warp curr luma pyramid by the applied affine → stabilized pyramid
|
||||
// (identity affine ⇒ exact copy, so the flow below is unchanged when
|
||||
// disengaged). The dense flow reads pyrAs from here on.
|
||||
for (int l = flowFinest_; l < kLevels; ++l) {
|
||||
disp(pGmPrewarp_, g_scratch.sGmPrewarp[gmSlot][l], levelExtent(extent_, l));
|
||||
bar(pyrAs_[l]);
|
||||
}
|
||||
// 3) coarse->fine flow (only down to flowFinest_ - reduced res for perf; the
|
||||
// finest level is perf_preset-selectable). Curr input is now the STABILIZED
|
||||
// pyramid (pyrAs), wired in ensureScratch.
|
||||
for (int l = kLevels - 1; l >= flowFinest_; --l) {
|
||||
disp(flowP, g_scratch.sFlow[l], levelExtent(extent_, l));
|
||||
bar(flowLvl_[l]);
|
||||
}
|
||||
|
||||
// 3b) ── C2: TV-L1 flow regularization on the finest residual field ──────────
|
||||
// Run frIters semi-implicit TV-L1 denoising iterations on flowLvl_[flowFinest_]
|
||||
// (the object-only residual left by C1). flowLvl_[F] is the fixed data term f0
|
||||
// (read, never written here); u ping-pongs A<->B; the final iterate is copied
|
||||
// back into flowLvl_[F] so of3_expand consumes the cleaned field unchanged.
|
||||
// Disengaged (frMode==off or frIters==0) ⇒ flowLvl_[F] untouched ⇒ byte-identical
|
||||
// to pre-C2. Identity/zero iterations is therefore never worse than "no reg".
|
||||
const bool frEngaged = (cfg_.frMode != 2) && (cfg_.frIters > 0);
|
||||
if (frEngaged) {
|
||||
const int F = flowFinest_;
|
||||
@@ -471,9 +370,6 @@ void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView cur
|
||||
bar(*outImg);
|
||||
last = outImg;
|
||||
}
|
||||
// Copy the cleaned finest flow back into flowLvl_[F] (GENERAL->GENERAL).
|
||||
// Barrier: C2's f0 reads (SHADER_READ of flowLvl_[F]) and the last iterate
|
||||
// write must complete before the transfer overwrites/reads.
|
||||
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
mb.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
||||
mb.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
@@ -491,7 +387,7 @@ void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView cur
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0, 1, &mb2, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
{ // periodic C2 status (tag win-fg), mirrors the C1 log cadence
|
||||
{
|
||||
static int lastFr = -1;
|
||||
int fe = frEngaged ? 1 : 0;
|
||||
if (fe != lastFr || (gmFrames_ % 300ull) == 0) {
|
||||
@@ -501,14 +397,11 @@ void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView cur
|
||||
}
|
||||
}
|
||||
|
||||
// 4) expand finest solved flow -> fwd/bwd (+conf), + C1 global-motion add-back.
|
||||
disp(expandP, g_scratch.sExpand[gmSlot], levelExtent(extent_, 0));
|
||||
bar(flowExpA_); bar(flowExpB_);
|
||||
// 5) synthesise interpolated frame
|
||||
disp(pSynth_, g_scratch.sSynth, levelExtent(extent_, 0));
|
||||
}
|
||||
|
||||
// ---- lazy scratch builder ----
|
||||
static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
const VkPhysicalDeviceMemoryProperties& mp, VkDescriptorPool pool,
|
||||
VkSampler samp, VkExtent2D ext,
|
||||
@@ -528,7 +421,6 @@ static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
makeUbo(dd, dev, mp, sizeof(SynthUBO), S.synthUbo, S.synthUboMem, S.synthUboPtr);
|
||||
makeUbo(dd, dev, mp, sizeof(FlowUBO), S.expandUbo, S.expandUboMem, S.expandUboPtr);
|
||||
if (S.expandUboPtr) { FlowUBO u{cfg.flowScale, 0, 0.6f, 1.6f}; std::memcpy(S.expandUboPtr, &u, sizeof(u)); }
|
||||
// C2: flow-reg UBO (params refreshed each frame in record() for hot-reload).
|
||||
makeUbo(dd, dev, mp, sizeof(FlowRegUBO), S.flowRegUbo, S.flowRegUboMem, S.flowRegUboPtr);
|
||||
if (S.flowRegUboPtr) { FlowRegUBO fu{cfg.frDt, cfg.frLambda, cfg.frEdge, cfg.frEps}; std::memcpy(S.flowRegUboPtr, &fu, sizeof(fu)); }
|
||||
S.flowUbo.resize(kLevels); S.flowUboMem.resize(kLevels); S.flowUboPtr.resize(kLevels);
|
||||
@@ -536,19 +428,16 @@ static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
makeUbo(dd, dev, mp, sizeof(FlowUBO), S.flowUbo[l], S.flowUboMem[l], S.flowUboPtr[l]);
|
||||
if (S.flowUboPtr[l]) { FlowUBO u{cfg.flowScale, (uint32_t)l, 0.6f, 1.6f}; std::memcpy(S.flowUboPtr[l], &u, sizeof(u)); }
|
||||
}
|
||||
// C1: per-slot GMUBO (initialised to identity)
|
||||
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
|
||||
makeUbo(dd, dev, mp, sizeof(GMUBO), S.gmUbo[s], S.gmUboMem[s], S.gmUboPtr[s]);
|
||||
if (S.gmUboPtr[s]) { GMUBO g{}; std::memcpy(S.gmUboPtr[s], &g, sizeof(g)); }
|
||||
}
|
||||
|
||||
// luma sets (external src wired per frame; dst is pyramid[0])
|
||||
S.sLumaCurr = allocSet(dd, dev, pool, pLuma.setLayout);
|
||||
S.sLumaPrev = allocSet(dd, dev, pool, pLuma.setLayout);
|
||||
wImg(dd, dev, S.sLumaCurr, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrA[0].view, VK_NULL_HANDLE, GEN);
|
||||
wImg(dd, dev, S.sLumaPrev, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrB[0].view, VK_NULL_HANDLE, GEN);
|
||||
|
||||
// downsample sets (fixed)
|
||||
S.sDownA.resize(kLevels - 1); S.sDownB.resize(kLevels - 1);
|
||||
for (int l = 1; l < kLevels; ++l) {
|
||||
S.sDownA[l-1] = allocSet(dd, dev, pool, pDown.setLayout);
|
||||
@@ -559,10 +448,6 @@ static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
wImg(dd, dev, S.sDownB[l-1], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrB[l].view, VK_NULL_HANDLE, GEN);
|
||||
}
|
||||
|
||||
// flow sets (fixed): predictor = coarser flow level, or self at coarsest.
|
||||
// C1: the CURR input (binding 33) is the STABILIZED pyramid pyrAs for the
|
||||
// solved flow levels; prev stays raw. Below kFlowFinest the level is never
|
||||
// dispatched, so leaving those at raw curr is harmless.
|
||||
S.sFlow.resize(kLevels);
|
||||
for (int l = 0; l < kLevels; ++l) {
|
||||
S.sFlow[l] = allocSet(dd, dev, pool, pFlow.setLayout);
|
||||
@@ -575,19 +460,17 @@ static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
wImg(dd, dev, S.sFlow[l], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowLvl[l].view, VK_NULL_HANDLE, GEN);
|
||||
}
|
||||
|
||||
// C1: reduce sets (one per slot) - prev/curr raw luma at kGmLevel + SSBO + GMUBO.
|
||||
if (gmSsbo) {
|
||||
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
|
||||
if (!gmSsbo[s]) continue;
|
||||
S.sGmReduce[s] = allocSet(dd, dev, pool, pGmReduce.setLayout);
|
||||
wUbo (dd, dev, S.sGmReduce[s], 1, S.gmUbo[s], sizeof(GMUBO));
|
||||
wSsbo(dd, dev, S.sGmReduce[s], 16, gmSsbo[s], VK_WHOLE_SIZE);
|
||||
wImg (dd, dev, S.sGmReduce[s], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[FrameGen::kGmLevel].view, samp, GEN); // prev = template
|
||||
wImg (dd, dev, S.sGmReduce[s], 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrA[FrameGen::kGmLevel].view, samp, GEN); // curr = image
|
||||
wImg (dd, dev, S.sGmReduce[s], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[FrameGen::kGmLevel].view, samp, GEN);
|
||||
wImg (dd, dev, S.sGmReduce[s], 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrA[FrameGen::kGmLevel].view, samp, GEN);
|
||||
}
|
||||
}
|
||||
|
||||
// C1: prewarp sets (one per slot, per solved flow level) - raw curr in, stabilized out.
|
||||
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
|
||||
S.sGmPrewarp[s].assign(kLevels, VK_NULL_HANDLE);
|
||||
for (int l = fg->flowFinest(); l < kLevels; ++l) {
|
||||
@@ -598,7 +481,6 @@ static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
}
|
||||
}
|
||||
|
||||
// expand sets (one per slot): flow inputs fixed; binding 1 = that slot's GMUBO.
|
||||
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
|
||||
S.sExpand[s] = allocSet(dd, dev, pool, pExpand.setLayout);
|
||||
wUbo(dd, dev, S.sExpand[s], 0, S.expandUbo, sizeof(FlowUBO));
|
||||
@@ -608,9 +490,6 @@ static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
wImg(dd, dev, S.sExpand[s], 49, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, expB.view, VK_NULL_HANDLE, GEN);
|
||||
}
|
||||
|
||||
// C2: flow-reg sets. F = kFlowFinest. f0 (binding 33) = flowLvl_[F] in all; luma
|
||||
// (binding 34) = prev luma at F (pyrB) for the edge-aware weight. u_in (32) and
|
||||
// u_out (48) alternate to realize the ping-pong (see ScratchState comment).
|
||||
{
|
||||
const int F = fg->flowFinest();
|
||||
S.sFlowReg0 = allocSet(dd, dev, pool, pFlowReg.setLayout);
|
||||
@@ -619,26 +498,19 @@ static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
VkDescriptorSet all3[3] = {S.sFlowReg0, S.sFlowRegAB, S.sFlowRegBA};
|
||||
for (VkDescriptorSet s : all3) {
|
||||
wUbo(dd, dev, s, 0, S.flowRegUbo, sizeof(FlowRegUBO));
|
||||
wImg(dd, dev, s, 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[F].view, samp, GEN); // f0
|
||||
wImg(dd, dev, s, 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[F].view, samp, GEN); // luma
|
||||
wImg(dd, dev, s, 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[F].view, samp, GEN);
|
||||
wImg(dd, dev, s, 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[F].view, samp, GEN);
|
||||
}
|
||||
// iter0: in=flowLvl_[F] (=f0), out=A
|
||||
wImg(dd, dev, S.sFlowReg0, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[F].view, samp, GEN);
|
||||
wImg(dd, dev, S.sFlowReg0, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegA.view, VK_NULL_HANDLE, GEN);
|
||||
// odd iters: in=A, out=B
|
||||
wImg(dd, dev, S.sFlowRegAB, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowRegA.view, samp, GEN);
|
||||
wImg(dd, dev, S.sFlowRegAB, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegB.view, VK_NULL_HANDLE, GEN);
|
||||
// even iters (>0): in=B, out=A
|
||||
wImg(dd, dev, S.sFlowRegBA, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowRegB.view, samp, GEN);
|
||||
wImg(dd, dev, S.sFlowRegBA, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegA.view, VK_NULL_HANDLE, GEN);
|
||||
}
|
||||
|
||||
// synth set: external prev/curr/out wired per frame; flow inputs fixed
|
||||
S.sSynth = allocSet(dd, dev, pool, pSynth.setLayout);
|
||||
wUbo(dd, dev, S.sSynth, 0, S.synthUbo, sizeof(SynthUBO));
|
||||
// GENERAL (not SR): these internal flow images stay in GENERAL across the whole
|
||||
// record() (written as storage by expand, sampled here) - see the discard
|
||||
// transition at the top of record().
|
||||
wImg(dd, dev, S.sSynth, 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, expA.view, samp, GEN);
|
||||
wImg(dd, dev, S.sSynth, 35, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, expB.view, samp, GEN);
|
||||
|
||||
@@ -646,4 +518,4 @@ static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace winfg
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
// win-fg - minimal Vulkan layer dispatch tables.
|
||||
// Structure follows the public Khronos loader/layer interface and the
|
||||
// renderdoc "Vulkan layer guide" skeleton (learning references, Apache-2.0 /
|
||||
// CC-BY). No code from bionic-fg / lsfg / GameScope.
|
||||
#pragma once
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <vulkan/vk_layer.h>
|
||||
@@ -9,53 +5,35 @@
|
||||
|
||||
namespace winfg {
|
||||
|
||||
// The loader sets the first pointer-sized word of a dispatchable handle to the
|
||||
// dispatch table pointer; layers key their own maps on that value so that all
|
||||
// handles sharing a chain resolve to the same table.
|
||||
static inline void* dispatch_key(void* handle) { return *reinterpret_cast<void**>(handle); }
|
||||
|
||||
// Instance-level functions we call from the layer.
|
||||
struct InstanceDispatch {
|
||||
PFN_vkGetInstanceProcAddr GetInstanceProcAddr = nullptr;
|
||||
PFN_vkDestroyInstance DestroyInstance = nullptr;
|
||||
PFN_vkGetPhysicalDeviceMemoryProperties GetPhysicalDeviceMemoryProperties = nullptr;
|
||||
PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties = nullptr;
|
||||
// Device-context logging (affected-device fingerprint). Properties2 is core 1.1;
|
||||
// may be null on a 1.0 instance - always null-check before calling.
|
||||
PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties = nullptr;
|
||||
PFN_vkGetPhysicalDeviceProperties2 GetPhysicalDeviceProperties2 = nullptr;
|
||||
// Surface caps - used to clamp the extra-image-headroom request to the surface's
|
||||
// maxImageCount so the bump never exceeds what the driver can grant.
|
||||
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR GetPhysicalDeviceSurfaceCapabilitiesKHR = nullptr;
|
||||
};
|
||||
|
||||
// Device-level functions we call. Populated by walking vkGetDeviceProcAddr on
|
||||
// the next layer down at device-create time.
|
||||
struct DeviceDispatch {
|
||||
PFN_vkGetDeviceProcAddr GetDeviceProcAddr = nullptr;
|
||||
PFN_vkDestroyDevice DestroyDevice = nullptr;
|
||||
PFN_vkGetDeviceQueue GetDeviceQueue = nullptr;
|
||||
PFN_vkQueueSubmit QueueSubmit = nullptr;
|
||||
// Synchronization2 submit path (core 1.3 + KHR alias). DXVK may use either the
|
||||
// legacy QueueSubmit or QueueSubmit2/2KHR; both are hooked for the guest-submit
|
||||
// diagnostic. May be null if the device did not enable them - always null-check.
|
||||
PFN_vkQueueSubmit2 QueueSubmit2 = nullptr;
|
||||
PFN_vkQueueSubmit2KHR QueueSubmit2KHR = nullptr;
|
||||
PFN_vkQueueWaitIdle QueueWaitIdle = nullptr;
|
||||
PFN_vkDeviceWaitIdle DeviceWaitIdle = nullptr;
|
||||
|
||||
// swapchain / present
|
||||
PFN_vkCreateSwapchainKHR CreateSwapchainKHR = nullptr;
|
||||
PFN_vkDestroySwapchainKHR DestroySwapchainKHR = nullptr;
|
||||
PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR = nullptr;
|
||||
PFN_vkAcquireNextImageKHR AcquireNextImageKHR = nullptr;
|
||||
// Alternate acquire entry (VK_KHR_swapchain 1.1+ / device-group). DXVK may use
|
||||
// either AcquireNextImageKHR or AcquireNextImage2KHR - both hooked for the
|
||||
// guest-acquire diagnostic. May be null - always null-check.
|
||||
PFN_vkAcquireNextImage2KHR AcquireNextImage2KHR = nullptr;
|
||||
PFN_vkQueuePresentKHR QueuePresentKHR = nullptr;
|
||||
|
||||
// resources
|
||||
PFN_vkCreateImage CreateImage = nullptr;
|
||||
PFN_vkDestroyImage DestroyImage = nullptr;
|
||||
PFN_vkCreateImageView CreateImageView = nullptr;
|
||||
@@ -73,7 +51,6 @@ struct DeviceDispatch {
|
||||
PFN_vkCreateSampler CreateSampler = nullptr;
|
||||
PFN_vkDestroySampler DestroySampler = nullptr;
|
||||
|
||||
// pipeline / descriptors
|
||||
PFN_vkCreateShaderModule CreateShaderModule = nullptr;
|
||||
PFN_vkDestroyShaderModule DestroyShaderModule = nullptr;
|
||||
PFN_vkCreateDescriptorSetLayout CreateDescriptorSetLayout = nullptr;
|
||||
@@ -84,15 +61,10 @@ struct DeviceDispatch {
|
||||
PFN_vkDestroyPipeline DestroyPipeline = nullptr;
|
||||
PFN_vkCreateDescriptorPool CreateDescriptorPool = nullptr;
|
||||
PFN_vkDestroyDescriptorPool DestroyDescriptorPool = nullptr;
|
||||
// Frees every set in the pool at once - used by FrameGen::configure() to reclaim
|
||||
// the old scratch descriptor sets when a perf_preset change forces a live rebuild
|
||||
// (otherwise repeated preset switches would exhaust the pool). May be null in
|
||||
// theory; the rebuild path null-checks before calling.
|
||||
PFN_vkResetDescriptorPool ResetDescriptorPool = nullptr;
|
||||
PFN_vkAllocateDescriptorSets AllocateDescriptorSets = nullptr;
|
||||
PFN_vkUpdateDescriptorSets UpdateDescriptorSets = nullptr;
|
||||
|
||||
// command recording
|
||||
PFN_vkCreateCommandPool CreateCommandPool = nullptr;
|
||||
PFN_vkDestroyCommandPool DestroyCommandPool = nullptr;
|
||||
PFN_vkAllocateCommandBuffers AllocateCommandBuffers = nullptr;
|
||||
@@ -104,7 +76,7 @@ struct DeviceDispatch {
|
||||
PFN_vkCmdDispatch CmdDispatch = nullptr;
|
||||
PFN_vkCmdPipelineBarrier CmdPipelineBarrier = nullptr;
|
||||
PFN_vkCmdCopyImage CmdCopyImage = nullptr;
|
||||
PFN_vkCmdCopyImageToBuffer CmdCopyImageToBuffer = nullptr; // capture readback
|
||||
PFN_vkCmdCopyImageToBuffer CmdCopyImageToBuffer = nullptr;
|
||||
PFN_vkCmdBlitImage CmdBlitImage = nullptr;
|
||||
PFN_vkCmdClearColorImage CmdClearColorImage = nullptr;
|
||||
PFN_vkCreateFence CreateFence = nullptr;
|
||||
@@ -120,4 +92,4 @@ static inline void load(PFN& slot, PFN_vkGetDeviceProcAddr gdpa, VkDevice dev, c
|
||||
slot = reinterpret_cast<PFN>(gdpa(dev, name));
|
||||
}
|
||||
|
||||
} // namespace winfg
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// See winfg_engine.h.
|
||||
#include "winfg_engine.h"
|
||||
#include "winfg_vkd.h"
|
||||
#include "log.hpp"
|
||||
@@ -8,11 +7,6 @@
|
||||
namespace winfg {
|
||||
namespace {
|
||||
|
||||
// 2x: the guest layer synthesises at 0.35, not 0.5 - biased towards prev to
|
||||
// cut the double-exposure ghost on HUD/text ("iterative bring-up knob" in
|
||||
// layer.cpp). Kept for parity, since that is the tuning that was proven.
|
||||
// 3x/4x: evenly spaced; never exercised by the guest layer, so this is the
|
||||
// new ground native opens up and the first thing to judge by eye.
|
||||
float alphaFor(uint32_t k, uint32_t n) {
|
||||
if (n <= 1) return 0.35f;
|
||||
return (float)(k + 1) / (float)(n + 1);
|
||||
@@ -30,7 +24,7 @@ VkImageMemoryBarrier bar(VkImage img, VkImageLayout from, VkImageLayout to,
|
||||
return b;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
}
|
||||
|
||||
Engine::Engine() = default;
|
||||
Engine::~Engine() { destroyRing(); fg_.destroy(); }
|
||||
@@ -65,8 +59,8 @@ bool Engine::prepare(uint32_t w, uint32_t h, VkFormat fmt) {
|
||||
if (unavailable_ || w == 0 || h == 0 || fmt == VK_FORMAT_UNDEFINED) return false;
|
||||
if (builtW_ == w && builtH_ == h && builtFmt_ == fmt && fg_.valid()) return true;
|
||||
|
||||
// onResize rebuilds the pyramids/scratch; our own input ring follows it.
|
||||
if (!fg_.onResize(VkExtent2D{w, h}, fmt, /*force=*/false)) {
|
||||
|
||||
if (!fg_.onResize(VkExtent2D{w, h}, fmt, false)) {
|
||||
WFG_LOGE("native: onResize %ux%u failed; frame generation unavailable", w, h);
|
||||
unavailable_ = true;
|
||||
return false;
|
||||
@@ -83,21 +77,17 @@ bool Engine::prepare(uint32_t w, uint32_t h, VkFormat fmt) {
|
||||
}
|
||||
|
||||
uint32_t Engine::plan(uint32_t capacity) const {
|
||||
if (!valid() || history_ < 2) return 0; // need prev AND curr
|
||||
if (!valid() || history_ < 2) return 0;
|
||||
const uint32_t want = (uint32_t)std::max(cfg_.multiplier - 1, 0);
|
||||
return std::min({want, capacity, kMaxGenerations});
|
||||
}
|
||||
|
||||
void Engine::process(VkCommandBuffer cmd, VkImage source, uint32_t w, uint32_t h,
|
||||
uint32_t /*generations*/) {
|
||||
uint32_t ) {
|
||||
if (!valid()) return;
|
||||
// Rotate: the slot we are about to overwrite is the OLD prev; after the
|
||||
// copy it is the new curr and the other slot is prev.
|
||||
curr_ = 1 - curr_;
|
||||
Slot& dst = ring_[curr_];
|
||||
|
||||
// source is GENERAL (composite render pass leaves it there); the slot may
|
||||
// be in any layout from last use - UNDEFINED is legal as "discard".
|
||||
VkImageMemoryBarrier pre[2] = {
|
||||
bar(source, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT),
|
||||
@@ -115,8 +105,6 @@ void Engine::process(VkCommandBuffer cmd, VkImage source, uint32_t w, uint32_t h
|
||||
dd_.CmdCopyImage(cmd, source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
dst.img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||
|
||||
// source back to GENERAL for the real-frame copy that follows; the new
|
||||
// curr becomes SHADER_READ_ONLY, which is what the chain samples it as.
|
||||
VkImageMemoryBarrier post[2] = {
|
||||
bar(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT),
|
||||
@@ -138,17 +126,11 @@ void Engine::generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t cou
|
||||
const Slot& curr = ring_[curr_];
|
||||
if (!prev.valid || !curr.valid) return;
|
||||
|
||||
// The chain writes the output through a STORAGE descriptor in GENERAL.
|
||||
// The composite ring already keeps its targets in GENERAL; make the
|
||||
// prior contents' writes visible and take ownership for the compute write.
|
||||
VkImageMemoryBarrier toWrite = bar(targetImage, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT);
|
||||
dd_.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toWrite);
|
||||
|
||||
// gmSlot indexes the C1 global-motion SSBO read back on the CPU next
|
||||
// frame; kGmSlots (3) >= our frames in flight (2), so rotating per source
|
||||
// frame keeps a slot from being read while its reduce is still in flight.
|
||||
fg_.record(cmd, prev.view, curr.view, targetView, alphaFor(generation, count), gmSlot_);
|
||||
if (generation + 1 == count) gmSlot_ = (gmSlot_ + 1) % (uint32_t)FrameGen::kGmSlots;
|
||||
|
||||
@@ -157,7 +139,6 @@ void Engine::generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t cou
|
||||
|
||||
void Engine::reset() { history_ = 0; frames_ = 0; }
|
||||
|
||||
// ---- input ring --------------------------------------------------------------
|
||||
|
||||
uint32_t Engine::memType(uint32_t bits, VkMemoryPropertyFlags props) const {
|
||||
for (uint32_t i = 0; i < memProps_.memoryTypeCount; i++)
|
||||
@@ -201,4 +182,4 @@ void Engine::destroyRing() {
|
||||
history_ = 0;
|
||||
}
|
||||
|
||||
} // namespace winfg
|
||||
}
|
||||
@@ -1,28 +1,4 @@
|
||||
#pragma once
|
||||
// ============================================================================
|
||||
// winfg_engine - win-fg (Bannerlator's own frame generation) running inside
|
||||
// the compositor, behind the same contract the LSFG engine uses:
|
||||
//
|
||||
// prepare(w, h, format) build/rebuild the chain for this size
|
||||
// plan(capacity) how many frames to generate this time
|
||||
// process(cmd, source, w, h, gens) take frame N in (becomes "curr")
|
||||
// generateInto(cmd, k, n, ...) synthesise generated frame k of n
|
||||
//
|
||||
// Why this exists: as a guest-side Vulkan layer, win-fg's generated frames
|
||||
// had to cross Wine -> DRI3 -> our X server -> the compositor, where the
|
||||
// per-window latest-wins overwrite dropped them before they were ever
|
||||
// presented - the HUD counted 2x, the panel got 1x. Here the chain runs in the
|
||||
// compositor's own command stream and its frames are presented directly, on
|
||||
// the same multi-present path that is device-proven for LSFG Native.
|
||||
//
|
||||
// Unlike LSFG this needs nothing from the user: the ten shaders are ours
|
||||
// (MIT, see LICENSE.win-fg / NOTICE_FIDELITYFX_OPTICALFLOW.md) and embedded.
|
||||
//
|
||||
// The chain (framegen.cpp) is used unmodified. Its inputs are two sampled
|
||||
// rgba8 views (prev, curr) and its output a GENERAL storage view; the guest
|
||||
// layer transitions curr to SHADER_READ_ONLY before record() and the output
|
||||
// UNDEFINED -> GENERAL, so we do exactly the same around the same call.
|
||||
// ============================================================================
|
||||
#include <cstdint>
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "framegen.hpp"
|
||||
@@ -32,8 +8,6 @@ struct VkTable;
|
||||
|
||||
namespace winfg {
|
||||
|
||||
// Hard ceiling on generated frames per source frame (2x..4x -> 1..3), matching
|
||||
// the LSFG engine and the drawer's buttons.
|
||||
constexpr uint32_t kMaxGenerations = 3;
|
||||
|
||||
class Engine {
|
||||
@@ -48,22 +22,15 @@ public:
|
||||
bool valid() const { return fg_.valid() && !unavailable_; }
|
||||
bool unavailable() const { return unavailable_; }
|
||||
|
||||
// model 3/4, perf preset 0..2, flow scale - the same knobs the drawer
|
||||
// already exposes for win-fg; they map straight onto winfg::Config.
|
||||
void configure(uint32_t multiplier, int model, int perfPreset, float flowScale);
|
||||
|
||||
bool prepare(uint32_t width, uint32_t height, VkFormat format);
|
||||
|
||||
// 0 until there is a previous frame to interpolate from.
|
||||
uint32_t plan(uint32_t capacity) const;
|
||||
|
||||
// Copy the composited frame into the input ring as "curr" (the previous
|
||||
// one becomes "prev"). `source` must be in GENERAL.
|
||||
void process(VkCommandBuffer cmd, VkImage source, uint32_t width, uint32_t height,
|
||||
uint32_t generations);
|
||||
|
||||
// Synthesise generated frame `generation` of `count` into the storage view.
|
||||
// The target must be in GENERAL (the composite ring keeps it there).
|
||||
void generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t count,
|
||||
VkImage targetImage, VkImageView targetView,
|
||||
uint32_t width, uint32_t height);
|
||||
@@ -91,12 +58,12 @@ private:
|
||||
bool unavailable_ = false;
|
||||
|
||||
Slot ring_[2];
|
||||
int curr_ = 0; // index of "curr" in ring_; prev is 1 - curr_
|
||||
uint32_t history_ = 0; // frames copied so far (need 2)
|
||||
uint32_t gmSlot_ = 0; // rotates through FrameGen::kGmSlots
|
||||
int curr_ = 0;
|
||||
uint32_t history_ = 0;
|
||||
uint32_t gmSlot_ = 0;
|
||||
uint32_t builtW_ = 0, builtH_ = 0;
|
||||
VkFormat builtFmt_ = VK_FORMAT_UNDEFINED;
|
||||
uint64_t frames_ = 0;
|
||||
};
|
||||
|
||||
} // namespace winfg
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// See winfg_vkd.h.
|
||||
#include "winfg_vkd.h"
|
||||
#include "../VulkanRendererContext.h"
|
||||
#include <android/log.h>
|
||||
|
||||
@@ -1,19 +1,4 @@
|
||||
#pragma once
|
||||
// ============================================================================
|
||||
// winfg_vkd - fill win-fg's own dispatch tables from the renderer's VkTable.
|
||||
//
|
||||
// win-fg's chain (framegen.cpp / record_impl.inc) was written as a Vulkan
|
||||
// LAYER and calls through winfg::DeviceDispatch / InstanceDispatch. Inside the
|
||||
// compositor there is no layer chain to resolve those from, so they are filled
|
||||
// from the renderer's per-context table instead - the same bridge lsfg_vkd
|
||||
// provides for the LSFG chain, for the same reason: an adrenotools driver
|
||||
// shares no global symbols with the system loader.
|
||||
//
|
||||
// Only the entry points the chain actually calls are filled (36 device-level
|
||||
// plus GetPhysicalDeviceMemoryProperties); everything else in the structs
|
||||
// stays null and is never reached. winfgVkdInit reports a false if any of
|
||||
// those failed to resolve, so a null is never called mid-frame.
|
||||
// ============================================================================
|
||||
#include "vk_dispatch.hpp"
|
||||
struct VkTable;
|
||||
bool winfgVkdInit(const VkTable& table, winfg::DeviceDispatch& dd, winfg::InstanceDispatch& id);
|
||||
@@ -1,17 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
// Renderer-neutral direct-scanout implementation (Android SurfaceControl /
|
||||
// SurfaceFlinger). Pure NDK: dlsym'd ASurfaceControl_* / ASurfaceTransaction_*
|
||||
// plus AHardwareBuffer_*. It contains ZERO Vulkan/GL/EGL calls and ZERO
|
||||
// render-thread coupling: the owning renderer drives it through the public
|
||||
// surface below and decides WHEN the deferred cursor transaction is applied
|
||||
// (the Vulkan path defers to its render loop; a GL path would apply inline).
|
||||
//
|
||||
// Extracted verbatim from VulkanRendererContext / VulkanRendererScanout.cpp
|
||||
// (P0 of the GL direct-scanout plan). The only intended semantic change vs the
|
||||
// old code is that the cursor render-thread signalling (needsRender / dirtyCV)
|
||||
// stays in the owner: setCursorImage()/setCursorPos() return whether an apply
|
||||
// became pending so the owner can signal its loop exactly as before.
|
||||
|
||||
#include <android/hardware_buffer.h>
|
||||
#include <android/native_window.h>
|
||||
@@ -27,50 +15,34 @@
|
||||
|
||||
class ScanoutContext {
|
||||
public:
|
||||
// --- API load / lifecycle ---
|
||||
bool loadApi();
|
||||
// Fallback path: creates the child SurfaceControls under the owner's own
|
||||
// ANativeWindow. Passing a window updates the stored fallback window.
|
||||
void initFromWindow(ANativeWindow* win = nullptr);
|
||||
// Primary path: wraps the two sibling game/cursor windows (handed in by the
|
||||
// owner) as ASurfaceControls. Takes ownership and releases both windows.
|
||||
void initFromWindows(ANativeWindow* gameWin, ANativeWindow* cursorWin);
|
||||
void destroy();
|
||||
|
||||
// --- per-frame game buffer (applies its transaction INLINE) ---
|
||||
void setBuffer(AHardwareBuffer* ahb, int x, int y, int w, int h, int fenceFd = -1);
|
||||
|
||||
// --- cursor: pure state-setters. They mutate state only and DO NOT signal
|
||||
// any render thread. Each returns true if a deferred apply is now
|
||||
// pending (i.e. the owner should arrange for applyPendingCursor()).
|
||||
bool setCursorImage(void* pixels, short w, short h, short stride);
|
||||
bool setCursorPos(short x, short y, short hotX, short hotY);
|
||||
// Applies the pending cursor image/position transaction. Safe to call from
|
||||
// any thread (a SurfaceControl transaction apply is thread-safe). The Vulkan
|
||||
// owner calls this from its render loop; an inline owner may call it directly.
|
||||
void applyPendingCursor();
|
||||
|
||||
// --- geometry / sizing fed by the owner ---
|
||||
void setDst(int x, int y, int w, int h);
|
||||
void setContainerSize(int w, int h) { containerWidth = w; containerHeight = h; }
|
||||
void setSurfaceSize(int w, int h) { surfaceWidth = w; surfaceHeight = h; }
|
||||
void setFallbackWindow(ANativeWindow* win) { window = win; }
|
||||
void setVerboseLog(bool v) { verboseLog = v; }
|
||||
|
||||
// --- queries ---
|
||||
bool isActive() const { return scanoutActive.load(); }
|
||||
bool isGameFrameDelivered() const {
|
||||
return gameFrameDelivered.load(std::memory_order_acquire);
|
||||
}
|
||||
void* debugGameSC() const { return scanoutGameSC; }
|
||||
|
||||
// Public so the owner can expose them through its existing ABI without a
|
||||
// second source of truth (Vulkan binds references to these).
|
||||
std::atomic<bool> scanoutActive{false};
|
||||
std::atomic<bool> gameFrameDelivered{false};
|
||||
|
||||
private:
|
||||
ANativeWindow* window = nullptr; // owner's own window, for the fallback path
|
||||
ANativeWindow* window = nullptr;
|
||||
bool verboseLog = true;
|
||||
|
||||
int surfaceWidth = 0, surfaceHeight = 0, containerWidth = 0, containerHeight = 0;
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
/*
|
||||
* One function: the release fence the fake-input ring writer needs.
|
||||
*
|
||||
* FakeInputWriter publishes events into a shared-memory ring that libfakeinput.so reads from
|
||||
* another process. Java's memory model says nothing about what a foreign reader sees, so the
|
||||
* writer calls this between filling the events and advancing the sequence counter: everything
|
||||
* written before it is visible to the reader before the counter moves.
|
||||
*/
|
||||
#include <jni.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// 1116.2.0
|
||||
#pragma once
|
||||
const uint32_t cas_code[] = {
|
||||
0x07230203,0x00010000,0x0008000b,0x000000f3,0x00000000,0x00020011,0x00000001,0x0006000b,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// 1116.5.0
|
||||
#pragma once
|
||||
const uint32_t color_code[] = {
|
||||
0x07230203,0x00010000,0x0008000b,0x00000059,0x00000000,0x00020011,0x00000001,0x0006000b,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// 1116.2.0
|
||||
#pragma once
|
||||
const uint32_t crt_code[] = {
|
||||
0x07230203,0x00010000,0x0008000b,0x00000063,0x00000000,0x00020011,0x00000001,0x0006000b,
|
||||
|
||||
Loaded 100 of 852 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user