diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 20b85261b..f8510bb82 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -186,7 +186,7 @@ jobs: uses: actions/cache@v4 with: path: app/src/main/assets/linuxfs - key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/build-x86-preloads.sh', 'tools/linuxfs/fex/*', 'tools/linuxfs/clipboard/*.c', 'tools/linuxfs/ssbs/*.c', 'tools/proot/fastpath/*.c', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/linuxfs/uruntime.env', 'tools/linuxfs/licenses/*', 'tools/mangoapp/*', 'tools/msitools/**', 'tools/linuxfs/debian-image.env') }} + key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/build-x86-preloads.sh', 'tools/linuxfs/fex/*', 'tools/linuxfs/clipboard/*.c', 'tools/linuxfs/ssbs/*.c', 'tools/linuxfs/xclipse-layer/*', 'tools/proot/fastpath/*.c', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/linuxfs/uruntime.env', 'tools/linuxfs/licenses/*', 'tools/mangoapp/*', 'tools/msitools/**', 'tools/linuxfs/debian-image.env') }} - name: Build libfakeinput.so and libblsession.so (glibc aarch64) if: steps.preloads.outputs.cache-hit != 'true' @@ -195,7 +195,7 @@ jobs: run: | set -euo pipefail sudo apt-get update -qq - sudo apt-get install -y -qq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu >/dev/null + sudo apt-get install -y -qq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libvulkan-dev >/dev/null D=app/src/main/assets/linuxfs mkdir -p "$D" aarch64-linux-gnu-g++ -shared -fPIC -O2 -Wall -Wno-attributes -Wno-nonnull-compare \ @@ -215,6 +215,12 @@ jobs: aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \ -o "$D/libssbs.so" tools/linuxfs/ssbs/ssbs_adapter.c -ldl aarch64-linux-gnu-strip --strip-unneeded "$D/libssbs.so" + # Samsung Xclipse driver workarounds, a Vulkan layer the system-driver path enables there. + mkdir -p "$D/usr/local/lib/droiddeck-xclipse" + aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -Wextra -Wno-unused-parameter -fvisibility=hidden -pthread \ + -o "$D/usr/local/lib/droiddeck-xclipse/libVkLayer_droiddeck_xclipse.so" tools/linuxfs/xclipse-layer/xclipse_layer.c -ldl + aarch64-linux-gnu-strip --strip-unneeded "$D/usr/local/lib/droiddeck-xclipse/libVkLayer_droiddeck_xclipse.so" + install -m644 tools/linuxfs/xclipse-layer/VkLayer_droiddeck_xclipse.json "$D/usr/local/lib/droiddeck-xclipse/" mkdir -p "$D/usr/local/bin" aarch64-linux-gnu-gcc -O2 -Wall -Wextra -o "$D/usr/local/bin/droiddeck-clipboard" tools/linuxfs/clipboard/clipboard.c -ldl aarch64-linux-gnu-strip --strip-unneeded "$D/usr/local/bin/droiddeck-clipboard" @@ -497,7 +503,10 @@ jobs: if-no-files-found: ignore - name: Steam import and game environment tests - run: python3 -m unittest discover -s tools/tests -p 'test_*.py' + run: | + # The Xclipse layer regression test compiles the layer against the distribution's headers. + sudo apt-get update -qq && sudo apt-get install -y -qq libvulkan-dev >/dev/null + python3 -m unittest discover -s tools/tests -p 'test_*.py' - name: Unit tests run: ./gradlew testReleaseUnitTest --console=plain -PndkVersion="$NDK_VERSION" diff --git a/app/src/main/cpp/waylandcomp/src/wl_android_buffer.c b/app/src/main/cpp/waylandcomp/src/wl_android_buffer.c index a52c3eac7..c7b436f46 100644 --- a/app/src/main/cpp/waylandcomp/src/wl_android_buffer.c +++ b/app/src/main/cpp/waylandcomp/src/wl_android_buffer.c @@ -112,8 +112,12 @@ static struct wl_resource *create_android_buffer(struct wl_client *client, uint3 static void create_buffer(struct wl_client *client, struct wl_resource *resource, uint32_t id, int32_t width, int32_t height, int32_t stride, int32_t format, int32_t usage, struct wl_resource *handle_resource) { - if (!valid_description(width, height, stride, format) || + /* Some vendor allocators (Samsung Xclipse) record no stride for GPU-only buffers. The import + * must pass that 0 back unchanged: gralloc validates it against the handle's own value. */ + if (!valid_description(width, height, stride ? stride : width, format) || !wl_resource_instance_of(handle_resource, &android_wlegl_handle_interface, &handle_impl)) { + droiddeck_log("display", "android_wlegl rejected: %dx%d stride=%d format=%d usage=0x%x", + width, height, stride, format, (uint32_t)usage); wl_resource_post_error(resource, ANDROID_WLEGL_ERROR_BAD_VALUE, "invalid Android buffer description"); return; } diff --git a/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt b/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt index a1fc2f15f..faeca690b 100644 --- a/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt +++ b/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt @@ -106,7 +106,7 @@ object DeviceReport { k("KGSL gpu_model", readSys("/sys/class/kgsl/kgsl-3d0/gpu_model")) k("KGSL chip id", readSys("/sys/class/kgsl/kgsl-3d0/gpu_chipid")) k("System Vulkan ICD", when { - SystemVulkanDriver.isPowerVr() -> "/vendor/lib64/hw/vulkan.powervr.so" + SystemVulkanDriver.isPowerVr() -> SystemVulkanDriver.hal() File("/vendor/lib64/hw/vulkan.adreno.so").exists() -> "/vendor/lib64/hw/vulkan.adreno.so" else -> "not at the usual path" }) diff --git a/app/src/main/java/com/droiddeck/launcher/gpu/GpuInfo.kt b/app/src/main/java/com/droiddeck/launcher/gpu/GpuInfo.kt index e8772f3f7..b27c85aaa 100644 --- a/app/src/main/java/com/droiddeck/launcher/gpu/GpuInfo.kt +++ b/app/src/main/java/com/droiddeck/launcher/gpu/GpuInfo.kt @@ -106,7 +106,9 @@ data class GpuInfo( val samsung = Build.MANUFACTURER.equals("samsung", ignoreCase = true) return GpuInfo( name = when { - family == Family.POWERVR -> VulkanInfo.cachedOrNull()?.get("device")?.takeIf { it.contains("PowerVR", ignoreCase = true) } ?: "PowerVR" + family == Family.POWERVR -> SystemVulkanDriver.gpuName().let { gpu -> + VulkanInfo.cachedOrNull()?.get("device")?.takeIf { it.contains(gpu, ignoreCase = true) } ?: gpu + } !adreno -> Build.HARDWARE.ifBlank { UNNAMED } model > 0 -> "Adreno $model" else -> "Adreno" diff --git a/app/src/main/java/com/droiddeck/launcher/gpu/SystemVulkanDriver.java b/app/src/main/java/com/droiddeck/launcher/gpu/SystemVulkanDriver.java index 87d3be9c6..b6f0e6efd 100644 --- a/app/src/main/java/com/droiddeck/launcher/gpu/SystemVulkanDriver.java +++ b/app/src/main/java/com/droiddeck/launcher/gpu/SystemVulkanDriver.java @@ -22,7 +22,11 @@ import org.json.JSONObject; /** Isolated glibc-to-Android Vulkan bridge. It does not replace the rootfs's Mesa libraries. */ public final class SystemVulkanDriver { private static final String ASSET = "hybris/runtime.tzst"; - private static final String PVR_HAL = "/vendor/lib64/hw/vulkan.powervr.so"; + /** Vendor HALs the bridge loads. Samsung Xclipse (Exynos, sgpu) is experimental. */ + private static final String[] HALS = { + "/vendor/lib64/hw/vulkan.powervr.so", + "/vendor/lib64/hw/vulkan.samsung.so" + }; private static final String[] REQUIRED = { "lib/libhybris-vulkan-icd.so.0", "lib/libhybris-common.so.1", "lib/libhybris/linker/q.so", "lib/libVkLayer_hybris_compat.so", @@ -34,9 +38,28 @@ public final class SystemVulkanDriver { "dxvk/x64/d3d11.dll", "dxvk/x64/dxgi.dll" }; + /** Rootfs directory of the Xclipse layer the apk stages (SessionFiles). */ + private static final String XCLIPSE_LAYER_DIR = "/usr/local/lib/droiddeck-xclipse"; + private SystemVulkanDriver() {} - public static boolean isPowerVr() { return new File(PVR_HAL).isFile(); } + /** The first vendor HAL present, or null. */ + public static String hal() { + for (String path : HALS) if (new File(path).isFile()) return path; + return null; + } + + /** Samsung's Xclipse (Exynos, sgpu) rather than PowerVR. */ + public static boolean isXclipse() { + String hal = hal(); + return hal != null && hal.endsWith("/vulkan.samsung.so"); + } + + /** The system-driver GPU family, for labels. */ + public static String gpuName() { return isXclipse() ? "Xclipse" : "PowerVR"; } + + /** Whether a system-driver GPU (PowerVR or Xclipse) is present; the name predates Xclipse. */ + public static boolean isPowerVr() { return hal() != null; } /** HAL detection names the GPU; launching also needs the real DRM render node. */ public static boolean isAvailable() { return isPowerVr() && renderNodeAvailable(); } @@ -77,7 +100,8 @@ public final class SystemVulkanDriver { } static boolean powerVrDriver(String driver) { - return "pvrsrvkm".equals(driver) || "pvr".equals(driver) || "powervr".equals(driver); + return "pvrsrvkm".equals(driver) || "pvr".equals(driver) || "powervr".equals(driver) + || "sgpu".equals(driver); } static boolean powerVrDevice(File device) { @@ -200,12 +224,22 @@ public final class SystemVulkanDriver { String node = renderNodePath(); if (node == null) throw new IllegalStateException("System PowerVR render node is unavailable"); environment.add("HYBRIS_ANDROID_SDK_VERSION=" + Build.VERSION.SDK_INT); - environment.add("HYBRIS_VULKAN_HAL=" + PVR_HAL); + environment.add("HYBRIS_VULKAN_HAL=" + hal()); environment.add("HYBRIS_LINKER_DIR=" + new File(directory, "lib/libhybris/linker").getPath()); // Prefer framework dependencies for the system Vulkan loader, including libbase.so. environment.add("HYBRIS_LD_LIBRARY_PATH=/system/lib64:/system_ext/lib64:/vendor/lib64/egl:/vendor/lib64/hw:/vendor/lib64"); - environment.add("VK_LAYER_PATH=" + new File(directory, "lib").getPath()); - environment.add("VK_INSTANCE_LAYERS=VK_LAYER_HYBRIS_compat"); + boolean xclipse = isXclipse(); + // Xclipse gets its own layer (tools/linuxfs/xclipse-layer) in front of everything: it sets + // bionic's environ for the driver's shader compiler, among other driver workarounds. The + // session's own clients need nothing else; games keep the compat layer's BC emulation. + String layers = xclipse ? "VK_LAYER_DROIDDECK_xclipse" : "VK_LAYER_HYBRIS_compat"; + environment.add("VK_LAYER_PATH=" + new File(directory, "lib").getPath() + (xclipse ? ":" + XCLIPSE_LAYER_DIR : "")); + environment.add("VK_INSTANCE_LAYERS=" + layers); + // Proton game launches add their WSI layer in front of these (droiddeck-game-env). + environment.add("DROIDDECK_HYBRIS_LAYERS=" + (xclipse ? layers + ":VK_LAYER_HYBRIS_compat" : layers)); + if (xclipse) environment.add("DROIDDECK_HYBRIS_LAYER_PATH=" + XCLIPSE_LAYER_DIR); + // DXVK 3 needs 256 bytes of push constants; this driver has 128. + if (xclipse) environment.add("DROIDDECK_HYBRIS_DXVK=dxvk-2.6.2"); // The Android driver has a render node, but no KMS primary node. SDL presents // through the wrapper's Wayland Vulkan swapchain instead of a KMS-style path. environment.add("BL_GAMESCOPE_BACKEND=sdl"); diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionFiles.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionFiles.kt index 6c6ed9d8b..923143a33 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionFiles.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionFiles.kt @@ -130,6 +130,10 @@ object SessionFiles { "usr/local/lib/mangoapp/libtraceevent.so.1", "usr/local/lib/mangoapp/libtracefs.so.1", ).map { it to it } + // Samsung Xclipse driver workarounds (tools/linuxfs/xclipse-layer), the Vulkan layer + // SystemVulkanDriver enables on that GPU. + val xclipse = listOf("libVkLayer_droiddeck_xclipse.so", "VkLayer_droiddeck_xclipse.json") + .map { "usr/local/lib/droiddeck-xclipse/$it" } // What the Windows components installer reads .msi packages with (tools/msitools). val msitools = listOf("msiinfo", "cabextract", "7z", "7z.so", "libmsi-1.0.so.0", "libgsf-1.so.114", "libgcab-1.0.so.0") .map { "usr/local/lib/droiddeck-msitools/$it" } + @@ -141,7 +145,7 @@ object SessionFiles { "usr/local/bin/gamescope" to "usr/local/bin/gamescope", "usr/local/lib/droiddeck/uruntime" to "usr/local/lib/droiddeck/uruntime", "usr/local/share/licenses/uruntime/LICENSE" to "usr/local/share/licenses/uruntime/LICENSE", - ) + mangoapp + msitools.map { it to it } + fexPreloads).filter { (asset, _) -> + ) + mangoapp + xclipse.map { it to it } + msitools.map { it to it } + fexPreloads).filter { (asset, _) -> val dir = asset.substringBeforeLast('/') runCatching { context.assets.list("linuxfs/$dir")?.contains(asset.substringAfterLast('/')) == true }.getOrDefault(false) } diff --git a/app/src/test/java/com/droiddeck/launcher/gpu/SystemVulkanSelectionTest.kt b/app/src/test/java/com/droiddeck/launcher/gpu/SystemVulkanSelectionTest.kt index f1891d9df..29a25037f 100644 --- a/app/src/test/java/com/droiddeck/launcher/gpu/SystemVulkanSelectionTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/gpu/SystemVulkanSelectionTest.kt @@ -35,6 +35,16 @@ class SystemVulkanSelectionTest { assertFalse(name, SystemVulkanDriver.powerVrDriver(name)) } + @Test fun samsungXclipseGpuNodeIsSelectedOverItsDisplayNode() { + assertTrue(SystemVulkanDriver.powerVrDriver("sgpu")) + assertFalse("the display controller is not the GPU", SystemVulkanDriver.powerVrDriver("exynos-drm")) + // Galaxy A56 (Exynos 1580): the GPU owns renderD128, the display controller renderD129. + val drivers = mapOf("/dev/dri/renderD128" to "sgpu", "/dev/dri/renderD129" to "exynos-drm") + assertEquals("/dev/dri/renderD128", SystemVulkanDriver.findRenderNode(null) { path -> + SystemVulkanDriver.powerVrDriver(drivers[path]) + }) + } + @Test fun renderNodeIdentityUsesTheDriverLinkOrUevent() { val device = Files.createTempDirectory("powervr-device").toFile() try { diff --git a/tools/build_local.sh b/tools/build_local.sh index 36bceae26..07af63b2a 100755 --- a/tools/build_local.sh +++ b/tools/build_local.sh @@ -13,7 +13,7 @@ else fi sdk_dir=${ANDROID_HOME:-${ANDROID_SDK_ROOT:-"${HOME}/Library/Android/sdk"}} java_dir=${JAVA_HOME:-"/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home"} -image_name=${DROIDDECK_BUILD_IMAGE:-droiddeck-local-cross:24.04-v3} +image_name=${DROIDDECK_BUILD_IMAGE:-droiddeck-local-cross:24.04-v4} build_variant=${DROIDDECK_BUILD_VARIANT:-release} case "$build_variant" in debug) gradle_task=assembleDebug ;; @@ -100,6 +100,11 @@ docker run --rm --platform linux/amd64 \ aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \ -o "$d/libssbs.so" tools/linuxfs/ssbs/ssbs_adapter.c -ldl aarch64-linux-gnu-strip --strip-unneeded "$d/libssbs.so" + mkdir -p "$d/usr/local/lib/droiddeck-xclipse" + aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -Wextra -Wno-unused-parameter -fvisibility=hidden -pthread \ + -o "$d/usr/local/lib/droiddeck-xclipse/libVkLayer_droiddeck_xclipse.so" tools/linuxfs/xclipse-layer/xclipse_layer.c -ldl + aarch64-linux-gnu-strip --strip-unneeded "$d/usr/local/lib/droiddeck-xclipse/libVkLayer_droiddeck_xclipse.so" + install -m644 tools/linuxfs/xclipse-layer/VkLayer_droiddeck_xclipse.json "$d/usr/local/lib/droiddeck-xclipse/" mkdir -p "$d/usr/local/bin" aarch64-linux-gnu-gcc -O2 -Wall -Wextra -o "$d/usr/local/bin/droiddeck-clipboard" tools/linuxfs/clipboard/clipboard.c -ldl aarch64-linux-gnu-strip --strip-unneeded "$d/usr/local/bin/droiddeck-clipboard" diff --git a/tools/hybris/run-proton.py b/tools/hybris/run-proton.py index 4349f029f..c8c1b0eb4 100644 --- a/tools/hybris/run-proton.py +++ b/tools/hybris/run-proton.py @@ -11,6 +11,7 @@ change synchronization settings, or provide game-specific workarounds. """ import argparse +import os from pathlib import Path import sys @@ -58,7 +59,12 @@ def main(): parser.error("expected: PROTON waitforexitandrun GAME [ARGUMENTS...]") proton = Path(args.proton) - dxvk = Path(__file__).resolve().parent / "dxvk" + # A sibling payload directory by name (dxvk-2.6.2 for Xclipse); never a path. + name = os.environ.get("DROIDDECK_HYBRIS_DXVK") or "dxvk" + if not name.startswith("dxvk") or "/" in name or name.startswith("."): + print(f"DroidDeck DXVK profile: invalid payload name {name!r}", file=sys.stderr) + return 1 + dxvk = Path(__file__).resolve().parent / name try: code = prepare(proton, dxvk) except (OSError, ValueError, SyntaxError) as error: diff --git a/tools/linuxfs/check-preloads.sh b/tools/linuxfs/check-preloads.sh index 6e9e1dcff..f1ae1df91 100644 --- a/tools/linuxfs/check-preloads.sh +++ b/tools/linuxfs/check-preloads.sh @@ -31,5 +31,10 @@ SSBS=$(syms "$D/libssbs.so") for sym in sigaction signal bsd_signal sysv_signal; do grep -qx "$sym" <<<"$SSBS" || { echo "::error::libssbs.so does not export $sym"; exit 1; } done +XCLIPSE=$(syms "$D/usr/local/lib/droiddeck-xclipse/libVkLayer_droiddeck_xclipse.so") +for sym in vkNegotiateLoaderLayerInterfaceVersion vkGetInstanceProcAddr vkGetDeviceProcAddr; do + grep -qx "$sym" <<<"$XCLIPSE" || { echo "::error::the Xclipse layer does not export $sym"; exit 1; } +done +test -f "$D/usr/local/lib/droiddeck-xclipse/VkLayer_droiddeck_xclipse.json" test -f "$D/usr/local/bin/droiddeck-session" test -f "$D/usr/local/bin/droiddeck-proton-extra" diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env b/tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env index 15bb5e493..3e5256ac3 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env @@ -287,10 +287,14 @@ def system_graphics_launch(command, env): directory = Path(runtime) if not directory.is_absolute(): raise SystemGraphicsError("system Vulkan runtime path is not absolute") + # run-proton.py picks the same payload by name; Xclipse needs a DXVK within 128 bytes of push data. + payload = env.get("DROIDDECK_HYBRIS_DXVK") or "dxvk" + if not payload.startswith("dxvk") or "/" in payload: + raise SystemGraphicsError("invalid DXVK payload name: %s" % payload) required = ( "run-proton.py", "wsi/libVkLayer_window_system_integration.so", "wsi/VkLayer_window_system_integration.json", "lib/libVkLayer_hybris_compat.so", - *("dxvk/%s/%s.dll" % (arch, name) for arch in ("x32", "x64") + *("%s/%s/%s.dll" % (payload, arch, name) for arch in ("x32", "x64") for name in ("d3d8", "d3d9", "d3d10core", "d3d11", "dxgi")), ) try: @@ -304,10 +308,12 @@ def system_graphics_launch(command, env): except OSError as error: raise SystemGraphicsError("system Vulkan component is unreadable: %s" % error) from error result = dict(env) + layers = "VK_LAYER_window_system_integration:" + env.get("DROIDDECK_HYBRIS_LAYERS", "VK_LAYER_HYBRIS_compat") result.update({ "HYBRIS_BC_TEXTURES": "dxvk", - "VK_LAYER_PATH": "%s:%s" % (directory / "wsi", directory / "lib"), - "VK_INSTANCE_LAYERS": "VK_LAYER_window_system_integration:VK_LAYER_HYBRIS_compat", + "VK_LAYER_PATH": ":".join(filter(None, (str(directory / "wsi"), str(directory / "lib"), + env.get("DROIDDECK_HYBRIS_LAYER_PATH")))), + "VK_INSTANCE_LAYERS": layers, "ENABLE_GAMESCOPE_WSI": "0", "VK_LOADER_LAYERS_DISABLE": "*gamescope*", "DISABLE_VK_LAYER_VALVE_steam_fossilize_1": "1", diff --git a/tools/linuxfs/xclipse-layer/VkLayer_droiddeck_xclipse.json b/tools/linuxfs/xclipse-layer/VkLayer_droiddeck_xclipse.json new file mode 100644 index 000000000..888d0a89d --- /dev/null +++ b/tools/linuxfs/xclipse-layer/VkLayer_droiddeck_xclipse.json @@ -0,0 +1,11 @@ +{ + "file_format_version": "1.2.0", + "layer": { + "name": "VK_LAYER_DROIDDECK_xclipse", + "type": "GLOBAL", + "library_path": "./libVkLayer_droiddeck_xclipse.so", + "api_version": "1.3.0", + "implementation_version": "1", + "description": "Workarounds for Samsung Xclipse drivers loaded through libhybris" + } +} diff --git a/tools/linuxfs/xclipse-layer/device_chain.inc b/tools/linuxfs/xclipse-layer/device_chain.inc new file mode 100644 index 000000000..e8a131411 --- /dev/null +++ b/tools/linuxfs/xclipse-layer/device_chain.inc @@ -0,0 +1,180 @@ +/* Generated by gen_device_chain.py from Vulkan-Headers 1.3.275; do not edit. */ +DEVICE_CHAIN(VK_STRUCTURE_TYPE_DEVICE_DEVICE_MEMORY_REPORT_CREATE_INFO_EXT, VkDeviceDeviceMemoryReportCreateInfoEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV, VkDeviceDiagnosticsConfigCreateInfoNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO, VkDeviceGroupDeviceCreateInfo) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_DEVICE_MEMORY_OVERALLOCATION_CREATE_INFO_AMD, VkDeviceMemoryOverallocationCreateInfoAMD) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_DEVICE_PRIVATE_DATA_CREATE_INFO, VkDevicePrivateDataCreateInfo) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_DEVICE_QUEUE_SHADER_CORE_CONTROL_CREATE_INFO_ARM, VkDeviceQueueShaderCoreControlCreateInfoARM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES, VkPhysicalDevice16BitStorageFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_4444_FORMATS_FEATURES_EXT, VkPhysicalDevice4444FormatsFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES, VkPhysicalDevice8BitStorageFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR, VkPhysicalDeviceAccelerationStructureFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ADDRESS_BINDING_REPORT_FEATURES_EXT, VkPhysicalDeviceAddressBindingReportFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_AMIGO_PROFILING_FEATURES_SEC, VkPhysicalDeviceAmigoProfilingFeaturesSEC) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ASTC_DECODE_FEATURES_EXT, VkPhysicalDeviceASTCDecodeFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_FEATURES_EXT, VkPhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT, VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_FEATURES_EXT, VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BORDER_COLOR_SWIZZLE_FEATURES_EXT, VkPhysicalDeviceBorderColorSwizzleFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES, VkPhysicalDeviceBufferDeviceAddressFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT, VkPhysicalDeviceBufferDeviceAddressFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CLUSTER_CULLING_SHADER_FEATURES_HUAWEI, VkPhysicalDeviceClusterCullingShaderFeaturesHUAWEI) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COHERENT_MEMORY_FEATURES_AMD, VkPhysicalDeviceCoherentMemoryFeaturesAMD) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COLOR_WRITE_ENABLE_FEATURES_EXT, VkPhysicalDeviceColorWriteEnableFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COMPUTE_SHADER_DERIVATIVES_FEATURES_NV, VkPhysicalDeviceComputeShaderDerivativesFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONDITIONAL_RENDERING_FEATURES_EXT, VkPhysicalDeviceConditionalRenderingFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR, VkPhysicalDeviceCooperativeMatrixFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_NV, VkPhysicalDeviceCooperativeMatrixFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COPY_MEMORY_INDIRECT_FEATURES_NV, VkPhysicalDeviceCopyMemoryIndirectFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CORNER_SAMPLED_IMAGE_FEATURES_NV, VkPhysicalDeviceCornerSampledImageFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COVERAGE_REDUCTION_MODE_FEATURES_NV, VkPhysicalDeviceCoverageReductionModeFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUBIC_CLAMP_FEATURES_QCOM, VkPhysicalDeviceCubicClampFeaturesQCOM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUBIC_WEIGHTS_FEATURES_QCOM, VkPhysicalDeviceCubicWeightsFeaturesQCOM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUDA_KERNEL_LAUNCH_FEATURES_NV, VkPhysicalDeviceCudaKernelLaunchFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT, VkPhysicalDeviceCustomBorderColorFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEDICATED_ALLOCATION_IMAGE_ALIASING_FEATURES_NV, VkPhysicalDeviceDedicatedAllocationImageAliasingFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_BIAS_CONTROL_FEATURES_EXT, VkPhysicalDeviceDepthBiasControlFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLAMP_ZERO_ONE_FEATURES_EXT, VkPhysicalDeviceDepthClampZeroOneFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT, VkPhysicalDeviceDepthClipControlFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT, VkPhysicalDeviceDepthClipEnableFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT, VkPhysicalDeviceDescriptorBufferFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES, VkPhysicalDeviceDescriptorIndexingFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_POOL_OVERALLOCATION_FEATURES_NV, VkPhysicalDeviceDescriptorPoolOverallocationFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_SET_HOST_MAPPING_FEATURES_VALVE, VkPhysicalDeviceDescriptorSetHostMappingFeaturesVALVE) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_GENERATED_COMMANDS_COMPUTE_FEATURES_NV, VkPhysicalDeviceDeviceGeneratedCommandsComputeFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_GENERATED_COMMANDS_FEATURES_NV, VkPhysicalDeviceDeviceGeneratedCommandsFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_MEMORY_REPORT_FEATURES_EXT, VkPhysicalDeviceDeviceMemoryReportFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DIAGNOSTICS_CONFIG_FEATURES_NV, VkPhysicalDeviceDiagnosticsConfigFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES, VkPhysicalDeviceDynamicRenderingFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_FEATURES_EXT, VkPhysicalDeviceDynamicRenderingUnusedAttachmentsFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXCLUSIVE_SCISSOR_FEATURES_NV, VkPhysicalDeviceExclusiveScissorFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT, VkPhysicalDeviceExtendedDynamicState2FeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT, VkPhysicalDeviceExtendedDynamicState3FeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT, VkPhysicalDeviceExtendedDynamicStateFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_SPARSE_ADDRESS_SPACE_FEATURES_NV, VkPhysicalDeviceExtendedSparseAddressSpaceFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_RDMA_FEATURES_NV, VkPhysicalDeviceExternalMemoryRDMAFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT, VkPhysicalDeviceFaultFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, VkPhysicalDeviceFeatures2) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_2_FEATURES_EXT, VkPhysicalDeviceFragmentDensityMap2FeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_FEATURES_EXT, VkPhysicalDeviceFragmentDensityMapFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_OFFSET_FEATURES_QCOM, VkPhysicalDeviceFragmentDensityMapOffsetFeaturesQCOM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_BARYCENTRIC_FEATURES_KHR, VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT, VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_ENUMS_FEATURES_NV, VkPhysicalDeviceFragmentShadingRateEnumsFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR, VkPhysicalDeviceFragmentShadingRateFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAME_BOUNDARY_FEATURES_EXT, VkPhysicalDeviceFrameBoundaryFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GLOBAL_PRIORITY_QUERY_FEATURES_KHR, VkPhysicalDeviceGlobalPriorityQueryFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT, VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_FEATURES_EXT, VkPhysicalDeviceHostImageCopyFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES, VkPhysicalDeviceHostQueryResetFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES, VkPhysicalDeviceImagelessFramebufferFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_2D_VIEW_OF_3D_FEATURES_EXT, VkPhysicalDeviceImage2DViewOf3DFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_FEATURES_EXT, VkPhysicalDeviceImageCompressionControlFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_FEATURES_EXT, VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_PROCESSING_2_FEATURES_QCOM, VkPhysicalDeviceImageProcessing2FeaturesQCOM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_PROCESSING_FEATURES_QCOM, VkPhysicalDeviceImageProcessingFeaturesQCOM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_ROBUSTNESS_FEATURES, VkPhysicalDeviceImageRobustnessFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_SLICED_VIEW_OF_3D_FEATURES_EXT, VkPhysicalDeviceImageSlicedViewOf3DFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_VIEW_MIN_LOD_FEATURES_EXT, VkPhysicalDeviceImageViewMinLodFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT, VkPhysicalDeviceIndexTypeUint8FeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INHERITED_VIEWPORT_SCISSOR_FEATURES_NV, VkPhysicalDeviceInheritedViewportScissorFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES, VkPhysicalDeviceInlineUniformBlockFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INVOCATION_MASK_FEATURES_HUAWEI, VkPhysicalDeviceInvocationMaskFeaturesHUAWEI) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LEGACY_DITHERING_FEATURES_EXT, VkPhysicalDeviceLegacyDitheringFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINEAR_COLOR_ATTACHMENT_FEATURES_NV, VkPhysicalDeviceLinearColorAttachmentFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT, VkPhysicalDeviceLineRasterizationFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_FEATURES, VkPhysicalDeviceMaintenance4Features) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES_KHR, VkPhysicalDeviceMaintenance5FeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_6_FEATURES_KHR, VkPhysicalDeviceMaintenance6FeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_DECOMPRESSION_FEATURES_NV, VkPhysicalDeviceMemoryDecompressionFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PRIORITY_FEATURES_EXT, VkPhysicalDeviceMemoryPriorityFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT, VkPhysicalDeviceMeshShaderFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_NV, VkPhysicalDeviceMeshShaderFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_FEATURES_EXT, VkPhysicalDeviceMultisampledRenderToSingleSampledFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES, VkPhysicalDeviceMultiviewFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PER_VIEW_RENDER_AREAS_FEATURES_QCOM, VkPhysicalDeviceMultiviewPerViewRenderAreasFeaturesQCOM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PER_VIEW_VIEWPORTS_FEATURES_QCOM, VkPhysicalDeviceMultiviewPerViewViewportsFeaturesQCOM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_FEATURES_EXT, VkPhysicalDeviceMultiDrawFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MUTABLE_DESCRIPTOR_TYPE_FEATURES_EXT, VkPhysicalDeviceMutableDescriptorTypeFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NESTED_COMMAND_BUFFER_FEATURES_EXT, VkPhysicalDeviceNestedCommandBufferFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NON_SEAMLESS_CUBE_MAP_FEATURES_EXT, VkPhysicalDeviceNonSeamlessCubeMapFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_OPACITY_MICROMAP_FEATURES_EXT, VkPhysicalDeviceOpacityMicromapFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_OPTICAL_FLOW_FEATURES_NV, VkPhysicalDeviceOpticalFlowFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PAGEABLE_DEVICE_LOCAL_MEMORY_FEATURES_EXT, VkPhysicalDevicePageableDeviceLocalMemoryFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR, VkPhysicalDevicePerformanceQueryFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PER_STAGE_DESCRIPTOR_SET_FEATURES_NV, VkPhysicalDevicePerStageDescriptorSetFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_CREATION_CACHE_CONTROL_FEATURES, VkPhysicalDevicePipelineCreationCacheControlFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR, VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_LIBRARY_GROUP_HANDLES_FEATURES_EXT, VkPhysicalDevicePipelineLibraryGroupHandlesFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_PROPERTIES_FEATURES_EXT, VkPhysicalDevicePipelinePropertiesFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_PROTECTED_ACCESS_FEATURES_EXT, VkPhysicalDevicePipelineProtectedAccessFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES_EXT, VkPhysicalDevicePipelineRobustnessFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_BARRIER_FEATURES_NV, VkPhysicalDevicePresentBarrierFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR, VkPhysicalDevicePresentIdFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR, VkPhysicalDevicePresentWaitFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT, VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT, VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIVATE_DATA_FEATURES, VkPhysicalDevicePrivateDataFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES, VkPhysicalDeviceProtectedMemoryFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT, VkPhysicalDeviceProvokingVertexFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT, VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR, VkPhysicalDeviceRayQueryFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_INVOCATION_REORDER_FEATURES_NV, VkPhysicalDeviceRayTracingInvocationReorderFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_MAINTENANCE_1_FEATURES_KHR, VkPhysicalDeviceRayTracingMaintenance1FeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_MOTION_BLUR_FEATURES_NV, VkPhysicalDeviceRayTracingMotionBlurFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR, VkPhysicalDeviceRayTracingPipelineFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_POSITION_FETCH_FEATURES_KHR, VkPhysicalDeviceRayTracingPositionFetchFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RELAXED_LINE_RASTERIZATION_FEATURES_IMG, VkPhysicalDeviceRelaxedLineRasterizationFeaturesIMG) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RENDER_PASS_STRIPED_FEATURES_ARM, VkPhysicalDeviceRenderPassStripedFeaturesARM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_REPRESENTATIVE_FRAGMENT_TEST_FEATURES_NV, VkPhysicalDeviceRepresentativeFragmentTestFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RGBA10X6_FORMATS_FEATURES_EXT, VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT, VkPhysicalDeviceRobustness2FeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, VkPhysicalDeviceSamplerYcbcrConversionFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES, VkPhysicalDeviceScalarBlockLayoutFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCHEDULING_CONTROLS_FEATURES_ARM, VkPhysicalDeviceSchedulingControlsFeaturesARM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SEPARATE_DEPTH_STENCIL_LAYOUTS_FEATURES, VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_2_FEATURES_EXT, VkPhysicalDeviceShaderAtomicFloat2FeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_FEATURES_EXT, VkPhysicalDeviceShaderAtomicFloatFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_INT64_FEATURES, VkPhysicalDeviceShaderAtomicInt64Features) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CLOCK_FEATURES_KHR, VkPhysicalDeviceShaderClockFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CORE_BUILTINS_FEATURES_ARM, VkPhysicalDeviceShaderCoreBuiltinsFeaturesARM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DEMOTE_TO_HELPER_INVOCATION_FEATURES, VkPhysicalDeviceShaderDemoteToHelperInvocationFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES, VkPhysicalDeviceShaderDrawParametersFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_EARLY_AND_LATE_FRAGMENT_TESTS_FEATURES_AMD, VkPhysicalDeviceShaderEarlyAndLateFragmentTestsFeaturesAMD) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES, VkPhysicalDeviceShaderFloat16Int8Features) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_IMAGE_ATOMIC_INT64_FEATURES_EXT, VkPhysicalDeviceShaderImageAtomicInt64FeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_IMAGE_FOOTPRINT_FEATURES_NV, VkPhysicalDeviceShaderImageFootprintFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_DOT_PRODUCT_FEATURES, VkPhysicalDeviceShaderIntegerDotProductFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_FUNCTIONS_2_FEATURES_INTEL, VkPhysicalDeviceShaderIntegerFunctions2FeaturesINTEL) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_MODULE_IDENTIFIER_FEATURES_EXT, VkPhysicalDeviceShaderModuleIdentifierFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT, VkPhysicalDeviceShaderObjectFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SM_BUILTINS_FEATURES_NV, VkPhysicalDeviceShaderSMBuiltinsFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES, VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_UNIFORM_CONTROL_FLOW_FEATURES_KHR, VkPhysicalDeviceShaderSubgroupUniformControlFlowFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_TERMINATE_INVOCATION_FEATURES, VkPhysicalDeviceShaderTerminateInvocationFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_TILE_IMAGE_FEATURES_EXT, VkPhysicalDeviceShaderTileImageFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADING_RATE_IMAGE_FEATURES_NV, VkPhysicalDeviceShadingRateImageFeaturesNV) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES, VkPhysicalDeviceSubgroupSizeControlFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBPASS_MERGE_FEEDBACK_FEATURES_EXT, VkPhysicalDeviceSubpassMergeFeedbackFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBPASS_SHADING_FEATURES_HUAWEI, VkPhysicalDeviceSubpassShadingFeaturesHUAWEI) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT, VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES, VkPhysicalDeviceSynchronization2Features) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_FEATURES_EXT, VkPhysicalDeviceTexelBufferAlignmentFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXTURE_COMPRESSION_ASTC_HDR_FEATURES, VkPhysicalDeviceTextureCompressionASTCHDRFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TILE_PROPERTIES_FEATURES_QCOM, VkPhysicalDeviceTilePropertiesFeaturesQCOM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES, VkPhysicalDeviceTimelineSemaphoreFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT, VkPhysicalDeviceTransformFeedbackFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES, VkPhysicalDeviceUniformBufferStandardLayoutFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES, VkPhysicalDeviceVariablePointersFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_KHR, VkPhysicalDeviceVertexAttributeDivisorFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_INPUT_DYNAMIC_STATE_FEATURES_EXT, VkPhysicalDeviceVertexInputDynamicStateFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_MAINTENANCE_1_FEATURES_KHR, VkPhysicalDeviceVideoMaintenance1FeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, VkPhysicalDeviceVulkan11Features) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, VkPhysicalDeviceVulkan12Features) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, VkPhysicalDeviceVulkan13Features) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES, VkPhysicalDeviceVulkanMemoryModelFeatures) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_FEATURES_KHR, VkPhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_2_PLANE_444_FORMATS_FEATURES_EXT, VkPhysicalDeviceYcbcr2Plane444FormatsFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_DEGAMMA_FEATURES_QCOM, VkPhysicalDeviceYcbcrDegammaFeaturesQCOM) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_IMAGE_ARRAYS_FEATURES_EXT, VkPhysicalDeviceYcbcrImageArraysFeaturesEXT) +DEVICE_CHAIN(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ZERO_INITIALIZE_WORKGROUP_MEMORY_FEATURES, VkPhysicalDeviceZeroInitializeWorkgroupMemoryFeatures) diff --git a/tools/linuxfs/xclipse-layer/gen_device_chain.py b/tools/linuxfs/xclipse-layer/gen_device_chain.py new file mode 100755 index 000000000..3ef47693c --- /dev/null +++ b/tools/linuxfs/xclipse-layer/gen_device_chain.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Generate device_chain.inc: the structures a VkDeviceCreateInfo chain may hold, with their sizes. + +The Xclipse layer copies the chain ahead of the VkPhysicalDeviceFeatures2 it edits, and copying a +structure needs its size. Run against the Vulkan-Headers release the build compiles with (Ubuntu +24.04's libvulkan-dev is 1.3.275): + + gen_device_chain.py Vulkan-Headers/registry/vk.xml Vulkan-Headers/include/vulkan/vulkan_core.h +""" +import re +import sys +import xml.etree.ElementTree as ElementTree +from pathlib import Path + + +def main(registry, core_header, output): + core = Path(core_header).read_text() + root = ElementTree.parse(registry).getroot() + entries = [] + for struct in root.iter("type"): + if struct.get("category") != "struct" or struct.get("alias"): + continue + if "VkDeviceCreateInfo" not in (struct.get("structextends") or "").split(","): + continue + name = struct.get("name") + stype = next((member.get("values") for member in struct.findall("member") + if member.findtext("name") == "sType"), None) + # Platform and provisional structures live outside vulkan_core.h; the layer never builds them. + if not stype or not re.search(r"\}\s*" + re.escape(name) + r"\s*;", core): + continue + entries.append((stype, name)) + version = re.search(r"#define VK_HEADER_VERSION (\d+)", core).group(1) + lines = ["/* Generated by gen_device_chain.py from Vulkan-Headers 1.3.%s; do not edit. */" % version] + lines += ["DEVICE_CHAIN(%s, %s)" % entry for entry in sorted(entries)] + Path(output).write_text("\n".join(lines) + "\n") + print("%d structures" % len(entries)) + + +if __name__ == "__main__": + if len(sys.argv) not in (3, 4): + sys.exit(__doc__) + main(sys.argv[1], sys.argv[2], sys.argv[3] if len(sys.argv) == 4 else + Path(__file__).with_name("device_chain.inc")) diff --git a/tools/linuxfs/xclipse-layer/xclipse_layer.c b/tools/linuxfs/xclipse-layer/xclipse_layer.c new file mode 100644 index 000000000..68c3379dd --- /dev/null +++ b/tools/linuxfs/xclipse-layer/xclipse_layer.c @@ -0,0 +1,493 @@ +/* + * VK_LAYER_DROIDDECK_xclipse: workarounds for Samsung's Xclipse driver loaded through libhybris. + * + * 1. bionic environ. A glibc process never initializes bionic's `environ`; the driver's + * AMD-derived shader compiler walks it for AMD_SC_* settings and crashed on the NULL. + * Once instance creation has loaded the driver, point it at a snapshot of ours. + * 2. YCbCr immutable samplers in compute. Compute pipelines whose layout binds an array of + * immutable YCbCr-conversion samplers fail to compile (VK_ERROR_UNKNOWN); gamescope's + * composite shaders all bind one. Such bindings get a plain linear sampler instead, so only + * multi-planar (NV12) layers sample incorrectly. + * 3. Calibrated timestamps. The driver has only VK_EXT_calibrated_timestamps, but the bridge + * lists the KHR names too, and Wine calls those; the loader then aborts (it took down Wine's + * explorer.exe and with it every game window). The KHR entry points forward to the EXT ones. + * 4. BC4/BC5 filtering. The libhybris compat layer emulates BC textures; BC4/BC5 decode into + * R16/R16G16 images, for which this driver advertises no linear filtering, so DXVK would + * treat them as unfilterable. The layer reports linear filtering for BC4/BC5 where they are + * sampled at all (RDNA filters 16-bit normalized formats; at worst they sample unfiltered). + * 5. textureCompressionBC. The compat layer emulates BC formats but keeps the feature bit off; + * DXVK before 3.0 (the last that fits this driver's 128 bytes of push constants) requires + * it. The layer reports it while BC formats are sampleable and clears it at device creation. + * Everything else passes straight through. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#define EXPORT __attribute__((visibility("default"))) +#define MAX_OBJECTS 64 + +extern char **environ; + +/* The loader's dispatch table pointer heads every dispatchable object: an instance shares it + * with its physical devices, a device with its queues and command buffers. */ +typedef void *dispatch_key; +static dispatch_key key_of(const void *object) { return *(dispatch_key *)object; } + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static int debug; + +struct instance_data { + dispatch_key key; + VkInstance handle; + PFN_vkGetInstanceProcAddr next; + PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT time_domains; + PFN_vkGetPhysicalDeviceFeatures features; + PFN_vkGetPhysicalDeviceFeatures2 features2; + PFN_vkGetPhysicalDeviceFormatProperties format_properties; + PFN_vkGetPhysicalDeviceFormatProperties2 format_properties2; +}; +struct device_data { + dispatch_key key; + VkDevice handle; + PFN_vkGetDeviceProcAddr next; + PFN_vkCreateSampler create_sampler; + PFN_vkDestroySampler destroy_sampler; + PFN_vkCreateDescriptorSetLayout create_set_layout; + PFN_vkDestroyDevice destroy_device; + PFN_vkGetCalibratedTimestampsEXT timestamps; + VkSampler fallback; +}; +static struct instance_data instances[MAX_OBJECTS]; +static struct device_data devices[MAX_OBJECTS]; +/* The live YCbCr samplers each device created, so set layouts can recognise them. Guarded by lock. */ +static struct ycbcr_sampler { VkDevice device; VkSampler sampler; } *ycbcr_samplers; +static size_t ycbcr_count, ycbcr_capacity; + +static struct instance_data *instance_by_key(dispatch_key key) { + for (int i = 0; i < MAX_OBJECTS; i++) + if (instances[i].handle && instances[i].key == key) return &instances[i]; + return NULL; +} + +static struct device_data *device_by_key(dispatch_key key) { + for (int i = 0; i < MAX_OBJECTS; i++) + if (devices[i].handle && devices[i].key == key) return &devices[i]; + return NULL; +} + +/* Entries are never freed while their object lives, so a looked-up copy of the pointers is safe. */ +static struct instance_data instance_of(const void *object) { + struct instance_data copy = {0}; + pthread_mutex_lock(&lock); + struct instance_data *data = instance_by_key(key_of(object)); + if (data) copy = *data; + pthread_mutex_unlock(&lock); + return copy; +} + +static struct device_data device_of(const void *object) { + struct device_data copy = {0}; + pthread_mutex_lock(&lock); + struct device_data *data = device_by_key(key_of(object)); + if (data) copy = *data; + pthread_mutex_unlock(&lock); + return copy; +} + +static void fix_bionic_environ(void) { + void *common = dlopen("libhybris-common.so.1", RTLD_NOW | RTLD_NOLOAD); + if (!common) return; /* not a libhybris driver */ + void *(*h_dlopen)(const char *, int) = (void *(*)(const char *, int))dlsym(common, "hybris_dlopen"); + void *(*h_dlsym)(void *, const char *) = (void *(*)(void *, const char *))dlsym(common, "hybris_dlsym"); + if (!h_dlopen || !h_dlsym) return; + void *libc = h_dlopen("libc.so", RTLD_NOW); + char ***bionic_environ = libc ? (char ***)h_dlsym(libc, "environ") : NULL; + if (!bionic_environ || *bionic_environ) return; + size_t count = 0; + while (environ && environ[count]) count++; + char **snapshot = calloc(count + 1, sizeof(*snapshot)); + if (!snapshot) return; + for (size_t i = 0; i < count; i++) snapshot[i] = strdup(environ[i]); + *bionic_environ = snapshot; + if (debug) fprintf(stderr, "xclipse layer: bionic environ set (%zu entries)\n", count); +} + +/* ---- instance ---- */ + +static VkResult VKAPI_CALL create_instance(const VkInstanceCreateInfo *info, const VkAllocationCallbacks *allocator, + VkInstance *instance) { + VkLayerInstanceCreateInfo *chain = (VkLayerInstanceCreateInfo *)info->pNext; + while (chain && !(chain->sType == VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO && chain->function == VK_LAYER_LINK_INFO)) + chain = (VkLayerInstanceCreateInfo *)chain->pNext; + if (!chain || !chain->u.pLayerInfo) return VK_ERROR_INITIALIZATION_FAILED; + PFN_vkGetInstanceProcAddr next = chain->u.pLayerInfo->pfnNextGetInstanceProcAddr; + chain->u.pLayerInfo = chain->u.pLayerInfo->pNext; + PFN_vkCreateInstance next_create = (PFN_vkCreateInstance)next(NULL, "vkCreateInstance"); + if (!next_create) return VK_ERROR_INITIALIZATION_FAILED; + VkResult result = next_create(info, allocator, instance); + if (result != VK_SUCCESS) return result; + fix_bionic_environ(); + struct instance_data data = { + .key = key_of(*instance), + .handle = *instance, + .next = next, + .time_domains = (PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT) + next(*instance, "vkGetPhysicalDeviceCalibrateableTimeDomainsEXT"), + .features = (PFN_vkGetPhysicalDeviceFeatures)next(*instance, "vkGetPhysicalDeviceFeatures"), + .features2 = (PFN_vkGetPhysicalDeviceFeatures2)next(*instance, "vkGetPhysicalDeviceFeatures2"), + .format_properties = (PFN_vkGetPhysicalDeviceFormatProperties)next(*instance, "vkGetPhysicalDeviceFormatProperties"), + .format_properties2 = (PFN_vkGetPhysicalDeviceFormatProperties2)next(*instance, "vkGetPhysicalDeviceFormatProperties2"), + }; + pthread_mutex_lock(&lock); + for (int i = 0; i < MAX_OBJECTS; i++) + if (!instances[i].handle) { instances[i] = data; break; } + pthread_mutex_unlock(&lock); + return result; +} + +static void VKAPI_CALL destroy_instance(VkInstance instance, const VkAllocationCallbacks *allocator) { + PFN_vkGetInstanceProcAddr next = instance_of(instance).next; + pthread_mutex_lock(&lock); + struct instance_data *data = instance_by_key(key_of(instance)); + if (data) memset(data, 0, sizeof(*data)); + pthread_mutex_unlock(&lock); + PFN_vkDestroyInstance next_destroy = next ? (PFN_vkDestroyInstance)next(instance, "vkDestroyInstance") : NULL; + if (next_destroy) next_destroy(instance, allocator); +} + +static VkResult VKAPI_CALL time_domains_khr(VkPhysicalDevice physical, uint32_t *count, VkTimeDomainEXT *domains) { + PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT ext = instance_of(physical).time_domains; + if (!ext) { *count = 0; return VK_SUCCESS; } + return ext(physical, count, domains); +} + +/* BC formats sampleable below us although the driver's feature bit is off: the compat layer emulates them. */ +static int bc_emulated(VkPhysicalDevice physical) { + struct instance_data data = instance_of(physical); + if (!data.features || !data.format_properties) return 0; + VkPhysicalDeviceFeatures native; + data.features(physical, &native); + if (native.textureCompressionBC) return 0; + VkFormatProperties bc1, bc7; + data.format_properties(physical, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, &bc1); + data.format_properties(physical, VK_FORMAT_BC7_UNORM_BLOCK, &bc7); + return (bc1.optimalTilingFeatures & bc7.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0; +} + +static void VKAPI_CALL get_features(VkPhysicalDevice physical, VkPhysicalDeviceFeatures *features) { + PFN_vkGetPhysicalDeviceFeatures next = instance_of(physical).features; + if (next) next(physical, features); + if (bc_emulated(physical)) features->textureCompressionBC = VK_TRUE; +} + +static void VKAPI_CALL get_features2(VkPhysicalDevice physical, VkPhysicalDeviceFeatures2 *features) { + PFN_vkGetPhysicalDeviceFeatures2 next = instance_of(physical).features2; + if (next) next(physical, features); + if (bc_emulated(physical)) features->features.textureCompressionBC = VK_TRUE; +} + +static int bc45(VkFormat format) { + return format >= VK_FORMAT_BC4_UNORM_BLOCK && format <= VK_FORMAT_BC5_SNORM_BLOCK; +} + +static void VKAPI_CALL get_format_properties(VkPhysicalDevice physical, VkFormat format, VkFormatProperties *out) { + PFN_vkGetPhysicalDeviceFormatProperties next = instance_of(physical).format_properties; + if (next) next(physical, format, out); + if (bc45(format) && (out->optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) + out->optimalTilingFeatures |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; +} + +static void VKAPI_CALL get_format_properties2(VkPhysicalDevice physical, VkFormat format, VkFormatProperties2 *out) { + PFN_vkGetPhysicalDeviceFormatProperties2 next = instance_of(physical).format_properties2; + if (next) next(physical, format, out); + if (!bc45(format)) return; + if (out->formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) + out->formatProperties.optimalTilingFeatures |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; + for (VkBaseOutStructure *p = (VkBaseOutStructure *)out->pNext; p; p = p->pNext) { + if (p->sType != VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3) continue; + VkFormatProperties3 *f3 = (VkFormatProperties3 *)p; + if (f3->optimalTilingFeatures & VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_BIT) + f3->optimalTilingFeatures |= VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_FILTER_LINEAR_BIT; + } +} + +/* ---- device ---- */ + +/* The size of each structure a VkDeviceCreateInfo chain may hold (device_chain.inc, generated from the + * Vulkan registry), plus the loader's own; 0 for anything else. */ +static size_t chain_size(VkStructureType type) { + switch (type) { +#define DEVICE_CHAIN(stype, name) case stype: return sizeof(name); +#include "device_chain.inc" +#undef DEVICE_CHAIN + case VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO: return sizeof(VkLayerDeviceCreateInfo); + default: return 0; + } +} + +struct chain_copy { void **nodes; size_t count; }; + +static void free_chain_copy(struct chain_copy *copy) { + for (size_t i = 0; i < copy->count; i++) free(copy->nodes[i]); + free(copy->nodes); + copy->nodes = NULL; + copy->count = 0; +} + +/* The chain with textureCompressionBC cleared in a copy of the VkPhysicalDeviceFeatures2 that enables + * it: every structure up to it is copied into [copy], the rest is shared. The caller's structures are + * never written. *head_out stays [head] when nothing needs clearing, or when a structure ahead of it + * is unknown to this build (newer than its Vulkan headers); the compat layer then refuses the bit. */ +static VkResult chain_without_bc(const void *head, struct chain_copy *copy, const void **head_out) { + *head_out = head; + const VkBaseInStructure *target = NULL; + size_t count = 0; + for (const VkBaseInStructure *p = head; p && !target; p = p->pNext) { + if (!chain_size(p->sType)) return VK_SUCCESS; + count++; + if (p->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 && + ((const VkPhysicalDeviceFeatures2 *)p)->features.textureCompressionBC) target = p; + } + if (!target) return VK_SUCCESS; + if (!(copy->nodes = calloc(count, sizeof(*copy->nodes)))) return VK_ERROR_OUT_OF_HOST_MEMORY; + VkBaseOutStructure *first = NULL, *last = NULL; + for (const VkBaseInStructure *p = head; ; p = p->pNext) { + size_t size = chain_size(p->sType); + VkBaseOutStructure *node = malloc(size); + if (!node) { free_chain_copy(copy); return VK_ERROR_OUT_OF_HOST_MEMORY; } + copy->nodes[copy->count++] = node; + memcpy(node, p, size); + if (last) last->pNext = node; else first = node; + last = node; + if (p == target) { + ((VkPhysicalDeviceFeatures2 *)node)->features.textureCompressionBC = VK_FALSE; + *head_out = first; /* its pNext still leads to the caller's remaining structures */ + return VK_SUCCESS; + } + } +} + +static VkResult VKAPI_CALL create_device(VkPhysicalDevice physical, const VkDeviceCreateInfo *info, + const VkAllocationCallbacks *allocator, VkDevice *device) { + VkLayerDeviceCreateInfo *chain = (VkLayerDeviceCreateInfo *)info->pNext; + while (chain && !(chain->sType == VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO && chain->function == VK_LAYER_LINK_INFO)) + chain = (VkLayerDeviceCreateInfo *)chain->pNext; + if (!chain || !chain->u.pLayerInfo) return VK_ERROR_INITIALIZATION_FAILED; + PFN_vkGetInstanceProcAddr next_gipa = chain->u.pLayerInfo->pfnNextGetInstanceProcAddr; + PFN_vkGetDeviceProcAddr next_gdpa = chain->u.pLayerInfo->pfnNextGetDeviceProcAddr; + chain->u.pLayerInfo = chain->u.pLayerInfo->pNext; + PFN_vkCreateDevice next_create = (PFN_vkCreateDevice)next_gipa(NULL, "vkCreateDevice"); + if (!next_create) return VK_ERROR_INITIALIZATION_FAILED; + + VkDeviceCreateInfo patched = *info; + /* An emulated textureCompressionBC never reaches the compat layer, which refuses the bit. */ + int strip_bc = bc_emulated(physical); + VkPhysicalDeviceFeatures plain_features; + if (strip_bc && info->pEnabledFeatures && info->pEnabledFeatures->textureCompressionBC) { + plain_features = *info->pEnabledFeatures; + plain_features.textureCompressionBC = VK_FALSE; + patched.pEnabledFeatures = &plain_features; + } + struct chain_copy copy = {0}; + if (strip_bc) { + VkResult copied = chain_without_bc(info->pNext, ©, &patched.pNext); + if (copied != VK_SUCCESS) return copied; + } + VkResult result = next_create(physical, &patched, allocator, device); + free_chain_copy(©); + if (result != VK_SUCCESS) return result; + + PFN_vkCreateSampler create_sampler = (PFN_vkCreateSampler)next_gdpa(*device, "vkCreateSampler"); + VkSamplerCreateInfo plain = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .magFilter = VK_FILTER_LINEAR, .minFilter = VK_FILTER_LINEAR, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, + }; + VkSampler fallback = VK_NULL_HANDLE; + if (create_sampler) create_sampler(*device, &plain, NULL, &fallback); + struct device_data data = { + .key = key_of(*device), + .handle = *device, + .next = next_gdpa, + .create_sampler = create_sampler, + .destroy_sampler = (PFN_vkDestroySampler)next_gdpa(*device, "vkDestroySampler"), + .create_set_layout = (PFN_vkCreateDescriptorSetLayout)next_gdpa(*device, "vkCreateDescriptorSetLayout"), + .destroy_device = (PFN_vkDestroyDevice)next_gdpa(*device, "vkDestroyDevice"), + .timestamps = (PFN_vkGetCalibratedTimestampsEXT)next_gdpa(*device, "vkGetCalibratedTimestampsEXT"), + .fallback = fallback, + }; + pthread_mutex_lock(&lock); + for (int i = 0; i < MAX_OBJECTS; i++) + if (!devices[i].handle) { devices[i] = data; break; } + pthread_mutex_unlock(&lock); + return result; +} + +static void VKAPI_CALL destroy_device(VkDevice device, const VkAllocationCallbacks *allocator) { + struct device_data data = device_of(device); + pthread_mutex_lock(&lock); + struct device_data *entry = device_by_key(key_of(device)); + if (entry) memset(entry, 0, sizeof(*entry)); + for (size_t k = 0; k < ycbcr_count;) + if (ycbcr_samplers[k].device == device) ycbcr_samplers[k] = ycbcr_samplers[--ycbcr_count]; + else k++; + pthread_mutex_unlock(&lock); + if (data.fallback && data.destroy_sampler) data.destroy_sampler(device, data.fallback, NULL); + if (data.destroy_device) data.destroy_device(device, allocator); +} + +static VkResult VKAPI_CALL timestamps_khr(VkDevice device, uint32_t count, const VkCalibratedTimestampInfoEXT *infos, + uint64_t *timestamps, uint64_t *deviation) { + PFN_vkGetCalibratedTimestampsEXT ext = device_of(device).timestamps; + return ext ? ext(device, count, infos, timestamps, deviation) : VK_ERROR_FEATURE_NOT_PRESENT; +} + +static VkResult VKAPI_CALL create_sampler(VkDevice device, const VkSamplerCreateInfo *info, + const VkAllocationCallbacks *allocator, VkSampler *sampler) { + PFN_vkCreateSampler next = device_of(device).create_sampler; + if (!next) return VK_ERROR_INITIALIZATION_FAILED; + VkResult result = next(device, info, allocator, sampler); + if (result != VK_SUCCESS) return result; + int ycbcr = 0; + for (const VkBaseInStructure *p = info->pNext; p && !ycbcr; p = p->pNext) + ycbcr = p->sType == VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO; + if (!ycbcr) return result; + pthread_mutex_lock(&lock); + if (ycbcr_count == ycbcr_capacity) { + size_t capacity = ycbcr_capacity ? ycbcr_capacity * 2 : 16; + struct ycbcr_sampler *grown = realloc(ycbcr_samplers, capacity * sizeof(*grown)); + if (!grown) { + pthread_mutex_unlock(&lock); + /* An untracked YCbCr sampler would reach a compute layout unreplaced: fail instead. */ + PFN_vkDestroySampler destroy = device_of(device).destroy_sampler; + if (destroy) destroy(device, *sampler, allocator); + *sampler = VK_NULL_HANDLE; + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + ycbcr_samplers = grown; + ycbcr_capacity = capacity; + } + ycbcr_samplers[ycbcr_count++] = (struct ycbcr_sampler){device, *sampler}; + pthread_mutex_unlock(&lock); + return result; +} + +static void VKAPI_CALL destroy_sampler(VkDevice device, VkSampler sampler, const VkAllocationCallbacks *allocator) { + pthread_mutex_lock(&lock); + for (size_t k = 0; k < ycbcr_count; k++) + if (ycbcr_samplers[k].device == device && ycbcr_samplers[k].sampler == sampler) { + ycbcr_samplers[k] = ycbcr_samplers[--ycbcr_count]; + break; + } + pthread_mutex_unlock(&lock); + PFN_vkDestroySampler next = device_of(device).destroy_sampler; + if (next) next(device, sampler, allocator); +} + +/* Called with lock held. */ +static int is_ycbcr(VkDevice device, VkSampler sampler) { + for (size_t k = 0; sampler && k < ycbcr_count; k++) + if (ycbcr_samplers[k].device == device && ycbcr_samplers[k].sampler == sampler) return 1; + return 0; +} + +static VkResult VKAPI_CALL create_set_layout(VkDevice device, const VkDescriptorSetLayoutCreateInfo *info, + const VkAllocationCallbacks *allocator, VkDescriptorSetLayout *layout) { + struct device_data data = device_of(device); + if (!data.create_set_layout) return VK_ERROR_INITIALIZATION_FAILED; + int replace = 0; + pthread_mutex_lock(&lock); + for (uint32_t b = 0; data.fallback && b < info->bindingCount && !replace; b++) { + const VkDescriptorSetLayoutBinding *binding = &info->pBindings[b]; + if (!(binding->stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) || !binding->pImmutableSamplers) continue; + for (uint32_t s = 0; s < binding->descriptorCount; s++) + if (is_ycbcr(device, binding->pImmutableSamplers[s])) { replace = 1; break; } + } + pthread_mutex_unlock(&lock); + if (!replace) return data.create_set_layout(device, info, allocator, layout); + + VkResult result = VK_ERROR_OUT_OF_HOST_MEMORY; + VkDescriptorSetLayoutBinding *bindings = calloc(info->bindingCount, sizeof(*bindings)); + VkSampler **arrays = calloc(info->bindingCount, sizeof(*arrays)); + if (!bindings || !arrays) goto done; + for (uint32_t b = 0; b < info->bindingCount; b++) { + bindings[b] = info->pBindings[b]; + if (!(bindings[b].stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) || !bindings[b].pImmutableSamplers) continue; + /* Without the replacement the driver would see the samplers it cannot compile: fail instead. */ + if (!(arrays[b] = malloc(bindings[b].descriptorCount * sizeof(VkSampler)))) goto done; + pthread_mutex_lock(&lock); + for (uint32_t s = 0; s < bindings[b].descriptorCount; s++) { + VkSampler original = bindings[b].pImmutableSamplers[s]; + arrays[b][s] = is_ycbcr(device, original) ? data.fallback : original; + } + pthread_mutex_unlock(&lock); + bindings[b].pImmutableSamplers = arrays[b]; + } + VkDescriptorSetLayoutCreateInfo patched = *info; + patched.pBindings = bindings; + result = data.create_set_layout(device, &patched, allocator, layout); + if (debug) fprintf(stderr, "xclipse layer: compute YCbCr immutable samplers replaced (%d)\n", result); +done: + for (uint32_t b = 0; arrays && b < info->bindingCount; b++) free(arrays[b]); + free(arrays); + free(bindings); + return result; +} + +/* ---- dispatch ---- */ + +static PFN_vkVoidFunction device_hook(const char *name) { + if (!strcmp(name, "vkGetDeviceProcAddr")) return NULL; /* handled by the caller */ + if (!strcmp(name, "vkDestroyDevice")) return (PFN_vkVoidFunction)destroy_device; + if (!strcmp(name, "vkCreateSampler")) return (PFN_vkVoidFunction)create_sampler; + if (!strcmp(name, "vkDestroySampler")) return (PFN_vkVoidFunction)destroy_sampler; + if (!strcmp(name, "vkCreateDescriptorSetLayout")) return (PFN_vkVoidFunction)create_set_layout; + if (!strcmp(name, "vkGetCalibratedTimestampsKHR")) return (PFN_vkVoidFunction)timestamps_khr; + return NULL; +} + +EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char *name) { + if (!strcmp(name, "vkGetDeviceProcAddr")) return (PFN_vkVoidFunction)vkGetDeviceProcAddr; + PFN_vkVoidFunction hook = device_hook(name); + if (hook) return hook; + struct device_data data = device ? device_of(device) : (struct device_data){0}; + return data.next ? data.next(device, name) : NULL; +} + +EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char *name) { + if (!strcmp(name, "vkGetInstanceProcAddr")) return (PFN_vkVoidFunction)vkGetInstanceProcAddr; + if (!strcmp(name, "vkCreateInstance")) return (PFN_vkVoidFunction)create_instance; + if (!strcmp(name, "vkDestroyInstance")) return (PFN_vkVoidFunction)destroy_instance; + if (!strcmp(name, "vkCreateDevice")) return (PFN_vkVoidFunction)create_device; + if (!strcmp(name, "vkGetDeviceProcAddr")) return (PFN_vkVoidFunction)vkGetDeviceProcAddr; + if (!strcmp(name, "vkGetPhysicalDeviceFeatures")) return (PFN_vkVoidFunction)get_features; + if (!strcmp(name, "vkGetPhysicalDeviceFeatures2") || !strcmp(name, "vkGetPhysicalDeviceFeatures2KHR")) + return (PFN_vkVoidFunction)get_features2; + if (!strcmp(name, "vkGetPhysicalDeviceCalibrateableTimeDomainsKHR")) return (PFN_vkVoidFunction)time_domains_khr; + if (!strcmp(name, "vkGetPhysicalDeviceFormatProperties")) return (PFN_vkVoidFunction)get_format_properties; + if (!strcmp(name, "vkGetPhysicalDeviceFormatProperties2") || !strcmp(name, "vkGetPhysicalDeviceFormatProperties2KHR")) + return (PFN_vkVoidFunction)get_format_properties2; + PFN_vkVoidFunction hook = device_hook(name); + if (hook) return hook; + PFN_vkGetInstanceProcAddr next = instance ? instance_of(instance).next : NULL; + return next ? next(instance, name) : NULL; +} + +EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface *negotiate) { + if (!negotiate || negotiate->loaderLayerInterfaceVersion < 2) return VK_ERROR_INITIALIZATION_FAILED; + debug = getenv("DROIDDECK_XCLIPSE_LAYER_DEBUG") != NULL; + negotiate->loaderLayerInterfaceVersion = 2; + negotiate->pfnGetInstanceProcAddr = vkGetInstanceProcAddr; + negotiate->pfnGetDeviceProcAddr = vkGetDeviceProcAddr; + negotiate->pfnGetPhysicalDeviceProcAddr = NULL; + return VK_SUCCESS; +} diff --git a/tools/local-cross.Dockerfile b/tools/local-cross.Dockerfile index 8d143e7ca..c448d0238 100644 --- a/tools/local-cross.Dockerfile +++ b/tools/local-cross.Dockerfile @@ -10,6 +10,7 @@ RUN apt-get update \ g++ \ gcc-aarch64-linux-gnu \ g++-aarch64-linux-gnu \ + libvulkan-dev \ python3 \ zstd \ unzip \ diff --git a/tools/powervr/fetch.py b/tools/powervr/fetch.py index 91bf3cfed..75c8b539e 100644 --- a/tools/powervr/fetch.py +++ b/tools/powervr/fetch.py @@ -12,6 +12,7 @@ import json import os from pathlib import Path, PurePosixPath import posixpath +import re import stat import subprocess import tarfile @@ -194,6 +195,50 @@ def read_zip(data, component): return entries +def read_tar_gz(data, component): + """An upstream release tarball, held to the same per-file pins as a CI archive.""" + entries, seen = {}, set() + expected = {item["name"]: item for item in component["files"]} + allowed = set(expected) | set(component["ignored"]) + total = 0 + with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as archive: + for member in archive: + name = safe_name(member.name) + if name in seen: + raise ValueError("duplicate archive path: " + name) + seen.add(name) + if member.isdir(): + continue + if not member.isfile() or name not in allowed: + raise ValueError("unexpected component archive entry: " + name) + total += member.size + if total > MAX_UNPACKED: + raise ValueError("component archive exceeds size limit") + if name not in expected: + continue + pin = expected[name] + content = archive.extractfile(member).read() + if len(content) != pin["size"] or sha256(content) != pin["sha256"]: + raise ValueError("component member checksum mismatch: " + name) + path = safe_name(pin["path"]) + if path in entries: + raise ValueError("duplicate component destination: " + path) + entries[path] = ("file", pin["mode"], content) + if set(expected) - seen: + raise ValueError("component archive missing files: " + ", ".join(sorted(set(expected) - seen))) + check_tree(entries) + return entries + + +def read_component(data, component): + kind = component.get("format", "zip") + if kind == "zip": + return read_zip(data, component) + if kind == "tar.gz": + return read_tar_gz(data, component) + raise ValueError("unsupported component archive format: " + kind) + + def check_bundle(entries, release, root): check_tree(entries) if read(entries, "assembly-inputs.json") != json_bytes(assembly_inputs(root)): @@ -221,15 +266,20 @@ def check_bundle(entries, release, root): content = read(entries, name) if content[:6] != b"\x7fELF\x02\x01" or content[18:20] != b"\xb7\x00": raise ValueError("runtime library is not ELF64 AArch64: " + name) - for architecture, machine, magic in (("x32", b"\x4c\x01", b"\x0b\x01"), - ("x64", b"\x64\x86", b"\x0b\x02")): - for name in ("d3d8.dll", "d3d9.dll", "d3d10core.dll", "d3d11.dll", "dxgi.dll"): - path = "dxvk/" + architecture + "/" + name - content = read(entries, path) - pe = int.from_bytes(content[60:64], "little") - if (content[:2] != b"MZ" or pe < 64 or content[pe:pe + 4] != b"PE\0\0" - or content[pe + 4:pe + 6] != machine or content[pe + 24:pe + 26] != magic): - raise ValueError("DXVK library has wrong PE architecture: " + path) + # dxvk/ is the default payload; run-proton.py can select another dxvk-* sibling by name. + payloads = sorted({name.split("/")[0] for name in entries if re.fullmatch(r"dxvk[^/]*/x(32|64)/[^/]+\.dll", name)}) + if "dxvk" not in payloads: + raise ValueError("default DXVK payload missing") + for payload in payloads: + for architecture, machine, magic in (("x32", b"\x4c\x01", b"\x0b\x01"), + ("x64", b"\x64\x86", b"\x0b\x02")): + for name in ("d3d8.dll", "d3d9.dll", "d3d10core.dll", "d3d11.dll", "dxgi.dll"): + path = payload + "/" + architecture + "/" + name + content = read(entries, path) + pe = int.from_bytes(content[60:64], "little") + if (content[:2] != b"MZ" or pe < 64 or content[pe:pe + 4] != b"PE\0\0" + or content[pe + 4:pe + 6] != machine or content[pe + 24:pe + 26] != magic): + raise ValueError("DXVK library has wrong PE architecture: " + path) def pack(entries): @@ -273,7 +323,7 @@ def assemble(root, cache): if tree_digest(entries) != release["hybris"]["tree_sha256"]: raise ValueError("PowerVR bridge tree checksum mismatch") for component in release["archives"]: - additions = read_zip(fetch(component, cache), component) + additions = read_component(fetch(component, cache), component) if set(entries) & set(additions): raise ValueError("component overwrites existing runtime files") entries.update(additions) diff --git a/tools/powervr/release.json b/tools/powervr/release.json index a9bf29f0e..dd5496a37 100644 --- a/tools/powervr/release.json +++ b/tools/powervr/release.json @@ -226,6 +226,91 @@ "sha256": "348f9ec42159c1ee6e2baba4312a5e845d26687238f29e3f4a1b0c3c97e13b3c", "size": 362717, "url": "https://github.com/wallentx/DroidDeck/releases/download/v0.3.2-progress.20261010.4/wsi-abe5861.zip" + }, + { + "files": [ + { + "mode": 493, + "name": "dxvk-2.6.2/x32/d3d10core.dll", + "path": "dxvk-2.6.2/x32/d3d10core.dll", + "sha256": "b34b479fa0bce29eba6dcff5b1f8584c2ae699d53a123668590f8a2b84e84094", + "size": 176142 + }, + { + "mode": 493, + "name": "dxvk-2.6.2/x32/d3d11.dll", + "path": "dxvk-2.6.2/x32/d3d11.dll", + "sha256": "858d1a848dd5023191cb16cd986e0f00008faf5df8a87b5c835b56cf4b646910", + "size": 4845582 + }, + { + "mode": 493, + "name": "dxvk-2.6.2/x32/d3d8.dll", + "path": "dxvk-2.6.2/x32/d3d8.dll", + "sha256": "617459c8d1239f98687f8157bdaec85738ee4699885ed7d1ff13deb32609c759", + "size": 1658894 + }, + { + "mode": 493, + "name": "dxvk-2.6.2/x32/d3d9.dll", + "path": "dxvk-2.6.2/x32/d3d9.dll", + "sha256": "00bc142e98ddad1e334a4d88fa630038e092b0758b3d79e295172b181a4c772d", + "size": 4460558 + }, + { + "mode": 493, + "name": "dxvk-2.6.2/x32/dxgi.dll", + "path": "dxvk-2.6.2/x32/dxgi.dll", + "sha256": "67d7ca4a4f8f66baabd381c73b52a8e85870083e868ed474a4bc58b7da486883", + "size": 3137550 + }, + { + "mode": 493, + "name": "dxvk-2.6.2/x64/d3d10core.dll", + "path": "dxvk-2.6.2/x64/d3d10core.dll", + "sha256": "565154a0763c6460843fe47e5aaec62e609163d9995a922d5e19e85027fe2f88", + "size": 155662 + }, + { + "mode": 493, + "name": "dxvk-2.6.2/x64/d3d11.dll", + "path": "dxvk-2.6.2/x64/d3d11.dll", + "sha256": "5b63515a432d19302f7dc81dd2ee5bf40043683b5064a2bce03a2b6d27dcce5d", + "size": 4399118 + }, + { + "mode": 493, + "name": "dxvk-2.6.2/x64/d3d8.dll", + "path": "dxvk-2.6.2/x64/d3d8.dll", + "sha256": "dfd837c3467eeb15f69a773e18bbc0550cb69c9e33d5d57b4ea42c5f63bfdc4b", + "size": 1544206 + }, + { + "mode": 493, + "name": "dxvk-2.6.2/x64/d3d9.dll", + "path": "dxvk-2.6.2/x64/d3d9.dll", + "sha256": "01e7ae2b0544b6031fe695fd16fbc76786ded57fdbe3f6bd11eee7c98d2c370d", + "size": 4108302 + }, + { + "mode": 493, + "name": "dxvk-2.6.2/x64/dxgi.dll", + "path": "dxvk-2.6.2/x64/dxgi.dll", + "sha256": "074616e1af172edf175f3fc6ed7ca30f6c685a70875eed3af10797a10840ed39", + "size": 2891790 + } + ], + "format": "tar.gz", + "id": "dxvk-xclipse", + "ignored": [], + "provenance": { + "note": "Unmodified upstream release, selected on Samsung Xclipse: its 128 bytes of push constants predate DXVK 3's 256-byte requirement. Covered by the zlib license already shipped as licenses/dxvk-LICENSE.", + "upstream_release": "https://github.com/doitsujin/dxvk/releases/tag/v2.6.2" + }, + "recipe_inputs": {}, + "sha256": "17761876556afd55736cb895d184f5a1c55d43350f1b1e3b129f8d28706d7992", + "size": 10107492, + "url": "https://github.com/doitsujin/dxvk/releases/download/v2.6.2/dxvk-2.6.2.tar.gz" } ], "format": 1, diff --git a/tools/release/runtime_inputs.py b/tools/release/runtime_inputs.py index 3fab564f1..a91416a34 100644 --- a/tools/release/runtime_inputs.py +++ b/tools/release/runtime_inputs.py @@ -64,6 +64,8 @@ def required(): paths += ['assets/linuxfs/i386/libfaultreport.so'] paths += ['assets/linuxfs/usr/local/lib/droiddeck-msitools/' + name for name in ['msiinfo', 'cabextract', '7z', '7z.so', 'libmsi-1.0.so.0', 'libgsf-1.so.114', 'libgcab-1.0.so.0']] + paths += ['assets/linuxfs/usr/local/lib/droiddeck-xclipse/' + name for name in + ['libVkLayer_droiddeck_xclipse.so', 'VkLayer_droiddeck_xclipse.json']] paths += ['assets/linuxfs/usr/local/lib/mangoapp/' + name for name in ['mangoapp', 'libfmt.so.10', 'libspdlog.so.1.13', 'libglfw.so.3', 'libtraceevent.so.1', 'libtracefs.so.1']] for driver in ['linux-wine11', 'linux-wine11-systhread']: diff --git a/tools/test_local.sh b/tools/test_local.sh index a2a69da62..8cd05467c 100755 --- a/tools/test_local.sh +++ b/tools/test_local.sh @@ -6,7 +6,7 @@ cd "$repo_root" if [[ $(uname -s) == Linux ]]; then exec python3 -m unittest discover -s tools/tests "$@" fi -image=${DROIDDECK_BUILD_IMAGE:-droiddeck-local-cross:24.04-v3} +image=${DROIDDECK_BUILD_IMAGE:-droiddeck-local-cross:24.04-v4} python3 tools/docker_preflight.py "$image" exec docker run --rm --platform linux/amd64 --user "$(id -u):$(id -g)" \ -v "$repo_root:/src:ro" -w /src "$image" python3 -m unittest discover -s tools/tests "$@" diff --git a/tools/tests/test_game_environment.py b/tools/tests/test_game_environment.py index 575d1cf3c..bdd786456 100644 --- a/tools/tests/test_game_environment.py +++ b/tools/tests/test_game_environment.py @@ -82,6 +82,32 @@ class SystemGraphicsLaunchTest(unittest.TestCase): for key in ("FEX_TSOENABLED", "WINESERVER", "CUSTOM"): self.assertEqual(result[key], env[key]) + def test_xclipse_selection_adds_its_layers_layer_path_and_dxvk_payload(self): + with tempfile.TemporaryDirectory() as tmp: + runtime, _ = self.runtime(Path(tmp)) + env = {"DROIDDECK_POWERVR_RUNTIME": str(runtime), "STEAM_COMPAT_DATA_PATH": "/compatdata/42", + "DROIDDECK_HYBRIS_LAYERS": "VK_LAYER_DROIDDECK_xclipse:VK_LAYER_HYBRIS_compat", + "DROIDDECK_HYBRIS_LAYER_PATH": "/usr/local/lib/droiddeck-xclipse", + "DROIDDECK_HYBRIS_DXVK": "dxvk-2.6.2"} + command = ["/proton", "waitforexitandrun", "game.exe"] + # The selected payload is required like the default one. + with self.assertRaises(MODULE["SystemGraphicsError"]): + MODULE["system_graphics_launch"](command, env) + for arch in ("x32", "x64"): + for name in ("d3d8", "d3d9", "d3d10core", "d3d11", "dxgi"): + target = runtime / "dxvk-2.6.2" / arch / (name + ".dll") + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(b"component") + launch, result = MODULE["system_graphics_launch"](command, env) + self.assertEqual(launch, [str(runtime / "run-proton.py"), *command]) + self.assertEqual(result["VK_INSTANCE_LAYERS"], + "VK_LAYER_window_system_integration:VK_LAYER_DROIDDECK_xclipse:VK_LAYER_HYBRIS_compat") + self.assertEqual(result["VK_LAYER_PATH"], f"{runtime}/wsi:{runtime}/lib:/usr/local/lib/droiddeck-xclipse") + self.assertEqual(result["DROIDDECK_HYBRIS_DXVK"], "dxvk-2.6.2") + for name in ("../dxvk", "other", "dxvk/x"): + with self.subTest(name=name), self.assertRaises(MODULE["SystemGraphicsError"]): + MODULE["system_graphics_launch"](command, dict(env, DROIDDECK_HYBRIS_DXVK=name)) + def test_incomplete_runtime_fails_before_any_proton_exec(self): with tempfile.TemporaryDirectory() as tmp: runtime, files = self.runtime(Path(tmp)) diff --git a/tools/tests/test_powervr_runtime.py b/tools/tests/test_powervr_runtime.py index d6f383079..475f89927 100644 --- a/tools/tests/test_powervr_runtime.py +++ b/tools/tests/test_powervr_runtime.py @@ -48,6 +48,29 @@ def tar(entries): capture_output=True, check=True).stdout +def tar_gz(entries): + stream = io.BytesIO() + with tarfile.open(fileobj=stream, mode='w:gz') as archive: + for name, kind, content in entries: + info = tarfile.TarInfo(name) + info.type = kind + if kind == tarfile.SYMTYPE: + info.linkname = content + archive.addfile(info) + elif kind == tarfile.REGTYPE: + info.size = len(content) + archive.addfile(info, io.BytesIO(content)) + else: + archive.addfile(info) + return stream.getvalue() + + +def upstream_dxvk(version, architecture_of=lambda arch: arch): + return {'dxvk-%s/%s/%s' % (version, arch, name): pe(architecture_of(arch)) + for arch in ('x32', 'x64') + for name in ('d3d8.dll', 'd3d9.dll', 'd3d10core.dll', 'd3d11.dll', 'dxgi.dll')} + + class PowerVRRuntimeTest(unittest.TestCase): def setUp(self): self.temporary = tempfile.TemporaryDirectory() @@ -95,6 +118,18 @@ class PowerVRRuntimeTest(unittest.TestCase): (self.cache / (component['sha256'] + '-' + name)).write_bytes(data) return component + def add_upstream_dxvk(self, files): + top = next(iter(files)).split('/')[0] + data = tar_gz([(top, tarfile.DIRTYPE, None)] + + [(name, tarfile.REGTYPE, content) for name, content in files.items()]) + component = self.component('dxvk-upstream.tar.gz', data) + component.update({'format': 'tar.gz', 'ignored': [], 'files': [ + {'name': name, 'path': name, 'size': len(content), 'sha256': runtime.sha256(content), 'mode': 0o755} + for name, content in files.items()]}) + self.release['archives'].append(component) + self.pins.write_bytes(runtime.json_bytes(self.release)) + return component + def assembled(self): runtime.assemble(self.root, self.cache) return runtime.read_tar((self.root / runtime.ASSETS / 'runtime.tzst').read_bytes()) @@ -107,6 +142,38 @@ class PowerVRRuntimeTest(unittest.TestCase): self.assertEqual(first, (self.root / runtime.ASSETS / 'runtime.tzst').read_bytes()) self.assertEqual(runtime.read_tar(first)['lib/libhybris-common.so.1'][0], 'symlink') + def test_upstream_tarball_adds_a_second_dxvk_payload(self): + files = upstream_dxvk('2.6.2') + self.add_upstream_dxvk(files) + entries = self.assembled() + runtime.verify(self.root) + for name, content in files.items(): + self.assertEqual(entries[name], ('file', 0o755, content)) + self.assertIn('dxvk/x64/d3d9.dll', entries) + + def test_second_dxvk_payload_with_wrong_pe_architecture_is_rejected(self): + self.add_upstream_dxvk(upstream_dxvk('2.6.2', lambda arch: 'x64' if arch == 'x32' else 'x32')) + with self.assertRaisesRegex(ValueError, 'wrong PE architecture: dxvk-2.6.2/'): + self.assembled() + + def test_tar_gz_rejects_symlinks_unknown_members_and_changed_files(self): + pin = {'name': 'dxvk-1/x64/d3d9.dll', 'path': 'dxvk-1/x64/d3d9.dll', 'size': 4, + 'sha256': runtime.sha256(b'good'), 'mode': 0o755} + component = {'files': [pin], 'ignored': []} + cases = [ + [('dxvk-1/x64/d3d9.dll', tarfile.SYMTYPE, '../../outside')], + [('dxvk-1/x64/d3d9.dll', tarfile.REGTYPE, b'good'), ('dxvk-1/extra.dll', tarfile.REGTYPE, b'x')], + [('dxvk-1/x64/d3d9.dll', tarfile.REGTYPE, b'evil')], + [], + ] + for entries in cases: + with self.subTest(entries=[name for name, _, _ in entries]), self.assertRaises(ValueError): + runtime.read_tar_gz(tar_gz(entries), component) + self.assertEqual(runtime.read_tar_gz(tar_gz([('dxvk-1/x64/d3d9.dll', tarfile.REGTYPE, b'good')]), component), + {'dxvk-1/x64/d3d9.dll': ('file', 0o755, b'good')}) + with self.assertRaisesRegex(ValueError, 'unsupported component archive format'): + runtime.read_component(b'', {'format': 'rar'}) + def test_stale_wrapper_cannot_pass_with_an_updated_archive_checksum(self): self.assembled() (self.root / 'tools/hybris/run-proton.py').write_text('print("new wrapper")\n') diff --git a/tools/tests/test_proton_wrapper.py b/tools/tests/test_proton_wrapper.py index 8243000df..175d4600a 100644 --- a/tools/tests/test_proton_wrapper.py +++ b/tools/tests/test_proton_wrapper.py @@ -89,7 +89,7 @@ class ProtonWrapperTest(unittest.TestCase): return {"wrapper": wrapper, "dxvk": dxvk, "depot": depot, "proton": proton, "probe": root / "proton-ran.json"} - def run_wrapper(self, fixture, *proton_args, check=False, exit_status=0): + def run_wrapper(self, fixture, *proton_args, check=False, exit_status=0, env=None): command = [sys.executable, str(fixture["wrapper"])] if check: command.append("--check") @@ -98,7 +98,7 @@ class ProtonWrapperTest(unittest.TestCase): command, cwd=fixture["wrapper"].parent, env={"PATH": os.defpath, "PROBE": str(fixture["probe"]), - "PROTON_EXIT": str(exit_status)}, + "PROTON_EXIT": str(exit_status), **(env or {})}, text=True, capture_output=True, ) @@ -126,6 +126,36 @@ class ProtonWrapperTest(unittest.TestCase): {name: f"{architecture}:{name}" for name in DLL_NAMES}, ) + def test_named_sibling_payload_replaces_the_default(self): + with tempfile.TemporaryDirectory() as tmp: + fixture = self.fixture(Path(tmp)) + for architecture in ("x32", "x64"): + payload = fixture["wrapper"].parent / "dxvk-2.6.2" / architecture + payload.mkdir(parents=True) + for name in DLL_NAMES: + (payload / name).write_text(f"2.6.2 {architecture}:{name}") + result = self.run_wrapper(fixture, "waitforexitandrun", "game.exe", + env={"DROIDDECK_HYBRIS_DXVK": "dxvk-2.6.2"}) + + self.assertEqual(result.returncode, 0, result.stderr) + record = json.loads(fixture["probe"].read_text()) + for architecture in ("x32", "x64"): + self.assertEqual( + record["payloads"][architecture], + {name: f"2.6.2 {architecture}:{name}" for name in DLL_NAMES}, + ) + + def test_payload_names_outside_the_wrapper_directory_are_refused(self): + for name in ("../dxvk", "dxvk/../../x", "/tmp/dxvk", "other", ".dxvk"): + with self.subTest(name=name), tempfile.TemporaryDirectory() as tmp: + fixture = self.fixture(Path(tmp)) + result = self.run_wrapper(fixture, "waitforexitandrun", "game.exe", + env={"DROIDDECK_HYBRIS_DXVK": name}) + + self.assertEqual(result.returncode, 1) + self.assertIn("invalid payload name", result.stderr) + self.assertFalse(fixture["probe"].exists()) + def test_check_validates_without_executing_proton(self): with tempfile.TemporaryDirectory() as tmp: fixture = self.fixture(Path(tmp)) diff --git a/tools/tests/test_xclipse_layer.py b/tools/tests/test_xclipse_layer.py new file mode 100644 index 000000000..36042c9c7 --- /dev/null +++ b/tools/tests/test_xclipse_layer.py @@ -0,0 +1,47 @@ +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + + +TOOLS = Path(__file__).resolve().parents[1] +LAYER = TOOLS / "linuxfs/xclipse-layer" + + +class XclipseLayerTest(unittest.TestCase): + def test_layer_against_a_mock_loader_and_driver(self): + compiler = shutil.which(os.environ.get("CC", "cc")) + if not compiler: + self.fail("A C compiler is required for the Xclipse layer regression test") + # The build compiles the layer against the distribution's headers (libvulkan-dev); + # VULKAN_INCLUDE points elsewhere, e.g. a Vulkan-Headers checkout on macOS. + include = Path(os.environ.get("VULKAN_INCLUDE", "/usr/include")) + if not (include / "vulkan/vk_layer.h").is_file(): + self.fail("Vulkan headers are required: install libvulkan-dev or set VULKAN_INCLUDE") + with tempfile.TemporaryDirectory(prefix="droiddeck-xclipse-") as tmp: + program = Path(tmp) / "xclipse-layer" + built = subprocess.run([compiler, "-std=gnu11", "-Wall", "-Wextra", "-Werror", "-Wno-unused-parameter", + "-UNDEBUG", "-pthread", "-I", str(LAYER), "-I", str(include), + str(Path(__file__).with_name("xclipse_layer_test.c")), "-ldl", "-o", str(program)], + capture_output=True, text=True) + self.assertEqual(built.returncode, 0, built.stdout + built.stderr) + ran = subprocess.run([str(program)], capture_output=True, text=True, timeout=30) + self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr) + self.assertIn("xclipse layer ok", ran.stdout) + + def test_device_chain_table_matches_its_generator(self): + registry = os.environ.get("VULKAN_REGISTRY") + if not registry: + self.skipTest("set VULKAN_REGISTRY to a Vulkan-Headers 1.3.275 checkout to recheck device_chain.inc") + with tempfile.TemporaryDirectory(prefix="droiddeck-chain-") as tmp: + output = Path(tmp) / "device_chain.inc" + subprocess.run(["python3", str(LAYER / "gen_device_chain.py"), str(Path(registry) / "registry/vk.xml"), + str(Path(registry) / "include/vulkan/vulkan_core.h"), str(output)], + check=True, capture_output=True) + self.assertEqual(output.read_text(), (LAYER / "device_chain.inc").read_text()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/xclipse_layer_test.c b/tools/tests/xclipse_layer_test.c new file mode 100644 index 000000000..26105d69f --- /dev/null +++ b/tools/tests/xclipse_layer_test.c @@ -0,0 +1,217 @@ +/* The Xclipse layer between a simulated loader and a mock driver. The driver refuses what the compat + * layer and Samsung's driver refuse: textureCompressionBC, and a compute layout holding an immutable + * YCbCr sampler. Built by test_xclipse_layer.py with the layer source included directly. */ +#include "xclipse_layer.c" +#include + +struct object { void *dispatch; }; +static int instance_table, device_table[4]; +static struct object mock_instance = {&instance_table}, mock_physical = {&instance_table}; +static struct object mock_devices[4] = {{&device_table[0]}, {&device_table[1]}, {&device_table[2]}, {&device_table[3]}}; +static int devices_created; + +/* What the driver last saw. */ +static int driver_saw_bc, driver_saw_layout_sampler_count; +static VkSampler driver_layout_samplers[16]; +static uint64_t next_sampler = 0x100; +static VkSampler driver_ycbcr[16]; +static int driver_ycbcr_count; + +static VkResult VKAPI_CALL mock_create_instance(const VkInstanceCreateInfo *info, const VkAllocationCallbacks *allocator, + VkInstance *instance) { + *instance = (VkInstance)&mock_instance; + return VK_SUCCESS; +} + +static void VKAPI_CALL mock_features(VkPhysicalDevice physical, VkPhysicalDeviceFeatures *features) { + memset(features, 0, sizeof(*features)); +} + +static void VKAPI_CALL mock_features2(VkPhysicalDevice physical, VkPhysicalDeviceFeatures2 *features) { + mock_features(physical, &features->features); +} + +/* The compat layer's emulation: BC formats sample, the feature bit stays off. */ +static void VKAPI_CALL mock_format_properties(VkPhysicalDevice physical, VkFormat format, VkFormatProperties *out) { + memset(out, 0, sizeof(*out)); + if (format >= VK_FORMAT_BC1_RGB_UNORM_BLOCK && format <= VK_FORMAT_BC7_SRGB_BLOCK) + out->optimalTilingFeatures = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; +} + +static void VKAPI_CALL mock_format_properties2(VkPhysicalDevice physical, VkFormat format, VkFormatProperties2 *out) { + mock_format_properties(physical, format, &out->formatProperties); +} + +static VkResult VKAPI_CALL mock_time_domains(VkPhysicalDevice physical, uint32_t *count, VkTimeDomainEXT *domains) { + if (domains) { domains[0] = VK_TIME_DOMAIN_DEVICE_EXT; domains[1] = VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT; } + *count = 2; + return VK_SUCCESS; +} + +static VkResult VKAPI_CALL mock_create_device(VkPhysicalDevice physical, const VkDeviceCreateInfo *info, + const VkAllocationCallbacks *allocator, VkDevice *device) { + driver_saw_bc = info->pEnabledFeatures && info->pEnabledFeatures->textureCompressionBC; + for (const VkBaseInStructure *p = info->pNext; p; p = p->pNext) + if (p->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 && + ((const VkPhysicalDeviceFeatures2 *)p)->features.textureCompressionBC) driver_saw_bc = 1; + if (driver_saw_bc) return VK_ERROR_FEATURE_NOT_PRESENT; + assert(devices_created < 4); + *device = (VkDevice)&mock_devices[devices_created++]; + return VK_SUCCESS; +} + +static VkResult VKAPI_CALL mock_create_sampler(VkDevice device, const VkSamplerCreateInfo *info, + const VkAllocationCallbacks *allocator, VkSampler *sampler) { + *sampler = (VkSampler)next_sampler++; + for (const VkBaseInStructure *p = info->pNext; p; p = p->pNext) + if (p->sType == VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO) driver_ycbcr[driver_ycbcr_count++] = *sampler; + return VK_SUCCESS; +} + +static void VKAPI_CALL mock_destroy_sampler(VkDevice device, VkSampler sampler, const VkAllocationCallbacks *allocator) { + for (int i = 0; i < driver_ycbcr_count; i++) + if (driver_ycbcr[i] == sampler) driver_ycbcr[i] = driver_ycbcr[--driver_ycbcr_count]; +} + +static VkResult VKAPI_CALL mock_create_set_layout(VkDevice device, const VkDescriptorSetLayoutCreateInfo *info, + const VkAllocationCallbacks *allocator, VkDescriptorSetLayout *layout) { + driver_saw_layout_sampler_count = 0; + for (uint32_t b = 0; b < info->bindingCount; b++) { + const VkDescriptorSetLayoutBinding *binding = &info->pBindings[b]; + for (uint32_t s = 0; binding->pImmutableSamplers && s < binding->descriptorCount; s++) { + VkSampler sampler = binding->pImmutableSamplers[s]; + driver_layout_samplers[driver_saw_layout_sampler_count++] = sampler; + for (int i = 0; i < driver_ycbcr_count; i++) + if ((binding->stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) && driver_ycbcr[i] == sampler) + return VK_ERROR_UNKNOWN; + } + } + *layout = (VkDescriptorSetLayout)0x5000; + return VK_SUCCESS; +} + +static void VKAPI_CALL mock_destroy_device(VkDevice device, const VkAllocationCallbacks *allocator) {} + +static VkResult VKAPI_CALL mock_timestamps(VkDevice device, uint32_t count, const VkCalibratedTimestampInfoEXT *infos, + uint64_t *timestamps, uint64_t *deviation) { + for (uint32_t i = 0; i < count; i++) timestamps[i] = 42; + *deviation = 1; + return VK_SUCCESS; +} + +#define MOCK(name, function) if (!strcmp(requested, name)) return (PFN_vkVoidFunction)function +static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL mock_gipa(VkInstance instance, const char *requested) { + MOCK("vkCreateInstance", mock_create_instance); + MOCK("vkCreateDevice", mock_create_device); + MOCK("vkGetPhysicalDeviceFeatures", mock_features); + MOCK("vkGetPhysicalDeviceFeatures2", mock_features2); + MOCK("vkGetPhysicalDeviceFormatProperties", mock_format_properties); + MOCK("vkGetPhysicalDeviceFormatProperties2", mock_format_properties2); + MOCK("vkGetPhysicalDeviceCalibrateableTimeDomainsEXT", mock_time_domains); + return NULL; +} + +static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL mock_gdpa(VkDevice device, const char *requested) { + MOCK("vkCreateSampler", mock_create_sampler); + MOCK("vkDestroySampler", mock_destroy_sampler); + MOCK("vkCreateDescriptorSetLayout", mock_create_set_layout); + MOCK("vkDestroyDevice", mock_destroy_device); + MOCK("vkGetCalibratedTimestampsEXT", mock_timestamps); + return NULL; +} + +/* The loader's link for one vkCreateDevice call, then the caller's own chain. */ +static VkResult create(const void *chain, const VkPhysicalDeviceFeatures *enabled, VkDevice *device) { + VkLayerDeviceLink link = {NULL, mock_gipa, mock_gdpa}; + VkLayerDeviceCreateInfo loader = {VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO, chain, VK_LAYER_LINK_INFO, {&link}}; + VkDeviceCreateInfo info = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, &loader, 0, 0, NULL, 0, NULL, 0, NULL, enabled}; + PFN_vkCreateDevice create_device = (PFN_vkCreateDevice)vkGetInstanceProcAddr((VkInstance)&mock_instance, "vkCreateDevice"); + return create_device((VkPhysicalDevice)&mock_physical, &info, NULL, device); +} + +int main(void) { + VkNegotiateLayerInterface negotiate = {LAYER_NEGOTIATE_INTERFACE_STRUCT, NULL, 2, NULL, NULL, NULL}; + assert(vkNegotiateLoaderLayerInterfaceVersion(&negotiate) == VK_SUCCESS); + + VkLayerInstanceLink instance_link = {NULL, mock_gipa, NULL}; + VkLayerInstanceCreateInfo instance_loader = {VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO, NULL, VK_LAYER_LINK_INFO, + {&instance_link}}; + VkInstanceCreateInfo instance_info = {.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = &instance_loader}; + VkInstance instance; + PFN_vkCreateInstance create_instance_fn = (PFN_vkCreateInstance)vkGetInstanceProcAddr(NULL, "vkCreateInstance"); + assert(create_instance_fn(&instance_info, NULL, &instance) == VK_SUCCESS); + + /* The emulated bit is reported... */ + VkPhysicalDeviceFeatures features; + ((PFN_vkGetPhysicalDeviceFeatures)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures"))( + (VkPhysicalDevice)&mock_physical, &features); + assert(features.textureCompressionBC); + + /* ...and cleared before the driver, through pEnabledFeatures... */ + VkDevice device; + VkPhysicalDeviceFeatures legacy = {.textureCompressionBC = VK_TRUE}; + assert(create(NULL, &legacy, &device) == VK_SUCCESS && !driver_saw_bc && legacy.textureCompressionBC); + + /* ...and through a VkPhysicalDeviceFeatures2 behind another structure, leaving the caller's chain intact. */ + VkPhysicalDeviceVulkan12Features vk12 = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES}; + VkPhysicalDeviceFeatures2 features2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &vk12, + {.textureCompressionBC = VK_TRUE}}; + VkPhysicalDeviceTimelineSemaphoreFeatures timeline = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES, + &features2, VK_TRUE}; + VkPhysicalDeviceFeatures2 features2_before = features2; + VkPhysicalDeviceTimelineSemaphoreFeatures timeline_before = timeline; + VkDevice chained; + assert(create(&timeline, NULL, &chained) == VK_SUCCESS && !driver_saw_bc); + assert(!memcmp(&features2, &features2_before, sizeof(features2))); + assert(!memcmp(&timeline, &timeline_before, sizeof(timeline))); + + /* A structure newer than the headers this was built with cannot be copied: the chain passes as it + * is and the driver refuses the bit, without the caller's structures being written. */ + struct { VkStructureType sType; const void *pNext; uint32_t payload; } future = {(VkStructureType)0x7ffffff0, &features2, 7}; + VkDevice unused; + assert(create(&future, NULL, &unused) == VK_ERROR_FEATURE_NOT_PRESENT); + assert(!memcmp(&features2, &features2_before, sizeof(features2)) && future.pNext == &features2); + + /* Compute layouts get a plain sampler in place of a YCbCr one. */ + PFN_vkCreateSampler create_sampler_fn = (PFN_vkCreateSampler)vkGetDeviceProcAddr(device, "vkCreateSampler"); + PFN_vkDestroySampler destroy_sampler_fn = (PFN_vkDestroySampler)vkGetDeviceProcAddr(device, "vkDestroySampler"); + PFN_vkCreateDescriptorSetLayout create_layout = (PFN_vkCreateDescriptorSetLayout)vkGetDeviceProcAddr(device, "vkCreateDescriptorSetLayout"); + VkSamplerYcbcrConversionInfo conversion = {VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO, NULL, (VkSamplerYcbcrConversion)1}; + VkSamplerCreateInfo ycbcr_info = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = &conversion}; + VkSamplerCreateInfo plain_info = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + VkSampler ycbcr; + assert(create_sampler_fn(device, &ycbcr_info, NULL, &ycbcr) == VK_SUCCESS); + VkSampler immutable[2] = {ycbcr, ycbcr}; + VkDescriptorSetLayoutBinding binding = {4, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, VK_SHADER_STAGE_COMPUTE_BIT, immutable}; + VkDescriptorSetLayoutCreateInfo layout_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, NULL, 0, 1, &binding}; + VkDescriptorSetLayout layout; + assert(create_layout(device, &layout_info, NULL, &layout) == VK_SUCCESS); + assert(driver_layout_samplers[0] != ycbcr && immutable[0] == ycbcr); + + /* A destroyed YCbCr sampler's handle, reused for a plain sampler, is left alone. */ + destroy_sampler_fn(device, ycbcr, NULL); + next_sampler = (uint64_t)ycbcr; + VkSampler reused; + assert(create_sampler_fn(device, &plain_info, NULL, &reused) == VK_SUCCESS && reused == ycbcr); + immutable[0] = immutable[1] = reused; + assert(create_layout(device, &layout_info, NULL, &layout) == VK_SUCCESS && driver_layout_samplers[0] == reused); + + /* Registrations belong to their device and go with it. */ + VkSampler other; + assert(create_sampler_fn(chained, &ycbcr_info, NULL, &other) == VK_SUCCESS && ycbcr_count == 1); + ((PFN_vkDestroyDevice)vkGetDeviceProcAddr(chained, "vkDestroyDevice"))(chained, NULL); + assert(ycbcr_count == 0); + + /* The KHR calibrated-timestamp entry points reach the EXT ones. */ + uint32_t domains = 0; + PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT time_domains = (PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT) + vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceCalibrateableTimeDomainsKHR"); + assert(time_domains((VkPhysicalDevice)&mock_physical, &domains, NULL) == VK_SUCCESS && domains == 2); + VkCalibratedTimestampInfoEXT stamp = {VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT, NULL, VK_TIME_DOMAIN_DEVICE_EXT}; + uint64_t value = 0, deviation = 0; + PFN_vkGetCalibratedTimestampsEXT timestamps = (PFN_vkGetCalibratedTimestampsEXT)vkGetDeviceProcAddr(device, "vkGetCalibratedTimestampsKHR"); + assert(timestamps(device, 1, &stamp, &value, &deviation) == VK_SUCCESS && value == 42); + + puts("xclipse layer ok"); + return 0; +}