mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
Stage pinned Qualcomm bridge and constant-index shader compatibility
(cherry picked from commit 03ca7778eff0a1216a57c7e7933297c49ea14532)
(cherry picked from commit ca9f416118)
This commit is contained in:
1 parent
5f949856b0
commit
6339d00a7a
9 files changed
+763
-1
No files matched your search
+23
-1
@@ -72,7 +72,7 @@ if (!(treeState in ['clean', 'dirty'])) {
|
||||
def status = gitValue(['status', '--porcelain'])
|
||||
treeState = status == null ? 'unknown' : (status.isEmpty() ? 'clean' : 'dirty')
|
||||
}
|
||||
buildLabel = "${buildOrigin} ${treeState} · ${buildLabel}"
|
||||
buildLabel = "${buildOrigin} ${treeState} · ${buildLabel}"
|
||||
def escapedBuildLabel = buildLabel.replace('\\', '\\\\').replace('"', '\\"')
|
||||
|
||||
def sourceCommit = providers.environmentVariable('DROIDDECK_SOURCE_COMMIT').getOrNull()
|
||||
@@ -80,6 +80,11 @@ def sourceCommit = providers.environmentVariable('DROIDDECK_SOURCE_COMMIT').getO
|
||||
def prNumber = (providers.environmentVariable('DROIDDECK_PR_NUMBER').getOrNull() ?: '0').with { it.isInteger() ? it : '0' }
|
||||
def ciBuild = providers.environmentVariable('GITHUB_ACTIONS').getOrNull() == 'true'
|
||||
if (project.hasProperty('reuseInstalledRuntime')) throw new GradleException('All APKs require prepared Linux runtime packages; runtime reuse is not allowed')
|
||||
def qualcommRuntime = new groovy.json.JsonSlurper().parse(rootProject.file('tools/qualcomm/runtime.json'))
|
||||
def qualcommSources = fileTree(rootProject.file('tools/qualcomm')) { include '*.py', '*.c', '*.json', '*.sh', '**/*.patch' }
|
||||
def qualcommDigest = java.security.MessageDigest.getInstance('SHA-256')
|
||||
qualcommSources.files.sort { it.name }.each { qualcommDigest.update(it.bytes) }
|
||||
def qualcommRevision = qualcommDigest.digest().encodeHex().toString()
|
||||
def releaseSigner = rootProject.file('keystore/release-signer.sha256').text.trim()
|
||||
def commitTime = (gitValue(['show', '-s', '--format=%ct', '--end-of-options', sourceCommit]) ?: '0').with { it.isLong() ? it : '0' }
|
||||
def desktopKde = rootProject.file('tools/desktop-kde/release.env').readLines()
|
||||
@@ -129,6 +134,9 @@ android {
|
||||
buildConfigField "String", "SOURCE_COMMIT", "\"${sourceCommit}\""
|
||||
buildConfigField "int", "PR_NUMBER", "${prNumber}"
|
||||
buildConfigField "boolean", "CI_BUILD", "${ciBuild}"
|
||||
buildConfigField "String", "QUALCOMM_BRIDGE_SHA256", "\"${qualcommRuntime.sha256}\""
|
||||
buildConfigField "String", "QUALCOMM_GAMESCOPE_SHA256", "\"${qualcommRuntime.gamescope.member_sha256}\""
|
||||
buildConfigField "String", "QUALCOMM_FIXES_REVISION", "\"${qualcommRevision}\""
|
||||
buildConfigField "String", "RELEASE_SIGNER", "\"${releaseSigner}\""
|
||||
buildConfigField "long", "COMMIT_TIME", "${commitTime}L * 1000L"
|
||||
buildConfigField "String", "VARIANT_SUFFIXES", "\"${variantSuffixes}\""
|
||||
@@ -278,6 +286,20 @@ def stageRuntimeAssets = tasks.register('stageRuntimeAssets', Sync) {
|
||||
into(runtimeAssetsDir)
|
||||
}
|
||||
android.sourceSets.main.assets.setSrcDirs([runtimeAssetsDir])
|
||||
def stageQualcommRuntime = tasks.register('stageQualcommRuntime', Exec) {
|
||||
workingDir rootProject.projectDir
|
||||
inputs.files(qualcommSources)
|
||||
inputs.property('ndkVersion', android.ndkVersion)
|
||||
outputs.dir(layout.buildDirectory.dir('qualcommAssets'))
|
||||
commandLine pythonExecutable, 'tools/qualcomm/fetch_runtime.py',
|
||||
'--output', layout.buildDirectory.dir('qualcommAssets/qualcomm').get().asFile.absolutePath,
|
||||
'--cache', rootProject.file('build/qualcomm-cache').absolutePath,
|
||||
'--ndk', android.ndkDirectory.absolutePath
|
||||
}
|
||||
android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('qualcommAssets'))
|
||||
tasks.configureEach { task ->
|
||||
if (task.name ==~ /merge.*Assets/) task.dependsOn(stageQualcommRuntime)
|
||||
}
|
||||
if (vitureSdkDir) android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('vitureAssets'))
|
||||
if (uvcDir) {
|
||||
def stageUvcNotices = tasks.register('stageUvcNotices', Copy) {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#include <android/hardware_buffer.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 2) return 2;
|
||||
void *library = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL);
|
||||
if (!library) { fprintf(stderr, "%s\n", dlerror()); return 3; }
|
||||
int (*supported)(void) = dlsym(library, "vkp_android_buffer_supported");
|
||||
void *(*import_buffer)(AHardwareBuffer *) = dlsym(library, "vkp_image_from_android_buffer");
|
||||
int (*readback)(void *, uint32_t *, int) = dlsym(library, "vkp_image_readback");
|
||||
void (*destroy)(void *) = dlsym(library, "vkp_image_destroy");
|
||||
if (!supported || !import_buffer || !readback || !destroy || !supported()) return 4;
|
||||
AHardwareBuffer_Desc desc = {
|
||||
.width = 64, .height = 64, .layers = 1, .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
|
||||
.usage = AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
|
||||
AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT,
|
||||
};
|
||||
AHardwareBuffer *buffer = NULL;
|
||||
if (AHardwareBuffer_allocate(&desc, &buffer)) return 5;
|
||||
AHardwareBuffer_describe(buffer, &desc);
|
||||
void *pixels = NULL;
|
||||
if (AHardwareBuffer_lock(buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY, -1, NULL, &pixels)) return 6;
|
||||
for (uint32_t y = 0; y < desc.height; y++)
|
||||
for (uint32_t x = 0; x < desc.width; x++)
|
||||
((uint32_t *)pixels)[y * desc.stride + x] = 0xff224466u;
|
||||
if (AHardwareBuffer_unlock(buffer, NULL)) return 7;
|
||||
void *image = import_buffer(buffer);
|
||||
if (!image) return 8;
|
||||
uint32_t result[64 * 64];
|
||||
int status = readback(image, result, 64 * 64);
|
||||
if (status < 0) { fprintf(stderr, "Readback failed: %d\n", status); return 9; }
|
||||
/* vkp_image_readback converts its input to B8G8R8A8_UNORM. */
|
||||
const uint32_t expected = 0xff664422u;
|
||||
for (int i = 0; i < 64 * 64; i++) {
|
||||
if (result[i] != expected) {
|
||||
fprintf(stderr, "Pixel %d: %#x expected %#x\n", i, result[i], expected);
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
destroy(image);
|
||||
AHardwareBuffer_release(buffer);
|
||||
puts("PASS: Android hardware buffer imported and 4096 pixels read back through the compositor");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Build the Gamescope shader workaround and its ARM64 glibc Vulkan layer."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path, PurePosixPath
|
||||
import shutil
|
||||
import subprocess
|
||||
import tarfile
|
||||
import tempfile
|
||||
|
||||
|
||||
def build(output, archive, lock, ndk):
|
||||
host = 'windows-x86_64' if os.name == 'nt' else 'darwin-x86_64' if os.uname().sysname == 'Darwin' else 'linux-x86_64'
|
||||
suffix = '.exe' if os.name == 'nt' else ''
|
||||
toolchain = ndk / 'toolchains/llvm/prebuilt' / host
|
||||
compiler = toolchain / ('bin/clang' + suffix)
|
||||
shader_compiler = ndk / 'shader-tools' / host / ('glslc' + suffix)
|
||||
here = Path(__file__).resolve().parent
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix='qualcomm-shaders-') as temporary:
|
||||
source = Path(temporary)
|
||||
with tarfile.open(archive, 'r:gz') as package:
|
||||
for member in package:
|
||||
path = PurePosixPath(member.name)
|
||||
relative = PurePosixPath(*path.parts[1:])
|
||||
if not member.isfile() or '..' in relative.parts:
|
||||
continue
|
||||
if not str(relative).startswith('src/shaders/') and str(relative) != 'LICENSE':
|
||||
continue
|
||||
target = source / str(relative)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(package.extractfile(member).read())
|
||||
patch = here / 'constant-yuv-samplers.patch'
|
||||
subprocess.run(['git', 'apply', '--check', str(patch)], cwd=source, check=True)
|
||||
subprocess.run(['git', 'apply', str(patch)], cwd=source, check=True)
|
||||
for fingerprint, name in lock['replacements'].items():
|
||||
subprocess.run([str(shader_compiler), str(source / 'src/shaders' / (name + '.comp')),
|
||||
'-o', str(output / (fingerprint + '.spv'))], check=True)
|
||||
licenses = output / 'licenses'
|
||||
licenses.mkdir(exist_ok=True)
|
||||
shutil.copyfile(source / 'LICENSE', licenses / 'Gamescope-LICENSE')
|
||||
shutil.copyfile(archive, licenses / 'gamescope-source.tar.gz')
|
||||
shutil.copyfile(patch, licenses / patch.name)
|
||||
# Only Vulkan types and opaque libc pointers cross this glibc ABI boundary.
|
||||
subprocess.run([str(compiler), '--target=aarch64-linux-gnu', '--sysroot=' + str(toolchain / 'sysroot'),
|
||||
'-I' + str(toolchain / 'sysroot/usr/include/aarch64-linux-android'),
|
||||
'-fuse-ld=lld', '-nostdlib', '-shared', '-fPIC', '-O2', '-fno-stack-protector',
|
||||
'-Wl,-z,max-page-size=16384', '-Wall', '-Wextra', '-Werror',
|
||||
str(here / 'shader_layer.c'), '-o', str(output / 'qualcomm-shaders.so')], check=True)
|
||||
manifest = dict(file_format_version='1.2.0', layer=dict(
|
||||
name='VK_LAYER_DROIDDECK_qualcomm_shaders', type='GLOBAL', library_path='./qualcomm-shaders.so',
|
||||
api_version='1.4.0', implementation_version='1', description='Gamescope constant YUV sampler indices for Qualcomm'))
|
||||
(output / 'VkLayer_DROIDDECK_qualcomm_shaders.json').write_text(json.dumps(manifest) + '\n', encoding='utf-8')
|
||||
@@ -0,0 +1,150 @@
|
||||
diff --git a/src/shaders/cs_composite_blit.comp b/src/shaders/cs_composite_blit.comp
|
||||
index 4d974dc..1bf726d 100644
|
||||
--- a/src/shaders/cs_composite_blit.comp
|
||||
+++ b/src/shaders/cs_composite_blit.comp
|
||||
@@ -15,7 +15,19 @@ layout(
|
||||
|
||||
vec4 sampleLayer(uint layerIdx, vec2 uv) {
|
||||
if ((c_ycbcrMask & (1 << layerIdx)) != 0)
|
||||
- return sampleLayer(s_ycbcr_samplers[layerIdx], layerIdx, uv, false);
|
||||
+ {
|
||||
+ switch (layerIdx) {
|
||||
+ case 0u: return sampleLayer(s_ycbcr_samplers[0], layerIdx, uv, false);
|
||||
+ case 1u: return sampleLayer(s_ycbcr_samplers[1], layerIdx, uv, false);
|
||||
+ case 2u: return sampleLayer(s_ycbcr_samplers[2], layerIdx, uv, false);
|
||||
+ case 3u: return sampleLayer(s_ycbcr_samplers[3], layerIdx, uv, false);
|
||||
+ case 4u: return sampleLayer(s_ycbcr_samplers[4], layerIdx, uv, false);
|
||||
+ case 5u: return sampleLayer(s_ycbcr_samplers[5], layerIdx, uv, false);
|
||||
+ case 6u: return sampleLayer(s_ycbcr_samplers[6], layerIdx, uv, false);
|
||||
+ case 7u: return sampleLayer(s_ycbcr_samplers[7], layerIdx, uv, false);
|
||||
+ }
|
||||
+ return vec4(0.0);
|
||||
+ }
|
||||
return sampleLayer(s_samplers[layerIdx], layerIdx, uv, true);
|
||||
}
|
||||
|
||||
diff --git a/src/shaders/cs_composite_blur.comp b/src/shaders/cs_composite_blur.comp
|
||||
index 318b0e9..d99193b 100644
|
||||
--- a/src/shaders/cs_composite_blur.comp
|
||||
+++ b/src/shaders/cs_composite_blur.comp
|
||||
@@ -18,7 +18,19 @@ layout(
|
||||
|
||||
vec4 sampleLayer(uint layerIdx, vec2 uv) {
|
||||
if ((c_ycbcrMask & (1 << layerIdx)) != 0)
|
||||
- return sampleLayer(s_ycbcr_samplers[layerIdx], layerIdx, uv, false);
|
||||
+ {
|
||||
+ switch (layerIdx) {
|
||||
+ case 0u: return sampleLayer(s_ycbcr_samplers[0], layerIdx, uv, false);
|
||||
+ case 1u: return sampleLayer(s_ycbcr_samplers[1], layerIdx, uv, false);
|
||||
+ case 2u: return sampleLayer(s_ycbcr_samplers[2], layerIdx, uv, false);
|
||||
+ case 3u: return sampleLayer(s_ycbcr_samplers[3], layerIdx, uv, false);
|
||||
+ case 4u: return sampleLayer(s_ycbcr_samplers[4], layerIdx, uv, false);
|
||||
+ case 5u: return sampleLayer(s_ycbcr_samplers[5], layerIdx, uv, false);
|
||||
+ case 6u: return sampleLayer(s_ycbcr_samplers[6], layerIdx, uv, false);
|
||||
+ case 7u: return sampleLayer(s_ycbcr_samplers[7], layerIdx, uv, false);
|
||||
+ }
|
||||
+ return vec4(0.0);
|
||||
+ }
|
||||
return sampleLayer(s_samplers[layerIdx], layerIdx, uv, true);
|
||||
}
|
||||
|
||||
diff --git a/src/shaders/cs_composite_blur_cond.comp b/src/shaders/cs_composite_blur_cond.comp
|
||||
index 39b0405..28f4e1e 100644
|
||||
--- a/src/shaders/cs_composite_blur_cond.comp
|
||||
+++ b/src/shaders/cs_composite_blur_cond.comp
|
||||
@@ -18,7 +18,19 @@ layout(
|
||||
|
||||
vec4 sampleLayer(uint layerIdx, vec2 uv) {
|
||||
if ((c_ycbcrMask & (1 << layerIdx)) != 0)
|
||||
- return sampleLayer(s_ycbcr_samplers[layerIdx], layerIdx, uv, false);
|
||||
+ {
|
||||
+ switch (layerIdx) {
|
||||
+ case 0u: return sampleLayer(s_ycbcr_samplers[0], layerIdx, uv, false);
|
||||
+ case 1u: return sampleLayer(s_ycbcr_samplers[1], layerIdx, uv, false);
|
||||
+ case 2u: return sampleLayer(s_ycbcr_samplers[2], layerIdx, uv, false);
|
||||
+ case 3u: return sampleLayer(s_ycbcr_samplers[3], layerIdx, uv, false);
|
||||
+ case 4u: return sampleLayer(s_ycbcr_samplers[4], layerIdx, uv, false);
|
||||
+ case 5u: return sampleLayer(s_ycbcr_samplers[5], layerIdx, uv, false);
|
||||
+ case 6u: return sampleLayer(s_ycbcr_samplers[6], layerIdx, uv, false);
|
||||
+ case 7u: return sampleLayer(s_ycbcr_samplers[7], layerIdx, uv, false);
|
||||
+ }
|
||||
+ return vec4(0.0);
|
||||
+ }
|
||||
return sampleLayer(s_samplers[layerIdx], layerIdx, uv, true);
|
||||
}
|
||||
|
||||
diff --git a/src/shaders/cs_composite_rcas.comp b/src/shaders/cs_composite_rcas.comp
|
||||
index 3caefb1..269d42c 100644
|
||||
--- a/src/shaders/cs_composite_rcas.comp
|
||||
+++ b/src/shaders/cs_composite_rcas.comp
|
||||
@@ -46,7 +46,19 @@ void FsrRcasInputF(inout float r, inout float g, inout float b) {}
|
||||
|
||||
vec4 sampleLayer(uint layerIdx, vec2 uv) {
|
||||
if ((c_ycbcrMask & (1 << layerIdx)) != 0)
|
||||
- return sampleLayerEx(s_ycbcr_samplers[layerIdx], layerIdx - 1, layerIdx, uv, false);
|
||||
+ {
|
||||
+ switch (layerIdx) {
|
||||
+ case 0u: return sampleLayerEx(s_ycbcr_samplers[0], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 1u: return sampleLayerEx(s_ycbcr_samplers[1], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 2u: return sampleLayerEx(s_ycbcr_samplers[2], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 3u: return sampleLayerEx(s_ycbcr_samplers[3], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 4u: return sampleLayerEx(s_ycbcr_samplers[4], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 5u: return sampleLayerEx(s_ycbcr_samplers[5], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 6u: return sampleLayerEx(s_ycbcr_samplers[6], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 7u: return sampleLayerEx(s_ycbcr_samplers[7], layerIdx - 1, layerIdx, uv, false);
|
||||
+ }
|
||||
+ return vec4(0.0);
|
||||
+ }
|
||||
return sampleLayerEx(s_samplers[layerIdx], layerIdx - 1, layerIdx, uv, true);
|
||||
}
|
||||
|
||||
diff --git a/src/shaders/cs_composite_rcas_1px.comp b/src/shaders/cs_composite_rcas_1px.comp
|
||||
index 822aa82..8632ddf 100644
|
||||
--- a/src/shaders/cs_composite_rcas_1px.comp
|
||||
+++ b/src/shaders/cs_composite_rcas_1px.comp
|
||||
@@ -46,7 +46,19 @@ void FsrRcasInputF(inout float r, inout float g, inout float b) {}
|
||||
|
||||
vec4 sampleLayer(uint layerIdx, vec2 uv) {
|
||||
if ((c_ycbcrMask & (1 << layerIdx)) != 0)
|
||||
- return sampleLayerEx(s_ycbcr_samplers[layerIdx], layerIdx - 1, layerIdx, uv, false);
|
||||
+ {
|
||||
+ switch (layerIdx) {
|
||||
+ case 0u: return sampleLayerEx(s_ycbcr_samplers[0], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 1u: return sampleLayerEx(s_ycbcr_samplers[1], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 2u: return sampleLayerEx(s_ycbcr_samplers[2], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 3u: return sampleLayerEx(s_ycbcr_samplers[3], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 4u: return sampleLayerEx(s_ycbcr_samplers[4], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 5u: return sampleLayerEx(s_ycbcr_samplers[5], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 6u: return sampleLayerEx(s_ycbcr_samplers[6], layerIdx - 1, layerIdx, uv, false);
|
||||
+ case 7u: return sampleLayerEx(s_ycbcr_samplers[7], layerIdx - 1, layerIdx, uv, false);
|
||||
+ }
|
||||
+ return vec4(0.0);
|
||||
+ }
|
||||
return sampleLayerEx(s_samplers[layerIdx], layerIdx - 1, layerIdx, uv, true);
|
||||
}
|
||||
|
||||
diff --git a/src/shaders/cs_gaussian_blur_horizontal.comp b/src/shaders/cs_gaussian_blur_horizontal.comp
|
||||
index 2037f1c..3c202bb 100644
|
||||
--- a/src/shaders/cs_gaussian_blur_horizontal.comp
|
||||
+++ b/src/shaders/cs_gaussian_blur_horizontal.comp
|
||||
@@ -51,7 +51,19 @@ void main()
|
||||
vec4 layerColor;
|
||||
// YCBCR technically has incorrect blending here but... meh.
|
||||
if ((c_ycbcrMask & (1 << i)) != 0)
|
||||
- layerColor = gaussian_blur(s_ycbcr_samplers[i], i, pos, u_blur_radius, false, false);
|
||||
+ {
|
||||
+ switch (i) {
|
||||
+ case 0: layerColor = gaussian_blur(s_ycbcr_samplers[0], i, pos, u_blur_radius, false, false); break;
|
||||
+ case 1: layerColor = gaussian_blur(s_ycbcr_samplers[1], i, pos, u_blur_radius, false, false); break;
|
||||
+ case 2: layerColor = gaussian_blur(s_ycbcr_samplers[2], i, pos, u_blur_radius, false, false); break;
|
||||
+ case 3: layerColor = gaussian_blur(s_ycbcr_samplers[3], i, pos, u_blur_radius, false, false); break;
|
||||
+ case 4: layerColor = gaussian_blur(s_ycbcr_samplers[4], i, pos, u_blur_radius, false, false); break;
|
||||
+ case 5: layerColor = gaussian_blur(s_ycbcr_samplers[5], i, pos, u_blur_radius, false, false); break;
|
||||
+ case 6: layerColor = gaussian_blur(s_ycbcr_samplers[6], i, pos, u_blur_radius, false, false); break;
|
||||
+ case 7: layerColor = gaussian_blur(s_ycbcr_samplers[7], i, pos, u_blur_radius, false, false); break;
|
||||
+ default: layerColor = vec4(0.0); break;
|
||||
+ }
|
||||
+ }
|
||||
else
|
||||
layerColor = gaussian_blur(s_samplers[i], i, pos, u_blur_radius, false, true);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
"""Stage the checksum-pinned ARM64 glibc bridge, including its corresponding source."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
|
||||
def fetch(cache, lock):
|
||||
expected = lock['sha256']
|
||||
cache.mkdir(parents=True, exist_ok=True)
|
||||
archive = cache / (expected + '.tzst')
|
||||
def valid(path):
|
||||
return path.is_file() and path.stat().st_size == lock['size'] and hashlib.sha256(path.read_bytes()).hexdigest() == expected
|
||||
if not valid(archive):
|
||||
temporary = archive.with_suffix('.partial')
|
||||
try:
|
||||
with urllib.request.urlopen(lock['url'], timeout=120) as source, temporary.open('wb') as target:
|
||||
shutil.copyfileobj(source, target)
|
||||
if not valid(temporary):
|
||||
raise ValueError('Qualcomm bridge archive failed SHA-256 verification')
|
||||
temporary.replace(archive)
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
return archive
|
||||
|
||||
|
||||
def stage(output, cache, lock, ndk=None):
|
||||
archive = fetch(cache, lock)
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(archive, output / 'runtime.tzst')
|
||||
gamescope = lock['gamescope']
|
||||
with zipfile.ZipFile(fetch(cache, gamescope)) as package:
|
||||
binary = package.read(gamescope['member'])
|
||||
if hashlib.sha256(binary).hexdigest() != gamescope['member_sha256']:
|
||||
raise ValueError('Qualcomm Gamescope binary failed SHA-256 verification')
|
||||
(output / 'gamescope').write_bytes(binary)
|
||||
if 'wsi' in lock:
|
||||
with zipfile.ZipFile(fetch(cache, lock['wsi'])) as package:
|
||||
for entry in lock['wsi']['files']:
|
||||
target = Path(entry['path'])
|
||||
if target.is_absolute() or '..' in target.parts:
|
||||
raise ValueError('Invalid WSI destination')
|
||||
data = package.read(entry['name'])
|
||||
if len(data) != entry['size'] or hashlib.sha256(data).hexdigest() != entry['sha256']:
|
||||
raise ValueError('Qualcomm WSI member failed SHA-256 verification')
|
||||
destination = output / target
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
destination.write_bytes(data)
|
||||
if 'shaders' in lock:
|
||||
if ndk is None:
|
||||
raise ValueError('The Android NDK is required to build Qualcomm shader compatibility')
|
||||
from build_shaders import build
|
||||
build(output / 'shaders', fetch(cache, lock['shaders']), lock['shaders'], ndk)
|
||||
(output / 'runtime.json').write_text(json.dumps(lock, indent=2) + '\n', encoding='utf-8')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
parser.add_argument('--cache', type=Path, required=True)
|
||||
parser.add_argument('--ndk', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
stage(args.output, args.cache, json.loads(Path(__file__).with_name('runtime.json').read_text()), args.ndk)
|
||||
@@ -0,0 +1,122 @@
|
||||
"""Run a real Vulkan transfer and readback through the selected Linux ICD."""
|
||||
import ctypes as c
|
||||
import json
|
||||
|
||||
|
||||
U = c.c_uint32
|
||||
H = c.c_void_p
|
||||
Q = c.c_uint64
|
||||
|
||||
|
||||
def structure(name, fields):
|
||||
return type(name, (c.Structure,), {'_fields_': fields})
|
||||
|
||||
|
||||
Application = structure('Application', [('type', U), ('next', H), ('name', c.c_char_p), ('version', U), ('engine', c.c_char_p), ('engineVersion', U), ('api', U)])
|
||||
Instance = structure('Instance', [('type', U), ('next', H), ('flags', U), ('application', c.POINTER(Application)), ('layerCount', U), ('layers', H), ('extensionCount', U), ('extensions', H)])
|
||||
QueueCreate = structure('QueueCreate', [('type', U), ('next', H), ('flags', U), ('family', U), ('count', U), ('priorities', c.POINTER(c.c_float))])
|
||||
DeviceCreate = structure('DeviceCreate', [('type', U), ('next', H), ('flags', U), ('queueCount', U), ('queues', c.POINTER(QueueCreate)), ('layerCount', U), ('layers', H), ('extensionCount', U), ('extensions', H), ('features', H)])
|
||||
BufferCreate = structure('BufferCreate', [('type', U), ('next', H), ('flags', U), ('size', Q), ('usage', U), ('sharing', U), ('familyCount', U), ('families', H)])
|
||||
Requirements = structure('Requirements', [('size', Q), ('alignment', Q), ('types', U)])
|
||||
MemoryType = structure('MemoryType', [('flags', U), ('heap', U)])
|
||||
Heap = structure('Heap', [('size', Q), ('flags', U)])
|
||||
MemoryProperties = structure('MemoryProperties', [('typeCount', U), ('types', MemoryType * 32), ('heapCount', U), ('heaps', Heap * 16)])
|
||||
Allocate = structure('Allocate', [('type', U), ('next', H), ('size', Q), ('index', U)])
|
||||
PoolCreate = structure('PoolCreate', [('type', U), ('next', H), ('flags', U), ('family', U)])
|
||||
CommandAllocate = structure('CommandAllocate', [('type', U), ('next', H), ('pool', H), ('level', U), ('count', U)])
|
||||
Begin = structure('Begin', [('type', U), ('next', H), ('flags', U), ('inheritance', H)])
|
||||
Barrier = structure('Barrier', [('type', U), ('next', H), ('source', U), ('destination', U)])
|
||||
Copy = structure('Copy', [('source', Q), ('destination', Q), ('size', Q)])
|
||||
Submit = structure('Submit', [('type', U), ('next', H), ('waitCount', U), ('waits', H), ('stages', H), ('commandCount', U), ('commands', c.POINTER(H)), ('signalCount', U), ('signals', H)])
|
||||
|
||||
|
||||
def run():
|
||||
vk = c.CDLL('libvulkan.so.1')
|
||||
def function(name, arguments, result=c.c_int):
|
||||
f = getattr(vk, name)
|
||||
f.argtypes, f.restype = arguments, result
|
||||
return f
|
||||
def check(result, operation):
|
||||
if result != 0:
|
||||
raise RuntimeError(f'{operation}: VkResult {result}')
|
||||
instance, device, pool = H(), H(), H()
|
||||
buffers, memories = [], []
|
||||
try:
|
||||
app = Application(type=0, name=b'DroidDeck Qualcomm probe', api=(1 << 22) | (1 << 12))
|
||||
info = Instance(type=1, application=c.pointer(app))
|
||||
check(function('vkCreateInstance', [c.POINTER(Instance), H, c.POINTER(H)])(c.byref(info), None, c.byref(instance)), 'instance')
|
||||
count = U()
|
||||
enumerate_devices = function('vkEnumeratePhysicalDevices', [H, c.POINTER(U), c.POINTER(H)])
|
||||
check(enumerate_devices(instance, c.byref(count), None), 'device count')
|
||||
if not count.value: raise RuntimeError('No Vulkan devices')
|
||||
devices = (H * count.value)()
|
||||
check(enumerate_devices(instance, c.byref(count), devices), 'devices')
|
||||
physical = H(devices[0])
|
||||
properties = c.create_string_buffer(4096)
|
||||
function('vkGetPhysicalDeviceProperties', [H, H], None)(physical, properties)
|
||||
name = properties.raw[20:276].split(b'\0')[0].decode()
|
||||
family_count = U()
|
||||
families_fn = function('vkGetPhysicalDeviceQueueFamilyProperties', [H, c.POINTER(U), H], None)
|
||||
families_fn(physical, c.byref(family_count), None)
|
||||
families = (U * (family_count.value * 6))()
|
||||
families_fn(physical, c.byref(family_count), families)
|
||||
family = next(i for i in range(family_count.value) if families[i * 6] & 7)
|
||||
priority = c.c_float(1)
|
||||
queue_info = QueueCreate(type=2, family=family, count=1, priorities=c.pointer(priority))
|
||||
device_info = DeviceCreate(type=3, queueCount=1, queues=c.pointer(queue_info))
|
||||
check(function('vkCreateDevice', [H, c.POINTER(DeviceCreate), H, c.POINTER(H)])(physical, c.byref(device_info), None, c.byref(device)), 'device')
|
||||
queue = H()
|
||||
function('vkGetDeviceQueue', [H, U, U, c.POINTER(H)], None)(device, family, 0, c.byref(queue))
|
||||
memory_props = MemoryProperties()
|
||||
function('vkGetPhysicalDeviceMemoryProperties', [H, c.POINTER(MemoryProperties)], None)(physical, c.byref(memory_props))
|
||||
for _ in range(2):
|
||||
buffer = H()
|
||||
buffer_info = BufferCreate(type=12, size=4096, usage=3)
|
||||
check(function('vkCreateBuffer', [H, c.POINTER(BufferCreate), H, c.POINTER(H)])(device, c.byref(buffer_info), None, c.byref(buffer)), 'buffer')
|
||||
buffers.append(buffer)
|
||||
requirements = Requirements()
|
||||
function('vkGetBufferMemoryRequirements', [H, H, c.POINTER(Requirements)], None)(device, buffer, c.byref(requirements))
|
||||
index = next(i for i in range(memory_props.typeCount) if requirements.types & (1 << i) and memory_props.types[i].flags & 6 == 6)
|
||||
allocation = Allocate(type=5, size=requirements.size, index=index)
|
||||
memory = H()
|
||||
check(function('vkAllocateMemory', [H, c.POINTER(Allocate), H, c.POINTER(H)])(device, c.byref(allocation), None, c.byref(memory)), 'memory')
|
||||
memories.append(memory)
|
||||
check(function('vkBindBufferMemory', [H, H, H, Q])(device, buffer, memory, 0), 'bind')
|
||||
pool_info = PoolCreate(type=39, family=family)
|
||||
check(function('vkCreateCommandPool', [H, c.POINTER(PoolCreate), H, c.POINTER(H)])(device, c.byref(pool_info), None, c.byref(pool)), 'pool')
|
||||
command = H()
|
||||
command_info = CommandAllocate(type=40, pool=pool, count=1)
|
||||
check(function('vkAllocateCommandBuffers', [H, c.POINTER(CommandAllocate), c.POINTER(H)])(device, c.byref(command_info), c.byref(command)), 'command')
|
||||
begin = Begin(type=42, flags=1)
|
||||
check(function('vkBeginCommandBuffer', [H, c.POINTER(Begin)])(command, c.byref(begin)), 'begin')
|
||||
pattern = 0x5A17C0DE
|
||||
function('vkCmdFillBuffer', [H, H, Q, Q, U], None)(command, buffers[0], 0, 4096, pattern)
|
||||
barrier_fn = function('vkCmdPipelineBarrier', [H, U, U, U, U, c.POINTER(Barrier), U, H, U, H], None)
|
||||
barrier = Barrier(type=46, source=0x1000, destination=0x800)
|
||||
barrier_fn(command, 0x1000, 0x1000, 0, 1, c.byref(barrier), 0, None, 0, None)
|
||||
region = Copy(size=4096)
|
||||
function('vkCmdCopyBuffer', [H, H, H, U, c.POINTER(Copy)], None)(command, buffers[0], buffers[1], 1, c.byref(region))
|
||||
barrier = Barrier(type=46, source=0x1000, destination=0x2000)
|
||||
barrier_fn(command, 0x1000, 0x4000, 0, 1, c.byref(barrier), 0, None, 0, None)
|
||||
check(function('vkEndCommandBuffer', [H])(command), 'end')
|
||||
submit = Submit(type=4, commandCount=1, commands=c.pointer(command))
|
||||
check(function('vkQueueSubmit', [H, U, c.POINTER(Submit), H])(queue, 1, c.byref(submit), None), 'submit')
|
||||
check(function('vkQueueWaitIdle', [H])(queue), 'wait')
|
||||
mapped = H()
|
||||
check(function('vkMapMemory', [H, H, Q, Q, U, c.POINTER(H)])(device, memories[1], 0, 4096, 0, c.byref(mapped)), 'map')
|
||||
words = list((U * 1024).from_address(mapped.value))
|
||||
function('vkUnmapMemory', [H, H], None)(device, memories[1])
|
||||
if words != [pattern] * 1024: raise RuntimeError('GPU transfer readback mismatch')
|
||||
print(json.dumps({'status': 'PASS', 'device': name, 'gpu_fill_copy_readback_bytes': 4096}))
|
||||
finally:
|
||||
if device:
|
||||
function('vkDeviceWaitIdle', [H])(device)
|
||||
if pool: function('vkDestroyCommandPool', [H, H, H], None)(device, pool, None)
|
||||
for buffer in buffers: function('vkDestroyBuffer', [H, H, H], None)(device, buffer, None)
|
||||
for memory in memories: function('vkFreeMemory', [H, H, H], None)(device, memory, None)
|
||||
function('vkDestroyDevice', [H, H], None)(device, None)
|
||||
if instance: function('vkDestroyInstance', [H, H], None)(instance, None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"url": "https://github.com/wallentx/DroidDeck/releases/download/hybris-4ed93fb9ee6e2a0b24ff087196f2e93d247fd985/runtime.tzst",
|
||||
"sha256": "a4e1ab9581c83ee6feff95e0e527930977a6c14c0e72bd2c4abf777e8472d987",
|
||||
"size": 5799930,
|
||||
"provenance": {
|
||||
"component_ci_run": "https://github.com/wallentx/DroidDeck/actions/runs/39360327540",
|
||||
"note": "Candidate pin: publish this exact archive and verify its URL before updating release.json.",
|
||||
"producer_commit": "4ed93fb9ee6e2a0b24ff087196f2e93d247fd985",
|
||||
"upstream_source_commit": "bc8cb3586b79d208fd21b925fa0209b3e432e008"
|
||||
},
|
||||
"gamescope": {
|
||||
"url": "https://github.com/wallentx/DroidDeck/releases/download/main-af320dd86c7de5738ff16cfeeefbcf42581c199e/DroidDeck-0.5.0-wallentx.4.apk",
|
||||
"sha256": "f73b229bf5d29c53c4ac618815e77c6e5a3eddd5c99e7c93eab276c831fcef6e",
|
||||
"size": 124331295,
|
||||
"source_commit": "af320dd86c7de5738ff16cfeeefbcf42581c199e",
|
||||
"member": "assets/linuxfs/usr/local/bin/gamescope",
|
||||
"member_sha256": "2250f26cdf0b6c73af80324c2a99b60465bae46ae6719083b9f9f8d1cf680165"
|
||||
},
|
||||
"wsi": {
|
||||
"files": [
|
||||
{
|
||||
"mode": 420,
|
||||
"name": "libVkLayer_window_system_integration.so",
|
||||
"path": "wsi/libVkLayer_window_system_integration.so",
|
||||
"sha256": "d07b961181b9547fd48ad9f05cc6d3b808b824676eb5a186fe3feb8caa013f19",
|
||||
"size": 436888
|
||||
},
|
||||
{
|
||||
"mode": 420,
|
||||
"name": "VkLayer_window_system_integration.json",
|
||||
"path": "wsi/VkLayer_window_system_integration.json",
|
||||
"sha256": "a1df8b1cee7315015d8fe82534451e373ae4d9cb2adfc232c68346ecbf2818ee",
|
||||
"size": 3683
|
||||
},
|
||||
{
|
||||
"mode": 420,
|
||||
"name": "LICENSE",
|
||||
"path": "licenses/wsi-LICENSE",
|
||||
"sha256": "5eb8d2da52230d2d66ce078a89ae0173aac63e2e73bed5f907a34736d114ea30",
|
||||
"size": 1086
|
||||
},
|
||||
{
|
||||
"mode": 420,
|
||||
"name": "source-commit",
|
||||
"path": "provenance/wsi-source-commit",
|
||||
"sha256": "8367150b20502af36c472651850694a15a3e2a1fd6b6beb1f6e12a85a97824f5",
|
||||
"size": 41
|
||||
},
|
||||
{
|
||||
"mode": 420,
|
||||
"name": "SHA256SUMS",
|
||||
"path": "provenance/wsi-SHA256SUMS",
|
||||
"sha256": "fda6f61028659648801de0e3a6fe1676efd99e1551881a5db8630970eae86158",
|
||||
"size": 108
|
||||
},
|
||||
{
|
||||
"mode": 420,
|
||||
"name": "downstream.patch",
|
||||
"path": "licenses/wsi-downstream.patch",
|
||||
"sha256": "e2ed113d22f05563248ef606c774c22658b184fbc05912229007cd5281027e78",
|
||||
"size": 769
|
||||
},
|
||||
{
|
||||
"mode": 420,
|
||||
"name": "source.tar.zst",
|
||||
"path": "licenses/wsi-source.tar.zst",
|
||||
"sha256": "22ae7c2d8aa3dfffdda601c3442d1966ab53ad225acdfadee69ba38974d0c4c5",
|
||||
"size": 209468
|
||||
},
|
||||
{
|
||||
"mode": 420,
|
||||
"name": "vulkan-headers-commit",
|
||||
"path": "provenance/wsi-vulkan-headers-commit",
|
||||
"sha256": "16706d6cdbeb788426a90d66574dfd71068c4d993d39e76ec5c281b1f30dc7a6",
|
||||
"size": 41
|
||||
}
|
||||
],
|
||||
"id": "wsi",
|
||||
"ignored": [],
|
||||
"provenance": {
|
||||
"ci_run": "https://github.com/wallentx/DroidDeck/actions/runs/38047627963",
|
||||
"repository_commit": "abe58618a5194f1f15499cbd5f9ec75503782556",
|
||||
"vulkan_headers_licenses": {
|
||||
"note": "Added from tracked assembly inputs; the original WSI archive and compiled library are unchanged.",
|
||||
"revision": "8d6039a455a7ecc7d2a592ff97f62db4e59b70bf",
|
||||
"source_urls": [
|
||||
"https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/8d6039a455a7ecc7d2a592ff97f62db4e59b70bf/LICENSE.md",
|
||||
"https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/8d6039a455a7ecc7d2a592ff97f62db4e59b70bf/LICENSES/MIT.txt"
|
||||
]
|
||||
}
|
||||
},
|
||||
"recipe_inputs": {
|
||||
"tools/vulkan-wsi/build.sh": "9e403151c58e6b2ee15241f525c4bacb2495f072191da6a63065e7fcc25b3b3c",
|
||||
"tools/vulkan-wsi/licenses/Vulkan-Headers/LICENSE.md": "95ad366d23fadf701d355bc45fb8b82ae2d700239471d35d41286ac3b08ff903",
|
||||
"tools/vulkan-wsi/licenses/Vulkan-Headers/MIT.txt": "1ca3502222d967f3be5751c55f6b7ee735b5383909c3b501495f54b216dbf227",
|
||||
"tools/vulkan-wsi/patches/0001-link-x11-external-memory.patch": "e2ed113d22f05563248ef606c774c22658b184fbc05912229007cd5281027e78",
|
||||
"tools/vulkan-wsi/source.env": "71a88087265a09b4ce06e7c9b98ef6799c6a7657cc6ef9084de663e96f5cfe63"
|
||||
},
|
||||
"sha256": "348f9ec42159c1ee6e2baba4312a5e845d26687238f29e3f4a1b0c3c97e13b3c",
|
||||
"size": 362717,
|
||||
"url": "https://github.com/wallentx/DroidDeck/releases/download/v0.3.2-progress.20261010.4/wsi-abe5861.zip"
|
||||
},
|
||||
"shaders": {
|
||||
"url": "https://codeload.github.com/ValveSoftware/gamescope/tar.gz/8f212644c46460549035971ab914421180f61a7c",
|
||||
"sha256": "40b75e449168caed811824b20716e2e7d236cf1bea6fb7dcfd742d50c8c6d6cc",
|
||||
"size": 970867,
|
||||
"source_commit": "8f212644c46460549035971ab914421180f61a7c",
|
||||
"replacements": {
|
||||
"05a8bfdf4ff5d71b": "cs_gaussian_blur_horizontal",
|
||||
"ee0a3fcfa1cf1281": "cs_composite_rcas_1px",
|
||||
"519ce32f2e53f5db": "cs_composite_rcas",
|
||||
"ee86eeb49ae9951c": "cs_composite_blur_cond",
|
||||
"73368f4579fe0d27": "cs_composite_blur",
|
||||
"80346d82c5a6d850": "cs_composite_blit"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <vulkan/vk_layer.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static PFN_vkGetInstanceProcAddr next_instance;
|
||||
static PFN_vkGetDeviceProcAddr next_device;
|
||||
static int gamescope;
|
||||
static PFN_vkVoidFunction VKAPI_CALL instance_proc(VkInstance, const char *);
|
||||
static PFN_vkVoidFunction VKAPI_CALL device_proc(VkDevice, const char *);
|
||||
|
||||
static VkResult VKAPI_CALL create_instance(const VkInstanceCreateInfo *info, const VkAllocationCallbacks *allocator, VkInstance *instance) {
|
||||
VkLayerInstanceCreateInfo *chain = (void *)info->pNext;
|
||||
while (chain && (chain->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO || chain->function != VK_LAYER_LINK_INFO)) chain = (void *)chain->pNext;
|
||||
if (!chain) return VK_ERROR_INITIALIZATION_FAILED;
|
||||
next_instance = chain->u.pLayerInfo->pfnNextGetInstanceProcAddr;
|
||||
gamescope = info->pApplicationInfo && info->pApplicationInfo->pApplicationName &&
|
||||
!strcmp(info->pApplicationInfo->pApplicationName, "gamescope");
|
||||
PFN_vkCreateInstance create = (void *)next_instance(0, "vkCreateInstance");
|
||||
chain->u.pLayerInfo = chain->u.pLayerInfo->pNext;
|
||||
return create(info, allocator, instance);
|
||||
}
|
||||
|
||||
static VkResult VKAPI_CALL create_device(VkPhysicalDevice physical, const VkDeviceCreateInfo *info, const VkAllocationCallbacks *allocator, VkDevice *device) {
|
||||
VkLayerDeviceCreateInfo *chain = (void *)info->pNext;
|
||||
while (chain && (chain->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO || chain->function != VK_LAYER_LINK_INFO)) chain = (void *)chain->pNext;
|
||||
if (!chain) return VK_ERROR_INITIALIZATION_FAILED;
|
||||
PFN_vkGetInstanceProcAddr get = chain->u.pLayerInfo->pfnNextGetInstanceProcAddr;
|
||||
next_device = chain->u.pLayerInfo->pfnNextGetDeviceProcAddr;
|
||||
PFN_vkCreateDevice create = (void *)get(0, "vkCreateDevice");
|
||||
chain->u.pLayerInfo = chain->u.pLayerInfo->pNext;
|
||||
return create(physical, info, allocator, device);
|
||||
}
|
||||
|
||||
static uint64_t fingerprint(const void *data, size_t size) {
|
||||
const unsigned char *bytes = data;
|
||||
uint64_t hash = 14695981039346656037ULL;
|
||||
while (size--) hash = (hash ^ *bytes++) * 1099511628211ULL;
|
||||
return hash;
|
||||
}
|
||||
|
||||
static VkResult VKAPI_CALL create_shader(VkDevice device, const VkShaderModuleCreateInfo *info, const VkAllocationCallbacks *allocator, VkShaderModule *module) {
|
||||
PFN_vkCreateShaderModule create = (void *)next_device(device, "vkCreateShaderModule");
|
||||
const char *directory = getenv("DROIDDECK_QUALCOMM_SHADERS");
|
||||
void *data = 0;
|
||||
VkShaderModuleCreateInfo replacement = *info;
|
||||
if (directory && gamescope) {
|
||||
char path[4096];
|
||||
snprintf(path, sizeof(path), "%s/%016llx.spv", directory, (unsigned long long)fingerprint(info->pCode, info->codeSize));
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (file) {
|
||||
fseek(file, 0, SEEK_END);
|
||||
long size = ftell(file);
|
||||
rewind(file);
|
||||
if (size > 20 && size < 4194304 && !(size & 3)) {
|
||||
data = malloc(size);
|
||||
if (data && fread(data, 1, size, file) == (size_t)size && *(const uint32_t *)data == 0x07230203) {
|
||||
replacement.codeSize = size;
|
||||
replacement.pCode = data;
|
||||
fprintf(stderr, "Qualcomm: substituting Gamescope shader %016llx\n", (unsigned long long)fingerprint(info->pCode, info->codeSize));
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
VkResult result = create(device, &replacement, allocator, module);
|
||||
free(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
static PFN_vkVoidFunction VKAPI_CALL device_proc(VkDevice device, const char *name) {
|
||||
if (!strcmp(name, "vkGetDeviceProcAddr")) return (void *)device_proc;
|
||||
if (!strcmp(name, "vkCreateShaderModule")) return (void *)create_shader;
|
||||
return next_device ? next_device(device, name) : 0;
|
||||
}
|
||||
|
||||
static PFN_vkVoidFunction VKAPI_CALL instance_proc(VkInstance instance, const char *name) {
|
||||
if (!strcmp(name, "vkGetInstanceProcAddr")) return (void *)instance_proc;
|
||||
if (!strcmp(name, "vkGetDeviceProcAddr")) return (void *)device_proc;
|
||||
if (!strcmp(name, "vkCreateInstance")) return (void *)create_instance;
|
||||
if (!strcmp(name, "vkCreateDevice")) return (void *)create_device;
|
||||
if (!strcmp(name, "vkCreateShaderModule")) return (void *)create_shader;
|
||||
return next_instance ? next_instance(instance, name) : 0;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface *interface) {
|
||||
if (interface->loaderLayerInterfaceVersion > 2) interface->loaderLayerInterfaceVersion = 2;
|
||||
interface->pfnGetInstanceProcAddr = instance_proc;
|
||||
interface->pfnGetDeviceProcAddr = device_proc;
|
||||
interface->pfnGetPhysicalDeviceProcAddr = 0;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
import zipfile
|
||||
|
||||
|
||||
SOURCE = Path(__file__).resolve().parents[1] / 'qualcomm/fetch_runtime.py'
|
||||
spec = importlib.util.spec_from_file_location('qualcomm_fetch', SOURCE)
|
||||
runtime = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(runtime)
|
||||
|
||||
|
||||
class QualcommRuntimeTest(unittest.TestCase):
|
||||
def lock(self, data):
|
||||
return dict(sha256=hashlib.sha256(data).hexdigest(), size=len(data), url='https://example.invalid/runtime')
|
||||
|
||||
def test_cached_payload_is_rechecked_before_it_is_used(self):
|
||||
data = b'approved bridge'
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
cache = Path(directory)
|
||||
lock = self.lock(data)
|
||||
target = cache / (lock['sha256'] + '.tzst')
|
||||
target.write_bytes(b'x' * len(data))
|
||||
with patch.object(runtime.urllib.request, 'urlopen', return_value=io.BytesIO(data)):
|
||||
self.assertEqual(runtime.fetch(cache, lock).read_bytes(), data)
|
||||
|
||||
def test_bad_download_never_becomes_the_cached_payload(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
cache = Path(directory)
|
||||
with patch.object(runtime.urllib.request, 'urlopen', return_value=io.BytesIO(b'corrupt')):
|
||||
with self.assertRaisesRegex(ValueError, 'SHA-256'):
|
||||
runtime.fetch(cache, self.lock(b'approved'))
|
||||
self.assertEqual(list(cache.iterdir()), [])
|
||||
|
||||
def test_only_the_pinned_gamescope_member_is_staged(self):
|
||||
bridge, binary = b'bridge', b'ARM64 gamescope'
|
||||
archive = io.BytesIO()
|
||||
with zipfile.ZipFile(archive, 'w') as package:
|
||||
package.writestr('assets/gamescope', binary)
|
||||
package.writestr('other-file', b'not staged')
|
||||
lock = self.lock(bridge)
|
||||
lock['gamescope'] = self.lock(archive.getvalue()) | dict(member='assets/gamescope', member_sha256=hashlib.sha256(binary).hexdigest())
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
cache, output = root / 'cache', root / 'output'
|
||||
cache.mkdir()
|
||||
for data, entry in [(bridge, lock), (archive.getvalue(), lock['gamescope'])]:
|
||||
(cache / (entry['sha256'] + '.tzst')).write_bytes(data)
|
||||
runtime.stage(output, cache, lock)
|
||||
self.assertEqual((output / 'gamescope').read_bytes(), binary)
|
||||
self.assertEqual(set(p.name for p in output.iterdir()), {'runtime.tzst', 'gamescope', 'runtime.json'})
|
||||
self.assertEqual(json.loads((output / 'runtime.json').read_text()), lock)
|
||||
lock['gamescope']['member_sha256'] = '0' * 64
|
||||
with self.assertRaisesRegex(ValueError, 'Gamescope'):
|
||||
runtime.stage(root / 'rejected', cache, lock)
|
||||
|
||||
def test_wsi_files_must_match_their_individual_pins(self):
|
||||
bridge, binary, wsi = b'bridge', b'gamescope', b'WSI library'
|
||||
def package(name, data):
|
||||
output = io.BytesIO()
|
||||
with zipfile.ZipFile(output, 'w') as archive:
|
||||
archive.writestr(name, data)
|
||||
return output.getvalue()
|
||||
gamescope_archive = package('gamescope', binary)
|
||||
wsi_archive = package('layer.so', wsi)
|
||||
lock = self.lock(bridge)
|
||||
lock['gamescope'] = self.lock(gamescope_archive) | dict(member='gamescope', member_sha256=hashlib.sha256(binary).hexdigest())
|
||||
lock['wsi'] = self.lock(wsi_archive) | dict(files=[dict(
|
||||
name='layer.so', path='wsi/layer.so', size=len(wsi), sha256='0' * 64)])
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
cache = root / 'cache'
|
||||
cache.mkdir()
|
||||
for data, entry in [(bridge, lock), (gamescope_archive, lock['gamescope']), (wsi_archive, lock['wsi'])]:
|
||||
(cache / (entry['sha256'] + '.tzst')).write_bytes(data)
|
||||
with self.assertRaisesRegex(ValueError, 'WSI member'):
|
||||
runtime.stage(root / 'bad-member', cache, lock)
|
||||
lock['wsi']['files'][0]['sha256'] = hashlib.sha256(wsi).hexdigest()
|
||||
lock['wsi']['files'][0]['path'] = '../outside.so'
|
||||
with self.assertRaisesRegex(ValueError, 'destination'):
|
||||
runtime.stage(root / 'bad-path', cache, lock)
|
||||
self.assertFalse((root / 'outside.so').exists())
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user