Buffer Management
KraftShade's pipeline system includes a sophisticated buffer management mechanism that efficiently handles OpenGL texture buffers. This document explains how buffers are managed, tracked, and recycled throughout the pipeline execution process.
Overview
In graphics processing, texture buffers are used to store intermediate results between shader operations. Creating and destroying these buffers is expensive, so KraftShade implements a buffer pooling system that reuses buffers when possible, significantly improving performance.
The buffer management system consists of three main components:
- TextureBufferPool: A pool that manages the lifecycle of texture buffers
- BufferReference: A handle that references a buffer in the pool
- Buffer Collection Mechanism: A system that tracks buffer usage and recycles buffers when they're no longer needed
TextureBufferPool
The TextureBufferPool
is responsible for managing a collection of TextureBuffer
objects. It provides the following functionality:
Key Operations
- Buffer Allocation: When a
BufferReference
requests a buffer, the pool first checks if there's an available buffer that can be reused. If not, it creates a new buffer. - Buffer Recycling: When a buffer is no longer needed, it's moved from the active map to the available buffers list.
- Size Management: All buffers in the pool have the same size. When the size changes, all buffers are deleted and will be recreated with the new size when needed.
Implementation Details
operator fun get(bufferReference: BufferReference): TextureBuffer {
return map[bufferReference] ?: run {
// Try to reuse an available buffer
availableBuffers
.removeFirstOrNull()
?.let { availableBuffer ->
map[bufferReference] = availableBuffer
return availableBuffer
}
// Create a new buffer if none are available
TextureBuffer(bufferSize).also {
map[bufferReference] = it
}
}
}
BufferReference
A BufferReference
is a handle that represents a buffer in the TextureBufferPool
. It implements both GlBufferProvider
and TextureProvider
interfaces, allowing it to be used in different contexts:
Usage
BufferReference
objects are created by the pipeline and used to:
- Request Buffers: When a shader needs a target buffer to render to
- Provide Textures: When a shader needs a texture input from a previous step
- Track Buffer Lifecycle: The pipeline system tracks when each reference is last used
Implementation Details
class BufferReference internal constructor(
private val pipeline: Pipeline,
val nameForDebug: String? = null,
) : GlBufferProvider, TextureProvider {
override fun provideBuffer(): GlBuffer {
return pipeline.bufferPool[this]
}
override fun provideTexture(): Texture {
return pipeline.getTextureFromBufferPool(this)
}
}
Buffer Collection Mechanism
The buffer collection mechanism is a key part of KraftShade's performance optimization. It automatically tracks buffer usage and recycles buffers when they're no longer needed.
How It Works
- Usage Tracking: When a
BufferReference
is used as a texture input, the pipeline records the step index inbufferReferenceUsage
. - Automatic Recycling: After each step, the pipeline checks if any buffers won't be used in future steps and recycles them.
- Final Cleanup: At the end of the pipeline run, all buffers are recycled.
Implementation Details
// In Pipeline.kt
private fun recycleUnusedBuffers(currentStep: Int) {
val buffersToRecycle = bufferReferenceUsage.entries
.filter { (_, lastUsedStepIndex) -> lastUsedStepIndex == currentStep }
.map { it.key }
.toTypedArray()
if (buffersToRecycle.isNotEmpty()) {
bufferPool.recycle(currentStep.toString(), *buffersToRecycle)
}
}
Two-Phase Execution and Buffer Management
KraftShade's pipeline execution has two phases that work together with the buffer management system:
Configuration Phase
During the first run of a pipeline:
- The pipeline executes each step with
isRenderPhase = false
- As steps request texture inputs, the pipeline tracks which
BufferReference
is used in which step - This builds a complete map of buffer usage throughout the pipeline
Render Phase
During subsequent runs:
- The pipeline executes each step with
isRenderPhase = true
- After each step, it checks if any buffers won't be used in future steps
- Buffers that won't be used again are recycled immediately
- At the end of the run, all buffers are recycled
Performance Benefits
This buffer management system provides several performance benefits:
- Reduced Memory Usage: By recycling buffers, the system minimizes the number of texture buffers needed.
- Minimized GPU Operations: Creating and destroying OpenGL textures is expensive. Reusing buffers reduces these operations.
- Automatic Cleanup: Developers don't need to manually manage buffer lifecycles.
- Optimized for Complex Pipelines: The system scales well with pipeline complexity, as buffers are recycled as soon as possible.
Example: Buffer Lifecycle in a Pipeline
Consider a simple pipeline with three steps:
- Step 0: Renders to Buffer A
- Step 1: Uses Buffer A as input, renders to Buffer B
- Step 2: Uses Buffer B as input, renders to the final output
The buffer lifecycle would be:
-
Configuration Phase:
- Records that Buffer A is last used in Step 1
- Records that Buffer B is last used in Step 2
-
Render Phase:
- After Step 1: Recycles Buffer A since it won't be used again
- After Step 2: Recycles Buffer B
- End of pipeline: All buffers are recycled
This ensures that at any point, only the necessary buffers are allocated, minimizing memory usage and maximizing performance.