I was trying to learn compute shaders, and for this reason I decided to go for a simple particle system. And it works, but there are things I still don’t understand, marked with FIXME:
import org.openrndr.Fullscreen
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.transform
fun main() = application {
configure {
fullscreen = Fullscreen.CURRENT_DISPLAY_MODE
}
program {
val computeWidth = 1000
val computeHeight = 100
val particleCount = computeWidth * computeHeight
val computeShader = ComputeShader.fromCode(
"""
#version 430
// FIXME why 16?
layout(local_size_x = 16, local_size_y = 16) in;
uniform int computeWidth;
uniform float width;
uniform float height;
struct ParticleTransform {
mat4 transform;
};
struct ParticleProperties {
vec2 velocity;
};
layout(binding=0) buffer transformsBuffer {
ParticleTransform transforms[];
};
layout(binding=1) buffer propertiesBuffer {
ParticleProperties properties[];
};
void main() {
// FIXME is offset calculated correctly?
const uint offset = gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * computeWidth;
ParticleTransform pt = transforms[offset];
ParticleProperties pp = properties[offset];
vec2 position = vec2(pt.transform[3][0], pt.transform[3][1]);
if ((position.x < 0) || (position.x > width)) {
properties[offset].velocity *= vec2(-1, 1);
}
if ((position.y < 0) || (position.y > height)) {
properties[offset].velocity *= vec2(1, -1);
}
position += properties[offset].velocity;
transforms[offset].transform[3][0] = position.x;
transforms[offset].transform[3][1] = position.y;
}
"""
)
// -- create the vertex buffer
val geometry = vertexBuffer(vertexFormat {
position(3)
}, 4)
// -- fill the vertex buffer with vertices for a unit quad
geometry.put {
write(Vector3(-1.0, -1.0, 0.0))
write(Vector3(-1.0, 1.0, 0.0))
write(Vector3(1.0, -1.0, 0.0))
write(Vector3(1.0, 1.0, 0.0))
}
// -- create the secondary vertex buffer, which will hold particle transformations
val transformsBuffer = vertexBuffer(vertexFormat {
attribute("transform", VertexElementType.MATRIX44_FLOAT32)
}, particleCount)
// FIXME would it be possible to somehow hold it in the transformsBuffer?
// -- create the tertiary vertex buffer, which will hold particle properties
val propertiesBuffer = vertexBuffer(vertexFormat {
attribute("velocity", VertexElementType.VECTOR2_FLOAT32)
}, particleCount)
// -- fill the initial transform buffer
transformsBuffer.put {
for (i in 0 until particleCount) {
write(transform {
translate(Math.random() * width, Math.random() * height)
rotate(Vector3.UNIT_Z, Math.random() * 360.0)
scale(1.0 + Math.random() * 3.0)
})
}
}
propertiesBuffer.put {
for (i in 0 until particleCount) {
// velocity
write(Vector2((Math.random() * .1 - .05), Math.random() * .1 - .05))
}
}
computeShader.uniform("computeWidth", computeWidth)
computeShader.uniform("width", width.toDouble())
computeShader.uniform("height", height.toDouble())
computeShader.buffer("transformsBuffer", transformsBuffer)
computeShader.buffer("propertiesBuffer", propertiesBuffer)
extend {
drawer.isolated {
fill = ColorRGBa.PINK.opacify(.5)
shadeStyle = shadeStyle {
vertexTransform = "x_viewMatrix = x_viewMatrix * i_transform;"
// FIXME assuming that I have my custom fragmentTransform, is there anyway to pass particle properties buffer to it?
}
vertexBufferInstances(
listOf(geometry), listOf(transformsBuffer), DrawPrimitive.TRIANGLE_STRIP, particleCount
)
}
computeShader.execute(computeWidth, computeHeight)
}
}
}
I still don’t understand what layout(local_size_x = 16, local_size_y = 16) in;
stands for and why it doesn’t work with size 1. Also what are the performance implications. But I guess this I have to read on my own.
With this program I can animate up to half a million particles on my integrated intel GPU, even bigger particles than 1 pixel in size. I guess not bad. I was wondering if it is the most efficient way of doing it. Maybe rendering points instead of quads, increasing their size, and using custom fragment shader, would be even faster. I also wonder if I should use transformation matrix, or rather use compute shader to rewrite vertices buffer contents?
And also I was wondering if there is any way I can pass my particle properties buffer (velocity) to the fragment shader or fragmentTransform
?
So my code just works, but I was also reading about memory consistency of buffers, should I be afraid of that?