From 98a7714cc711c4eb82f8d757b13e41d5873cae4c Mon Sep 17 00:00:00 2001 From: fabmax Date: Sun, 14 Jan 2024 15:36:12 +0100 Subject: [PATCH] use scoped bind groups --- .../de/fabmax/kool/modules/ksl/KslShader.kt | 24 ++++++-- .../kool/modules/ksl/blocks/CameraData.kt | 2 +- .../modules/ksl/blocks/LitMaterialBlock.kt | 4 +- .../kool/modules/ksl/lang/KslProgram.kt | 12 ++-- .../kool/modules/ksl/lang/KslUniformBuffer.kt | 4 +- .../fabmax/kool/pipeline/BindGroupLayout.kt | 33 +++++------ .../kotlin/de/fabmax/kool/pipeline/Uniform.kt | 6 ++ .../pipeline/backend/gl/CompiledShader.kt | 58 ++++++++++--------- .../kool/pipeline/backend/gl/GlslGenerator.kt | 8 +-- .../kool/pipeline/backend/gl/MappedUniform.kt | 10 ++-- .../kool/pipeline/deferred/DeferredCamData.kt | 7 +-- 11 files changed, 92 insertions(+), 76 deletions(-) diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/KslShader.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/KslShader.kt index 3157509a9..3ed312a3f 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/KslShader.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/KslShader.kt @@ -145,15 +145,17 @@ open class KslShader private constructor(val program: KslProgram) : Shader(progr } fun KslProgram.makeBindGroupLayout(): List { - 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) @@ -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) } @@ -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) } diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/blocks/CameraData.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/blocks/CameraData.kt index c1964ce69..77babb46c 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/blocks/CameraData.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/blocks/CameraData.kt @@ -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) diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/blocks/LitMaterialBlock.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/blocks/LitMaterialBlock.kt index 76a91bf10..f736cbdef 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/blocks/LitMaterialBlock.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/blocks/LitMaterialBlock.kt @@ -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") @@ -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) diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/lang/KslProgram.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/lang/KslProgram.kt index a350ff001..00fa24677 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/lang/KslProgram.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/lang/KslProgram.kt @@ -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>() val uniformStorage = mutableMapOf>() @@ -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) } } diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/lang/KslUniformBuffer.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/lang/KslUniformBuffer.kt index 0db7d9f75..0521da89f 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/lang/KslUniformBuffer.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/modules/ksl/lang/KslUniformBuffer.kt @@ -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>() diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/BindGroupLayout.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/BindGroupLayout.kt index b6306fb22..d2b014c30 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/BindGroupLayout.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/BindGroupLayout.kt @@ -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) { +class BindGroupLayout(val scope: BindGroupScope, val bindings: List) { + 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() val textures = mutableListOf() val storage = mutableListOf() - fun create(group: Int): BindGroupLayout { - val groupItems = mutableListOf() - // 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, diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/Uniform.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/Uniform.kt index 1083b603c..d9578e658 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/Uniform.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/Uniform.kt @@ -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 "" } diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/backend/gl/CompiledShader.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/backend/gl/CompiledShader.kt index 83c8a8a38..6349ee57d 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/backend/gl/CompiledShader.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/backend/gl/CompiledShader.kt @@ -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 @@ -11,7 +10,9 @@ 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() private val instanceAttributes = mutableMapOf() @@ -19,10 +20,7 @@ class CompiledShader(val program: GlProgram, val pipeline: PipelineBase, val bac private val instances = mutableMapOf() private val computeInstances = mutableMapOf() - private val ctx: KoolContext = backend.ctx - private val gl: GlApi = backend.gl - - private val pipelineInfo = PipelineInfo(pipeline) + private val compatUbos = mutableSetOf() init { (pipeline as? Pipeline)?.apply { @@ -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 -> { @@ -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++) } } } @@ -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() - protected val textures1d = mutableListOf() - protected val textures2d = mutableListOf() - protected val textures3d = mutableListOf() - protected val texturesCube = mutableListOf() - protected val storage1d = mutableListOf() - protected val storage2d = mutableListOf() - protected val storage3d = mutableListOf() + private val ubos = mutableListOf() + private val textures1d = mutableListOf() + private val textures2d = mutableListOf() + private val textures3d = mutableListOf() + private val texturesCube = mutableListOf() + private val storage1d = mutableListOf() + private val storage2d = mutableListOf() + private val storage3d = mutableListOf() - protected val mappings = mutableListOf>() - protected var uboBuffers = mutableListOf() - protected var nextTexUnit = gl.TEXTURE0 + private val mappings = mutableListOf>() + private var uboBuffers = mutableListOf() + private var nextTexUnit = gl.TEXTURE0 init { pipelineInstance.bindGroupLayouts.forEachIndexed { group, bindGroupLayout -> @@ -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) } } diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/backend/gl/GlslGenerator.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/backend/gl/GlslGenerator.kt index 443e42b8d..950135d10 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/backend/gl/GlslGenerator.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/backend/gl/GlslGenerator.kt @@ -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) { @@ -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 { @@ -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 ) diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/backend/gl/MappedUniform.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/backend/gl/MappedUniform.kt index b45ace0cf..26627d550 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/backend/gl/MappedUniform.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/backend/gl/MappedUniform.kt @@ -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 { @@ -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 } @@ -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)) diff --git a/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/deferred/DeferredCamData.kt b/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/deferred/DeferredCamData.kt index 606321a92..850938e90 100644 --- a/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/deferred/DeferredCamData.kt +++ b/kool-core/src/commonMain/kotlin/de/fabmax/kool/pipeline/deferred/DeferredCamData.kt @@ -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 { @@ -22,7 +19,7 @@ class DeferredCamData(program: KslProgram) : KslDataBlock, KslShaderListener { val invViewMat: KslUniformMatrix val viewport: KslUniformVector - val camUbo = KslUniformBuffer("CameraUniforms", program, false).apply { + val camUbo = KslUniformBuffer("CameraUniforms", program, BindGroupScope.SCENE).apply { projMat = uniformMat4("uProjMat") invViewMat = uniformMat4("uInvViewMat") viewport = uniformFloat4("uViewport")