# Connect two GPU passes

Paint a checker, then soften it with a small neighborhood filter.

Last updated: 2026-09-09

Package: examples.gpu-shaders | Status: native-metal-example

Tags: complete-program, compute, gpu, image, shader, yeho

An image effect often reads neighboring pixels. Keep input and output in separate buffers, and finish the painting pass before the blur reads it.

```yh
[gpu] compute Paint(buffer of int pixels, int width, int height, float time)
```


## 04 / Connect two GPU passes

Paint a checker, then soften it with a small neighborhood filter.

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)
    int cellX = column / 16
    int cellY = row / 16
    int sum = cellX + cellY
    float r = 0.02
    float g = 0.04
    float b = 0.08
    if sum / 2 * 2 == sum
    {
        r = 0.08
        g = 0.9
        b = 0.7
    }
    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
}

[gpu]
compute Blur(buffer of int input, buffer of int output, int width, int height)
{
    int index = compute.index
    if index >= input.count { return }
    if index >= output.count { return }
    int row = index / width
    int column = index - row * width
    if row == 0 or row + 1 >= height or column == 0 or column + 1 >= width
    {
        output[index] = input[index]
        return
    }
    int red = 0
    int green = 0
    int blue = 0
    for yOffset from 0 < 3
    {
        for xOffset from 0 < 3
        {
            int pixel = input[(row + yOffset - 1) * width + column + xOffset - 1]
            int r = pixel / 65536
            int g = (pixel - r * 65536) / 256
            int b = pixel - r * 65536 - g * 256
            red = red + r
            green = green + g
            blue = blue + b
        }
    }
    output[index] = (red / 9) * 65536 + (green / 9) * 256 + blue / 9
}

// 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) }

buffer of int filtered
filtered.Resize(width * height)
computeTask filter = dispatch Blur(pixels, filtered, width, height)
wait filter
if filter.failed { Console.Error(filter.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 = filtered[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_blur"
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-blur/complete --backend metal -o /tmp/yeho-example
/tmp/yeho-example
```


Replace the Paint checker’s cell size with 8. Compare the sharp and blurred outputs. Keep input and output as distinct storage.

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
