Skip to content

Commit 0dc50c0

Browse files
authored
Rework Shader API (#307)
* add initial shader builder dsl * continue work on shader api * flesh out more of new Shader api * update shader api to include compute shader * add MainShaderBlock and MainShaderBlockBuilder * add a shaderBlock dsl builder * add include shaderblockbuilder functionality * update ShaderCode to format blocks when generating source * start implementing standard shaders & blocks with new api * add ShaderBlockBindGroup * update ShaderCode to allow for tracking bind groups * continue work on building shader code api * update Shader to use a ShaderCode * add PBR functions * start adding shader struct types * update ShaderStruct to calculate offset, alignment, and size * add more shader struct offset tests * add shader bindings and bind groups * rework more of the shader builder * twaek src generating for shader code * fix extending shadercode with duplicate mains * fix up extending & including shaders * fix shader rules replacement string logic to format better * tweak after instert rules * add shader bind group builder & bindings to shader code * update shader block missing appendLine * add multiple bindgroup shader test * add shader binding group test for textures and samplers * add generating bindgrouplayout descriptors and bindgroup layout entries * clean up and start rewokring CommonShaderBlocks and StandardShaders * add more shader code builder tests * tweak marker regex to handle anything in between % symmbols add more shader code tests * uncomment the rest of the standrd common blocks & shaders and add test vertex attribute layout to tests * update shader code main entry points to be main shader blcok specific ('vs_', 'fs_', 'cmp_') * rework ShaderBlocks to require a name/id to be unique for deduplication when extending a shader update ShaderCode to dedupe bind groups using the same group id & combine them * rework 3d pipeline to use new shader code shaders * fix SpriteBatch & SpriteCache shaders and usages * remove unusuded old shader builder code * add render function to ModelBatch to override current material for mesh primtiive shader: update setBindGroup to handle unused bindings (such as Material) tweak UnlitMaterail to handle create bindgrouplayout for null usages * examples: add a DepthSlice and Depth materials for debugging
1 parent 2da77a5 commit 0dc50c0

File tree

75 files changed

+4143
-1896
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+4143
-1896
lines changed

core/src/commonMain/kotlin/com/littlekt/graphics/g2d/SpriteBatch.kt

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,9 @@ class SpriteBatch(
109109
private val renderPipelineByBlendState: MutableMap<RenderInfo, RenderPipeline> =
110110
mutableMapOf(
111111
RenderInfo(shader, blendState) to
112-
device.createRenderPipeline(
113-
createRenderPipelineDescriptor(RenderInfo(shader, blendState))
114-
)
112+
device.createRenderPipeline(
113+
createRenderPipelineDescriptor(RenderInfo(shader, blendState))
114+
)
115115
)
116116

117117
private val spriteIndices = mutableMapOf(lastMeshIdx to 0)
@@ -616,7 +616,7 @@ class SpriteBatch(
616616
lastDynamicOffsetIndex * device.limits.minUniformBufferOffsetAlignment
617617
shader.setBindGroup(
618618
renderPassEncoder,
619-
cameraBuffers.bindGroup,
619+
cameraBuffers.getOrCreateBindGroup(shader),
620620
BindingUsage.CAMERA,
621621
lastDynamicMeshOffsets,
622622
)
@@ -727,11 +727,7 @@ class SpriteBatch(
727727
private fun createRenderPipelineDescriptor(renderInfo: RenderInfo): RenderPipelineDescriptor {
728728
val (shader, blendState) = renderInfo
729729
return RenderPipelineDescriptor(
730-
layout =
731-
shader.getOrCreatePipelineLayout { bindingUsage ->
732-
if (bindingUsage == BindingUsage.CAMERA) cameraBuffers.bindGroupLayout
733-
else error("Unsupported $bindingUsage in SpriteBatch")
734-
},
730+
layout = shader.getOrCreatePipelineLayout(),
735731
vertex =
736732
VertexState(
737733
module = shader.shaderModule,

core/src/commonMain/kotlin/com/littlekt/graphics/g2d/SpriteBatchShader.kt

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.littlekt.graphics.g2d
33
import com.littlekt.graphics.shader.Shader
44
import com.littlekt.graphics.util.BindingUsage
55
import com.littlekt.graphics.webgpu.*
6+
import com.littlekt.util.align
67

78
/**
89
* The default [Shader] that is used in [SpriteBatch].
@@ -56,41 +57,30 @@ class SpriteBatchShader(device: Device) :
5657
bindGroupLayoutUsageLayout = listOf(BindingUsage.CAMERA, BindingUsage.TEXTURE),
5758
layout =
5859
mapOf(
60+
BindingUsage.CAMERA to
61+
BindGroupLayoutDescriptor(
62+
listOf(
63+
BindGroupLayoutEntry(
64+
0,
65+
ShaderStage.VERTEX,
66+
BufferBindingLayout(
67+
hasDynamicOffset = true,
68+
minBindingSize =
69+
(Float.SIZE_BYTES * 16)
70+
.align(device.limits.minUniformBufferOffsetAlignment)
71+
.toLong(),
72+
),
73+
)
74+
),
75+
label = "SpriteBatch Camera BindGroupLayoutDescriptor",
76+
),
5977
BindingUsage.TEXTURE to
6078
BindGroupLayoutDescriptor(
6179
listOf(
6280
BindGroupLayoutEntry(0, ShaderStage.FRAGMENT, TextureBindingLayout()),
6381
BindGroupLayoutEntry(1, ShaderStage.FRAGMENT, SamplerBindingLayout()),
6482
),
6583
label = "SpriteBatchShader texture BindGroupLayoutDescriptor",
66-
)
84+
),
6785
),
68-
) {
69-
70-
override fun createBindGroup(
71-
usage: BindingUsage,
72-
vararg args: IntoBindingResource,
73-
): BindGroup? {
74-
return when (usage) {
75-
BindingUsage.TEXTURE -> {
76-
val view =
77-
args[0] as? TextureView
78-
?: error(
79-
"SpriteBatchShader requires view, sampler for BindingUsage.TEXTURE"
80-
)
81-
val sampler =
82-
args[1] as? Sampler
83-
?: error(
84-
"SpriteBatchShader requires view, sampler for BindingUsage.TEXTURE"
85-
)
86-
device.createBindGroup(
87-
BindGroupDescriptor(
88-
getBindGroupLayoutByUsage(BindingUsage.TEXTURE),
89-
listOf(BindGroupEntry(0, view), BindGroupEntry(1, sampler)),
90-
)
91-
)
92-
}
93-
else -> null
94-
}
95-
}
96-
}
86+
)

core/src/commonMain/kotlin/com/littlekt/graphics/g2d/SpriteCache.kt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ class SpriteCache(
226226
lastBindGroupsSet = textureBindGroup
227227
shader.setBindGroup(
228228
encoder,
229-
cameraBuffers.bindGroup,
229+
cameraBuffers.getOrCreateBindGroup(shader),
230230
BindingUsage.CAMERA,
231231
lastDynamicMeshOffsets,
232232
)
@@ -459,11 +459,7 @@ class SpriteCache(
459459
blendState: BlendState,
460460
): RenderPipelineDescriptor {
461461
return RenderPipelineDescriptor(
462-
layout =
463-
shader.getOrCreatePipelineLayout { bindingUsage ->
464-
if (bindingUsage == BindingUsage.CAMERA) cameraBuffers.bindGroupLayout
465-
else error("Unsupported $bindingUsage in SpriteCache")
466-
},
462+
layout = shader.getOrCreatePipelineLayout(),
467463
vertex =
468464
VertexState(
469465
module = shader.shaderModule,

core/src/commonMain/kotlin/com/littlekt/graphics/g2d/SpriteCacheShader.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.littlekt.graphics.shader.Shader
55
import com.littlekt.graphics.util.BindingUsage
66
import com.littlekt.graphics.webgpu.*
77
import com.littlekt.log.Logger
8+
import com.littlekt.util.align
89
import kotlin.math.min
910

1011
/**
@@ -144,6 +145,23 @@ class SpriteCacheShader(device: Device, staticSize: Int, dynamicSize: Int) :
144145
listOf(BindingUsage.CAMERA, SPRITE_STORAGE, BindingUsage.TEXTURE),
145146
layout =
146147
mapOf(
148+
BindingUsage.CAMERA to
149+
BindGroupLayoutDescriptor(
150+
listOf(
151+
BindGroupLayoutEntry(
152+
0,
153+
ShaderStage.VERTEX,
154+
BufferBindingLayout(
155+
hasDynamicOffset = true,
156+
minBindingSize =
157+
(Float.SIZE_BYTES * 16)
158+
.align(device.limits.minUniformBufferOffsetAlignment)
159+
.toLong(),
160+
),
161+
)
162+
),
163+
label = "SpriteCache Camera BindGroupLayoutDescriptor",
164+
),
147165
SPRITE_STORAGE to
148166
BindGroupLayoutDescriptor(
149167
listOf(

core/src/commonMain/kotlin/com/littlekt/graphics/g2d/util/CameraSpriteBuffers.kt

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.littlekt.graphics.g2d.util
22

33
import com.littlekt.file.FloatBuffer
4+
import com.littlekt.graphics.shader.Shader
5+
import com.littlekt.graphics.util.BindingUsage
46
import com.littlekt.graphics.util.CameraBuffersViaMatrix
57
import com.littlekt.graphics.webgpu.*
68
import com.littlekt.math.Mat4
@@ -20,7 +22,7 @@ class CameraSpriteBuffers(val device: Device, override val cameraDynamicSize: In
2022
}
2123

2224
/** The [GPUBuffer] that holds the matrix data. */
23-
val cameraUniformBuffer = run {
25+
private val cameraUniformBuffer = run {
2426
val buffer =
2527
device.createBuffer(
2628
BufferDescriptor(
@@ -47,33 +49,17 @@ class CameraSpriteBuffers(val device: Device, override val cameraDynamicSize: In
4749
.align(device.limits.minUniformBufferOffsetAlignment)
4850
.toLong(),
4951
)
50-
override val bindGroupLayout: BindGroupLayout =
51-
device.createBindGroupLayout(
52-
BindGroupLayoutDescriptor(
53-
listOf(
54-
BindGroupLayoutEntry(
55-
0,
56-
ShaderStage.VERTEX,
57-
BufferBindingLayout(
58-
hasDynamicOffset = true,
59-
minBindingSize =
60-
(Float.SIZE_BYTES * 16)
61-
.align(device.limits.minUniformBufferOffsetAlignment)
62-
.toLong(),
63-
),
64-
)
65-
),
66-
label = "CameraSpriteBuffers viewProj BindGroupLayout",
67-
)
68-
)
69-
override val bindGroup: BindGroup =
70-
device.createBindGroup(
71-
BindGroupDescriptor(
72-
bindGroupLayout,
73-
listOf(BindGroupEntry(0, cameraUniformBufferBinding)),
74-
label = "CameraSpriteBuffers viewProj BindGroup",
75-
)
76-
)
52+
53+
private var bindGroups = mutableMapOf<Int, BindGroup>()
54+
override val bindingUsage: BindingUsage = BindingUsage.CAMERA
55+
56+
override fun getOrCreateBindGroup(shader: Shader): BindGroup {
57+
return bindGroups[shader.id]
58+
?: shader.createBindGroup(bindingUsage, cameraUniformBufferBinding)?.also {
59+
bindGroups[shader.id] = it
60+
}
61+
?: error("Unable to create bind group for shader: ${shader.id}")
62+
}
7763

7864
override fun update(viewProj: Mat4, dt: Duration, dynamicOffset: Long) {
7965
device.queue.writeBuffer(
@@ -84,7 +70,7 @@ class CameraSpriteBuffers(val device: Device, override val cameraDynamicSize: In
8470
}
8571

8672
override fun release() {
87-
super.release()
73+
bindGroups.values.forEach { it.release() }
8874
cameraUniformBuffer.release()
8975
}
9076
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package com.littlekt.graphics.g3d
22

3+
import com.littlekt.graphics.g3d.util.CameraComplexBuffers
34
import com.littlekt.graphics.g3d.util.CameraLightBuffers
4-
import com.littlekt.graphics.g3d.util.CameraSimpleBuffers
55
import com.littlekt.graphics.g3d.util.LightBuffer
66
import com.littlekt.graphics.webgpu.Device
77

88
/** @return a new [PBREnvironment] that uses an underlying [CameraLightBuffers]. */
99
fun PBREnvironment(device: Device, maxLightCount: Int = 1024): PBREnvironment =
1010
PBREnvironment(CameraLightBuffers(device, LightBuffer(device, maxLightCount)))
1111

12-
/** @return a new [Environment] that uses an underlying [CameraSimpleBuffers]. */
13-
fun UnlitEnvironment(device: Device): Environment = Environment(CameraSimpleBuffers(device))
12+
/** @return a new [Environment] that uses an underlying [CameraComplexBuffers]. */
13+
fun UnlitEnvironment(device: Device): Environment = Environment(CameraComplexBuffers(device))

core/src/commonMain/kotlin/com/littlekt/graphics/g3d/ModelBatch.kt

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,30 @@ class ModelBatch(val device: Device) : Releasable {
117117
node.forEachMeshPrimitive { render(it, environment) }
118118
}
119119

120+
/**
121+
* Adds a [Node3D] to be drawn on the next [flush] using the specified [Environment] with the
122+
* given material override.
123+
*/
124+
fun render(node: Node3D, material: Material, environment: Environment) {
125+
node.forEachMeshPrimitive { render(it, material, environment) }
126+
}
127+
128+
/**
129+
* Render any mesh primitives with the given node using the given [Camera] to attempt any
130+
* frustum culling.
131+
*/
120132
fun render(node: Node3D, camera: Camera, environment: Environment) {
121133
node.forEachMeshPrimitive(camera) { render(it, environment) }
122134
}
123135

136+
/**
137+
* Render any mesh primitives with the given node using the given [Camera] to attempt any
138+
* frustum culling with an material override.
139+
*/
140+
fun render(node: Node3D, camera: Camera, material: Material, environment: Environment) {
141+
node.forEachMeshPrimitive(camera) { render(it, material, environment) }
142+
}
143+
124144
/** Adds a [MeshPrimitive] to be drawn on the next [flush] using the specified [Environment]. */
125145
fun render(meshPrimitive: MeshPrimitive, environment: Environment) {
126146
val pipeline =
@@ -152,6 +172,37 @@ class ModelBatch(val device: Device) : Releasable {
152172
}
153173
}
154174

175+
/** Render a [MeshPrimitive] by overriding the attached material with the given material. */
176+
fun render(meshPrimitive: MeshPrimitive, material: Material, environment: Environment) {
177+
val pipeline =
178+
pipelineProviders[material::class]?.getMaterialPipeline(
179+
device,
180+
material,
181+
environment,
182+
meshPrimitive.mesh.geometry.layout,
183+
meshPrimitive.topology,
184+
meshPrimitive.stripIndexFormat,
185+
colorFormat,
186+
depthFormat,
187+
) ?: error("Unable to find pipeline for given instance!")
188+
if (material.ready) {
189+
if (!pipelines.contains(pipeline)) {
190+
pipelines += pipeline
191+
}
192+
primitivesByPipeline
193+
.getOrPut(pipeline) { listPool.alloc() }
194+
.apply { add(meshPrimitive) }
195+
196+
bindGroupByMaterialId.getOrPutNotNull(material.id) {
197+
material.createBindGroup(pipeline.shader)
198+
}
199+
}
200+
val skin = meshPrimitive.skin
201+
if (material.skinned && skin != null) {
202+
bindGroupBySkinId.getOrPut(skin.id) { skin.createBindGroup(pipeline.shader) }
203+
}
204+
}
205+
155206
fun flush(renderPassEncoder: RenderPassEncoder, camera: Camera, dt: Duration) {
156207
sorter.sort(pipelines)
157208
var lastEnvironmentSet: Environment? = null
@@ -166,8 +217,8 @@ class ModelBatch(val device: Device) : Releasable {
166217
lastEnvironmentSet = pipeline.environment
167218
pipeline.shader.setBindGroup(
168219
renderPassEncoder,
169-
pipeline.environment.buffers.bindGroup,
170-
BindingUsage.CAMERA,
220+
pipeline.environment.buffers.getOrCreateBindGroup(pipeline.shader),
221+
pipeline.environment.buffers.bindingUsage,
171222
)
172223
}
173224
val primitives = primitivesByPipeline[pipeline]

0 commit comments

Comments
 (0)