[orx-compute-graph, orx-compute-graph-nodes] Remove compute graph from orx

This commit is contained in:
Edwin Jakobs
2025-01-20 11:43:36 +01:00
parent b62746c94e
commit 38b7f52d46
10 changed files with 0 additions and 482 deletions

View File

@@ -1,22 +0,0 @@
# orx-compute-graph-nodes
A collection of nodes that can be used with `orx-compute-graph`.
## List of nodes
### Multi-platform
Name | Description | Inputs | Outputs
----------------|-----------------------|--------|---------
`filterNode` | Wrap around a `Filter`| | `image`
`fitImageNode` | Fit image to window bounds | `image` | `image`
### JVM only
Name | Description | Inputs | Outputs
----------------|-------------------|--------|---------
`drawCacheNode` | Cache drawing in an internal color buffer, commonly used as the final stage node | | `image`
`dropImageNode` | Listen for window file drop events | | `image`

View File

@@ -1,21 +0,0 @@
plugins {
org.openrndr.extra.convention.`kotlin-multiplatform`
}
kotlin {
sourceSets {
@Suppress("UNUSED_VARIABLE")
val commonMain by getting {
dependencies {
implementation(project(":orx-parameters"))
implementation(project(":orx-shader-phrases"))
implementation(project(":orx-compute-graph"))
implementation(project(":orx-image-fit"))
implementation(libs.openrndr.application)
implementation(libs.openrndr.draw)
implementation(libs.openrndr.filter)
implementation(libs.kotlin.reflect)
}
}
}
}

View File

@@ -1,27 +0,0 @@
package org.openrndr.extra.computegraph.nodes
import org.openrndr.draw.ColorBuffer
import org.openrndr.draw.Filter
import org.openrndr.draw.createEquivalent
import org.openrndr.extra.computegraph.ComputeGraph
import org.openrndr.extra.computegraph.ComputeNode
import org.openrndr.extra.computegraph.withKey
fun <T : Filter> ComputeGraph.filterNode(
filter: T, input: ComputeNode, inputKey: String = "image", outputKey: String = "image",
config: ComputeNode.(f: Filter) -> Unit
): ComputeNode {
return node {
name = "filter-${filter::class.simpleName}"
inputs = filter.parameters
config(filter)
val inputImage by input.outputs.withKey<ColorBuffer>(inputKey)
var outputImage by outputs.withKey<ColorBuffer>(outputKey)
outputImage = inputImage.createEquivalent()
compute {
filter.apply(inputImage, outputImage)
}
dependOn(input)
}
}

View File

@@ -1,29 +0,0 @@
package org.openrndr.extra.computegraph.nodes
import org.openrndr.Program
import org.openrndr.draw.ColorBuffer
import org.openrndr.draw.isolatedWithTarget
import org.openrndr.draw.renderTarget
import org.openrndr.extra.computegraph.ComputeGraph
import org.openrndr.extra.computegraph.ComputeNode
import org.openrndr.extra.computegraph.withKey
import org.openrndr.extra.imageFit.imageFit
fun ComputeGraph.fitImageNode(program: Program, input: ComputeNode) : ComputeNode {
return node {
name = "fit-image"
val rt = renderTarget(program.width, program.height) {
colorBuffer()
}
val inputImage: ColorBuffer by input.outputs.withKey("image")
var outputImage:ColorBuffer by outputs.withKey("image")
outputImage = rt.colorBuffer(0)
compute {
program.drawer.isolatedWithTarget(rt) {
ortho(rt)
imageFit(inputImage, bounds)
}
}
dependOn(input)
}
}

View File

