diff --git a/shaders/slang/compileshaders.py b/shaders/slang/compileshaders.py index 012c810e..0008835a 100644 --- a/shaders/slang/compileshaders.py +++ b/shaders/slang/compileshaders.py @@ -45,6 +45,8 @@ def getShaderStages(filename): stages.append("closesthit") if '[shader("callable")]' in filecontent: stages.append("callable") + if '[shader("compute")]' in filecontent: + stages.append("compute") f.close() return stages @@ -52,10 +54,14 @@ compiler_path = findCompiler() print("Found slang compiler at %s", compiler_path) +compile_single_sample = "computeparticles" + dir_path = os.path.dirname(os.path.realpath(__file__)) dir_path = dir_path.replace('\\', '/') for root, dirs, files in os.walk(dir_path): for file in files: + if (compile_single_sample != "" and os.path.basename(root) != compile_single_sample): + continue if file.endswith(".slang"): input_file = os.path.join(root, file) # Slang can store multiple shader stages in a single file, we need to split into separate SPIR-V files for the sample framework @@ -80,8 +86,10 @@ for root, dirs, files in os.walk(dir_path): output_ext = ".rchit" case "callable": output_ext = ".rcall" + case "compute": + output_ext = ".comp" output_file = input_file + output_ext + ".spv" output_file = output_file.replace(".slang", "") - res = subprocess.call("%s %s -profile spirv_1_4 -matrix-layout-column-major -target spirv -o %s -entry %s -stage %s" % (compiler_path, input_file, output_file, entry_point, stage), shell=True) + res = subprocess.call("%s %s -profile spirv_1_4 -matrix-layout-column-major -target spirv -o %s -entry %s -stage %s -warnings-disable 39001" % (compiler_path, input_file, output_file, entry_point, stage), shell=True) if res != 0: sys.exit(res) \ No newline at end of file diff --git a/shaders/slang/computeparticles/particle.slang b/shaders/slang/computeparticles/particle.slang new file mode 100644 index 00000000..c5959164 --- /dev/null +++ b/shaders/slang/computeparticles/particle.slang @@ -0,0 +1,120 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +[[vk::binding(0, 0)]] Sampler2D samplerColorMap; +[[vk::binding(1, 0)]] Sampler2D samplerGradientRamp; + +struct VSInput +{ + float2 Pos : POSITION0; + float4 GradientPos : POSITION1; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float PointSize : SV_PointSize; + float4 Color : COLOR0; + float GradientPos : POSITION0; +}; + +struct Particle +{ + float2 pos; + float2 vel; + float4 gradientPos; +}; +// Binding 0 : Position storage buffer +[[vk::binding(0, 0)]] RWStructuredBuffer particles; + +struct UBO +{ + float deltaT; + float destX; + float destY; + int particleCount; +}; +[[vk::binding(1, 0)]] ConstantBuffer ubo; + +struct PushConsts +{ + float2 screendim; +}; + +float2 attraction(float2 pos, float2 attractPos) +{ + float2 delta = attractPos - pos; + const float damp = 0.5; + float dDampedDot = dot(delta, delta) + damp; + float invDist = 1.0f / sqrt(dDampedDot); + float invDistCubed = invDist*invDist*invDist; + return delta * invDistCubed * 0.0035; +} + +float2 repulsion(float2 pos, float2 attractPos) +{ + float2 delta = attractPos - pos; + float targetDistance = sqrt(dot(delta, delta)); + return delta * (1.0 / (targetDistance * targetDistance * targetDistance)) * -0.000035; +} + +[shader("vertex")] +VSOutput vertexMain(VSInput input) +{ + VSOutput output; + output.PointSize = 8.0; + output.Color = float4(0.035, 0.035, 0.035, 0.035); + output.GradientPos = input.GradientPos.x; + output.Pos = float4(input.Pos.xy, 1.0, 1.0); + return output; +} + +[shader("fragment")] +float4 fragmentMain(VSOutput input, float2 pointCoord: SV_PointCoord) : SV_TARGET +{ + float3 color = samplerGradientRamp.Sample(float2(input.GradientPos, 0.0)).rgb; + return float4(samplerColorMap.Sample(pointCoord).rgb * color, 1.0); +} + +[shader("compute")] +[numthreads(256, 1, 1)] +void computeMain(uint3 GlobalInvocationID : SV_DispatchThreadID) +{ + // Current SSBO index + uint index = GlobalInvocationID.x; + // Don't try to write beyond particle count + if (index >= ubo.particleCount) { + return; + } + + // Read position and velocity + float2 vVel = particles[index].vel.xy; + float2 vPos = particles[index].pos.xy; + + float2 destPos = float2(ubo.destX, ubo.destY); + + float2 delta = destPos - vPos; + float targetDistance = sqrt(dot(delta, delta)); + vVel += repulsion(vPos, destPos.xy) * 0.05; + + // Move by velocity + vPos += vVel * ubo.deltaT; + + // collide with boundary + if ((vPos.x < -1.0) || (vPos.x > 1.0) || (vPos.y < -1.0) || (vPos.y > 1.0)) { + vVel = (-vVel * 0.1) + attraction(vPos, destPos) * 12; + } else { + particles[index].pos.xy = vPos; + } + + // Write back + particles[index].vel.xy = vVel; + particles[index].gradientPos.x += 0.02 * ubo.deltaT; + if (particles[index].gradientPos.x > 1.0) { + particles[index].gradientPos.x -= 1.0; + } +} +