Compare commits

...
14 changed files with 481 additions and 26 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
# Patch context includes the indentation and blank lines of the source being patched.
tools/gamescope/patches/*.patch -whitespace
@@ -3478,7 +3478,8 @@ int banner_wayland_run(void) {
/* libdecor binds wl_output at 4; a lower version is a protocol error for the client. */
wl_global_create(display, &wl_output_interface, 4, NULL, bind_output);
wl_global_create(display, &xdg_wm_base_interface, 1, NULL, bind_xdg_wm_base);
wl_global_create(display, &zwp_linux_dmabuf_v1_interface, 4, NULL, bind_dmabuf);
if (vkp_dmabuf_supported())
wl_global_create(display, &zwp_linux_dmabuf_v1_interface, 4, NULL, bind_dmabuf);
/* gamescope's Wayland backend refuses a seat older than 8. */
wl_global_create(display, &wl_seat_interface, 9, NULL, bind_seat);
wl_global_create(display, &banner_desktop_v1_interface, 1, NULL, bind_desktop);
+1 -1
View File
@@ -9,7 +9,7 @@
#define VK_USE_PLATFORM_ANDROID_KHR
#include <vulkan/vulkan.h>
#define VK_GLOBAL_FUNCS(X) X(CreateInstance) X(EnumerateInstanceExtensionProperties)
#define VK_GLOBAL_FUNCS(X) X(CreateInstance) X(EnumerateInstanceExtensionProperties) X(EnumerateInstanceVersion)
#define VK_INSTANCE_FUNCS(X) \
X(EnumeratePhysicalDevices) X(GetPhysicalDeviceProperties) \
+30 -13
View File
@@ -40,6 +40,7 @@ struct vkp_image {
static ANativeWindow *g_window; /* the window frames go to; compositor thread only */
static char *g_driver_path, *g_library_name, *g_native_lib_dir;
static int g_dev_state; /* 0 = not yet, 1 = ok, -1 = failed, -2 = lost (VK_ERROR_DEVICE_LOST) */
static int g_dmabuf_import;
static VkInstance g_inst;
static VkPhysicalDevice g_pd;
static VkDevice g_dev;
@@ -411,11 +412,18 @@ static int dev_init(void) {
"generation %s)", g_colorspace_ext ? "enables" : "has no",
g_colorspace_ext ? "is possible where the screen surface offers one" : "is not possible: tone-mapped instead");
}
/* 1.3 like the X11 renderer's instance: the frame-generation probe (framegen_engine.cpp)
* queries VkPhysicalDeviceVulkan12Features, and a 1.1 instance may be answered as 1.1. */
/* Keep the existing 1.3 request unless the selected loader positively reports an
* older usable API. Custom ICDs may omit this optional global entry point. */
uint32_t loader_api = VK_API_VERSION_1_3;
if (g_vk.EnumerateInstanceVersion) {
uint32_t reported_api = VK_API_VERSION_1_3;
if (g_vk.EnumerateInstanceVersion(&reported_api) == VK_SUCCESS &&
reported_api >= VK_API_VERSION_1_1)
loader_api = reported_api;
}
VkApplicationInfo app = {.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "banner-wayland-present",
.apiVersion = VK_API_VERSION_1_3};
.apiVersion = loader_api < VK_API_VERSION_1_3 ? loader_api : VK_API_VERSION_1_3};
VkInstanceCreateInfo ici = {.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &app,
.enabledExtensionCount = n_inst_exts,
@@ -450,18 +458,26 @@ static int dev_init(void) {
}
g_vk.GetPhysicalDeviceMemoryProperties(g_pd, &g_memprops);
/* Verify the dmabuf-import extensions are present, and log any that are missing. */
const char *dev_exts[7] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, "VK_KHR_external_memory_fd",
"VK_EXT_external_memory_dma_buf", "VK_EXT_image_drm_format_modifier",
"VK_KHR_image_format_list", NULL, NULL};
uint32_t n_dev_exts = 5;
/* Shared-memory clients need only a swapchain. Advertise dma-buf import only when its
* complete extension set is available, rather than failing device creation on older GPUs. */
const char *import_exts[] = {"VK_KHR_external_memory_fd", "VK_EXT_external_memory_dma_buf",
"VK_EXT_image_drm_format_modifier", "VK_KHR_image_format_list"};
const char *dev_exts[7] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
uint32_t n_dev_exts = 1;
uint32_t ne = 0;
g_vk.EnumerateDeviceExtensionProperties(g_pd, NULL, &ne, NULL);
VkExtensionProperties *exts = calloc(ne ? ne : 1, sizeof(*exts));
g_vk.EnumerateDeviceExtensionProperties(g_pd, NULL, &ne, exts);
for (unsigned i = 0; i < 5; i++)
if (!has_ext(exts, ne, dev_exts[i]))
LOGE("present: driver MISSING %s (dmabuf import will fail)", dev_exts[i]);
g_dmabuf_import = 1;
for (unsigned i = 0; i < sizeof(import_exts) / sizeof(import_exts[0]); i++) {
if (!has_ext(exts, ne, import_exts[i])) {
g_dmabuf_import = 0;
banner_log("gpu", "driver has no %s; shared-memory clients remain available", import_exts[i]);
}
}
if (g_dmabuf_import)
for (unsigned i = 0; i < sizeof(import_exts) / sizeof(import_exts[0]); i++)
dev_exts[n_dev_exts++] = import_exts[i];
/* HDR sessions only: the HDR10 swapchain (frame generation) can carry the game's metadata. */
int want_hdr_md = 0;
if (banner_color_requested()) {
@@ -538,6 +554,7 @@ static int dev_init(void) {
}
int vkp_ready(void) { return dev_init(); }
int vkp_dmabuf_supported(void) { return dev_init() == 0 && g_dmabuf_import; }
static int swap_init_locked(void);
@@ -899,7 +916,7 @@ static int modifier_importable(VkFormat fmt, uint64_t modifier, VkImageUsageFlag
}
int vkp_dmabuf_modifiers(uint32_t drm_format, uint64_t *out, int max) {
if (max <= 0 || dev_init() != 0 || !g_vk.GetPhysicalDeviceFormatProperties2) return 0;
if (max <= 0 || !vkp_dmabuf_supported() || !g_vk.GetPhysicalDeviceFormatProperties2) return 0;
VkFormat fmt = drm_to_vk(drm_format);
VkDrmFormatModifierPropertiesListEXT list = {
.sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT};
@@ -941,7 +958,7 @@ int vkp_image_is_dmabuf(const struct vkp_image *img) { return img && img->dmabuf
struct vkp_image *vkp_image_import_dmabuf(int fd, uint32_t drm_format, uint64_t modifier, int w, int h,
uint32_t stride, uint32_t offset, int as_blit_dst) {
if (modifier == MOD_INVALID || w <= 0 || h <= 0) return NULL;
if (dev_init() != 0) return NULL;
if (!vkp_dmabuf_supported()) return NULL;
struct vkp_image *img = calloc(1, sizeof(*img));
if (!img) return NULL;
@@ -87,6 +87,8 @@ const char *vkp_gpu_name(void);
// 0 if the renderer can create images (device up), -1 otherwise.
int vkp_ready(void);
/* Whether the selected driver supports the complete dma-buf import extension set. */
int vkp_dmabuf_supported(void);
/* Whether an output window is attached (or requested); without one vkp_render() draws nothing. */
int vkp_has_window(void);
/* 1 once the Vulkan device was lost: nothing is presented any more (the session must restart). */
@@ -24,11 +24,12 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* The Vulkan driver the in-app compositor runs on. It has to be Turnip: the system Adreno driver
* The Vulkan driver the in-app compositor runs on. Adreno needs Turnip: its system driver
* does not implement VK_EXT_image_drm_format_modifier, so importing the dma-bufs gamescope hands
* over fails and the session renders nothing. Two builds ship in the apk, one per Adreno
* generation, and the GPU decides which is unpacked - unless the user has imported an AdrenoTools
* zip of their own and chosen it, which then wins.
* zip of their own and chosen it, which then wins. A positively identified Mali kbase GPU
* instead uses system Vulkan for shared-memory presentation; this does not supply a Linux ICD.
*
* <p>The guest's own Turnip is a different copy entirely - a glibc build inside the rootfs, or an
* imported one ({@link LinuxVulkanDriverManager}). This one is the bionic build the app process
@@ -338,6 +339,12 @@ public final class TurnipDriver {
if (forced.startsWith("a7")) return DRIVER_A7XX;
}
String model = gpuModel();
// A Mali kbase node is positive evidence. Unknown hardware keeps the existing default.
if (model == null && new File("/dev/mali0").exists()
&& !com.droiddeck.launcher.core.DeviceSupport.INSTANCE.adreno()) {
Log.i(TAG, "Mali kbase GPU: using system Vulkan for the compositor");
return null;
}
Log.i(TAG, "gpu model: " + (model == null ? "unknown" : model));
if (model != null) {
// "Adreno750", "adreno_830" - the generation is the first digit of the three.
+79
View File
@@ -0,0 +1,79 @@
# Opt-in software Steam compatibility
This change provides an explicit software fallback for the Linux Steam/Gamescope
session. It does not enable fallback automatically or install a Mali Linux ICD.
The Android presenter still uses Vulkan; a positively identified Mali kbase
GPU uses the system Android driver. Adreno and unknown devices keep the existing
selection, and explicit Android driver choices retain precedence.
## Enable and recover
Set `/sdcard/Download/droiddeck-env` to include:
```sh
BL_STEAM_SOFTWARE=1
```
Remove that entry or set it to `0` to return to normal session rendering. No
preference is migrated or enabled by this branch. Only the exact value `1`
activates the fallback in the scripts and native Gamescope.
The fallback selects Lavapipe/llvmpipe, Gamescope's SDL Wayland swapchain,
software Steam CEF/SDL rendering, and disables mangoapp for that session.
An explicitly requested software session takes precedence over `BL_VK_DRIVER`;
with the mode off, imported Linux drivers retain their existing behavior.
Desktop Steam can also use this mode on a pixman desktop.
The optional present timing, local texture allocation, SHM refresh and ready
fence handling in the new Gamescope patches apply only in this mode. Normal
sessions retain the original dma-buf, buffer-cache and presentation paths.
The Android presenter lowers its 1.3 request when the loader positively reports
an older usable API. Custom ICDs that cannot report a version retain the original
request. Dma-buf import is advertised only with the complete extension set.
## Validation
`tools/tests/test_software_graphics.py` exercises the actual outer session
launcher with a capturing Gamescope executable: default accelerated backend,
custom-driver selection, software-only activation, precedence, overlay, HDR,
refresh rate and argument preservation. The native component is built against
all existing patches and checked against the runtime's shared-library list.
`gamescope-3.16.29-p6` is a versioned component release; the existing release
download/checksum path is unchanged. There is no CI artifact pin or extra build
workflow permission.
Local validation passed all 121 JVM tests and the macOS-applicable Python tests
(62 discovered, 13 Linux-only skipped). The corrected native build passed
[run 37161255955](https://github.com/Droid-Deck/DroidDeck/actions/runs/37161255955).
The final code's APK build, Python and JVM tests, and native dependency closure
check passed [run 37162182731](https://github.com/Droid-Deck/DroidDeck/actions/runs/37162182731).
The installed Gamescope binary matches the component's contents
(`2c92c155f7836a798c2a04eb1db5779e149680fcce888cd9a1ebe3fef7e5a2af`).
The cleanup build was installed in place on the wired Mali device using the
release-signed debug APK. Automatic selection logged the positive Mali kbase
identification, and Steam Big Picture reached the logged-in home screen through
the new software-compatible Gamescope. Animated frames and input worked. A
Geometry Wars retest stalled during Wine prefix startup; successful gameplay
below belongs to the earlier experiment, not this cleanup validation. No
physical Adreno device was updated during this cleanup.
Earlier experiment validation on the wired Unisoc T618 / Mali-G52 MC2 device
(Android 12, kbase JM 11.31) reached Steam Big Picture sign-in and downloaded
Geometry Wars: Retro Evolved. Its software-rendered menu, movement, shooting and
pause worked, at roughly 3–5 displayed FPS. The per-game workaround was:
```sh
DISABLE_GAMESCOPE_WSI=1 %command%
```
This option is not applied globally. Software gameplay remains too slow for
normal play. The previous experimental branch `mali-software-steam` retains
the private PanVK build and GPU proof: compute, draw/readback, indexed draw and
visible VKCube worked, but accelerated Geometry Wars lost the GPU device.
That driver lab is intentionally outside this compatibility change.
Before a PR, validate default rendering on supported Adreno devices, explicit
Android/Linux driver choices, overlays and supported HDR/frame-generation
behavior as well as the opted-in wired Mali session. Passing launcher checks
alone is not proof of physical Adreno regression safety.
+10
View File
@@ -49,3 +49,13 @@ library list, before anything is published.
Sixteen more of Armada's patches are DRM/lease/HDR-on-KMS work for a native display, which this
app's Wayland-hosted gamescope never reaches, or need a newer gamescope than the runtime has.
- `0114-software-swapchain-compat.patch` - this app: only with
`BL_STEAM_SOFTWARE=1`, allow the SDL Vulkan swapchain to run without present-ID/wait
support and keep local textures private. Explicit exports and dma-buf imports retain
their allocation requirements. With the mode unset, extension requests, feature chains,
texture allocation and present-wait pacing follow the existing path.
- `0115-software-shm-commits.patch` - this app: only in that compatibility mode,
refresh reused shared-memory buffers on every commit and treat their completed
synchronous uploads as ready. Normal sessions retain buffer memoization and fence
handling; dma-buf commits retain both in either mode.
@@ -0,0 +1,157 @@
diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
index 54e4efc..4777f7c 100644
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -57,6 +57,12 @@
#include "reshade_effect_manager.hpp"
+// Opt-in compatibility mode; an unset or other value preserves the normal backend.
+const bool g_bSoftwareCompat = [] {
+ const char *value = getenv( "BL_STEAM_SOFTWARE" );
+ return value && strcmp( value, "1" ) == 0;
+}();
+
extern bool g_bWasPartialComposite;
extern bool g_bAllowDeferredBackend;
@@ -450,7 +456,16 @@ bool CVulkanDevice::createDevice()
bool hasDrmProps = vulkan_has_drm_props();
bool supportsForeignQueue = false;
bool supportsHDRMetadata = false;
+ bool supportsPresentId = false;
+ bool supportsPresentWait = false;
for (const auto& ext : m_supportedExts) {
+ if ( g_bSoftwareCompat )
+ {
+ if ( strcmp(ext.extensionName, VK_KHR_PRESENT_ID_EXTENSION_NAME) == 0 )
+ supportsPresentId = true;
+ if ( strcmp(ext.extensionName, VK_KHR_PRESENT_WAIT_EXTENSION_NAME) == 0 )
+ supportsPresentWait = true;
+ }
if ( strcmp(ext.extensionName, VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME) == 0 )
m_bSupportsModifiers = true;
@@ -518,6 +533,26 @@ bool CVulkanDevice::createDevice()
m_bSupportsModifiers = false;
}
+ m_bSupportsPresentWait = !g_bSoftwareCompat;
+ if ( g_bSoftwareCompat && supportsPresentId && supportsPresentWait )
+ {
+ VkPhysicalDevicePresentWaitFeaturesKHR waitFeatures = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR,
+ };
+ VkPhysicalDevicePresentIdFeaturesKHR idFeatures = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR,
+ .pNext = &waitFeatures,
+ };
+ VkPhysicalDeviceFeatures2 features = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
+ .pNext = &idFeatures,
+ };
+ vk.GetPhysicalDeviceFeatures2( physDev(), &features );
+ m_bSupportsPresentWait = idFeatures.presentId && waitFeatures.presentWait;
+ }
+ if ( GetBackend()->UsesVulkanSwapchain() && !m_bSupportsPresentWait )
+ vk_log.infof( "Present ID/wait unavailable; using timer-based pacing." );
+
{
VkPhysicalDeviceVulkan12Features vulkan12Features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
@@ -570,8 +605,11 @@ bool CVulkanDevice::createDevice()
enabledExtensions.push_back( VK_KHR_SWAPCHAIN_EXTENSION_NAME );
enabledExtensions.push_back( VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME );
- enabledExtensions.push_back( VK_KHR_PRESENT_ID_EXTENSION_NAME );
- enabledExtensions.push_back( VK_KHR_PRESENT_WAIT_EXTENSION_NAME );
+ if ( m_bSupportsPresentWait )
+ {
+ enabledExtensions.push_back( VK_KHR_PRESENT_ID_EXTENSION_NAME );
+ enabledExtensions.push_back( VK_KHR_PRESENT_WAIT_EXTENSION_NAME );
+ }
if ( supportsHDRMetadata )
enabledExtensions.push_back( VK_EXT_HDR_METADATA_EXTENSION_NAME );
@@ -646,7 +684,7 @@ bool CVulkanDevice::createDevice()
VkPhysicalDeviceFeatures2 features2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
- .pNext = &presentIdFeatures,
+ .pNext = m_bSupportsPresentWait ? static_cast<void *>( &presentIdFeatures ) : static_cast<void *>( &features13 ),
.features = {
.shaderInt16 = m_bSupportsFp16,
},
@@ -2082,6 +2120,11 @@ static VkImageViewType VulkanImageTypeToViewType(VkImageType type)
bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uint32_t drmFormat, createFlags flags, wlr_dmabuf_attributes *pDMA /* = nullptr */, uint32_t contentWidth /* = 0 */, uint32_t contentHeight /* = 0 */, CVulkanTexture *pExistingImageToReuseMemory, gamescope::OwningRc<gamescope::IBackendFb> pBackendFb )
{
+ // Software swapchains need local cursor, blank and wl_shm textures, not scan-out.
+ // Explicit exports and dma-buf imports retain their original allocation requirements.
+ if ( g_bSoftwareCompat && GetBackend()->UsesVulkanSwapchain() && !pDMA && !flags.bExportable )
+ flags.bFlippable = false;
+
m_pBackendFb = std::move( pBackendFb );
m_drmFormat = drmFormat;
VkResult res = VK_ERROR_INITIALIZATION_FAILED;
@@ -3086,7 +3129,7 @@ void vulkan_present_to_window( void )
VkPresentInfoKHR presentInfo = {
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
- .pNext = &presentIdInfo,
+ .pNext = g_device.supportsPresentWait() ? &presentIdInfo : nullptr,
.swapchainCount = 1,
.pSwapchains = &g_output.swapChain,
.pImageIndices = &g_output.nOutImage,
@@ -3094,8 +3137,11 @@ void vulkan_present_to_window( void )
if ( g_device.vk.QueuePresentKHR( g_device.queue(), &presentInfo ) == VK_SUCCESS )
{
- g_currentPresentWaitId = presentId;
- g_currentPresentWaitId.notify_all();
+ if ( g_device.supportsPresentWait() )
+ {
+ g_currentPresentWaitId = presentId;
+ g_currentPresentWaitId.notify_all();
+ }
}
else
vulkan_remake_swapchain();
@@ -3662,7 +3708,7 @@ bool vulkan_init( VkInstance instance, VkSurfaceKHR surface )
if (!init_nis_data())
return false;
- if ( GetBackend()->UsesVulkanSwapchain() )
+ if ( GetBackend()->UsesVulkanSwapchain() && g_device.supportsPresentWait() )
{
std::thread present_wait_thread( present_wait_thread_func );
present_wait_thread.detach();
diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp
index fa519db..a784c29 100644
--- a/src/rendervulkan.hpp
+++ b/src/rendervulkan.hpp
@@ -20,6 +20,8 @@
#include "shaders/descriptor_set_constants.h"
+extern const bool g_bSoftwareCompat;
+
class CVulkanCmdBuffer;
// 1: Fade Plane (Fade outs between switching focus)
@@ -862,6 +864,7 @@ public:
inline VkPipelineLayout pipelineLayout() {return m_pipelineLayout;}
inline int drmRenderFd() {return m_drmRendererFd;}
inline bool supportsModifiers() {return m_bSupportsModifiers;}
+ inline bool supportsPresentWait() {return m_bSupportsPresentWait;}
inline bool hasDrmPrimaryDevId() {return m_bHasDrmPrimaryDevId;}
inline dev_t primaryDevId() {return m_drmPrimaryDevId;}
inline bool supportsFp16() {return m_bSupportsFp16;}
@@ -930,6 +933,7 @@ protected:
uint32_t m_uVendorID = 0;
bool m_bHasDrmPrimaryDevId = false;
bool m_bSupportsModifiers = false;
+ bool m_bSupportsPresentWait = true;
bool m_bInitialized = false;
@@ -0,0 +1,61 @@
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 18b799e..d854a68 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -1850,30 +1850,32 @@ import_commit (
commit->fifo = false;
}
- if ( gamescope::OwningRc<CVulkanTexture> pTexture = s_BufferMemos.LookupVulkanTexture( buf ) )
+ struct wlr_dmabuf_attributes dmabuf = {0};
+ // Only the software compatibility path refreshes private wl_shm copies.
+ // Normal sessions retain the original memo lookup before probing the buffer.
+ const bool bRefreshShm = g_bSoftwareCompat && !wlr_buffer_get_dmabuf( buf, &dmabuf );
+ if ( !bRefreshShm )
{
- // Going from OwningRc -> Rc now.
- commit->vulkanTex = pTexture;
- return commit;
+ if ( gamescope::OwningRc<CVulkanTexture> pTexture = s_BufferMemos.LookupVulkanTexture( buf ) )
+ {
+ commit->vulkanTex = pTexture;
+ return commit;
+ }
}
- struct wlr_dmabuf_attributes dmabuf = {0};
gamescope::OwningRc<gamescope::IBackendFb> pBackendFb;
if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) )
- {
pBackendFb = GetBackend()->ImportDmabufToBackend( &dmabuf );
- }
gamescope::OwningRc<CVulkanTexture> pOwnedTexture = vulkan_create_texture_from_wlr_buffer( buf, std::move( pBackendFb ) );
if ( pOwnedTexture == nullptr ) {
- // Failed to create Vulkan texture from Wayland buffer for some reason.
return nullptr;
}
commit->vulkanTex = pOwnedTexture;
-
- s_BufferMemos.MemoizeBuffer( buf, std::move( pOwnedTexture ) );
+ if ( !bRefreshShm )
+ s_BufferMemos.MemoizeBuffer( buf, std::move( pOwnedTexture ) );
return commit;
}
@@ -8504,6 +8506,13 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re
{
fence = dup( dmabuf.fd[0] );
}
+ else if ( g_bSoftwareCompat && GetBackend()->UsesVulkanSwapchain() &&
+ !newCommit->vulkanTex->externalImage() )
+ {
+ // wl_shm uploads wait for their copy to finish in vulkan_create_texture_from_wlr_buffer.
+ // A private swapchain texture has no dma-buf to poll; it is already ready.
+ bKnownReady = true;
+ }
else
{
fence = newCommit->vulkanTex->memoryFence();
+2 -2
View File
@@ -1,2 +1,2 @@
GAMESCOPE_TAG=gamescope-3.16.29-p5
GAMESCOPE_SHA256=188144e5564e020d0bc485a1f4f90e1c5fd61d0d1d6dd29db21e8b019036a5c6
GAMESCOPE_TAG=gamescope-3.16.29-p6
GAMESCOPE_SHA256=b34294a5c5358b8d0bb7a2d7d45c92595dad6d8257eacc5ef36b980b8b7931f0
@@ -53,7 +53,15 @@ trap 'collect_steam_logs 2>/dev/null || true' TERM INT HUP
# VK_DRIVER_FILES REPLACES the loader's search, so a manifest whose library is unreadable would
# leave the session with no Vulkan at all - check both before setting it, and say so in the log
# rather than fail silently half a minute later.
if [ -n "${BL_VK_DRIVER:-}" ]; then
# Compatibility mode: the Android presenter still uses its selected Vulkan driver, while the
# Linux session renders on the CPU. Gamescope's SDL swapchain can carry these frames as wl_shm;
# its Wayland backend requires exportable dma-bufs even for its dummy black texture.
if [ "${BL_STEAM_SOFTWARE:-0}" = 1 ]; then
export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json
export VK_ICD_FILENAMES=$VK_DRIVER_FILES
export LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe MESA_LOADER_DRIVER_OVERRIDE=swrast
echo "== linux graphics: software rendering requested (Lavapipe/llvmpipe)"
elif [ -n "${BL_VK_DRIVER:-}" ]; then
bl_icd_lib=$(sed -n 's/.*"library_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$BL_VK_DRIVER" 2>/dev/null | head -1)
# JSON may escape the separator: org.json writes "\/data\/user\/0\/...". The Vulkan loader
# parses that correctly; this check has to as well, or an absolute path looks relative and gets
@@ -148,7 +156,13 @@ if [ -z "${BL_INSIDE:-}" ]; then
echo "== gamescope: games at ${nested}x${height} stretched to the ${width}x${height} output"
fi
fi
args=(--backend wayland --expose-wayland -f -W "$width" -H "$height" -w "$nested" -h "$height")
backend=wayland
if [ "${BL_STEAM_SOFTWARE:-0}" = 1 ]; then
backend=sdl
export SDL_VIDEODRIVER=wayland
echo "== gamescope: SDL Wayland swapchain for software rendering"
fi
args=(--backend "$backend" --expose-wayland -f -W "$width" -H "$height" -w "$nested" -h "$height")
[ "$nested" != "$width" ] && args+=(-S stretch)
# -r is what gamescope advertises as the refresh rate, and what a game reads as the display's.
# Left unset it defaults to 60, so a 120 Hz panel offers only 60 Hz in game settings. The FPS
@@ -179,7 +193,8 @@ if [ -z "${BL_INSIDE:-}" ]; then
# MANGOHUD_CONFIGFILE. The apk stages Valve's aarch64 build (tools/mangoapp) - without it the menu
# still offers the levels and nothing appears. Starts hidden until the client writes a level.
# BL_MANGOAPP=0 turns it off (one community build found Valve's mangoapp crashing on Turnip).
if [ "$mode" = steam ] && [ "${BL_STEAMDECK:-0}" = 1 ] && [ "${BL_MANGOAPP:-1}" = 1 ] \
if [ "$mode" = steam ] && [ "${BL_STEAM_SOFTWARE:-0}" != 1 ] \
&& [ "${BL_STEAMDECK:-0}" = 1 ] && [ "${BL_MANGOAPP:-1}" = 1 ] \
&& [ "${BL_STEAM_UI:-}" != desktop ] && command -v mangoapp >/dev/null 2>&1; then
MANGOHUD_CONFIGFILE=$(mktemp /tmp/mangohud.XXXXXXXX)
echo no_display > "$MANGOHUD_CONFIGFILE"
@@ -639,6 +654,15 @@ BUSCONF
# desktop's launchers ask for; gamescope shows one window at a time and the game takes over.
_bl_ui=(-gamepadui)
[ "${BL_STEAM_UI:-}" = desktop ] && _bl_ui=()
# Software compatibility mode: CEF and Steam use CPU frames through Xwayland,
# inside either the desktop or the software Gamescope session.
_bl_cef=(-cef-force-gpu -cef-ozone-platform=x11 -cef-use-gl=angle -cef-use-angle=vulkan)
if [ "${BL_STEAM_SOFTWARE:-0}" = 1 ]; then
_bl_cef=(-cef-disable-gpu -cef-disable-gpu-compositing -cef-ozone-platform=x11)
export LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe
export MESA_LOADER_DRIVER_OVERRIDE=swrast SDL_RENDER_DRIVER=software SDL_VIDEODRIVER=x11
echo "== steam: software rendering requested"
fi
# Deck mode (BL_STEAMDECK=1, the Steam page's Client settings): -steamdeck is the flag that puts
# the Quick Access Menu and the Deck settings pages on screen; the QAM's performance overlay
# controls stay greyed out without -steamos3, as SteamOS and Armada launch it. -steamos3 makes
@@ -698,7 +722,7 @@ BUSCONF
done
) &
fi
echo "== steam args: ${_bl_ui[*]} -clientbeta $steam_channel -no-cef-sandbox -cef-force-gpu -cef-ozone-platform=x11 -cef-use-gl=angle -cef-use-angle=vulkan $*"
echo "== steam args: ${_bl_ui[*]} -clientbeta $steam_channel -no-cef-sandbox ${_bl_cef[*]} $*"
# An update that cannot install asks for a restart (rc=42) every time; the count is capped so a
# session can never again loop in silence, which reads as a menu opening and closing by itself.
steam_updates=0
@@ -745,8 +769,7 @@ BUSCONF
fi
step "starting the Steam client${BL_STEAM_UI:+ ($BL_STEAM_UI UI)}"
"$steam_root/steamrtarm64/steam" "${_bl_ui[@]}" -clientbeta "$steam_channel" \
-no-cef-sandbox -cef-force-gpu -cef-ozone-platform=x11 \
-cef-use-gl=angle -cef-use-angle=vulkan "$@"
-no-cef-sandbox "${_bl_cef[@]}" "$@"
rc=$?
echo "== steam exited rc=$rc"
collect_steam_logs
@@ -13,6 +13,7 @@
# On a software desktop (pixman: the GPU renderers failed on this device) there is no dma-buf, so a
# game - or the client's own Vulkan window - would die at its first frame. There the request goes
# to the app, which ends the desktop and starts a Steam session under gamescope in its place.
# BL_STEAM_SOFTWARE=1 opts into a CPU-rendered client on that desktop for compatibility testing.
set -u
ui=${1:-desktop}
target=${2:-}
@@ -28,7 +29,8 @@ esac
steam_root=${XDG_DATA_HOME:-${HOME:-/root}/.local/share}/Steam
client=$steam_root/steamrtarm64/steam
if [ "${BL_DESKTOP:-}" = 1 ] && [ -n "${WLR_RENDERER:-}" ] && [ "$WLR_RENDERER" != pixman ]; then
if [ "${BL_DESKTOP:-}" = 1 ] && [ -n "${WLR_RENDERER:-}" ] \
&& { [ "$WLR_RENDERER" != pixman ] || [ "${BL_STEAM_SOFTWARE:-0}" = 1 ]; }; then
log=${BL_DEBUG_DIR:-$HOME/.cache/droiddeck}/steam-desktop.log
mkdir -p "$(dirname "$log")" 2>/dev/null
if pgrep -f 'steamrtarm64/steam( |$)' >/dev/null 2>&1 && [ -x "$client" ]; then
+94
View File
@@ -0,0 +1,94 @@
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import unittest
SESSION = Path(__file__).resolve().parents[1] / 'linuxfs/overlay/usr/local/bin/bannerlator-session'
GRAPHICS_ENV = ('VK_DRIVER_FILES', 'VK_ICD_FILENAMES', 'LIBGL_ALWAYS_SOFTWARE',
'GALLIUM_DRIVER', 'MESA_LOADER_DRIVER_OVERRIDE', 'SDL_VIDEODRIVER')
class SoftwareGraphicsTest(unittest.TestCase):
def launch(self, **settings):
with tempfile.TemporaryDirectory() as tmp:
home = Path(tmp)
binary = home / 'bin'
binary.mkdir()
capture = home / 'capture.json'
gamescope = binary / 'gamescope'
gamescope.write_text(f'#!{sys.executable}\nimport json, os, sys\n'
'from pathlib import Path\n'
'Path(os.environ["TEST_CAPTURE"]).write_text(json.dumps({"argv": sys.argv[1:], "env": dict(os.environ)}))\n')
gamescope.chmod(0o755)
for name in ('wayland-info', 'mangoapp'):
probe = binary / name
probe.write_text('#!/bin/sh\nexit 0\n')
probe.chmod(0o755)
library = home / 'custom.so'
library.touch()
manifest = home / 'icd.json'
manifest.write_text(json.dumps({'ICD': {'library_path': str(library)}}))
env = {'PATH': str(binary) + os.pathsep + os.defpath, 'HOME': str(home),
'TEST_CAPTURE': str(capture), 'BL_WIDTH': '960', 'BL_HEIGHT': '544',
'BL_STEAMDECK': '1', 'BL_REFRESH': '120', 'BL_HDR': '1'}
env.update(settings)
if env.pop('TEST_CUSTOM_DRIVER', ''):
env['BL_VK_DRIVER'] = str(manifest)
subprocess.run([shutil.which('bash'), str(SESSION), 'steam', 'argument with spaces'],
env=env, check=True, capture_output=True, text=True)
result = json.loads(capture.read_text())
config = result['env'].get('MANGOHUD_CONFIGFILE')
if config:
Path(config).unlink()
return result, str(manifest)
def test_default_keeps_accelerated_backend_overlay_and_session_options(self):
result, _ = self.launch(VK_DRIVER_FILES='/selected/turnip.json', GALLIUM_DRIVER='zink')
args, env = result['argv'], result['env']
self.assertEqual(args[:2], ['--backend', 'wayland'])
self.assertEqual(env['VK_DRIVER_FILES'], '/selected/turnip.json')
self.assertEqual(env['GALLIUM_DRIVER'], 'zink')
self.assertNotIn('LIBGL_ALWAYS_SOFTWARE', env)
self.assertNotIn('MESA_LOADER_DRIVER_OVERRIDE', env)
self.assertNotIn('SDL_VIDEODRIVER', env)
self.assertIn('--mangoapp', args)
self.assertIn('--hdr-enabled', args)
self.assertEqual(args[args.index('-r') + 1], '120')
self.assertEqual(args[-1], 'argument with spaces')
def test_only_explicit_one_selects_software(self):
for value in ('', '0', 'true', 'yes'):
with self.subTest(value=value):
result, _ = self.launch(BL_STEAM_SOFTWARE=value)
self.assertEqual(result['argv'][:2], ['--backend', 'wayland'])
self.assertNotIn('LIBGL_ALWAYS_SOFTWARE', result['env'])
self.assertIn('--mangoapp', result['argv'])
result, _ = self.launch(BL_STEAM_SOFTWARE='1')
env = result['env']
self.assertEqual(result['argv'][:2], ['--backend', 'sdl'])
self.assertNotIn('--mangoapp', result['argv'])
self.assertEqual(env['VK_DRIVER_FILES'], '/usr/share/vulkan/icd.d/lvp_icd.json')
self.assertEqual(env['VK_ICD_FILENAMES'], env['VK_DRIVER_FILES'])
self.assertEqual(env['LIBGL_ALWAYS_SOFTWARE'], '1')
self.assertEqual(env['GALLIUM_DRIVER'], 'llvmpipe')
self.assertEqual(env['SDL_VIDEODRIVER'], 'wayland')
def test_custom_driver_is_preserved_except_when_software_is_requested(self):
for software in ('0', '1'):
result, manifest = self.launch(TEST_CUSTOM_DRIVER='1', BL_STEAM_SOFTWARE=software)
expected = manifest if software == '0' else '/usr/share/vulkan/icd.d/lvp_icd.json'
self.assertEqual(result['env']['VK_DRIVER_FILES'], expected)
self.assertEqual(result['env']['VK_ICD_FILENAMES'], expected)
def test_invalid_custom_driver_does_not_replace_inherited_selection(self):
result, _ = self.launch(BL_VK_DRIVER='/missing/manifest.json', VK_DRIVER_FILES='/selected/turnip.json')
self.assertEqual(result['env']['VK_DRIVER_FILES'], '/selected/turnip.json')
self.assertEqual(result['argv'][:2], ['--backend', 'wayland'])
if __name__ == '__main__':
unittest.main()