@@ -1,100 +0,0 @@
package org.openrndr.extra.computegraph.nodes
import io.github.oshai.kotlinlogging.KotlinLogging
import org.openrndr.KEY_SPACEBAR
import org.openrndr.Program
import org.openrndr.RequestAssetsEvent
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.extra.computegraph.ComputeGraph
import org.openrndr.extra.computegraph.ComputeNode
import org.openrndr.extra.computegraph.withKey
import java.io.File
val logger = KotlinLogging.logger { }
@JvmRecord
private data class RenderTargetDescription(val width: Int, val height: Int, val contentScale: Double)
private fun RenderTarget.description() = RenderTargetDescription(width, height, contentScale)
fun ComputeGraph.drawCacheNode(
program: Program,
inputNodes: List<ComputeNode>,
draw: Program.(node: ComputeNode) -> Unit
): ComputeNode {
return node {
var producingAssets: Boolean by inputs
producingAssets = false
program.keyboard.keyDown.listen {
if (!it.propagationCancelled) {
logger.info { "requesting assets" }
if (it.key == KEY_SPACEBAR) {
program.requestAssets.trigger(RequestAssetsEvent(this, program))
}
}
}
var screenshotTarget = ""
program.produceAssets.listen {
producingAssets = true
screenshotTarget = "screenshots/${it.assetMetadata.assetBaseName}.png"
}
name = "draw-cache"
var rt = renderTarget(program.width, program.height) {
colorBuffer()
depthBuffer()
}
var outputImage: ColorBuffer by outputs.withKey("image")
outputImage = rt.colorBuffer(0)
var description: RenderTargetDescription by inputs
description = RenderTarget.active.description()
update {
description = RenderTarget.active.description()
}
val defaultContentScale = program.window.contentScale
compute {
rt.colorBuffer(0).destroy()
rt.depthBuffer?.destroy()
rt.detachColorAttachments()
rt.detachDepthBuffer()
rt.destroy()
rt = renderTarget(
program.width,
program.height,
contentScale = if (producingAssets) 6.0 else defaultContentScale
) {
colorBuffer()
depthBuffer()
}
program.drawer.isolatedWithTarget(rt) {
clear(ColorRGBa.WHITE)
draw(program, this@node)
}
outputImage = rt.colorBuffer(0)
println(outputImage)
if (producingAssets) {
logger.info { "saving draw cache to file" }
val directory = File("screenshots")
if (!directory.exists()) {
directory.mkdirs()
}
outputImage.saveToFile(File(screenshotTarget), async = false)
producingAssets = false
screenshotTarget = ""
}
}
for (input in inputNodes) {
dependOn(input)
}
}
}

View File

@@ -1,28 +0,0 @@
package org.openrndr.extra.computegraph.nodes
import org.openrndr.Program
import org.openrndr.draw.ColorBuffer
import org.openrndr.draw.colorBuffer
import org.openrndr.draw.loadImage
import org.openrndr.extra.computegraph.ComputeGraph
import org.openrndr.extra.computegraph.ComputeNode
import java.io.File
fun ComputeGraph.dropImageNode(program: Program): ComputeNode {
return node {
name = "drop-image"
var file: File by inputs
file = File("data/images/cheeta.jpg")
program.window.drop.listen {
file = File(it.files.first())
}
var image: ColorBuffer by outputs
fun loadFileOrEmpty() = if (file.exists()) loadImage(file) else colorBuffer(256, 256)
image = loadFileOrEmpty()
compute {
image.destroy()
image = loadFileOrEmpty()
}
}
}

View File

@@ -1,55 +0,0 @@
# orx-compute-graph
A graph for computation.
## Status
In development. Things may change without prior notice.
## Usage
```kotlin
fun main() = application {
program {
val linkNode: ComputeNode
val cg = computeGraph {
val randomNode = node {
var seed: Int by inputs
seed = 0
var points: List<Vector2> by outputs
points = emptyList()
compute {
val r = Random(seed)
points = (0 until 1000).map {
val x = r.nextDouble(0.0, width.toDouble())
val y = r.nextDouble(0.0, height.toDouble())
Vector2(x, y)
}
}
}
linkNode = node {
var seed: Int by inputs
seed = 0
val points: List<Vector2> by randomNode.outputs
var links: List<LineSegment> by outputs
compute {
val r = Random(seed)
val shuffled = points.shuffled(r)
links = shuffled.windowed(2, 2).map {
LineSegment(it[0], it[1])
}
}
}
randomNode.dependOn(root)
linkNode.dependOn(randomNode)
}
cg.dispatch(dispatcher) {}
extend {
drawer.clear(ColorRGBa.WHITE)
drawer.stroke = ColorRGBa.BLACK
val links: List<LineSegment> by linkNode.outputs
drawer.lineSegments(links)
}
}
}
```

View File

@@ -1,20 +0,0 @@
plugins {
org.openrndr.extra.convention.`kotlin-multiplatform`
}
kotlin {
sourceSets {
all {
languageSettings {
optIn("kotlin.RequiresOptIn")
}
}
@Suppress("UNUSED_VARIABLE")
val commonMain by getting {
dependencies {
api(libs.openrndr.event)
implementation(libs.kotlin.coroutines)
}
}
}
}

View File

