diff --git a/app/src/main/assets/runtime/arm64-v8a/venus-mesa/MANIFEST.properties b/app/src/main/assets/runtime/arm64-v8a/venus-mesa/MANIFEST.properties index 0c07667..48b3831 100644 --- a/app/src/main/assets/runtime/arm64-v8a/venus-mesa/MANIFEST.properties +++ b/app/src/main/assets/runtime/arm64-v8a/venus-mesa/MANIFEST.properties @@ -3,11 +3,12 @@ runtime=26.1.5-jammy abi=arm64-v8a source_package=mesa mesa_commit=6a02618ccf6c5651ecb9cccbde571eb61fd73592 +bin/zink_check_requirements.sha256=690177f11f92a12ab413b4548051362960c4df892ad3124a89c7ad2e6b16d438 lib/dri/zink_dri.so.sha256=a85c32422ab9c0b1b98495fc2274d8bfc7c6892f02496d60f884023ca45ccc41 -lib/libEGL_mesa.so.0.sha256=b6b2e58a5e2b0b1aa6687891272625c89d727166b326cdf67cc2f040ae1cc484 +lib/libEGL_mesa.so.0.sha256=013607840e63dc6cbca87e3ca3126f46734ec102b2072b5757d509615a3a1a46 lib/libGLX_mesa.so.0.sha256=36bad44d10cdb8610cbdfce7c532c4520acd4f1a9f9e92943e66606cc530b2fb -lib/libgallium-26.1.5.so.sha256=1a190093d84f64372c9ae1b001b4adc0d226b308486dc21d8565c53f2a1df75b -lib/libvulkan_virtio.so.sha256=82b57a2c7b7ea71e2cef92919a9529c5ff2dcda9bc7f53ed1bdf38bc81ca6e47 +lib/libgallium-26.1.5.so.sha256=00de405a69fab61c49f0dbbe6a13c0b8332ffb664f20ed021baba0b713d2f06c +lib/libvulkan_virtio.so.sha256=9a21b21f90484960b3a3383144e7bd65bb725ede546ae4ce205f978b894f54a1 share/doc/license.rst.sha256=0d1a0472ecc81830e75c20d59b0ea02841e3db21255e0ebad97ab682c54d6615 share/drirc.d/00-mesa-defaults.conf.sha256=1406e018d5a83dacc9dceb5cecdc47410ad7f31a573390ec8c41c729e23c038a share/glvnd/egl_vendor.d/50_mesa.json.sha256=cd3881df4227c13ca7f9cfe6c78820f21d5f7a00a41b299214f6f827e63be369 diff --git a/app/src/main/assets/runtime/arm64-v8a/venus-mesa/bin/zink_check_requirements b/app/src/main/assets/runtime/arm64-v8a/venus-mesa/bin/zink_check_requirements new file mode 100755 index 0000000..9d2165f Binary files /dev/null and b/app/src/main/assets/runtime/arm64-v8a/venus-mesa/bin/zink_check_requirements differ diff --git a/app/src/main/assets/runtime/arm64-v8a/venus-mesa/lib/libEGL_mesa.so.0 b/app/src/main/assets/runtime/arm64-v8a/venus-mesa/lib/libEGL_mesa.so.0 index 16a2816..5c6c76d 100644 Binary files a/app/src/main/assets/runtime/arm64-v8a/venus-mesa/lib/libEGL_mesa.so.0 and b/app/src/main/assets/runtime/arm64-v8a/venus-mesa/lib/libEGL_mesa.so.0 differ diff --git a/app/src/main/assets/runtime/arm64-v8a/venus-mesa/lib/libgallium-26.1.5.so b/app/src/main/assets/runtime/arm64-v8a/venus-mesa/lib/libgallium-26.1.5.so index 680fbed..8cfb220 100644 Binary files a/app/src/main/assets/runtime/arm64-v8a/venus-mesa/lib/libgallium-26.1.5.so and b/app/src/main/assets/runtime/arm64-v8a/venus-mesa/lib/libgallium-26.1.5.so differ diff --git a/app/src/main/assets/runtime/arm64-v8a/venus-mesa/lib/libvulkan_virtio.so b/app/src/main/assets/runtime/arm64-v8a/venus-mesa/lib/libvulkan_virtio.so index 01002ab..367fc2e 100644 Binary files a/app/src/main/assets/runtime/arm64-v8a/venus-mesa/lib/libvulkan_virtio.so and b/app/src/main/assets/runtime/arm64-v8a/venus-mesa/lib/libvulkan_virtio.so differ diff --git a/app/src/main/assets/runtime/arm64-v8a/virgl-host/MANIFEST.properties b/app/src/main/assets/runtime/arm64-v8a/virgl-host/MANIFEST.properties index cbb266a..68a6931 100644 --- a/app/src/main/assets/runtime/arm64-v8a/virgl-host/MANIFEST.properties +++ b/app/src/main/assets/runtime/arm64-v8a/virgl-host/MANIFEST.properties @@ -1,13 +1,13 @@ format=1 -runtime=1.3.0-6 +runtime=1.3.0-7 abi=arm64-v8a source_package=virglrenderer-android -termux_packages_commit=978cdac5bdeb47f6061996240b65fc4e4dff11df -source_deb_sha256=847839a8cac5a349052e396f28bfe68061791e34901d4413b490e4555701399f -bin/virgl_test_server_android.sha256=c4fa02e432f2edbf974c8bf85922b6d1f7418be633bce443e5b1893c312273aa -libexec/virgl_render_server.sha256=e93c0082b7c82a8ec4c54c621e9d4c0e12fccbc7049f77b92398126199418c9e -lib/libepoxy.so.sha256=7b50fe1ef66cfea354f398523169bdb4ac8ec3b2715ac77073540bd5a1b3075f -lib/libvirglrenderer.so.sha256=15afb2811333f6947e29ebb498738bc9523708aeb05dbc9ae6630a708666798c +termux_packages_commit=d9696948261f136ccb9826a047c432f526dc99ba +source_deb_sha256=11595cd177b64c783b0ed6d3669e26b6bbd62f39e0426f1dbaf85d1a035f1c12 +bin/virgl_test_server_android.sha256=62855b51d45eb8200c19ad3710bd6e426d4e19ff5dd28061e9c094b741f5b468 +libexec/virgl_render_server.sha256=659544b68466d8af6ed85bdfd2f126903b9f36ebc6968dad034de0075b7de09c +lib/libepoxy.so.sha256=a50c278d336031d07a5e0ab43b7868496f71e56c7bf908daa0ada7bc32f0d5d8 +lib/libvirglrenderer.so.sha256=af425d45e96916d36417f45157f78518189345ff4a5611d97f5ef3ae28a89c86 share/doc/COPYING-gl4es.sha256=69a3049c77abd88658f6ad7cf6a5b6ceb5cf03b80e60f7c4657d90322cfab7bd share/doc/COPYING-libepoxy.sha256=8d5144666f9c4df9bbd69b8900086d5979259152a1060421cdcc0fb9061a1c12 share/doc/COPYING-virglrenderer.sha256=3b32caf16e2402c2aff2b716858713f3e1feded85c25e71bf829816ee5009402 diff --git a/app/src/main/assets/runtime/arm64-v8a/virgl-host/bin/virgl_test_server_android b/app/src/main/assets/runtime/arm64-v8a/virgl-host/bin/virgl_test_server_android index c8f4aac..aee5074 100755 Binary files a/app/src/main/assets/runtime/arm64-v8a/virgl-host/bin/virgl_test_server_android and b/app/src/main/assets/runtime/arm64-v8a/virgl-host/bin/virgl_test_server_android differ diff --git a/app/src/main/assets/runtime/arm64-v8a/virgl-host/lib/libepoxy.so b/app/src/main/assets/runtime/arm64-v8a/virgl-host/lib/libepoxy.so index 856fceb..e3eb358 100755 Binary files a/app/src/main/assets/runtime/arm64-v8a/virgl-host/lib/libepoxy.so and b/app/src/main/assets/runtime/arm64-v8a/virgl-host/lib/libepoxy.so differ diff --git a/app/src/main/assets/runtime/arm64-v8a/virgl-host/lib/libvirglrenderer.so b/app/src/main/assets/runtime/arm64-v8a/virgl-host/lib/libvirglrenderer.so index 9e36f84..446f77a 100755 Binary files a/app/src/main/assets/runtime/arm64-v8a/virgl-host/lib/libvirglrenderer.so and b/app/src/main/assets/runtime/arm64-v8a/virgl-host/lib/libvirglrenderer.so differ diff --git a/app/src/main/assets/runtime/arm64-v8a/virgl-host/libexec/virgl_render_server b/app/src/main/assets/runtime/arm64-v8a/virgl-host/libexec/virgl_render_server index 1609ca0..086a8d8 100755 Binary files a/app/src/main/assets/runtime/arm64-v8a/virgl-host/libexec/virgl_render_server and b/app/src/main/assets/runtime/arm64-v8a/virgl-host/libexec/virgl_render_server differ diff --git a/app/src/main/java/org/randomcoder/udroid/runtime/RuntimeSupervisorService.kt b/app/src/main/java/org/randomcoder/udroid/runtime/RuntimeSupervisorService.kt index c404399..9ae0210 100644 --- a/app/src/main/java/org/randomcoder/udroid/runtime/RuntimeSupervisorService.kt +++ b/app/src/main/java/org/randomcoder/udroid/runtime/RuntimeSupervisorService.kt @@ -45,6 +45,7 @@ import org.randomcoder.udroid.virgl.VirglHostBackend import org.randomcoder.udroid.virgl.VirglHostSnapshot import org.randomcoder.udroid.virgl.VirglProotLaunchProfile import org.randomcoder.udroid.virgl.VenusGuestRuntimeInstaller +import org.randomcoder.udroid.virgl.parseZinkRequirements import org.randomcoder.udroid.x11.X11ServerController import java.io.BufferedReader import java.io.File @@ -816,6 +817,28 @@ class RuntimeSupervisorService : Service() { ) } } + if (request.configuration.graphicsProfile == + DesktopGraphicsProfile.VENUS_EXPERIMENTAL + ) { + runCatching { + logZinkRequirements( + rootfs = rootfs, + socketDirectory = socketDirectory, + launchProfile = launchProfile, + bootId = runtime.bootId, + ) + }.onFailure { error -> + app.journal.append( + component = "graphics", + severity = "warning", + event = "zink_requirements_failed", + message = + error.message ?: "Could not run the Zink requirements probe", + bootId = runtime.bootId, + fields = mapOf("rootfs" to rootfs.name), + ) + } + } val launch = ProotDesktopLaunchBuilder.create( context = this, @@ -1147,6 +1170,83 @@ class RuntimeSupervisorService : Service() { } } + private fun logZinkRequirements( + rootfs: File, + socketDirectory: File, + launchProfile: ProotLaunchProfile?, + bootId: String?, + ) { + val profile = launchProfile as? VirglProotLaunchProfile ?: return + if (profile.backend != VirglHostBackend.VENUS || profile.venusGuestRuntime == null) return + val checker = "${VenusGuestRuntimeInstaller.GUEST_DIRECTORY}/bin/zink_check_requirements" + val launch = + ProotApplicationLaunchBuilder.create( + context = this, + runtime = ProotRuntimeInstaller.install(this), + rootfs = rootfs, + x11SocketDirectory = socketDirectory, + application = + LinuxApplication( + id = "udroid-zink-requirements", + name = "Zink requirements", + genericName = null, + comment = null, + executable = "/usr/bin/timeout", + arguments = listOf("5s", checker), + iconName = null, + iconPath = null, + desktopFilePath = "", + desktopFileGuestPath = "", + workingDirectory = "/root", + categories = emptyList(), + terminal = true, + ), + launchProfile = profile, + ) + app.journal.appendCommand( + component = "graphics", + command = launch.command, + bootId = bootId, + fields = mapOf("probe" to "zink_check_requirements", "rootfs" to rootfs.name), + ) + val process = + ProcessBuilder(launch.command) + .directory(launch.workingDirectory) + .redirectErrorStream(true) + .apply { + environment().clear() + environment().putAll(launch.environment) + }.start() + val output = process.inputStream.bufferedReader().use { it.readText() } + val exitCode = process.waitFor() + val results = parseZinkRequirements(output) + val highest = results.lastOrNull { it.supported } + val summary = + when { + exitCode != 0 -> "Zink requirements probe failed with exit code $exitCode" + highest == null -> "Venus does not satisfy the minimum Zink profile" + else -> "Highest supported Zink profile: ${highest.label}" + } + app.journal.append( + component = "graphics", + severity = if (exitCode != 0 || highest == null) "warning" else "info", + event = "zink_requirements", + message = summary, + bootId = bootId, + fields = mapOf("report" to output.trim(), "rootfs" to rootfs.name), + ) + results.forEach { result -> + app.journal.append( + component = "graphics", + severity = if (result.supported) "debug" else "info", + event = "zink_profile", + message = "${result.label}: ${if (result.supported) "supported" else "unsupported"}", + bootId = bootId, + fields = mapOf("profile" to result.profile), + ) + } + } + private fun closeVirglHost() { synchronized(virglHostLock) { ownedVirglHosts.values.forEach(VirglHostController::close) diff --git a/app/src/main/java/org/randomcoder/udroid/ui/AppShell.kt b/app/src/main/java/org/randomcoder/udroid/ui/AppShell.kt index 519b4f4..8053e2b 100644 --- a/app/src/main/java/org/randomcoder/udroid/ui/AppShell.kt +++ b/app/src/main/java/org/randomcoder/udroid/ui/AppShell.kt @@ -959,6 +959,7 @@ private fun ManagementPane( UdroidDestination.DEVICE -> DevicePage( capabilities = capabilities, + journalLines = journalLines, onRefresh = onRefresh, ) UdroidDestination.ABOUT -> @@ -1316,9 +1317,11 @@ private fun updateStatusText(state: AppUpdateState): String = @Composable private fun DevicePage( capabilities: List, + journalLines: List, onRefresh: () -> Unit, ) { val openDeveloperOptions = rememberDeveloperOptionsAction(onRefresh) + val zinkCapabilities = remember(journalLines) { zinkCompatibilityResults(journalLines) } LazyColumn( modifier = Modifier @@ -1348,6 +1351,34 @@ private fun DevicePage( onOpenDeveloperOptions = openDeveloperOptions, ) } + item { + UdroidSectionLabel( + text = "Venus and Zink", + modifier = Modifier.padding(top = 8.dp), + ) + } + if (zinkCapabilities.isEmpty()) { + item { + CapabilityRow( + capability = + CapabilityResult( + name = "Zink through Venus", + status = CapabilityStatus.INFO, + detail = + "Not checked yet. Start a desktop with Venus, then refresh this page.", + required = false, + ), + onOpenDeveloperOptions = openDeveloperOptions, + ) + } + } else { + items(zinkCapabilities) { capability -> + CapabilityRow( + capability = capability, + onOpenDeveloperOptions = openDeveloperOptions, + ) + } + } item { Spacer(Modifier.height(16.dp)) } } } diff --git a/app/src/main/java/org/randomcoder/udroid/ui/LinuxSystemPage.kt b/app/src/main/java/org/randomcoder/udroid/ui/LinuxSystemPage.kt index 818fc03..0c6d07f 100644 --- a/app/src/main/java/org/randomcoder/udroid/ui/LinuxSystemPage.kt +++ b/app/src/main/java/org/randomcoder/udroid/ui/LinuxSystemPage.kt @@ -1207,7 +1207,8 @@ private fun GraphicsProfileSelector( title = "Venus (experimental)", detail = "Accelerate Vulkan apps through Android’s driver. OpenGL stays on software " + - "rendering; X11 presentation uses Mesa’s copy path.", + "rendering; X11 presentation uses Mesa’s copy path. Zink profile results " + + "appear in Device compatibility after starting a desktop.", selected = selected, support = support, onSelected = onSelected, diff --git a/app/src/main/java/org/randomcoder/udroid/ui/ProotEnvironmentProfilesPage.kt b/app/src/main/java/org/randomcoder/udroid/ui/ProotEnvironmentProfilesPage.kt index 2ff460a..b18dcfe 100644 --- a/app/src/main/java/org/randomcoder/udroid/ui/ProotEnvironmentProfilesPage.kt +++ b/app/src/main/java/org/randomcoder/udroid/ui/ProotEnvironmentProfilesPage.kt @@ -593,7 +593,6 @@ private fun automaticManagedEnvironmentValues( -> set("GALLIUM_DRIVER", "virpipe") DesktopGraphicsProfile.VENUS_EXPERIMENTAL -> { set("VN_DEBUG", "vtest") - set("MESA_VK_WSI_DEBUG", "sw") } DesktopGraphicsProfile.GFXSTREAM_EXPERIMENTAL -> Unit } diff --git a/app/src/main/java/org/randomcoder/udroid/ui/SupervisorReport.kt b/app/src/main/java/org/randomcoder/udroid/ui/SupervisorReport.kt index 23ade6a..929870d 100644 --- a/app/src/main/java/org/randomcoder/udroid/ui/SupervisorReport.kt +++ b/app/src/main/java/org/randomcoder/udroid/ui/SupervisorReport.kt @@ -5,6 +5,8 @@ import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive +import org.randomcoder.udroid.runtime.CapabilityResult +import org.randomcoder.udroid.runtime.CapabilityStatus internal data class DiagnosticLogEntry( val raw: String, @@ -14,6 +16,7 @@ internal data class DiagnosticLogEntry( val event: String, val message: String, val fields: String, + val fieldValues: Map = emptyMap(), ) internal fun formatDiagnosticLogEntry(entry: DiagnosticLogEntry): String = @@ -51,6 +54,10 @@ private fun parseDiagnosticLogEntry(line: String): DiagnosticLogEntry { val payload = runCatching { Json.parseToJsonElement(line).jsonObject }.getOrNull() ?: return DiagnosticLogEntry(line, "", "raw", "journal", "event", line, "") val fields = payload["fields"] as? JsonObject + val fieldValues = + fields + ?.mapValues { (_, value) -> (value as? JsonPrimitive)?.content ?: value.toString() } + .orEmpty() return DiagnosticLogEntry( raw = line, timestamp = payload.string("timestamp").replace('T', ' '), @@ -59,16 +66,66 @@ private fun parseDiagnosticLogEntry(line: String): DiagnosticLogEntry { event = payload.string("event").ifBlank { "event" }, message = payload.string("message").ifBlank { line }, fields = - fields - ?.toSortedMap() + fieldValues + .toSortedMap() ?.entries ?.joinToString(" ") { (key, value) -> - "$key=${(value as? JsonPrimitive)?.content ?: value}" + "$key=$value" } .orEmpty(), + fieldValues = fieldValues, ) } +internal fun zinkCompatibilityResults( + newestFirstJournalLines: List, +): List { + val entries = newestFirstJournalLines.map(::parseDiagnosticLogEntry) + val summaryIndex = + entries.indexOfFirst { + it.component == "graphics" && + it.event in setOf("zink_requirements", "zink_requirements_failed") + } + if (summaryIndex < 0) return emptyList() + + val summary = entries[summaryIndex] + val rootfs = summary.fieldValues["rootfs"] + return buildList { + add( + CapabilityResult( + name = "Zink through Venus", + status = + if (summary.severity.equals("warning", ignoreCase = true)) { + CapabilityStatus.WARNING + } else { + CapabilityStatus.PASS + }, + detail = listOfNotNull(summary.message, rootfs?.let { "Linux system: $it" }).joinToString(" · "), + required = false, + ), + ) + entries + .take(summaryIndex) + .filter { it.component == "graphics" && it.event == "zink_profile" } + .asReversed() + .forEach { entry -> + val supported = entry.message.endsWith(": supported") + add( + CapabilityResult( + name = entry.message.substringBeforeLast(": "), + status = if (supported) CapabilityStatus.PASS else CapabilityStatus.FAIL, + detail = + listOfNotNull( + entry.fieldValues["profile"], + rootfs?.let { "Linux system: $it" }, + ).joinToString(" · "), + required = false, + ), + ) + } + } +} + private fun JsonObject.string(key: String): String = this[key]?.jsonPrimitive?.content.orEmpty() internal fun buildSupervisorReport( diff --git a/app/src/main/java/org/randomcoder/udroid/virgl/VirglHostRuntime.kt b/app/src/main/java/org/randomcoder/udroid/virgl/VirglHostRuntime.kt index c0d93ed..66ab002 100644 --- a/app/src/main/java/org/randomcoder/udroid/virgl/VirglHostRuntime.kt +++ b/app/src/main/java/org/randomcoder/udroid/virgl/VirglHostRuntime.kt @@ -20,7 +20,7 @@ internal data class VenusGuestRuntime( ) internal object VirglHostRuntimeInstaller { - const val VERSION = "1.3.0-6" + const val VERSION = "1.3.0-7" private val bundle = VerifiedRuntimeAssetBundle( name = "VirGL host", @@ -92,6 +92,7 @@ internal object VenusGuestRuntimeInstaller { version = VERSION, entries = listOf( + "bin/zink_check_requirements", "lib/dri/zink_dri.so", "lib/libEGL_mesa.so.0", "lib/libGLX_mesa.so.0", @@ -102,6 +103,7 @@ internal object VenusGuestRuntimeInstaller { "share/glvnd/egl_vendor.d/50_mesa.json", "share/vulkan/icd.d/virtio_icd.aarch64.json", ), + executables = setOf("bin/zink_check_requirements"), metadataKeys = setOf("mesa_commit"), ) @@ -129,3 +131,34 @@ internal object VenusGuestRuntimeInstaller { return values["ID"] == "ubuntu" && values["VERSION_ID"] in setOf("22.04", "24.04") } } + +internal data class ZinkRequirementResult( + val profile: String, + val supported: Boolean, +) { + val label: String = + profile + .removePrefix("VP_ZINK_") + .replace(Regex("^gl(\\d)(\\d)_"), "OpenGL $1.$2 ") + .replace('_', ' ') +} + +internal fun parseZinkRequirements(output: String): List { + var profile: String? = null + return buildList { + output.lineSequence().forEach { line -> + when { + line.startsWith("Checking profile ") -> + profile = line.removePrefix("Checking profile ").trim() + line == "Supported" -> { + profile?.let { add(ZinkRequirementResult(it, true)) } + profile = null + } + line.startsWith("UNSUPPORTED") -> { + profile?.let { add(ZinkRequirementResult(it, false)) } + profile = null + } + } + } + } +} diff --git a/app/src/main/java/org/randomcoder/udroid/virgl/VirglProotLaunchProfile.kt b/app/src/main/java/org/randomcoder/udroid/virgl/VirglProotLaunchProfile.kt index ecda65c..b2e3b99 100644 --- a/app/src/main/java/org/randomcoder/udroid/virgl/VirglProotLaunchProfile.kt +++ b/app/src/main/java/org/randomcoder/udroid/virgl/VirglProotLaunchProfile.kt @@ -40,8 +40,9 @@ internal data class VirglProotLaunchProfile( "GALLIUM_DRIVER", "-u", "MESA_LOADER_DRIVER_OVERRIDE", + "-u", + "MESA_VK_WSI_DEBUG", "VN_DEBUG=vtest", - "MESA_VK_WSI_DEBUG=sw", ) + if (venusGuestRuntime != null) { listOf( diff --git a/app/src/main/java/org/randomcoder/udroid/x11/X11ServerService.kt b/app/src/main/java/org/randomcoder/udroid/x11/X11ServerService.kt index a692938..53e38e5 100644 --- a/app/src/main/java/org/randomcoder/udroid/x11/X11ServerService.kt +++ b/app/src/main/java/org/randomcoder/udroid/x11/X11ServerService.kt @@ -14,6 +14,7 @@ import android.util.Log import java.io.File import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicBoolean +import org.randomcoder.udroid.BuildConfig class X11ServerService : Service() { private val monitorExecutor = @@ -142,6 +143,7 @@ class X11ServerService : Service() { Os.setenv("TMPDIR", runtime.absolutePath, true) Os.setenv("XKB_CONFIG_ROOT", xkbRoot.canonicalPath, true) Os.setenv("HOME", filesRoot.absolutePath, true) + if (BuildConfig.DEBUG) Os.setenv("TERMUX_X11_DEBUG", "1", true) } private fun monitorSocket( diff --git a/app/src/test/java/org/randomcoder/udroid/ui/SupervisorReportTest.kt b/app/src/test/java/org/randomcoder/udroid/ui/SupervisorReportTest.kt index 3e8b3d9..33c3aaf 100644 --- a/app/src/test/java/org/randomcoder/udroid/ui/SupervisorReportTest.kt +++ b/app/src/test/java/org/randomcoder/udroid/ui/SupervisorReportTest.kt @@ -44,4 +44,21 @@ class SupervisorReportTest { formatDiagnosticLogEntry(entry), ) } + + @Test + fun latestZinkCheckBecomesReadableCompatibilityRows() { + val results = + zinkCompatibilityResults( + listOf( + """{"timestamp":"2026-10-03T09:00:02Z","component":"graphics","severity":"info","event":"zink_profile","message":"OpenGL 4.6 baseline: unsupported","fields":{"profile":"VP_ZINK_gl46_baseline"}}""", + """{"timestamp":"2026-10-03T09:00:01Z","component":"graphics","severity":"debug","event":"zink_profile","message":"OpenGL 2.1 baseline: supported","fields":{"profile":"VP_ZINK_gl21_baseline"}}""", + """{"timestamp":"2026-10-03T09:00:00Z","component":"graphics","severity":"info","event":"zink_requirements","message":"Highest supported Zink profile: OpenGL 2.1 baseline","fields":{"rootfs":"proot-ubuntu-jammy"}}""", + ), + ) + + assertEquals("Zink through Venus", results[0].name) + assertEquals("OpenGL 2.1 baseline", results[1].name) + assertEquals("OpenGL 4.6 baseline", results[2].name) + assertEquals("VP_ZINK_gl46_baseline · Linux system: proot-ubuntu-jammy", results[2].detail) + } } diff --git a/app/src/test/java/org/randomcoder/udroid/virgl/VirglHostLaunchTest.kt b/app/src/test/java/org/randomcoder/udroid/virgl/VirglHostLaunchTest.kt index 8970cc8..218e63c 100644 --- a/app/src/test/java/org/randomcoder/udroid/virgl/VirglHostLaunchTest.kt +++ b/app/src/test/java/org/randomcoder/udroid/virgl/VirglHostLaunchTest.kt @@ -166,7 +166,7 @@ class VirglHostLaunchTest { } @Test - fun `selects Venus vtest with the X11 copy presentation path`() { + fun `selects Venus vtest with native WSI presentation`() { val socket = Files.createTempFile("udroid-venus", ".sock").toFile() val runtime = Files.createTempDirectory("udroid-venus-runtime").toFile() val profile = @@ -197,8 +197,9 @@ class VirglHostLaunchTest { "GALLIUM_DRIVER", "-u", "MESA_LOADER_DRIVER_OVERRIDE", + "-u", + "MESA_VK_WSI_DEBUG", "VN_DEBUG=vtest", - "MESA_VK_WSI_DEBUG=sw", "LD_LIBRARY_PATH=/opt/udroid/venus-mesa/lib:" + "/usr/lib/aarch64-linux-gnu:/lib/aarch64-linux-gnu", "LIBGL_DRIVERS_PATH=/opt/udroid/venus-mesa/lib/dri", @@ -227,4 +228,29 @@ class VirglHostLaunchTest { osRelease.writeText("ID=ubuntu\nVERSION_ID=\"20.04\"\n") assertFalse(VenusGuestRuntimeInstaller.requiresBundledMesa(rootfs)) } + + @Test + fun `parses verbose Zink profile results`() { + val results = + parseZinkRequirements( + """ + Checking profile VP_ZINK_gl21_baseline + Checking device Virtio-GPU Venus + Supported + + Checking profile VP_ZINK_gl46_baseline + Checking device Virtio-GPU Venus + UNSUPPORTED physical device + """.trimIndent(), + ) + + assertEquals( + listOf( + ZinkRequirementResult("VP_ZINK_gl21_baseline", true), + ZinkRequirementResult("VP_ZINK_gl46_baseline", false), + ), + results, + ) + assertEquals("OpenGL 2.1 baseline", results.first().label) + } } diff --git a/docs/DESKTOP_INPUT_AND_SESSION_ROADMAP.md b/docs/DESKTOP_INPUT_AND_SESSION_ROADMAP.md index 35a44f3..221a59f 100644 --- a/docs/DESKTOP_INPUT_AND_SESSION_ROADMAP.md +++ b/docs/DESKTOP_INPUT_AND_SESSION_ROADMAP.md @@ -539,3 +539,58 @@ not prohibited by the architecture. Normal UI may call these Linux workspaces. Advanced UI may expose display numbers, PIDs, sockets, leases, and renderer state. + +## Native Wayland session (planned, opt-in) + +Wayland is a separate session backend, not Wayland-over-Lorie. Keep the existing +Lorie/X11 session unchanged as the fallback. The Android app owns the Wayland +compositor and its Android surface; Linux clients run in the selected PRoot and +connect through a private Wayland socket. A guest desktop compositor such as +KWin is nested as a Wayland client, so this is rootless and does not pretend +that PRoot owns DRM/KMS. + +Use an Android-hosted wlroots compositor as the reference architecture. +WLDroid at `9560e767bb3e31f3d9b72d280e051c47cda35cf1` is a useful black-box +reference, not a safe integration dependency yet. Its current code advertises +fabricated DRM device feedback, assumes a private AHardwareBuffer native-handle +layout for DMA-BUF export, submits buffers without acquire fences, and drives +frames from a fixed 16 ms timer. Do not carry these assumptions into uDroid. +Audit every dependency license before considering any reuse. Do not port the +GPL-3.0 wlroots-android-bridge code into uDroid. The existing experimental +AHardwareBuffer/SurfaceControl presenter is a candidate output component, not +proof that the complete Wayland protocol, client-buffer import, or KWin path is +ready. + +### First checkpoint and current status + +The pinned source audit is complete, but its reference build is not. The local +macOS build reached the cross-build and stopped because the host Bison is 2.3, +below libxkbcommon's required 3.6. Meson was installed only in a temporary +Python environment; no APK or compositor library was produced, and nothing +was installed on a device. + +Do not run stock WLDroid with KWin: it advertises fabricated DRM device +feedback and its AHardwareBuffer output omits acquire fences. A standalone APK +also cannot connect to uDroid's app-private rootfs/socket because it has a +different Android UID. These are architectural blockers, not just build +friction. + +1. Keep the current X11 path unchanged. Do not import WLDroid's Proot, launcher, + VirGL, or Android shims into uDroid. +2. If continuing with this reference, first make a disposable development + variant that completely disables its linux-dmabuf global/feedback and uses + a software/SHM client-buffer path plus CPU-copy presentation. Do not expose + fake DRM identity or the current no-fence AHardwareBuffer path. +3. Embed that compositor in a uDroid dev build so the selected distro connects + through a same-UID private Wayland socket; a separate APK cannot test this + boundary. +4. Launch `weston-simple-shm`; verify pixels, resize, touch/pointer/keyboard + input, detach/reattach, and clean stop/restart before trying EGL or KWin. +5. Add EGL and guest KWin only after the SHM/lifecycle gate passes. Keep + `linux-dmabuf` disabled until device feedback is truthful and the complete + buffer import plus acquire/release ordering is validated. + +The first checkpoint passes only if X11 remains unaffected, the SHM client is +visibly correct, input reaches it, and the session survives lifecycle cycles. +GPU acceleration, Plasma support, and performance qualification are later +gates; the Wayland option stays hidden/disabled until each is demonstrated. diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md index ebf55a9..18ba40a 100644 --- a/docs/PERFORMANCE.md +++ b/docs/PERFORMANCE.md @@ -62,6 +62,9 @@ Keep expensive runtime scenarios small and reproducible first. Add focused Macrobenchmarks or microbenchmarks for a suspected cost, inspect traces, then validate the resulting change in the complete user journey. +For Venus, Zink, Lorie, and SurfaceFlinger profiling, use the reproducible +[Venus Perfetto capture](graphics/VENUS_PERFETTO_PROFILING.md). + References: - [Compose performance](https://developer.android.com/develop/ui/compose/performance) diff --git a/docs/graphics/VENUS_PERFETTO_PROFILING.md b/docs/graphics/VENUS_PERFETTO_PROFILING.md new file mode 100644 index 0000000..28feb90 --- /dev/null +++ b/docs/graphics/VENUS_PERFETTO_PROFILING.md @@ -0,0 +1,113 @@ +# Venus and Zink Perfetto profiling + +Use this trace when a Venus or Venus/Zink desktop feels slow, misses frames, +or stalls while the renderer still appears healthy. It separates guest work, +Lorie presentation, and Android composition instead of treating one FPS number +as the cause. + +## Capture + +1. Select **Venus (experimental)**, start the desktop, and open **Display**. +2. Start a continuously rendered workload. A static desktop or cursor-only + movement is not a useful graphics baseline. +3. Record the 15-second trace while reproducing the slowdown: + +```sh +adb shell perfetto --txt -c - \ + -o /data/misc/perfetto-traces/udroid-venus.perfetto-trace \ + < tools/profiling/venus-perfetto.pbtxt +adb pull /data/misc/perfetto-traces/udroid-venus.perfetto-trace . +``` + +Open the trace in [Perfetto UI](https://ui.perfetto.dev/). Keep the workload, +resolution, compositor setting, graphics profile, device temperature, and app +build identical when comparing two traces. + +The config records the uDroid process, CPU scheduling and frequency, Mali job +submission/completion, GPU frequency and memory, SurfaceFlinger frames and +layers, and FrameTimeline. It intentionally does not request +`gpu.renderstages`: the Pixel 6a used for this checkpoint does not publish that +data source. Check another device with: + +```sh +adb shell perfetto --query --long +``` + +Unavailable vendor ftrace events may be omitted by that device. Keep the +portable scheduling, SurfaceFlinger, and uDroid trace sources enabled. + +## Read the trace + +Find these tracks in order: + +1. The guest workload and `virgl_render_server` threads: look for runnable but + unscheduled gaps or a saturated CPU. +2. `org.randomcoder.udroid.dev`: expand the Lorie renderer thread and inspect + `uDroid/Lorie draw`, `root fence wait`, `eglSwapBuffers`, and + `post-swap fence wait` slices. +3. GPU frequency and Mali job tracks: confirm whether the GPU is busy while a + Lorie wait is open. +4. The uDroid `SurfaceView` under SurfaceFlinger: compare buffer arrival with + expected and actual presentation. + +A stable 16.67 ms interval is one frame at 60 Hz, not evidence of 16.67 ms of +rendering. Investigate intervals that repeatedly become 33.3 ms or longer, +long fence slices, scheduling gaps, or buffers that arrive after their desired +presentation time. + +FrameTimeline does not attribute every `SurfaceView` producer correctly on +every Android release. Treat it as Android composition evidence and correlate +it with the Lorie slices and SurfaceFlinger layer rather than using it alone. + +Use **Query (SQL)** for a reproducible summary: + +```sql +SELECT name, + count(*) AS frames, + round(avg(dur) / 1000000.0, 3) AS avg_ms, + round(max(dur) / 1000000.0, 3) AS max_ms +FROM slice +WHERE name LIKE 'uDroid/Lorie%' +GROUP BY name +ORDER BY name; +``` + +```sql +SELECT present_type, on_time_finish, jank_type, count(*) AS frames +FROM actual_frame_timeline_slice +GROUP BY present_type, on_time_finish, jank_type +ORDER BY frames DESC; +``` + +```sql +SELECT min(value) AS min_khz, + round(avg(value), 1) AS avg_khz, + max(value) AS max_khz +FROM counter +JOIN counter_track ON counter_track.id = counter.track_id +WHERE counter_track.type = 'gpu_frequency'; +``` + +## Pixel 6a checkpoint + +On Android 17 with uDroid `0.1.4+dev`, a 15-second Venus/Zink `glxgears` +capture produced 898 Lorie frames and 899 on-time SurfaceFlinger presents with +no reported jank. Lorie draw averaged 4.37 ms; the root-fence wait averaged +1.39 ms, `eglSwapBuffers` 0.79 ms, and the post-swap wait 0.73 ms. GPU +frequency averaged 171 MHz and peaked at 434 MHz. + +That run held the 60 Hz presentation budget. It also emitted per-frame +`writeReleaseFence ... Broken pipe` messages, so treat that message as a +separate Android buffer-release-channel compatibility problem, not proof of a +slow frame by itself. + +## Comparison gate + +Use at least three matched captures. A change is an improvement only if it +reduces p95/p99 missed-frame intervals or CPU/GPU work without introducing +corruption, transparent frames, resize failures, or teardown hangs. Do not +promote a Zink environment option from a single FPS result. + +References: [Perfetto FrameTimeline](https://perfetto.dev/docs/data-sources/frametimeline), +[GPU tracing](https://perfetto.dev/docs/data-sources/gpu), and +[ATrace](https://perfetto.dev/docs/data-sources/atrace). diff --git a/patches/termux-x11/0008-advertise-buffer-transport-protocol.patch b/patches/termux-x11/0008-advertise-buffer-transport-protocol.patch index 2d77d54..69c1dca 100644 --- a/patches/termux-x11/0008-advertise-buffer-transport-protocol.patch +++ b/patches/termux-x11/0008-advertise-buffer-transport-protocol.patch @@ -58,3 +58,83 @@ return TRUE; } /* end lorieScreenInit */ +@@ -1184,32 +1219,56 @@ + return NULL; + } +- +-static int lorieGetFormats(__unused ScreenPtr screen, CARD32 *num_formats, CARD32 **formats) { +- static CARD32 format = DRM_FORMAT_ARGB8888; +- *num_formats = 1; +- *formats = &format; +- return TRUE; +-} +- +-static int lorieGetModifiers(__unused ScreenPtr screen, uint32_t format, uint32_t *num_modifiers, uint64_t **modifiers) { +- static uint64_t modifier = DRM_FORMAT_MOD_LINEAR; +- +- if (format != DRM_FORMAT_ARGB8888 && format != DRM_FORMAT_XRGB8888) { +- *num_modifiers = 0; +- *modifiers = NULL; +- return TRUE; +- } +- +- *num_modifiers = 1; +- *modifiers = &modifier; +- return TRUE; +-} +- ++ ++static int lorieGetFormats(__unused ScreenPtr screen, CARD32 *num_formats, CARD32 **formats) { ++ static CARD32 format = DRM_FORMAT_ARGB8888; ++ *num_formats = 1; ++ *formats = &format; ++ return TRUE; ++} ++ ++static uint64_t lorieSupportedModifiers[] = { ++ 1255, /* AHardwareBuffer BGRA socket transport */ ++ 1257, /* AHardwareBuffer RGBA socket transport */ ++ DRM_FORMAT_MOD_LINEAR, ++}; ++ ++static int lorieGetModifiers(__unused ScreenPtr screen, uint32_t format, uint32_t *num_modifiers, uint64_t **modifiers) { ++ if (format != DRM_FORMAT_ARGB8888 && format != DRM_FORMAT_XRGB8888) { ++ *num_modifiers = 0; ++ *modifiers = NULL; ++ return TRUE; ++ } ++ ++ *num_modifiers = sizeof(lorieSupportedModifiers) / sizeof(lorieSupportedModifiers[0]); ++ *modifiers = lorieSupportedModifiers; ++ return TRUE; ++} ++ ++static int lorieGetDrawableModifiers(__unused DrawablePtr draw, uint32_t format, ++ uint32_t *num_modifiers, uint64_t **modifiers) { ++ uint64_t *copy; ++ ++ if (format != DRM_FORMAT_ARGB8888 && format != DRM_FORMAT_XRGB8888) { ++ *num_modifiers = 0; ++ *modifiers = NULL; ++ return TRUE; ++ } ++ ++ copy = malloc(sizeof(lorieSupportedModifiers)); ++ if (!copy) ++ return FALSE; ++ ++ memcpy(copy, lorieSupportedModifiers, sizeof(lorieSupportedModifiers)); ++ *num_modifiers = sizeof(lorieSupportedModifiers) / sizeof(lorieSupportedModifiers[0]); ++ *modifiers = copy; ++ return TRUE; ++} ++ + static dri3_screen_info_rec lorieDri3Info = { + .version = 2, + .fds_from_pixmap = FalseNoop, + .pixmap_from_fds = loriePixmapFromFds, + .get_formats = lorieGetFormats, + .get_modifiers = lorieGetModifiers, +- .get_drawable_modifiers = FalseNoop ++ .get_drawable_modifiers = lorieGetDrawableModifiers + }; diff --git a/patches/termux-x11/0011-lorie-perfetto-slices.patch b/patches/termux-x11/0011-lorie-perfetto-slices.patch new file mode 100644 index 0000000..34e72db --- /dev/null +++ b/patches/termux-x11/0011-lorie-perfetto-slices.patch @@ -0,0 +1,72 @@ +--- a/renderer.c ++++ b/renderer.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -30,6 +31,18 @@ + #define log(...) __android_log_print(ANDROID_LOG_DEBUG, "gles-renderer", __VA_ARGS__) + #define loge(...) __android_log_print(ANDROID_LOG_ERROR, "gles-renderer", __VA_ARGS__) + ++static inline bool traceBegin(const char *name) { ++ bool enabled = ATrace_isEnabled(); ++ if (enabled) ++ ATrace_beginSection(name); ++ return enabled; ++} ++ ++static inline void traceEnd(bool enabled) { ++ if (enabled) ++ ATrace_endSection(); ++} ++ + static GLuint createProgram(const char* p_vertex_source, const char* p_fragment_source); + + static int printEglError(char* msg, int line) { +@@ -890,6 +903,7 @@ void rendererRedrawLocked(bool* waitingForBuffers) { + sourceLeft, sourceTop, logicalSourceWidth, logicalSourceHeight); + + // We should signal X server to not use root window while we actively copy it ++ bool frameTrace = traceBegin("uDroid/Lorie draw"); + lorie_mutex_lock(&state->lock, &state->lockingPid); + // Share this draw's flush+fence below instead of a separate round trip per frame. + uint64_t gpuCopySerial = rendererApplyPendingGpuCopiesLocked(); +@@ -920,7 +934,9 @@ void rendererRedrawLocked(bool* waitingForBuffers) { + glFlush(); + + // Wait until root window drawing is finished before giving control back to X server ++ bool rootFenceTrace = traceBegin("uDroid/Lorie root fence wait"); + eglClientWaitSyncKHR(egl_display, fence, 0, EGL_FOREVER); ++ traceEnd(rootFenceTrace); + eglDestroySyncKHR(egl_display, fence); + if (gpuCopySerial) { + state->gpuCopyQueue.completedSerial = gpuCopySerial; +@@ -929,8 +945,10 @@ void rendererRedrawLocked(bool* waitingForBuffers) { + state->waitForNextFrame = true; + lorie_mutex_unlock(&state->lock, &state->lockingPid); + ++ bool swapTrace = traceBegin("uDroid/Lorie eglSwapBuffers"); + if (eglSwapBuffers(egl_display, sfc) != EGL_TRUE) + printEglError("Failed to swap buffers", __LINE__); ++ traceEnd(swapTrace); + + // Perform a little drawing operation to make sure the next buffer is ready on the next invocation of drawing + glEnable(GL_SCISSOR_TEST); +@@ -939,10 +957,13 @@ void rendererRedrawLocked(bool* waitingForBuffers) { + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + fence = eglCreateSyncKHR(egl_display, EGL_SYNC_FENCE_KHR, NULL); ++ bool postSwapFenceTrace = traceBegin("uDroid/Lorie post-swap fence wait"); + eglClientWaitSyncKHR(egl_display, fence, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER); ++ traceEnd(postSwapFenceTrace); + eglDestroySyncKHR(egl_display, fence); + + state->renderedFrames++; ++ traceEnd(frameTrace); + } + + static inline __always_inline bool rendererShouldWait(bool *waitingForBuffers) { diff --git a/patches/termux-x11/0012-lorie-raw-fd-import-diagnostics.patch b/patches/termux-x11/0012-lorie-raw-fd-import-diagnostics.patch new file mode 100644 index 0000000..c75fb7c --- /dev/null +++ b/patches/termux-x11/0012-lorie-raw-fd-import-diagnostics.patch @@ -0,0 +1,79 @@ +Log the complete raw-FD import contract and preserve the failing errno. + +--- a/buffer.c ++++ b/buffer.c +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -175,9 +176,11 @@ + AHardwareBuffer_Desc desc = {0}; + static uint64_t id = 0; + bool acceptable = (format == AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM || format == AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM) && width > 0 && height > 0; ++ int ownedFd = takeFd ? fd : dup(fd); ++ int duplicateError = !takeFd && ownedFd < 0 ? errno : 0; + LorieBuffer b = { .desc = { .width = width, .stride = stride, .height = height, .format = format, .type = type, + .needsBgraSwizzle = format != AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM, .buffer = buf, .id = id++ }, +- .fd = takeFd ? fd : dup(fd), .size = size, .offset = offset }; ++ .fd = ownedFd, .size = size, .offset = offset }; + + if (type != LORIEBUFFER_AHARDWAREBUFFER && !acceptable) + return NULL; +@@ -194,11 +197,27 @@ + break; + case LORIEBUFFER_FD: +- if (b.fd < 0) ++ if (b.fd < 0) { ++ int error = duplicateError ? duplicateError : EBADF; ++ struct stat info = {0}; ++ off_t fdSize = fstat(fd, &info) == 0 ? info.st_size : -1; ++ dprintf(2, "LorieBuffer: raw-fd import stage=%s source_fd=%d owned_fd=%d fd_size=%lld width=%d height=%d stride=%d offset=%lld map_size=%zu errno=%d (%s)\n", ++ takeFd ? "validate" : "dup", fd, b.fd, (long long) fdSize, ++ width, height, stride, (long long) offset, size, ++ error, strerror(error)); ++ errno = error; + return NULL; ++ } + + b.desc.data = mmap(NULL, b.size, PROT_READ|PROT_WRITE, MAP_SHARED, b.fd, b.offset); + if (b.desc.data == NULL || b.desc.data == MAP_FAILED) { ++ int error = errno; ++ struct stat info = {0}; ++ off_t fdSize = fstat(b.fd, &info) == 0 ? info.st_size : -1; ++ dprintf(2, "LorieBuffer: raw-fd import stage=mmap source_fd=%d owned_fd=%d fd_size=%lld width=%d height=%d stride=%d offset=%lld map_size=%zu errno=%d (%s)\n", ++ fd, b.fd, (long long) fdSize, width, height, stride, ++ (long long) offset, size, error, strerror(error)); + close(b.fd); ++ errno = error; + return NULL; + } + break; +--- a/InitOutput.c ++++ b/InitOutput.c +@@ -1206,10 +1206,20 @@ + priv->imported = true; + + if (modifier == DRM_FORMAT_MOD_INVALID || modifier == DRM_FORMAT_MOD_LINEAR || modifier == RAW_MMAPPABLE_FD) { +- check(!(priv->buffer = LorieBuffer_wrapFileDescriptor(width, strides[0]/4, height, AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM, fds[0], offsets[0])), "DRI3: LorieBuffer_wrapFileDescriptor failed for modifier %llu, %ux%u stride %u offset %u: %s", (unsigned long long) modifier, width, height, strides[0], offsets[0], strerror(errno)); ++ struct stat fdInfo = {0}; ++ int fdStatResult = fstat(fds[0], &fdInfo); ++ int fdStatError = fdStatResult == 0 ? 0 : errno; ++ off_t fdSize = fdStatResult == 0 ? fdInfo.st_size : -1; ++ if (lorieServerDebugEnabled) ++ log(INFO, "DRI3: raw-fd import request modifier=%llu fd=%d fd_size=%lld dimensions=%ux%u stride=%lu offset=%lu fstat_errno=%d", ++ (unsigned long long) modifier, fds[0], (long long) fdSize, ++ width, height, (unsigned long) strides[0], (unsigned long) offsets[0], fdStatError); ++ check(!(priv->buffer = LorieBuffer_wrapFileDescriptor(width, strides[0]/4, height, AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM, fds[0], offsets[0])), "DRI3: raw-fd import failed modifier=%llu fd=%d fd_size=%lld dimensions=%ux%u stride=%lu offset=%lu errno=%d (%s)", (unsigned long long) modifier, fds[0], (long long) fdSize, width, height, (unsigned long) strides[0], (unsigned long) offsets[0], errno, strerror(errno)); + screen->ModifyPixmapHeader(pixmap, width, height, 0, 0, strides[0], NULL); + if (lorieServerDebugEnabled) +- log(INFO, "DRI3: imported raw fd, modifier %llu, %ux%u stride %u", (unsigned long long) modifier, width, height, strides[0]); ++ log(INFO, "DRI3: raw-fd import succeeded modifier=%llu fd=%d fd_size=%lld dimensions=%ux%u stride=%lu offset=%lu", ++ (unsigned long long) modifier, fds[0], (long long) fdSize, ++ width, height, (unsigned long) strides[0], (unsigned long) offsets[0]); + return pixmap; + } + diff --git a/patches/termux-x11/0013-fix-present-flip-height-check.patch b/patches/termux-x11/0013-fix-present-flip-height-check.patch new file mode 100644 index 0000000..fc8fac6 --- /dev/null +++ b/patches/termux-x11/0013-fix-present-flip-height-check.patch @@ -0,0 +1,14 @@ +Compare the full-screen pixmap height with the root height. + +--- a/InitOutput.c ++++ b/InitOutput.c +@@ -1038,7 +1038,8 @@ + + Bool loriePresentFlip(__unused RRCrtcPtr crtc, __unused uint64_t event_id, __unused uint64_t target_msc, PixmapPtr pixmap, __unused Bool sync_flip) { + LoriePixmapPriv* priv = (LoriePixmapPriv*) exaGetPixmapDriverPrivate(pixmap); +- if (!priv || !priv->buffer || priv->mem || pvfb->root.width != pixmap->drawable.width || pvfb->root.width != pixmap->drawable.height) ++ /* Compare each pixmap axis with the matching root axis. */ ++ if (!priv || !priv->buffer || priv->mem || pvfb->root.width != pixmap->drawable.width || pvfb->root.height != pixmap->drawable.height) + return FALSE; + + const LorieBuffer_Desc *desc = LorieBuffer_description(priv->buffer); diff --git a/patches/termux-x11/0014-lorie-present-lifecycle-diagnostics.patch b/patches/termux-x11/0014-lorie-present-lifecycle-diagnostics.patch new file mode 100644 index 0000000..23dd68e --- /dev/null +++ b/patches/termux-x11/0014-lorie-present-lifecycle-diagnostics.patch @@ -0,0 +1,217 @@ +--- a/lorie/InitOutput.c ++++ b/lorie/InitOutput.c +@@ -998,6 +998,14 @@ Bool lorieTryScheduleGpuCopy(PixmapPtr pixmap, PixmapPtr dst, RegionPtr update, + for (i = 0; i < numRects; i++) + entry->rects[i] = (LorieGpuCopyRect) { box[i].x1, box[i].y1, box[i].x2, box[i].y2 }; + ++ if (lorieServerDebugEnabled) ++ log(INFO, "Present GPU copy schedule serial=%llu src_pixmap=0x%lx dst_pixmap=0x%lx src_buffer=%llu dst_buffer=%llu src=%dx%d dst=%dx%d rects=%d queue=%u->%u", ++ (unsigned long long) entry->serial, ++ (unsigned long) pixmap->drawable.id, (unsigned long) dst->drawable.id, ++ (unsigned long long) entry->srcBufferId, (unsigned long long) entry->dstBufferId, ++ desc->width, desc->height, dstDesc->width, dstDesc->height, ++ numRects, readIndex, writeIndex + 1); ++ + __sync_synchronize(); // publish entry contents before the renderer can see the new writeIndex + pvfb->state->gpuCopyQueue.writeIndex = writeIndex + 1; + pthread_cond_signal(rendererCond); +@@ -1011,8 +1019,23 @@ Bool lorieGpuCopyIsDone(uint64_t serial) { + return pvfb->state->gpuCopyQueue.completedSerial >= serial; + } + +-void lorieGpuCopyAck(PixmapPtr pixmap, void *dst_buffer) { ++uint64_t lorieGpuCopyCompletedSerial(void) { ++ return pvfb->state->gpuCopyQueue.completedSerial; ++} ++ ++Bool loriePresentDebugEnabled(void) { ++ return lorieServerDebugEnabled; ++} ++ ++void lorieGpuCopyAck(PixmapPtr pixmap, void *dst_buffer, uint64_t serial) { + LoriePixmapPriv *priv = LORIE_PIXMAP_PRIV_FROM_PIXMAP(pixmap); ++ const LorieBuffer_Desc *srcDesc = priv && priv->buffer ? LorieBuffer_description(priv->buffer) : NULL; ++ const LorieBuffer_Desc *dstDesc = dst_buffer ? LorieBuffer_description((LorieBuffer *) dst_buffer) : NULL; ++ if (lorieServerDebugEnabled) ++ log(INFO, "Present GPU copy ack serial=%llu pixmap=0x%lx src_buffer=%llu dst_buffer=%llu dst_is_root=%d", ++ (unsigned long long) serial, pixmap ? (unsigned long) pixmap->drawable.id : 0, ++ (unsigned long long) (srcDesc ? srcDesc->id : 0), ++ (unsigned long long) (dstDesc ? dstDesc->id : 0), dst_buffer == NULL); + if (priv && priv->buffer) + LorieBuffer_release(priv->buffer); + if (dst_buffer) +@@ -1133,7 +1157,14 @@ Bool loriePrepareAccess(PixmapPtr pPix, int index) { + if (!priv->locked && !priv->mem) { + int err = LorieBuffer_lock(priv->buffer, &priv->locked); + if (err) { +- dprintf(2, "Failed to lock buffer, err %d\n", err); ++ const LorieBuffer_Desc *desc = priv->buffer ? LorieBuffer_description(priv->buffer) : NULL; ++ Bool screenPixmap = pScreenPtr->GetScreenPixmap(pScreenPtr) == pPix; ++ dprintf(2, "Failed to lock buffer err=%d pixmap=0x%lx dimensions=%dx%d index=%d screen_pixmap=%d root=%dx%d imported=%d type=%d buffer_id=%llu mem=%p\n", ++ err, (unsigned long) pPix->drawable.id, ++ pPix->drawable.width, pPix->drawable.height, index, screenPixmap, ++ pvfb->root.width, pvfb->root.height, priv->imported, ++ desc ? desc->type : -1, ++ (unsigned long long) (desc ? desc->id : 0), priv->mem); + return FALSE; + } + priv->wasLocked = FALSE; +--- a/lorie/renderer.c ++++ b/lorie/renderer.c +@@ -179,10 +179,16 @@ static GLuint gpuCopyFbo = 0; + // GPU copy batch finishes instead of it waiting for the next vblank-tick poll. + extern volatile int conn_fd; + +-static void notifyGpuCopyDone(void) { ++static void notifyGpuCopyDone(uint64_t serial) { + if (conn_fd != -1) { + lorieEvent e = { .type = EVENT_GPU_COPY_DONE }; +- write(conn_fd, &e, sizeof(e)); ++ ssize_t result = write(conn_fd, &e, sizeof(e)); ++ int error = result == sizeof(e) ? 0 : errno; ++ if (lorieDebugEnabled) ++ log("Present GPU copy notify serial=%llu completedSerial=%llu write=%zd errno=%d (%s)\n", ++ (unsigned long long) serial, ++ (unsigned long long) (state ? state->gpuCopyQueue.completedSerial : 0), ++ result, error, error ? strerror(error) : "ok"); + } + } + +@@ -724,6 +731,12 @@ static uint64_t rendererApplyPendingGpuCopiesLocked(void) { + + while (state->gpuCopyQueue.readIndex != state->gpuCopyQueue.writeIndex) { + LorieGpuCopyEntry entry = state->gpuCopyQueue.entries[state->gpuCopyQueue.readIndex % LORIE_GPU_COPY_QUEUE_CAPACITY]; ++ if (lorieDebugEnabled) ++ log("Present GPU copy drain serial=%llu src_buffer=%llu dst_buffer=%llu rects=%u queue=%u/%u\n", ++ (unsigned long long) entry.serial, ++ (unsigned long long) entry.srcBufferId, ++ (unsigned long long) entry.dstBufferId, entry.numRects, ++ state->gpuCopyQueue.readIndex, state->gpuCopyQueue.writeIndex); + LorieBuffer *src = rendererFindBufferWithRetry(entry.srcBufferId); + LorieBuffer *dst = rendererFindBufferWithRetry(entry.dstBufferId); + +@@ -830,7 +843,10 @@ static void rendererApplyPendingGpuCopies(void) { + // Only now that the GPU has actually finished (not just been told to start) is it safe to + // let present_execute_copy release/idle the source pixmap back to the client. + state->gpuCopyQueue.completedSerial = serial; +- notifyGpuCopyDone(); ++ if (lorieDebugEnabled) ++ log("Present GPU copy completed path=standalone completedSerial=%llu\n", ++ (unsigned long long) serial); ++ notifyGpuCopyDone(serial); + } + lorie_mutex_unlock(&state->lock, &state->lockingPid); + } +@@ -981,7 +997,10 @@ void rendererRedrawLocked(bool* waitingForBuffers) { + eglDestroySyncKHR(egl_display, fence); + if (gpuCopySerial) { + state->gpuCopyQueue.completedSerial = gpuCopySerial; +- notifyGpuCopyDone(); ++ if (lorieDebugEnabled) ++ log("Present GPU copy completed path=redraw completedSerial=%llu\n", ++ (unsigned long long) gpuCopySerial); ++ notifyGpuCopyDone(gpuCopySerial); + } + state->waitForNextFrame = true; + lorie_mutex_unlock(&state->lock, &state->lockingPid); +--- a/xserver/present/present_priv.h ++++ b/xserver/present/present_priv.h +@@ -105,7 +105,9 @@ struct present_vblank { + extern Bool lorieTryScheduleGpuCopy(PixmapPtr pixmap, PixmapPtr dst, RegionPtr update, int16_t x_off, int16_t y_off, + uint64_t *out_serial, void **out_dst_buffer); + extern Bool lorieGpuCopyIsDone(uint64_t serial); +-extern void lorieGpuCopyAck(PixmapPtr pixmap, void *dst_buffer); ++extern uint64_t lorieGpuCopyCompletedSerial(void); ++extern Bool loriePresentDebugEnabled(void); ++extern void lorieGpuCopyAck(PixmapPtr pixmap, void *dst_buffer, uint64_t serial); + extern Bool loriePixmapRequiresGpuCopy(PixmapPtr pixmap); + /* lorie: implemented in lorie/cmdentrypoint.c. Avoids waiting forever on a GPU copy that a dead renderer will never finish. */ + extern bool lorieConnectionAlive(void); +--- a/xserver/present/present_execute.c ++++ b/xserver/present/present_execute.c +@@ -86,12 +86,18 @@ present_execute_copy(present_vblank_ptr vblank, uint64_t crtc_msc) + if (vblank->gpu_copy_pending && !lorieGpuCopyIsDone(vblank->gpu_copy_serial) && + lorieConnectionAlive() && + Success == screen_priv->queue_vblank(screen, window, vblank->crtc, vblank->event_id, crtc_msc + 1)) { ++ if (loriePresentDebugEnabled()) ++ ErrorF("uDroid Present GPU copy wait gpu_serial=%llu completed=%llu event=%llu present_serial=%u pixmap=0x%lx\n", ++ (unsigned long long) vblank->gpu_copy_serial, ++ (unsigned long long) lorieGpuCopyCompletedSerial(), ++ (unsigned long long) vblank->event_id, vblank->serial, ++ vblank->pixmap ? (unsigned long) vblank->pixmap->drawable.id : 0); + vblank->queued = TRUE; + return; + } + + if (vblank->gpu_copy_pending) { +- lorieGpuCopyAck(vblank->pixmap, vblank->gpu_copy_dst_buffer); ++ lorieGpuCopyAck(vblank->pixmap, vblank->gpu_copy_dst_buffer, vblank->gpu_copy_serial); + vblank->gpu_copy_pending = FALSE; + } else if (lorieTryScheduleGpuCopy(vblank->pixmap, gpuCopyDst, vblank->update, gpuCopyXOff, gpuCopyYOff, + &vblank->gpu_copy_serial, &vblank->gpu_copy_dst_buffer)) { +@@ -112,6 +121,12 @@ present_execute_copy(present_vblank_ptr vblank, uint64_t crtc_msc) + vblank->update = NULL; + } + if (Success == screen_priv->queue_vblank(screen, window, vblank->crtc, vblank->event_id, crtc_msc + 1)) { ++ if (loriePresentDebugEnabled()) ++ ErrorF("uDroid Present GPU copy queued gpu_serial=%llu event=%llu present_serial=%u pixmap=0x%lx target_msc=%llu\n", ++ (unsigned long long) vblank->gpu_copy_serial, ++ (unsigned long long) vblank->event_id, vblank->serial, ++ vblank->pixmap ? (unsigned long) vblank->pixmap->drawable.id : 0, ++ (unsigned long long) (crtc_msc + 1)); + vblank->gpu_copy_pending = TRUE; + vblank->queued = TRUE; + return; +@@ -120,7 +135,7 @@ present_execute_copy(present_vblank_ptr vblank, uint64_t crtc_msc) + } + /* Failed to requeue for polling (e.g. OOM) - release our extra ref and treat the + * presumably still in-flight GPU copy as done; same best-effort fallback as above. */ +- lorieGpuCopyAck(vblank->pixmap, vblank->gpu_copy_dst_buffer); ++ lorieGpuCopyAck(vblank->pixmap, vblank->gpu_copy_dst_buffer, vblank->gpu_copy_serial); + } else if (loriePixmapRequiresGpuCopy(vblank->pixmap)) { + /* Imported AHardwareBuffers are not guaranteed to be CPU-mappable. If + * the Android renderer is detached or its queue is temporarily full, +@@ -145,6 +160,11 @@ present_execute_copy(present_vblank_ptr vblank, uint64_t crtc_msc) + + screen_priv->flush(window); + ++ if (vblank->gpu_copy_serial && loriePresentDebugEnabled()) ++ ErrorF("uDroid Present GPU copy pixmap-idle gpu_serial=%llu event=%llu present_serial=%u pixmap=0x%lx\n", ++ (unsigned long long) vblank->gpu_copy_serial, ++ (unsigned long long) vblank->event_id, vblank->serial, ++ vblank->pixmap ? (unsigned long) vblank->pixmap->drawable.id : 0); + present_pixmap_idle(vblank->pixmap, vblank->window, vblank->serial, vblank->idle_fence); + } + +@@ -173,6 +194,12 @@ present_execute_post(present_vblank_ptr vblank, uint64_t ust, uint64_t crtc_msc) + else + mode = PresentCompleteModeCopy; + ++ if (vblank->gpu_copy_serial && loriePresentDebugEnabled()) ++ ErrorF("uDroid Present GPU copy complete gpu_serial=%llu event=%llu present_serial=%u pixmap=0x%lx mode=%u msc=%llu\n", ++ (unsigned long long) vblank->gpu_copy_serial, ++ (unsigned long long) vblank->event_id, vblank->serial, ++ vblank->pixmap ? (unsigned long) vblank->pixmap->drawable.id : 0, ++ mode, (unsigned long long) crtc_msc); + present_vblank_notify(vblank, vblank->kind, mode, ust, crtc_msc); + present_vblank_destroy(vblank); + } +--- a/xserver/present/present_scmd.c ++++ b/xserver/present/present_scmd.c +@@ -259,7 +259,15 @@ lorieRecheckGpuCopies(void) + present_vblank_ptr vblank, tmp; + + xorg_list_for_each_entry_safe(vblank, tmp, &present_exec_queue, event_queue) { +- if (vblank->gpu_copy_pending && lorieGpuCopyIsDone(vblank->gpu_copy_serial)) ++ Bool done = vblank->gpu_copy_pending && lorieGpuCopyIsDone(vblank->gpu_copy_serial); ++ if (vblank->gpu_copy_pending && loriePresentDebugEnabled()) ++ ErrorF("uDroid Present GPU copy recheck gpu_serial=%llu completed=%llu event=%llu present_serial=%u pixmap=0x%lx done=%d\n", ++ (unsigned long long) vblank->gpu_copy_serial, ++ (unsigned long long) lorieGpuCopyCompletedSerial(), ++ (unsigned long long) vblank->event_id, vblank->serial, ++ vblank->pixmap ? (unsigned long) vblank->pixmap->drawable.id : 0, ++ done); ++ if (done) + present_re_execute(vblank); + } + } diff --git a/patches/termux-x11/0015-export-pixmaps-through-dri3.patch b/patches/termux-x11/0015-export-pixmaps-through-dri3.patch new file mode 100644 index 0000000..03c904d --- /dev/null +++ b/patches/termux-x11/0015-export-pixmaps-through-dri3.patch @@ -0,0 +1,209 @@ +diff --git a/InitOutput.c b/InitOutput.c +index 2c7453f..eb1889f 100644 +--- a/InitOutput.c ++++ b/InitOutput.c +@@ -1299,6 +1299,27 @@ static int lorieGetFormats(__unused ScreenPtr screen, CARD32 *num_formats, CARD3 + return TRUE; + } + ++static int lorieFdsFromPixmap(__unused ScreenPtr screen, PixmapPtr pixmap, ++ int *fds, uint32_t *strides, ++ uint32_t *offsets, uint64_t *modifier) { ++ LorieBuffer *buffer = lorieEnsureGpuSampleable(pixmap, LORIEBUFFER_FD); ++ uint32_t size; ++ if (!buffer) ++ return 0; ++ ++ const LorieBuffer_Desc *desc = LorieBuffer_description(buffer); ++ fds[0] = LorieBuffer_exportDmaBuf(buffer, &offsets[0], &size); ++ if (fds[0] < 0) ++ return 0; ++ ++ strides[0] = desc->stride * 4; ++ *modifier = DRM_FORMAT_MOD_LINEAR; ++ if (lorieServerDebugEnabled) ++ log(INFO, "DRI3: exported DMA-BUF pixmap %ux%u stride %u size %u", ++ pixmap->drawable.width, pixmap->drawable.height, strides[0], size); ++ return 1; ++} ++ + static uint64_t lorieSupportedModifiers[] = { + 1255, /* AHardwareBuffer BGRA socket transport */ + 1257, /* AHardwareBuffer RGBA socket transport */ +@@ -1348,7 +1370,7 @@ static int lorieGetDrawableModifiers(__unused DrawablePtr draw, uint32_t format, + + static dri3_screen_info_rec lorieDri3Info = { + .version = 2, +- .fds_from_pixmap = FalseNoop, ++ .fds_from_pixmap = lorieFdsFromPixmap, + .pixmap_from_fds = loriePixmapFromFds, + .get_formats = lorieGetFormats, + .get_modifiers = lorieGetModifiers, +diff --git a/buffer.c b/buffer.c +index f063c79..8ee07db 100644 +--- a/buffer.c ++++ b/buffer.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -142,6 +143,49 @@ static inline size_t alignToPage(size_t size) { + return (size + page_size - 1) & ~(page_size - 1); + } + ++static inline int32_t alignGpuSampledStride(int32_t width) { ++ const int32_t pixelsPerAlignment = 64 / sizeof(uint32_t); ++ return (width + pixelsPerAlignment - 1) & ~(pixelsPerAlignment - 1); ++} ++ ++static int createDmaHeapRegion(size_t size) { ++ static const char *const heapPaths[] = { ++ "/dev/dma_heap/system-uncached", ++ "/dev/dma_heap/system", ++ }; ++ struct dma_heap_allocation_data allocation = { ++ .len = alignToPage(size), ++ .fd_flags = O_RDWR | O_CLOEXEC, ++ }; ++ ++ for (size_t i = 0; i < sizeof(heapPaths) / sizeof(heapPaths[0]); i++) { ++ int heap = open(heapPaths[i], O_RDONLY | O_CLOEXEC); ++ if (heap < 0) ++ continue; ++ ++ int result; ++ do { ++ result = ioctl(heap, DMA_HEAP_IOCTL_ALLOC, &allocation); ++ } while (result < 0 && errno == EINTR); ++ int savedErrno = errno; ++ close(heap); ++ errno = savedErrno; ++ if (result == 0) ++ return (int) allocation.fd; ++ } ++ ++ return -1; ++} ++ ++static int syncDmaBuf(int fd, uint64_t flags) { ++ struct dma_buf_sync sync = { .flags = flags }; ++ int result; ++ do { ++ result = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync); ++ } while (result < 0 && errno == EINTR); ++ return result; ++} ++ + #pragma clang diagnostic push + #pragma ide diagnostic ignored "UnreachableCallsOfFunction" + int LorieBuffer_createRegion(char const* name, size_t size) { +@@ -266,12 +308,16 @@ static LorieBuffer* allocate(int32_t width, int32_t stride, int32_t height, int8 + + __LIBC_HIDDEN__ LorieBuffer* LorieBuffer_allocate(int32_t width, int32_t height, int8_t format, int8_t type) { + int fd = -1; ++ int32_t stride = width; + size_t size = 0; + AHardwareBuffer *ahardwarebuffer = NULL; + + if (type == LORIEBUFFER_FD) { +- size = alignToPage(width * height * sizeof(uint32_t)); +- fd = LorieBuffer_createRegion("LorieBuffer", size); ++ stride = alignGpuSampledStride(width); ++ size = alignToPage(stride * height * sizeof(uint32_t)); ++ fd = createDmaHeapRegion(size); ++ if (fd < 0) ++ fd = LorieBuffer_createRegion("LorieBuffer", size); + if (fd < 0) + return NULL; + } else if (type == LORIEBUFFER_AHARDWAREBUFFER) { +@@ -281,7 +327,7 @@ __LIBC_HIDDEN__ LorieBuffer* LorieBuffer_allocate(int32_t width, int32_t height, + dprintf(2, "FATAL: failed to allocate AHardwareBuffer (width %d height %d format %d): error %d\n", width, height, format, err); + } + +- return allocate(width, width, height, format, type, ahardwarebuffer, fd, size, 0, true); ++ return allocate(width, stride, height, format, type, ahardwarebuffer, fd, size, 0, true); + } + + __LIBC_HIDDEN__ LorieBuffer* LorieBuffer_wrapFileDescriptor(int32_t width, int32_t stride, int32_t height, int8_t format, int fd, off_t offset) { +@@ -299,8 +345,12 @@ __LIBC_HIDDEN__ void LorieBuffer_convert(LorieBuffer* buffer, int8_t type, int8_ + return; + + if (type == LORIEBUFFER_FD) { +- size_t size = alignToPage(buffer->desc.stride * buffer->desc.height * sizeof(uint32_t)); +- int fd = LorieBuffer_createRegion("LorieBuffer", size); ++ const int32_t sourceStride = buffer->desc.stride; ++ const int32_t destinationStride = alignGpuSampledStride(buffer->desc.width); ++ size_t size = alignToPage(destinationStride * buffer->desc.height * sizeof(uint32_t)); ++ int fd = createDmaHeapRegion(size); ++ if (fd < 0) ++ fd = LorieBuffer_createRegion("LorieBuffer", size); + if (fd < 0) + return; + +@@ -311,10 +361,18 @@ __LIBC_HIDDEN__ void LorieBuffer_convert(LorieBuffer* buffer, int8_t type, int8_ + return; + } + +- pixman_blt(buffer->desc.data, data, buffer->desc.stride, buffer->desc.stride, 32, 32, 0, 0, 0, 0, buffer->desc.width, buffer->desc.height); ++ bool isDmaBuf = syncDmaBuf(fd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE) == 0; ++ pixman_blt(buffer->desc.data, data, sourceStride, destinationStride, 32, 32, ++ 0, 0, 0, 0, buffer->desc.width, buffer->desc.height); ++ if (isDmaBuf && syncDmaBuf(fd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE) < 0) { ++ munmap(data, size); ++ close(fd); ++ return; ++ } + + buffer->desc.type = type; + buffer->desc.format = format; ++ buffer->desc.stride = destinationStride; + buffer->fd = fd; + buffer->size = size; + buffer->offset = 0; +@@ -383,6 +441,27 @@ __LIBC_HIDDEN__ const LorieBuffer_Desc* LorieBuffer_description(LorieBuffer* buf + return buffer ? &buffer->desc : &none; + } + ++__LIBC_HIDDEN__ int LorieBuffer_exportDmaBuf(LorieBuffer* buffer, ++ uint32_t* offset, ++ uint32_t* size) { ++ if (!buffer || !offset || !size || buffer->desc.type != LORIEBUFFER_FD || ++ buffer->fd < 0 || buffer->offset < 0 || buffer->offset > UINT32_MAX || ++ buffer->size > UINT32_MAX) ++ return -1; ++ ++ if (syncDmaBuf(buffer->fd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ) < 0) ++ return -1; ++ if (syncDmaBuf(buffer->fd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ) < 0) ++ return -1; ++ ++ int fd = fcntl(buffer->fd, F_DUPFD_CLOEXEC, 0); ++ if (fd >= 0) { ++ *offset = (uint32_t) buffer->offset; ++ *size = (uint32_t) buffer->size; ++ } ++ return fd; ++} ++ + __LIBC_HIDDEN__ int LorieBuffer_lock(LorieBuffer* buffer, void** out) { + int ret = 0; + if (!buffer) +diff --git a/buffer.h b/buffer.h +index 751004b..98ac68b 100644 +--- a/buffer.h ++++ b/buffer.h +@@ -112,6 +112,10 @@ STATIC_INLINE void LorieBuffer_release(LorieBuffer* _Nullable buffer) { + */ + const LorieBuffer_Desc* _Nonnull LorieBuffer_description(LorieBuffer* _Nullable buffer); + ++int LorieBuffer_exportDmaBuf(LorieBuffer* _Nullable buffer, ++ uint32_t* _Nonnull offset, ++ uint32_t* _Nonnull size); ++ + /** + * Lock the AHardwareBuffer for direct CPU access. + * See AHardwareBuffer_lock() description for details diff --git a/patches/termux-x11/0016-accept-fd-gpu-copy-sources.patch b/patches/termux-x11/0016-accept-fd-gpu-copy-sources.patch new file mode 100644 index 0000000..6a05981 --- /dev/null +++ b/patches/termux-x11/0016-accept-fd-gpu-copy-sources.patch @@ -0,0 +1,37 @@ +Accept existing DMA-BUF and AHardwareBuffer pixmaps as GPU-copy sources. + +The renderer already handles both source types. Requiring AHardwareBuffer here +rejects Venus/Zink's CPU-mappable DMA-BUF pixmaps and forces Present through the +CPU fallback. + +--- a/InitOutput.c ++++ b/InitOutput.c +@@ -135,6 +135,19 @@ static LorieBuffer *lorieEnsureGpuSampleable(PixmapPtr pixmap, int8_t type) { + return desc->type == type ? priv->buffer : NULL; + } + ++static LorieBuffer *lorieGpuCopySource(PixmapPtr pixmap) { ++ LoriePixmapPriv *priv = LORIE_PIXMAP_PRIV_FROM_PIXMAP(pixmap); ++ const LorieBuffer_Desc *desc; ++ if (!priv || !priv->buffer || priv->mem) ++ return NULL; ++ ++ desc = LorieBuffer_description(priv->buffer); ++ if (desc->type == LORIEBUFFER_FD || desc->type == LORIEBUFFER_AHARDWAREBUFFER) ++ return priv->buffer; ++ ++ return lorieEnsureGpuSampleable(pixmap, LORIEBUFFER_AHARDWAREBUFFER); ++} ++ + static Bool lorieServerDebugEnabled = FALSE; + + void OsVendorInit(void) { +@@ -867,7 +880,7 @@ Bool lorieTryScheduleGpuCopy(PixmapPtr pixmap, PixmapPtr dst, RegionPtr update, + return FALSE; + } + +- if (!(srcBuffer = lorieEnsureGpuSampleable(pixmap, LORIEBUFFER_AHARDWAREBUFFER))) { ++ if (!(srcBuffer = lorieGpuCopySource(pixmap))) { + static uint8_t logged = 0; + LoriePixmapPriv *failed = LORIE_PIXMAP_PRIV_FROM_PIXMAP(pixmap); + const LorieBuffer_Desc *failedDesc = failed && failed->buffer ? LorieBuffer_description(failed->buffer) : NULL; diff --git a/tools/prepare-termux-x11-source.sh b/tools/prepare-termux-x11-source.sh index e7777bb..f48fd67 100755 --- a/tools/prepare-termux-x11-source.sh +++ b/tools/prepare-termux-x11-source.sh @@ -107,8 +107,40 @@ if [[ "${UDROID_EXPERIMENTAL_GFXSTREAM_X11:-0}" == "1" ]]; then "$cpp_root" \ "$repo_root/patches/termux-x11/0009-defer-gpu-only-present-while-detached.patch" \ 'Bool loriePixmapRequiresGpuCopy(PixmapPtr pixmap) {' + apply_once \ + "$cpp_root/lorie" \ + "$repo_root/patches/termux-x11/0012-lorie-raw-fd-import-diagnostics.patch" \ + 'raw-fd import stage=mmap' + apply_once \ + "$cpp_root" \ + "$repo_root/patches/termux-x11/0014-lorie-present-lifecycle-diagnostics.patch" \ + 'Present GPU copy schedule serial=' + apply_once \ + "$cpp_root/lorie" \ + "$repo_root/patches/termux-x11/0015-export-pixmaps-through-dri3.patch" \ + 'DRI3: exported DMA-BUF pixmap' + apply_once \ + "$cpp_root/lorie" \ + "$repo_root/patches/termux-x11/0016-accept-fd-gpu-copy-sources.patch" \ + 'lorieGpuCopySource' else source_profile="standard" + revert_if_applied \ + "$cpp_root/lorie" \ + "$repo_root/patches/termux-x11/0016-accept-fd-gpu-copy-sources.patch" \ + 'lorieGpuCopySource' + revert_if_applied \ + "$cpp_root/lorie" \ + "$repo_root/patches/termux-x11/0015-export-pixmaps-through-dri3.patch" \ + 'DRI3: exported DMA-BUF pixmap' + revert_if_applied \ + "$cpp_root" \ + "$repo_root/patches/termux-x11/0014-lorie-present-lifecycle-diagnostics.patch" \ + 'Present GPU copy schedule serial=' + revert_if_applied \ + "$cpp_root/lorie" \ + "$repo_root/patches/termux-x11/0012-lorie-raw-fd-import-diagnostics.patch" \ + 'raw-fd import stage=mmap' revert_if_applied \ "$cpp_root" \ "$repo_root/patches/termux-x11/0009-defer-gpu-only-present-while-detached.patch" \ @@ -135,4 +167,9 @@ else "uDroid standard profile: use Xorg's copy path" fi +apply_once \ + "$cpp_root/lorie" \ + "$repo_root/patches/termux-x11/0011-lorie-perfetto-slices.patch" \ + 'uDroid/Lorie draw' + echo "Termux:X11 native source is ready ($source_profile profile)." diff --git a/tools/profiling/venus-perfetto.pbtxt b/tools/profiling/venus-perfetto.pbtxt new file mode 100644 index 0000000..eb57dbf --- /dev/null +++ b/tools/profiling/venus-perfetto.pbtxt @@ -0,0 +1,75 @@ +duration_ms: 15000 + +buffers { + size_kb: 65536 + fill_policy: RING_BUFFER +} + +data_sources { + config { + name: "linux.ftrace" + ftrace_config { + ftrace_events: "sched/sched_switch" + ftrace_events: "sched/sched_waking" + ftrace_events: "sched/sched_wakeup_new" + ftrace_events: "power/cpu_frequency" + ftrace_events: "power/gpu_frequency" + ftrace_events: "power/gpu_work_period" + ftrace_events: "gpu_mem/gpu_mem_total" + ftrace_events: "mali/mali_JM_SUBMIT" + ftrace_events: "mali/mali_JM_JOB_DONE" + ftrace_events: "mali/mali_job_slots_event" + atrace_categories: "gfx" + atrace_categories: "input" + atrace_categories: "sched" + atrace_categories: "view" + atrace_apps: "org.randomcoder.udroid" + atrace_apps: "org.randomcoder.udroid.dev" + compact_sched { + enabled: true + } + } + } +} + +data_sources { + config { + name: "android.surfaceflinger.frame" + } +} + +data_sources { + config { + name: "android.surfaceflinger.frametimeline" + } +} + +data_sources { + config { + name: "android.surfaceflinger.layers" + surfaceflinger_layers_config { + mode: MODE_ACTIVE + trace_flags: TRACE_FLAG_COMPOSITION + trace_flags: TRACE_FLAG_BUFFERS + } + } +} + +data_sources { + config { + name: "android.gpu.memory" + } +} + +data_sources { + config { + name: "linux.process_stats" + process_stats_config { + scan_all_processes_on_start: true + proc_stats_poll_ms: 1000 + } + } +} + +write_into_file: true +file_write_period_ms: 1000