shader

Shader manages the compilation of your GLSL shaders into SPIR-V byte code and Dart code.

Quickstart

# Install cli
dart pub global activate shader

# Compile all glsl files in our project
shader --use-remote --to-dart

# Discover all features
shader --help

Table of Contents

Getting started

Usage

Writing shaders

Getting started

Install the command-line executable shader for your current user:

dart pub global activate shader

Now you can use the shader CLI tool:

shader --help

Hint: If pub binaries are not known to the path, you can also run it by:

dart pub global run shader --help

Usage

Compile to Dart

The easiest way of using shaders in your app, is to use it this way:

shader --use-remote --to-dart

It will scan your project for *.glsl files and use the hosted cloud service to compile it. Flutter needs SPR-V byte code at runtime.

A very simple shader red-shader.glsl could be this:

#version 320 es

precision highp float;

layout(location = 0) out vec4 fragColor;

void main() {
    fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

This shader has no uniforms (input parameters) and paints each pixel red.

The compiler created a dart file red_shader_sprv.dart that contains a function Future<FragmentProgram> redShaderFragmentProgram() that will initialize the shader at runtime.

We can utilize a FutureBuilder to load that:

import 'dart:ui';
import 'package:flutter/material.dart';

/// Import file generated by cli
import 'package:flutter_app/shader/red_shader_sprv.dart';

class RedShaderWidget extends StatelessWidget {
  const RedShaderWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<FragmentProgram>(
      /// Use the generated loader function here
      future: redShaderFragmentProgram(),
      builder: ((context, snapshot) {
        if (!snapshot.hasData) {
          /// Shader is loading
          return const WidgetWhenLoading();
        }

        /// Shader is ready to use
        return WidgetThatUsesShader(snapshot.data!);
      }),
    );
  }
}

You can find an app example of Red Shader.

Use of uniforms

Uniforms is the term in the GLSL world for input parameter. The uniforms can be changed for each frame, but they are constant for every pixel during a single frame.

Given the following GLSL file:

#version 320 es

precision highp float;

layout(location = 0) out vec4 fragColor;

// define uniforms:
layout(location = 0) uniform vec3 color1;
layout(location = 1) uniform vec3 color2;
layout(location = 2) uniform float someValue;
layout(location = 3) uniform vec2 size;

void main() {
    // ...
}

This can be addressed in FragmentProgram's shader() method:

@override
void paint(Canvas canvas, Size size) {
  /// Inputs
  Color color1 = Colors.blue;
  Color color2 = Colors.green;
  double someValue = 0.5;

  /// Create paint using a shader
  final paint = Paint()
    ..shader = fragmentProgram.shader(

        /// Specify input parameter (uniforms)
        floatUniforms: Float32List.fromList([
      /// color1 takes 3 floats and will be mapped to `vec3`
      color1.red / 255.0,
      color1.green / 255.0,
      color1.blue / 255.0,

      /// color2 also takes 3 floats and will be mapped to `vec3`
      color2.red / 255.0,
      color2.green / 255.0,
      color2.blue / 255.0,

      /// someValue takes 1 float and will be mapped to `float`
      someValue,

      /// size takes 2 floats and will be mapped to `vec2`
      size.width,
      size.height,
    ]));

  /// Draw a rectangle with the shader-paint
  canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
}

Also take a look at Color Shader example, that combines Flutter animtions and the use of uniforms.

Make use of sampler uniform

Image textures can be accessed via sampler2d uniforms:

layout(location = 0) uniform sampler2D image;

For that you need to create an ImageShader:

final asset = await rootBundle.load("assets/image.jpg");
final image = await decodeImageFromList(asset.buffer.asUint8List());

/// Create ImageShader that will provide a GLSL sampler
final ImageShader imageShader = ImageShader(
  image,
  // Specify how image repetition is handled for x and y dimension
  TileMode.repeated,
  TileMode.repeated,
  // Transformation matrix (identity matrix = no transformation)
  Matrix4.identity().storage,
);

That ImageShader can be passed into the FragmentProgram's shader() method as samplerUniform:

final paint = Paint()
  ..shader = fragmentProgram.shader(
    samplerUniforms: [
      imageShader,
    ],
  );

You can see everything wired up in the Image Scale app example.

Use a local compiler

If you don't want to rely on the hosted cloud service or the cloud service not available, you can use local compiler.

You can download the compiler at https://github.com/google/shaderc.

The --use-local option takes a path that roughly points to the compiler binary:

shader --use-local $HOME/sdk/shaderc --to-dart

Improve development cycle

In order to iterate faster and use the hot-reload, you can use the --watch flag:

shader --use-remote --to-dart --watch

shader --use-local $HOME/sdk/shaderc --to-dart --watch

Other features

The shader executable also supports some other options and output format. Type to find get information:

shader --help

Writing shaders

This section covers useful information and resources writing own shader code.

Constraints in Flutter

Shaders are not supported for Flutter web, yet. But there is a project plan for the Flutter engine developers to enable it.

Also the capabilities of GLSL language feature are restricted. Take a look at the specifications of the SPIR-V Transpiler.

This package compiles GLSL code to SPIR-V code, and at runtime SPIR-V transpiler converts it to native API (e.g. OpenGL, Vulkan). So it might be that shader will compile fine, but it fails at runtime.

Learning GLSL

There are various sources to learn GLSL:

Libraries

shader