Skip to content

Commit

Permalink
add DepthSliceMaterial & shader (#306)
Browse files Browse the repository at this point in the history
add CameraComplexBuffers

update Material to allow for nullable binding group

add Model to set mesh primitives material

fix get_tile function logic in shader code
  • Loading branch information
LeHaine authored Feb 6, 2025
1 parent 48e925e commit 7107acf
Show file tree
Hide file tree
Showing 12 changed files with 395 additions and 21 deletions.
6 changes: 6 additions & 0 deletions core/src/commonMain/kotlin/com/littlekt/graphics/g3d/Model.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.littlekt.graphics.g3d

import com.littlekt.graphics.g3d.material.Material
import com.littlekt.graphics.g3d.skin.Animation
import com.littlekt.util.datastructure.fastForEach
import com.littlekt.util.datastructure.fastForEachWithIndex
Expand All @@ -15,6 +16,11 @@ import kotlin.time.Duration
open class Model : Node3D() {
val animations = mutableListOf<Animation>()

/** Set's material for each [MeshPrimitive] child. */
fun setMaterial(material: Material) {
forEachMeshPrimitive { it.material = material }
}

fun disableAllAnimations() {
enableAnimation(-1)
}
Expand Down
33 changes: 20 additions & 13 deletions core/src/commonMain/kotlin/com/littlekt/graphics/g3d/ModelBatch.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class ModelBatch(val device: Device) : Releasable {
depthFormat,
) ?: error("Unable to find pipeline for given instance!")
if (meshPrimitive.material.ready) {
bindGroupByMaterialId.getOrPut(meshPrimitive.material.id) {
bindGroupByMaterialId.getOrPutNotNull(meshPrimitive.material.id) {
meshPrimitive.material.createBindGroup(pipeline.shader)
}
}
Expand Down Expand Up @@ -138,12 +138,11 @@ class ModelBatch(val device: Device) : Releasable {
if (!pipelines.contains(pipeline)) {
pipelines += pipeline
}
// todo - pool lists?
primitivesByPipeline
.getOrPut(pipeline) { listPool.alloc() }
.apply { add(meshPrimitive) }

bindGroupByMaterialId.getOrPut(meshPrimitive.material.id) {
bindGroupByMaterialId.getOrPutNotNull(meshPrimitive.material.id) {
meshPrimitive.material.createBindGroup(pipeline.shader)
}
}
Expand Down Expand Up @@ -176,11 +175,7 @@ class ModelBatch(val device: Device) : Releasable {
renderPassEncoder.setPipeline(pipeline.renderPipeline)
primitives.fastForEach primitives@{ primitive ->
if (primitive.visibleInstanceCount <= 0) return@primitives
val materialBindGroup =
bindGroupByMaterialId[primitive.material.id]
?: error(
"Material (${primitive.material.id}) bind groups could not be found!"
)
val materialBindGroup = bindGroupByMaterialId[primitive.material.id]

if (primitive.material.skinned) {
val skinBindGroup =
Expand All @@ -195,11 +190,13 @@ class ModelBatch(val device: Device) : Releasable {
}
if (lastMaterialSet != primitive.material.id) {
lastMaterialSet = primitive.material.id
pipeline.shader.setBindGroup(
renderPassEncoder,
materialBindGroup,
BindingUsage.MATERIAL,
)
materialBindGroup?.let {
pipeline.shader.setBindGroup(
renderPassEncoder,
it,
BindingUsage.MATERIAL,
)
}
primitive.material.update()
}
primitive.writeInstanceDataToBuffer()
Expand Down Expand Up @@ -261,6 +258,16 @@ class ModelBatch(val device: Device) : Releasable {
bindGroupByMaterialId.values.forEach { it.release() }
}

private inline fun <K, V> MutableMap<K, V>.getOrPutNotNull(key: K, defaultValue: () -> V?): V? {
val value = get(key)
return if (value == null) {
val answer = defaultValue()
answer?.let { put(key, answer) }
} else {
value
}
}

companion object {
private val logger = Logger<ModelBatch>()
private const val INSTANCED_STAT_NAME = "ModelBatch instanced count"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.littlekt.graphics.g3d.material

import com.littlekt.graphics.Color
import com.littlekt.graphics.Texture
import com.littlekt.graphics.shader.Shader
import com.littlekt.graphics.webgpu.*
import com.littlekt.resources.Textures

/**
* @author Colton Daily
* @date 2/5/2025
*/
class DepthSliceMaterial(val device: Device) : Material() {
override val baseColorTexture: Texture = Textures.textureWhite
override val baseColorFactor: Color = Color.WHITE
override val transparent: Boolean = false
override val doubleSided: Boolean = false
override val alphaCutoff: Float = 0f
override val castShadows: Boolean = true
override val depthWrite: Boolean = true
override val depthCompareFunction: CompareFunction = CompareFunction.LESS
override val skinned: Boolean = false

override fun createBindGroup(shader: Shader): BindGroup? = null

override fun update() = Unit

override fun release() = Unit

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as UnlitMaterial

if (baseColorTexture != other.baseColorTexture) return false
if (baseColorFactor != other.baseColorFactor) return false
if (transparent != other.transparent) return false
if (doubleSided != other.doubleSided) return false
if (alphaCutoff != other.alphaCutoff) return false
if (castShadows != other.castShadows) return false
if (depthWrite != other.depthWrite) return false
if (depthCompareFunction != other.depthCompareFunction) return false

return true
}

override fun hashCode(): Int {
var result = baseColorTexture.hashCode()
result = 31 * result + baseColorFactor.hashCode()
result = 31 * result + transparent.hashCode()
result = 31 * result + doubleSided.hashCode()
result = 31 * result + alphaCutoff.hashCode()
result = 31 * result + castShadows.hashCode()
result = 31 * result + depthWrite.hashCode()
result = 31 * result + depthCompareFunction.hashCode()
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ abstract class Material : Releasable {
result
}

abstract fun createBindGroup(shader: Shader): BindGroup
abstract fun createBindGroup(shader: Shader): BindGroup?

abstract fun update()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.littlekt.graphics.g3d.shader

import com.littlekt.graphics.VertexAttribute
import com.littlekt.graphics.g3d.util.shader.*
import com.littlekt.graphics.shader.Shader
import com.littlekt.graphics.util.BindingUsage
import com.littlekt.graphics.webgpu.*

/**
* @author Colton Daily
* @date 2/5/2025
*/
class DepthSliceShader(
device: Device,
layout: List<VertexAttribute>,
skinned: Boolean,
vertexEntryPoint: String = "vs_main",
fragmentEntryPoint: String = "fs_main",
vertexSrc: String = buildCommonShader {
vertex {
vertexInput(layout)
vertexOutput(layout)
cameraComplex(0, 0)
models(1, 0)
main(
layout,
cameraViewProjCombined = false,
skinned = skinned,
skinGroup = 2,
entryPoint = vertexEntryPoint,
)
}
},
fragmentSrc: String = buildCommonShader {
fragment { from(DepthSliceMaterialBuilder()) { main() } }
},
bindGroupLayoutUsageLayout: List<BindingUsage> = run {
val usages = mutableListOf(BindingUsage.CAMERA, BindingUsage.MODEL)
if (skinned) {
usages += BindingUsage.SKIN
}
usages
},
bindGroupLayout: Map<BindingUsage, BindGroupLayoutDescriptor> = run {
val layouts =
mutableMapOf(
BindingUsage.MODEL to
BindGroupLayoutDescriptor(
listOf(
// model
BindGroupLayoutEntry(
0,
ShaderStage.VERTEX,
BufferBindingLayout(BufferBindingType.READ_ONLY_STORAGE),
)
)
)
)

if (skinned) {
layouts[BindingUsage.SKIN] =
BindGroupLayoutDescriptor(
listOf(
// joint transforms
BindGroupLayoutEntry(
0,
ShaderStage.VERTEX,
BufferBindingLayout(BufferBindingType.READ_ONLY_STORAGE),
),
// inverse blend matrices
BindGroupLayoutEntry(
1,
ShaderStage.VERTEX,
BufferBindingLayout(BufferBindingType.READ_ONLY_STORAGE),
),
)
)
}
layouts
},
) :
Shader(
device = device,
src = "$vertexSrc\n$fragmentSrc",
bindGroupLayoutUsageLayout = bindGroupLayoutUsageLayout,
layout = bindGroupLayout,
vertexEntryPoint = vertexEntryPoint,
fragmentEntryPoint = fragmentEntryPoint,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.littlekt.graphics.g3d.util

import com.littlekt.file.FloatBuffer
import com.littlekt.graphics.Camera
import com.littlekt.graphics.util.CameraBuffersViaCamera
import com.littlekt.graphics.webgpu.*
import com.littlekt.util.seconds
import kotlin.time.Duration

/**
* @author Colton Daily
* @date 2/5/2025
*/
class CameraComplexBuffers(val device: Device) : CameraBuffersViaCamera {
private val camFloatBuffer = FloatBuffer(BUFFER_SIZE) // 56 floats required

/**
* The [GPUBuffer] that holds the camera data.
*
* @see update
*/
val cameraUniformBuffer =
device.createGPUFloatBuffer(
"camera",
camFloatBuffer,
BufferUsage.UNIFORM or BufferUsage.COPY_DST,
)
override val cameraDynamicSize: Int = 1

/** The [BufferBinding] for [cameraUniformBufferBinding]. */
override val cameraUniformBufferBinding = BufferBinding(cameraUniformBuffer)

override val bindGroupLayout: BindGroupLayout =
device.createBindGroupLayout(
BindGroupLayoutDescriptor(
listOf(
// camera
BindGroupLayoutEntry(
0,
ShaderStage.VERTEX or ShaderStage.FRAGMENT or ShaderStage.COMPUTE,
BufferBindingLayout(),
)
)
)
)

/** The camera uniform bind group. */
override val bindGroup =
device.createBindGroup(
BindGroupDescriptor(
bindGroupLayout,
listOf(BindGroupEntry(0, cameraUniformBufferBinding)),
label = "CameraLightBuffers Bind Group",
)
)

override fun update(camera: Camera, dt: Duration, dynamicOffset: Long) {
camFloatBuffer.put(camera.projection.data, dstOffset = 0)
camFloatBuffer.put(camera.invProj.data)
camFloatBuffer.put(camera.view.data)
camFloatBuffer.put(camera.position.fields)
camFloatBuffer.put(dt.seconds)
camFloatBuffer.put(camera.virtualWidth)
camFloatBuffer.put(camera.virtualHeight)
camFloatBuffer.put(camera.near)
camFloatBuffer.put(camera.far)
device.queue.writeBuffer(cameraUniformBuffer, camFloatBuffer)
}

override fun release() {
super.release()
cameraUniformBuffer.release()
}

companion object {
/** Size in total floats (NOT BYTE SIZE). */
const val BUFFER_SIZE = 56
}
}
Loading

0 comments on commit 7107acf

Please sign in to comment.