{"id":"shader--gpu-blur","name":"Connect two GPU passes","owner":"yeho","package":"examples.gpu-shaders","kind":"shader","signature":"[gpu] compute Paint(buffer of int pixels, int width, int height, float time)","description":"Paint a checker, then soften it with a small neighborhood filter.","context":"An image effect often reads neighboring pixels. Keep input and output in separate buffers, and finish the painting pass before the blur reads it.","parameters":[],"returns":"","members":[],"tags":["complete-program","compute","gpu","image","shader","yeho"],"updated":"2026-09-09","source":{"repository":"demonhunterlabs","path":"app/lib/yeho-gpu-shaders.ts","sha256":"75afd80c7f9127fd0a044bca3bd3e160b460b4abb3c7c4dcf80f71dd7645da45"},"status":"native-metal-example","notes":["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."],"examples":[{"id":"complete","title":"04 / Connect two GPU passes","description":"Paint a checker, then soften it with a small neighborhood filter.","code":"[gpu]\ncompute Paint(buffer of int pixels, int width, int height, float time)\n{\n    int index = compute.index\n    if index >= pixels.count { return }\n    int row = index / width\n    int column = index - row * width\n    float u = Math.IntToFloat(column) / Math.IntToFloat(width - 1)\n    float v = Math.IntToFloat(row) / Math.IntToFloat(height - 1)\n    int cellX = column / 16\n    int cellY = row / 16\n    int sum = cellX + cellY\n    float r = 0.02\n    float g = 0.04\n    float b = 0.08\n    if sum / 2 * 2 == sum\n    {\n        r = 0.08\n        g = 0.9\n        b = 0.7\n    }\n    int red = Math.RoundToInt(Math.ClampFloat(r, 0.0, 1.0) * 255.0)\n    int green = Math.RoundToInt(Math.ClampFloat(g, 0.0, 1.0) * 255.0)\n    int blue = Math.RoundToInt(Math.ClampFloat(b, 0.0, 1.0) * 255.0)\n    pixels[index] = red * 65536 + green * 256 + blue\n}\n\n[gpu]\ncompute Blur(buffer of int input, buffer of int output, int width, int height)\n{\n    int index = compute.index\n    if index >= input.count { return }\n    if index >= output.count { return }\n    int row = index / width\n    int column = index - row * width\n    if row == 0 or row + 1 >= height or column == 0 or column + 1 >= width\n    {\n        output[index] = input[index]\n        return\n    }\n    int red = 0\n    int green = 0\n    int blue = 0\n    for yOffset from 0 < 3\n    {\n        for xOffset from 0 < 3\n        {\n            int pixel = input[(row + yOffset - 1) * width + column + xOffset - 1]\n            int r = pixel / 65536\n            int g = (pixel - r * 65536) / 256\n            int b = pixel - r * 65536 - g * 256\n            red = red + r\n            green = green + g\n            blue = blue + b\n        }\n    }\n    output[index] = (red / 9) * 65536 + (green / 9) * 256 + blue / 9\n}\n\n// CPU: allocate an image and submit one GPU invocation per pixel.\nint width = 128\nint height = 128\nbuffer of int pixels\npixels.Resize(width * height)\ncomputeTask job = dispatch Paint(pixels, width, height, 0.0)\nwait job\nif job.failed { Console.Error(job.error.message) Process.Exit(1) }\n\nbuffer of int filtered\nfiltered.Resize(width * height)\ncomputeTask filter = dispatch Blur(pixels, filtered, width, height)\nwait filter\nif filter.failed { Console.Error(filter.error.message) Process.Exit(1) }\n\n// CPU: save the completed RGB pixels as a portable PPM image.\ntext image = \"P3\\n\" + Text.From(width) + \" \" + Text.From(height) + \"\\n255\\n\"\nfor row from 0 < height\n{\n    text line = \"\"\n    for column from 0 < width\n    {\n        int pixel = filtered[row * width + column]\n        int red = pixel / 65536\n        int green = (pixel - red * 65536) / 256\n        int blue = pixel - red * 65536 - green * 256\n        line = line + Text.From(red) + \" \" + Text.From(green) + \" \" + Text.From(blue) + \" \"\n    }\n    image = image + line + \"\\n\"\n}\nFile.WriteText(\"shader.ppm\", image)\nConsole.Log(\"Wrote shader.ppm\")\n","manifest":"manifestVersion = \"2\"\npackage = \"learning.shaders.gpu_blur\"\nversion = \"0.1.0\"\nlanguageEdition = \"yeho-core-2026.1\"\n\n[app]\nkind = \"headless\"\n","tags":["gpu","shader","image"],"run":"metal","expected":"Wrote shader.ppm","verification":"type checked","sha256":"b12f53bf5aec3a0de12774e0fc9bd77114c77f4613b491c56728a1cd38ba6414","checked":"2026-09-09","diagnostic":"","observedOutput":null,"checkedBackend":null}],"searchTerms":[],"url":"https://demonhunterlabs.com/academy/api?item=shader--gpu-blur","json":"https://demonhunterlabs.com/reference/items/shader--gpu-blur.json","markdown":"https://demonhunterlabs.com/reference/text/shader--gpu-blur.md"}
