Back to list
moinsen-dev

flutter-shader

by moinsen-dev

This project demonstrates the power of **Claude Code Skills** — a way to teach Claude specialized knowledge that it can apply automatically. The entire festive shader application you see here was built by Claude using a custom skill for Flutter fragment shader development.

0🍴 0📅 Dec 30, 2025

SKILL.md


name: flutter-shader description: Develop Flutter fragment shaders (GLSL) end-to-end. Use when creating, debugging, or integrating custom shaders in Flutter apps. Triggers on shader, GLSL, fragment shader, CustomPainter with shader, ShaderMask, visual effects, procedural graphics, or GPU rendering in Flutter.

Flutter Fragment Shader Development

Comprehensive guide for building, integrating, debugging, and shipping Flutter fragment shaders using Flutter's FragmentProgram / FragmentShader APIs.

Scope and Capabilities

What Flutter Supports

  • Fragment shaders (pixel shaders) only - vertex shaders are NOT supported
  • Core runtime objects:
    • FragmentProgram: compiled shader asset that creates shader instances
    • FragmentShader: instance with uniforms bound to Paint.shader

Backend Awareness

Flutter runs with Skia or Impeller backends:

  • Both support custom shaders
  • ImageFilter.shader is Impeller-only
  • Performance characteristics differ between backends

Project Setup Checklist

1. File Structure

/shaders/
  my_effect.frag
/lib/
  shaders/
  widgets/

2. Declare in pubspec.yaml

flutter:
  shaders:
    - shaders/my_effect.frag

3. Hot Reload

In debug mode, shader edits trigger recompilation on hot reload/restart.

GLSL Authoring Rules

Required Header

#version 460 core
#include <flutter/runtime_effect.glsl>

out vec4 fragColor;

Coordinate Access

Use FlutterFragCoord() instead of gl_FragCoord:

vec2 p = FlutterFragCoord().xy / u_size;

Key Limitations

  • Only sampler2D (no samplerCube, etc.)
  • Only texture(sampler, uv) two-argument form
  • No extra varying inputs
  • No UBO/SSBO
  • No unsigned ints or booleans

OpenGLES Y-Flip Fix

When sampling engine textures on OpenGLES:

vec2 uv = FlutterFragCoord().xy / u_size;
#ifdef IMPELLER_TARGET_OPENGLES
  uv.y = 1.0 - uv.y;
#endif

Uniform Indexing Rules

Rule 1: Float uniforms use declaration order

float, vec2, vec3, vec4 set via setFloat(index, value) in declaration order.

Rule 2: Samplers have separate index space

sampler2D set via setImageSampler(samplerIndex, image) - does NOT consume float indices.

Example

uniform float uScale;      // setFloat(0, ...)
uniform sampler2D uTexture; // setImageSampler(0, ...) - separate!
uniform vec2 uMagnitude;   // setFloat(1, x), setFloat(2, y)
uniform vec4 uColor;       // setFloat(3..6, r,g,b,a)
shader.setFloat(0, 23);           // uScale
shader.setFloat(1, 114);          // uMagnitude.x
shader.setFloat(2, 83);           // uMagnitude.y
shader.setFloat(3, r);            // uColor.r
shader.setFloat(4, g);            // uColor.g
shader.setFloat(5, b);            // uColor.b
shader.setFloat(6, a);            // uColor.a
shader.setImageSampler(0, image); // uTexture

Loading and Using Shaders

Load Program

final program = await FragmentProgram.fromAsset('shaders/my_effect.frag');

Create and Bind Uniforms

final shader = program.fragmentShader();
shader.setFloat(0, timeSeconds);
shader.setFloat(1, width);
shader.setFloat(2, height);

Draw with Shader

canvas.drawRect(rect, Paint()..shader = shader);

ImageFilter (Impeller-only)

ImageFilter.shader(shader) // Only works on Impeller!

Development Workflow

Step 1: Write Shader Spec

Define before coding:

  • Inputs (uniforms): u_time, u_size, effect parameters
  • Texture sampling requirements
  • Application context: full-screen, mask, backdrop

Step 2: Establish Uniform Contract

Consistent ordering convention:

  1. uniform vec2 u_size;
  2. uniform float u_time;
  3. Effect parameters...

Mirror in Dart with constants for indices.

Step 3: Create Integration Wrapper

Choose pattern:

  • CustomPainter - drawing into Canvas
  • ShaderMask - masking child content
  • BackdropFilter / ImageFiltered - post-processing (Impeller-only)

Step 4: Performance Optimization

  • Reuse FragmentShader instances - don't recreate each frame
  • Precache FragmentProgram before animations start
  • SkSL warm-up for Skia backend jank mitigation
  • Use Impeller where available (precompiles at build-time)

Step 5: Cross-Platform Testing

Test on:

  • Android (OpenGLES/Vulkan)
  • iOS (Metal)
  • Web (CanvasKit/skwasm)

Debugging Guide

Black Screen Diagnosis

  1. Check fragColor is written
  2. Verify coordinate normalization (divide by size)
  3. Audit uniform index mapping

Uniform Layout Inspection

Use impellerc + flatc for reflection data on uniform offsets.

Backend Errors

ImageFilter.shader throws on non-Impeller backends.

Additional Resources

Quick Reference

TaskTool/Pattern
Load shaderFragmentProgram.fromAsset()
Create instanceprogram.fragmentShader()
Set float uniformshader.setFloat(index, value)
Set textureshader.setImageSampler(index, image)
Draw to canvasPaint()..shader = shader
Post-processImageFilter.shader() (Impeller)

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新

+5
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon