diff --git a/flixel/addons/display/FlxRuntimeShader.hx b/flixel/addons/display/FlxRuntimeShader.hx index 8134f9c5..8f584785 100644 --- a/flixel/addons/display/FlxRuntimeShader.hx +++ b/flixel/addons/display/FlxRuntimeShader.hx @@ -5,7 +5,9 @@ package flixel.addons.display; #error "FlxRuntimeShader isn't available with nme or flash." #end #else +import flixel.addons.system.macros.FlxRuntimeShaderMacro; import flixel.graphics.tile.FlxGraphicsShader; +import flixel.util.FlxStringUtil; #if lime import lime.utils.Float32Array; #end @@ -28,140 +30,6 @@ using StringTools; */ class FlxRuntimeShader extends FlxGraphicsShader { - private static final BASE_VERTEX_HEADER: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;"; - - private static final BASE_VERTEX_BODY: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;"; - - private static final BASE_VERTEX_SOURCE: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; - } - }"; - - private static final BASE_FRAGMENT_HEADER: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); - - 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); - }"; - - private static final BASE_FRAGMENT_BODY: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; - }"; - - private static final BASE_FRAGMENT_SOURCE:String = "#pragma header - - void main(void) - { - gl_FragColor = flixel_texture2D(bitmap, openfl_TextureCoordv); - }"; - /** * Creates a `FlxRuntimeShader` with specified shader sources. * If none is provided, it will use the default shader sources. @@ -174,12 +42,12 @@ class FlxRuntimeShader extends FlxGraphicsShader if (fragmentSource != null && fragmentSource.length > 0) glFragmentSource = fragmentSource; else - glFragmentSource = BASE_FRAGMENT_SOURCE; + glFragmentSource = FlxRuntimeShaderMacro.retrieveMetadata('glFragmentSource', false); if (vertexSource != null && vertexSource.length > 0) glVertexSource = vertexSource; else - glVertexSource = BASE_VERTEX_SOURCE; + glVertexSource = FlxRuntimeShaderMacro.retrieveMetadata('glVertexSource', false); super(); } @@ -192,15 +60,15 @@ class FlxRuntimeShader extends FlxGraphicsShader */ public function setFloat(name:String, value:Float):Void { - var prop:ShaderParameter = Reflect.field(data, name); + final shaderParameter:ShaderParameter = Reflect.field(data, name); - if (prop == null) + if (shaderParameter == null) { - FlxG.log.warn('Shader float property "$name" not found.'); + FlxG.log.warn('Shader float parameter "$name" not found.'); return; } - prop.value = [value]; + shaderParameter.value = [value]; } /** @@ -210,15 +78,15 @@ class FlxRuntimeShader extends FlxGraphicsShader */ public function getFloat(name:String):Null { - var prop:ShaderParameter = Reflect.field(data, name); + final shaderParameter:ShaderParameter = Reflect.field(data, name); - if (prop == null) + if (shaderParameter == null) { - FlxG.log.warn('Shader float property "$name" not found.'); + FlxG.log.warn('Shader float parameter "$name" not found.'); return null; } - return prop.value[0]; + return shaderParameter.value[0]; } /** @@ -229,15 +97,15 @@ class FlxRuntimeShader extends FlxGraphicsShader */ public function setFloatArray(name:String, value:Array):Void { - var prop:ShaderParameter = Reflect.field(data, name); + final shaderParameter:ShaderParameter = Reflect.field(data, name); - if (prop == null) + if (shaderParameter == null) { - FlxG.log.warn('Shader float[] property "$name" not found.'); + FlxG.log.warn('Shader float[] parameter "$name" not found.'); return; } - prop.value = value; + shaderParameter.value = value; } /** @@ -247,15 +115,15 @@ class FlxRuntimeShader extends FlxGraphicsShader */ public function getFloatArray(name:String):Null> { - var prop:ShaderParameter = Reflect.field(data, name); + final shaderParameter:ShaderParameter = Reflect.field(data, name); - if (prop == null) + if (shaderParameter == null) { - FlxG.log.warn('Shader float[] property "$name" not found.'); + FlxG.log.warn('Shader float[] parameter "$name" not found.'); return null; } - return prop.value; + return shaderParameter.value; } /** @@ -266,15 +134,15 @@ class FlxRuntimeShader extends FlxGraphicsShader */ public function setInt(name:String, value:Int):Void { - var prop:ShaderParameter = Reflect.field(data, name); + final shaderParameter:ShaderParameter = Reflect.field(data, name); - if (prop == null) + if (shaderParameter == null) { - FlxG.log.warn('Shader int property "$name" not found.'); + FlxG.log.warn('Shader int parameter "$name" not found.'); return; } - prop.value = [value]; + shaderParameter.value = [value]; } /** @@ -284,15 +152,15 @@ class FlxRuntimeShader extends FlxGraphicsShader */ public function getInt(name:String):Null { - var prop:ShaderParameter = Reflect.field(data, name); + final shaderParameter:ShaderParameter = Reflect.field(data, name); - if (prop == null) + if (shaderParameter == null) { - FlxG.log.warn('Shader int property "$name" not found.'); + FlxG.log.warn('Shader int parameter "$name" not found.'); return null; } - return prop.value[0]; + return shaderParameter.value[0]; } /** @@ -303,15 +171,15 @@ class FlxRuntimeShader extends FlxGraphicsShader */ public function setIntArray(name:String, value:Array):Void { - var prop:ShaderParameter = Reflect.field(data, name); + final shaderParameter:ShaderParameter = Reflect.field(data, name); - if (prop == null) + if (shaderParameter == null) { - FlxG.log.warn('Shader int[] property "$name" not found.'); + FlxG.log.warn('Shader int[] parameter "$name" not found.'); return; } - prop.value = value; + shaderParameter.value = value; } /** @@ -321,15 +189,15 @@ class FlxRuntimeShader extends FlxGraphicsShader */ public function getIntArray(name:String):Null> { - var prop:ShaderParameter = Reflect.field(data, name); + final shaderParameter:ShaderParameter = Reflect.field(data, name); - if (prop == null) + if (shaderParameter == null) { - FlxG.log.warn('Shader int[] property "$name" not found.'); + FlxG.log.warn('Shader int[] parameter "$name" not found.'); return null; } - return prop.value; + return shaderParameter.value; } /** @@ -340,15 +208,15 @@ class FlxRuntimeShader extends FlxGraphicsShader */ public function setBool(name:String, value:Bool):Void { - var prop:ShaderParameter = Reflect.field(data, name); + final shaderParameter:ShaderParameter = Reflect.field(data, name); - if (prop == null) + if (shaderParameter == null) { - FlxG.log.warn('Shader bool property "$name" not found.'); + FlxG.log.warn('Shader bool parameter "$name" not found.'); return; } - prop.value = [value]; + shaderParameter.value = [value]; } /** @@ -358,15 +226,15 @@ class FlxRuntimeShader extends FlxGraphicsShader */ public function getBool(name:String):Null { - var prop:ShaderParameter = Reflect.field(data, name); + final shaderParameter:ShaderParameter = Reflect.field(data, name); - if (prop == null) + if (shaderParameter == null) { - FlxG.log.warn('Shader bool property "$name" not found.'); + FlxG.log.warn('Shader bool parameter "$name" not found.'); return null; } - return prop.value[0]; + return shaderParameter.value[0]; } /** @@ -377,15 +245,15 @@ class FlxRuntimeShader extends FlxGraphicsShader */ public function setBoolArray(name:String, value:Array):Void { - var prop:ShaderParameter = Reflect.field(data, name); + final shaderParameter:ShaderParameter = Reflect.field(data, name); - if (prop == null) + if (shaderParameter == null) { - FlxG.log.warn('Shader bool[] property "$name" not found.'); + FlxG.log.warn('Shader bool[] parameter "$name" not found.'); return; } - prop.value = value; + shaderParameter.value = value; } /** @@ -395,57 +263,65 @@ class FlxRuntimeShader extends FlxGraphicsShader */ public function getBoolArray(name:String):Null> { - var prop:ShaderParameter = Reflect.field(data, name); + final shaderParameter:ShaderParameter = Reflect.field(data, name); - if (prop == null) + if (shaderParameter == null) { - FlxG.log.warn('Shader bool[] property "$name" not found.'); + FlxG.log.warn('Shader bool[] parameter "$name" not found.'); return null; } - return prop.value; + return shaderParameter.value; } /** - * Modify a bitmap data parameter of the shader. + * Modify a bitmap data input of the shader. * * @param name The name of the parameter to modify. * @param value The new value to use. */ public function setSampler2D(name:String, value:BitmapData):Void { - var prop:ShaderInput = Reflect.field(data, name); + final shaderInput:ShaderInput = Reflect.field(data, name); - if (prop == null) + if (shaderInput == null) { - FlxG.log.warn('Shader sampler2D property "$name" not found.'); + FlxG.log.warn('Shader sampler2D input "$name" not found.'); return; } - prop.input = value; + shaderInput.input = value; } /** - * Retrieve a bitmap data parameter of the shader. + * Retrieve a bitmap data input of the shader. * * @param name The name of the parameter to retrieve. * @return The value of the parameter. */ public function getSampler2D(name:String):Null { - var prop:ShaderInput = Reflect.field(data, name); + final shaderInput:ShaderInput = Reflect.field(data, name); - if (prop == null) + if (shaderInput == null) { - FlxG.log.warn('Shader sampler2D property "$name" not found.'); + FlxG.log.warn('Shader sampler2D input "$name" not found.'); return null; } - return prop.input; + return shaderInput.input; + } + + /** + * Convert the shader to a readable string name. Useful for debugging. + */ + public function toString():String + { + return FlxStringUtil.getDebugString([for (field in Reflect.fields(data)) LabelValuePair.weak(field, Reflect.field(data, field))]); } - // Overrides - @:noCompletion private override function __processGLData(source:String, storageType:String):Void + @:noCompletion + private override function __processGLData(source:String, storageType:String):Void { var lastMatch = 0, position, regex, name, type; @@ -630,10 +506,11 @@ class FlxRuntimeShader extends FlxGraphicsShader } } - @:noCompletion private override function set_glFragmentSource(value:String):String + @:noCompletion + private override function set_glFragmentSource(value:String):String { if (value != null) - value = value.replace("#pragma header", BASE_FRAGMENT_HEADER).replace("#pragma body", BASE_FRAGMENT_BODY); + value = value.replace("#pragma header", FlxRuntimeShaderMacro.retrieveMetadata('glFragmentHeader')).replace("#pragma body", FlxRuntimeShaderMacro.retrieveMetadata('glFragmentBody')); if (value != __glFragmentSource) __glSourceDirty = true; @@ -641,10 +518,11 @@ class FlxRuntimeShader extends FlxGraphicsShader return __glFragmentSource = value; } - @:noCompletion private override function set_glVertexSource(value:String):String + @:noCompletion + private override function set_glVertexSource(value:String):String { if (value != null) - value = value.replace("#pragma header", BASE_VERTEX_HEADER).replace("#pragma body", BASE_VERTEX_BODY); + value = value.replace("#pragma header", FlxRuntimeShaderMacro.retrieveMetadata('glVertexHeader')).replace("#pragma body", FlxRuntimeShaderMacro.retrieveMetadata('glVertexBody')); if (value != __glVertexSource) __glSourceDirty = true; diff --git a/flixel/addons/system/macros/FlxRuntimeShaderMacro.hx b/flixel/addons/system/macros/FlxRuntimeShaderMacro.hx new file mode 100644 index 00000000..91c9630b --- /dev/null +++ b/flixel/addons/system/macros/FlxRuntimeShaderMacro.hx @@ -0,0 +1,78 @@ +package flixel.addons.system.macros; + +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.Type; + +using haxe.macro.ExprTools; + +class FlxRuntimeShaderMacro +{ + /** + * Retrieves the value of a specified metadata tag (only `String` values) from the current class + * or any of its superclasses. The function searches the fields of the class + * for the specified metadata and returns its value as a macro expression. + * If `overwrite` is set to `false`, it will use the first non-null `String` value + * found and ignore any subsequent values. + * + * @param metaName The name of the metadata tag to retrieve. + * @param overwrite If `true`, the metadata value will be concatenated when found; if `false`, only the first non-null value will be used. + * @return The value of the specified metadata as an expression, or `null` if not found. + */ + public static macro function retrieveMetadata(metaName:String, overwrite:Bool = true):Expr + { + var result:String = null; + + final localClass:ClassType = Context.getLocalClass().get(); + + result = checkClassForMetadata(localClass, metaName, overwrite, result); + + var parent:ClassType = localClass.superClass != null ? localClass.superClass.t.get() : null; + + while (parent != null) + { + result = checkClassForMetadata(parent, metaName, overwrite, result); + + parent = parent.superClass != null ? parent.superClass.t.get() : null; + } + + // Context.info('Retrieving $metaName: $result', Context.currentPos()); + + return macro $v{result}; + } + + #if macro + @:noCompletion + private static function checkClassForMetadata(classType:ClassType, metaName:String, overwrite:Bool, currentResult:String):String + { + var result:String = currentResult; + + for (field in [classType.constructor.get()].concat(classType.fields.get())) + { + for (meta in field.meta.get()) + { + if (meta.name == metaName || meta.name == ':' + metaName) + { + final value:Dynamic = meta.params[0].getValue(); + + if (!(value is String)) + continue; + + if (overwrite) + result = result == null ? value : '$value\n$result'; + else if (result == null) + { + result = value; + break; + } + } + } + + if (!overwrite && result != null) + break; + } + + return result; + } + #end +}