Skip to content

Commit 5b4452e

Browse files
committed
feat: Histogram support in graph node
feat: Velocity histogram
1 parent 74a5b05 commit 5b4452e

12 files changed

Lines changed: 246 additions & 82 deletions

File tree

particles-kool/src/commonMain/kotlin/me/dvyy/particles/Modules.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import de.fabmax.kool.KoolContext
44
import me.dvyy.particles.clustering.ParticleClustering
55
import me.dvyy.particles.compute.ConvertParticlesShader
66
import me.dvyy.particles.compute.ParticleBuffers
7+
import me.dvyy.particles.compute.data.VelocitiesDataShader
78
import me.dvyy.particles.compute.partitioning.GPUSort
89
import me.dvyy.particles.compute.partitioning.OffsetsShader
910
import me.dvyy.particles.compute.simulation.FieldsMultiPasses
@@ -45,6 +46,7 @@ fun shadersModule() = module {
4546
singleOf(::FieldsShader)
4647
singleOf(::VerletHalfStepShader)
4748
singleOf(::FieldsMultiPasses)
49+
singleOf(::VelocitiesDataShader)
4850
}
4951

5052
fun sceneModule() = module {

particles-kool/src/commonMain/kotlin/me/dvyy/particles/ParticlesScene.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.update
88
import me.dvyy.particles.clustering.ParticleClustering
99
import me.dvyy.particles.compute.ConvertParticlesShader
1010
import me.dvyy.particles.compute.ParticleBuffers
11+
import me.dvyy.particles.compute.data.VelocitiesDataShader
1112
import me.dvyy.particles.compute.partitioning.GPUSort
1213
import me.dvyy.particles.compute.partitioning.OffsetsShader
1314
import me.dvyy.particles.compute.partitioning.ReorderBuffersShader
@@ -27,6 +28,7 @@ class ParticlesScene(
2728
val cameraManager: CameraManager,
2829
val particlesMesh: ParticlesMesh,
2930
val offsetsShader: OffsetsShader,
31+
val velocitiesDataShader: VelocitiesDataShader,
3032
// val reorderBuffersShader: ReorderBuffersShader,
3133
val convertShader: ConvertParticlesShader,
3234
val fieldsShader: FieldsMultiPasses,
@@ -40,7 +42,6 @@ class ParticlesScene(
4042

4143
// === COMPUTE ===
4244
val computePass = ComputePass("Particles Compute")
43-
4445
gpuSort.addResetShader(computePass) // Reset keys and indices based on grid cell particle is in
4546
gpuSort.addSortingShader(configRepo.count, buffers = buffers, computePass = computePass) // Sort by grid cells
4647
ReorderBuffersShader(
@@ -59,8 +60,12 @@ class ParticlesScene(
5960
val fieldsPasses = fieldsShader.addTo(computePass) // Run force computations based on particle interactions
6061
convertShader.addTo(computePass) // Convert particles to different types as needed
6162

63+
// == DATA COLLECTION ==
64+
velocitiesDataShader.addTo(computePass)
65+
6266
addComputePass(computePass)
6367

68+
6469
// === RENDERING ===
6570
cameraManager.manageCameraFor(this)
6671
addNode(particlesMesh.mesh) // Render particles as instanced mesh
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package me.dvyy.particles.compute.data
2+
3+
import de.fabmax.kool.math.Vec3i
4+
import de.fabmax.kool.modules.ksl.KslComputeShader
5+
import de.fabmax.kool.modules.ksl.lang.*
6+
import de.fabmax.kool.pipeline.ComputePass
7+
import me.dvyy.particles.compute.ParticleBuffers
8+
import me.dvyy.particles.compute.helpers.ResetIntsShader
9+
import me.dvyy.particles.compute.partitioning.WORK_GROUP_SIZE
10+
import me.dvyy.particles.helpers.Buffers
11+
12+
class VelocitiesDataShader(
13+
val buffers: ParticleBuffers,
14+
) {
15+
val numBuckets = 64
16+
val reduce = KslComputeShader("Velocities Data") {
17+
computeStage(WORK_GROUP_SIZE) {
18+
val inputs = storage<KslFloat4>("inputs")
19+
val buckets = storage<KslInt1>("buckets")
20+
val numBuckets = uniformInt1("numBuckets")
21+
val maxVelocity = uniformFloat1("maxVelocity")
22+
23+
main {
24+
val id = int1Var(inGlobalInvocationId.x.toInt1())
25+
val velocity = float3Var(inputs[id].xyz)
26+
val length = float1Var(length(velocity))
27+
val bucket = (length / maxVelocity * numBuckets.toFloat1()).toInt1()
28+
//FIXME int1Var is needed since atomicAdd doesn't get called otherwise.
29+
// Report to kool-engine.
30+
int1Var(buckets.atomicAdd(bucket, 1.const))
31+
}
32+
}
33+
}
34+
35+
val buckets = Buffers.integers(numBuckets)
36+
37+
fun addTo(pass: ComputePass) {
38+
println("adding velocities data shader")
39+
reduce.apply {
40+
storage("inputs", buffers.velocitiesBuffer)
41+
storage("buckets", buckets)
42+
uniform1i("numBuckets", numBuckets)
43+
}
44+
pass.addTask(ResetIntsShader(buckets), Vec3i(1, 1, 1))
45+
pass.addTask(reduce, buffers.configRepo.numGroups).onBeforeDispatch {
46+
buffers.configRepo.whenDirty {
47+
reduce.uniform1f("maxVelocity").set(buffers.configRepo.config.value.simulation.maxVelocity.toFloat())
48+
}
49+
}
50+
}
51+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package me.dvyy.particles.compute.helpers
2+
3+
import de.fabmax.kool.modules.ksl.KslComputeShader
4+
import de.fabmax.kool.modules.ksl.lang.KslInt1
5+
import de.fabmax.kool.modules.ksl.lang.toInt1
6+
import de.fabmax.kool.modules.ksl.lang.x
7+
import de.fabmax.kool.pipeline.GpuBuffer
8+
import me.dvyy.particles.compute.partitioning.WORK_GROUP_SIZE
9+
10+
fun ResetIntsShader(
11+
buffer: GpuBuffer,
12+
) = KslComputeShader("ResetBuffer") {
13+
computeStage(WORK_GROUP_SIZE) {
14+
val reset = storage<KslInt1>("reset")
15+
main {
16+
val id = int1Var(inGlobalInvocationId.x.toInt1())
17+
reset[id] = 0.const
18+
}
19+
}
20+
}.apply {
21+
storage("reset", buffer)
22+
}

particles-kool/src/commonMain/kotlin/me/dvyy/particles/compute/partitioning/ReorderBuffers.kt

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,14 @@ class ReorderBuffersShader(
3535
val numValues = uniformInt1("numValues")
3636
val postSortIndices = storage<KslInt1>("indices")
3737
val storageToSort = buffersToSort.mapIndexed { id, buffer ->
38-
storage(buffer, "storage_$id")
38+
buffer.bindNumeric("storage_$id")
3939
}
40-
val outputs = buffersToSort.mapIndexed { id, buffer -> storage(buffer, "output_$id") }
41-
40+
val outputs = buffersToSort.mapIndexed { id, buffer -> buffer.bindNumeric("output_$id") }
4241
computeStage(WORK_GROUP_SIZE) {
4342
main {
4443
val id = int1Var(inGlobalInvocationId.x.toInt1())
4544
val destId = int1Var(postSortIndices[id])
46-
`if`((id lt numValues) and (destId ne id)) {
45+
`if`((id lt numValues) and (destId ne id)) {
4746
storageToSort.zip(outputs).forEach { (input, output) ->
4847
output[id] = input[destId]
4948
}
@@ -56,10 +55,10 @@ class ReorderBuffersShader(
5655
val numValues = uniformInt1("numValues")
5756
val postSortIndices = storage<KslInt1>("indices")
5857
val inputs = buffersToSort.mapIndexed { id, buffer ->
59-
storage(buffer, "storage_$id")
58+
buffer.bindNumeric("storage_$id")
6059
}
6160
val outputs = buffersToSort.mapIndexed { id, buffer ->
62-
storage(buffer, "output_$id")
61+
buffer.bindNumeric("output_$id")
6362
}
6463
computeStage(WORK_GROUP_SIZE) {
6564
main {
@@ -95,12 +94,17 @@ class ReorderBuffersShader(
9594
}
9695
}
9796

98-
fun KslProgram.storage(buffer: GpuBuffer, name: String): KslPrimitiveStorage<KslPrimitiveStorageType<KslNumericType>> {
99-
return when (buffer.type) {
100-
GpuType.Int4 -> storage<KslInt4>(name)
97+
context(program: KslProgram)
98+
fun GpuBuffer.bindNumeric(
99+
name: String = "storage_${this.name}",
100+
): KslPrimitiveStorage<KslPrimitiveStorageType<KslNumericType>> = with(program) {
101+
return when (type) {
102+
GpuType.Int4 -> {
103+
storage<KslInt4>(name)
104+
}
101105
GpuType.Float4 -> storage<KslFloat4>(name)
102106
GpuType.Int1 -> storage<KslInt1>(name)
103107
GpuType.Float1 -> storage<KslFloat1>(name)
104-
else -> throw IllegalArgumentException("Unsupported buffer type: ${buffer.type}")
108+
else -> throw IllegalArgumentException("Unsupported buffer type: ${type}")
105109
} as KslPrimitiveStorage<KslPrimitiveStorageType<KslNumericType>>
106110
}

particles-kool/src/commonMain/kotlin/me/dvyy/particles/ui/nodes/LineGraphNode.kt renamed to particles-kool/src/commonMain/kotlin/me/dvyy/particles/ui/nodes/GraphNode.kt

Lines changed: 57 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import de.fabmax.kool.math.Vec3i
66
import de.fabmax.kool.modules.ui2.Ui2Shader
77
import de.fabmax.kool.modules.ui2.UiNode
88
import de.fabmax.kool.modules.ui2.UiRenderer
9-
import de.fabmax.kool.pipeline.ComputePass
10-
import de.fabmax.kool.pipeline.ComputeShader
119
import de.fabmax.kool.pipeline.GpuType
1210
import de.fabmax.kool.pipeline.StorageBuffer
1311
import de.fabmax.kool.scene.Mesh
@@ -17,16 +15,14 @@ import de.fabmax.kool.scene.geometry.MeshBuilder
1715
import de.fabmax.kool.scene.geometry.Usage
1816
import de.fabmax.kool.util.Color
1917
import de.fabmax.kool.util.Float32Buffer
20-
import de.fabmax.kool.util.launchOnMainThread
21-
import kotlinx.coroutines.CompletableDeferred
2218
import kotlinx.coroutines.Deferred
2319
import me.dvyy.particles.compute.forces.ForceWithParameters
2420
import me.dvyy.particles.compute.forces.PairwiseForce
2521
import me.dvyy.particles.compute.partitioning.WORK_GROUP_SIZE
2622

27-
class LineGraphNode : UiRenderer<UiNode> {
23+
class GraphNode : UiRenderer<UiNode> {
2824
private val graphMesh: Mesh
29-
private val graphGeom = IndexedVertexList(Ui2Shader.Companion.UI_MESH_ATTRIBS)
25+
private val graphGeom = IndexedVertexList(Ui2Shader.UI_MESH_ATTRIBS)
3026
private val graphBuilder = MeshBuilder(graphGeom).apply { isInvertFaceOrientation = true }
3127
private val clipBounds = MutableVec4f()
3228
private var width = 0
@@ -35,6 +31,7 @@ class LineGraphNode : UiRenderer<UiNode> {
3531
private var valuesY = floatArrayOf()
3632
private var viewport = MutableVec4f(-0.1f, -0.1f, 1f, 1f) // graph -> ui node positions
3733
private var updated = true
34+
var style: GraphStyle = GraphStyle.Line(Color.WHITE)
3835

3936
fun Vec2f.toUi(): Vec2f {
4037
val vWidth = viewport.z - viewport.x
@@ -119,72 +116,65 @@ class LineGraphNode : UiRenderer<UiNode> {
119116
// })
120117
// }
121118
// }
122-
if (node.clipBoundsPx != clipBounds || updated) {
123-
node.surface.getMeshLayer(node.modifier.zLayer + 1)
124-
clipBounds.set(node.clipBoundsPx)
125-
updated = false
126-
println("Ran render ui!")
127-
node.apply {
128-
width = widthPx.toInt()
129-
height = heightPx.toInt()
130-
graphBuilder.clear()
131-
// Render graph
132-
if (valuesX.isEmpty() || valuesY.isEmpty()) return@apply
133-
graphBuilder.configured(Color.Companion.WHITE) {
134-
var prev = Vec2f(valuesX[0], valuesY[0]).toUi()
135-
for (i in 1..valuesX.lastIndex) {
136-
val new = Vec2f(valuesX[i], valuesY[i]).toUi()
137-
line(prev, new, 1f)
138-
prev = new
119+
node.surface.getMeshLayer(node.modifier.zLayer - 1).addCustomLayer("dt-graph") { graphMesh }
120+
if (node.clipBoundsPx == clipBounds && !updated) return
121+
clipBounds.set(node.clipBoundsPx)
122+
updated = false
123+
println("Ran render ui!")
124+
node.apply {
125+
width = widthPx.toInt()
126+
height = heightPx.toInt()
127+
graphBuilder.clear()
128+
if (valuesX.isEmpty() || valuesY.isEmpty()) return@apply
129+
when (val style = style) {
130+
is GraphStyle.Line -> {
131+
graphBuilder.configured(style.color) {
132+
var prev = Vec2f(valuesX[0], valuesY[0]).toUi()
133+
for (i in 1..valuesX.lastIndex) {
134+
val new = Vec2f(valuesX[i], valuesY[i]).toUi()
135+
line(prev, new, 1f)
136+
prev = new
137+
}
139138
}
140139
}
141-
// Render background grid
142-
graphBuilder.configured(Color.Companion.GRAY) {
143-
// horizontal
144-
line(Vec2f(0f, 0f).toUi(), Vec2f(valuesX.max(), 0f).toUi(), 1f)
145-
line(Vec2f(0f, 0f).toUi(), Vec2f(0f, valuesY.max()).toUi(), 1f)
140+
141+
is GraphStyle.Bar -> {
142+
graphBuilder.configured(Color.WHITE) {
143+
val width = style.width.toFloat()
144+
valuesX.zip(valuesY).forEach { (x, y) ->
145+
val bottom = Vec2f(x, 0f).toUi()//.plus(Vec2f(width /2, 0f))
146+
val top = Vec2f(x, y).toUi()//.plus(Vec2f(width /2, 0f))
147+
line(bottom, top, width)
148+
}
149+
}
146150
}
147151
}
148-
}
149-
node.surface.getMeshLayer(node.modifier.zLayer - 1).addCustomLayer("dt-graph") { graphMesh }
150-
}
151-
}
152-
153-
inline fun <T> execManyShaders(
154-
scene: Scene,
155-
setup: (ComputePass) -> Unit,
156-
crossinline read: suspend () -> T,
157-
): Deferred<T> {
158-
val computePass = ComputePass("single-shot")
159-
setup(computePass)
160-
scene.addComputePass(computePass)
161-
162-
val deferred = CompletableDeferred<T>()
163-
164-
computePass.onAfterPass {
165-
computePass.isEnabled = false
166-
launchOnMainThread {
167-
try {
168-
deferred.complete(read())
169-
} catch (e: Exception) {
170-
deferred.completeExceptionally(e)
171-
} finally {
172-
scene.removeComputePass(computePass)
152+
// Render background grid
153+
graphBuilder.configured(Color.GRAY) {
154+
// horizontal
155+
line(Vec2f(0f, 0f).toUi(), Vec2f(valuesX.max(), 0f).toUi(), 1f)
156+
line(Vec2f(0f, 0f).toUi(), Vec2f(0f, valuesY.max()).toUi(), 1f)
173157
}
174158
}
175159
}
176-
177-
return deferred
178-
179-
}
180-
181-
inline fun <T> execShader(
182-
scene: Scene,
183-
shader: ComputeShader,
184-
numGroups: Vec3i = Vec3i.ONES,
185-
crossinline read: suspend () -> T,
186-
): Deferred<T> {
187-
return execManyShaders(scene, setup = {
188-
it.addTask(shader, numGroups)
189-
}, read)
160+
//
161+
//override fun renderUi(node: UiNode) {
162+
// node.apply {
163+
// getTextBuilder(sizes.normalText).configured(Color.WHITE) {
164+
// text(TextProps(sizes.normalText).apply {
165+
// text = "Hello world"; scale = 2f; isYAxisUp = false
166+
// this.origin.set(0f, 70f, 0f)
167+
// })
168+
// }
169+
// }
170+
// if (node.clipBoundsPx != clipBounds || updated) {
171+
// node.surface.getMeshLayer(node.modifier.zLayer + 1)
172+
// clipBounds.set(node.clipBoundsPx)
173+
// updated = false
174+
// println("Ran render ui!")
175+
// node.apply {
176+
// }
177+
// node.surface.getMeshLayer(node.modifier.zLayer - 1).addCustomLayer("dt-graph") { graphMesh }
178+
// }
179+
//}
190180
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package me.dvyy.particles.ui.nodes
2+
3+
import de.fabmax.kool.util.Color
4+
5+
sealed interface GraphStyle {
6+
data class Line(val color: Color) : GraphStyle
7+
8+
data class Bar(val width: Double) : GraphStyle
9+
}

0 commit comments

Comments
 (0)