Optimize shaders
CI / quality (push) Failing after 5m56s

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-04-21 17:40:21 +02:00
parent 37e0814483
commit 9564096a5c
40 changed files with 678 additions and 1442 deletions
+44 -192
View File
@@ -5,8 +5,8 @@ import 'package:flutter/material.dart';
import 'shiny_controller.dart';
/// Available holographic material profiles rendered by the fragment shader.
enum HolographStyle {
/// Available holographic material profiles rendered by dedicated shader assets.
enum ShinyProfile {
/// Smooth silver foil with soft rainbow flow.
holographicSilver,
@@ -20,168 +20,20 @@ enum HolographStyle {
superGoldVinyl,
}
/// Sparkle silhouette families used by the shader when drawing glints.
enum SparkleShapeKind {
/// Disables sparkle rendering entirely.
none,
/// Star-shaped sparkle with configurable point count and inner radius.
star,
/// Rectangular sparkle streak.
rectangle,
/// Fixed-side polygon sparkle.
polygon,
/// Randomized polygon sparkle per sparkle cell.
randomPolygon,
/// Confetti-like elongated rectangle with random rotation.
confetti,
}
/// Declarative sparkle shape configuration for [Shiny] and [ShinyCard].
class SparkleShapeSpec {
/// Internal constructor that maps directly to shader uniform values.
const SparkleShapeSpec._({
required this.kind,
required this.primary,
required this.secondary,
required this.tertiary,
});
/// No sparkle output.
static const SparkleShapeSpec none = SparkleShapeSpec._(
kind: SparkleShapeKind.none,
primary: 0.0,
secondary: 0.0,
tertiary: 0.0,
);
/// Eight-point star sparkle preset.
static const SparkleShapeSpec eightPointStar = SparkleShapeSpec._(
kind: SparkleShapeKind.star,
primary: 8.0,
secondary: 0.42,
tertiary: 0.0,
);
/// Five-point star sparkle preset.
static const SparkleShapeSpec fivePointStar = SparkleShapeSpec._(
kind: SparkleShapeKind.star,
primary: 5.0,
secondary: 0.42,
tertiary: 0.0,
);
/// Rectangular sparkle preset.
static const SparkleShapeSpec rectangle = SparkleShapeSpec._(
kind: SparkleShapeKind.rectangle,
primary: 0.24,
secondary: 0.055,
tertiary: 1.0,
);
/// Diamond sparkle preset.
static const SparkleShapeSpec diamond = SparkleShapeSpec._(
kind: SparkleShapeKind.polygon,
primary: 4.0,
secondary: 1.0,
tertiary: 0.78539816339,
);
/// Hexagon sparkle preset.
static const SparkleShapeSpec hexagon = SparkleShapeSpec._(
kind: SparkleShapeKind.polygon,
primary: 6.0,
secondary: 1.0,
tertiary: 0.0,
);
/// Randomized polygon sparkle preset.
static const SparkleShapeSpec randomPolygon = SparkleShapeSpec._(
kind: SparkleShapeKind.randomPolygon,
primary: 0.0,
secondary: 0.0,
tertiary: 0.0,
);
/// Confetti sparkle preset.
static const SparkleShapeSpec confetti = SparkleShapeSpec._(
kind: SparkleShapeKind.confetti,
primary: 0.34,
secondary: 0.045,
tertiary: 1.0,
);
/// Creates a custom star sparkle.
///
/// [points] is clamped to `[4, 12]` and [innerRatio] to `[0.15, 0.85]`.
factory SparkleShapeSpec.customStar(
{int points = 8, double innerRatio = 0.42}) {
return SparkleShapeSpec._(
kind: SparkleShapeKind.star,
primary: points.toDouble().clamp(4.0, 12.0),
secondary: innerRatio.clamp(0.15, 0.85),
tertiary: 0.0,
);
extension ShinyProfileShaderAsset on ShinyProfile {
/// Local shader asset path for the profile.
String get shaderAsset {
switch (this) {
case ShinyProfile.holographicSilver:
return 'shaders/shiny_holographic_silver.frag';
case ShinyProfile.crackedIce:
return 'shaders/shiny_cracked_ice.frag';
case ShinyProfile.silverMosaic:
return 'shaders/shiny_silver_mosaic.frag';
case ShinyProfile.superGoldVinyl:
return 'shaders/shiny_super_gold_vinyl.frag';
}
}
/// Creates a custom polygon sparkle.
///
/// [sides] is clamped to `[3, 10]` and [aspectRatio] to `[0.4, 2.0]`.
factory SparkleShapeSpec.customPolygon(
{int sides = 6, double aspectRatio = 1.0, double rotation = 0.0}) {
return SparkleShapeSpec._(
kind: SparkleShapeKind.polygon,
primary: sides.toDouble().clamp(3.0, 10.0),
secondary: aspectRatio.clamp(0.4, 2.0),
tertiary: rotation,
);
}
/// Creates a custom rectangular sparkle.
///
/// Values are clamped to keep shapes visible and numerically stable.
factory SparkleShapeSpec.customRectangle(
{double halfWidth = 0.24,
double halfHeight = 0.055,
double rotationJitter = 1.0}) {
return SparkleShapeSpec._(
kind: SparkleShapeKind.rectangle,
primary: halfWidth.clamp(0.05, 0.45),
secondary: halfHeight.clamp(0.02, 0.30),
tertiary: rotationJitter.clamp(0.0, 1.0),
);
}
/// Creates a custom confetti sparkle.
///
/// Values are clamped to keep geometry inside the sparkle cell.
factory SparkleShapeSpec.customConfetti(
{double length = 0.34,
double thickness = 0.045,
double rotationJitter = 1.0}) {
return SparkleShapeSpec._(
kind: SparkleShapeKind.confetti,
primary: length.clamp(0.08, 0.48),
secondary: thickness.clamp(0.01, 0.14),
tertiary: rotationJitter.clamp(0.0, 1.0),
);
}
/// Sparkle family used by the shader.
final SparkleShapeKind kind;
/// Shape parameter 1. Meaning depends on [kind].
final double primary;
/// Shape parameter 2. Meaning depends on [kind].
final double secondary;
/// Shape parameter 3. Meaning depends on [kind].
final double tertiary;
}
/// Applies holographic shader lighting to any child widget.
@@ -197,8 +49,7 @@ class Shiny extends StatefulWidget {
this.specular = 0.8,
this.diffraction = 0.8,
this.opacity = 1.0,
this.sparkleShape = SparkleShapeSpec.none,
this.style = HolographStyle.crackedIce,
this.profile = ShinyProfile.crackedIce,
this.blendMode = BlendMode.screen,
this.enableShader = true,
});
@@ -227,11 +78,8 @@ class Shiny extends StatefulWidget {
/// Global shader opacity multiplier.
final double opacity;
/// Sparkle silhouette specification.
final SparkleShapeSpec sparkleShape;
/// Foil style preset.
final HolographStyle style;
final ShinyProfile profile;
/// Blend mode applied by [ShaderMask].
final BlendMode blendMode;
@@ -244,7 +92,8 @@ class Shiny extends StatefulWidget {
}
class _ShinyState extends State<Shiny> {
static Future<ui.FragmentProgram>? _programFuture;
static final Map<ShinyProfile, Future<ui.FragmentProgram>> _programFutures =
<ShinyProfile, Future<ui.FragmentProgram>>{};
ui.FragmentShader? _shader;
StreamSubscription<Offset>? _tiltSub;
@@ -269,30 +118,43 @@ class _ShinyState extends State<Shiny> {
}
if (oldWidget.enableShader != widget.enableShader) {
if (widget.enableShader) {
if (_shader == null) _loadShader();
_loadShader();
} else {
_shader?.dispose();
_shader = null;
}
}
if (oldWidget.profile != widget.profile && widget.enableShader) {
_loadShader();
}
}
Future<void> _loadShader() async {
final ShinyProfile profile = widget.profile;
try {
_programFuture ??= _loadProgram();
final ui.FragmentProgram program = await _programFuture!;
if (!mounted) return;
final Future<ui.FragmentProgram> future =
_programFutures.putIfAbsent(profile, () => _loadProgram(profile));
final ui.FragmentProgram program = await future;
if (!mounted || !widget.enableShader || widget.profile != profile) {
return;
}
final ui.FragmentShader shader = program.fragmentShader();
setState(() {
_shader = program.fragmentShader();
_shader?.dispose();
_shader = shader;
});
} catch (_) {
// Keep rendering without shader when runtime effects are unavailable.
}
}
Future<ui.FragmentProgram> _loadProgram() async {
Future<ui.FragmentProgram> _loadProgram(ShinyProfile profile) async {
final String localAsset = profile.shaderAsset;
try {
return await ui.FragmentProgram.fromAsset('shaders/shiny_card.frag');
return await ui.FragmentProgram.fromAsset(localAsset);
} catch (_) {
return await ui.FragmentProgram.fromAsset(
'packages/holo_shiny/shaders/shiny_card.frag');
'packages/holo_shiny/$localAsset');
}
}
@@ -331,12 +193,7 @@ class _ShinyState extends State<Shiny> {
..setFloat(5, widget.sparkle)
..setFloat(6, widget.specular)
..setFloat(7, widget.diffraction)
..setFloat(8, widget.style.index.toDouble())
..setFloat(9, widget.sparkleShape.kind.index.toDouble())
..setFloat(10, widget.sparkleShape.primary)
..setFloat(11, widget.sparkleShape.secondary)
..setFloat(12, widget.sparkleShape.tertiary)
..setFloat(13, widget.opacity);
..setFloat(8, widget.opacity);
return shader;
}
@@ -372,8 +229,7 @@ class ShinyCard extends StatefulWidget {
this.specular = 0.8,
this.diffraction = 0.8,
this.opacity = 1.0,
this.sparkleShape = SparkleShapeSpec.none,
this.style = HolographStyle.crackedIce,
this.profile = ShinyProfile.crackedIce,
this.enableShader = true,
});
@@ -410,11 +266,8 @@ class ShinyCard extends StatefulWidget {
/// Global shader opacity multiplier.
final double opacity;
/// Sparkle silhouette specification.
final SparkleShapeSpec sparkleShape;
/// Foil style preset.
final HolographStyle style;
final ShinyProfile profile;
/// Toggles shader execution.
final bool enableShader;
@@ -550,8 +403,7 @@ class _ShinyCardState extends State<ShinyCard>
specular: widget.specular,
diffraction: widget.diffraction,
opacity: widget.opacity,
sparkleShape: widget.sparkleShape,
style: widget.style,
profile: widget.profile,
child: base,
),
if (widget.foreground != null) widget.foreground!,