How to load GL shaders from a string?

I need to be able to load and build a GL shader from a .frag file containing the source for the shader. Since the shader will be provided post-compilation, I cannot use the currently accepted method that utilizes annotations.

However the code for this annotation appears to include a bunch of preprocessing that includes adding fields to the class.

Can these steps only be performed at compile time? Should this function’s code be split off into a utility function so that it can be called at runtime? Is there already a way of doing this that I don’t know of?

1 Like

Yes, see:https://github.com/rainyt/openfl-glsl

I’ve analyzed this sample project and it does pull from a text file, but it does so at compile time.

The text of the shader has to be loaded at runtime in my case (it’s being provided by the user).

class ZShader extends DisplayObjectShader {
     public function new(glVertexSource:Stirng,glFragmentSource:String) {
           	        this.glVertexSource = glVertexSource;
			this.glFragmentSource = glFragmentSource;
                        super();
     }
}

I used to do this in HTML5, but there will be errors in C + +. You can investigate.

I tried to make a custom shader class like so:

class CustomShader extends Shader
{
	public function new(fragshaderpath:String, vertshaderpath:String)
	{
		glVertexSource = Assets.getText(vertshaderpath);
		glFragmentSource = Assets.getText(fragshaderpath);

		super();

		__isGenerated = false;
		__initGL();
	}
}

However, this cannot be directly applied on haxeflixel’s FlxSprites (using the flxsprite.shader property). A workaround I used for this was to create an FlxCamera, apply it to the sprite, and then apply this custom shader to that camera (using flxcamera.setFilters). I used it as shown:

class PlayState extends FlxState
{
	var sampleSprite:FlxSprite;
	var mycustomshader:CustomShader;

	override public function create()
	{
		super.create();
		// initializing a sprite
		sampleSprite = new FlxSprite(50, 50);
		sampleSprite.frames = FlxAtlasFrames.fromSparrow('path/to/spritesheet', 'path/to/xml');
		sampleSprite.animation.addByPrefix("idle", 'example-prefix');
		sampleSprite.animation.play("idle");
		sampleSprite.setGraphicSize(200);

		// initializing a shader
		mycustomshader = new CustomShader("path/to/fragment-shader", "path/to/vertex-shader");
		mycustomshader.data.uTime.value = [0.0];

		// add an FlxCamera to sampleSprite
		var sampleSpriteCam = new FlxCamera(Std.int(sampleSprite.x), Std.int(sampleSprite.y), Std.int(sampleSprite.width), Std.int(sampleSprite.height));
		sampleSpriteCam.bgColor = FlxColor.TRANSPARENT;
		sampleSpriteCam.follow(sampleSprite);

		sampleSprite.offset.set();
		sampleSprite.updateHitbox();
		add(sampleSprite);

                // adding the custom shader to the camera
		sampleSprite.camera = sampleSpriteCam;
		sampleSpriteCam.setFilters([new ShaderFilter(cast mycustomshader)]);
		FlxG.cameras.add(sampleSpriteCam);
	}

	override public function update(elapsed:Float)
	{
		super.update(elapsed);
		mycustomshader.data.uTime.value[0] += elapsed;
	}
}

The vertex shader looked like this:

attribute float openfl_Alpha;
attribute vec4 openfl_ColorMultiplier;
attribute vec4 openfl_ColorOffset;
attribute vec4 openfl_Position;
attribute vec2 openfl_TextureCoord;

varying float openfl_Alphav;
varying vec4 openfl_ColorMultiplierv;
varying vec4 openfl_ColorOffsetv;
varying vec2 openfl_TextureCoordv;

uniform mat4 openfl_Matrix;
uniform bool openfl_HasColorTransform;
uniform vec2 openfl_TextureSize;

attribute float alpha;
attribute vec4 colorMultiplier;
attribute vec4 colorOffset;
uniform bool hasColorTransform;

void main(void)
{
    openfl_Alphav = openfl_Alpha;
    openfl_TextureCoordv = openfl_TextureCoord;

    if (openfl_HasColorTransform)
    {
        openfl_ColorMultiplierv = openfl_ColorMultiplier;
        openfl_ColorOffsetv = openfl_ColorOffset / 255.0;
    }

    gl_Position = openfl_Matrix * openfl_Position;

    openfl_Alphav = openfl_Alpha * alpha;		
    if (hasColorTransform)
    {
        openfl_ColorOffsetv = colorOffset / 255.0;
        openfl_ColorMultiplierv = colorMultiplier;
    }
}

And the fragment shader looked like this (this example fragment shader makes the sprite “blink”):

varying float openfl_Alphav;
varying vec4 openfl_ColorMultiplierv;
varying vec4 openfl_ColorOffsetv;
varying vec2 openfl_TextureCoordv;

uniform bool openfl_HasColorTransform;
uniform vec2 openfl_TextureSize;
uniform sampler2D bitmap;

uniform bool hasTransform;
uniform bool hasColorTransform;

vec4 flixel_texture2D(sampler2D bitmap, vec2 coord)
{
    vec4 color = texture2D(bitmap, coord);
    if (!hasTransform)
    {
        return color;
    }

    if (color.a == 0.0)
    {
        return vec4(0.0, 0.0, 0.0, 0.0);
    }

    if (!hasColorTransform)
    {
        return color * openfl_Alphav;
    }

    color = vec4(color.rgb / color.a, color.a);

    mat4 colorMultiplier = mat4(0);
    colorMultiplier[0][0] = openfl_ColorMultiplierv.x;
    colorMultiplier[1][1] = openfl_ColorMultiplierv.y;
    colorMultiplier[2][2] = openfl_ColorMultiplierv.z;
    colorMultiplier[3][3] = openfl_ColorMultiplierv.w;

    color = clamp(openfl_ColorOffsetv + (color * colorMultiplier), 0.0, 1.0);

    if (color.a > 0.0)
    {
        return vec4(color.rgb * color.a * openfl_Alphav, color.a * openfl_Alphav);
    }
    return vec4(0.0, 0.0, 0.0, 0.0);
}

uniform float uTime;

void main(void)
{
    vec4 color = flixel_texture2D(bitmap, fract((openfl_TextureCoordv+uTime*0.1)*uTime));
    gl_FragColor = color;
}

I had to explicitly declare some uniforms and attributes that were used internally (such as openfl_Alphav), which would usually be done automatically at compile time, however I think some modifications to the CustomShader class could solve that.

It seems that you can’t work in C++. Have you tried?

When I tested it using lime test windows it did work for me

1 Like