Skip to content

Commit

Permalink
use scoped bind groups
Browse files Browse the repository at this point in the history
  • Loading branch information
fabmax committed Jan 14, 2024
1 parent 59b75d4 commit 98a7714
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,17 @@ open class KslShader private constructor(val program: KslProgram) : Shader(progr
}

fun KslProgram.makeBindGroupLayout(): List<BindGroupLayout> {
val bindGrpBuilder = BindGroupLayout.Builder()
setupBindGroupLayoutUbos(bindGrpBuilder)
setupBindGroupLayoutTextures(bindGrpBuilder)
setupBindGroupLayoutStorage(bindGrpBuilder)
return listOf(bindGrpBuilder.create(0))
return BindGroupScope.entries.map {
val bindGrpBuilder = BindGroupLayout.Builder(it)
setupBindGroupLayoutUbos(bindGrpBuilder)
setupBindGroupLayoutTextures(bindGrpBuilder)
setupBindGroupLayoutStorage(bindGrpBuilder)
bindGrpBuilder.create()
}
}

private fun KslProgram.setupBindGroupLayoutUbos(bindGrpBuilder: BindGroupLayout.Builder) {
uniformBuffers.filter { it.uniforms.isNotEmpty() }.forEach { kslUbo ->
uniformBuffers.filter { it.uniforms.isNotEmpty() && it.scope == bindGrpBuilder.scope }.forEach { kslUbo ->
val uniforms = kslUbo.uniforms.values.map { uniform ->
when(val type = uniform.value.expressionType) {
is KslFloat1 -> Uniform.float1(uniform.name)
Expand Down Expand Up @@ -203,6 +205,11 @@ private fun KslProgram.setupBindGroupLayoutUbos(bindGrpBuilder: BindGroupLayout.
}

private fun KslProgram.setupBindGroupLayoutTextures(bindGrpBuilder: BindGroupLayout.Builder) {
if (bindGrpBuilder.scope != BindGroupScope.MATERIAL) {
// todo: add bind group scope to ksl textures -> for now we use material scope for all of them
return
}

uniformSamplers.values.forEach { sampler ->
val texStages = stages
.filter { it.dependsOn(sampler) }
Expand Down Expand Up @@ -235,6 +242,11 @@ private fun KslProgram.setupBindGroupLayoutTextures(bindGrpBuilder: BindGroupLay
}

private fun KslProgram.setupBindGroupLayoutStorage(bindGrpBuilder: BindGroupLayout.Builder) {
if (bindGrpBuilder.scope != BindGroupScope.MATERIAL) {
// todo: add bind group scope to ksl storage -> for now we use material scope for all of them
return
}

uniformStorage.values.forEach { storage ->
val storageStages = stages
.filter { it.dependsOn(storage) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class CameraData(program: KslProgram) : KslDataBlock, KslShaderListener {
val clipFar: KslExprFloat1
get() = clip.y

val camUbo = KslUniformBuffer("CameraUniforms", program, false).apply {
val camUbo = KslUniformBuffer("CameraUniforms", program, BindGroupScope.SCENE).apply {
viewMat = uniformMat4(UNIFORM_NAME_VIEW_MAT)
projMat = uniformMat4(UNIFORM_NAME_PROJ_MAT)
viewProjMat = uniformMat4(UNIFORM_NAME_VIEW_PROJ_MAT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package de.fabmax.kool.modules.ksl.blocks

import de.fabmax.kool.modules.ksl.lang.*

abstract class LitMaterialBlock(maxNumberOfLights: Int, name: String, parentScope: KslScopeBuilder) : KslBlock(name, parentScope) {
abstract class LitMaterialBlock(val maxNumberOfLights: Int, name: String, parentScope: KslScopeBuilder) : KslBlock(name, parentScope) {
val inCamPos = inFloat3("inCamPos")
val inNormal = inFloat3("inNormal")
val inFragmentPos = inFloat3("inFragmentPos")
Expand All @@ -24,7 +24,7 @@ abstract class LitMaterialBlock(maxNumberOfLights: Int, name: String, parentScop
lightStrength: KslExprFloat1 = KslValueFloat1(1f)
) {
inShadowFactors(shadowFactors)
inLightCount(lightData.lightCount)
inLightCount(KslBuiltinMinScalar(lightData.lightCount, KslValueInt1(maxNumberOfLights)))
inEncodedLightPositions(lightData.encodedPositions)
inEncodedLightDirections(lightData.encodedDirections)
inEncodedLightColors(lightData.encodedColors)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ open class KslProgram(val name: String) {
@PublishedApi
internal fun nextName(prefix: String): String = "${prefix}_${nextNameIdx++}"

val commonUniformBuffer = KslUniformBuffer("CommonUniforms", this)
val commonUniformBuffer = KslUniformBuffer("CommonUniforms", this, BindGroupScope.MATERIAL)
val uniformBuffers = mutableListOf(commonUniformBuffer)
val uniformSamplers = mutableMapOf<String, KslUniform<*>>()
val uniformStorage = mutableMapOf<String, KslStorage<*,*>>()
Expand Down Expand Up @@ -263,11 +263,15 @@ open class KslProgram(val name: String) {

stages.forEach { it.prepareGenerate() }

// remove unused uniforms
uniformBuffers.filter { !it.isShared }.forEach {
// filter uniforms:
// - remove unused uniforms from non-shared buffers
uniformBuffers.filter { ubo -> ubo.scope != BindGroupScope.SCENE }.forEach {
it.uniforms.values.retainAll { u -> stages.any { stage -> stage.dependsOn(u) } }
}
uniformBuffers.removeAll { it.uniforms.isEmpty() }
// - remove empty and completely unused uniform buffers
uniformBuffers.removeAll { ubo ->
ubo.uniforms.isEmpty() || ubo.uniforms.values.none { u -> stages.any { it.dependsOn(u) } }
}

// remove unused texture samplers
uniformSamplers.values.retainAll { u -> stages.any { stage -> stage.dependsOn(u) } }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package de.fabmax.kool.modules.ksl.lang

class KslUniformBuffer(val name: String, val program: KslProgram, val isShared: Boolean = false) {
import de.fabmax.kool.pipeline.BindGroupScope

class KslUniformBuffer(val name: String, val program: KslProgram, val scope: BindGroupScope) {

val uniforms = mutableMapOf<String, KslUniform<*>>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,37 @@ package de.fabmax.kool.pipeline
import de.fabmax.kool.modules.ksl.lang.KslNumericType
import de.fabmax.kool.util.LongHash

class BindGroupLayout private constructor(val group: Int, val bindings: List<BindingLayout>) {
class BindGroupLayout(val scope: BindGroupScope, val bindings: List<BindingLayout>) {
val group: Int get() = scope.group

val hash = LongHash()

// todo: bind group scope? (mesh / material / scene)

init {
val hash: Long = LongHash().let {
it += scope
bindings.forEachIndexed { i, binding ->
binding.bindingIndex = i
hash += binding.hash
it += binding.hash
}
it.hash
}

fun createData(): BindGroupData = BindGroupData(this)

class Builder {
class Builder(val scope: BindGroupScope) {
val ubos = mutableListOf<UniformBufferLayout>()
val textures = mutableListOf<TextureLayout>()
val storage = mutableListOf<StorageTextureLayout>()

fun create(group: Int): BindGroupLayout {
val groupItems = mutableListOf<BindingLayout>()
// fixme: binding number should be generated differently for OpenGL and Vulkan:
// - for Vulkan binding number has to be unique for each binding in the group
// - for OpenGL binding number is currently only relevant for storage bindings and should start at 0 for those
// we achieve this somewhat hacky by adding storage bindings first
groupItems += storage
groupItems += textures
groupItems += ubos
return BindGroupLayout(group, groupItems)
fun create(): BindGroupLayout {
return BindGroupLayout(scope, ubos + textures + storage)
}
}
}

enum class BindGroupScope(val group: Int) {
SCENE(0),
MATERIAL(1),
MESH(2)
}

sealed class BindingLayout(
val name: String,
val stages: Set<ShaderStage>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ data class Uniform(
val isArray: Boolean
get() = arraySize > 1

init {
check(arraySize >= 1) {
"Uniform $name ($type) has invalid arraySize: $arraySize. Uniform arraySize has to be >= 1 (non-array-types should have arraySize = 1)"
}
}

override fun toString(): String {
return name + if (isArray) "[$arraySize]" else ""
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package de.fabmax.kool.pipeline.backend.gl

import de.fabmax.kool.KoolContext
import de.fabmax.kool.pipeline.*
import de.fabmax.kool.pipeline.backend.stats.PipelineInfo
import de.fabmax.kool.pipeline.drawqueue.DrawCommand
Expand All @@ -11,18 +10,17 @@ import de.fabmax.kool.util.logE

class CompiledShader(val program: GlProgram, val pipeline: PipelineBase, val backend: RenderBackendGl) {

private val pipelineId = pipeline.pipelineHash
private val gl: GlApi = backend.gl

private val pipelineInfo = PipelineInfo(pipeline)

private val attributes = mutableMapOf<String, VertexLayout.VertexAttribute>()
private val instanceAttributes = mutableMapOf<String, VertexLayout.VertexAttribute>()
private val uniformLocations = mutableMapOf<String, IntArray>()
private val instances = mutableMapOf<Long, ShaderInstance>()
private val computeInstances = mutableMapOf<Long, ComputeShaderInstance>()

private val ctx: KoolContext = backend.ctx
private val gl: GlApi = backend.gl

private val pipelineInfo = PipelineInfo(pipeline)
private val compatUbos = mutableSetOf<String>()

init {
(pipeline as? Pipeline)?.apply {
Expand All @@ -36,17 +34,21 @@ class CompiledShader(val program: GlProgram, val pipeline: PipelineBase, val bac
}
}

var uboIndex = 0
var storageIndex = 0
pipeline.bindGroupLayouts.flatMap { it.bindings }.forEach { binding ->
when (binding) {
is UniformBufferLayout -> {
val blockIndex = gl.getUniformBlockIndex(program, binding.name)
if (blockIndex == gl.INVALID_INDEX) {
if (blockIndex != gl.INVALID_INDEX) {
val uboBinding = uboIndex++
uniformLocations[binding.name] = intArrayOf(uboBinding)
gl.uniformBlockBinding(program, blockIndex, uboBinding)
} else {
// binding does not describe an actual UBO but plain old uniforms
val locations = binding.uniforms.map { gl.getUniformLocation(program, it.name) }.toIntArray()
uniformLocations[binding.name] = locations

} else {
gl.uniformBlockBinding(program, blockIndex, binding.bindingIndex)
compatUbos += binding.name
}
}
is Texture1dLayout -> {
Expand All @@ -63,15 +65,15 @@ class CompiledShader(val program: GlProgram, val pipeline: PipelineBase, val bac
}
is StorageTexture1dLayout -> {
checkStorageTexSupport()
uniformLocations[binding.name] = intArrayOf(binding.bindingIndex)
uniformLocations[binding.name] = intArrayOf(storageIndex++)
}
is StorageTexture2dLayout -> {
checkStorageTexSupport()
uniformLocations[binding.name] = intArrayOf(binding.bindingIndex)
uniformLocations[binding.name] = intArrayOf(storageIndex++)
}
is StorageTexture3dLayout -> {
checkStorageTexSupport()
uniformLocations[binding.name] = intArrayOf(binding.bindingIndex)
uniformLocations[binding.name] = intArrayOf(storageIndex++)
}
}
}
Expand Down Expand Up @@ -154,18 +156,18 @@ class CompiledShader(val program: GlProgram, val pipeline: PipelineBase, val bac

abstract inner class ShaderInstanceBase(private val pipelineInstance: PipelineBase) {

protected val ubos = mutableListOf<UniformBufferLayout>()
protected val textures1d = mutableListOf<Texture1dLayout>()
protected val textures2d = mutableListOf<Texture2dLayout>()
protected val textures3d = mutableListOf<Texture3dLayout>()
protected val texturesCube = mutableListOf<TextureCubeLayout>()
protected val storage1d = mutableListOf<StorageTexture1dLayout>()
protected val storage2d = mutableListOf<StorageTexture2dLayout>()
protected val storage3d = mutableListOf<StorageTexture3dLayout>()
private val ubos = mutableListOf<UniformBufferLayout>()
private val textures1d = mutableListOf<Texture1dLayout>()
private val textures2d = mutableListOf<Texture2dLayout>()
private val textures3d = mutableListOf<Texture3dLayout>()
private val texturesCube = mutableListOf<TextureCubeLayout>()
private val storage1d = mutableListOf<StorageTexture1dLayout>()
private val storage2d = mutableListOf<StorageTexture2dLayout>()
private val storage3d = mutableListOf<StorageTexture3dLayout>()

protected val mappings = mutableListOf<Pair<Int, MappedUniform>>()
protected var uboBuffers = mutableListOf<BufferResource>()
protected var nextTexUnit = gl.TEXTURE0
private val mappings = mutableListOf<Pair<Int, MappedUniform>>()
private var uboBuffers = mutableListOf<BufferResource>()
private var nextTexUnit = gl.TEXTURE0

init {
pipelineInstance.bindGroupLayouts.forEachIndexed { group, bindGroupLayout ->
Expand Down Expand Up @@ -235,11 +237,11 @@ class CompiledShader(val program: GlProgram, val pipeline: PipelineBase, val bac

private fun mapUbo(group: Int, ubo: UniformBufferLayout) {
ubos.add(ubo)
val uniformLocations = uniformLocations[ubo.name]
mappings += if (uniformLocations != null) {
group to MappedUboCompat(ubo, uniformLocations, gl)
val uniformLocations = uniformLocations[ubo.name]!!
mappings += if (ubo.name !in compatUbos) {
group to MappedUbo(ubo, uniformLocations[0], gl)
} else {
group to MappedUbo(ubo, gl)
group to MappedUboCompat(ubo, uniformLocations, gl)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ open class GlslGenerator(val hints: Hints) : KslGenerator() {
}

protected open fun StringBuilder.generateUbos(stage: KslShaderStage, pipeline: PipelineBase) {
val ubos = stage.getUsedUbos()
val ubos = stage.getUsedUbos().sortedBy { it.scope.group }
if (ubos.isNotEmpty()) {
appendLine("// uniform buffer objects")
for (ubo in ubos) {
Expand All @@ -291,10 +291,7 @@ open class GlslGenerator(val hints: Hints) : KslGenerator() {
}

} else {
// if isShared is true, the underlying buffer is externally provided without the buffer layout
// being queried via OpenGL API -> use standardized std140 layout
val layoutPrefix = if (hints.alwaysGenerateStd140Layout || ubo.isShared) "layout(std140) " else ""
appendLine("${layoutPrefix}uniform ${ubo.name} {")
appendLine("layout(std140) uniform ${ubo.name} {")
ubo.uniforms.values
.filter { it.expressionType !is KslArrayType<*> || it.arraySize > 0 }
.forEach {
Expand Down Expand Up @@ -626,7 +623,6 @@ open class GlslGenerator(val hints: Hints) : KslGenerator() {

data class Hints(
val glslVersionStr: String,
val alwaysGenerateStd140Layout: Boolean = true,
val replaceUbosByPlainUniforms: Boolean = false
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface MappedUniform {
fun setUniform(bindGroupData: BindGroupData): Boolean
}

class MappedUbo(val ubo: UniformBufferLayout, val gl: GlApi) : MappedUniform {
class MappedUbo(val ubo: UniformBufferLayout, val uboIndex: Int, val gl: GlApi) : MappedUniform {
var uboBuffer: BufferResource? = null

override fun setUniform(bindGroupData: BindGroupData): Boolean {
Expand All @@ -22,7 +22,7 @@ class MappedUbo(val ubo: UniformBufferLayout, val gl: GlApi) : MappedUniform {
if (uboData.getAndClearDirtyFlag()) {
buffer.setData(uboData.buffer, gl.DYNAMIC_DRAW)
}
gl.bindBufferBase(gl.UNIFORM_BUFFER, ubo.bindingIndex, buffer.buffer)
gl.bindBufferBase(gl.UNIFORM_BUFFER, uboIndex, buffer.buffer)
true
} ?: false
}
Expand All @@ -43,9 +43,9 @@ class MappedUboCompat(val ubo: UniformBufferLayout, val locations: IntArray, val
}
} else {
when (it.type) {
GpuType.MAT3 -> 9
GpuType.MAT4 -> 16
else -> 1
GpuType.MAT3 -> 9
GpuType.MAT4 -> 16
else -> 1
}
}
add(Float32Buffer(bufferSize))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ package de.fabmax.kool.pipeline.deferred
import de.fabmax.kool.math.Vec4f
import de.fabmax.kool.modules.ksl.KslShaderListener
import de.fabmax.kool.modules.ksl.lang.*
import de.fabmax.kool.pipeline.ShaderBase
import de.fabmax.kool.pipeline.UniformBinding3f
import de.fabmax.kool.pipeline.UniformBinding4f
import de.fabmax.kool.pipeline.UniformBindingMat4f
import de.fabmax.kool.pipeline.*
import de.fabmax.kool.pipeline.drawqueue.DrawCommand

fun KslProgram.deferredCameraData(): DeferredCamData {
Expand All @@ -22,7 +19,7 @@ class DeferredCamData(program: KslProgram) : KslDataBlock, KslShaderListener {
val invViewMat: KslUniformMatrix<KslMat4, KslFloat4>
val viewport: KslUniformVector<KslFloat4, KslFloat1>

val camUbo = KslUniformBuffer("CameraUniforms", program, false).apply {
val camUbo = KslUniformBuffer("CameraUniforms", program, BindGroupScope.SCENE).apply {
projMat = uniformMat4("uProjMat")
invViewMat = uniformMat4("uInvViewMat")
viewport = uniformFloat4("uViewport")
Expand Down

0 comments on commit 98a7714

Please sign in to comment.