Color buffers

A color buffer is an image stored in GPU memory.

Creating a color buffer

Color buffers are created using the colorBuffer() function.

val cb = colorBuffer(640, 480)

Specifying buffer format

Color buffers can be created in different formats. The buffer format specifies the number and order of channels in the image. Color buffers can have 1 to 4 channels. The format argument can be any ColorFormat value.

val cb = colorBuffer(640, 480, format = ColorFormat.R)

Specifying buffer type

The buffer type specifies which data type is used for storing colors in the buffer. The type argument can be any ColorType value.

val cb = colorBuffer(640, 480, type = ColorType.FLOAT16)

Loading color buffers

Color buffers can be loaded from an image stored on disk. Supported file types are png, jpg, dds and exr (OpenEXR).

val cb = loadImage("data/images/pm5544.jpg")

Generating color buffers

Use drawImage to create a color buffer and draw into it.

../media/colorbuffer-001.jpg

fun main() = application {
    program {
        val gradientBackground = drawImage(width, height) {
            // Draw anything here, for example a radial gradient.
            drawer.shadeStyle = RadialGradient(ColorRGBa.WHITE, ColorRGBa.PINK)
            val r = Rectangle.fromCenter(drawer.bounds.center, 800.0, 800.0)
            drawer.rectangle(r)
        }
        extend {
            drawer.image(gradientBackground)
            drawer.circle(drawer.bounds.center, sin(seconds) * 80 + 100)
        }
    }
}

Link to the full example

drawImage is convenient for creating static images. If a program requires redrawing a color buffer use a render target instead.

Freeing color buffers

If a program creates new buffers while it runs it is important to free those buffers when no longer needed to avoid running out of memory.

// -- When done using the buffer call destroy() to free its memory.
cb.destroy()

Saving color buffers

Color buffers can be saved to disk using the saveToFile member function. Supported file types are png, jpg, dds and exr (OpenEXR).

val cb = colorBuffer(640, 480)
cb.saveToFile(File("output.jpg"))

When repeatedly saving color buffers asynchronously (the default) it is possible to run out of memory. This can happen if the software can not save images at the requested frame rate. In such situations we can either set async = false in saveToFile() or avoid saveToFile and use the VideoWriter together with pngSequence or tiffSequence instead.

Note that some image file types take longer to save than others.

Copying between color buffers

Color buffer contents can be copied using the copyTo member function. Copying works between color buffers of different formats and types.

// -- create color buffers
val cb0 = colorBuffer(640, 480, type = ColorType.FLOAT16)
val cb1 = colorBuffer(640, 480, type = ColorType.FLOAT32)
//-- copy contents of cb0 to cb1
cb0.copyTo(cb1)

Writing into color buffers

To upload data into the color buffer one uses the write member function.

// -- create a color buffer that uses 8 bits per channel (the default)
val cb = colorBuffer(640, 480, type = ColorType.UINT8)

// -- create a buffer (on CPU) that matches size and layout of the color buffer
val buffer = ByteBuffer.allocateDirect(cb.width * cb.height * cb.format.componentCount * cb.type.componentSize)

// -- fill buffer with random data
for (y in 0 until cb.height) {
    for (x in 0 until cb.width) {
        for (c in 0 until cb.format.componentCount) {
            buffer.put((Math.random() * 255).toInt().toByte())
        }
    }
}

// -- rewind the buffer, this is essential as upload will be from the position we left the buffer at
buffer.rewind()
// -- write into color buffer
cb.write(buffer)

Reading from color buffers

To download data from a color buffer one uses the read member function.

// -- create a color buffer that uses 8 bits per channel (the default)
val cb = colorBuffer(640, 480, type = ColorType.UINT8)

// -- create a buffer (on CPU) that matches size and layout of the color buffer
val buffer = ByteBuffer.allocateDirect(cb.width * cb.height * cb.format.componentCount * cb.type.componentSize)

// -- download data into buffer
cb.read(buffer)

// -- read the first UINT8 pixel's color
val r = buffer[0].toUByte().toDouble() / 255.0
val g = buffer[1].toUByte().toDouble() / 255.0
val b = buffer[2].toUByte().toDouble() / 255.0
val a = buffer[3].toUByte().toDouble() / 255.0
val c = rgb(r, g, b, a)

Color buffer shadows

To simplify the process of reading and writing from and to color buffers we added a shadow buffer to ColorBuffer. A shadow buffer offers a simple interface to access the color buffer’s contents.

Note that shadow buffers have more overhead than using read() and write().

// -- create a color buffer that uses 8 bits per channel (the default)
val cb = colorBuffer(640, 480, type = ColorType.UINT8)
val shadow = cb.shadow

// -- download cb's contents into shadow
shadow.download()

// -- place random data in the shadow buffer
for (y in 0 until cb.height) {
    for (x in 0 until cb.width) {
        shadow[x, y] = ColorRGBa(Math.random(), Math.random(), Math.random())
    }
}

// -- upload shadow to cb
shadow.upload()

edit on GitHub