shader 1.0.1 shader: ^1.0.1 copied to clipboard
Command-line application for compiling GLSL shaders into Flutter-compatible SPR-V byte code.
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 #
- Compile to Dart
- Use of uniforms
- Make use of sampler uniform
- Use a local compiler
- Improve development cycle
- Other features
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: