Array textures

Array textures are a special type of texture that make it possible to access 2048 layers of texture data from a single texture sampler.

Array textures are encapsulated by the ArrayTexture interface

Creation

Array textures are created using the arrayTexture function.

fun main() = application {
    program {
        // -- create an array texture with 100 layers
        val at = arrayTexture(512, 512, 100)
    }
}

Writing to array textures

There are several ways to get texture data into array textures. Let’s have a look at them.

One can copy from a ColorBuffer using .copyTo()

fun main() = application {
    program {
        val at = arrayTexture(512, 512, 100)
        val cb = colorBuffer(512, 512)
        // -- copy from the color buffer to the 4th layer of the array texture
        cb.copyTo(at, 4)
    }
}

or copy from an array texture layer to another layer

fun main() = application {
    program {
        val at = arrayTexture(512, 512, 100)
        // -- copy from the 2nd array texture layer to the 4th array texture layer
        at.copyTo(2, at, 4)
    }
}

or write to an array texture layer from a direct ByteBuffer

fun main() = application {
    program {
        val at = arrayTexture(512, 512, 100)
        val buffer = ByteBuffer.allocateDirect(512 * 512 * 4)
        
        // fill buffer with random data
        for (y in 0 until 512) {
            for (x in 0 until 512) {
                for (c in 0 until 4) {
                    buffer.put((Math.random() * 255).toInt().toByte())
                }
            }
        }
        buffer.rewind()
        // -- write the buffer into the 0th layer
        at.write(0, buffer)
    }
}

Drawing array textures

Array textures can be drawn using the Drawer.image functions.

As example we show how to draw the 0th layer of an array texture

fun main() = application {
    program {
        val at = arrayTexture(512, 512, 100)
        
        extend {
            drawer.image(at, 0)
            // -- with position and size arguments
            drawer.image(at, 0, 100.0, 100.0, 256.0, 256.0)
        }
    }
}

We can also render batches of array textures by passing lists of layer indexes and source-target rectangle pairs.

fun main() = application {
    program {
        val at = arrayTexture(512, 512, 100)
        
        extend {
            drawer.image(at, 0)
            
            val layers = mutableListOf<Int>()
            val rectangles = mutableListOf<Pair<Rectangle, Rectangle>>()
            
            for (i in 0 until 100) {
                layers.add((Math.random() * 100).toInt())
                val source = Rectangle(Math.random() * 512.0, Math.random() * 512.0, Math.random() * 512.0, Math.random() * 512.0)
                val target = Rectangle(Math.random() * 512.0, Math.random() * 512.0, Math.random() * 512.0, Math.random() * 512.0)
                rectangles.add(source to target)
            }
            drawer.image(at, layers, rectangles)
        }
    }
}

Drawing into array textures

Array textures can be used as attachment for render targets.

Here we show how to use a single layer from an array texture as an attachment for a render target.

fun main() = application {
    program {
        val at = arrayTexture(512, 512, 100)
        // -- create a render target
        val rt = renderTarget(512, 512) {
            // -- attach the 0th layer of the array texture
            arrayTexture(at, 0)
            depthBuffer()
        }
        
        extend {
            drawer.isolatedWithTarget(rt) {
                drawer.ortho(rt)
                drawer.clear(ColorRGBa.PINK)
            }
            drawer.image(at, 0)
        }
    }
}

Let’s conclude this chapter by means of a small slit scanning demonstration. Here we use a single array texture and a list of render targets, all using different layers of the same array texture.

fun main() = application {
    
    program {
        val at = arrayTexture(770, 576, 116)
        val renderTargets = List(at.layers) {
            renderTarget(at.width, at.height) {
                arrayTexture(at, it)
            }
        }
        var index = 0
        extend {
            drawer.clear(ColorRGBa.BLACK)
            drawer.isolatedWithTarget(renderTargets[index % renderTargets.size]) {
                drawer.clear(ColorRGBa.BLACK)
                drawer.fill = ColorRGBa.PINK.opacify(0.5)
                drawer.stroke = null
                for (i in 0 until 20) {
                    drawer.circle(cos(seconds * 5.0 + i) * 256 + width / 2.0, sin(i + seconds * 6.32) * 256 + height / 2.0, 100.0)
                }
            }
            
            val layers = (0 until at.layers).map {
                mod(index - it, at.layers)
            }
            val rectangles = (0 until at.layers).map {
                val span = Rectangle(0.0, it * 5.0, at.width * 1.0, 5.0)
                span to span
            }
            
            drawer.image(at, layers, rectangles)
            index++
        }
    }
}

Link to the full example

edit on GitHub