mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Compare commits
401
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f635e92498 | ||
|
|
eda1e9b9c6 | ||
|
|
fa9a9446bb | ||
|
|
3e3c4d69e4 | ||
|
|
913806f169 | ||
|
|
7b018e6c42 | ||
|
|
e45d6f7192 | ||
|
|
b3ff93e80f | ||
|
|
c035351b4f | ||
|
|
10cdf97480 | ||
|
|
f614483d01 | ||
|
|
f5eb5e7b81 | ||
|
|
bf99dbbe73 | ||
|
|
46fac69438 | ||
|
|
82f04fb22e | ||
|
|
9796525449 | ||
|
|
9838c83fc2 | ||
|
|
799be037e5 | ||
|
|
d99d728ba5 | ||
|
|
4b17bc9a23 | ||
|
|
4ac78621cc | ||
|
|
079261759e | ||
|
|
3fe03c0a88 | ||
|
|
6211faf6bc | ||
|
|
81e645a917 | ||
|
|
06b09b37bd | ||
|
|
0c3444afbc | ||
|
|
0988cdc43a | ||
|
|
06bd0de4d7 | ||
|
|
3a408058ba | ||
|
|
a074ba994b | ||
|
|
d310e59fce | ||
|
|
0d0465d19e | ||
|
|
83ec5708ed | ||
|
|
4163b31b02 | ||
|
|
02888834d4 | ||
|
|
ec537421ac | ||
|
|
5e23c668f3 | ||
|
|
a12ac8134a | ||
|
|
4562d61139 | ||
|
|
cc5ae4ff0b | ||
|
|
f0c52c273b | ||
|
|
975c76e3e8 | ||
|
|
a394dfd31b | ||
|
|
a2e555f4cc | ||
|
|
31d12a9f6c | ||
|
|
3820445561 | ||
|
|
75d7edd2c3 | ||
|
|
da8fe068d9 | ||
|
|
8f9ce19638 | ||
|
|
b74edb7e8d | ||
|
|
2503d69293 | ||
|
|
5e38e79c96 | ||
|
|
5e5ffee93e | ||
|
|
a7662ae13a | ||
|
|
a249e896f7 | ||
|
|
ca3743520e | ||
|
|
98822fdd27 | ||
|
|
8d0225932c | ||
|
|
40bca96db5 | ||
|
|
a2a9586b54 | ||
|
|
2fe6ba36c4 | ||
|
|
e4c84e6e24 | ||
|
|
10ef8c4bd6 | ||
|
|
8e01cfb2ef | ||
|
|
b25c4ea26b | ||
|
|
cebad5692d | ||
|
|
6b2c8ace09 | ||
|
|
4d1b8421fb | ||
|
|
ced4ac5db8 | ||
|
|
e190f57b53 | ||
|
|
22f02fb6bf | ||
|
|
52c67df4c7 | ||
|
|
de1a734fb5 | ||
|
|
be9939d82c | ||
|
|
e4c375ad0d | ||
|
|
0cafa83ea7 | ||
|
|
435beb7f19 | ||
|
|
78fcb60fd5 | ||
|
|
4995b3553b | ||
|
|
3c2878cc52 | ||
|
|
715032062d | ||
|
|
dde08414fc | ||
|
|
ec24b3a472 | ||
|
|
6455b1dade | ||
|
|
542ad3e060 | ||
|
|
910fb47692 | ||
|
|
3dd14dd625 | ||
|
|
d1b0ac4df1 | ||
|
|
0e225d0c6e | ||
|
|
d9cd0d2cc8 | ||
|
|
eadebc46d5 | ||
|
|
96d7c66535 | ||
|
|
90690d1c5c | ||
|
|
abbcb8e3ad | ||
|
|
fe60b3c279 | ||
|
|
c7455b74ff | ||
|
|
c875ceda4d | ||
|
|
666abd134f | ||
|
|
c115916962 | ||
|
|
0579e688ba | ||
|
|
bc50de3b55 | ||
|
|
6489232598 | ||
|
|
c23c436502 | ||
|
|
fad52ec125 | ||
|
|
d22e458d64 | ||
|
|
1b7cb4179d | ||
|
|
c1c22dfd37 | ||
|
|
0043cefab5 | ||
|
|
41f68ef1a1 | ||
|
|
9743ac3abc | ||
|
|
ca6cfcd193 | ||
|
|
d41424170a | ||
|
|
f4678935c4 | ||
|
|
416eb41d8d | ||
|
|
6e0d456dd7 | ||
|
|
8ac555664a | ||
|
|
4bf4e53ecf | ||
|
|
3d50e6b1ae | ||
|
|
846b20f78a | ||
|
|
a229d196bb | ||
|
|
61bfc04704 | ||
|
|
9b90386e73 | ||
|
|
ddc4c18faf | ||
|
|
fb8361b197 | ||
|
|
a5fd64f9f2 | ||
|
|
e0a7ce4f88 | ||
|
|
b213ae77b4 | ||
|
|
6e5932cb68 | ||
|
|
38e3fa2a33 | ||
|
|
8e1a676f19 | ||
|
|
7c946db440 | ||
|
|
eaf26c8a20 | ||
|
|
0a96767657 | ||
|
|
32054e156e | ||
|
|
fd6d6b7c46 | ||
|
|
c101a451e3 | ||
|
|
a76ba69217 | ||
|
|
d06caf9838 | ||
|
|
134fa167fe | ||
|
|
da42a5d7a7 | ||
|
|
b6c1d3c09c | ||
|
|
a58b0ac3e0 | ||
|
|
e1700ba982 | ||
|
|
736ac7db74 | ||
|
|
6e2966be43 | ||
|
|
679948e792 | ||
|
|
e0fc8362b8 | ||
|
|
9d8ff9615f | ||
|
|
1304809240 | ||
|
|
343a516e1a | ||
|
|
7c08cb58ae | ||
|
|
7e81668de2 | ||
|
|
d231564f8e | ||
|
|
37e89c5b4d | ||
|
|
e070bd72e0 | ||
|
|
9310d19857 | ||
|
|
4454a6d39a | ||
|
|
453f6f5180 | ||
|
|
6d0484941e | ||
|
|
cf47910881 | ||
|
|
4f0f0cf5b1 | ||
|
|
a78d5cb600 | ||
|
|
206c8a8846 | ||
|
|
bf17ce1151 | ||
|
|
ef38c975b9 | ||
|
|
61953c4f7b | ||
|
|
1f13285ab8 | ||
|
|
1bc23ac169 | ||
|
|
50f50b16e0 | ||
|
|
adc86fc7df | ||
|
|
f3fd3a2307 | ||
|
|
1e877fa602 | ||
|
|
906009a33a | ||
|
|
08559a02f8 | ||
|
|
1d93626453 | ||
|
|
9d0989374b | ||
|
|
3278afaa1a | ||
|
|
bf221ffbbd | ||
|
|
c636eea355 | ||
|
|
ffaa906cd1 | ||
|
|
d61af961c1 | ||
|
|
bc01b77752 | ||
|
|
a1b01a04a8 | ||
|
|
b44235c495 | ||
|
|
b80d5db176 | ||
|
|
d3c0d258b7 | ||
|
|
9dcdbf245a | ||
|
|
369e218afb | ||
|
|
119d24bf6b | ||
|
|
2a905510d5 | ||
|
|
da29919b5a | ||
|
|
05608ac4d4 | ||
|
|
135b84a501 | ||
|
|
e7cbba37d1 | ||
|
|
d9bb5e15b0 | ||
|
|
46cdc32cf1 | ||
|
|
387572e733 | ||
|
|
9f46c7a50b | ||
|
|
0607bb0b3a | ||
|
|
b89d87cbde | ||
|
|
06e06b9673 | ||
|
|
b896e7a3b9 | ||
|
|
c91daa9147 | ||
|
|
31ea194f3d | ||
|
|
472bb28c88 | ||
|
|
eaa4a7a047 | ||
|
|
4fa467c800 | ||
|
|
beb96e6f0c | ||
|
|
26cb9e2f03 | ||
|
|
6dc74523c3 | ||
|
|
3858a69867 | ||
|
|
49a5f4aa94 | ||
|
|
4cc9f2a3f3 | ||
|
|
b0d7fc01d8 | ||
|
|
9cc40246a3 | ||
|
|
423cf11919 | ||
|
|
33f7213599 | ||
|
|
6f1fb462ed | ||
|
|
336b8851f7 | ||
|
|
5da3cf673c | ||
|
|
9e54e094fb | ||
|
|
eb9ca4ab6b | ||
|
|
e7b6876877 | ||
|
|
c535f6ef6e | ||
|
|
db2e1d7da2 | ||
|
|
f5e204eb1d | ||
|
|
705a5e7b2a | ||
|
|
e60eae3646 | ||
|
|
b04900b1b2 | ||
|
|
6357543b44 | ||
|
|
67fb338e5d | ||
|
|
ba140c4ab5 | ||
|
|
f5fdbc7941 | ||
|
|
90ae85da4d | ||
|
|
f3600fa618 | ||
|
|
2d8befe1cb | ||
|
|
f982ffcaf7 | ||
|
|
255c64551e | ||
|
|
746f28c1b2 | ||
|
|
2b481ba250 | ||
|
|
49622623de | ||
|
|
69f99ade42 | ||
|
|
c236e9711f | ||
|
|
4c2efb6688 | ||
|
|
7eb2d1cf1a | ||
|
|
b4ff83cf9f | ||
|
|
7b20b70c6b | ||
|
|
86207f9d0e | ||
|
|
eda5ece382 | ||
|
|
3404bbc128 | ||
|
|
b5a431aa3c | ||
|
|
49249f6541 | ||
|
|
5614621d6e | ||
|
|
0816562065 | ||
|
|
39618e6862 | ||
|
|
c66e4ba4b3 | ||
|
|
e4b033861f | ||
|
|
85a07a556c | ||
|
|
8dfc48d58b | ||
|
|
82ebdaa0b5 | ||
|
|
ec016f85bc | ||
|
|
c2c812deaa | ||
|
|
eec3428f78 | ||
|
|
4a5e4b76e3 | ||
|
|
43f952b2fb | ||
|
|
0d86bec34d | ||
|
|
47a6f29ad7 | ||
|
|
84834f180e | ||
|
|
bfd445618a | ||
|
|
eddfd37b93 | ||
|
|
d7341863bf | ||
|
|
2f51be7b5a | ||
|
|
fbc0014d14 | ||
|
|
46df6171fc | ||
|
|
761b62914f | ||
|
|
91b0299f77 | ||
|
|
6d08e59d10 | ||
|
|
83cc0fe585 | ||
|
|
c711c627b8 | ||
|
|
0b34690721 | ||
|
|
b640734db5 | ||
|
|
f8feb63dde | ||
|
|
25a5d3fe5d | ||
|
|
41dd93fd25 | ||
|
|
adf3c9405d | ||
|
|
abf4f57b47 | ||
|
|
c1e36975d4 | ||
|
|
0bfa0915d4 | ||
|
|
4da87672a2 | ||
|
|
3ba0d287e9 | ||
|
|
b4a86cb5bb | ||
|
|
0fa1f2f868 | ||
|
|
71610ddfdb | ||
|
|
5c9e683769 | ||
|
|
32a5e83b88 | ||
|
|
88904c08f7 | ||
|
|
9928bfe780 | ||
|
|
a491dec5c4 | ||
|
|
17ef33ee31 | ||
|
|
06caaee343 | ||
|
|
1c161a76dd | ||
|
|
aa05ab03fa | ||
|
|
43ea3f3181 | ||
|
|
3966b27948 | ||
|
|
7e24327621 | ||
|
|
d025aa3c6d | ||
|
|
325f6893c5 | ||
|
|
466419407d | ||
|
|
cbd4ce6636 | ||
|
|
d4130181e7 | ||
|
|
16a9d0af2a | ||
|
|
e51698e01f | ||
|
|
519091dd86 | ||
|
|
972088f1b3 | ||
|
|
542d31daf0 | ||
|
|
8f9a849c48 | ||
|
|
e8bfe6e520 | ||
|
|
15618f357b | ||
|
|
b74010435a | ||
|
|
0a58a7faee | ||
|
|
0d21e2f913 | ||
|
|
ebb82d2dc9 | ||
|
|
557e346810 | ||
|
|
08b2cf0894 | ||
|
|
4e90e03536 | ||
|
|
11d997dc65 | ||
|
|
454193da9b | ||
|
|
cdd5c99bc7 | ||
|
|
fb1da1c39a | ||
|
|
b29ece82c8 | ||
|
|
1330fe4d6c | ||
|
|
0e1f6f15a1 | ||
|
|
4df0e012fd | ||
|
|
48054a86ab | ||
|
|
38d658abf5 | ||
|
|
5547391cfc | ||
|
|
6782e5a717 | ||
|
|
e1a80c1751 | ||
|
|
05933a5ee3 | ||
|
|
1f29cd92a5 | ||
|
|
4f30356c9b | ||
|
|
baebf50197 | ||
|
|
01674b7c6f | ||
|
|
728bfa2043 | ||
|
|
c4de482b6a | ||
|
|
55697535a1 | ||
|
|
65733c23a1 | ||
|
|
fd51b86060 | ||
|
|
d6e4ab1824 | ||
|
|
566c7d703a | ||
|
|
d7eb3256a5 | ||
|
|
c96f2d1070 | ||
|
|
cc8b743ecb | ||
|
|
45422b739b | ||
|
|
f58bf9b23c | ||
|
|
f0e8ef1274 | ||
|
|
bb6d6fee97 | ||
|
|
d59734e375 | ||
|
|
14db12290e | ||
|
|
1e476539df | ||
|
|
3c8e370744 | ||
|
|
3fcd01f536 | ||
|
|
0e2ee35b49 | ||
|
|
27920711f0 | ||
|
|
d4c16b5b59 | ||
|
|
e392364e30 | ||
|
|
3cdb77fd65 | ||
|
|
a91be68992 | ||
|
|
e16f351540 | ||
|
|
efdab60b22 | ||
|
|
8dc24f244c | ||
|
|
b565c4a6e5 | ||
|
|
a999fbdb38 | ||
|
|
52eee568f5 | ||
|
|
05cebd4a4f | ||
|
|
8cfd54a739 | ||
|
|
8565acf12b | ||
|
|
941a6c3ef9 | ||
|
|
fe84c050ad | ||
|
|
f13c885fec | ||
|
|
8ee1cbe3eb | ||
|
|
6c5d6cda73 | ||
|
|
2725ee4512 | ||
|
|
7cc1c93775 | ||
|
|
38c9b733b5 | ||
|
|
27e5f86793 | ||
|
|
a6331e44ae | ||
|
|
f53b844add | ||
|
|
fb34098006 | ||
|
|
c729f4c8e7 | ||
|
|
9f343278c3 | ||
|
|
6beeb29486 | ||
|
|
ade2e982b6 | ||
|
|
8a61a1898b | ||
|
|
93382ff241 | ||
|
|
24b74d8346 | ||
|
|
051b9a320e | ||
|
|
71057fefe1 | ||
|
|
49934ce933 | ||
|
|
5179b54745 |
No files matched your search
@@ -0,0 +1,67 @@
|
||||
language: en-US
|
||||
early_access: false
|
||||
tone_instructions: >-
|
||||
Prioritize actionable correctness, security, and runtime regressions. Keep
|
||||
feedback concise. Do not request explanatory code comments; preserve license
|
||||
headers and compiler-consumed annotations.
|
||||
reviews:
|
||||
profile: chill
|
||||
request_changes_workflow: false
|
||||
high_level_summary: true
|
||||
high_level_summary_in_walkthrough: true
|
||||
poem: false
|
||||
sequence_diagrams: false
|
||||
tools:
|
||||
github-checks:
|
||||
enabled: false
|
||||
auto_review:
|
||||
enabled: true
|
||||
auto_incremental_review: true
|
||||
auto_pause_after_reviewed_commits: 0
|
||||
drafts: false
|
||||
pre_merge_checks:
|
||||
docstrings:
|
||||
mode: "off"
|
||||
title:
|
||||
mode: "off"
|
||||
description:
|
||||
mode: "off"
|
||||
finishing_touches:
|
||||
docstrings:
|
||||
enabled: false
|
||||
unit_tests:
|
||||
enabled: false
|
||||
simplify:
|
||||
enabled: false
|
||||
autofix:
|
||||
enabled: false
|
||||
fix_ci:
|
||||
enabled: false
|
||||
resolve_merge_conflict:
|
||||
enabled: false
|
||||
path_instructions:
|
||||
- path: "**/*"
|
||||
instructions: >-
|
||||
For cleanup changes, compare executable behavior against the PR merge
|
||||
base. Check for removed guards, changed return values, weakened compiler
|
||||
flags, and deleted regression tests. A passing APK build alone does not
|
||||
establish behavior preservation. Avoid style-only or comment-coverage
|
||||
requests.
|
||||
- path: "app/src/main/**"
|
||||
instructions: >-
|
||||
Review Android lifecycle, external-display visibility, input cancellation,
|
||||
permissions, threading, and Compose state. For JNI, native, and Rust
|
||||
changes, check ABI compatibility, resource ownership, synchronization,
|
||||
and error paths. Preserve persisted user settings and existing runtime
|
||||
contracts.
|
||||
- path: "tools/**"
|
||||
instructions: >-
|
||||
Check shell quoting, error handling, path and filesystem semantics, and
|
||||
Python helper behavior. Preserve MSI same-file protection and strict
|
||||
compiler checks. Verify downstream patches apply to their pinned sources
|
||||
and retain intended executable behavior, including embedded scripts.
|
||||
- path: ".github/workflows/**"
|
||||
instructions: >-
|
||||
Check job permissions, triggers, merge validation, build variants,
|
||||
signing, artifact paths, and release conditions. Verify checks apply to
|
||||
the current PR head and keep review configuration advisory.
|
||||
@@ -0,0 +1,4 @@
|
||||
tools/linuxfs/** text=auto eol=lf
|
||||
tools/mangoapp/** text=auto eol=lf
|
||||
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:
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
name: Build desktop package (KDE Plasma)
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'tools/desktop-kde/**'
|
||||
- '.github/workflows/build-desktop-kde.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to publish as (e.g. desktop-kde-r1); empty = artifact only'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
kwin:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build KWin with nested desktop scaling
|
||||
run: |
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq zstd xz-utils gpgv patch binutils >/dev/null
|
||||
bash tools/desktop-kde/build-kwin.sh out/kwin
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: desktop-kde-kwin
|
||||
retention-days: 7
|
||||
path: out/kwin/
|
||||
build:
|
||||
needs: kwin
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: desktop-kde-kwin
|
||||
path: out/kwin
|
||||
- uses: actions/create-github-app-token@v2
|
||||
id: components-token
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
with:
|
||||
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
|
||||
owner: Droid-Deck
|
||||
repositories: DroidDeck-Components
|
||||
- name: Refuse a published tag
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.components-token.outputs.token }}
|
||||
run: |
|
||||
if gh release view '${{ inputs.tag }}' -R Droid-Deck/DroidDeck-Components >/dev/null 2>&1; then
|
||||
echo "::error::${{ inputs.tag }} is already published, and published tags are never replaced: use a new tag"
|
||||
exit 1
|
||||
fi
|
||||
- name: Build
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq zstd xz-utils gpgv >/dev/null
|
||||
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)"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: desktop-kde
|
||||
retention-days: 7
|
||||
compression-level: 0
|
||||
path: |
|
||||
out/desktop-kde.tar.zst
|
||||
out/desktop-kde.packages.txt
|
||||
out/desktop-kde.kwin.txt
|
||||
out/desktop-kde.sha256
|
||||
- name: Publish
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.components-token.outputs.token }}
|
||||
GH_REPO: Droid-Deck/DroidDeck-Components
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG='${{ inputs.tag }}'
|
||||
if gh release view "$TAG" >/dev/null 2>&1; then
|
||||
echo "::error::$TAG is already published in DroidDeck-Components, and published tags are never replaced: use a new tag"
|
||||
exit 1
|
||||
fi
|
||||
gh release create "$TAG" --latest=false --prerelease=false --title "$TAG" \
|
||||
--notes "KDE Plasma desktop package for DroidDeck's Linux runtime ($GITHUB_REPOSITORY tools/desktop-kde, $GITHUB_SHA). size $(stat -c %s out/desktop-kde.tar.zst), sha256 $(cat out/desktop-kde.sha256). desktop-kde.packages.txt lists the signature-checked Arch Linux ARM packages it was built from." \
|
||||
out/desktop-kde.tar.zst out/desktop-kde.sha256 out/desktop-kde.packages.txt out/desktop-kde.kwin.txt
|
||||
gh release edit "$TAG" --latest=false --prerelease=false
|
||||
@@ -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:
|
||||
@@ -42,4 +38,5 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh release create "${{ inputs.tag }}" --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
|
||||
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
|
||||
gh release edit "${{ inputs.tag }}" --latest=false --prerelease=false
|
||||
@@ -1,46 +0,0 @@
|
||||
name: Build wlroots (patched)
|
||||
|
||||
# The desktop's wlroots (Arch Linux ARM's wlroots0.20, what the desktop package's labwc links)
|
||||
# rebuilt with tools/wlroots/patches: a Vulkan DMA-BUF allocator for the Adreno node, which is not
|
||||
# a DRM device, so labwc's vulkan and gles2 renderers can start. Built in an Arch Linux ARM
|
||||
# container on an arm64 runner and published as a release asset the apk build fetches
|
||||
# (tools/wlroots/release.env names the tag and sha). The hosted desktop package is never changed.
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'tools/wlroots/**'
|
||||
- '.github/workflows/build-wlroots.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to publish as (e.g. wlroots-0.20.2-p1); empty = artifact only'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build in Arch Linux ARM
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker run --rm -v "$PWD:/work" -w /work menci/archlinuxarm:base-devel bash tools/wlroots/build-in-arch.sh
|
||||
sudo chown -R "$(id -u):$(id -g)" .
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wlroots-patched
|
||||
path: |
|
||||
wlroots.tzst
|
||||
wlroots.tzst.sha256
|
||||
- name: Publish
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh release create "${{ inputs.tag }}" --title "${{ inputs.tag }}" --notes "wlroots 0.20.2 with tools/wlroots/patches (Vulkan DMA-BUF allocator for non-DRM GPU nodes); staged by the apk at /usr/local/lib/droiddeck-wlroots for the desktop's GPU renderers. sha256 $(cut -d' ' -f1 wlroots.tzst.sha256)" wlroots.tzst wlroots.tzst.sha256 || gh release upload "${{ inputs.tag }}" wlroots.tzst wlroots.tzst.sha256 --clobber
|
||||
+235
-83
@@ -22,13 +22,11 @@ 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' }}
|
||||
|
||||
env:
|
||||
PRELOAD_CACHE_VERSION: v2
|
||||
PRELOAD_CACHE_VERSION: v3
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -44,6 +42,19 @@ jobs:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check co-authors
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin main
|
||||
base=$(git merge-base origin/main HEAD)
|
||||
messages=$(git log --format=%B "$base..HEAD")
|
||||
messages+=$'\n'"$(jq -r '.pull_request.title, (.pull_request.body // "")' "$GITHUB_EVENT_PATH")"
|
||||
if grep -Eiq '^[[:space:]]*Co-authored-by:.*(Claude|Anthropic|OpenAI|ChatGPT|GPT|Copilot|Cursor|Gemini|Codex|Devin|Windsurf|Codeium|Amazon Q|Tabnine|Cody|\[bot\])' <<<"$messages"; then
|
||||
echo "::error::AI and bot identities cannot be co-authors; remove the Co-authored-by trailer."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- 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"
|
||||
@@ -57,6 +68,22 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Version bumps are isolated
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin main
|
||||
base=$(git merge-base origin/main HEAD)
|
||||
diff=$(git diff -U0 "$base..HEAD" -- app/build.gradle)
|
||||
grep -Eq '^[+-][[:space:]]*version(Code|Name)[[:space:]]' <<<"$diff" || exit 0
|
||||
other_files=$(git diff --name-only "$base..HEAD" | grep -v '^app/build.gradle$' || true)
|
||||
other_gradle=$(grep -E '^[+-]' <<<"$diff" | grep -Ev '^(---|\+\+\+)' | grep -Ev '^[+-][[:space:]]*version(Code|Name)[[:space:]]' || true)
|
||||
if [[ -n "$other_files" || -n "$other_gradle" ]]; then
|
||||
echo "::error::versionCode/versionName changes must be isolated in their own PR"
|
||||
echo "A version PR may only change the versionCode/versionName lines in app/build.gradle." >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Merge onto main, or say what conflicts
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
@@ -148,6 +175,8 @@ jobs:
|
||||
label="${BUILD_REF} at ${BUILD_SHA:0:7}"
|
||||
fi
|
||||
echo "label=$label" >> "$GITHUB_OUTPUT"
|
||||
echo "DROIDDECK_SOURCE_COMMIT=${PR_HEAD_SHA:-$BUILD_SHA}" >> "$GITHUB_ENV"
|
||||
echo "DROIDDECK_PR_NUMBER=${PR_NUMBER:-0}" >> "$GITHUB_ENV"
|
||||
echo "Build: \`$label\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Cache the preloads
|
||||
@@ -155,7 +184,7 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: app/src/main/assets/linuxfs
|
||||
key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/mangoapp/*') }}
|
||||
key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'app/src/main/cpp/adrenotools/src/hook/kgsl.h', 'tools/quest/*', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/build-x86-preloads.sh', 'tools/linuxfs/fex/*', 'tools/linuxfs/clipboard/*.c', 'tools/linuxfs/ssbs/*.c', 'tools/proot/fastpath/*.c', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/linuxfs/uruntime.env', 'tools/linuxfs/licenses/*', 'tools/mangoapp/*', 'tools/msitools/**', 'tools/comet/**', 'tools/linuxfs/debian-image.env', 'tools/vr/**') }}
|
||||
|
||||
- name: Build libfakeinput.so and libblsession.so (glibc aarch64)
|
||||
if: steps.preloads.outputs.cache-hit != 'true'
|
||||
@@ -174,7 +203,21 @@ jobs:
|
||||
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
|
||||
-o "$D/libblsession.so" tools/linuxfs/preload/*.c -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/libblsession.so"
|
||||
for script in tools/linuxfs/overlay/usr/local/bin/bannerlator-*; do
|
||||
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
|
||||
-o "$D/libblfastpath.so" tools/proot/fastpath/fastpath.c -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/libblfastpath.so"
|
||||
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"
|
||||
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"
|
||||
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
|
||||
install -Dm644 "$script" "$D/usr/local/bin/$(basename "$script")"
|
||||
done
|
||||
for f in tools/linuxfs/overlay/usr/bin/* tools/linuxfs/overlay/usr/bin/steamos-polkit-helpers/*; do
|
||||
@@ -182,20 +225,18 @@ jobs:
|
||||
done
|
||||
if [ -f tools/gamescope/release.env ]; then
|
||||
. tools/gamescope/release.env
|
||||
gh release download "${GAMESCOPE_TAG}" -R "${GITHUB_REPOSITORY}" -p gamescope.tzst -O gamescope.tzst
|
||||
gh release download "${GAMESCOPE_TAG}" -R "${GAMESCOPE_REPO:-${GITHUB_REPOSITORY}}" -p gamescope.tzst -O gamescope.tzst
|
||||
echo "${GAMESCOPE_SHA256} gamescope.tzst" | sha256sum -c -
|
||||
tar --use-compress-program=unzstd -xf gamescope.tzst -C "$D"
|
||||
test -f "$D/usr/local/bin/gamescope"
|
||||
rm gamescope.tzst
|
||||
fi
|
||||
if [ -f tools/wlroots/release.env ]; then
|
||||
. tools/wlroots/release.env
|
||||
gh release download "${WLROOTS_TAG}" -R "${GITHUB_REPOSITORY}" -p wlroots.tzst -O wlroots.tzst
|
||||
echo "${WLROOTS_SHA256} wlroots.tzst" | sha256sum -c -
|
||||
tar --use-compress-program=unzstd -xf wlroots.tzst -C "$D"
|
||||
test -f "$D/usr/local/lib/droiddeck-wlroots/libwlroots-0.20.so"
|
||||
rm wlroots.tzst
|
||||
fi
|
||||
. tools/linuxfs/uruntime.env
|
||||
curl -fsSL --retry 3 -o uruntime "https://github.com/VHSgunzo/uruntime/releases/download/${URUNTIME_VERSION}/${URUNTIME_ASSET}"
|
||||
echo "${URUNTIME_SHA256} uruntime" | sha256sum -c -
|
||||
install -Dm644 uruntime "$D/usr/local/lib/droiddeck/uruntime"
|
||||
install -Dm644 tools/linuxfs/licenses/uruntime-LICENSE "$D/usr/local/share/licenses/uruntime/LICENSE"
|
||||
rm uruntime
|
||||
M="$D/usr/local/lib/mangoapp"
|
||||
mkdir -p "$M" mango-pkgs
|
||||
grep -v '^#' tools/mangoapp/packages.txt | while read -r sum url; do
|
||||
@@ -206,21 +247,106 @@ jobs:
|
||||
rm mango-pkgs/pkg.tar.zst
|
||||
done
|
||||
install -m644 mango-pkgs/usr/bin/mangoapp "$M/mangoapp"
|
||||
for so in libfmt.so.10 libspdlog.so.1.13 libglfw.so.3 libtraceevent.so.1 libtracefs.so.1; do
|
||||
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
|
||||
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"
|
||||
T="$D/usr/local/lib/droiddeck-msitools"
|
||||
mkdir -p "$T" msi-pkgs
|
||||
grep -v '^#' tools/msitools/packages.txt | while read -r sum url; do
|
||||
[ -n "$url" ] || continue
|
||||
curl -fsSL --retry 3 -o msi-pkgs/pkg.tar.xz "$url"
|
||||
echo "$sum msi-pkgs/pkg.tar.xz" | sha256sum -c -
|
||||
tar -xJf msi-pkgs/pkg.tar.xz -C msi-pkgs
|
||||
rm msi-pkgs/pkg.tar.xz
|
||||
done
|
||||
install -m644 msi-pkgs/usr/bin/msiinfo "$T/msiinfo"
|
||||
install -m644 msi-pkgs/usr/bin/cabextract "$T/cabextract"
|
||||
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
|
||||
cp -L "msi-pkgs/usr/lib/$so" "$T/$so"
|
||||
done
|
||||
for f in NOTICE GPL-2 GPL-3 LGPL-2.1 7zip-License 7zip-unRarLicense; do
|
||||
install -Dm644 "tools/msitools/licenses/$f" "$D/usr/local/share/licenses/droiddeck-msitools/$f"
|
||||
done
|
||||
rm -rf msi-pkgs
|
||||
ls -l "$T"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop "$D/usr/local/bin/droiddeck-desktop"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-gpu "$D/usr/local/bin/droiddeck-gpu"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop-gpu "$D/usr/local/bin/droiddeck-desktop-gpu"
|
||||
install -Dm644 tools/linuxfs/desktop/autostart "$D/etc/xdg/labwc/autostart"
|
||||
install -Dm644 tools/linuxfs/desktop/rc.xml "$D/etc/xdg/labwc/rc.xml"
|
||||
install -Dm644 tools/linuxfs/desktop/panel.conf "$D/etc/xdg/lxqt/panel.conf"
|
||||
install -Dm644 tools/linuxfs/desktop/kwin_wayland_wrapper "$D/usr/local/bin/kwin_wayland_wrapper"
|
||||
install -Dm644 tools/linuxfs/desktop/firefox-droiddeck.js "$D/usr/lib/firefox/defaults/pref/droiddeck.js"
|
||||
ls -lR "$D"
|
||||
|
||||
- name: Fetch the Debian image the preloads build in
|
||||
if: steps.preloads.outputs.cache-hit != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
. tools/linuxfs/debian-image.env
|
||||
image=public.ecr.aws/docker/library/debian:bullseye
|
||||
if gh release download "$DEBIAN_IMAGE_TAG" -R "$GITHUB_REPOSITORY" -p "$DEBIAN_IMAGE_ASSET" -O /tmp/debian-image.tar.zst \
|
||||
&& { [ -z "$DEBIAN_IMAGE_SHA256" ] || echo "$DEBIAN_IMAGE_SHA256 /tmp/debian-image.tar.zst" | sha256sum -c -; }; then
|
||||
zstd -dc /tmp/debian-image.tar.zst | docker load
|
||||
rm -f /tmp/debian-image.tar.zst
|
||||
else
|
||||
echo "::warning::the hosted Debian image could not be fetched; pulling from the registry instead"
|
||||
for attempt in 1 2 3 4 5; do
|
||||
docker pull "$image" && break
|
||||
[ "$attempt" -lt 5 ] || { echo "::error::could not pull $image after $attempt attempts"; exit 1; }
|
||||
echo "pull attempt $attempt failed (registry busy?); retrying in $((attempt * 30)) s"
|
||||
sleep $((attempt * 30))
|
||||
done
|
||||
fi
|
||||
docker image ls "$image"
|
||||
|
||||
- name: Build the x86 preloads FEX guests load (glibc 2.31)
|
||||
if: steps.preloads.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker run --rm -v "$PWD:/src" -w /src public.ecr.aws/docker/library/debian:bullseye bash -c '
|
||||
set -euo pipefail
|
||||
printf "deb http://archive.debian.org/debian bullseye main\ndeb http://archive.debian.org/debian-security bullseye-security main\n" > /etc/apt/sources.list
|
||||
apt-get -o Acquire::Check-Valid-Until=false update -qq
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends gcc g++ gcc-multilib g++-multilib binutils >/dev/null
|
||||
tools/linuxfs/build-x86-preloads.sh app/src/main/assets/linuxfs
|
||||
'
|
||||
sudo chown -R "$(id -u):$(id -g)" app/src/main/assets/linuxfs
|
||||
ls -l app/src/main/assets/linuxfs/x86_64 app/src/main/assets/linuxfs/i386
|
||||
|
||||
- name: Bundle the droiddeck-esync packs
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -f tools/droiddeck-esync/release.env ]; then
|
||||
. tools/droiddeck-esync/release.env
|
||||
gh release download "$SYNC_BUNDLE_TAG" -R "$SYNC_BUNDLE_REPO" -p "$SYNC_BUNDLE_ASSET" -O sync-bundle.tzst
|
||||
echo "$SYNC_BUNDLE_SHA256 sync-bundle.tzst" | sha256sum -c -
|
||||
mkdir -p app/src/main/assets/droiddeck-esync
|
||||
tar --use-compress-program=unzstd -xf sync-bundle.tzst -C app/src/main/assets/droiddeck-esync
|
||||
rm sync-bundle.tzst
|
||||
gh release download "$SYNC_BUNDLE_TAG" -R "$SYNC_BUNDLE_REPO" -p index.json -O sync-index.json
|
||||
revoked=$(python3 -c 'import json, sys; print(" ".join(p["id"] for p in json.load(open(sys.argv[1]))["packs"] if p.get("revoked") is True))' sync-index.json)
|
||||
for id in $revoked; do
|
||||
if [ -e "app/src/main/assets/droiddeck-esync/packs/$id.tzst" ]; then
|
||||
echo "::warning::$SYNC_BUNDLE_ASSET carries revoked pack $id; it is left out of the APK (point tools/droiddeck-esync/release.env at a newer bundle)"
|
||||
rm -f "app/src/main/assets/droiddeck-esync/packs/$id.tzst"
|
||||
fi
|
||||
done
|
||||
rm sync-index.json
|
||||
test -f app/src/main/assets/droiddeck-esync/index.json
|
||||
test -f app/src/main/assets/droiddeck-esync/index.json.sig
|
||||
ls -lR app/src/main/assets/droiddeck-esync
|
||||
fi
|
||||
|
||||
- name: Cache PulseAudio 13.0 source
|
||||
id: pa-src
|
||||
uses: actions/cache@v4
|
||||
@@ -233,6 +359,8 @@ 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
|
||||
- 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
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -242,12 +370,12 @@ jobs:
|
||||
tar --use-compress-program=unzstd -xf app/src/main/assets/pulseaudio.tzst -C pa-bundle
|
||||
test ! -f pa-bundle/modules/arm64/module-aaudio-sink.so
|
||||
install -m755 sink-out/module-aaudio-sink.so pa-bundle/modules/arm64/module-aaudio-sink.so
|
||||
install -m755 sink-out/module-directaudio-sink.so pa-bundle/modules/arm64/module-directaudio-sink.so
|
||||
install -m755 sink-out/module-directaudio-native-sink.so pa-bundle/modules/arm64/module-directaudio-native-sink.so
|
||||
(cd pa-bundle && tar --use-compress-program='zstd -19' -cf ../pulseaudio.tzst .)
|
||||
mv pulseaudio.tzst app/src/main/assets/pulseaudio.tzst
|
||||
tar --use-compress-program=unzstd -tf app/src/main/assets/pulseaudio.tzst | grep -c '\.so$'
|
||||
mkdir -p app/build/prepared-assets
|
||||
mv pulseaudio.tzst app/build/prepared-assets/pulseaudio.tzst
|
||||
tar --use-compress-program=unzstd -tf app/build/prepared-assets/pulseaudio.tzst | grep -c '\.so$'
|
||||
rm -rf pa-bundle sink-out
|
||||
|
||||
- name: Cache proot
|
||||
id: proot
|
||||
uses: actions/cache@v4
|
||||
@@ -266,30 +394,34 @@ jobs:
|
||||
- name: Check the preloads
|
||||
run: |
|
||||
set -euo pipefail
|
||||
D=app/src/main/assets/linuxfs
|
||||
NEED=$(readelf -d "$D/libfakeinput.so" | sed -n 's/.*NEEDED.*\[\(.*\)\]/\1/p')
|
||||
for bad in libstdc++.so.6 libgcc_s.so.1; do
|
||||
if printf '%s\n' "$NEED" | grep -qx "$bad"; then
|
||||
echo "::error::libfakeinput.so links $bad - the C++ runtime must stay static"; exit 1
|
||||
fi
|
||||
done
|
||||
syms() { readelf -Ws "$1" | awk '$4 == "FUNC" && $5 == "GLOBAL" {sub(/@.*/, "", $8); print $8}'; }
|
||||
FAKE=$(syms "$D/libfakeinput.so")
|
||||
for sym in open openat ioctl read close poll ppoll select stat fstat access scandir; do
|
||||
grep -qx "$sym" <<<"$FAKE" || { echo "::error::libfakeinput.so does not export $sym"; exit 1; }
|
||||
done
|
||||
# The static C++ runtime stays private: every session process preloads this library,
|
||||
# and an exported personality routine takes over other programs' unwinding (shadPS4
|
||||
# aborted in pthread_exit).
|
||||
if grep -qxE '__gxx_personality_v0|__cxa_throw|_Unwind_Resume' <<<"$FAKE"; then
|
||||
echo "::error::libfakeinput.so exports the C++ runtime"; exit 1
|
||||
fi
|
||||
SESSION=$(syms "$D/libblsession.so")
|
||||
for sym in socket bind getsockname setsockopt statfs statvfs; do
|
||||
grep -qx "$sym" <<<"$SESSION" || { echo "::error::libblsession.so does not export $sym"; exit 1; }
|
||||
done
|
||||
test -f "$D/usr/local/bin/bannerlator-session"
|
||||
test -f "$D/usr/local/bin/bannerlator-proton-extra"
|
||||
bash tools/linuxfs/check-preloads.sh app/src/main/assets/linuxfs
|
||||
|
||||
- name: Rust toolchain for the store engines (aarch64-linux-android)
|
||||
id: rust
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rustup target add aarch64-linux-android
|
||||
echo "rustc=$(rustc -V | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
rustc -V; cargo -V
|
||||
|
||||
- name: Cache cargo registry and the store engines' target
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
~/.cargo/bin/cargo-ndk
|
||||
app/src/main/rust/stores/target
|
||||
key: stores-${{ steps.rust.outputs.rustc }}-${{ hashFiles('app/src/main/rust/stores/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
stores-${{ steps.rust.outputs.rustc }}-
|
||||
|
||||
- name: cargo-ndk
|
||||
run: |
|
||||
set -euo pipefail
|
||||
command -v cargo-ndk >/dev/null || cargo install cargo-ndk --locked
|
||||
cargo ndk --version
|
||||
|
||||
- name: Cache the CMake build tree
|
||||
uses: actions/cache@v4
|
||||
@@ -301,13 +433,50 @@ jobs:
|
||||
restore-keys: |
|
||||
cxx-${{ env.NDK_VERSION }}-
|
||||
|
||||
- name: Build GOG Galaxy companion
|
||||
if: steps.preloads.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
rustup toolchain install 1.90.0 --profile minimal
|
||||
rustup target add --toolchain 1.90.0 aarch64-unknown-linux-gnu
|
||||
RUSTUP_TOOLCHAIN=1.90.0 bash tools/comet/build.sh
|
||||
|
||||
- name: Build
|
||||
id: build
|
||||
env:
|
||||
DROIDDECK_BUILD_LABEL: ${{ steps.build-label.outputs.label }}
|
||||
SGDB_API_KEY: ${{ secrets.SGDB_API_KEY }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
python3 tools/quest/build-driver.py "$ANDROID_HOME/ndk/$NDK_VERSION"
|
||||
python3 tools/release/runtime_inputs.py record
|
||||
./gradlew assembleRelease --console=plain -PndkVersion="$NDK_VERSION" 2>&1 | tee /tmp/build.log
|
||||
python3 tools/release/check_session_assets.py app/build/outputs/apk/release/app-release.apk
|
||||
python3 tools/release/runtime_inputs.py apk app/build/outputs/apk/release/app-release.apk
|
||||
|
||||
- name: Check the store engines' library
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
|
||||
python3 tools/release/check_android_libraries.py app/build/outputs/apk/release/app-release.apk \
|
||||
--readelf "$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readelf"
|
||||
git diff --stat -- app/src/main/rust/stores/Cargo.lock
|
||||
git diff --exit-code -- app/src/main/rust/stores/Cargo.lock \
|
||||
|| { 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
|
||||
timeout-minutes: 10
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd app/src/main/rust/stores
|
||||
cargo test --locked 2>&1 | tail -60
|
||||
|
||||
- name: Upload the regenerated Cargo.lock
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: stores-cargo-lock
|
||||
path: app/src/main/rust/stores/Cargo.lock
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Steam import and game environment tests
|
||||
run: python3 -m unittest discover -s tools/tests -p 'test_*.py'
|
||||
@@ -315,27 +484,6 @@ jobs:
|
||||
- name: Unit tests
|
||||
run: ./gradlew testReleaseUnitTest --console=plain -PndkVersion="$NDK_VERSION"
|
||||
|
||||
- name: Check the native dependency closure
|
||||
run: |
|
||||
set -euo pipefail
|
||||
APK=$(find app/build/outputs/apk -name '*.apk' | head -1)
|
||||
WORK=$(mktemp -d)
|
||||
unzip -q "$APK" 'lib/arm64-v8a/*' -d "$WORK"
|
||||
cd "$WORK/lib/arm64-v8a"
|
||||
SYSTEM="libc.so libm.so libdl.so liblog.so libandroid.so libz.so libvulkan.so libstdc++.so
|
||||
libGLESv2.so libEGL.so libnativewindow.so libjnigraphics.so libaaudio.so
|
||||
libOpenSLES.so libmediandk.so libcamera2ndk.so libsync.so libneuralnetworks.so"
|
||||
fail=0
|
||||
for so in *.so; do
|
||||
for need in $(readelf -d "$so" | sed -n 's/.*NEEDED.*\[\(.*\)\]/\1/p'); do
|
||||
[ -f "$need" ] && continue
|
||||
case " $(echo $SYSTEM) " in *" $need "*) continue ;; esac
|
||||
echo "::error::$so needs $need, which is neither in the apk nor a platform library"
|
||||
fail=1
|
||||
done
|
||||
done
|
||||
exit $fail
|
||||
|
||||
- name: Sign with the testkey (v1 + v2 + v3)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -372,6 +520,7 @@ jobs:
|
||||
PR: ${{ github.event.pull_request.number }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
BUILD_OUTCOME: ${{ steps.build.outcome }}
|
||||
SAME_REPO: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
|
||||
run: |
|
||||
set -uo pipefail
|
||||
MARK="<!-- droiddeck-pr-build -->"
|
||||
@@ -385,7 +534,15 @@ jobs:
|
||||
success)
|
||||
echo "### Build passed"
|
||||
echo
|
||||
echo "The \`droiddeck-apk\` artifact is [on this run]($RUN_URL), signed with the public test key. A maintainer can run **PR Release Publish** from \`main\` to create an APK that installs over the release."
|
||||
echo "The \`droiddeck-apk\` artifact is [on this run]($RUN_URL), signed with the public test key."
|
||||
echo
|
||||
if [ "$SAME_REPO" = true ]; then
|
||||
echo "A release-signed tester build that installs over Stable or Preview is being published now and will appear in DroidDeck's **Test builds** list. Label the PR \`no-test-build\` to skip that."
|
||||
else
|
||||
echo "**Maintainer:** add the \`publish-test\` label to publish a release-signed tester build that installs over Stable or Preview and appears in DroidDeck's **Test builds** list. The label is removed automatically after publishing."
|
||||
fi
|
||||
echo
|
||||
echo "Fallback: [open **PR Release Publish**]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/workflows/pr-release-publish.yml) on \`main\` and enter PR \`$PR\`."
|
||||
;;
|
||||
failure)
|
||||
echo "### ERROR: Build failed"
|
||||
@@ -441,12 +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 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.
|
||||
announce:
|
||||
needs: [build, sign]
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
||||
@@ -473,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 }}
|
||||
@@ -513,6 +662,8 @@ jobs:
|
||||
notes="$(cat notes-head.md)
|
||||
|
||||
Signed build of $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$GITHUB_SHA - in main, not yet in a DroidDeck release; for testing.
|
||||
Commit: $GITHUB_SHA
|
||||
VersionCode: $(sed -n 's/^ *versionCode \([0-9][0-9]*\)$/\1/p' app/build.gradle)
|
||||
Each apk installs over a DroidDeck release and keeps its data. \`standard\` is the normal app; the others are the same app under another package name, installed beside it.
|
||||
|
||||
SHA-256:
|
||||
@@ -522,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"
|
||||
@@ -546,7 +694,7 @@ jobs:
|
||||
echo "apk=$GITHUB_SERVER_URL/$CI_REPO/releases/download/$tag/$apk" >> "$GITHUB_OUTPUT"
|
||||
echo "Published $url"
|
||||
|
||||
- name: Post to Discord
|
||||
- name: Post the Preview build to Discord
|
||||
env:
|
||||
WEBHOOK: ${{ secrets.DISCORD_BUILDS_WEBHOOK }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -560,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
|
||||
@@ -570,7 +716,13 @@ jobs:
|
||||
what="[\`${GITHUB_SHA::7}\`]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$GITHUB_SHA): $subject"
|
||||
fi
|
||||
get=$(printf 'Download: [standard APK](<%s>) · [all variants](<%s>)' "$APK" "$RELEASE")
|
||||
content=$(printf '**New signed build** `main-%s`\n%s\n%s' "${GITHUB_SHA::7}" "$what" "$get")
|
||||
content=$(printf '**New signed Preview build** `main-%s`\n%s\nIn the app: **Updates → Preview**\n%s' "${GITHUB_SHA::7}" "$what" "$get")
|
||||
jq -n --arg content "$content" '{content: $content, allowed_mentions: {parse: []}}' |
|
||||
curl -fsS -H 'Content-Type: application/json' -d @- "$WEBHOOK" -o /dev/null
|
||||
echo "Posted: $content"
|
||||
|
||||
catalog:
|
||||
needs: announce
|
||||
if: ${{ always() && github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && needs.announce.result != 'skipped' }}
|
||||
uses: ./.github/workflows/update-catalog.yml
|
||||
secrets: inherit
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Publish the CI Debian image
|
||||
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to publish to (a new -rN tag for a refresh; builds pin the one in tools/linuxfs/debian-image.env)'
|
||||
required: true
|
||||
default: 'ci-debian-bullseye-r2'
|
||||
replace:
|
||||
description: 'Overwrite the asset if the release already has one (changes its checksum: re-pin it in debian-image.env)'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Pull, save and publish
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ inputs.tag }}
|
||||
REPLACE: ${{ inputs.replace }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
image=public.ecr.aws/docker/library/debian:bullseye
|
||||
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
|
||||
fi
|
||||
for attempt in 1 2 3 4 5 6; do
|
||||
docker pull "$image" && break
|
||||
[ "$attempt" -lt 6 ] || { echo "::error::could not pull $image"; exit 1; }
|
||||
echo "pull attempt $attempt failed (registry busy?); retrying in $((attempt * 30)) s"
|
||||
sleep $((attempt * 30))
|
||||
done
|
||||
docker image inspect "$image" --format 'image id {{.Id}} created {{.Created}}'
|
||||
docker save "$image" | zstd -19 -T0 -o debian-bullseye.tar.zst
|
||||
sha256sum debian-bullseye.tar.zst | tee SHA256SUMS
|
||||
ls -l debian-bullseye.tar.zst
|
||||
if ! gh release view "$TAG" -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release create "$TAG" -R "$GITHUB_REPOSITORY" --title "CI Debian 11 image ($TAG)" --latest=false \
|
||||
--notes "The debian:bullseye image build.yml compiles the x86 preloads in (glibc 2.31), saved with docker save and compressed with zstd. Pulled from public.ecr.aws/docker/library/debian:bullseye on $(date -u +%Y-%m-%d); image id $(docker image inspect "$image" --format '{{.Id}}'). Pinned in tools/linuxfs/debian-image.env."
|
||||
fi
|
||||
gh release upload "$TAG" -R "$GITHUB_REPOSITORY" debian-bullseye.tar.zst SHA256SUMS --clobber
|
||||
{ echo "Published \`$TAG\`:"; echo; echo '```'; cat SHA256SUMS; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
|
||||
@@ -0,0 +1,77 @@
|
||||
name: PR Release Cleanup
|
||||
run-name: remove PR
|
||||
|
||||
|
||||
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ closed ]
|
||||
branches: [ main ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: pr-release-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: false
|
||||
queue: max
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check the current PR state
|
||||
id: state
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
state=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq .state)
|
||||
echo "closed=$([ "$state" = closed ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/create-github-app-token@v2
|
||||
if: ${{ steps.state.outputs.closed == 'true' }}
|
||||
id: ci-token
|
||||
with:
|
||||
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
|
||||
owner: Droid-Deck
|
||||
repositories: DroidDeck-CI
|
||||
- name: Delete the test build
|
||||
if: ${{ steps.state.outputs.closed == 'true' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.ci-token.outputs.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
found=0
|
||||
for tag in "pr-$PR_NUMBER" "pr-$PR_NUMBER.0"; do
|
||||
if gh release view "$tag" -R Droid-Deck/DroidDeck-CI >/dev/null 2>&1; then
|
||||
gh release delete "$tag" -R Droid-Deck/DroidDeck-CI --yes --cleanup-tag
|
||||
echo "Deleted $tag"
|
||||
found=1
|
||||
fi
|
||||
done
|
||||
[ "$found" = 1 ] || echo "No test build for PR #$PR_NUMBER"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ steps.state.outputs.closed == 'true' }}
|
||||
with:
|
||||
ref: main
|
||||
persist-credentials: false
|
||||
- name: Retire the Discord PR announcement
|
||||
if: ${{ steps.state.outputs.closed == 'true' }}
|
||||
env:
|
||||
WEBHOOK: ${{ secrets.DISCORD_PR_BUILDS_WEBHOOK }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: python3 tools/release/pr_discord.py retire
|
||||
|
||||
catalog:
|
||||
needs: cleanup
|
||||
if: ${{ always() }}
|
||||
uses: ./.github/workflows/update-catalog.yml
|
||||
secrets: inherit
|
||||
@@ -1,84 +1,148 @@
|
||||
name: PR Release Publish
|
||||
run-name: sign PR #${{ inputs.pr_number }} with the release key
|
||||
run-name: "sign PR #${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number || inputs.pr_number }} with the release key"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: Open pull request
|
||||
description: Open pull request number
|
||||
required: true
|
||||
type: number
|
||||
type: string
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
branches: [main]
|
||||
workflow_run:
|
||||
workflows: [Build APK]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: pr-release-${{ inputs.pr_number }}
|
||||
group: pr-release-${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number || inputs.pr_number }}
|
||||
cancel-in-progress: false
|
||||
queue: max
|
||||
|
||||
jobs:
|
||||
authorize:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
if: >-
|
||||
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') ||
|
||||
(github.event_name == 'pull_request_target' && github.event.label.name == 'publish-test') ||
|
||||
(github.event_name == 'workflow_run' && github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.head_repository.full_name == github.repository &&
|
||||
github.event.workflow_run.pull_requests[0] != null)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
outputs:
|
||||
pr_number: ${{ steps.gate.outputs.pr_number }}
|
||||
run_id: ${{ steps.gate.outputs.run_id }}
|
||||
head_sha: ${{ steps.gate.outputs.head_sha }}
|
||||
base_sha: ${{ steps.gate.outputs.base_sha }}
|
||||
version_code: ${{ steps.gate.outputs.version_code }}
|
||||
label: ${{ steps.gate.outputs.label }}
|
||||
skip: ${{ steps.gate.outputs.skip }}
|
||||
automatic: ${{ steps.gate.outputs.automatic }}
|
||||
variants: ${{ steps.variants.outputs.list }}
|
||||
steps:
|
||||
- name: Check the PR and build
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- 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"
|
||||
- name: Check the requester, PR, and build
|
||||
id: gate
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
REQUESTER: ${{ github.actor }}
|
||||
PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number || inputs.pr_number }}
|
||||
BUILT_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] || { echo "::error::Invalid PR number '$PR_NUMBER'"; exit 1; }
|
||||
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"
|
||||
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
|
||||
[ "$(jq -r .state <<<"$pr")" = open ] || skip "it is not open"
|
||||
[ "$(jq -r .draft <<<"$pr")" = false ] || skip "it is a draft"
|
||||
[ "$(jq -r .base.ref <<<"$pr")" = main ] || skip "it does not target main"
|
||||
[ "$(jq -r .head.repo.full_name <<<"$pr")" = "$GITHUB_REPOSITORY" ] || skip "it comes from a fork"
|
||||
jq -e 'any(.labels[]; .name == "no-test-build")' <<<"$pr" >/dev/null && skip "it is labelled no-test-build"
|
||||
[ "$(jq -r .head.sha <<<"$pr")" = "$BUILT_SHA" ] || skip "a newer push is building"
|
||||
fi
|
||||
if [ "$EVENT_NAME" = pull_request_target ]; then
|
||||
permission=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$REQUESTER/permission" --jq .permission)
|
||||
case "$permission" in
|
||||
admin|maintain|write) ;;
|
||||
*) echo "::error::@$REQUESTER does not have permission to publish release-signed test builds"; exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
|
||||
jq -e '.state == "open" and .draft == false and .base.ref == "main"' <<<"$pr" >/dev/null
|
||||
jq -e '.state == "open" and .draft == false and .base.ref == "main"' <<<"$pr" >/dev/null ||
|
||||
{ echo "::error::PR #$PR_NUMBER must be open, ready for review, and target main"; exit 1; }
|
||||
head=$(jq -r '.head.sha' <<<"$pr")
|
||||
base=$(jq -r '.base.sha' <<<"$pr")
|
||||
|
||||
run_id=$(gh run list -R "$GITHUB_REPOSITORY" --workflow build.yml \
|
||||
--event pull_request --commit "$head" \
|
||||
--json databaseId,headSha,status,conclusion,createdAt,attempt -L 100 |
|
||||
jq -r --arg head "$head" '[.[] | select(.headSha == $head)] |
|
||||
sort_by([.createdAt, .attempt]) | last |
|
||||
select(.status == "completed" and .conclusion == "success") | .databaseId // empty')
|
||||
[[ -n "$run_id" ]] || { echo '::error::The latest PR build is not green'; exit 1; }
|
||||
run_id=
|
||||
for attempt in $(seq 1 60); do
|
||||
latest=$(gh run list -R "$GITHUB_REPOSITORY" --workflow build.yml \
|
||||
--event pull_request --commit "$head" \
|
||||
--json databaseId,headSha,status,conclusion,createdAt,attempt -L 100 |
|
||||
jq -c --arg head "$head" '[.[] | select(.headSha == $head)] |
|
||||
sort_by([.createdAt, .attempt]) | last // empty')
|
||||
if [[ -n "$latest" && $(jq -r .status <<<"$latest") == completed ]]; then
|
||||
conclusion=$(jq -r .conclusion <<<"$latest")
|
||||
[[ "$conclusion" == success ]] || { echo "::error::The latest PR build is not green ($conclusion)"; exit 1; }
|
||||
run_id=$(jq -r .databaseId <<<"$latest")
|
||||
break
|
||||
fi
|
||||
status=not-started
|
||||
[[ -z "$latest" ]] || status=$(jq -r .status <<<"$latest")
|
||||
echo "Waiting for the PR build of ${head:0:7} to finish ($status)"
|
||||
sleep 30
|
||||
done
|
||||
[[ -n "$run_id" ]] || { echo '::error::The PR build did not finish within 30 minutes'; exit 1; }
|
||||
run=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id")
|
||||
jq -e --arg head "$head" --arg base "$base" --argjson number "$PR_NUMBER" '
|
||||
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
|
||||
any(.pull_requests[]?; .number == $number and .head.sha == $head and .base.sha == $base)
|
||||
' <<<"$run" >/dev/null
|
||||
.head_repository.full_name == $repo and .head_branch == $ref
|
||||
' <<<"$run" >/dev/null ||
|
||||
{ echo "::error::Build run $run_id is not a green build of PR #$PR_NUMBER at ${head:0:7}"; exit 1; }
|
||||
|
||||
sdk=${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}
|
||||
tools=$(find "$sdk/build-tools" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -1)
|
||||
stable_tag=$(gh api repos/The412Banner/DroidDeck/releases/latest --jq .tag_name)
|
||||
mkdir -p "$RUNNER_TEMP/stable" "$RUNNER_TEMP/pr"
|
||||
gh release download "$stable_tag" -R The412Banner/DroidDeck \
|
||||
-p "DroidDeck-$stable_tag.apk" -D "$RUNNER_TEMP/stable"
|
||||
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"
|
||||
gh run download "$run_id" -R "$GITHUB_REPOSITORY" -n droiddeck-apk -D "$RUNNER_TEMP/pr"
|
||||
mapfile -t apks < <(find "$RUNNER_TEMP/pr" -type f -name '*.apk')
|
||||
[[ ${#apks[@]} == 1 ]] || { echo '::error::Expected one PR APK'; exit 1; }
|
||||
package_line() {
|
||||
"$tools/aapt" dump badging "$1" | sed -n "s/^package: name='\\([^']*\\)' versionCode='\\([^']*\\)'.*/\\1 \\2/p"
|
||||
}
|
||||
stable=$(package_line "$RUNNER_TEMP/stable/DroidDeck-$stable_tag.apk")
|
||||
candidate=$(package_line "${apks[0]}")
|
||||
[[ "$stable" =~ ^com\.droiddeck\.launcher\ [0-9]+$ && "$candidate" =~ ^com\.droiddeck\.launcher\ [0-9]+$ &&
|
||||
${candidate##* } -ge ${stable##* } ]] || {
|
||||
echo "::error::PR package/versionCode '$candidate' cannot install over stable '$stable'"; exit 1;
|
||||
[[ "$candidate" =~ ^com\.droiddeck\.launcher\ [0-9]+$ && ${candidate##* } -ge $main_code ]] || {
|
||||
echo "::error::PR package/versionCode '$candidate' cannot install over main (versionCode $main_code)"; exit 1;
|
||||
}
|
||||
|
||||
current=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
|
||||
jq -e --arg head "$head" --arg base "$base" '
|
||||
.state == "open" and .draft == false and .base.ref == "main" and
|
||||
.head.sha == $head and .base.sha == $base
|
||||
' <<<"$current" >/dev/null
|
||||
' <<<"$current" >/dev/null ||
|
||||
{ echo "::error::PR #$PR_NUMBER moved on while its build was checked; publish again"; exit 1; }
|
||||
{
|
||||
echo "pr_number=$PR_NUMBER"
|
||||
echo "run_id=$run_id"
|
||||
echo "head_sha=$head"
|
||||
echo "base_sha=$base"
|
||||
echo "version_code=${candidate##* }"
|
||||
echo "label=PR.$PR_NUMBER-${head:0:7}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -87,11 +151,12 @@ jobs:
|
||||
uses: ./.github/workflows/sign.yml
|
||||
with:
|
||||
environment: signing-main
|
||||
variants: '["standard"]'
|
||||
variants: ${{ needs.authorize.outputs.variants }}
|
||||
run_id: ${{ needs.authorize.outputs.run_id }}
|
||||
label: ${{ needs.authorize.outputs.label }}
|
||||
artifact: droiddeck-pr-signed
|
||||
secrets: inherit
|
||||
if: ${{ needs.authorize.outputs.skip != 'true' }}
|
||||
|
||||
publish:
|
||||
needs: [authorize, sign]
|
||||
@@ -99,18 +164,22 @@ jobs:
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
persist-credentials: false
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: droiddeck-pr-signed-standard
|
||||
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 }}
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
|
||||
@@ -125,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:
|
||||
@@ -134,29 +202,124 @@ jobs:
|
||||
owner: Droid-Deck
|
||||
repositories: DroidDeck-CI
|
||||
- name: Publish to DroidDeck-CI
|
||||
id: publish
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.ci-token.outputs.token }}
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
|
||||
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
|
||||
LABEL: ${{ needs.authorize.outputs.label }}
|
||||
BASE_SHA: ${{ needs.authorize.outputs.base_sha }}
|
||||
VERSION_CODE: ${{ needs.authorize.outputs.version_code }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
repo=Droid-Deck/DroidDeck-CI
|
||||
tag="pr-$PR_NUMBER"
|
||||
apk="DroidDeck-$LABEL.apk"
|
||||
sha=$(sha256sum "signed/$apk" | cut -d ' ' -f 1)
|
||||
cat signed/SHA256SUMS-*.txt > signed/SHA256SUMS.txt
|
||||
rm signed/SHA256SUMS-*.txt
|
||||
notes="$(cat notes-head.md)
|
||||
|
||||
Source: https://github.com/$GITHUB_REPOSITORY/pull/$PR_NUMBER
|
||||
Commit: $HEAD_SHA
|
||||
APK SHA-256: $sha"
|
||||
Base: $BASE_SHA
|
||||
VersionCode: $VERSION_CODE
|
||||
Each apk installs over a DroidDeck release and keeps its data. \`standard\` is the normal app; the others are the same app under another package name, installed beside it.
|
||||
|
||||
SHA-256:
|
||||
\`\`\`
|
||||
$(cat signed/SHA256SUMS.txt)
|
||||
\`\`\`"
|
||||
first=false
|
||||
if ! gh release view "$tag" -R "$repo" >/dev/null 2>&1; then
|
||||
gh release create "$tag" -R "$repo" --target main --title "PR #$PR_NUMBER" --notes "$notes" --prerelease
|
||||
first=true
|
||||
fi
|
||||
gh release upload "$tag" "signed/$apk" -R "$repo" --clobber
|
||||
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
|
||||
keep=$(cd signed && ls | jq -R . | jq -sc .)
|
||||
gh api "repos/$repo/releases/tags/$tag" |
|
||||
jq -r --arg keep "$apk" '.assets[] | select(.name | endswith(".apk")) | select(.name != $keep) | .id' |
|
||||
jq -r --argjson keep "$keep" '.assets[] | select(.name as $n | $keep | index($n) | not) | .id' |
|
||||
while IFS= read -r id; do
|
||||
gh api --method DELETE "repos/$repo/releases/assets/$id" >/dev/null
|
||||
done
|
||||
|
||||
published=$(gh release view "$tag" -R "$repo" --json tagName,assets)
|
||||
jq -e --arg tag "$tag" '.tagName == $tag and (.assets | length) > 0' <<<"$published" >/dev/null
|
||||
release="$GITHUB_SERVER_URL/$repo/releases/tag/$tag"
|
||||
apk=$(cd signed && ls DroidDeck-*.apk | awk 'length(a) == 0 || length($0) < length(a) { a = $0 } END { print a }')
|
||||
echo "release=$release" >> "$GITHUB_OUTPUT"
|
||||
echo "apk=$GITHUB_SERVER_URL/$repo/releases/download/$tag/$apk" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Whether the PR's branch is in this repo
|
||||
id: same-repo
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
|
||||
run: |
|
||||
head_repo=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq .head.repo.full_name)
|
||||
echo "same=$([ "$head_repo" = "$GITHUB_REPOSITORY" ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clear the publish request label
|
||||
if: ${{ github.event_name == 'pull_request_target' && steps.publish.outcome == 'success' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
|
||||
run: |
|
||||
gh api --method DELETE "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/labels/publish-test"
|
||||
|
||||
- name: Report the signed build on the PR
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
|
||||
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
|
||||
RELEASE: ${{ steps.publish.outputs.release }}
|
||||
APK: ${{ steps.publish.outputs.apk }}
|
||||
SAME_REPO: ${{ steps.same-repo.outputs.same }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
short=${HEAD_SHA:0:7}
|
||||
mark='<!-- droiddeck-pr-release -->'
|
||||
if [ "$SAME_REPO" = true ]; then
|
||||
NEXT='Each green build of a new push is signed and published here on its own. Label the PR `no-test-build` to stop that.'
|
||||
else
|
||||
NEXT='Pushes to this PR do not re-sign automatically. After a new green build, a maintainer can add the `publish-test` label again.'
|
||||
fi
|
||||
body=$(cat <<EOF
|
||||
$mark
|
||||
## Signed test build
|
||||
|
||||
A release-signed build of this PR at \`$short\` is ready. It installs over Stable or Preview and keeps app data.
|
||||
|
||||
[Download the standard APK]($APK) · [All variants and checksums]($RELEASE)
|
||||
|
||||
It is also available in **DroidDeck → Updates → Test builds → PR #$PR_NUMBER**.
|
||||
|
||||
$NEXT
|
||||
EOF
|
||||
)
|
||||
comment=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --paginate --slurp | \
|
||||
jq "[.[][] | select(.user.login == \"github-actions[bot]\") | select(.body | startswith(\"$mark\"))] | first // {}")
|
||||
id=$(jq -r '.id // empty' <<<"$comment")
|
||||
if [ -n "$id" ]; then
|
||||
state=$(jq -r '.body | split("\n")[] | select(startswith("<!-- droiddeck-discord: ") and endswith(" -->"))' <<<"$comment")
|
||||
if [ -n "$state" ]; then body=$(printf '%s\n%s' "$body" "$state"); fi
|
||||
gh api -X PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$id" -f body="$body" >/dev/null
|
||||
else
|
||||
gh api -X POST "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" -f body="$body" >/dev/null
|
||||
fi
|
||||
|
||||
- name: Update the Discord PR announcement
|
||||
if: ${{ always() && steps.publish.outcome == 'success' }}
|
||||
env:
|
||||
WEBHOOK: ${{ secrets.DISCORD_PR_BUILDS_WEBHOOK }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
|
||||
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
|
||||
RELEASE: ${{ steps.publish.outputs.release }}
|
||||
APK: ${{ steps.publish.outputs.apk }}
|
||||
run: python3 tools/release/pr_discord.py publish
|
||||
|
||||
catalog:
|
||||
needs: publish
|
||||
if: ${{ always() && needs.publish.result != 'skipped' }}
|
||||
uses: ./.github/workflows/update-catalog.yml
|
||||
secrets: inherit
|
||||
@@ -62,3 +62,56 @@ jobs:
|
||||
label: ${{ inputs.version }}
|
||||
artifact: droiddeck-release
|
||||
secrets: inherit
|
||||
|
||||
publish:
|
||||
name: publish the release
|
||||
needs: [find, sign]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: droiddeck-release-*
|
||||
merge-multiple: true
|
||||
path: out
|
||||
|
||||
- name: Publish
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
BUILD_RUN: ${{ needs.find.outputs.run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
notes="docs/releases/$VERSION.md"
|
||||
[ -f "$notes" ] || { echo "::error::$notes is missing: add the release notes first"; exit 1; }
|
||||
if gh release view "$VERSION" -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
echo "::error::release $VERSION already exists"; exit 1
|
||||
fi
|
||||
title=$(sed -n '1s/^# *//p' "$notes")
|
||||
[ -n "$title" ] || title="DroidDeck $VERSION"
|
||||
raw="https://raw.githubusercontent.com/$GITHUB_REPOSITORY/$VERSION/docs/releases/media/"
|
||||
{
|
||||
sed '1{/^# /d;}' "$notes" | sed -E "s#(\]\(|src=\")media/#\1$raw#g"
|
||||
echo
|
||||
echo "---"
|
||||
echo
|
||||
echo "Built by [Build APK run $BUILD_RUN]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$BUILD_RUN) from \`$GITHUB_SHA\`, signed and published by [Release APKs run $GITHUB_RUN_ID]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID)."
|
||||
} > "$RUNNER_TEMP/body.md"
|
||||
( cd out && cat SHA256SUMS-*.txt | sort -k2 > SHA256SUMS.txt && rm SHA256SUMS-*.txt && cat SHA256SUMS.txt )
|
||||
gh release create "$VERSION" -R "$GITHUB_REPOSITORY" out/*.apk out/SHA256SUMS.txt \
|
||||
--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"
|
||||
|
||||
catalog:
|
||||
needs: publish
|
||||
uses: ./.github/workflows/update-catalog.yml
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
secrets: inherit
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Update app catalog
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published, edited, deleted, unpublished]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: update-app-catalog
|
||||
cancel-in-progress: false
|
||||
queue: max
|
||||
|
||||
jobs:
|
||||
catalog:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/create-github-app-token@v2
|
||||
id: ci-token
|
||||
with:
|
||||
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
|
||||
owner: Droid-Deck
|
||||
repositories: DroidDeck-CI
|
||||
|
||||
- name: Regenerate catalog.json
|
||||
env:
|
||||
SOURCE_GITHUB_TOKEN: ${{ github.token }}
|
||||
CI_GITHUB_TOKEN: ${{ steps.ci-token.outputs.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 tools/release/update_catalog.py
|
||||
+6
-6
@@ -8,18 +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
|
||||
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
|
||||
@@ -31,5 +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/
|
||||
@@ -7,22 +7,34 @@
|
||||
|
||||
DroidDeck brings the SteamOS experience to Android: Valve's Steam client in Big Picture on your Adreno handheld, with Windows games through Valve's ARM64 Proton.
|
||||
|
||||
<p align="center"><a href="https://discord.gg/JRGAvawjsm"><img src="https://img.shields.io/badge/Discord-Join%20the%20community-5865F2?logo=discord&logoColor=white" alt="Join the DroidDeck Discord"></a></p>
|
||||
|
||||
<p align="center"><img src="docs/releases/media/0.2.0/launch-into-steam.gif" width="80%" alt="Tapping DroidDeck on the Android home screen and landing in Steam Big Picture"></p>
|
||||
|
||||
> Note: DroidDeck does not have a stand-alone website. Do not click on any download links from websites claiming to be the DroidDeck team.
|
||||
|
||||
## Requirements and install
|
||||
|
||||
Use Android 9 or newer on a supported Adreno device (730 or newer, or 8xx). Mali, Xclipse, PowerVR, and Adreno 710 are unsupported. No root is required. Allow about 3 GB for the runtime and 1.1 GB more for the desktop and emulators. Install the APK from [Releases](https://github.com/The412Banner/DroidDeck/releases), install the Linux runtime, then press **Play** and sign in. Steam downloads on first launch. Install **Desktop & apps** to use the desktop and emulators. The **Store** installs Linux apps and games from Flathub (ARM64 builds) with Flatpak; see [docs/development/flatpak.md](docs/development/flatpak.md).
|
||||
Use Android 9 or newer on a supported Adreno device (730 or newer, or 8xx). Adreno 6xx is experimental: DirectX 11 uses DXVK 2 and may run, and DirectX 12 games can still crash. Mali, Xclipse, PowerVR, and Adreno 710 are unsupported. No root is required. Allow about 3 GB for the runtime and 1.1 GB more for the desktop and emulators. Install the APK from [Releases](https://github.com/Droid-Deck/DroidDeck/releases), install the Linux runtime, then press **Play** and sign in. Steam downloads on first launch. Install **Desktop & apps** to use the desktop and emulators. The **Store** installs Linux apps and games from Flathub (ARM64 builds) with Flatpak; with additional options to install Appimages and set up scripts. **Stores** (Setup › Stores › "Show Stores in the rail") signs in to GOG, Epic Games and Amazon Games, browses their catalogs and libraries, downloads games into the Games storage and adds each one to the Steam client as a non-Steam game, so it launches through Proton like everything else; the cog on its chip row sets which tab a store opens on and the download speed tier.
|
||||
|
||||
On Android 12+, if Steam exits without a log, turn off **Restrict child processes** in Developer options.
|
||||
On Android 14 and newer, before Steam launches, you must turn on **Disable child process restrictions** in Developer options. If this option is not available in developer settings (Android 12 and 13 devices), first launch of Steam will present a "Fix it for me" button, which will help automate the setup process.
|
||||
|
||||
## Community
|
||||
|
||||
Join the [DroidDeck Discord](https://discord.gg/JRGAvawjsm) for help, Preview builds, and device reports. Bug reports go in its **#bug-reports** forum; attach the zip from **Share logs** (Setup, or the session drawer) so the logs come with it.
|
||||
|
||||
## Build
|
||||
|
||||
Run `tools/build_local.sh` with Docker, Java 17, the Android SDK/NDK, and `zstd` installed. It builds the ARM64 audio sinks from PulseAudio 13.0 and packages them into the APK at `app/build/outputs/apk/release/app-release.apk`. Set `DROIDDECK_PA13_SOURCE_DIR` to an existing PulseAudio 13.0 source directory to skip downloading it. To install the APK on an attached device, run `tools/deploy_local.sh`.
|
||||
Run `tools/build_local.sh` with Docker, Java 17, the Android SDK/NDK, Rust through rustup, GitHub CLI, Python 3, and `zstd` installed. It prepares the same native libraries, pinned components and audio sinks as CI, then checks the APK's assets, JNI exports and dependencies. The APK is at `app/build/outputs/apk/release/app-release.apk`. Set `DROIDDECK_PA13_SOURCE_DIR` to an existing PulseAudio 13.0 source directory to skip downloading it. Set `DROIDDECK_SIGNING_ENV` to a signing environment file to install over a release build; relative keystore paths resolve beside that file. To install on an attached device, run `tools/deploy_local.sh`.
|
||||
|
||||
After preparation, ordinary `./gradlew :app:assembleRelease` builds can reuse the verified inputs. If native sources, component pins or staged files change, packaging stops with instructions to rerun the helper. Generated audio stays under `app/build/`; the tracked base bundle is never modified. `./gradlew -PskipRust=true :app:testDebugUnitTest` needs no native preparation. For an APK without store engines, use `DROIDDECK_SKIP_RUST=1 tools/build_local.sh`; it omits cached engine libraries too.
|
||||
|
||||
Run the helper and preload regression suite with `bash tools/test_local.sh`. On macOS it runs in Linux through Docker, matching the helpers' runtime and CI instead of compiling Linux preloads against macOS headers.
|
||||
|
||||
## Limits
|
||||
|
||||
Compatibility and performance vary by device; hardware validation is limited. Desktop compositing uses software rendering. Firefox sandboxing is reduced under proot. See the session logs in `Download/DroidDeck/` when diagnosing problems.
|
||||
Compatibility and performance vary by device; hardware validation is limited. Desktop compositing uses software rendering. Firefox sandboxing is reduced under proot. Logs are kept app-private (`files/logs/`, the last 30 sessions); **Share logs** packs the newest session, with the stores and tool logs, into one scrubbed zip.
|
||||
|
||||
## Credits and licence
|
||||
|
||||
GPL-3.0. Runtime, shim, input, and controller work build on WinNative and Bannerlator (maxjivi05). See [LICENSE](LICENSE). Steam and Proton belong to Valve Corporation; this project is not affiliated with Valve.
|
||||
GPL-3.0. Runtime, shim, input, and controller work build on WinNative and Bannerlator (maxjivi05). Epic Games and GOG support build on [Phobos’s](https://github.com/phobos665) adaptation of [Legendary](https://github.com/legendary-gl/legendary) and [gogdl](https://github.com/Heroic-Games-Launcher/heroic-gogdl), respectively. LSFG frame generation is from the work of Camille LaVey and the [Eden](https://eden-emu.dev) emulator project, following [lsfg-vk](https://github.com/PancakeTAS/lsfg-vk), ported to WinNative and DroidDeck by [@maxjivi05](https://github.com/maxjivi05); it needs your own copy of [Lossless Scaling](https://store.steampowered.com/app/993090/) and ships none of its shaders. x86 AppImages, and any whose own runtime cannot unpack them, are unpacked with [uruntime](https://github.com/VHSgunzo/uruntime) by VHSgunzo (MIT), shipped unmodified with its licence. See [LICENSE](LICENSE). Steam and Proton belong to Valve Corporation; this project is not affiliated with Valve.
|
||||
+97
-29
@@ -49,23 +49,50 @@ if (!(treeState in ['clean', 'dirty'])) {
|
||||
buildLabel = "${buildOrigin} ${treeState} · ${buildLabel}"
|
||||
def escapedBuildLabel = buildLabel.replace('\\', '\\\\').replace('"', '\\"')
|
||||
|
||||
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'
|
||||
def releaseSigner = rootProject.file('keystore/release-signer.sha256').text.trim()
|
||||
def commitTime = (gitValue(['show', '-s', '--format=%ct', 'HEAD']) ?: '0').with { it.isLong() ? it : '0' }
|
||||
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()] }
|
||||
def desktopRemovalSources = new groovy.json.JsonSlurper().parse(file('src/main/assets/desktop-removal/sources.json'))
|
||||
if (desktopRemovalSources.archives[desktopKde.DESKTOP_KDE_TAG] != desktopKde.DESKTOP_KDE_SHA256) {
|
||||
throw new GradleException('Desktop removal inventories do not match the pinned archive')
|
||||
}
|
||||
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)
|
||||
}
|
||||
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]}" }
|
||||
.join(';')
|
||||
|
||||
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 9
|
||||
versionName "0.2.0"
|
||||
versionCode 13
|
||||
versionName "0.4.0"
|
||||
buildConfigField "String", "BUILD_LABEL", "\"${escapedBuildLabel}\""
|
||||
buildConfigField "String", "SOURCE_COMMIT", "\"${sourceCommit}\""
|
||||
buildConfigField "int", "PR_NUMBER", "${prNumber}"
|
||||
buildConfigField "boolean", "CI_BUILD", "${ciBuild}"
|
||||
buildConfigField "String", "RELEASE_SIGNER", "\"${releaseSigner}\""
|
||||
buildConfigField "long", "COMMIT_TIME", "${commitTime}L * 1000L"
|
||||
buildConfigField "String", "VARIANT_SUFFIXES", "\"${variantSuffixes}\""
|
||||
buildConfigField "String", "SGDB_API_KEY", "\"${System.getenv("SGDB_API_KEY") ?: project.findProperty("sgdbApiKey") ?: ""}\""
|
||||
buildConfigField "String", "DESKTOP_KDE_TAG", "\"${desktopKde.DESKTOP_KDE_TAG}\""
|
||||
buildConfigField "String", "DESKTOP_KDE_URL", "\"https://github.com/${desktopKde.DESKTOP_KDE_REPO}/releases/download/${desktopKde.DESKTOP_KDE_TAG}/desktop-kde.tar.zst\""
|
||||
buildConfigField "String", "DESKTOP_KDE_SHA256", "\"${desktopKde.DESKTOP_KDE_SHA256}\""
|
||||
buildConfigField "long", "DESKTOP_KDE_SIZE", "${desktopKde.DESKTOP_KDE_SIZE}L"
|
||||
|
||||
ndk {
|
||||
abiFilters 'arm64-v8a'
|
||||
@@ -85,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"
|
||||
@@ -113,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']
|
||||
}
|
||||
}
|
||||
@@ -141,8 +155,8 @@ android {
|
||||
}
|
||||
|
||||
testOptions {
|
||||
// Plain JVM tests: a stray android.util.Log call returns a default instead of throwing.
|
||||
unitTests.returnDefaultValues = true
|
||||
unitTests.includeAndroidResources = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -154,27 +168,81 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
def stageSessionScripts = tasks.register('stageSessionScripts', Copy) {
|
||||
from(rootProject.file('tools/linuxfs/overlay')) {
|
||||
include 'usr/local/bin/droiddeck-*', 'usr/local/bin/steam-compatibility', 'usr/bin/**'
|
||||
}
|
||||
into(layout.projectDirectory.dir('src/main/assets/linuxfs'))
|
||||
}
|
||||
tasks.named('preBuild').configure { dependsOn(stageSessionScripts) }
|
||||
|
||||
def runtimeAssetsDir = layout.buildDirectory.dir('runtimeAssets')
|
||||
def stageRuntimeAssets = tasks.register('stageRuntimeAssets', Sync) {
|
||||
dependsOn(stageSessionScripts)
|
||||
from(layout.projectDirectory.dir('src/main/assets')) { exclude 'pulseaudio.tzst' }
|
||||
from(layout.buildDirectory.dir('prepared-assets'))
|
||||
into(runtimeAssetsDir)
|
||||
}
|
||||
android.sourceSets.main.assets.setSrcDirs([runtimeAssetsDir])
|
||||
tasks.named('preBuild').configure { dependsOn(stageRuntimeAssets) }
|
||||
|
||||
def verifyRuntimeInputs = tasks.register('verifyRuntimeInputs', Exec) {
|
||||
mustRunAfter(stageSessionScripts, stageRuntimeAssets)
|
||||
workingDir rootProject.projectDir
|
||||
commandLine 'python3', 'tools/release/runtime_inputs.py', 'verify'
|
||||
}
|
||||
tasks.configureEach { task ->
|
||||
if (task.name in ['packageDebug', 'packageRelease']) task.dependsOn(verifyRuntimeInputs)
|
||||
}
|
||||
|
||||
def rustCrateDir = layout.projectDirectory.dir('src/main/rust/stores')
|
||||
def rustJniLibsDir = layout.buildDirectory.dir('rustJniLibs')
|
||||
def skipRust = (project.findProperty('skipRust') ?: 'false').toString().toBoolean()
|
||||
def buildRustStores = tasks.register('buildRustStores', Exec) {
|
||||
description = 'Cross-compiles the store download engines (libdroiddeckstores.so) with cargo-ndk.'
|
||||
onlyIf { !skipRust }
|
||||
inputs.dir(rustCrateDir.dir('src'))
|
||||
inputs.files(rustCrateDir.file('Cargo.toml'), rustCrateDir.file('Cargo.lock'))
|
||||
inputs.property('ndkVersion', android.ndkVersion)
|
||||
if (!skipRust) inputs.property('rustToolchain', providers.exec {
|
||||
environment 'PATH', "${System.getProperty('user.home')}/.cargo/bin${File.pathSeparator}${System.getenv('PATH')}"
|
||||
commandLine 'rustup', 'run', 'stable', 'rustc', '-Vv'
|
||||
}.standardOutput.asText)
|
||||
outputs.dir(rustJniLibsDir)
|
||||
workingDir rustCrateDir
|
||||
def cargoBin = "${System.getProperty('user.home')}/.cargo/bin"
|
||||
environment 'PATH', "${cargoBin}${File.pathSeparator}${System.getenv('PATH')}"
|
||||
environment 'ANDROID_NDK_HOME', android.ndkDirectory.absolutePath
|
||||
environment 'CARGO_TERM_COLOR', 'never'
|
||||
if (!skipRust) environment 'RUSTC', providers.exec {
|
||||
commandLine 'rustup', 'which', '--toolchain', 'stable', 'rustc'
|
||||
}.standardOutput.asText.get().trim()
|
||||
commandLine 'rustup', 'run', 'stable', 'cargo', 'ndk', '--target', 'arm64-v8a', '--platform', '26',
|
||||
'--output-dir', rustJniLibsDir.get().asFile.absolutePath,
|
||||
'build', '--release', '--locked'
|
||||
}
|
||||
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'
|
||||
// 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 'org.tukaani:xz:1.10'
|
||||
implementation platform('androidx.compose:compose-bom:2024.02.00')
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
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'
|
||||
testRuntimeOnly 'com.github.luben:zstd-jni:1.5.2-3@jar'
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application>
|
||||
<!-- Debug builds only. Android grants DUMP to adb shell; release APKs have no bridge. -->
|
||||
<activity
|
||||
android:name=".agent.AgentStartActivity"
|
||||
android:exported="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:noHistory="true"
|
||||
android:permission="android.permission.DUMP"
|
||||
android:theme="@style/SessionTheme" />
|
||||
<provider
|
||||
android:name=".agent.AgentBridgeProvider"
|
||||
android:authorities="${applicationId}.agent"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.DUMP"
|
||||
android:grantUriPermissions="false" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,166 +0,0 @@
|
||||
package com.droiddeck.launcher.agent
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.os.Process
|
||||
import com.droiddeck.launcher.BuildConfig
|
||||
import com.droiddeck.launcher.runtime.LinuxRuntime
|
||||
import com.droiddeck.launcher.runtime.LinuxRuntimeInstaller
|
||||
import com.droiddeck.launcher.session.SessionArtifacts
|
||||
import com.droiddeck.launcher.session.SessionEvents
|
||||
import com.droiddeck.launcher.session.SessionPhase
|
||||
import com.droiddeck.launcher.session.SessionPaths
|
||||
import com.droiddeck.launcher.session.SessionService
|
||||
import com.droiddeck.launcher.session.SessionState
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/** A shell-only control surface, installed only by debug builds. */
|
||||
class AgentBridgeProvider : ContentProvider() {
|
||||
override fun onCreate(): Boolean {
|
||||
val appContext = context?.applicationContext ?: return false
|
||||
Thread({
|
||||
try {
|
||||
SessionArtifacts.finishAbandoned(appContext)
|
||||
} finally {
|
||||
recoveryComplete.countDown()
|
||||
}
|
||||
}, "agent-recover-artifacts").start()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun call(method: String, arg: String?, extras: Bundle?): Bundle {
|
||||
val context = requireNotNull(context)
|
||||
if (Binder.getCallingUid() != Process.myUid() &&
|
||||
context.checkCallingPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
throw SecurityException("android.permission.DUMP is required")
|
||||
}
|
||||
|
||||
val response = try {
|
||||
when (method) {
|
||||
"state" -> state(context).put("ok", true)
|
||||
"start" -> error("USE_DROIDDECKCTL", "Start sessions with tools/droiddeckctl so Android launches a visible Activity")
|
||||
"stop" -> stop(context)
|
||||
"resume" -> resume(context)
|
||||
else -> error("UNKNOWN_COMMAND", "Unknown agent command '$method'")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
error("COMMAND_FAILED", e.message ?: e.javaClass.simpleName)
|
||||
}
|
||||
return Bundle().apply { putString(RESULT_JSON, response.toString()) }
|
||||
}
|
||||
|
||||
private fun state(context: Context): JSONObject {
|
||||
val runtimeVersion = LinuxRuntimeInstaller.installedVersion(context)
|
||||
val dir = SessionState.logDirectory ?: SessionState.logFile?.parentFile ?: latestSessionDirectory()
|
||||
val session = JSONObject()
|
||||
.put("id", SessionState.sessionId ?: dir?.name ?: JSONObject.NULL)
|
||||
.put("phase", SessionState.phase.name)
|
||||
.put("running", SessionState.running)
|
||||
.put("mode", SessionState.mode)
|
||||
.put("program", SessionState.program ?: JSONObject.NULL)
|
||||
.put("steamUi", SessionState.steamUi ?: JSONObject.NULL)
|
||||
.put("steamUrl", SessionState.steamUrl ?: JSONObject.NULL)
|
||||
.put("suspended", SessionState.suspended)
|
||||
.put("firstFrame", SessionState.firstFrameSeen)
|
||||
.put("output", JSONArray().put(SessionState.outputSize.first).put(SessionState.outputSize.second))
|
||||
.put("refreshHz", SessionState.refreshHz.toDouble())
|
||||
.put("lastTransitionAt", SessionState.lastTransitionAt)
|
||||
.put("guestPid", SessionState.guestPid.takeIf { it > 1 } ?: JSONObject.NULL)
|
||||
.put("installing", SessionState.installing ?: JSONObject.NULL)
|
||||
.put("logDir", dir?.absolutePath ?: JSONObject.NULL)
|
||||
.put("eventsFile", dir?.let { File(it, "events.jsonl").absolutePath } ?: JSONObject.NULL)
|
||||
.put("artifactsAvailable", dir?.isDirectory ?: false)
|
||||
.put("artifactsComplete", dir?.let { File(it, ".complete").isFile } ?: false)
|
||||
|
||||
val failure = if (SessionState.failureCode == null) JSONObject.NULL else JSONObject()
|
||||
.put("code", SessionState.failureCode)
|
||||
.put("message", SessionState.failureMessage ?: JSONObject.NULL)
|
||||
.put("status", SessionState.failureStatus ?: JSONObject.NULL)
|
||||
session.put("failure", failure)
|
||||
|
||||
return JSONObject()
|
||||
.put("schema", 1)
|
||||
.put("build", BuildConfig.BUILD_LABEL)
|
||||
.put("appVersion", context.packageManager.getPackageInfo(context.packageName, 0).versionName)
|
||||
.put("runtime", JSONObject()
|
||||
.put("installed", LinuxRuntime.isInstalled(context))
|
||||
.put("version", runtimeVersion ?: JSONObject.NULL))
|
||||
.put("session", session)
|
||||
}
|
||||
|
||||
private fun latestSessionDirectory(): File? {
|
||||
val parent = LinuxRuntime.debugLogDir()
|
||||
return parent.listFiles { file -> file.isDirectory && file.name.startsWith("session-") }
|
||||
?.maxWithOrNull(compareBy<File> { it.lastModified() }.thenBy { it.name })
|
||||
}
|
||||
|
||||
private fun stop(context: Context): JSONObject {
|
||||
if (SessionState.running) {
|
||||
SessionService.stop(context)
|
||||
return JSONObject().put("ok", true).put("command", "stop")
|
||||
}
|
||||
if (SessionState.phase == SessionPhase.STOPPING) {
|
||||
return JSONObject().put("ok", true).put("command", "stop")
|
||||
}
|
||||
if (SessionState.phase in STARTING_PHASES) {
|
||||
SessionState.stopRequested = true
|
||||
SessionEvents.transition(SessionPhase.STOPPING, "session.stop_requested")
|
||||
return JSONObject().put("ok", true).put("command", "stop")
|
||||
}
|
||||
return error("NO_ACTIVE_SESSION", "There is no session to stop")
|
||||
}
|
||||
|
||||
private fun resume(context: Context): JSONObject {
|
||||
if (!SessionState.running || !SessionState.suspended) {
|
||||
return error("SESSION_NOT_SUSPENDED", "There is no suspended session to resume")
|
||||
}
|
||||
SessionService.resume(context)
|
||||
return JSONObject().put("ok", true).put("command", "resume")
|
||||
}
|
||||
|
||||
private fun error(code: String, message: String) = JSONObject()
|
||||
.put("ok", false)
|
||||
.put("error", JSONObject().put("code", code).put("message", message))
|
||||
|
||||
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? = null
|
||||
override fun getType(uri: Uri): String? = null
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
|
||||
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int = 0
|
||||
|
||||
companion object {
|
||||
const val RESULT_JSON = "json"
|
||||
private val recoveryComplete = CountDownLatch(1)
|
||||
|
||||
fun awaitRecovery() {
|
||||
try {
|
||||
if (!recoveryComplete.await(90, TimeUnit.SECONDS)) {
|
||||
throw IllegalStateException("Session artifact recovery did not finish")
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
throw IllegalStateException("Interrupted while recovering session artifacts", e)
|
||||
}
|
||||
}
|
||||
|
||||
private val STARTING_PHASES = setOf(
|
||||
SessionPhase.PREPARING,
|
||||
SessionPhase.INSTALLING_RUNTIME,
|
||||
SessionPhase.STARTING_COMPOSITOR,
|
||||
SessionPhase.STARTING_GUEST,
|
||||
SessionPhase.STARTING_STEAM,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4,30 +4,28 @@
|
||||
<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.MANAGE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<!-- 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.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<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" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
|
||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
|
||||
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
@@ -38,11 +36,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
|
||||
@@ -54,12 +47,22 @@
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="droiddeck" android:host="game" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.CREATE_SHORTCUT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.droiddeck.launcher.LAUNCH_GAME" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</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"
|
||||
@@ -76,16 +79,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"
|
||||
@@ -106,34 +100,45 @@
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".stores.StoreLoginActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".agent.AgentStartActivity"
|
||||
android:exported="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:noHistory="true"
|
||||
android:permission="android.permission.DUMP"
|
||||
android:theme="@style/SessionTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".SessionActivity"
|
||||
android:exported="false"
|
||||
android:launchMode="singleTop"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/SessionTheme"
|
||||
android:preferMinimalPostProcessing="true"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
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" />
|
||||
<!-- Hands a zipped session folder to the share sheet; nothing else is exposed. -->
|
||||
<service
|
||||
android:name=".stores.download.StoreDownloadService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.logs"
|
||||
@@ -143,8 +148,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"
|
||||
@@ -155,5 +158,11 @@
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
<provider
|
||||
android:name=".agent.AgentBridgeProvider"
|
||||
android:authorities="${applicationId}.agent"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.DUMP"
|
||||
android:grantUriPermissions="false" />
|
||||
</application>
|
||||
</manifest>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"archives": {
|
||||
"lxqt-r1": "c2bb860b3b1688287451e2495be5db313ec07f8c8af501dfac2768a9f417bc86",
|
||||
"desktop-kde-r1": "55747e75d0ec055ab7ea1e92a757f3be227cc3205bb9101f5b53cadef6318579",
|
||||
"desktop-kde-r2": "9ba832677b498654136c69d8c8330434d17e4d53cd099acac9bde3ba429b35bf",
|
||||
"emulators-r1": "a1f4806a9663d28f424b4cab1277e0ede80a92e69de5bc063cdc8b45af220164"
|
||||
},
|
||||
"desktopApplications": {
|
||||
"ark-26.08.1-1-aarch64.pkg.tar.xz": "93e2c2b75ff20cd29b4f312ed1979ef6f27255b93232b93bfcdb31239903c1d4",
|
||||
"breeze-6.7.5-1-aarch64.pkg.tar.xz": "1d7f4eb9d8d234caaea23621909f5233d19462c2f058afeeb54bed7b81bd03bd",
|
||||
"breeze-gtk-6.7.5-1-any.pkg.tar.xz": "a0f59bc4fc426879c8f8a97f6dcc707fb7d1b52913da5eb7e9db83777b6957b7",
|
||||
"breeze-icons-6.30.0-1-aarch64.pkg.tar.xz": "4bae4689032501156687a227024e56554820747644953c67fc8d2e2b6b0b045a",
|
||||
"dolphin-26.08.1-1-aarch64.pkg.tar.xz": "fef738ab8132d3eba1156b98c116699ce0a5efcb4cb462efbacdcf4c47f79a23",
|
||||
"firefox-157.0.1-1-aarch64.pkg.tar.xz": "d142fdb8a339a65eca7cea6a2bba5b29202386166abd9d59851d6757c9eb2163",
|
||||
"gwenview-26.08.1-1-aarch64.pkg.tar.xz": "a9dc7d5ab73fa677905d5a57248b1b9b404b6b16e862958cc7443d387f684a47",
|
||||
"kate-26.08.1-1-aarch64.pkg.tar.xz": "acf50710042746f243a3b219a1ae0f788e338f3dd9d75093e8bb671a618b83b6",
|
||||
"kde-cli-tools-6.7.5-1-aarch64.pkg.tar.xz": "af466398a912a994a97fadfe46aff401d88a90d7fdce7e843dc4218098fa38ac",
|
||||
"kde-gtk-config-6.7.5-1-aarch64.pkg.tar.xz": "ba6cb4cb01dbb3f2c247f2f6973ede3b64c0849f89340217b51dfbf11cb8cb5c",
|
||||
"kinfocenter-6.7.5-1-aarch64.pkg.tar.xz": "f3d5416db96b50f9a5b8a284d82ae21d834a8cce646010abdd7087491c399b0b",
|
||||
"konsole-26.08.1-1-aarch64.pkg.tar.xz": "1ecf87c0d11d620ebe4aeafb02788e0d5f4fe9019ad9c5d9f1011c43237dd5ec",
|
||||
"kscreen-6.7.5-1-aarch64.pkg.tar.xz": "8fbaa6fedf93edeba021a5416b551663654919b27c2ee237d696ec0b0ce7d5b7",
|
||||
"kwin-6.7.5-1-aarch64.pkg.tar.xz": "a7535710c74b90359f80306134b2b7581132cbac508dc84b7e07ef275cc8c3fe",
|
||||
"mpv-1:0.41.0-6-aarch64.pkg.tar.xz": "6c89d00736820210a3c7f32af4cf95d155191d463f9df8bd22137c45f30a554a",
|
||||
"plasma-desktop-6.7.5-1-aarch64.pkg.tar.xz": "b266dd42cea965b5ab1cc1ae600877e9d3bc83ec642c61fe1669c4457e641a55",
|
||||
"plasma-integration-6.7.5-1-aarch64.pkg.tar.xz": "daa96da14d9a4d12c0d9c0341e1be6836a9a437c469d61da5e9844d51e2606cf",
|
||||
"plasma-pa-6.7.5-1-aarch64.pkg.tar.xz": "02dbefe9d3e0069b2bb37cac2a6be18b8f0a1887c87704a0716e7a9268eadcd8",
|
||||
"plasma-workspace-6.7.5-1-aarch64.pkg.tar.xz": "3a1455857afaa9ccf342ab6167f0f15794ee9d248b0925398ea6f62f12730a86",
|
||||
"spectacle-1:6.7.5-1-aarch64.pkg.tar.xz": "0c9837412ea6119edef18bcb0a8fceb74634cb35be0ca78b3e1dd1ff2443866e",
|
||||
"systemsettings-6.7.5-1-aarch64.pkg.tar.xz": "b02b6a726857d5125c92d1ec95379216c7a3bc1f187a3b46e88c0d056b29ce70",
|
||||
"xdg-desktop-portal-kde-6.7.5-1-aarch64.pkg.tar.xz": "2c57d17d34d577023eb9cd7b0fca1ba9163549afe60450f6271aa148af099ef2"
|
||||
},
|
||||
"legacyApplications": {
|
||||
"featherpad-1.6.3-1-aarch64.pkg.tar.xz": "fcac55215815c0b712453f9bcc09abfe4b518e36025a111a7a7598ef04913f3d",
|
||||
"labwc-0.20.2-1-aarch64.pkg.tar.xz": "bcd4cc3945165b0fdd33c0117cd7b9dfd05cf8c327326c37f3c3a27ef23ee5ca",
|
||||
"lximage-qt-2.4.0-1-aarch64.pkg.tar.xz": "5d6bdd5472ea6bcb4b993b36df3ab7010bb2e62b36a78a649708fc9a52cb0928",
|
||||
"lxqt-config-2.4.0-1-aarch64.pkg.tar.xz": "4b601d798fc7689ee76787dbef8c8958c02429ee60248cd99d950c919dbefba5",
|
||||
"lxqt-panel-2.4.1-1-aarch64.pkg.tar.xz": "a141e0f8049e73f859d843510c6f45d6ed9087c990bcebf44f5f2430621024c4",
|
||||
"lxqt-qtplugin-2.4.0-3-aarch64.pkg.tar.xz": "61c8f828be73111ee0bd5e3163b0d64c2098dd0189710a190f42be11137695cf",
|
||||
"lxqt-runner-2.4.0-1-aarch64.pkg.tar.xz": "5afa74652a37366ea4e7774cf33379977582fbdca599cef1005ebf637cbcae4b",
|
||||
"lxqt-session-2.4.0-2-aarch64.pkg.tar.xz": "e9c7c8a0011859029622ad202e0d3826e4e5bc8d64659031f6efa211456ff6eb",
|
||||
"lxqt-themes-2.4.0-1-any.pkg.tar.xz": "7ea16c62a1fa70cdac13cb3f8fdcdaad299ae1f5bd514acfda671484755787bf",
|
||||
"lxqt-wayland-session-0.4.1-1-any.pkg.tar.xz": "cd8dc11f5035469d23b6bbd937a90593402428c43de37517412aa43fe1fc6b10",
|
||||
"papirus-icon-theme-20260801-1-any.pkg.tar.xz": "4aae3f47b800a60d6fd360e55adc581408ceaafc481c5a420a681741a3966510",
|
||||
"pcmanfm-qt-2.4.1-1-aarch64.pkg.tar.xz": "7ec1a6b39dd0e4055b39f0e2c0549ea263d83004e1b821fb9e42f1d7f5fff0f9",
|
||||
"qterminal-2.4.0-1-aarch64.pkg.tar.xz": "aa4adc9c9d47641b9452ec8d879038a9cb4762c3b6b5299045fac968fd0ed22d",
|
||||
"swaybg-1.2.2-1-aarch64.pkg.tar.xz": "a26caa16ca7d581b5ca05758677058b4b60af10dc8d83e447ad6824b762b48b8",
|
||||
"wlr-randr-0.5.0-1-aarch64.pkg.tar.xz": "58c3651c6a15dc8f95a310c7e225f759aa766be2fb3c0981a9a8b567854675b3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
desktop-kde-r1
|
||||
desktop-kde-r2
|
||||
@@ -1,115 +0,0 @@
|
||||
# DirectAudio for the Linux Steam client (relay build)
|
||||
|
||||
This is the driver for games that run under **Valve's ARM64 Proton inside a Linux
|
||||
runtime on Android** - Bannerlator's linuxfs session with the native Steam client -
|
||||
where the game process is a glibc process and cannot touch Android's audio
|
||||
libraries itself.
|
||||
|
||||
Two pieces, both required:
|
||||
|
||||
| piece | what it is | where it runs |
|
||||
|---|---|---|
|
||||
| `aarch64-unix/winedirectaudio.so` + the two PE shells | the Wine `mmdevapi` driver, glibc build, **no AAudio inside** | in the game, inside the Linux rootfs |
|
||||
| `directaudio-relay` | a small bionic program that owns the AAudio output and microphone streams | on the Android side, under the app's uid |
|
||||
|
||||
They meet over a unix socket. The driver connects, sends its launch config, and
|
||||
gets back two shared-memory rings: one it fills with mixed game audio, one the
|
||||
helper fills with microphone audio. The helper plays and records; the driver
|
||||
keeps doing everything else it always did.
|
||||
|
||||
## Which Proton
|
||||
|
||||
Built inside `ValveSoftware/wine` at `proton_11.0`. The private `mmdevapi`
|
||||
interface it implements is byte-identical across **Proton 11.0 (ARM64)**,
|
||||
**Proton Experimental (ARM64)** and our own Wine-11 layers (checked 2026-09-19),
|
||||
so this one build serves any of them. A future Proton on a Wine 12 base will
|
||||
need a rebuild - the interface is not versioned, and a mismatch is silence, not
|
||||
an error.
|
||||
|
||||
## Installing the driver next to Proton (not into it)
|
||||
|
||||
Steam verifies and updates its Proton depots, so files added under
|
||||
`steamapps/common/Proton .../files/lib/wine/` will not survive. Put the driver
|
||||
in a directory of its own and point Wine at it:
|
||||
|
||||
```
|
||||
<somewhere>/directaudio/lib/wine/aarch64-unix/winedirectaudio.so
|
||||
<somewhere>/directaudio/lib/wine/aarch64-windows/winedirectaudio.drv
|
||||
<somewhere>/directaudio/lib/wine/i386-windows/winedirectaudio.drv
|
||||
```
|
||||
|
||||
and set, for the game process:
|
||||
|
||||
```
|
||||
WINEDLLPATH=<somewhere>/directaudio/lib/wine
|
||||
```
|
||||
|
||||
Wine searches `WINEDLLPATH` entries with the per-arch subdirectories appended,
|
||||
so both the PE shell and the unixlib are found there. In Bannerlator's runtime
|
||||
the natural place to export it is the `bannerlator-proton` compatibility-tool
|
||||
wrapper, which every Proton launch goes through.
|
||||
|
||||
## Selecting the driver
|
||||
|
||||
`mmdevapi` only tries the drivers named in the registry (or its built-in
|
||||
`pulse,alsa,oss,coreaudio` list, which does not know about this one). Set, in the
|
||||
game's prefix:
|
||||
|
||||
```
|
||||
wine reg add "HKCU\Software\Wine\Drivers" /v Audio /d directaudio /f
|
||||
```
|
||||
|
||||
Under Proton the prefix is `compatdata/<appid>/pfx`; run the command with that
|
||||
prefix as `WINEPREFIX` using Proton's own `bin-arm64/wine`, or write the key from
|
||||
the wrapper before it hands off to Proton.
|
||||
|
||||
## Running the helper
|
||||
|
||||
```
|
||||
directaudio-relay --socket <path> [--log]
|
||||
```
|
||||
|
||||
- Run it **under the app's uid**, the way the app already runs its PulseAudio
|
||||
daemon. Android checks `RECORD_AUDIO` against the calling uid, so a helper
|
||||
started any other way can play but cannot record.
|
||||
- Start it **before** the game: the driver asks "is a helper there?" once, when
|
||||
Wine picks an audio driver, and falls back to unavailable if not.
|
||||
- The socket path must be reachable from inside the Linux rootfs. Bannerlator
|
||||
binds the app's files directory and its runtime directory into the session at
|
||||
their own paths, so any socket under either works unchanged on both sides.
|
||||
- The helper serves any number of game processes; each gets its own streams.
|
||||
It exits on `SIGTERM`; a game exiting tears down that game's streams only.
|
||||
|
||||
## Telling the driver where the helper is
|
||||
|
||||
```
|
||||
BANNER_AUDIO_DIRECT_RELAY=<the same socket path>
|
||||
```
|
||||
|
||||
Unset, the driver looks for `$XDG_RUNTIME_DIR/directaudio-relay`.
|
||||
|
||||
## Everything else is unchanged
|
||||
|
||||
All `BANNER_AUDIO_DIRECT_*` knobs mean what they mean in the in-process build:
|
||||
`_PERF`, `_ADAPTIVE`, `_DECAY`, `_MS`, `_MAXMS`, `_BF`, `_MBF`, `_EXCLUSIVE`,
|
||||
`_WATCHDOG`, `_STALL_MS`, `_DECAY_*`, `_LOG`, `_PERIOD_MS`, `_MINPERIOD_MS`, and
|
||||
the live-config mailbox `_RUNTIME` (the helper watches the file; it must be a
|
||||
path the helper can read on the Android side). **`BANNER_AUDIO_DIRECT_MIC=1`**
|
||||
exposes the capture endpoint exactly as before: the helper opens the input
|
||||
stream (`VOICE_COMMUNICATION` preset) when the game connects and makes it hot on
|
||||
the game's first capture start.
|
||||
|
||||
One thing is different: there is one extra hand-off, the ring. The helper's
|
||||
callback pulls a burst from it, so the driver keeps two bursts queued (about 8 ms
|
||||
on a 192-frame device) on top of the AAudio buffer the presets control, and the
|
||||
helper raises that if a callback ever finds the ring short. `get_latency` reports
|
||||
the sum, so a game that sizes its own buffers from it still sees the truth.
|
||||
|
||||
## Proving it is running
|
||||
|
||||
- Helper side: `logcat -s DA-Relay:I` shows `hello from <exe>`, then `open: buffer
|
||||
... ring target ...`, then `capture open` / `capture start` for the mic.
|
||||
- Game side: the session log carries `DirectAudio: relay: connected - ...`.
|
||||
- The game process's `/proc/<pid>/maps` shows `winedirectaudio.so` and **no**
|
||||
`libaaudio.so` (it is in the helper's maps instead). The AudioFlinger track is
|
||||
owned by the helper's pid.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +0,0 @@
|
||||
directaudio linux-wine11 relay build
|
||||
source 3548a4a730e8d01cb0cb0268d51a2fb8f23dcd33
|
||||
wine ValveSoftware/wine@proton_11.0
|
||||
unixlib.h sha256 fdbed263338d0719c4d12e02b045c23721de33219ec8878e4c2d7ebc9efcbe09
|
||||
@@ -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,6 +33,7 @@ add_subdirectory(waylandcomp)
|
||||
add_library(sdnative SHARED sdnative/sdnative.c)
|
||||
target_link_libraries(sdnative log)
|
||||
|
||||
# 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(deviceinfo SHARED deviceinfo/vkinfo.c)
|
||||
target_link_libraries(deviceinfo dl)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
#include <dlfcn.h>
|
||||
#include <jni.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#define VK_NO_PROTOTYPES
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#define LINE(...) do { if (used < sizeof(out)) used += snprintf(out + used, sizeof(out) - used, __VA_ARGS__); } while (0)
|
||||
|
||||
static const char *const EXTENSIONS[] = {
|
||||
"VK_EXT_robustness2", "VK_EXT_transform_feedback", "VK_EXT_custom_border_color",
|
||||
"VK_KHR_dynamic_rendering", "VK_EXT_extended_dynamic_state3", "VK_EXT_descriptor_indexing",
|
||||
"VK_KHR_timeline_semaphore", "VK_EXT_mesh_shader", "VK_KHR_ray_query",
|
||||
};
|
||||
|
||||
static const char *type_name(VkPhysicalDeviceType type) {
|
||||
switch (type) {
|
||||
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: return "integrated GPU";
|
||||
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: return "discrete GPU";
|
||||
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: return "virtual GPU";
|
||||
case VK_PHYSICAL_DEVICE_TYPE_CPU: return "CPU";
|
||||
default: return "other";
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_droiddeck_launcher_gpu_VulkanInfo_nativeQuery(JNIEnv *env, jclass clazz) {
|
||||
static char out[4096];
|
||||
size_t used = 0;
|
||||
out[0] = 0;
|
||||
void *lib = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
|
||||
if (!lib) { LINE("error=no Vulkan loader\n"); return (*env)->NewStringUTF(env, out); }
|
||||
PFN_vkGetInstanceProcAddr gipa = (PFN_vkGetInstanceProcAddr) dlsym(lib, "vkGetInstanceProcAddr");
|
||||
PFN_vkCreateInstance create = gipa ? (PFN_vkCreateInstance) gipa(NULL, "vkCreateInstance") : NULL;
|
||||
PFN_vkEnumerateInstanceVersion instanceVersion = gipa ? (PFN_vkEnumerateInstanceVersion) gipa(NULL, "vkEnumerateInstanceVersion") : NULL;
|
||||
if (!create) { LINE("error=no vkCreateInstance\n"); dlclose(lib); return (*env)->NewStringUTF(env, out); }
|
||||
uint32_t loader = VK_API_VERSION_1_0;
|
||||
if (instanceVersion) instanceVersion(&loader);
|
||||
LINE("loader=%u.%u.%u\n", VK_API_VERSION_MAJOR(loader), VK_API_VERSION_MINOR(loader), VK_API_VERSION_PATCH(loader));
|
||||
|
||||
VkApplicationInfo app = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "DroidDeck device info",
|
||||
.apiVersion = loader >= VK_API_VERSION_1_1 ? VK_API_VERSION_1_1 : VK_API_VERSION_1_0 };
|
||||
VkInstanceCreateInfo info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app };
|
||||
VkInstance instance = VK_NULL_HANDLE;
|
||||
if (create(&info, NULL, &instance) != VK_SUCCESS) { LINE("error=vkCreateInstance failed\n"); dlclose(lib); return (*env)->NewStringUTF(env, out); }
|
||||
|
||||
#define GET(name) PFN_##name name = (PFN_##name) gipa(instance, #name)
|
||||
GET(vkDestroyInstance); GET(vkEnumeratePhysicalDevices); GET(vkGetPhysicalDeviceProperties);
|
||||
GET(vkGetPhysicalDeviceProperties2); GET(vkGetPhysicalDeviceMemoryProperties);
|
||||
GET(vkEnumerateDeviceExtensionProperties); GET(vkGetPhysicalDeviceFeatures);
|
||||
uint32_t count = 1;
|
||||
VkPhysicalDevice device = VK_NULL_HANDLE;
|
||||
VkResult listed = vkEnumeratePhysicalDevices ? vkEnumeratePhysicalDevices(instance, &count, &device) : VK_ERROR_INITIALIZATION_FAILED;
|
||||
if ((listed != VK_SUCCESS && listed != VK_INCOMPLETE) || count == 0 || !device || !vkGetPhysicalDeviceProperties) {
|
||||
LINE("error=no Vulkan device\n");
|
||||
} else {
|
||||
VkPhysicalDeviceProperties p;
|
||||
vkGetPhysicalDeviceProperties(device, &p);
|
||||
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));
|
||||
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);
|
||||
|
||||
uint32_t extCount = 0;
|
||||
if (vkEnumerateDeviceExtensionProperties) vkEnumerateDeviceExtensionProperties(device, NULL, &extCount, NULL);
|
||||
static VkExtensionProperties exts[512];
|
||||
if (extCount > 512) extCount = 512;
|
||||
if (extCount && vkEnumerateDeviceExtensionProperties) vkEnumerateDeviceExtensionProperties(device, NULL, &extCount, exts);
|
||||
int driverProps = p.apiVersion >= VK_API_VERSION_1_2;
|
||||
for (uint32_t i = 0; i < extCount; i++)
|
||||
if (!strcmp(exts[i].extensionName, "VK_KHR_driver_properties")) driverProps = 1;
|
||||
if (driverProps && vkGetPhysicalDeviceProperties2) {
|
||||
VkPhysicalDeviceDriverProperties driver = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES };
|
||||
VkPhysicalDeviceProperties2 p2 = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, .pNext = &driver };
|
||||
vkGetPhysicalDeviceProperties2(device, &p2);
|
||||
LINE("driver_name=%s\n", driver.driverName);
|
||||
LINE("driver_info=%s\n", driver.driverInfo);
|
||||
LINE("conformance=%u.%u.%u.%u\n", driver.conformanceVersion.major, driver.conformanceVersion.minor,
|
||||
driver.conformanceVersion.subminor, driver.conformanceVersion.patch);
|
||||
}
|
||||
LINE("extension_count=%u\n", extCount);
|
||||
for (size_t e = 0; e < sizeof(EXTENSIONS) / sizeof(*EXTENSIONS); e++) {
|
||||
int has = 0;
|
||||
for (uint32_t i = 0; i < extCount && !has; i++) has = !strcmp(exts[i].extensionName, EXTENSIONS[e]);
|
||||
LINE("ext.%s=%s\n", EXTENSIONS[e], has ? "yes" : "no");
|
||||
}
|
||||
if (vkGetPhysicalDeviceFeatures) {
|
||||
VkPhysicalDeviceFeatures f;
|
||||
vkGetPhysicalDeviceFeatures(device, &f);
|
||||
LINE("geometry_shader=%s\ntessellation_shader=%s\ntexture_bc=%s\n",
|
||||
f.geometryShader ? "yes" : "no", f.tessellationShader ? "yes" : "no", f.textureCompressionBC ? "yes" : "no");
|
||||
}
|
||||
if (vkGetPhysicalDeviceMemoryProperties) {
|
||||
VkPhysicalDeviceMemoryProperties m;
|
||||
vkGetPhysicalDeviceMemoryProperties(device, &m);
|
||||
VkDeviceSize largest = 0;
|
||||
for (uint32_t i = 0; i < m.memoryHeapCount; i++)
|
||||
if ((m.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) && m.memoryHeaps[i].size > largest) largest = m.memoryHeaps[i].size;
|
||||
LINE("device_heap_mb=%llu\n", (unsigned long long) (largest >> 20));
|
||||
}
|
||||
}
|
||||
if (vkDestroyInstance) vkDestroyInstance(instance, NULL);
|
||||
dlclose(lib);
|
||||
return (*env)->NewStringUTF(env, out);
|
||||
}
|
||||
+1705
-279
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,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_alpha.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_beta.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_chain.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_common.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_delta.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (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.
|
||||
//
|
||||
// Derived from WinNative's lsfg_dll.c (GPL-3.0-or-later), LSFG port credited to
|
||||
// Camille LaVey / the Eden Emulator Project, following upstream lsfg-vk
|
||||
// (PancakeTAS). Bannerlator is GPL-3.0.
|
||||
// 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 = [] {
|
||||
@@ -478,20 +463,6 @@ DllStatus validateDll(const std::string& dllPath) {
|
||||
return status;
|
||||
}
|
||||
|
||||
Variant dllVariant(const std::string& dllPath, bool preferFp16) {
|
||||
if (dllPath.empty()) return Variant::None;
|
||||
|
||||
PeImage img;
|
||||
if (!img.open(dllPath)) return Variant::None;
|
||||
|
||||
ResourceTable table;
|
||||
if (parseResources(img, table) != DllStatus::Ok) return Variant::None;
|
||||
|
||||
Variant v = selectVariant(table, preferFp16);
|
||||
if (v == Variant::None && hasBaseChain(table)) v = Variant::DxbcTranslated;
|
||||
return v;
|
||||
}
|
||||
|
||||
DllStatus buildCache(const std::string& dllPath, const std::string& cachePath, bool preferFp16) {
|
||||
if (dllPath.empty() || cachePath.empty()) return DllStatus::NotInstalled;
|
||||
|
||||
@@ -547,22 +518,6 @@ DllStatus buildCache(const std::string& dllPath, const std::string& cachePath, b
|
||||
return DllStatus::Ok;
|
||||
}
|
||||
|
||||
DllStatus cacheMatchesSource(const std::string& cachePath, const std::string& dllPath,
|
||||
bool& outMatches) {
|
||||
outMatches = false;
|
||||
if (cachePath.empty() || dllPath.empty()) return DllStatus::CacheUnusable;
|
||||
|
||||
CacheHeader header{};
|
||||
if (!readCacheHeader(cachePath, header)) return DllStatus::CacheUnusable;
|
||||
|
||||
PeImage img;
|
||||
if (!img.open(dllPath)) return DllStatus::NotInstalled;
|
||||
|
||||
outMatches = header.sourceSize == (uint64_t)img.size()
|
||||
&& header.sourceHash == fnv1a64(img.data(), img.size());
|
||||
return DllStatus::Ok;
|
||||
}
|
||||
|
||||
DllStatus cacheVariant(const std::string& cachePath, Variant& outVariant) {
|
||||
outVariant = Variant::None;
|
||||
CacheHeader header{};
|
||||
@@ -614,7 +569,6 @@ DllStatus loadModules(const std::string& cachePath, ModuleSet& outSet) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// ---- SPIR-V version lowering (Vulkan 1.1/1.2 compat) ----------------------
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -623,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;
|
||||
@@ -654,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;
|
||||
@@ -675,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) {
|
||||
@@ -689,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");
|
||||
@@ -699,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. Bannerlator 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.
|
||||
//
|
||||
// Derived from WinNative's lsfg_dll.c (GPL-3.0-or-later), whose LSFG port is
|
||||
// credited to Camille LaVey / the Eden Emulator Project, and which follows
|
||||
// upstream lsfg-vk (PancakeTAS). Bannerlator is GPL-3.0; see THIRD-PARTY-LICENSES.md.
|
||||
// ============================================================================
|
||||
|
||||
#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,48 +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);
|
||||
|
||||
// Which producer would be used for this DLL.
|
||||
Variant dllVariant(const std::string& dllPath, bool preferFp16);
|
||||
|
||||
// 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);
|
||||
|
||||
// Is the cache current for this DLL? Compares source size + content hash.
|
||||
DllStatus cacheMatchesSource(const std::string& cachePath, const std::string& dllPath,
|
||||
bool& outMatches);
|
||||
|
||||
// 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,9 +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. Derived from WinNative's lsfg_dxbc.cpp
|
||||
// (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"
|
||||
|
||||
@@ -20,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();
|
||||
@@ -37,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) {
|
||||
@@ -59,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];
|
||||
@@ -74,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,7 +1,3 @@
|
||||
// See lsfg_engine.h.
|
||||
//
|
||||
// Ported from WinNative's vkr_lsfg.cpp (GPL-3.0-or-later), LSFG port credited
|
||||
// to Camille LaVey / the Eden Emulator Project, following upstream lsfg-vk.
|
||||
|
||||
#include "lsfg_engine.h"
|
||||
|
||||
@@ -20,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;
|
||||
@@ -49,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[] = {
|
||||
@@ -87,7 +78,7 @@ void copyPresentedFrame(VkCommandBuffer cmd, VkImage source, LsfgImage& destinat
|
||||
destination.SetLayout(VK_IMAGE_LAYOUT_GENERAL);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
}
|
||||
|
||||
Engine::Engine() = default;
|
||||
|
||||
@@ -141,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;
|
||||
@@ -170,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;
|
||||
@@ -203,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_);
|
||||
}
|
||||
@@ -222,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;
|
||||
@@ -264,12 +236,7 @@ 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_
|
||||
const bool needHistory = generations > 0 || primeHistory_ || followEveryFrame()
|
||||
|| (governorEnabled_ && governor_.wantsHistory());
|
||||
if (needHistory) {
|
||||
copyPresentedFrame(cmd, source, chain_->Input(count), VkExtent2D{width, height});
|
||||
@@ -277,14 +244,7 @@ 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) chain_->DispatchShared(cmd, count);
|
||||
if (warm_ && (generations > 0 || followEveryFrame())) chain_->DispatchShared(cmd, count);
|
||||
}
|
||||
|
||||
void Engine::generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t targetIndex,
|
||||
@@ -317,4 +277,4 @@ void Engine::reset() {
|
||||
plan_ = {};
|
||||
}
|
||||
|
||||
} // namespace lsfg
|
||||
}
|
||||
@@ -1,24 +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.
|
||||
//
|
||||
// Ported from WinNative's vkr_lsfg.cpp (GPL-3.0-or-later), LSFG port credited
|
||||
// to Camille LaVey / the Eden Emulator Project, following upstream lsfg-vk.
|
||||
// ============================================================================
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -27,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"
|
||||
|
||||
@@ -36,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);
|
||||
|
||||
@@ -70,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; }
|
||||
|
||||
@@ -101,26 +60,16 @@ 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_; }
|
||||
|
||||
bool followEveryFrame() const { return warm_ && pacer_.Config().target_rate != 0; }
|
||||
|
||||
Device device_{};
|
||||
std::string cachePath_;
|
||||
std::unique_ptr<LsfgShaders> shaders_;
|
||||
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_{};
|
||||
|
||||
@@ -146,4 +95,4 @@ private:
|
||||
bool unavailable_{};
|
||||
};
|
||||
|
||||
} // namespace lsfg
|
||||
}
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_gamma.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_generate.hpp"
|
||||
#include "lsfg_dll.h"
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (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,19 +15,13 @@ 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) {
|
||||
return (jint)lsfg::validateDll(toStdString(env, dllPath));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_droiddeck_launcher_gpu_LsfgNative_nativeDllVariant(JNIEnv* env, jclass, jstring dllPath,
|
||||
jboolean preferFp16) {
|
||||
return (jint)lsfg::dllVariant(toStdString(env, dllPath), preferFp16 == JNI_TRUE);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_droiddeck_launcher_gpu_LsfgNative_nativeBuildCache(JNIEnv* env, jclass, jstring dllPath,
|
||||
jstring cachePath, jboolean preferFp16) {
|
||||
@@ -40,16 +29,6 @@ Java_com_droiddeck_launcher_gpu_LsfgNative_nativeBuildCache(JNIEnv* env, jclass,
|
||||
preferFp16 == JNI_TRUE);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_droiddeck_launcher_gpu_LsfgNative_nativeCacheMatchesSource(JNIEnv* env, jclass,
|
||||
jstring cachePath,
|
||||
jstring dllPath) {
|
||||
bool matches = false;
|
||||
const lsfg::DllStatus status = lsfg::cacheMatchesSource(toStdString(env, cachePath),
|
||||
toStdString(env, dllPath), matches);
|
||||
return (jboolean)(status == lsfg::DllStatus::Ok && matches);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_droiddeck_launcher_gpu_LsfgNative_nativeCacheVariant(JNIEnv* env, jclass, jstring cachePath) {
|
||||
lsfg::Variant variant = lsfg::Variant::None;
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_mipmaps.hpp"
|
||||
#include "lsfg_dll.h"
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_pacer.hpp"
|
||||
|
||||
@@ -22,6 +17,7 @@ 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;
|
||||
constexpr float TARGET_MARGIN = 0.1f;
|
||||
constexpr float CREDIT_EPSILON = 1.0e-4f;
|
||||
constexpr float SOURCE_ACCUM_FLOOR = 0.01f;
|
||||
constexpr uint32_t MIN_RATE_SAMPLES = 12;
|
||||
@@ -117,19 +113,9 @@ LsfgPlan LsfgPacer::Plan(size_t capacity, uint64_t source_frames) {
|
||||
if (target_rate > 0.0f && config.refresh_rate > 0.0f) {
|
||||
target_rate = std::min(target_rate, config.refresh_rate);
|
||||
}
|
||||
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,10 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (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,10 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_shaders.hpp"
|
||||
#include "lsfg_common.hpp"
|
||||
@@ -28,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,10 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
|
||||
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
|
||||
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
|
||||
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -21,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. Bannerlator'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();
|
||||
Loaded 100 of 1225 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user