scriptable shaders!
Co-Authored-By: Ne_Eo <NeeEoo@users.noreply.github.com>
This commit is contained in:
@@ -8,3 +8,8 @@ pages/** linguist-vendored
|
||||
*.hxs linguist-language=Haxe
|
||||
*.hsc linguist-language=Haxe
|
||||
*.hscript linguist-language=Haxe
|
||||
|
||||
# Include Shader files as GLSL
|
||||
*.frag linguist-language=GLSL
|
||||
*.vert linguist-language=GLSL
|
||||
*.glsl linguist-language=GLSL
|
||||
@@ -0,0 +1,8 @@
|
||||
package funkin.backend.scripting.events.shader;
|
||||
|
||||
final class ShaderProcessEvent extends CancellableEvent
|
||||
{
|
||||
public var source:String;
|
||||
|
||||
public var storageType:String;
|
||||
}
|
||||
@@ -25,6 +25,7 @@ class CustomShader extends FunkinShader {
|
||||
var fragCode = Assets.exists(fragShaderPath) ? Assets.getText(fragShaderPath) : null;
|
||||
var vertCode = Assets.exists(vertShaderPath) ? Assets.getText(vertShaderPath) : null;
|
||||
|
||||
fileName = name;
|
||||
path = fragShaderPath+vertShaderPath;
|
||||
|
||||
if (fragCode == null && vertCode == null)
|
||||
|
||||
@@ -2,10 +2,12 @@ package funkin.backend.shaders;
|
||||
|
||||
import haxe.Exception;
|
||||
import hscript.IHScriptCustomBehaviour;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
|
||||
import lime.utils.Float32Array;
|
||||
|
||||
import openfl.display3D._internal.GLProgram;
|
||||
import openfl.display.ShaderParameterType;
|
||||
import openfl.display.ShaderParameter;
|
||||
import openfl.display.BitmapData;
|
||||
@@ -19,7 +21,15 @@ using StringTools;
|
||||
class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
private static var __instanceFields = Type.getInstanceFields(FunkinShader);
|
||||
|
||||
public var onGLUpdate:FlxTypedSignal<Void->Void> = new FlxTypedSignal<Void->Void>();
|
||||
public var onProcessGLData:FlxTypedSignal<(String, String)->Void> = new FlxTypedSignal<(String, String)->Void>();
|
||||
|
||||
public var glslVer:String = "120";
|
||||
public var fileName:String = "FunkinShader";
|
||||
|
||||
public var shaderPrefix:String = "";
|
||||
public var fragmentPrefix:String = "";
|
||||
public var vertexPrefix:String = "";
|
||||
|
||||
/**
|
||||
* Creates a new shader from the specified fragment and vertex source.
|
||||
@@ -38,11 +48,32 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
super();
|
||||
}
|
||||
|
||||
static var IMPORT_REGEX = ~/#import\s+<(.*)>/;
|
||||
|
||||
private function processImports(value:String):String
|
||||
{
|
||||
while(IMPORT_REGEX.match(value))
|
||||
{
|
||||
var importPath = IMPORT_REGEX.matched(1);
|
||||
var importSource = Assets.getText("assets/shaders/" + importPath);
|
||||
if(importSource == null) {
|
||||
Logs.traceColored([
|
||||
Logs.logText('[Shader] ', RED),
|
||||
Logs.logText('Failed to import shader ${importPath} in ${fileName}', RED),
|
||||
]);
|
||||
} else {
|
||||
value = value.replace(IMPORT_REGEX.matched(0), importSource);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@:noCompletion override private function set_glFragmentSource(value:String):String
|
||||
{
|
||||
if(value == null)
|
||||
value = ShaderTemplates.defaultFragmentSource;
|
||||
value = value.replace("#pragma header", ShaderTemplates.fragHeader).replace("#pragma body", ShaderTemplates.fragBody);
|
||||
value = processImports(value);
|
||||
if (value != __glFragmentSource)
|
||||
{
|
||||
__glSourceDirty = true;
|
||||
@@ -56,6 +87,7 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
if(value == null)
|
||||
value = ShaderTemplates.defaultVertexSource;
|
||||
value = value.replace("#pragma header", ShaderTemplates.vertHeader).replace("#pragma body", ShaderTemplates.vertBody);
|
||||
value = processImports(value);
|
||||
if (value != __glVertexSource)
|
||||
{
|
||||
__glSourceDirty = true;
|
||||
@@ -64,6 +96,29 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
return __glVertexSource = value;
|
||||
}
|
||||
|
||||
override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram
|
||||
{
|
||||
var program:GLProgram = null;
|
||||
try
|
||||
{
|
||||
program = super.__createGLProgram(vertexSource, fragmentSource);
|
||||
}
|
||||
catch (error:Dynamic)
|
||||
{
|
||||
Logs.traceColored([
|
||||
Logs.logText('[Shader] ', BLUE),
|
||||
Logs.logText('Failed to compile shader ${fileName}: ', RED),
|
||||
Logs.logText(Std.string(error))
|
||||
], TRACE);
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
override function __updateGL():Void {
|
||||
onGLUpdate.dispatch();
|
||||
super.__updateGL();
|
||||
}
|
||||
|
||||
@:noCompletion private override function __initGL():Void
|
||||
{
|
||||
if (__glSourceDirty || __paramBool == null)
|
||||
@@ -83,7 +138,7 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
|
||||
if (__context != null && program == null)
|
||||
{
|
||||
var prefix = '#version ${glslVer}\n';
|
||||
var prefix = '#version ${glslVer}\n' + shaderPrefix;
|
||||
|
||||
var gl = __context.gl;
|
||||
|
||||
@@ -98,8 +153,8 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
#endif
|
||||
";
|
||||
|
||||
var vertex = prefix + glVertexSource;
|
||||
var fragment = prefix + glFragmentSource;
|
||||
var vertex = prefix + vertexPrefix + glVertexSource;
|
||||
var fragment = prefix + fragmentPrefix + glFragmentSource;
|
||||
|
||||
var id = vertex + fragment;
|
||||
|
||||
@@ -178,8 +233,12 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
}
|
||||
}
|
||||
|
||||
var __cancelNextProcessGLData:Bool = false;
|
||||
@:noCompletion private override function __processGLData(source:String, storageType:String):Void
|
||||
{
|
||||
onProcessGLData.dispatch(source, storageType);
|
||||
if (__cancelNextProcessGLData != (__cancelNextProcessGLData = false))
|
||||
return;
|
||||
var lastMatch = 0, position, regex, name, type;
|
||||
|
||||
if (storageType == "uniform")
|
||||
@@ -202,12 +261,19 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
}
|
||||
|
||||
var isUniform = (storageType == "uniform");
|
||||
registerParameter(name, type, isUniform);
|
||||
|
||||
position = regex.matchedPos();
|
||||
lastMatch = position.pos + position.len;
|
||||
}
|
||||
}
|
||||
|
||||
function registerParameter(name:String, type:String, isUniform:Bool):Void
|
||||
{
|
||||
if (StringTools.startsWith(type, "sampler"))
|
||||
{
|
||||
var input = new ShaderInput<BitmapData>();
|
||||
input.name = name;
|
||||
@:privateAccess
|
||||
input.__isUniform = isUniform;
|
||||
__inputBitmapData.push(input);
|
||||
|
||||
@@ -274,15 +340,10 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
case BOOL, BOOL2, BOOL3, BOOL4:
|
||||
var parameter = new ShaderParameter<Bool>();
|
||||
parameter.name = name;
|
||||
@:privateAccess
|
||||
parameter.type = parameterType;
|
||||
@:privateAccess
|
||||
parameter.__arrayLength = arrayLength;
|
||||
@:privateAccess
|
||||
parameter.__isBool = true;
|
||||
@:privateAccess
|
||||
parameter.__isUniform = isUniform;
|
||||
@:privateAccess
|
||||
parameter.__length = length;
|
||||
__paramBool.push(parameter);
|
||||
|
||||
@@ -297,17 +358,11 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
case INT, INT2, INT3, INT4:
|
||||
var parameter = new ShaderParameter<Int>();
|
||||
parameter.name = name;
|
||||
@:privateAccess
|
||||
parameter.type = parameterType;
|
||||
@:privateAccess
|
||||
parameter.__arrayLength = arrayLength;
|
||||
@:privateAccess
|
||||
parameter.__isInt = true;
|
||||
@:privateAccess
|
||||
parameter.__isUniform = isUniform;
|
||||
@:privateAccess
|
||||
parameter.__length = length;
|
||||
@:privateAccess
|
||||
__paramInt.push(parameter);
|
||||
Reflect.setField(__data, name, parameter);
|
||||
try{Reflect.setField(this, name, parameter);} catch(e) {}
|
||||
@@ -315,19 +370,13 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
default:
|
||||
var parameter = new ShaderParameter<Float>();
|
||||
parameter.name = name;
|
||||
@:privateAccess
|
||||
parameter.type = parameterType;
|
||||
@:privateAccess
|
||||
parameter.__arrayLength = arrayLength;
|
||||
#if lime
|
||||
@:privateAccess
|
||||
if (arrayLength > 0) parameter.__uniformMatrix = new Float32Array(arrayLength * arrayLength);
|
||||
#end
|
||||
@:privateAccess
|
||||
parameter.__isFloat = true;
|
||||
@:privateAccess
|
||||
parameter.__isUniform = isUniform;
|
||||
@:privateAccess
|
||||
parameter.__length = length;
|
||||
__paramFloat.push(parameter);
|
||||
|
||||
@@ -350,10 +399,6 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
try{Reflect.setField(this, name, parameter);} catch(e) {}
|
||||
}
|
||||
}
|
||||
|
||||
position = regex.matchedPos();
|
||||
lastMatch = position.pos + position.len;
|
||||
}
|
||||
}
|
||||
|
||||
public function hget(name:String):Dynamic {
|
||||
@@ -394,7 +439,6 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
var cl:String = Type.getClassName(Type.getClass(field));
|
||||
// cant do "field is ShaderInput" for some reason
|
||||
if (cl.startsWith("openfl.display.ShaderParameter")) {
|
||||
@:privateAccess
|
||||
if (field.__length <= 1) {
|
||||
if ((val is Array))
|
||||
return field.value = val;
|
||||
@@ -457,7 +501,14 @@ vec4 flixel_texture2D(sampler2D bitmap, vec2 coord)
|
||||
}
|
||||
|
||||
color = vec4(color.rgb / color.a, color.a);
|
||||
color = clamp(openfl_ColorOffsetv + (color * openfl_ColorMultiplierv), 0.0, 1.0);
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -484,9 +535,39 @@ vec4 textureCam(sampler2D bitmap, vec2 pos) {
|
||||
return flixel_texture2D(bitmap, camToOg(pos));
|
||||
}";
|
||||
|
||||
public static final fragBody:String = "vec4 color = flixel_texture2D (bitmap, openfl_TextureCoordv);
|
||||
gl_FragColor = color;
|
||||
";
|
||||
public static final fragBody:String = "vec4 color = texture2D (bitmap, openfl_TextureCoordv);
|
||||
|
||||
if (color.a == 0.0) {
|
||||
|
||||
gl_FragColor = vec4 (0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
} else if (openfl_HasColorTransform) {
|
||||
|
||||
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] = 1.0; // openfl_ColorMultiplierv.w;
|
||||
|
||||
color = clamp (openfl_ColorOffsetv + (color * colorMultiplier), 0.0, 1.0);
|
||||
|
||||
if (color.a > 0.0) {
|
||||
|
||||
gl_FragColor = vec4 (color.rgb * color.a * openfl_Alphav, color.a * openfl_Alphav);
|
||||
|
||||
} else {
|
||||
|
||||
gl_FragColor = vec4 (0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
gl_FragColor = color * openfl_Alphav;
|
||||
|
||||
}";
|
||||
public static final vertHeader:String = "attribute float openfl_Alpha;
|
||||
attribute vec4 openfl_ColorMultiplier;
|
||||
attribute vec4 openfl_ColorOffset;
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package funkin.backend.shaders;
|
||||
|
||||
import hscript.IHScriptCustomBehaviour;
|
||||
import funkin.backend.scripting.events.shader.ShaderProcessEvent;
|
||||
import funkin.backend.scripting.ScriptPack;
|
||||
import funkin.backend.scripting.Script;
|
||||
|
||||
@:structInit
|
||||
class FieldInfo {
|
||||
public var get:Void->Dynamic;
|
||||
public var set:Dynamic->Dynamic;
|
||||
}
|
||||
|
||||
@:access(funkin.backend.shaders.FunkinShader)
|
||||
class ScriptableShader extends FlxBasic implements IHScriptCustomBehaviour {
|
||||
private static var __instanceFields = Type.getInstanceFields(ScriptableShader);
|
||||
|
||||
public var shader:FunkinShader;
|
||||
public var script:Script;
|
||||
|
||||
var fields:Map<String, FieldInfo> = [];
|
||||
|
||||
public function new(shader:FunkinShader, ?scriptName:String, ?parentScriptPack:ScriptPack) {
|
||||
super();
|
||||
|
||||
if(parentScriptPack == null && PlayState.instance != null)
|
||||
parentScriptPack = PlayState.instance.scripts;
|
||||
|
||||
if(scriptName == null)
|
||||
if((shader is CustomShader)) scriptName = cast(shader, CustomShader).fileName;
|
||||
else throw "Missing name for shader script, please provide a scriptName, or use CustomShader";
|
||||
|
||||
script = Script.create(Paths.script('shaders/$scriptName'));
|
||||
script.setParent(shader);
|
||||
script.set("shader", shader);
|
||||
script.set("registerParameter", shader.registerParameter);
|
||||
script.set("registerField", (name:String, get: Void->Dynamic, set: Dynamic->Dynamic) -> {
|
||||
registerField(name, get, set);
|
||||
});
|
||||
script.set("registerShaderField", (name:String, shaderField:String) -> {
|
||||
var get = () -> shader.hget(shaderField);
|
||||
var set = (value:Dynamic) -> {
|
||||
var val = shader.hset(shaderField, value);
|
||||
script.set(name, val);
|
||||
return val;
|
||||
};
|
||||
registerField(name, get, set);
|
||||
var gg = script.get(name);
|
||||
if(gg != null) {
|
||||
shader.hset(shaderField, gg);
|
||||
}
|
||||
});
|
||||
if(parentScriptPack != null)
|
||||
script.setPublicMap(parentScriptPack.publicVariables);
|
||||
script.load();
|
||||
script.call("create");
|
||||
|
||||
shader.onGLUpdate.add(updateGL);
|
||||
shader.onProcessGLData.add(processGLData);
|
||||
}
|
||||
|
||||
function registerField(name:String, get: Void->Dynamic, set: Dynamic->Dynamic) {
|
||||
fields.set(name, {
|
||||
get: get,
|
||||
set: set
|
||||
});
|
||||
script.set("get_"+name, get);
|
||||
script.set("set_"+name, set);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float) script.call("update", [elapsed]);
|
||||
public override function draw() script.call("draw", []);
|
||||
public function updateGL() script.call("updateGL", []);
|
||||
public function processGLData(source:String, storageType:String) {
|
||||
var event = EventManager.get(ShaderProcessEvent).recycle(source, storageType);
|
||||
script.call("processGLData", [event]);
|
||||
if(event.cancelled)
|
||||
shader.__cancelNextProcessGLData = true;
|
||||
script.call("processGLDataPost", [event]);
|
||||
}
|
||||
|
||||
public override function destroy() {
|
||||
shader.onGLUpdate.remove(updateGL);
|
||||
shader.onProcessGLData.remove(processGLData);
|
||||
if(script != null) {
|
||||
script.call("destroy");
|
||||
script.destroy();
|
||||
}
|
||||
fields = null;
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
public function hget(name:String):Dynamic {
|
||||
if(fields.exists(name))
|
||||
return fields.get(name).get();
|
||||
if (__instanceFields.contains(name) || __instanceFields.contains('get_${name}'))
|
||||
return Reflect.getProperty(this, name);
|
||||
return shader.hget(name);
|
||||
}
|
||||
|
||||
public function hset(name:String, val:Dynamic):Dynamic {
|
||||
if (fields.exists(name)) {
|
||||
return fields.get(name).set(val);
|
||||
}
|
||||
if (__instanceFields.contains(name) || __instanceFields.contains('set_${name}')) {
|
||||
Reflect.setProperty(this, name, val);
|
||||
return val;
|
||||
}
|
||||
return shader.hset(name, val);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user