Merge pull request #5 from stefanz92/xclipse

Experimental Samsung Xclipse support on the system Vulkan path
This commit is contained in:
William Allen authored and GitHub committed 2026-10-12 02:31:45 -05:00
commit feff080d5b
25 files changed
+1368 -31

No files matched your search

+12 -3
View File
@@ -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"
@@ -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;
}
@@ -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"
})
@@ -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"
@@ -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");
@@ -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)
}
@@ -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 {
+6 -1
View File
@@ -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"
+7 -1
View File
@@ -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:
+5
View File
@@ -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"
@@ -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",
@@ -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"
}
}
@@ -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)
+43
View File
@@ -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"))
+493
View File
@@ -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 <vulkan/vk_layer.h>
#include <vulkan/vulkan.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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, &copy, &patched.pNext);
if (copied != VK_SUCCESS) return copied;
}
VkResult result = next_create(physical, &patched, allocator, device);
free_chain_copy(&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;
}
+1
View File
@@ -10,6 +10,7 @@ RUN apt-get update \
g++ \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
libvulkan-dev \
python3 \
zstd \
unzip \
+60 -10
View File
@@ -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)
+85
View File
@@ -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,
+2
View File
@@ -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']:
+1 -1
View File
@@ -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 "$@"
+26
View File
@@ -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))
+67
View File
@@ -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')
+32 -2
View File
@@ -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))
+47
View File
@@ -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()
+217
View File
@@ -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 <assert.h>
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;
}