# DSL Kotlin pour les shaders AGSL sur Android : Approche typée
Le développement de shaders AGSL pour Android avec RuntimeShader et RenderEffect bute souvent sur des obstacles comme les littéraux en chaîne, le bind manuel des uniforms et un faible support IDE. Le DSL Kotlin résout ces problèmes en générant du code AGSL natif sans perte de performance. La bibliothèque transforme des constructions typées en shaders prêts à l'emploi tout en conservant l'accès au AGSL brut via agslSource().
Cela vous permet de vous concentrer sur la logique des effets plutôt que sur la syntaxe des chaînes. Compatible API 33+, intégration avec Compose, et bibliothèque standard de primitives procédurales.
Points douloureux du AGSL brut en Kotlin
Les shaders sous forme de chaînes perdent les avantages de l'IDE comme l'autocomplétion, le refactoring et la vérification de types. Les uniforms sont déclarés manuellement — une incohérence de nom fait planter l'app. Les effets complexes avec smoothstep, fract et mix deviennent des monstres illisibles. En Compose, vous ajoutez de la boilerplate pour le temps et les mises à jour.
Le DSL supprime ces obstacles : les uniforms sont typés, les calculs intermédiaires nommés via let(), et les fonctions standard appelées de manière déclarative.
Ajout de la bibliothèque
Ajoutez ces dépendances à votre build.gradle :
dependencies {
implementation("io.github.i-redbyte:redbytefx-core:1.0.0")
implementation("io.github.i-redbyte:redbytefx-compose:1.0.0")
implementation("io.github.i-redbyte:redbytefx-stdlib:1.0.0")
}
Comparaison : Déplacement ondulatoire
AGSL brut
uniform shader content;
uniform float wave_amplitude;
uniform float wave_frequency;
half4 main(float2 fragCoord) {
float2 offset = float2(
0.0,
sin(fragCoord.x * wave_frequency) * wave_amplitude
);
return content.eval(fragCoord + offset);
}
DSL Kotlin
val effect = redbytefx {
val amplitudeUniform = uniformFloat(0f, "wave_amplitude")
val frequencyUniform = uniformFloat(0.08f, "wave_frequency")
val x = let(fragCoord.x, "x")
val waveOffset = let(
float2(0f, sin(x * frequencyUniform) * amplitudeUniform),
"wave_offset"
)
sample(fragCoord + waveOffset)
}
Le AGSL généré correspond exactement à la logique, mais votre code source est typé et lisible.
Exemples d'effets par complexité
1. Onde : Effet de base
Simple décalage Y avec sin. Idéal pour apprendre fragCoord, sample(), uniforms et let() pour nommer les étapes sans complexité superflue.
2. Signal : Fonctions et masques
val effect = redbytefx {
val densityUniform = uniformFloat(8f, "signal_density")
val lineWidthUniform = uniformFloat(0.08f, "signal_line_width")
val amountUniform = uniformFloat(0.85f, "signal_amount")
val pulseBand = fn(
name = "pulse_band",
arg1 = FloatType,
arg2 = FloatType,
returns = FloatType
) { phase, threshold ->
step(threshold, smoothstep(0.08f, 0.92f, fract(phase)))
}
val base = let(sample(), "base")
val uv = let(normalizedUv(), "uv")
val grid = let(gridMask(uv, densityUniform, lineWidthUniform), "grid")
val scan = let(scanlines(fragCoord.y, 14f, 3f), "scan")
val pulse = let(pulseBand(uv.y * densityUniform * 0.5f + grid * 0.35f, 0.55f), "pulse")
val hardMask = let(step(0.45f, scan * pulse), "hard_mask")
val active = let((grid gt 0.05f) or (hardMask gt 0.5f), "active")
val accent = let(color(float3(0.05f, 0.95f, 0.82f), base.a), "accent")
val mixed = let(mix(base, accent, min(grid * 0.85f + hardMask * 0.35f, 1f)), "mixed")
ifElse(active, mix(base, mixed, amountUniform), base)
}
fn() extrait des fonctions personnalisées, gridMask() et scanlines() de la stdlib simplifient les masques. ifElse() garde un style déclaratif.
3. Radar : Coordonnées polaires
val effect = redbytefx {
val time by autoUniformTime()
val speed by autoUniformFloat(0.72f)
val radius by autoUniformFloat(0.34f)
val amount by autoUniformFloat(0.86f)
val base = let(sample(), "base")
val uv = let(fragCoord / resolution, "uv")
val polar = let(polarCoordinates(uv), "polar")
val sweepAngle = let(fract(time * speed * 0.08f), "sweep_angle")
val sweep = let(angularSweep(uv = uv, angle = sweepAngle, width = 0.12f, feather = 0.03f), "sweep")
val arc = let(
arcMask(
uv = uv,
radius = radius,
ringWidth = 0.09f,
angle = sweepAngle,
arcWidth = 0.18f,
feather = 0.03f
),
"arc"
)
val outerRing = let(ringMask(uv, radius = radius, width = 0.016f, feather = 0.012f), "outer_ring")
val innerRing = let(ringMask(uv, radius = max(radius * 0.58f, 0.08f), width = 0.014f, feather = 0.012f), "inner_ring")
val beam = let(radialRamp(uv = uv, innerRadius = float(0.06f), outerRadius = radius + 0.18f), "beam")
val mask = let(max(max(sweep * beam, arc), max(outerRing, innerRing)), "mask")
val tint = let(
color(
mix(0.05f, 0.18f, polar.x * 1.4f),
mix(0.24f, 1f, sweep + arc * 0.55f),
mix(0.10f, 0.62f, polar.y * 0.45f + outerRing * 0.35f),
base.a
),
"tint"
)
val screened = let(maskedScreen(base, tint, mask, amount), "screened")
maskedOverlay(screened, color(float3(0.82f, 1f, 0.72f), base.a), arc, amount * 0.32f)
}
polarCoordinates(), arcMask() et ringMask() de la stdlib remplacent les chaînes de smoothstep manuelles. maskedScreen() et maskedOverlay() simplifient le blending.
4. Metaballs : Formes procédurales
Cet effet construit des cercles SDF avec un min lissé pour des blobs néon. Le DSL gère des effets procéduraux complets tout en gardant la géométrie complexe lisible.
Points clés
- Le DSL Kotlin génère du AGSL natif sans surcharge — vérifiez via agslSource().
- Uniforms typés et let() corrigent les maux du code en chaînes et du refactoring.
- Bibliothèque standard (gridMask, polarCoordinates, arcMask) gagne du temps sur les primitives.
- fn() et ifElse() permettent la modularité sans impératif.
- Intégration Compose via autoUniformTime() et bind de paramètres.
— Editorial Team
Aucun commentaire pour le moment.