HGSL
A shading language that can be compiled into GLSL ES 3.0
Install / Use
/learn @saharan/HGSLREADME
HGSL: Haxe to GL Shading Language
What is this?
This library enables you to write GLSL ES 3.0 programs (especially for WebGL 2.0) with the full help of an IDE, including any kinds of completions and type checks.
You can also use features that are not included in GLSL ES 3.0: implicit type conversions, modularization of common logic and constants, inheritances, anonymous structures, and many other things.
Everything is done at compile time, so you can use this library just to obtain GLSL source codes, or you can integrate shader classes into your project if it uses Haxe.
Getting started
- Install Haxe 4.3.1 or later somewhere
- Install vshaxe in VSCode Marketplace and configure the Haxe path
- Create a Haxe project (
>Haxe: Initialize Project) - Copy
src/hgslto your source folder- or install HGSL using Haxelib by
haxelib install hgsl
- or install HGSL using Haxelib by
- Write your shaders!
How to use
Introduction
Import hgsl.Global.* for built-in functions and variables, hgsl.Types for built-in types. Note that importing hgsl.Types hides standard types (Int, UInt, Float, Bool, Array), so make sure not to import it in any non-shader source files.
A main shader class should extend hgsl.ShaderMain, and implement entry-point functions for both vertex and fragment shader.
import hgsl.Global.*;
import hgsl.Types;
import hgsl.ShaderMain;
class Shader extends ShaderMain {
function vertex():Void { // vertex shader entrypoint
}
function fragment():Void { // fragment shader entrypoint
}
}
- Define vertex attributes using
@attributemetadata. You can also specify layout location by@attribute(<location>). - Define uniforms using
@uniformmetadata. Defined uniforms can be used from both vertex shader part and fragment shader part. - Define varyings using
@varyingmetadata. You can specify types of varyings by@varying(flat)and@varying(centroid). - Define output colors using
@colormetadata. MRT (Multiple Render Targets) is available by setting location with@color(<location>).
import hgsl.Global.*;
import hgsl.Types;
import hgsl.ShaderMain;
class Shader extends ShaderMain {
@attribute(0) var position:Vec4;
@attribute(1) var color:Vec4;
@attribute(2) var texCoord:Vec2;
@uniform var transform:Mat4;
@uniform var colorTexture:Sampler2D;
@varying var vcolor:Vec4;
@varying(centroid) var vtexCoord:Vec2;
@color var ocolor:Vec4;
function vertex():Void { // vertex shader entrypoint
gl_Position = transform * position; // built-in output position
vcolor = color;
vtexCoord = texCoord;
}
function fragment():Void { // fragment shader entrypoint
ocolor = vcolor * texture(colorTexture, vtexCoord);
}
}
Modules
You can define a shader module that contains compile-time constants and functions by extending hgsl.ShaderModule class.
import hgsl.Global.*;
import hgsl.Types;
import hgsl.ShaderModule;
import hgsl.ShaderMain;
class Module extends ShaderModule {
final SOME_CONST_NUMBER:Int = 16;
final SOME_CONST_VECTOR:Vec3 = vec3(1, 2, 3); // you can use type constructors
final ANOTHER_CONST_NUMBER:Float = SOME_CONST_NUMBER * 2.0; // you can refer another const
// final SIN1:Float = sin(1); // ERROR! built-in functions are not compile-time constant
function someUtility(input:Vec3):Vec3 { // you can use this function from outside the module
var output:Vec3 = input * 2;
return output;
}
}
class Shader extends ShaderMain {
function vertex():Void {
Module.SOME_CONST_NUMBER; // use consts
var foo:Vec3 = Module.someUtility(vec3(1, 2, 3)); // and functions
}
function fragment():Void {
}
}
Automatic Typing
As long as a variable definition has an initial value, the type specification of the variable can be omitted.
import hgsl.Global.*;
import hgsl.Types;
import hgsl.ShaderModule;
class Module extends ShaderModule {
final INT_CONST = 16; // int
final FLOAT_CONST = 8.0; // float
final VEC3_CONST = vec3(1, 2, 3); // vec3
function func():Void {
var nonConstMatrix = mat3(1); // mat3x3, also available
// for non-const variables
// var foo; // ERROR! variable declaration without
// both initial value and type is illegal
}
// function func2(a, b, c) { // ERROR! return type and
// } // argument types cannot be omitted
}
var and final
var and final can be both used to declare a variable, but there are differences.
varcan be used to declare a mutable variablefinalcan be used to declare an immutable variable- A
finalvariable is NOT necessarily a compile-time constant - If a
finalvariable is initialized with a compile-time constant value, then it becomes a compile-time constant - You cannot use
finalto define a field of a structure
- A
Structures
You can define a named structure by making a class that extends hgsl.ShaderStruct.
import hgsl.Types;
import hgsl.ShaderStruct;
class Struct extends ShaderStruct {
var fieldA:Int;
var fieldB:Vec3;
var fieldC:Mat3x3;
}
Structures can be nested, but no circular reference is allowed.
import hgsl.Types;
import hgsl.ShaderStruct;
class Struct2 extends ShaderStruct {
var nestedStruct:Struct;
// var ng:Struct2; // ERROR! infinite recursion occurs
}
class Struct extends ShaderStruct {
var fieldA:Int;
var fieldB:Vec3;
var fieldC:Mat3x3;
// var ng:Struct2; // ERROR! this also creates an infinite loop
}
Anonymous structures can also be used everywhere.
import hgsl.Types;
import hgsl.ShaderStruct;
import hgsl.ShaderModule;
class Struct extends ShaderStruct {
var fieldA:Int;
var fieldB:Vec3;
var fieldC:{
var some:Vec2;
var nested:{
var fields:Float;
}
}
}
class Module extends ShaderModule {
function func(arg:{a:Int, b:Float}):{a:Int, b:Float} {
arg.a++;
return arg;
}
}
In fact, named structures are just type alias or syntax sugar of anonymous structures.
Arrays
Use Array<Type, Size> to refer an array type. Every array must be given a compile-time constant size.
import hgsl.Types;
import hgsl.ShaderStruct;
import hgsl.ShaderModule;
class Struct extends ShaderStruct {
var floats:Array<Float, 8>;
var vec3s:Array<Vec3, 4>;
var mat4s:Array<Mat4, Module.LENGTH>; // you can refer external values
// var ints:Array<Int, Module.length>; // ERROR! fields start with a
// lower-case alphabet cannot be used
}
class Module extends ShaderModule {
final LENGTH = 32;
final length = LENGTH; // ... even though it IS a compile-time constant :(
}
Limitation: due to the Haxe's grammar, you cannot use a field starts with a lower-case alphabet for an array size parameter, even if it is actually a compile-time constant.
Literal arrays and structures
You can use literal arrays and structures to generate a value of an array or structure. There is no constructor for them.
import hgsl.Types;
import hgsl.ShaderStruct;
import hgsl.ShaderModule;
class Struct extends ShaderStruct {
var ints:Array<Int, 3>;
var floats:Array<Float, 5>;
var structs:Array<{
a:Int,
b:Int
}, 2>;
}
class Module extends ShaderModule {
final CONST_STRUCT:Struct = {
ints: [1, 2, 3],
floats: [1.0, 2.0, 3.0, 4.0, 5.0],
structs: [{a: 1, b: 2}, {a: 3, b: 4}]
} // this is a compile-time constant
}
A literal array that consists only of compile-time constants will also be a compile-time constant value. Likewise, a literal structure that consists only of compile-time constant fields will also be a compile-time constant value.
If a certain type is expected, elements of a literal array will be implicitly converted.
var a = [1, 2, 3, 4, 5]; // int[5]
var b:Array<Float, 5> = [1, 2, 3, 4, 5]; // float[5], since floats are expected
// a = b; // ERROR! cannot assign one to the other,
// b = a; // ERROR! since Array is parameter invariant
If there is no expected type, the first element of an array will determine the expected type for the rest of the elements.
var a = [1.0, 2, 3, 4, 5]; // float[5]
var b = [uint(1), 2, 3, 4, 5]; // uint[5]
// var c = [1, 2.0, 3.0, 4.0, 5.0]; // ERROR! cannot convert floats to ints
This "expected type" rule also applies for structures.
var a:{x:Array<Float, 5>, y:UInt} = {
x: [1, 2, 3, 4, 5], // float[5], since floats are expected
y: 1 // uint, since uint is expected
}
var b = {
x: [1, 2, 3, 4, 5], // int[5]
y: 1 // int
}
// a = b; // ERROR! types are different
Unlike some other languages (including Haxe), the order of fields in a structure matters.
var a = {x: 1, y: 1}
var b = {y: 1, x: 1}
// a = b; // ERROR! types of these variables are different
This is mainly because the actual data on memory vary depending on the order of the fields, and there should be a way for users to determine the order of the fields (especially for uniform variables).
Inheritance
You can inherit a shader to make its variances.
import hgsl.Global.*;
import hgsl.Types;
import hgsl.ShaderMain;
class Shader extends ShaderMain {
function foo():Int {
return 1;
}
function vertex():Void {
foo(); // 1
}
function fragment():Void {
}
}
class ChildShader extends Shader {
function foo():Int { // override! make it return 2
return 2;
}
}
In the original shader (Shader), 1 is computed in the vertex part. However, since the function foo is overridden in the child shader(ChildShader), 2 is computed in the vertex part of the shader. Note that you do NOT need override qualifier to override a function.
class ChildShader extends Shader {
function foo():Int { // override! increase the value by one
return super.foo() + 1;
}
}
You can refer parent implementations by using the keyword super. In this ca
Related Skills
node-connect
349.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
109.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
349.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
349.0kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
