Support older Qualcomm drivers without DMA-BUF or maintenance5

(cherry picked from commit 10939e6bc31e50dcf18ce0fcad4b48d5874b4af6)
(cherry picked from commit 7873adcb23)
This commit is contained in:
K11MCH1 authored and The412Banner committed 2026-10-25 23:09:02 +00:00
1 parent 3dfe06b05a
commit cf0ba191da
23 files changed
+1110 -12

No files matched your search

+8 -1
View File
@@ -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()
@@ -289,6 +289,13 @@ android.sourceSets.main.assets.setSrcDirs([runtimeAssetsDir])
def stageQualcommRuntime = tasks.register('stageQualcommRuntime', Exec) {
workingDir rootProject.projectDir
inputs.files(qualcommSources)
if (qualcommRuntime.gamescope.build) {
inputs.file(rootProject.file(qualcommRuntime.gamescope.build.binary))
inputs.file(rootProject.file(qualcommRuntime.gamescope.build.source))
}
if (qualcommRuntime.mesa) {
inputs.dir(rootProject.file(qualcommRuntime.mesa.directory))
}
inputs.property('ndkVersion', android.ndkVersion)
outputs.dir(layout.buildDirectory.dir('qualcommAssets'))
commandLine pythonExecutable, 'tools/qualcomm/fetch_runtime.py',
@@ -13,7 +13,10 @@ object QualcommSystemDriver {
const val ID = "system-qualcomm"
const val HAL = "/vendor/lib64/hw/vulkan.adreno.so"
private val required = listOf("lib/libhybris-vulkan-icd.so.0", "lib/libhybris-common.so.1", "lib/libhybris/linker/q.so",
"bin/gamescope", "shaders/qualcomm-shaders.so", "wsi/libVkLayer_window_system_integration.so")
"bin/gamescope", "shaders/qualcomm-shaders.so", "wsi/libVkLayer_window_system_integration.so",
"mesa/libgallium-26.2.3.so", "mesa/dri/zink_dri.so", "mesa/libGLX_droiddeck_qualcomm.so.0",
"mesa/libEGL_droiddeck_qualcomm.so.0", "mesa/libgbm.so.1", "mesa/gbm/dri_gbm.so",
"mesa/egl-vendor.json", "mesa/qualcomm-gl.sh")
@JvmStatic fun detected(): Boolean {
if (Build.VERSION.SDK_INT < 29 || !File(HAL).canRead()) return false
@@ -36,7 +39,7 @@ object QualcommSystemDriver {
val vk = VulkanInfo.query()
val version = vk["driver_version_raw"]?.toULongOrNull()?.let { "${(it shr 22) and 0x7fu}.${(it shr 12) and 0x3ffu}.${it and 0xfffu}" }
?: vk["driver_version"] ?: "unknown version"
return "System Qualcomm · ${vk["device"] ?: "Adreno"} · $version (experimental)"
return "System Qualcomm · ${vk["device"] ?: "Adreno"} · $version (experimental)"
}
@JvmStatic @Synchronized fun prepare(context: Context): File {
@@ -55,6 +58,7 @@ object QualcommSystemDriver {
copyAssets(context, "qualcomm/wsi", File(staging, "wsi"))
copyAssets(context, "qualcomm/shaders", File(staging, "shaders"))
copyAssets(context, "qualcomm/licenses", File(staging, "licenses"))
copyAssets(context, "qualcomm/mesa", File(staging, "mesa"))
check(required.all { File(staging, it).isFile }) { "The Qualcomm Vulkan bridge is incomplete" }
File(staging, ".complete").writeText(sha)
FileUtils.delete(dir)
+6 -1
View File
@@ -27,7 +27,12 @@ if [ "${DROIDDECK_SYSTEM_QUALCOMM:-0}" = 1 ]; then
export VK_LAYER_PATH="$DROIDDECK_QUALCOMM_ROOT/shaders"
export VK_INSTANCE_LAYERS=VK_LAYER_DROIDDECK_qualcomm_shaders
child_env+=("VK_LAYER_PATH=$DROIDDECK_QUALCOMM_ROOT/wsi:$DROIDDECK_QUALCOMM_ROOT/lib"
VK_INSTANCE_LAYERS=VK_LAYER_window_system_integration:VK_LAYER_HYBRIS_compat WSI_X11_FORCE_SHM=1 DISABLE_GAMESCOPE_WSI=1)
VK_INSTANCE_LAYERS=VK_LAYER_window_system_integration:VK_LAYER_HYBRIS_compat WSI_X11_FORCE_SHM=1 DISABLE_GAMESCOPE_WSI=1
"LD_LIBRARY_PATH=$DROIDDECK_QUALCOMM_ROOT/mesa${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
"LIBGL_DRIVERS_PATH=$DROIDDECK_QUALCOMM_ROOT/mesa/dri"
"GBM_BACKENDS_PATH=$DROIDDECK_QUALCOMM_ROOT/mesa/gbm"
__GLX_VENDOR_LIBRARY_NAME=droiddeck_qualcomm
"__EGL_VENDOR_LIBRARY_FILENAMES=$DROIDDECK_QUALCOMM_ROOT/mesa/egl-vendor.json")
fi
"${BL_GAMESCOPE_BINARY:-gamescope}" "${args[@]}" -- /usr/bin/env "${child_env[@]}" /usr/local/bin/droiddeck-gpu-window "$@"
rc=$?
+1
View File
@@ -4,6 +4,7 @@ if [ "${DROIDDECK_SYSTEM_QUALCOMM:-0}" = 1 ]; then
export VK_LAYER_PATH="$DROIDDECK_QUALCOMM_ROOT/wsi:$DROIDDECK_QUALCOMM_ROOT/lib"
export VK_INSTANCE_LAYERS=VK_LAYER_window_system_integration:VK_LAYER_HYBRIS_compat
export WSI_X11_FORCE_SHM=1 DISABLE_GAMESCOPE_WSI=1
. "$DROIDDECK_QUALCOMM_ROOT/mesa/qualcomm-gl.sh"
fi
starts=/tmp/droiddeck-mangoapp-starts
now=$(date +%s)
+84
View File
@@ -0,0 +1,84 @@
"""Build the private Qualcomm Gamescope on ARM64 Linux and retain its source."""
import argparse
import gzip
import hashlib
import json
from pathlib import Path
import shutil
import subprocess
import tarfile
ROOT = Path(__file__).resolve().parents[2]
HERE = Path(__file__).resolve().parent
REVISION = '8f212644c46460549035971ab914421180f61a7c'
OPTIONS = ['-Dbuildtype=release', '-Denable_openvr_support=false',
'-Denable_gamescope_wsi_layer=false', '-Denable_tests=false',
'-Ddrm_backend=enabled', '-Dsdl2_backend=enabled', '-Dpipewire=disabled',
'-Davif_screenshots=disabled', '-Dinput_emulation=disabled', '-Dbenchmark=disabled']
def run(args, cwd=None):
subprocess.run([str(arg) for arg in args], cwd=cwd, check=True)
def patches():
base = ROOT / 'tools/gamescope/patches'
extra = HERE / 'gamescope-patches'
return sorted([*base.glob('*.patch'), *extra.glob('*.patch')], key=lambda p: p.name) + [
HERE / 'constant-yuv-samplers.patch', HERE / 'gamescope-no-dmabuf.patch']
def prepare(source):
if source.exists():
raise ValueError('Source directory already exists; use a new directory or --prepared-source')
source.mkdir(parents=True)
run(['git', 'init', source])
run(['git', 'remote', 'add', 'origin', 'https://github.com/ValveSoftware/gamescope.git'], source)
run(['git', 'fetch', '--depth', '1', 'origin', REVISION], source)
run(['git', 'checkout', '--detach', 'FETCH_HEAD'], source)
run(['git', 'submodule', 'update', '--init', '--depth', '1', 'src/reshade',
'subprojects/libliftoff', 'subprojects/vkroots', 'subprojects/wlroots',
'thirdparty/SPIRV-Headers'], source)
for patch in patches():
run(['git', 'apply', '--ignore-whitespace', patch], source)
def archive_source(source, destination):
with destination.open('wb') as raw, gzip.GzipFile(filename='', mode='wb', fileobj=raw, mtime=0) as compressed:
with tarfile.open(fileobj=compressed, mode='w', format=tarfile.PAX_FORMAT) as package:
for path in sorted(source.rglob('*')):
relative = path.relative_to(source)
if any(part in {'.git', 'build', '__pycache__'} for part in relative.parts) or not path.is_file():
continue
info = package.gettarinfo(str(path), arcname='gamescope/' + relative.as_posix())
info.uid = info.gid = info.mtime = 0
info.uname = info.gname = ''
info.mode = 0o755 if path.suffix == '.sh' else 0o644
info.pax_headers = {}
with path.open('rb') as data:
package.addfile(info, data)
def package(source, output, strip_tool='strip'):
output.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source / 'build/src/gamescope', output / 'gamescope')
run([strip_tool, '--strip-unneeded', output / 'gamescope'])
archive_source(source, output / 'source.tar.gz')
manifest = {'source_commit': REVISION, 'options': OPTIONS,
'patches': {str(p.relative_to(ROOT)).replace('\\', '/'): hashlib.sha256(p.read_bytes()).hexdigest() for p in patches()},
'files': {name: {'sha256': hashlib.sha256((output / name).read_bytes()).hexdigest(),
'size': (output / name).stat().st_size} for name in ['gamescope', 'source.tar.gz']}}
(output / 'build.json').write_text(json.dumps(manifest, indent=2) + '\n')
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--source', type=Path, default=ROOT / 'build/qualcomm-gamescope/source')
parser.add_argument('--output', type=Path, default=ROOT / 'build/qualcomm-gamescope')
parser.add_argument('--prepared-source', action='store_true')
parser.add_argument('--prepare-only', action='store_true')
parser.add_argument('--package-only', action='store_true')
parser.add_argument('--strip', default='strip')
parser.add_argument('--jobs', type=int, default=4)
args = parser.parse_args()
if not args.prepared_source and not args.package_only:
prepare(args.source)
if not args.prepare_only:
if not args.package_only:
run(['meson', 'setup', 'build', *OPTIONS], args.source)
run(['ninja', '-C', 'build', '-j' + str(args.jobs), 'src/gamescope'], args.source)
package(args.source, args.output, args.strip)
+76
View File
@@ -0,0 +1,76 @@
"""Build private ARM64 Zink libraries with the pre-maintenance5 fallback."""
import argparse
import hashlib
import json
from pathlib import Path
import shutil
import subprocess
ROOT = Path(__file__).resolve().parents[2]
HERE = Path(__file__).resolve().parent
REVISION = '31e9a6b2e95e30d84bf3177d1f497d063e59b6b2'
OPTIONS = ['-Dbuildtype=release', '-Dgallium-drivers=zink', '-Dvulkan-drivers=[]',
'-Dllvm=disabled', '-Dplatforms=x11,wayland', '-Dglvnd=enabled',
'-Dglvnd-vendor-name=droiddeck_qualcomm', '-Dgallium-va=disabled',
'-Dvideo-codecs=[]', '-Dbuild-tests=false', '-Dtools=[]',
'-Dglx=dri', '-Degl=enabled', '-Dgbm=enabled']
LIBRARIES = {
'libgallium-26.2.3.so': 'src/gallium/targets/dri/libgallium-26.2.3.so',
'dri/zink_dri.so': 'src/gallium/targets/dril/libdril_dri.so',
'libGLX_droiddeck_qualcomm.so.0': 'src/glx/libGLX_droiddeck_qualcomm.so.0.0.0',
'libEGL_droiddeck_qualcomm.so.0': 'src/egl/libEGL_droiddeck_qualcomm.so.0.0.0',
'libgbm.so.1': 'src/gbm/libgbm.so.1.0.0',
'gbm/dri_gbm.so': 'src/gbm/backends/dri/dri_gbm.so',
}
def run(args, cwd=None):
subprocess.run([str(arg) for arg in args], cwd=cwd, check=True)
def prepare(source):
if source.exists():
raise ValueError('Source directory already exists; use a new directory or --prepared-source')
source.mkdir(parents=True)
run(['git', 'init', source])
run(['git', 'remote', 'add', 'origin', 'https://gitlab.freedesktop.org/mesa/mesa.git'], source)
run(['git', 'fetch', '--depth', '1', 'origin', REVISION], source)
run(['git', 'checkout', '--detach', 'FETCH_HEAD'], source)
run(['git', 'apply', HERE / 'zink-optional-maintenance5.patch'], source)
def package(source, output, strip_tool):
output.mkdir(parents=True, exist_ok=True)
for name, relative in LIBRARIES.items():
destination = output / name
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source / 'build' / relative, destination)
run([strip_tool, '--strip-unneeded', destination])
(output / 'egl-vendor.json').write_text(json.dumps({
'file_format_version': '1.0.0', 'ICD': {'library_path': 'libEGL_droiddeck_qualcomm.so.0'}}) + '\n')
shutil.copyfile(HERE / 'qualcomm-gl.sh', output / 'qualcomm-gl.sh')
shutil.copytree(source / 'licenses', output / 'licenses', dirs_exist_ok=True)
shutil.copyfile(source / 'docs/license.rst', output / 'licenses/Mesa-license.rst')
shutil.copyfile(HERE / 'zink-optional-maintenance5.patch', output / 'licenses/zink-optional-maintenance5.patch')
(output / 'licenses/source.json').write_text(json.dumps({
'url': 'https://gitlab.freedesktop.org/mesa/mesa/-/tree/' + REVISION,
'source_commit': REVISION, 'options': OPTIONS,
'patch_sha256': hashlib.sha256((HERE / 'zink-optional-maintenance5.patch').read_bytes()).hexdigest()}, indent=2) + '\n')
files = [{'path': p.relative_to(output).as_posix(), 'sha256': hashlib.sha256(p.read_bytes()).hexdigest(),
'size': p.stat().st_size} for p in sorted(output.rglob('*')) if p.is_file() and p.name != 'build.json']
(output / 'build.json').write_text(json.dumps({'source_commit': REVISION, 'files': files}, indent=2) + '\n')
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--source', type=Path, default=ROOT / 'build/qualcomm-zink/source')
parser.add_argument('--output', type=Path, default=ROOT / 'build/qualcomm-zink/runtime')
parser.add_argument('--prepared-source', action='store_true')
parser.add_argument('--prepare-only', action='store_true')
parser.add_argument('--package-only', action='store_true')
parser.add_argument('--strip', default='strip')
parser.add_argument('--jobs', type=int, default=4)
args = parser.parse_args()
if not args.prepared_source and not args.package_only:
prepare(args.source)
if not args.prepare_only:
if not args.package_only:
run(['meson', 'setup', 'build', *OPTIONS], args.source)
run(['ninja', '-C', 'build', '-j' + str(args.jobs)], args.source)
package(args.source, args.output, args.strip)
+56
View File
@@ -0,0 +1,56 @@
#include <EGL/egl.h>
#include <GL/gl.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint major, minor, count;
const EGLint attributes[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE };
const EGLint size[] = { EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE };
EGLConfig config;
if (!eglInitialize(display, &major, &minor) || !eglBindAPI(EGL_OPENGL_API) ||
!eglChooseConfig(display, attributes, &config, 1, &count) || count != 1) {
fprintf(stderr, "EGL setup failed: 0x%x\n", eglGetError());
return 1;
}
EGLSurface surface = eglCreatePbufferSurface(display, config, size);
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, NULL);
if (surface == EGL_NO_SURFACE || context == EGL_NO_CONTEXT ||
!eglMakeCurrent(display, surface, surface, context)) {
fprintf(stderr, "EGL context failed: 0x%x\n", eglGetError());
return 1;
}
const char *renderer = (const char *)glGetString(GL_RENDERER);
printf("Renderer: %s\nOpenGL: %s\n", renderer, glGetString(GL_VERSION));
if (!renderer || !strstr(renderer, "zink") || !strstr(renderer, "Adreno") ||
strstr(renderer, "llvmpipe")) return 1;
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, 16, 16);
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glVertex2f(-1.0f, -1.0f);
glVertex2f(3.0f, -1.0f);
glVertex2f(-1.0f, 3.0f);
glEnd();
unsigned char pixels[16 * 16 * 4];
memset(pixels, 0, sizeof(pixels));
glReadPixels(0, 0, 16, 16, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
if (glGetError() != GL_NO_ERROR) return 1;
for (unsigned i = 0; i < sizeof(pixels); i += 4) {
if (pixels[i] != 255 || pixels[i + 1] || pixels[i + 2] || pixels[i + 3] != 255) {
fprintf(stderr, "Readback mismatch at pixel %u\n", i / 4);
return 1;
}
}
puts("GPU triangle/readback: all 256 pixels passed");
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(display, context);
eglDestroySurface(display, surface);
eglTerminate(display);
return 0;
}
+39 -2
View File
@@ -32,11 +32,48 @@ def stage(output, cache, lock, ndk=None):
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'])
built = gamescope.get('build')
if built:
root = Path(__file__).resolve().parents[2]
def build_input(name):
target = (root / built[name]).resolve()
if not target.is_relative_to(root / 'build'):
raise ValueError('Invalid Qualcomm Gamescope build path')
if not target.is_file():
raise FileNotFoundError('Build private Gamescope first with tools/qualcomm/build_gamescope.py: ' + str(target))
return target.read_bytes()
binary = build_input('binary')
source = build_input('source')
if len(source) != built['source_size'] or hashlib.sha256(source).hexdigest() != built['source_sha256']:
raise ValueError('Qualcomm Gamescope source failed SHA-256 verification')
licenses = output / 'licenses'
licenses.mkdir(exist_ok=True)
(licenses / 'gamescope-patched-source.tar.gz').unlink(missing_ok=True)
(licenses / 'gamescope-patched-source.tar.gzip').write_bytes(source)
else:
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 'mesa' in lock:
root = Path(__file__).resolve().parents[2]
directory = (root / lock['mesa']['directory']).resolve()
if not directory.is_relative_to(root / 'build'):
raise ValueError('Invalid Qualcomm Mesa build path')
for entry in lock['mesa']['files']:
relative = Path(entry['path'])
if relative.is_absolute() or '..' in relative.parts:
raise ValueError('Invalid Qualcomm Mesa destination')
origin = directory / relative
if not origin.is_file():
raise FileNotFoundError('Build private Zink first with tools/qualcomm/build_zink.py: ' + str(origin))
data = origin.read_bytes()
if len(data) != entry['size'] or hashlib.sha256(data).hexdigest() != entry['sha256']:
raise ValueError('Qualcomm Mesa member failed SHA-256 verification: ' + entry['path'])
target = output / 'mesa' / relative
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(data)
if 'wsi' in lock:
with zipfile.ZipFile(fetch(cache, lock['wsi'])) as package:
for entry in lock['wsi']['files']:
+124
View File
@@ -0,0 +1,124 @@
--- a/src/rendervulkan.hpp
+++ b/src/rendervulkan.hpp
@@ -866,6 +866,7 @@
inline VkPipelineLayout pipelineLayout() {return m_pipelineLayout;}
inline int drmRenderFd() {return m_drmRendererFd;}
inline bool supportsModifiers() {return m_bSupportsModifiers;}
+ inline bool supportsDmaBuf() {return m_bSupportsDmaBuf;}
inline uint32_t vendorID() const {return m_uVendorID;}
inline const VkPhysicalDeviceMemoryProperties &memoryProperties() const {return m_memoryProperties;}
inline bool supportsMutableSwapchain() {return m_bSupportsMutableSwapchain;}
@@ -938,6 +939,7 @@
uint32_t m_uVendorID = 0;
bool m_bHasDrmPrimaryDevId = false;
bool m_bSupportsModifiers = false;
+ bool m_bSupportsDmaBuf = false;
bool m_bSupportsMutableSwapchain = false;
bool m_bSupportsPresentWait = false;
bool m_bInitialized = false;
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -452,10 +452,16 @@
bool hasDrmProps = vulkan_has_drm_props();
bool supportsForeignQueue = false;
+ bool supportsMemoryFd = false;
+ bool supportsDmaBuf = false;
bool supportsHDRMetadata = false;
bool presentIdExtension = false;
bool presentWaitExtension = false;
for (const auto& ext : m_supportedExts) {
+ if ( strcmp(ext.extensionName, VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME) == 0 )
+ supportsMemoryFd = true;
+ if ( strcmp(ext.extensionName, VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME) == 0 )
+ supportsDmaBuf = true;
if ( strcmp(ext.extensionName, VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME) == 0 )
m_bSupportsModifiers = true;
@@ -471,6 +477,18 @@
if ( strcmp(ext.extensionName, VK_EXT_HDR_METADATA_EXTENSION_NAME) == 0 )
supportsHDRMetadata = true;
+ }
+
+ m_bSupportsDmaBuf = supportsMemoryFd && supportsDmaBuf;
+ if ( !m_bSupportsDmaBuf )
+ {
+ if ( !GetBackend()->UsesVulkanSwapchain() )
+ {
+ vk_log.errorf( "This backend requires DMA-BUF import/export support" );
+ return false;
+ }
+ m_bSupportsModifiers = false;
+ vk_log.infof( "DMA-BUF unavailable; using SHM inputs with Vulkan swapchain rendering" );
}
vk_log.infof( "physical device %s DRM format modifiers", m_bSupportsModifiers ? "supports" : "does not support" );
@@ -617,8 +635,11 @@
enabledExtensions.push_back( VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME );
}
- enabledExtensions.push_back( VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME );
- enabledExtensions.push_back( VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME );
+ if ( m_bSupportsDmaBuf )
+ {
+ enabledExtensions.push_back( VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME );
+ enabledExtensions.push_back( VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME );
+ }
enabledExtensions.push_back( VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME );
@@ -2144,6 +2165,11 @@
{
flags.bExportable = true;
}
+ if ( ( pDMA || flags.bExportable ) && !g_device.supportsDmaBuf() )
+ {
+ vk_log.errorf( "DMA-BUF texture requested without import/export support" );
+ return false;
+ }
if ( flags.bTransferSrc == true )
{
@@ -2983,6 +3009,8 @@
}
wlr_drm_format_set_add( &sampledShmFormats, drmFormat, DRM_FORMAT_MOD_LINEAR );
+ if ( !g_device.supportsDmaBuf() )
+ return true;
if ( g_device.supportsModifiers() )
{
@@ -4836,7 +4864,7 @@
{
if (buffer_caps & WLR_BUFFER_CAP_DMABUF)
{
- return &sampledDRMFormats;
+ return g_device.supportsDmaBuf() ? &sampledDRMFormats : nullptr;
}
else if (buffer_caps & WLR_BUFFER_CAP_DATA_PTR)
{
@@ -4878,7 +4906,10 @@
struct wlr_renderer *vulkan_renderer_create( void )
{
VulkanRenderer_t *renderer = new VulkanRenderer_t();
- wlr_renderer_init(&renderer->base, &renderer_impl, WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_DATA_PTR);
+ uint32_t caps = WLR_BUFFER_CAP_DATA_PTR;
+ if ( g_device.supportsDmaBuf() )
+ caps |= WLR_BUFFER_CAP_DMABUF;
+ wlr_renderer_init(&renderer->base, &renderer_impl, caps);
return &renderer->base;
}
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -2243,7 +2243,8 @@
// Have to make this old ancient thing for compat with older XWayland.
// Someday, he will be purged.
- wlr_drm_create(wlserver.display, wlserver.wlr.renderer);
+ if ( wlr_renderer_get_texture_formats( wlserver.wlr.renderer, WLR_BUFFER_CAP_DMABUF ) != nullptr )
+ wlr_drm_create(wlserver.display, wlserver.wlr.renderer);
if ( GetBackend()->SupportsExplicitSync() )
{
@@ -0,0 +1,15 @@
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -203,6 +203,12 @@
}
void xwayland_surface_commit(struct wlr_surface *wlr_surface) {
+ // Cursor surfaces never acquire an X11/xdg window association. Keeping their
+ // commits in g_PendingCommits pins every old SHM buffer until the cursor
+ // surface is destroyed. X11 cursor images are handled by MouseCursor.
+ if ( wlr_surface->role && strcmp( wlr_surface->role->name, "wl_pointer-cursor" ) == 0 )
+ return;
+
wlr_surface->current.committed = 0;
wlserver_x11_surface_info *wlserver_x11_surface_info = get_wl_surface_info(wlr_surface)->x11_surface;
@@ -0,0 +1,15 @@
diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -996,8 +996,9 @@
SHADER(BLUR, cs_composite_blur);
SHADER(BLUR_COND, cs_composite_blur_cond);
SHADER(BLUR_FIRST_PASS, cs_gaussian_blur_horizontal);
- // The one pixel layout is only measured on Adreno, every other vendor keeps the quad swizzle.
- if (m_uVendorID == 0x5143) /* Qualcomm */
+ // PowerVR rejects the quad-swizzled RCAS for 3+ layers with YCbCr mask 2.
+ // The existing one-pixel shader compiles those variants and covers the same 16x16 tile.
+ if (m_uVendorID == 0x5143 || m_uVendorID == 0x1010) /* Qualcomm or Imagination */
{
SHADER(RCAS, cs_composite_rcas_1px);
}
@@ -0,0 +1,156 @@
diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -13,6 +13,7 @@
#include <dlfcn.h>
#include "vulkan_include.h"
#include "nested_swapchain.hpp"
+#include "dmabuf_import.hpp"
#include "Utils/Algorithm.h"
#if defined(__linux__)
@@ -2229,8 +2230,27 @@
return false;
}
- if ( res == VK_SUCCESS &&
- ( externalImageProperties.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT ) )
+ bool importable = res == VK_SUCCESS &&
+ ( externalImageProperties.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT );
+ if ( gamescope::RetryPowerVRLinearQuery( g_device.vendorID(), imageInfo.format,
+ pDMA->modifier, pDMA->n_planes, imageInfo.flags, res ) )
+ {
+ // PowerVR's mutable-format query rejects this linear import even though
+ // explicit-layout imports and UNORM/sRGB GPU sampling work. Query the base
+ // format without changing the image we actually create or its supplied stride.
+ VkImageCreateInfo baseFormat = imageInfo;
+ baseFormat.flags &= ~VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+ baseFormat.pNext = nullptr;
+ res = getModifierProps( &baseFormat, pDMA->modifier, &externalImageProperties );
+ importable = res == VK_SUCCESS &&
+ ( externalImageProperties.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT );
+ }
+ if ( !importable && g_device.vendorID() == 0x1010 )
+ {
+ vk_log.errorf( "PowerVR DMA-BUF modifier is not importable; refusing a different tiling" );
+ return false;
+ }
+ if ( importable )
{
modifierInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT,
@@ -2348,6 +2368,22 @@
m_format = imageInfo.format;
+ VkMemoryFdPropertiesKHR fdProperties = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR,
+ };
+ if ( pDMA )
+ {
+ if ( !g_device.vk.GetMemoryFdPropertiesKHR )
+ return false;
+ res = g_device.vk.GetMemoryFdPropertiesKHR( g_device.device(),
+ VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, pDMA->fd[0], &fdProperties );
+ if ( res != VK_SUCCESS )
+ {
+ vk_errorf( res, "vkGetMemoryFdPropertiesKHR failed" );
+ return false;
+ }
+ }
+
res = g_device.vk.CreateImage(g_device.device(), &imageInfo, nullptr, &m_vkImage);
if (res != VK_SUCCESS) {
vk_errorf( res, "vkCreateImage failed" );
@@ -2357,10 +2393,22 @@
VkMemoryRequirements memRequirements;
g_device.vk.GetImageMemoryRequirements(g_device.device(), m_vkImage, &memRequirements);
+ uint32_t memoryType = pDMA
+ ? gamescope::DmabufMemoryType( g_device.memoryProperties(), memRequirements.memoryTypeBits,
+ fdProperties.memoryTypeBits, properties )
+ : uint32_t( g_device.findMemoryType( properties, memRequirements.memoryTypeBits ) );
+ if ( memoryType == UINT32_MAX )
+ {
+ vk_log.errorf( "No compatible image memory type (image=0x%x, fd=0x%x)",
+ memRequirements.memoryTypeBits, fdProperties.memoryTypeBits );
+ g_device.vk.DestroyImage( g_device.device(), m_vkImage, nullptr );
+ m_vkImage = VK_NULL_HANDLE;
+ return false;
+ }
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memRequirements.size,
- .memoryTypeIndex = uint32_t(g_device.findMemoryType(properties, memRequirements.memoryTypeBits)),
+ .memoryTypeIndex = memoryType,
};
m_size = allocInfo.allocationSize;
@@ -2453,6 +2501,13 @@
if ( res != VK_SUCCESS )
{
vk_errorf( res, "vkBindImageMemory failed" );
+ if ( pDMA )
+ vk_log.errorf( "DMA-BUF: %ux%u format=%u modifier=0x%" PRIx64
+ " planes=%d stride=%u offset=%u tiling=%u allocation=%" PRIu64
+ " memoryType=%u imageBits=0x%x fdBits=0x%x", width, height, imageInfo.format,
+ pDMA->modifier, pDMA->n_planes, pDMA->stride[0], pDMA->offset[0], imageInfo.tiling,
+ uint64_t( allocInfo.allocationSize ), allocInfo.memoryTypeIndex,
+ memRequirements.memoryTypeBits, fdProperties.memoryTypeBits );
return false;
}
diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp
--- a/src/rendervulkan.hpp
+++ b/src/rendervulkan.hpp
@@ -791,6 +791,7 @@
VK_FUNC(GetImageMemoryRequirements) \
VK_FUNC(GetImageSubresourceLayout) \
VK_FUNC(GetMemoryFdKHR) \
+ VK_FUNC(GetMemoryFdPropertiesKHR) \
VK_FUNC(GetQueryPoolResults) \
VK_FUNC(GetSemaphoreCounterValue) \
VK_FUNC(GetSwapchainImagesKHR) \
@@ -865,6 +866,8 @@
inline VkPipelineLayout pipelineLayout() {return m_pipelineLayout;}
inline int drmRenderFd() {return m_drmRendererFd;}
inline bool supportsModifiers() {return m_bSupportsModifiers;}
+ inline uint32_t vendorID() const {return m_uVendorID;}
+ inline const VkPhysicalDeviceMemoryProperties &memoryProperties() const {return m_memoryProperties;}
inline bool supportsMutableSwapchain() {return m_bSupportsMutableSwapchain;}
inline bool supportsPresentWait() {return m_bSupportsPresentWait;}
inline bool hasDrmPrimaryDevId() {return m_bHasDrmPrimaryDevId;}
diff --git a/src/dmabuf_import.hpp b/src/dmabuf_import.hpp
new file mode 100644
--- /dev/null
+++ b/src/dmabuf_import.hpp
@@ -0,0 +1,29 @@
+#pragma once
+#include <vulkan/vulkan_core.h>
+
+namespace gamescope
+{
+inline bool RetryPowerVRLinearQuery( uint32_t vendor, VkFormat format, uint64_t modifier,
+ uint32_t planes, VkImageCreateFlags flags, VkResult result )
+{
+ return vendor == 0x1010 && modifier == 0 && planes == 1 &&
+ flags == VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT && result == VK_ERROR_FORMAT_NOT_SUPPORTED &&
+ ( format == VK_FORMAT_R8G8B8A8_UNORM || format == VK_FORMAT_B8G8R8A8_UNORM );
+}
+
+inline uint32_t DmabufMemoryType( const VkPhysicalDeviceMemoryProperties &memory,
+ uint32_t imageBits, uint32_t fdBits, VkMemoryPropertyFlags preferred )
+{
+ uint32_t fallback = UINT32_MAX;
+ const uint32_t compatible = imageBits & fdBits;
+ for ( uint32_t i = 0; i < memory.memoryTypeCount; ++i )
+ {
+ if ( !( compatible & ( 1u << i ) ) ) continue;
+ if ( fallback == UINT32_MAX ) fallback = i;
+ if ( ( memory.memoryTypes[i].propertyFlags & preferred ) == preferred ) return i;
+ }
+ // Device-local is a preference for an existing imported buffer. CPU mapping
+ // properties are requirements and must never be silently relaxed.
+ return preferred == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ? fallback : UINT32_MAX;
+}
+}
@@ -0,0 +1,61 @@
diff --git a/src/nested_surface_format.hpp b/src/nested_surface_format.hpp
new file mode 100644
--- /dev/null
+++ b/src/nested_surface_format.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <vulkan/vulkan_core.h>
+
+namespace gamescope
+{
+// The SDR composition shader declares rgba8, which requires an R8G8B8A8_UNORM
+// storage view. Binding a BGRA view is a format mismatch; a same-format copy
+// cannot correct it. Keep the workaround scoped to the PowerVR SDR copy path
+// where the channel reversal was reproduced, not game-facing texture formats.
+// On success, formatCount means the existing selector should choose the format.
+// On failure, no matching RGBA SDR surface was advertised for this scoped path.
+inline bool ChooseNestedSDRCopySurfaceFormat( uint32_t vendorId, bool copy, bool hdr,
+ const VkSurfaceFormatKHR *formats, uint32_t formatCount, uint32_t &surfaceFormat )
+{
+ surfaceFormat = formatCount;
+ if ( vendorId != 0x1010 || !copy || hdr )
+ return true;
+
+ for ( uint32_t i = 0; i < formatCount; ++i )
+ {
+ if ( formats[i].format == VK_FORMAT_R8G8B8A8_UNORM &&
+ formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR )
+ {
+ surfaceFormat = i;
+ return true;
+ }
+ }
+ return false;
+}
+}
diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -14,6 +14,7 @@
#include "vulkan_include.h"
#include "nested_swapchain.hpp"
#include "dmabuf_import.hpp"
+#include "nested_surface_format.hpp"
#include "Utils/Algorithm.h"
#if defined(__linux__)
@@ -3330,6 +3331,13 @@
uint32_t surfaceFormat = formatCount;
VkColorSpaceKHR preferredColorSpace = g_bOutputHDREnabled ? VK_COLOR_SPACE_HDR10_ST2084_EXT : VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
+ if ( !gamescope::ChooseNestedSDRCopySurfaceFormat( g_device.vendorID(), config.copy,
+ g_bOutputHDREnabled, pOutput->surfaceFormats.data(), formatCount, surfaceFormat ) )
+ {
+ vk_log.errorf( "PowerVR copy composition requires an RGBA SDR surface" );
+ return false;
+ }
+
if ( surfaceFormat == formatCount )
{
for ( surfaceFormat = 0; surfaceFormat < formatCount; surfaceFormat++ )
@@ -0,0 +1,23 @@
diff --git a/src/Backends/SDLBackend.cpp b/src/Backends/SDLBackend.cpp
--- a/src/Backends/SDLBackend.cpp
+++ b/src/Backends/SDLBackend.cpp
@@ -351,14 +351,15 @@ namespace gamescope
std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false );
if ( !oCompositeResult )
return -EINVAL;
+ // Present has no render-finished semaphore, so wait before the native consumer can read
+ // the old contents. This serializes composition and present; a future pipelined path must
+ // signal a binary render-finished semaphore and make vkQueuePresentKHR wait on it.
+ vulkan_wait( *oCompositeResult, true );
+
vulkan_present_to_window();
// TODO: Hook up PresentationFeedback.
-
- // Wait for the composite result on our side *after* we
- // commit the buffer to the compositor to avoid a bubble.
- vulkan_wait( *oCompositeResult, true );
GetVBlankTimer().UpdateWasCompositing( true );
GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime );
@@ -0,0 +1,176 @@
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -95,6 +95,7 @@
#include "Script/Script.h"
#include "refresh_rate.h"
#include "commit.h"
+#include "buffer_commit_sync.hpp"
#include "reshade_effect_manager.hpp"
#include "BufferMemo.h"
#include "vrclient_detect.h"
@@ -8451,7 +8452,6 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re
return;
}
- int fence = -1;
global_focus_t *pCurrentFocus = GetCurrentFocus();
static bool bMangoappSocketDisable = env_to_bool( getenv( "GAMESCOPE_MANGOAPP_SOCKET_DISABLE" ));
@@ -8492,8 +8492,6 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re
bool bValidPreemptiveScale = reslistentry.pAcquirePoint && pUpscaleFocus && cv_upscale_preemptive;
bool bPreemptiveUpscale = bValidPreemptiveScale && newCommit->ShouldPreemptivelyUpscale( pUpscaleFocus->eUpscaleFilter, pUpscaleFocus->eUpscaleScaler );
- bool bKnownReady = false;
-
std::pair<int32_t, bool> eventFd = gamescope::CAcquireTimelinePoint::k_InvalidEvent;
if ( bPreemptiveUpscale )
@@ -8589,31 +8587,44 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re
pBackendFb->SetBuffer( buf );
}
- if ( eventFd != gamescope::CAcquireTimelinePoint::k_InvalidEvent )
- {
- fence = eventFd.first;
- bKnownReady = eventFd.second;
- }
- else
- {
- struct wlr_dmabuf_attributes dmabuf = {0};
- if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) )
- {
- fence = dup( dmabuf.fd[0] );
- }
- else
+ gamescope::BufferCommitSync commitSync = gamescope::SelectBufferCommitSync(
+ eventFd != gamescope::CAcquireTimelinePoint::k_InvalidEvent,
+ eventFd.first,
+ eventFd.second,
+ [&]() -> std::optional<int> {
+ struct wlr_dmabuf_attributes dmabuf = {0};
+ if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) )
+ {
+ int fd = dup( dmabuf.fd[0] );
+ if ( fd < 0 )
+ xwm_log.errorf( "Failed to duplicate DMA-BUF fence: %d", errno );
+ return fd;
+ }
+ return std::nullopt;
+ } );
+ if ( commitSync.source == gamescope::BufferCommitSyncSource::SynchronousUpload )
+ {
+ // vulkan_create_texture_from_wlr_buffer waited for this SHM upload to finish.
+ // Exporting its image memory as a DMA-BUF does not produce a sync fence and can
+ // return an invalid fd on drivers that do not export the allocation.
+ static bool loggedSynchronousUpload = false;
+ if ( !loggedSynchronousUpload )
{
- fence = newCommit->vulkanTex->memoryFence();
+ xwm_log.infof( "Treating synchronously uploaded SHM commit %lu as ready without an external fence",
+ newCommit->commitID );
+ loggedSynchronousUpload = true;
}
}
gpuvis_trace_printf( "pushing wait for commit %lu win %lx", newCommit->commitID, w->type == steamcompmgr_win_type_t::XWAYLAND ? w->xwayland().id : 0 );
+ if ( !gamescope::DispatchBufferCommitSync( newCommit.get(), g_ImageWaiter, commitSync,
+ mango_nudge, uMangoMsgType, doneCommits ) )
{
- newCommit->SetFence( fence, mango_nudge, uMangoMsgType, doneCommits );
- if ( bKnownReady )
- newCommit->Signal();
- else
- g_ImageWaiter.AddWaitable( newCommit.get() );
+ xwm_log.errorf( "Failed to queue %s commit fence fd %d for commit %lu",
+ gamescope::BufferCommitSyncSourceName( commitSync.source ), commitSync.fence, newCommit->commitID );
+ // Dropping the commit releases its buffer and discards presentation feedback.
+ w->receivedDoneCommit = true;
+ return;
}
w->commit_queue.push_back( std::move(newCommit) );
diff --git a/src/buffer_commit_sync.hpp b/src/buffer_commit_sync.hpp
new file mode 100644
--- /dev/null
+++ b/src/buffer_commit_sync.hpp
@@ -0,0 +1,79 @@
+#pragma once
+
+#include <optional>
+
+namespace gamescope
+{
+enum class BufferCommitSyncSource
+{
+ ExplicitAcquire,
+ DmaBuf,
+ SynchronousUpload,
+};
+
+struct BufferCommitSync
+{
+ int fence = -1;
+ bool knownReady = false;
+ BufferCommitSyncSource source = BufferCommitSyncSource::SynchronousUpload;
+
+ static constexpr BufferCommitSync ExplicitAcquire( int fence, bool knownReady )
+ {
+ return { fence, knownReady, BufferCommitSyncSource::ExplicitAcquire };
+ }
+
+ static constexpr BufferCommitSync DmaBuf( int fence )
+ {
+ return { fence, false, BufferCommitSyncSource::DmaBuf };
+ }
+
+ static constexpr BufferCommitSync SynchronousUpload()
+ {
+ return { -1, true, BufferCommitSyncSource::SynchronousUpload };
+ }
+};
+
+template <typename GetDmaBufFence>
+BufferCommitSync SelectBufferCommitSync( bool hasExplicitAcquire, int explicitFence,
+ bool explicitKnownReady, GetDmaBufFence &&getDmaBufFence )
+{
+ if ( hasExplicitAcquire )
+ return BufferCommitSync::ExplicitAcquire( explicitFence, explicitKnownReady );
+
+ if ( std::optional<int> dmaBufFence = getDmaBufFence() )
+ return BufferCommitSync::DmaBuf( *dmaBufFence );
+
+ return BufferCommitSync::SynchronousUpload();
+}
+
+constexpr const char *BufferCommitSyncSourceName( BufferCommitSyncSource source )
+{
+ switch ( source )
+ {
+ case BufferCommitSyncSource::ExplicitAcquire:
+ return "explicit acquire";
+ case BufferCommitSyncSource::DmaBuf:
+ return "DMA-BUF";
+ case BufferCommitSyncSource::SynchronousUpload:
+ return "synchronous upload";
+ }
+
+ return "unknown";
+}
+
+template <typename Commit, typename Waiter, typename DoneCommits>
+bool DispatchBufferCommitSync( Commit *commit, Waiter &waiter, const BufferCommitSync &sync,
+ bool mangoNudge, unsigned int mangoMsgType, DoneCommits *doneCommits )
+{
+ commit->SetFence( sync.fence, mangoNudge, mangoMsgType, doneCommits );
+ if ( !sync.knownReady && sync.fence < 0 )
+ return false;
+ if ( sync.knownReady )
+ {
+ commit->Signal();
+ return true;
+ }
+
+ return waiter.AddWaitable( commit );
+}
+}
@@ -0,0 +1,54 @@
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -1851,16 +1851,22 @@
commit->fifo = false;
}
- if ( gamescope::OwningRc<CVulkanTexture> pTexture = s_BufferMemos.LookupVulkanTexture( buf ) )
- {
- // Going from OwningRc -> Rc now.
- commit->vulkanTex = pTexture;
- return commit;
- }
-
struct wlr_dmabuf_attributes dmabuf = {0};
+ const bool bIsDmabuf = wlr_buffer_get_dmabuf( buf, &dmabuf );
+ // DMA-BUF textures reference the producer's live allocation. SHM textures are
+ // snapshots uploaded into separate memory and must be refreshed every commit.
+ if ( bIsDmabuf )
+ {
+ if ( gamescope::OwningRc<CVulkanTexture> pTexture = s_BufferMemos.LookupVulkanTexture( buf ) )
+ {
+ // Going from OwningRc -> Rc now.
+ commit->vulkanTex = pTexture;
+ return commit;
+ }
+ }
+
gamescope::OwningRc<gamescope::IBackendFb> pBackendFb;
- if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) )
+ if ( bIsDmabuf )
{
pBackendFb = GetBackend()->ImportDmabufToBackend( &dmabuf );
}
@@ -1874,7 +1880,8 @@
commit->vulkanTex = pOwnedTexture;
- s_BufferMemos.MemoizeBuffer( buf, std::move( pOwnedTexture ) );
+ if ( bIsDmabuf )
+ s_BufferMemos.MemoizeBuffer( buf, std::move( pOwnedTexture ) );
return commit;
}
@@ -8599,7 +8606,8 @@
} );
if ( commitSync.source == gamescope::BufferCommitSyncSource::SynchronousUpload )
{
- // vulkan_create_texture_from_wlr_buffer waited for this SHM upload to finish.
+ // Every SHM commit is uploaded synchronously; only DMA-BUF textures are memoized.
+ // vulkan_create_texture_from_wlr_buffer waited for this upload to finish.
// Exporting its image memory as a DMA-BUF does not produce a sync fence and can
// return an invalid fd on drivers that do not export the allocation.
static bool loggedSynchronousUpload = false;
@@ -0,0 +1,15 @@
diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -4966,7 +4966,10 @@
CVulkanTexture::createFlags texCreateFlags;
texCreateFlags.bSampled = true;
texCreateFlags.bTransferDst = true;
- texCreateFlags.bFlippable = true;
+ // Swapchain backends sample this synchronous SHM upload locally. They never
+ // scan it out or forward its DMA-BUF, so external allocation and ownership
+ // transfers are unnecessary. Keep exportable inputs for the other backends.
+ texCreateFlags.bFlippable = !GetBackend()->UsesVulkanSwapchain();
if ( pTex->BInit( width, height, 1u, drmFormat, texCreateFlags, nullptr, 0, 0, nullptr, pBackendFb ) == false )
return nullptr;
@@ -0,0 +1,14 @@
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -2350,6 +2350,9 @@ bool MouseCursor::getTexture()
CVulkanTexture::createFlags texCreateFlags;
- texCreateFlags.bFlippable = true;
+ // The Vulkan swapchain backend samples cursor pixels locally. It does not
+ // scan out or forward a cursor DMA-BUF, so keep this allocation internal.
+ // Hardware and forwarding backends retain their exportable cursor texture.
+ texCreateFlags.bFlippable = !GetBackend()->UsesVulkanSwapchain();
if ( GetBackend()->SupportsPlaneHardwareCursor() )
{
texCreateFlags.bLinear = true; // cursor buffer needs to be linear
@@ -0,0 +1,10 @@
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -2,6 +2,7 @@
#define _GNU_SOURCE 1
#include <assert.h>
+#include <cfloat>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
+5
View File
@@ -0,0 +1,5 @@
export LD_LIBRARY_PATH="$DROIDDECK_QUALCOMM_ROOT/mesa${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export LIBGL_DRIVERS_PATH="$DROIDDECK_QUALCOMM_ROOT/mesa/dri"
export GBM_BACKENDS_PATH="$DROIDDECK_QUALCOMM_ROOT/mesa/gbm"
export __GLX_VENDOR_LIBRARY_NAME=droiddeck_qualcomm
export __EGL_VENDOR_LIBRARY_FILENAMES="$DROIDDECK_QUALCOMM_ROOT/mesa/egl-vendor.json"
+104 -6
View File
@@ -9,12 +9,14 @@
"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"
"source_commit": "8f212644c46460549035971ab914421180f61a7c",
"member_sha256": "1efe37d2bee38b86a051f84279802054d770aed6d9eff7d9379b4648705d0881",
"build": {
"binary": "build/qualcomm-gamescope/gamescope",
"source": "build/qualcomm-gamescope/source.tar.gz",
"source_sha256": "11e9134ef9fe0616bc1c1ef8bdf61c52b48f473ce07e562108cf7476a017e531",
"source_size": 10104613
}
},
"wsi": {
"files": [
@@ -113,5 +115,101 @@
"73368f4579fe0d27": "cs_composite_blur",
"80346d82c5a6d850": "cs_composite_blit"
}
},
"mesa": {
"source_commit": "31e9a6b2e95e30d84bf3177d1f497d063e59b6b2",
"directory": "build/qualcomm-zink/runtime",
"files": [
{
"path": "dri/zink_dri.so",
"sha256": "a65a4b895ebf49ec2841023fa550f50344574e1b68d574468365ea7857f577fa",
"size": 133368
},
{
"path": "egl-vendor.json",
"sha256": "aafac15b0a72b9b782d88729f3481c217b27f6e8664ffc9d6493df566ebb466a",
"size": 93
},
{
"path": "gbm/dri_gbm.so",
"sha256": "57e5aa8c3903c13db6a38e28cdc9160f2bd09cec04c88c90d6603d15b761b524",
"size": 134024
},
{
"path": "libEGL_droiddeck_qualcomm.so.0",
"sha256": "234d2041db7e87717c507ee90697058f646d1d9b61adc94f4a8cc52576fe733d",
"size": 398632
},
{
"path": "libgallium-26.2.3.so",
"sha256": "bcda320860b051725ca49fd546937cc6512373bc21ce0d1e7f9c097341f3e9bc",
"size": 21578336
},
{
"path": "libgbm.so.1",
"sha256": "e9f131c50d423832eb947292b2a4f6405e51c272ce76906e3e31d5e5988334a9",
"size": 67840
},
{
"path": "libGLX_droiddeck_qualcomm.so.0",
"sha256": "0c508865568327c98230c7ae5c241716715e70cc5b9dfc942cdb7bb79fc346a5",
"size": 397704
},
{
"path": "licenses/Apache-2.0",
"sha256": "aeae52465380b8b7959c940355e869cbbc277d4559a7ababa49cbc4818c051f5",
"size": 10232
},
{
"path": "licenses/BSL-1.0",
"sha256": "cf7105f24feaf70f28b8de10074e4d37bef89b8f3ae0a5f1280237f027d77ec0",
"size": 1666
},
{
"path": "licenses/exceptions/Linux-Syscall-Note",
"sha256": "d3a16686cf8797fd8a7b35564b9de8586e6ed8e19c9447e16ab6dda0d411215d",
"size": 1281
},
{
"path": "licenses/GPL-1.0-or-later",
"sha256": "13ff2a162a56796d863de9373ea46860a0fc28da81c192e76da9704dc117c27b",
"size": 13307
},
{
"path": "licenses/GPL-2.0-only",
"sha256": "535fe49430f3098603b190f07040d5b0c65eb8ed5fe173108e26eb668e758c00",
"size": 18774
},
{
"path": "licenses/Mesa-license.rst",
"sha256": "e272a136f6ce1b855afeef2fbe44f94f17d3b4d91cef29f8364af5800c5838a8",
"size": 1866
},
{
"path": "licenses/MIT",
"sha256": "a1179eae4e8b0ef06cbad6b6a2e324bb1c1cd9281cf40b42c0b15b0e34df2683",
"size": 1402
},
{
"path": "licenses/SGI-B-2.0",
"sha256": "8299bbd83c4d77eb5ae767cf250e9b3b470218e863714cba2e5e7eaed51285f2",
"size": 1844
},
{
"path": "licenses/source.json",
"sha256": "6bb1d1549c264228466853656258f1e3c6822c2bf314260e4900cd0eed3a393c",
"size": 658
},
{
"path": "licenses/zink-optional-maintenance5.patch",
"sha256": "25f0733bc42199344f205a916f6674d578a9a78250fd6b4cecbfb1cbbc8f8cf5",
"size": 1682
},
{
"path": "qualcomm-gl.sh",
"sha256": "2ed84bb2a5fa20cde52f18733fe3e8d1fda22f3f0d0bdad47363ff71a04522f5",
"size": 353
}
]
}
}
@@ -0,0 +1,34 @@
--- a/src/gallium/drivers/zink/zink_device_info.py
+++ b/src/gallium/drivers/zink/zink_device_info.py
@@ -68,8 +68,7 @@
features=True),
Extension("VK_KHR_maintenance5",
alias="maint5",
- features=True, properties=True,
- required=True),
+ features=True, properties=True),
Extension("VK_KHR_maintenance6",
alias="maint6",
features=True, properties=True),
--- a/src/gallium/drivers/zink/zink_compiler.c
+++ b/src/gallium/drivers/zink/zink_compiler.c
@@ -3056,7 +3056,7 @@
bool can_remove = false;
if (!nir_find_variable_with_location(consumer, nir_var_shader_in, VARYING_SLOT_PSIZ)) {
/* maintenance5 guarantees "A default size of 1.0 is used if PointSize is not written" */
- if (!var->data.explicit_xfb_buffer && delete_psiz_store(producer, true))
+ if (screen->info.have_KHR_maintenance5 && !var->data.explicit_xfb_buffer && delete_psiz_store(producer, true))
can_remove = !(producer->info.outputs_written & VARYING_BIT_PSIZ);
else if (consumer->info.stage != MESA_SHADER_FRAGMENT)
can_remove = !var->data.explicit_location;
--- a/src/gallium/drivers/zink/zink_context.c
+++ b/src/gallium/drivers/zink/zink_context.c
@@ -1007,7 +1007,7 @@
NULL,
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT
};
- bvci.pNext = &usage;
+ bvci.pNext = screen->info.have_KHR_maintenance5 ? &usage : NULL;
if (zink_get_format_props(screen, format)->bufferFeatures & VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT)
usage.usage |= VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT;
+28
View File
@@ -86,6 +86,34 @@ class QualcommRuntimeTest(unittest.TestCase):
runtime.stage(root / 'bad-path', cache, lock)
self.assertFalse((root / 'outside.so').exists())
def test_local_builds_are_pinned_and_never_fall_back_to_a_download(self):
bridge, binary, source, mesa = b'bridge', b'built gamescope', b'corresponding source', b'private mesa'
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
built, cache = root / 'build/private', root / 'cache'
built.mkdir(parents=True)
cache.mkdir()
lock = self.lock(bridge)
(cache / (lock['sha256'] + '.tzst')).write_bytes(bridge)
for name, data in [('gamescope', binary), ('source.tar.gz', source), ('mesa.so', mesa)]:
(built / name).write_bytes(data)
lock['gamescope'] = dict(member_sha256=hashlib.sha256(binary).hexdigest(), build=dict(
binary='build/private/gamescope', source='build/private/source.tar.gz',
source_sha256=hashlib.sha256(source).hexdigest(), source_size=len(source)))
lock['mesa'] = dict(directory='build/private', files=[dict(
path='mesa.so', sha256=hashlib.sha256(mesa).hexdigest(), size=len(mesa))])
with patch.object(runtime, '__file__', str(root / 'tools/qualcomm/fetch_runtime.py')):
output = root / 'output'
runtime.stage(output, cache, lock)
self.assertEqual((output / 'gamescope').read_bytes(), binary)
self.assertEqual((output / 'licenses/gamescope-patched-source.tar.gzip').read_bytes(), source)
self.assertEqual((output / 'mesa/mesa.so').read_bytes(), mesa)
(built / 'mesa.so').write_bytes(b'wrong mesa')
with self.assertRaisesRegex(ValueError, 'Mesa member'):
runtime.stage(output, cache, lock)
(built / 'gamescope').unlink()
with self.assertRaisesRegex(FileNotFoundError, 'Build private Gamescope'):
runtime.stage(output, cache, lock)
if __name__ == '__main__':