[orx-compositor] Remove use() add aside {}

This commit is contained in:
Edwin Jakobs
2022-12-28 14:02:40 +01:00
parent 750b5ef67e
commit 391ebce42b
6 changed files with 218 additions and 206 deletions

View File

@@ -43,6 +43,7 @@ kotlin {
val jvmDemo by getting { val jvmDemo by getting {
dependencies { dependencies {
implementation(project(":orx-fx")) implementation(project(":orx-fx"))
implementation(project(":orx-compositor"))
} }
} }
} }

View File

@@ -10,7 +10,6 @@ import org.openrndr.extra.parameters.BooleanParameter
import org.openrndr.extra.parameters.Description import org.openrndr.extra.parameters.Description
import org.openrndr.math.Matrix44 import org.openrndr.math.Matrix44
private val postBufferCache = mutableListOf<ColorBuffer>()
fun RenderTarget.deepDestroy() { fun RenderTarget.deepDestroy() {
val cbcopy = colorAttachments.map { it } val cbcopy = colorAttachments.map { it }
@@ -30,19 +29,26 @@ fun RenderTarget.deepDestroy() {
destroy() destroy()
} }
enum class LayerType {
LAYER,
ASIDE
}
/** /**
* A single layer representation * A single layer representation
*/ */
@Description("Layer") @Description("Layer")
open class Layer internal constructor(val bufferMultisample: BufferMultisample = BufferMultisample.Disabled) { open class Layer internal constructor(
var copyLayers: List<Layer> = listOf() val type: LayerType,
val bufferMultisample: BufferMultisample = BufferMultisample.Disabled
) {
var sourceOut = SourceOut() var sourceOut = SourceOut()
var sourceIn = SourceIn() var sourceIn = SourceIn()
var maskLayer: Layer? = null var maskLayer: Layer? = null
var drawFunc: () -> Unit = {} var drawFunc: () -> Unit = {}
val children: MutableList<Layer> = mutableListOf() val children: MutableList<Layer> = mutableListOf()
var blendFilter: Pair<Filter, Filter.() -> Unit>? = null var blendFilter: Pair<Filter, Filter.() -> Unit>? = null
val postFilters: MutableList<Pair<Filter, Filter.() -> Unit>> = mutableListOf() val postFilters: MutableList<Triple<Filter, Array<out Layer>, Filter.() -> Unit>> = mutableListOf()
var colorType = ColorType.UINT8 var colorType = ColorType.UINT8
private var unresolvedAccumulation: ColorBuffer? = null private var unresolvedAccumulation: ColorBuffer? = null
var accumulation: ColorBuffer? = null var accumulation: ColorBuffer? = null
@@ -63,7 +69,7 @@ open class Layer internal constructor(val bufferMultisample: BufferMultisample =
/** /**
* draw the layer * draw the layer
*/ */
fun drawLayer(drawer: Drawer) { protected fun drawLayer(drawer: Drawer, cache: ColorBufferCache) {
if (!enabled) { if (!enabled) {
return return
} }
@@ -75,25 +81,6 @@ open class Layer internal constructor(val bufferMultisample: BufferMultisample =
} }
layerTarget?.let { target -> layerTarget?.let { target ->
if (copyLayers.isNotEmpty()) {
copyLayers.forEach {
drawer.isolatedWithTarget(target) {
clearColor?.let {
drawer.clear(it)
}
it.layerTarget?.let { copyTarget ->
if (it.bufferMultisample == BufferMultisample.Disabled) {
drawer.image(copyTarget.colorBuffer(0))
} else {
copyTarget.colorBuffer(0).copyTo(it.accumulation!!)
drawer.image(it.accumulation!!)
}
}
}
}
}
maskLayer?.let { maskLayer?.let {
if (it.shouldCreateLayerTarget(activeRenderTarget)) { if (it.shouldCreateLayerTarget(activeRenderTarget)) {
it.createLayerTarget(activeRenderTarget, drawer, it.bufferMultisample) it.createLayerTarget(activeRenderTarget, drawer, it.bufferMultisample)
@@ -101,11 +88,6 @@ open class Layer internal constructor(val bufferMultisample: BufferMultisample =
it.layerTarget?.let { maskRt -> it.layerTarget?.let { maskRt ->
drawer.isolatedWithTarget(maskRt) { drawer.isolatedWithTarget(maskRt) {
if (copyLayers.isEmpty()) {
clearColor?.let { color ->
drawer.clear(color)
}
}
drawer.fill = ColorRGBa.WHITE drawer.fill = ColorRGBa.WHITE
drawer.stroke = ColorRGBa.WHITE drawer.stroke = ColorRGBa.WHITE
it.drawFunc() it.drawFunc()
@@ -114,48 +96,34 @@ open class Layer internal constructor(val bufferMultisample: BufferMultisample =
} }
drawer.isolatedWithTarget(target) { drawer.isolatedWithTarget(target) {
if (copyLayers.isEmpty()) { children.filter { it.type == LayerType.ASIDE }.forEach {
clearColor?.let { it.drawLayer(drawer, cache)
drawer.clear(it) }
}
clearColor?.let {
drawer.clear(it)
} }
drawFunc() drawFunc()
children.forEach { children.filter { it.type == LayerType.LAYER }.forEach {
it.drawLayer(drawer) it.drawLayer(drawer, cache)
} }
} }
if (postFilters.size > 0) { val layerPost = if (postFilters.isEmpty()) target.colorBuffer(0) else postFilters.let { filters ->
val sizeMismatch = postBufferCache.isNotEmpty() val targets = cache[ColorBufferCacheKey(colorType, target.contentScale)]
&& (postBufferCache[0].width != activeRenderTarget.width targets.forEach {
|| postBufferCache[0].height != activeRenderTarget.height) it.fill(ColorRGBa.TRANSPARENT)
if (sizeMismatch) {
postBufferCache.forEach { it.destroy() }
postBufferCache.clear()
} }
var localSource = target.colorBuffer(0)
if (postBufferCache.isEmpty()) { for ((i, filter) in filters.withIndex()) {
postBufferCache += persistent { filter.first.apply(filter.third)
colorBuffer(activeRenderTarget.width, activeRenderTarget.height, val sources =
activeRenderTarget.contentScale, type = colorType) arrayOf(localSource) + filter.second.map { it.result ?: error("no result for layer $it") }
} .toTypedArray()
postBufferCache += persistent { filter.first.apply(sources, arrayOf(targets[i % targets.size]))
colorBuffer(activeRenderTarget.width, activeRenderTarget.height, localSource = targets[i % targets.size]
activeRenderTarget.contentScale, type = colorType)
}
} }
} targets[postFilters.lastIndex % targets.size]
val layerPost = postFilters.let { filters ->
val targets = postBufferCache
val result = filters.foldIndexed(target.colorBuffer(0)) { i, source, filter ->
val localTarget = targets[i % targets.size]
filter.first.apply(filter.second)
filter.first.apply(source, localTarget)
localTarget
}
result
} }
maskLayer?.let { maskLayer?.let {
@@ -163,41 +131,52 @@ open class Layer internal constructor(val bufferMultisample: BufferMultisample =
maskFilter.apply(arrayOf(layerPost, it.layerTarget!!.colorBuffer(0)), layerPost) maskFilter.apply(arrayOf(layerPost, it.layerTarget!!.colorBuffer(0)), layerPost)
} }
val localBlendFilter = blendFilter if (type == LayerType.ASIDE) {
if (localBlendFilter == null) { if (postFilters.isNotEmpty()) {
drawer.isolatedWithTarget(activeRenderTarget) { require(layerPost != result)
drawer.ortho() layerPost.copyTo(result ?: error("no result"))
drawer.view = Matrix44.IDENTITY }
drawer.model = Matrix44.IDENTITY } else if (type == LayerType.LAYER) {
val localBlendFilter = blendFilter
if (localBlendFilter == null) {
drawer.isolated {
drawer.defaults()
if (bufferMultisample == BufferMultisample.Disabled) {
drawer.image(layerPost, layerPost.bounds, drawer.bounds)
} else {
layerPost.copyTo(accumulation!!)
drawer.image(accumulation!!, layerPost.bounds, drawer.bounds)
}
}
} else {
localBlendFilter.first.apply(localBlendFilter.second)
activeRenderTarget.colorBuffer(0).copyTo(unresolvedAccumulation!!)
if (bufferMultisample == BufferMultisample.Disabled) { if (bufferMultisample == BufferMultisample.Disabled) {
drawer.image(layerPost, layerPost.bounds, drawer.bounds) localBlendFilter.first.apply(
arrayOf(unresolvedAccumulation!!, layerPost),
unresolvedAccumulation!!
)
} else { } else {
layerPost.copyTo(accumulation!!) layerPost.copyTo(accumulation!!)
drawer.image(accumulation!!, layerPost.bounds, drawer.bounds) localBlendFilter.first.apply(
arrayOf(unresolvedAccumulation!!, accumulation!!),
unresolvedAccumulation!!
)
} }
}
} else {
localBlendFilter.first.apply(localBlendFilter.second)
activeRenderTarget.colorBuffer(0).copyTo(unresolvedAccumulation!!)
if (bufferMultisample == BufferMultisample.Disabled) {
localBlendFilter.first.apply(arrayOf(unresolvedAccumulation!!, layerPost), unresolvedAccumulation!!)
} else {
layerPost.copyTo(accumulation!!)
localBlendFilter.first.apply(arrayOf(unresolvedAccumulation!!, accumulation!!), unresolvedAccumulation!!)
}
if (activeRenderTarget !is ProgramRenderTarget) { if (activeRenderTarget !is ProgramRenderTarget) {
unresolvedAccumulation!!.copyTo(target.colorBuffer(0)) unresolvedAccumulation!!.copyTo(target.colorBuffer(0))
}
unresolvedAccumulation!!.copyTo(activeRenderTarget.colorBuffer(0))
} }
unresolvedAccumulation!!.copyTo(activeRenderTarget.colorBuffer(0))
} }
} }
} }
private fun shouldCreateLayerTarget(activeRenderTarget: RenderTarget): Boolean { private fun shouldCreateLayerTarget(activeRenderTarget: RenderTarget): Boolean {
return layerTarget == null return layerTarget == null
|| ((layerTarget?.width != activeRenderTarget.width || layerTarget?.height != activeRenderTarget.height) || ((layerTarget?.width != activeRenderTarget.width || layerTarget?.height != activeRenderTarget.height)
&& activeRenderTarget.width > 0 && activeRenderTarget.height > 0) && activeRenderTarget.width > 0 && activeRenderTarget.height > 0)
} }
private fun createLayerTarget( private fun createLayerTarget(
@@ -229,26 +208,72 @@ open class Layer internal constructor(val bufferMultisample: BufferMultisample =
} }
} }
} }
fun Drawer.image(layer: Layer) {
val cb = layer.result
if (cb != null) {
image(cb)
}
}
} }
/** /**
* create a layer within the composition * create a layer within the composition
*/ */
fun Layer.layer(function: Layer.() -> Unit): Layer { fun Layer.layer(
val layer = Layer().apply { function() } colorType: ColorType = this.colorType,
multisample: BufferMultisample = BufferMultisample.Disabled,
function: Layer.() -> Unit
): Layer {
val layer = Layer(LayerType.LAYER, multisample).apply { function() }
layer.colorType = colorType
children.add(layer) children.add(layer)
return layer return layer
} }
/** fun Layer.aside(
* create a layer within the composition with a custom [BufferMultisample] colorType: ColorType = this.colorType,
*/ multisample: BufferMultisample = BufferMultisample.Disabled,
fun Layer.layer(bufferMultisample: BufferMultisample, function: Layer.() -> Unit): Layer { function: Layer.() -> Unit
val layer = Layer(bufferMultisample).apply { function() } ): Layer {
val layer = Layer(LayerType.ASIDE, multisample).apply { function() }
layer.colorType = colorType
children.add(layer) children.add(layer)
return layer return layer
} }
fun <T : Filter1to1> Layer.apply(drawer: Drawer,
filter: T, source: Layer, colorType: ColorType = this.colorType,
function: T.() -> Unit
): Layer {
val layer = Layer(LayerType.ASIDE)
layer.colorType = colorType
layer.draw {
drawer.image(source.result!!)
//source.result!!.copyTo(result!!)
}
layer.post(filter, function)
children.add(layer)
return layer
}
fun <T : Filter2to1> Layer.apply(drawer: Drawer,
filter: T, source0: Layer, source1:Layer, colorType: ColorType = this.colorType,
function: T.() -> Unit
): Layer {
val layer = Layer(LayerType.ASIDE)
layer.colorType = colorType
layer.draw {
//source0.result!!.copyTo(result!!)
drawer.image(source0.result!!)
}
layer.post(filter, source1, function)
children.add(layer)
return layer
}
/** /**
* set the draw contents of the layer * set the draw contents of the layer
*/ */
@@ -256,18 +281,12 @@ fun Layer.draw(function: () -> Unit) {
drawFunc = function drawFunc = function
} }
/**
* use the layer as a base
*/
fun Layer.use(vararg layer: Layer) {
copyLayers = layer.toList()
}
/** /**
* the drawing acts as a mask on the layer * the drawing acts as a mask on the layer
*/ */
fun Layer.mask(function: () -> Unit) { fun Layer.mask(function: () -> Unit) {
maskLayer = Layer().apply { maskLayer = Layer(LayerType.LAYER).apply {
this.drawFunc = function this.drawFunc = function
} }
} }
@@ -275,24 +294,73 @@ fun Layer.mask(function: () -> Unit) {
/** /**
* add a post-processing filter to the layer * add a post-processing filter to the layer
*/ */
fun <F : Filter> Layer.post(filter: F, configure: F.() -> Unit = {}): F { fun <F : Filter1to1> Layer.post(filter: F, configure: F.() -> Unit = {}): F {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
postFilters.add(Pair(filter as Filter, configure as Filter.() -> Unit)) postFilters.add(Triple(filter as Filter, emptyArray(), configure as Filter.() -> Unit))
return filter return filter
} }
fun <F : Filter2to1> Layer.post(filter: F, input1: Layer, configure: F.() -> Unit = {}): F {
require(input1.type == LayerType.ASIDE)
@Suppress("UNCHECKED_CAST")
postFilters.add(Triple(filter as Filter, arrayOf(input1), configure as Filter.() -> Unit))
return filter
}
fun <F : Filter3to1> Layer.post(filter: F, input1: Layer, input2: Layer, configure: F.() -> Unit = {}): F {
require(input1.type == LayerType.ASIDE)
require(input2.type == LayerType.ASIDE)
@Suppress("UNCHECKED_CAST")
postFilters.add(Triple(filter as Filter, arrayOf(input1, input2), configure as Filter.() -> Unit))
return filter
}
/** /**
* add a blend filter to the layer * add a blend filter to the layer
*/ */
fun <F : Filter> Layer.blend(filter: F, configure: F.() -> Unit = {}): F { fun <F : Filter2to1> Layer.blend(filter: F, configure: F.() -> Unit = {}): F {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
blendFilter = Pair(filter as Filter, configure as Filter.() -> Unit) blendFilter = Pair(filter as Filter, configure as Filter.() -> Unit)
return filter return filter
} }
class Composite : Layer() { data class ColorBufferCacheKey(
val colorType: ColorType,
val contentScale: Double
)
class ColorBufferCache(val width: Int, val height: Int) {
val cache = mutableMapOf<ColorBufferCacheKey, List<ColorBuffer>>()
operator fun get(key: ColorBufferCacheKey): List<ColorBuffer> {
return cache.getOrPut(key) {
listOf(
colorBuffer(width, height, type = key.colorType, contentScale = key.contentScale),
colorBuffer(width, height, type = key.colorType, contentScale = key.contentScale),
)
}
}
fun destroy() {
cache.forEach {
it.value.forEach { cb -> cb.destroy() }
}
}
}
class Composite : Layer(LayerType.LAYER) {
private var cache = ColorBufferCache(RenderTarget.active.width, RenderTarget.active.height)
fun draw(drawer: Drawer) { fun draw(drawer: Drawer) {
drawLayer(drawer)
if (cache.width != RenderTarget.active.width || cache.height != RenderTarget.active.height) {
cache.destroy()
cache = ColorBufferCache(RenderTarget.active.width, RenderTarget.active.height)
}
drawLayer(drawer, cache)
} }
} }
@@ -305,6 +373,9 @@ fun compose(function: Layer.() -> Unit): Composite {
return root return root
} }
class Compositor : Extension { class Compositor : Extension {
override var enabled: Boolean = true override var enabled: Boolean = true
var composite = Composite() var composite = Composite()
@@ -312,7 +383,7 @@ class Compositor : Extension {
override fun afterDraw(drawer: Drawer, program: Program) { override fun afterDraw(drawer: Drawer, program: Program) {
drawer.isolated { drawer.isolated {
drawer.defaults() drawer.defaults()
composite.drawLayer(drawer) composite.draw(drawer)
} }
} }
} }

View File

@@ -0,0 +1,37 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.ColorType
import org.openrndr.extra.compositor.*
import org.openrndr.extra.fx.blur.DirectionalBlur
import org.openrndr.extra.fx.blur.HashBlurDynamic
import org.openrndr.extra.fx.patterns.Checkers
import kotlin.math.cos
fun main() {
application {
program {
val c = compose {
layer {
val a = aside(colorType = ColorType.FLOAT32) {
post(Checkers()) {
this.size = cos(seconds)*0.5 + 0.5
}
}
draw {
drawer.clear(ColorRGBa.GRAY.shade(0.5))
drawer.circle(width/2.0, height/2.0, 100.0)
}
post(HashBlurDynamic(), a) {
time = seconds
radius = 25.0
}
}
}
extend {
c.draw(drawer)
}
}
}
}

View File

@@ -23,13 +23,6 @@ fun main() = application {
} }
program { program {
// -- this block is for automation purposes only
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
data class Item(var pos: Vector3, val color: ColorRGBa) { data class Item(var pos: Vector3, val color: ColorRGBa) {
fun draw(drawer: Drawer) { fun draw(drawer: Drawer) {
pos -= Vector3(pos.z * 3.0, 0.0, 0.0) pos -= Vector3(pos.z * 3.0, 0.0, 0.0)

View File

@@ -21,7 +21,7 @@ fun main() = application {
program { program {
val layers = compose { val layers = compose {
layer(BufferMultisample.SampleCount(16)) { layer(multisample = BufferMultisample.SampleCount(16)) {
draw { draw {
drawer.translate(drawer.bounds.center) drawer.translate(drawer.bounds.center)
drawer.rotate(seconds) drawer.rotate(seconds)
@@ -29,7 +29,7 @@ fun main() = application {
drawer.rectangle(Rectangle.fromCenter(Vector2.ZERO, 200.0)) drawer.rectangle(Rectangle.fromCenter(Vector2.ZERO, 200.0))
} }
layer() { layer {
blend(Normal()) { blend(Normal()) {
clip = true clip = true
} }

View File

@@ -1,90 +0,0 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.color.rgb
import org.openrndr.extensions.SingleScreenshot
import org.openrndr.extra.compositor.*
import org.openrndr.extra.fx.blend.Add
import org.openrndr.extra.fx.blur.ApproximateGaussianBlur
import org.openrndr.extra.fx.color.ColorCorrection
import kotlin.random.Random
/**
* Compositor demo of `use`, which makes it possible to
* use the color buffer of a previous layer.
*
* This program draws a series of concentric rings, most of them gray,
* 10% are pink.
*
* In a second layer we reuse that image with rings, applying an extreme
* color correction to make everything black except the pink rings,
* then apply a strong blur and finally compose it over the original
* image using blend mode Add.
*
* The result is an sharp image of gray rings with glowing pink rings.
*
* Note: see also orx-fx Bloom()
*
*/
// Toggle to see the difference between a simple blur and multilayer bloom
const val effectEnabled = true
fun main() = application {
configure {
width = 900
height = 900
}
program {
// -- this block is for automation purposes only
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
val composite = compose {
val circles = layer {
draw {
drawer.stroke = null
val rnd = Random(frameCount / 100 + 1)
for (i in 18 downTo 0) {
drawer.fill = if (rnd.nextDouble() < 0.1)
ColorRGBa.PINK.shade(Random.nextDouble(0.88, 1.0))
else
rgb(rnd.nextInt(6) / 15.0)
drawer.circle(drawer.bounds.center, 50.0 + i * 20)
}
}
// A. To see how the plain blur looks like
if (!effectEnabled) {
post(ApproximateGaussianBlur()) {
sigma = 25.0
window = 25
}
}
}
// B. This is the bloom effect
if (effectEnabled) {
layer {
use(circles) // <-- use the previous layer as starting point
post(ColorCorrection()) {
brightness = -0.3
contrast = 0.8
}
post(ApproximateGaussianBlur()) {
sigma = 25.0
window = 25
}
blend(Add())
}
}
}
extend {
drawer.clear(rgb(0.2))
composite.draw(drawer)
}
}
}