{"id":"shader--gpu-rings","name":"Make a neon ripple","owner":"yeho","package":"examples.gpu-shaders","kind":"shader","signature":"[gpu] compute Paint(buffer of int pixels, int width, int height, float time)","description":"Distance, a sine wave, and a fade make a bright procedural pattern.","context":"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.","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":"1e4eb71d216d7fe06d1b00770bf023953f674417e275062544215e68e2bdadde"},"status":"native-metal-example","notes":["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."],"examples":[{"id":"complete","title":"02 / Make a neon ripple","description":"Distance, a sine wave, and a fade make a bright procedural pattern.","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    float x = u - 0.5\n    float y = v - 0.5\n    float distance = Math.SqrtFloat(x * x + y * y)\n    float wave = 0.5 + 0.5 * Math.SinFloat(distance * 42.0 - time)\n    float fade = Math.ClampFloat(1.0 - distance * 1.4, 0.0, 1.0)\n    float r = wave * wave * fade\n    float g = (1.0 - wave) * fade * 0.75\n    float b = (0.3 + wave * 0.7) * fade\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// 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\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 = pixels[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_rings\"\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":"276b5a488afd410930554ce10c6b3ebb1a72c715855d725efa23364ce2f3e576","checked":"2026-09-09","diagnostic":"","observedOutput":null,"checkedBackend":null}],"searchTerms":[],"url":"https://demonhunterlabs.com/academy/api?item=shader--gpu-rings","json":"https://demonhunterlabs.com/reference/items/shader--gpu-rings.json","markdown":"https://demonhunterlabs.com/reference/text/shader--gpu-rings.md"}
