Files
CodenameEngine/source/funkin/backend/shaders/FunkinShader.hx
T
2024-09-16 01:10:36 +02:00

558 lines
15 KiB
Haxe

package funkin.backend.shaders;
import haxe.Exception;
import hscript.IHScriptCustomBehaviour;
import flixel.system.FlxAssets.FlxShader;
import lime.utils.Float32Array;
import openfl.display.ShaderParameterType;
import openfl.display.ShaderParameter;
import openfl.display.BitmapData;
import openfl.display.ShaderInput;
using StringTools;
@:access(openfl.display3D.Context3D)
@:access(openfl.display3D.Program3D)
@:access(openfl.display.ShaderInput)
@:access(openfl.display.ShaderParameter)
class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
private static var __instanceFields = Type.getInstanceFields(FunkinShader);
public var glslVer:String = "120";
/**
* Creates a new shader from the specified fragment and vertex source.
* Accepts `#pragma header`.
* @param frag Fragment source (pass `null` to use default)
* @param vert Vertex source (pass `null` to use default)
* @param glslVer Version of GLSL to use (defaults to 120)
*/
public override function new(frag:String, vert:String, glslVer:String = "120") {
if (frag == null) frag = ShaderTemplates.defaultFragmentSource;
if (vert == null) vert = ShaderTemplates.defaultVertexSource;
this.glFragmentSource = frag;
this.glVertexSource = vert;
this.glslVer = glslVer;
super();
}
@: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);
if (value != __glFragmentSource)
{
__glSourceDirty = true;
}
return __glFragmentSource = value;
}
@:noCompletion override private function set_glVertexSource(value:String):String
{
if(value == null)
value = ShaderTemplates.defaultVertexSource;
value = value.replace("#pragma header", ShaderTemplates.vertHeader).replace("#pragma body", ShaderTemplates.vertBody);
if (value != __glVertexSource)
{
__glSourceDirty = true;
}
return __glVertexSource = value;
}
@:noCompletion private override function __initGL():Void
{
if (__glSourceDirty || __paramBool == null)
{
__glSourceDirty = false;
program = null;
__inputBitmapData = new Array();
__paramBool = new Array();
__paramFloat = new Array();
__paramInt = new Array();
__processGLData(glVertexSource, "attribute");
__processGLData(glVertexSource, "uniform");
__processGLData(glFragmentSource, "uniform");
}
if (__context != null && program == null)
{
var prefix = '#version ${glslVer}\n';
var gl = __context.gl;
prefix += "#ifdef GL_ES
"
+ (precisionHint == FULL ? "#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif" : "precision lowp float;")
+ "
#endif
";
var vertex = prefix + glVertexSource;
var fragment = prefix + glFragmentSource;
var id = vertex + fragment;
if (__context.__programs.exists(id))
{
program = __context.__programs.get(id);
}
else
{
program = __context.createProgram(GLSL);
program.__glProgram = __createGLProgram(vertex, fragment);
__context.__programs.set(id, program);
}
if (program != null)
{
glProgram = program.__glProgram;
for (input in __inputBitmapData)
{
if (input.__isUniform)
{
input.index = gl.getUniformLocation(glProgram, input.name);
}
else
{
input.index = gl.getAttribLocation(glProgram, input.name);
}
}
for (parameter in __paramBool)
{
if (parameter.__isUniform)
{
parameter.index = gl.getUniformLocation(glProgram, parameter.name);
}
else
{
parameter.index = gl.getAttribLocation(glProgram, parameter.name);
}
}
for (parameter in __paramFloat)
{
if (parameter.__isUniform)
{
parameter.index = gl.getUniformLocation(glProgram, parameter.name);
}
else
{
parameter.index = gl.getAttribLocation(glProgram, parameter.name);
}
}
for (parameter in __paramInt)
{
if (parameter.__isUniform)
{
parameter.index = gl.getUniformLocation(glProgram, parameter.name);
}
else
{
parameter.index = gl.getAttribLocation(glProgram, parameter.name);
}
}
}
// initInstance(vertex, fragment); // btw make sure to disable the prefixes for ._isInstance
}
}
@:noCompletion private override function __processGLData(source:String, storageType:String):Void
{
var lastMatch = 0, position, regex, name, type;
if (storageType == "uniform")
{
regex = ~/uniform ([A-Za-z0-9]+) ([A-Za-z0-9_]+)/;
}
else
{
regex = ~/attribute ([A-Za-z0-9]+) ([A-Za-z0-9_]+)/;
}
while (regex.matchSub(source, lastMatch))
{
type = regex.matched(1);
name = regex.matched(2);
if (StringTools.startsWith(name, "gl_"))
{
continue;
}
var isUniform = (storageType == "uniform");
if (StringTools.startsWith(type, "sampler"))
{
var input = new ShaderInput<BitmapData>();
input.name = name;
@:privateAccess
input.__isUniform = isUniform;
__inputBitmapData.push(input);
switch (name)
{
case "openfl_Texture":
__texture = input;
case "bitmap":
__bitmap = input;
default:
}
Reflect.setField(__data, name, input);
try{Reflect.setField(this, name, input);} catch(e) {}
}
else if (!Reflect.hasField(__data, name) || Reflect.field(__data, name) == null)
{
var parameterType:ShaderParameterType = switch (type)
{
case "bool": BOOL;
case "double", "float": FLOAT;
case "int", "uint": INT;
case "bvec2": BOOL2;
case "bvec3": BOOL3;
case "bvec4": BOOL4;
case "ivec2", "uvec2": INT2;
case "ivec3", "uvec3": INT3;
case "ivec4", "uvec4": INT4;
case "vec2", "dvec2": FLOAT2;
case "vec3", "dvec3": FLOAT3;
case "vec4", "dvec4": FLOAT4;
case "mat2", "mat2x2": MATRIX2X2;
case "mat2x3": MATRIX2X3;
case "mat2x4": MATRIX2X4;
case "mat3x2": MATRIX3X2;
case "mat3", "mat3x3": MATRIX3X3;
case "mat3x4": MATRIX3X4;
case "mat4x2": MATRIX4X2;
case "mat4x3": MATRIX4X3;
case "mat4", "mat4x4": MATRIX4X4;
default: null;
}
var length = switch (parameterType)
{
case BOOL2, INT2, FLOAT2: 2;
case BOOL3, INT3, FLOAT3: 3;
case BOOL4, INT4, FLOAT4, MATRIX2X2: 4;
case MATRIX3X3: 9;
case MATRIX4X4: 16;
default: 1;
}
var arrayLength = switch (parameterType)
{
case MATRIX2X2: 2;
case MATRIX3X3: 3;
case MATRIX4X4: 4;
default: 1;
}
switch (parameterType)
{
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);
if (name == "openfl_HasColorTransform")
{
__hasColorTransform = parameter;
}
Reflect.setField(__data, name, parameter);
try{Reflect.setField(this, name, parameter);} catch(e) {}
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) {}
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);
if (StringTools.startsWith(name, "openfl_"))
{
switch (name)
{
case "openfl_Alpha": __alpha = parameter;
case "openfl_ColorMultiplier": __colorMultiplier = parameter;
case "openfl_ColorOffset": __colorOffset = parameter;
case "openfl_Matrix": __matrix = parameter;
case "openfl_Position": __position = parameter;
case "openfl_TextureCoord": __textureCoord = parameter;
case "openfl_TextureSize": __textureSize = parameter;
default:
}
}
Reflect.setField(__data, name, parameter);
try{Reflect.setField(this, name, parameter);} catch(e) {}
}
}
position = regex.matchedPos();
lastMatch = position.pos + position.len;
}
}
public function hget(name:String):Dynamic {
if (__instanceFields.contains(name) || __instanceFields.contains('get_${name}'))
return Reflect.getProperty(this, name);
if (!Reflect.hasField(data, name))
return null;
var field:Dynamic = Reflect.field(data, name);
var cl:String = Type.getClassName(Type.getClass(field));
// little problem we are facing boys...
// cant do "field is ShaderInput" because ShaderInput has the @:generic metadata
// aka instead of ShaderInput<Float> it gets built as ShaderInput_Float
// this should be fine tho because we check the class, and the fields dont vary based on the type
// thanks for looking in the code cne fans :D!! -lunar
if (cl.startsWith("openfl.display.ShaderParameter"))
return (field.__length > 1) ? field.value : field.value[0];
else if (cl.startsWith("openfl.display.ShaderInput"))
return field.input;
return field;
}
public function hset(name:String, val:Dynamic):Dynamic {
if (__instanceFields.contains(name) || __instanceFields.contains('set_${name}')) {
Reflect.setProperty(this, name, val);
return val;
}
if (!Reflect.hasField(data, name)) {
Reflect.setField(data, name, val);
return val;
}
var field:Dynamic = Reflect.field(data, name);
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;
// that means we wait for a single number, instead of an array
if (field.__isInt && !(val is Int))
throw new ShaderTypeException(name, Type.getClass(val), 'Int');
else if (field.__isBool && !(val is Bool))
throw new ShaderTypeException(name, Type.getClass(val), 'Bool');
else if (field.__isFloat && !(val is Float))
throw new ShaderTypeException(name, Type.getClass(val), 'Float');
return field.value = [val];
} else {
if (!(val is Array))
throw new ShaderTypeException(name, Type.getClass(val), Array);
return field.value = val;
}
} else if (cl.startsWith("openfl.display.ShaderInput")) {
// shader input!!
if (!(val is BitmapData)) {
throw new ShaderTypeException(name, Type.getClass(val), BitmapData);
}
field.input = cast val;
}
return val;
}
}
class ShaderTemplates {
public static final fragHeader:String = "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);
return applyFlixelEffects(color);
}
vec4 applyFlixelEffects(vec4 color) {
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.rgb = color.rgb / color.a;
color = clamp(openfl_ColorOffsetv + (color * openfl_ColorMultiplierv), 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 vec4 _camSize;
float map(float value, float min1, float max1, float min2, float max2) {
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}
vec2 getCamPos(vec2 pos) {
vec4 size = _camSize / vec4(openfl_TextureSize, openfl_TextureSize);
return vec2(map(pos.x, size.x, size.x + size.z, 0.0, 1.0), map(pos.y, size.y, size.y + size.w, 0.0, 1.0));
}
vec2 camToOg(vec2 pos) {
vec4 size = _camSize / vec4(openfl_TextureSize, openfl_TextureSize);
return vec2(map(pos.x, 0.0, 1.0, size.x, size.x + size.z), map(pos.y, 0.0, 1.0, size.y, size.y + size.w));
}
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 vertHeader:String = "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;";
public static final vertBody:String = "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;";
public static final defaultVertexSource:String = "#pragma header
attribute float alpha;
attribute vec4 colorMultiplier;
attribute vec4 colorOffset;
uniform bool hasColorTransform;
void main(void)
{
#pragma body
openfl_Alphav = openfl_Alpha * alpha;
if (hasColorTransform)
{
openfl_ColorOffsetv = colorOffset / 255.0;
openfl_ColorMultiplierv = colorMultiplier;
}
}";
// TODO: camera stuff
public static final defaultFragmentSource:String = "#pragma header
void main(void)
{
gl_FragColor = flixel_texture2D(bitmap, openfl_TextureCoordv);
}";
}
class ShaderTypeException extends Exception {
var has:Class<Dynamic>;
var want:Class<Dynamic>;
var name:String;
public function new(name:String, has:Class<Dynamic>, want:Dynamic) {
this.has = has;
this.want = want;
this.name = name;
super('ShaderTypeException - Tried to set the shader uniform "${name}" as a ${Type.getClassName(has)}, but the shader uniform is a ${Std.string(want)}.');
}
}