# Make a neon ripple

Distance, a sine wave, and a fade make a bright procedural pattern.

Last updated: 2026-09-09

Package: examples.gpu-shaders | Status: native-metal-example

Tags: complete-program, compute, gpu, image, shader, yeho

A shader can create detail without reading an image texture. Describe where a pixel is, then turn that position into color. One float time parameter gives the pattern an animation control.

```yh
[gpu] compute Paint(buffer of int pixels, int width, int height, float time)
```


## 02 / Make a neon ripple

Distance, a sine wave, and a fade make a bright procedural pattern.

Verification: type checked

```yh
[gpu]
compute Paint(buffer of int pixels, int width, int height, float time)
{
    int index = compute.index
    if index >= pixels.count { return }
    int row = index / width
    int column = index - row * width
    float u = Math.IntToFloat(column) / Math.IntToFloat(width - 1)
    float v = Math.IntToFloat(row) / Math.IntToFloat(height - 1)
    float x = u - 0.5
    float y = v - 0.5
    float distance = Math.SqrtFloat(x * x + y * y)
    float wave = 0.5 + 0.5 * Math.SinFloat(distance * 42.0 - time)
    float fade = Math.ClampFloat(1.0 - distance * 1.4, 0.0, 1.0)
    float r = wave * wave * fade
    float g = (1.0 - wave) * fade * 0.75
    float b = (0.3 + wave * 0.7) * fade
    int red = Math.RoundToInt(Math.ClampFloat(r, 0.0, 1.0) * 255.0)
    int green = Math.RoundToInt(Math.ClampFloat(g, 0.0, 1.0) * 255.0)
    int blue = Math.RoundToInt(Math.ClampFloat(b, 0.0, 1.0) * 255.0)
    pixels[index] = red * 65536 + green * 256 + blue
}

// CPU: allocate an image and submit one GPU invocation per pixel.
int width = 128
int height = 128
buffer of int pixels
pixels.Resize(width * height)
computeTask job = dispatch Paint(pixels, width, height, 0.0)
wait job
if job.failed { Console.Error(job.error.message) Process.Exit(1) }

// CPU: save the completed RGB pixels as a portable PPM image.
text image = "P3\n" + Text.From(width) + " " + Text.From(height) + "\n255\n"
for row from 0 < height
{
    text line = ""
    for column from 0 < width
    {
        int pixel = pixels[row * width + column]
        int red = pixel / 65536
        int green = (pixel - red * 65536) / 256
        int blue = pixel - red * 65536 - green * 256
        line = line + Text.From(red) + " " + Text.From(green) + " " + Text.From(blue) + " "
    }
    image = image + line + "\n"
}
File.WriteText("shader.ppm", image)
Console.Log("Wrote shader.ppm")

```

Save this beside start.yh as project.mech:

```toml
manifestVersion = "2"
package = "learning.shaders.gpu_rings"
version = "0.1.0"
languageEdition = "yeho-core-2026.1"

[app]
kind = "headless"

```

Expected output:

```text
Wrote shader.ppm
```

Extract the example archive beside the yeho and dolphin checkouts. This example requires those source dependencies and a current developer compiler.

```sh
./yeho/build/dolphin-metal/yehoc ./api-examples/shader--gpu-rings/complete --backend metal -o /tmp/yeho-example
/tmp/yeho-example
```


Change 42.0 to 18.0 for wider rings, then change the dispatch time from 0.0 to 1.5. These are separate saved frames, not an automatic live preview.

The dedicated shader verifier executes this program on Metal and compares every output channel with a separate CPU reference. Evidence: https://demonhunterlabs.com/learning/gpu-shaders/verification.json

The program writes shader.ppm in its current directory. Native image generation is separate from presenting frames in a game window.

Source: demonhunterlabs/app/lib/yeho-gpu-shaders.ts

AI training permission: https://demonhunterlabs.com/ai-use
