Skip to content

NgxNative.kt

src/jvmMain/kotlin/afield/media/reconstruction/NgxNative.kt · 77 lines

@file:afield.runtime.completion.CompletionCategory("Internal")
package afield.media.reconstruction

import java.lang.foreign.*
import java.lang.foreign.ValueLayout.*
import java.lang.invoke.MethodHandle
import java.util.concurrent.Executors
import java.util.concurrent.ExecutionException
import java.nio.file.Path
import kotlin.io.path.*

internal class NgxNative private constructor(path: Path, sdk: Path)

One optional FFM library, following Manifold/Ceres. No JNI and no SDK parameter maps in Kotlin.

internal class NgxNative private constructor(path: Path, sdk: Path) {
    private val lookup=SymbolLookup.libraryLookup(path,Arena.global())

internal class Call(private val handle: MethodHandle)

The installed NGX discovery code reserves >1 MiB on its native stack. Calling it from Field's small render-thread stack overflows before the JVM can report an exception. One serialized platform thread gives all SDK calls a measured, explicit 8 MiB stack. The render thread waits for recording to finish; GPU work remains on Field's queue.

fun invokeWithArguments(vararg args: Any?): Any?

        fun invokeWithArguments(vararg args: Any?): Any? = try {
            worker.submit<Any?> { handle.invokeWithArguments(*args) }.get()
        } catch(e: ExecutionException) { throw e.cause ?: e }
    }
    private fun bind(name: String, result: MemoryLayout?, vararg args: MemoryLayout): Call = Call(
        Linker.nativeLinker().downcallHandle(lookup.find("f4ngx_$name").orElseThrow(),
            if(result==null)FunctionDescriptor.ofVoid(*args) else FunctionDescriptor.of(result,*args)))

val extensions=bind("extensions",JAVA_INT,JAVA_LONG,JAVA_LONG,JAVA_INT,ADDRESS,JAVA_INT)

val open=bind("open",JAVA_INT,JAVA_LONG,JAVA_LONG,JAVA_LONG,ADDRESS)

val optimal=bind("optimal",JAVA_INT,ADDRESS,JAVA_INT,JAVA_INT,JAVA_INT,ADDRESS)

val create

    val create=bind("create",JAVA_INT,ADDRESS,JAVA_LONG,JAVA_INT,JAVA_INT,JAVA_INT,JAVA_INT,JAVA_INT,ADDRESS)

val evaluate=bind("evaluate",JAVA_INT,ADDRESS,JAVA_LONG,ADDRESS,ADDRESS,JAVA_INT)

val rrAvailable=bind("rr_available",JAVA_INT,ADDRESS)

val rrOptimal=bind("rr_optimal",JAVA_INT,ADDRESS,JAVA_INT,JAVA_INT,JAVA_INT,ADDRESS)

val rrCreate

    val rrCreate=bind("rr_create",JAVA_INT,ADDRESS,JAVA_LONG,JAVA_INT,JAVA_INT,JAVA_INT,JAVA_INT,JAVA_INT,ADDRESS)

val rrEvaluate=bind("rr_evaluate",JAVA_INT,ADDRESS,JAVA_LONG,ADDRESS,ADDRESS,ADDRESS,JAVA_INT)

val memory=bind("memory",JAVA_LONG,ADDRESS)

val destroy=bind("destroy",null,ADDRESS)

val close=bind("close",null,ADDRESS)

    private val error=bind("error",ADDRESS)

init

    init {
        check(bind("abi",JAVA_INT).invokeWithArguments()==5 && bind("image_size",JAVA_INT).invokeWithArguments()==32) {
            "Incompatible Field NGX ABI; rebuild with bash tools/lighting/build_ngx.sh"
        }
        Arena.ofShared().use { arena ->
            fun wide(text: String): MemorySegment {
                val points=text.codePoints().toArray();val data=arena.allocate((points.size+1)*4L,4)
                points.forEachIndexed { i,c -> data.set(JAVA_INT,i*4L,c) };return data
            }
            val cache=Path.of(System.getProperty("user.home"),".cache","field4","ngx").also { it.createDirectories() }
            bind("configure",null,ADDRESS,ADDRESS).invokeWithArguments(wide(sdk.resolve("lib/Linux_x86_64/rel").toString()),wide(cache.toString()))
        }
    }

fun check(code: Any?)

    fun check(code: Any?) {
        check(code==0) { (error.invokeWithArguments() as MemorySegment).reinterpret(4096).getString(0) }
    }

fun required(instance: Long=0, physical: Long=0,rr: Boolean=false): Set<String>

    fun required(instance: Long=0, physical: Long=0,rr: Boolean=false): Set<String> = Arena.ofShared().use { arena ->
        val names=arena.allocate(4096);check(extensions.invokeWithArguments(instance,physical,if(rr)1 else 0,names,4096))
        names.getString(0).lineSequence().filter { it.isNotBlank() }.toSet()
    }

companion object

    companion object {
        private val worker=Executors.newSingleThreadExecutor { work ->
            Thread(null,work,"field-ngx",8L*1024*1024).apply { isDaemon=true }
        }

val loaded: Result<NgxNative> by lazy

        val loaded: Result<NgxNative> by lazy {
            runCatching {
                check(System.getProperty("os.name").lowercase().contains("linux")) { "Field NGX native adapter is currently built for Linux x86_64 only" }
                check(System.getenv("FIELD_NGX_DISABLE")!="1" && !java.lang.Boolean.getBoolean("afield.ngx.disabled")) { "NGX explicitly disabled" }
                val path=Path.of(System.getenv("FIELD_NGX_LIBRARY") ?: "build/native/field-ngx/libfield_ngx.so").toAbsolutePath()
                check(path.isRegularFile()) { "Optional NGX adapter absent; build with bash tools/lighting/build_ngx.sh" }
                val sdk=Path.of(System.getenv("FIELD_DLSS_ROOT") ?: "build/lighting-experiment/upstream/DLSS").toAbsolutePath()
                check(sdk.resolve("lib/Linux_x86_64/rel").isDirectory()) { "FIELD_DLSS_ROOT must name the pinned NVIDIA/DLSS checkout" }
                NgxNative(path,sdk)
            }
        }
    }
}