@@ -1,178 +0,0 @@
package org.openrndr.extra.computegraph
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.*
import org.openrndr.events.Event
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.jvm.JvmRecord
import kotlin.reflect.KProperty
private val logger = KotlinLogging.logger { }
@JvmRecord
data class ComputeEvent(val source: ComputeNode)
open class ComputeNode(val graph: ComputeGraph, var computeFunction: suspend () -> Unit = {}) {
internal var updateFunction = {}
var inputs = mutableMapOf<String, Any>()
val outputs = mutableMapOf<String, Any>()
var name = "unnamed-node-${this.hashCode()}"
private var lastInputsHash = inputs.hashCode()
var receivedComputeRequest = true
private val computeFinished = Event<Unit>("compute-finished")
fun needsRecompute(): Boolean {
return receivedComputeRequest || (inputs.hashCode() != lastInputsHash)
}
fun dependOn(node: ComputeNode) {
graph.nodes.add(this)
graph.inbound.getOrPut(this) { mutableSetOf() }.add(node)
graph.outbound.getOrPut(node) { mutableSetOf() }.add(node)
node.computeFinished.listen {
receivedComputeRequest = true
graph.requireCompute.add(this)
computeFinished.trigger(Unit)
}
}
/**
* Set an update function, this function is called unconditionally by the compute-graph processor. This update
* function can be used to change values of [inputs] to trigger compute of the node.
*/
fun update(updateFunction: () -> Unit) {
this.updateFunction = updateFunction
}
fun compute(computeFunction: suspend () -> Unit) {
this.computeFunction = computeFunction
}
suspend fun execute() {
receivedComputeRequest = false
lastInputsHash = inputs.hashCode()
computeFunction()
computeFinished.trigger(Unit)
}
override fun toString(): String {
return "ComputeNode(name='$name', receivedComputeRequest=$receivedComputeRequest)"
}
}
class ComputeGraph {
val root = ComputeNode(this, {})
internal val requireCompute = ArrayDeque<ComputeNode>()
val nodes = mutableListOf<ComputeNode>()
val inbound = mutableMapOf<ComputeNode, MutableSet<ComputeNode>>()
val outbound = mutableMapOf<ComputeNode, MutableSet<ComputeNode>>()
var job: Job? = null
fun node(builder: ComputeNode.() -> Unit): ComputeNode {
val cn = ComputeNode(this)
cn.builder()
return cn
}
private var computeHash = -1
/**
* Run the compute graph in [context].
*
* Eventually we likely want to separate compute-graph definitions from the compute-graph processor.
*/
@OptIn(DelicateCoroutinesApi::class)
fun dispatch(context: CoroutineDispatcher, delayBeforeCompute: Long = 500) {
var firstRodeo = true
GlobalScope.launch(context, CoroutineStart.DEFAULT) {
while (true) {
for (node in nodes) {
node.updateFunction()
}
val testHash = nodes.map { it.inputs.hashCode() }.reduce { acc, computeNode -> acc * 31 + computeNode }
if (testHash != computeHash) {
logger.info { "canceling job $job" }
job?.cancel()
job = null
}
if (testHash != computeHash && job == null) {
computeHash = testHash
job = GlobalScope.launch(context) {
if (!firstRodeo) {
delay(delayBeforeCompute)
}
logger.info { "compute started" }
compute()
logger.info { "compute finished" }
firstRodeo = false
}
}
yield()
}
}
}
suspend fun compute() {
for (node in nodes) {
if (node.needsRecompute()) {
if (node !in requireCompute) {
logger.info { "node '${node.name}' needs computation" }
requireCompute.add(node)
}
}
}
val processed = mutableListOf<ComputeNode>()
root.receivedComputeRequest = false
while (requireCompute.isNotEmpty()) {
val node = requireCompute.first {
val deps = (inbound[it] ?: emptyList())
if (deps.isEmpty()) {
true
} else {
deps.none { dep -> dep in requireCompute }
}
}
requireCompute.remove(node)
if (node !in processed) {
logger.info { "computing ${node.name}" }
node.execute()
processed.add(node)
}
}
}
}
@OptIn(ExperimentalContracts::class)
fun computeGraph(builder: ComputeGraph.() -> Unit): ComputeGraph {
contract {
callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
}
val cg = ComputeGraph()
cg.builder()
return cg
}
class MutableMapKeyReference<T : Any>(private val map: MutableMap<String, Any>, private val key: String) {
operator fun getValue(any: Any?, property: KProperty<*>): T {
@Suppress("UNCHECKED_CAST")
return map[key] as T
}
operator fun setValue(any: Any?, property: KProperty<*>, value: Any) {
@Suppress("UNCHECKED_CAST")
map[key] = value as T
}
}
/**
* Create a map delegation by [key]
*/
fun <T : Any> MutableMap<String, Any>.withKey(key: String): MutableMapKeyReference<T> {
return MutableMapKeyReference(this, key)
}

View File

@@ -22,8 +22,6 @@ include(
"orx-color",
"orx-composition",
"orx-compositor",
"orx-compute-graph",
"orx-compute-graph-nodes",
"orx-delegate-magic",
"orx-jvm:orx-dnk3",
"orx-easing",