Skip to content

LinuxElectronImportProbe.kt

src/jvmMain/kotlin/afield/media/stream/LinuxElectronImportProbe.kt · 104 lines

package afield.media.stream

import afield.graphics.vulkan.Renderer
import afield.graphics.vulkan.buffers.VulkanBuffer
import org.lwjgl.vulkan.VK10.*
import org.lwjgl.vulkan.VkBufferImageCopy
import org.lwjgl.vulkan.VkImageMemoryBarrier
import org.lwjgl.vulkan.VK11.VK_QUEUE_FAMILY_EXTERNAL
import afield.graphics.vulkan.device.CommandBuffer
import afield.graphics.vulkan.device.Fence
import afield.graphics.vulkan.targets.RenderToTexture
import afield.graphics.texture.LinuxDmaBufExport
import afield.graphics.texture.UnixFdPass
import afield.graphics.vulkan.util.Options
import afield.graphics.vulkan.util.Window
import afield.math.linalg.vec
import org.lwjgl.system.MemoryStack
import java.io.File

A standalone Linux probe that hands Electron one finished Vulkan image and reads back its verdict on the pixels.

It clears a 64×64 dma-buf-exportable RenderToTexture to a known colour, copies that back through a staging buffer to confirm the Field side is not blank, releases the image to VK_QUEUE_FAMILY_EXTERNAL behind a waited fence, and sends the descriptor over UnixFdPass with the plane's stride and offset and the allocation's DRM modifier written beside it as texture.json. Then it waits up to a minute for the Node side's result.json. Start tools/electron_dmabuf_probe/main.js first; the single argument is the directory the two sides exchange evidence in, and native/field-fdpass has to be built.

object LinuxElectronImportProbe

One immutable, fence-completed Vulkan image for the Electron nativePixmap import.

@JvmStatic fun main(args: Array<String>)

body — 69 lines, from line 35
    @JvmStatic fun main(args: Array<String>) {
        val directory = File(args.single()).apply { mkdirs() }
        Options.enableHDR = false
        val window = Window("Field Vulkan → Electron import probe")
        val renderer = Renderer(window)
        val rtt = RenderToTexture(renderer) {
            width = 64; height = 64; useDepth = false
            exportDmaBuf = true
            clearColor = vec(1.0, 0.25, 0.125, 1.0)
        }
        try {
            println("PROBE DEVICE ${renderer.device.physicalDevice.driverName}")
            check(UnixFdPass.available) { "Build native/field-fdpass first" }
            val command = CommandBuffer(renderer.commandPool, primary = true, oneTimeSubmit = true)
            val fence = Fence(renderer.device, signaled = false)
            val staging = VulkanBuffer(renderer, 64L * 64 * 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
                VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT or VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
            try {
                command.beginRecording()
                rtt.render(command, 0)
                rtt.recordColorTransferSource(command) { image ->
                    MemoryStack.stackPush().use { stack ->
                        val region = VkBufferImageCopy.calloc(1, stack)
                        region.imageSubresource().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT).layerCount(1)
                        region.imageExtent().set(64, 64, 1)
                        vkCmdCopyImageToBuffer(command.vkCommandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                            staging.buffer, region)
                    }
                }
                // Release the immutable image to the external consumer in GENERAL layout.
                // Fence completion provides execution ordering; queue-family release provides ownership.
                MemoryStack.stackPush().use { stack ->
                    val barrier = VkImageMemoryBarrier.calloc(1, stack)
                        .sType(VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER)
                        .srcAccessMask(VK_ACCESS_MEMORY_READ_BIT or VK_ACCESS_MEMORY_WRITE_BIT).dstAccessMask(0)
                        .oldLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL).newLayout(VK_IMAGE_LAYOUT_GENERAL)
                        .srcQueueFamilyIndex(renderer.graphQueue.queueFamilyIndex).dstQueueFamilyIndex(VK_QUEUE_FAMILY_EXTERNAL)
                        .image(rtt.colorImageHandles.single())
                    barrier.subresourceRange().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT).levelCount(1).layerCount(1)
                    vkCmdPipelineBarrier(command.vkCommandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
                        VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, null, null, barrier)
                }
                command.endRecording()
                MemoryStack.stackPush().use {
                    renderer.graphQueue.submit(it.pointers(command.vkCommandBuffer), null, null, null, fence)
                }
                fence.fenceWait()
                val bytes = ByteArray(64 * 64 * 4)
                staging.mapAndRead_bytes { get(bytes) }
                directory.resolve("vulkan.bgra").writeBytes(bytes)
                val nonBlack = (bytes.indices step 4).count { bytes[it] != 0.toByte() || bytes[it+1] != 0.toByte() || bytes[it+2] != 0.toByte() }
                check(nonBlack == 4096) { "Vulkan source was blank: $nonBlack pixels" }
                println("PROBE SOURCE $nonBlack/4096 RGB pixels, BGRA ${bytes.take(4).map { it.toInt() and 255 }}")
                val handle = LinuxDmaBufExport.handles(rtt).single()
                try {
                    val plane = handle.planes.single()
                    directory.resolve("texture.json").writeText(
                        """{"width":64,"height":64,"modifier":"${java.lang.Long.toUnsignedString(handle.modifier)}","stride":${plane.rowPitch},"offset":${plane.offset},"size":${handle.size},"fenceCompleted":true,"externalOwnership":true}"""
                    )
                    check(UnixFdPass.sendFds(directory.resolve("fds.sock").path, intArrayOf(handle.fd)))
                } finally { UnixFdPass.closeFd(handle.fd) }
                val deadline = System.nanoTime() + 60_000_000_000L
                while (!directory.resolve("result.json").exists() && System.nanoTime() < deadline) Thread.sleep(50)
                check(directory.resolve("result.json").exists()) { "Electron produced no pixel verdict" }
                println("PROBE RESULT ${directory.resolve("result.json").readText()}")
            } finally { staging.cleanup(); fence.cleanup(); command.cleanup() }
        } finally { renderer.cleanup(); window.cleanup() }
    }
}