diff --git a/.vscode/settings.json b/.vscode/settings.json index d9ea16dd8..e45b72390 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,6 @@ "export/**/*.hx": true }, "[haxe]": { - "editor.formatOnSave": true, "editor.formatOnPaste": true, "editor.codeActionsOnSave": { "source.sortImports": true diff --git a/assets/shaders/vhs.frag b/assets/shaders/vhs.frag new file mode 100644 index 000000000..86e0aaeae --- /dev/null +++ b/assets/shaders/vhs.frag @@ -0,0 +1,86 @@ +// Based on a shader by FMS_Cat. +// https://www.shadertoy.com/view/XtBXDt +// Modified to support OpenFL. + +#pragma header +#define PI 3.14159265 + +uniform float time; + +vec3 tex2D(sampler2D _tex,vec2 _p) +{ + vec3 col=texture(_tex,_p).xyz; + if(.5; + public var colorMultiplier:ShaderParameter; + public var colorOffset:ShaderParameter; + public var hasTransform:ShaderParameter; + public var hasColorTransform:ShaderParameter; + + public function new() + { + super( + // Vertex + "#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; + } + }", + // Fragment + "#pragma header + + void main(void) + { + gl_FragColor = flixel_texture2D(bitmap, openfl_TextureCoordv); + }", false); + + glFragmentHeader += "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); + }"; + + __initGL(); + + bitmap = data.bitmap; + alpha = data.alpha; + colorMultiplier = data.colorMultiplier; + colorOffset = data.colorOffset; + hasTransform = data.hasTransform; + hasColorTransform = data.hasColorTransform; + } + + /* override function __initGL() + { + super.__initGL(); + + alpha = new ShaderParameter(); + colorMultiplier = new ShaderParameter(); + colorOffset = new ShaderParameter(); + hasTransform = new ShaderParameter(); + hasColorTransform = new ShaderParameter(); + } */ +} +#end diff --git a/source/gameFolder/meta/state/PlayState.hx b/source/gameFolder/meta/state/PlayState.hx index 590c52566..f92406e60 100644 --- a/source/gameFolder/meta/state/PlayState.hx +++ b/source/gameFolder/meta/state/PlayState.hx @@ -32,9 +32,12 @@ import gameFolder.meta.data.Song.SwagSong; import gameFolder.meta.state.charting.*; import gameFolder.meta.state.menus.*; import gameFolder.meta.subState.*; +import openfl.display.GraphicsShader; import openfl.events.KeyboardEvent; +import openfl.filters.ShaderFilter; import openfl.media.Sound; import openfl.utils.Assets; +import sys.io.File; using StringTools; @@ -317,6 +320,37 @@ class PlayState extends MusicBeatState songIntroCutscene(); else startCountdown(); + + /** + * SHADERS + * + * This is a highly experimental code by gedehari to support runtime shader parsing. + * Usually, to add a shader, you would make it a class, but now, I modified it so + * you can parse it from a file. + * + * This feature is planned to be used for modcharts + * (at this time of writing, it's not available yet). + * + * This example below shows that you can apply shaders as a FlxCamera filter. + * the GraphicsShader class accepts two arguments, one is for vertex shader, and + * the second is for fragment shader. + * Pass in an empty string to use the default vertex/fragment shader. + * + * Next, the Shader is passed to a new instance of ShaderFilter, neccesary to make + * the filter work. And that's it! + * + * To access shader uniforms, just reference the `data` property of the GraphicsShader + * instance. + * + * Thank you for reading! -gedehari + */ + + // Uncomment the code below to apply the effect + + /* + var shader:GraphicsShader = new GraphicsShader("", File.getContent("./assets/shaders/vhs.frag")); + FlxG.camera.setFilters([new ShaderFilter(shader)]); + */ } var staticDisplace:Int = 0; diff --git a/source/openfl/display/GraphicsShader.hx b/source/openfl/display/GraphicsShader.hx new file mode 100644 index 000000000..6efc342dc --- /dev/null +++ b/source/openfl/display/GraphicsShader.hx @@ -0,0 +1,134 @@ +package openfl.display; + +import openfl.utils.ByteArray; + +#if !openfl_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +class GraphicsShader extends Shader +{ + public var bitmap:ShaderInput; + + var glVertexHeader: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;"; + + var glVertexBody: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;"; + + var glFragmentHeader: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;"; + + var glFragmentBody: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 function new(glVertexSource:String = "", glFragmentSource:String = "", initNow:Bool = true) + { + super(null); + + if (glVertexSource != "") + this.glVertexSource = glVertexSource; + else + this.glVertexSource = "#pragma header + void main(void) { + #pragma body + }"; + + if (glFragmentSource != "") + this.glFragmentSource = glFragmentSource; + else + this.glFragmentSource = "#pragma header + void main(void) { + #pragma body + }"; + + if (initNow) + __initGL(); + } + + override public function __initGL() + { + processSource(); + + __isGenerated = true; + super.__initGL(); + + bitmap = data.bitmap; + } + + function processSource() + { + if (glVertexSource != null || glFragmentSource != null) + { + if (glFragmentSource != null && glFragmentHeader != null && glFragmentBody != null) + { + glFragmentSource = StringTools.replace(glFragmentSource, "#pragma header", glFragmentHeader); + glFragmentSource = StringTools.replace(glFragmentSource, "#pragma body", glFragmentBody); + } + + if (glVertexSource != null && glVertexHeader != null && glVertexBody != null) + { + glVertexSource = StringTools.replace(glVertexSource, "#pragma header", glVertexHeader); + glVertexSource = StringTools.replace(glVertexSource, "#pragma body", glVertexBody); + } + } + } +} diff --git a/source/openfl/display/Shader.hx b/source/openfl/display/Shader.hx new file mode 100644 index 000000000..7cec52148 --- /dev/null +++ b/source/openfl/display/Shader.hx @@ -0,0 +1,951 @@ +package openfl.display; + +#if !flash +import openfl.display._internal.ShaderBuffer; +import openfl.display3D.Context3D; +import openfl.display3D.Program3D; +import openfl.display3D._internal.GLProgram; +import openfl.display3D._internal.GLShader; +import openfl.utils.ByteArray; +import openfl.utils._internal.Float32Array; +import openfl.utils._internal.Log; + +/** + // TODO: Document GLSL Shaders + A Shader instance represents a Pixel Bender shader kernel in ActionScript. + To use a shader in your application, you create a Shader instance for it. + You then use that Shader instance in the appropriate way according to the + effect you want to create. For example, to use the shader as a filter, you + assign the Shader instance to the `shader` property of a ShaderFilter + object. + A shader defines a function that executes on all the pixels in an image, + one pixel at a time. The result of each call to the function is the output + color at that pixel coordinate in the image. A shader can specify one or + more input images, which are images whose content can be used in + determining the output of the function. A shader can also specify one or + more parameters, which are input values that can be used in calculating + the function output. In a single shader execution, the input and parameter + values are constant. The only thing that varies is the coordinate of the + pixel whose color is the function result. Shader function calls for + multiple output pixel coordinates execute in parallel to improve shader + execution performance. + + The shader bytecode can be loaded at run time using a URLLoader instance. + The following example demonstrates loading a shader bytecode file at run + time and linking it to a Shader instance. + + ```as3 + var loader:URLLoader = new URLLoader(); + loader.dataFormat = URLLoaderDataFormat.BINARY; + loader.addEventListener(Event.COMPLETE, onLoadComplete); + loader.load(new URLRequest("myShader.pbj")); + var shader:Shader; + + function onLoadComplete(event:Event):void { + // Create a new shader and set the loaded data as its bytecode + shader = new Shader(); + shader.byteCode = loader.data; + + // You can also pass the bytecode to the Shader() constructor like this: + // shader = new Shader(loader.data); + + // do something with the shader + } + ``` + + You can also embed the shader into the SWF at compile time using the + `[Embed]` metadata tag. The `[Embed]` metadata tag is only available if + you use the Flex SDK to compile the SWF. The `[Embed]` tag's `source` + parameter points to the shader file, and its `mimeType` parameter is + `"application/octet-stream"`, as in this example: + + ```as3 + [Embed(source="myShader.pbj", mimeType="application/octet-stream)] var MyShaderClass:Class; + + // ... + + // create a new shader and set the embedded shader as its bytecode var + shaderShader = new Shader(); + shader.byteCode = new MyShaderClass(); + + // You can also pass the bytecode to the Shader() constructor like this: + // var shader:Shader = new Shader(new MyShaderClass()); + + // do something with the shader + ``` + + In either case, you link the raw shader (the `URLLoader.data` property or + an instance of the `[Embed]` data class) to the Shader instance. As the + previous examples demonstrate, you can do this in two ways. You can pass + the shader bytecode as an argument to the `Shader()` constructor. + Alternatively, you can set it as the Shader instance's `byteCode` + property. + + Once a Shader instance is created, it can be used in one of several ways: + + * A shader fill: The output of the shader is used as a fill for content + drawn with the drawing API. Pass the Shader instance as an argument to the + `Graphics.beginShaderFill()` method. + * A shader filter: The output of the shader is used as a graphic filter + applied to a display object. Assign the Shader instance to the `shader` + property of a ShaderFilter instance. + * A blend mode: The output of the shader is rendered as the blending + between two overlapping display objects. Assign the Shader instance to the + `blendShader` property of the upper of the two display objects. + * Background shader processing: The shader executes in the background, + avoiding the possibility of freezing the display, and dispatches an event + when processing is complete. Assign the Shader instance to the `shader` + property of a ShaderJob instance. + + Shader fills, filters, and blends are not supported under GPU rendering. + + **Mobile Browser Support:** This feature is not supported in mobile + browsers. + + _AIR profile support:_ This feature is supported on all desktop operating + systems, but it is not supported on all mobile devices. It is not + supported on AIR for TV devices. See + AIR Profile Support for more information regarding API support across + multiple profiles. +**/ +#if !openfl_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +@:access(openfl.display3D.Context3D) +@:access(openfl.display3D.Program3D) +@:access(openfl.display.ShaderInput) +@:access(openfl.display.ShaderParameter) +// #if (!display && !macro) +#if !macro +@:autoBuild(openfl.utils._internal.ShaderMacro.build()) +#end +class Shader +{ + /** + The raw shader bytecode for this Shader instance. + **/ + public var byteCode(null, default):ByteArray; + + /** + Provides access to parameters, input images, and metadata for the + Shader instance. ShaderParameter objects representing parameters for + the shader, ShaderInput objects representing the input images for the + shader, and other values representing the shader's metadata are + dynamically added as properties of the `data` property object when the + Shader instance is created. Those properties can be used to introspect + the shader and to set parameter and input values. + For information about accessing and manipulating the dynamic + properties of the `data` object, see the ShaderData class description. + **/ + public var data(get, set):ShaderData; + + /** + Get or set the fragment source used when compiling with GLSL. + + This property is not available on the Flash target. + **/ + public var glFragmentSource(get, set):String; + + /** + The compiled GLProgram if available. + + This property is not available on the Flash target. + **/ + @SuppressWarnings("checkstyle:Dynamic") public var glProgram(default, null):GLProgram; + + /** + Get or set the vertex source used when compiling with GLSL. + + This property is not available on the Flash target. + **/ + public var glVertexSource(get, set):String; + + /** + The precision of math operations performed by the shader. + The set of possible values for the `precisionHint` property is defined + by the constants in the ShaderPrecision class. + + The default value is `ShaderPrecision.FULL`. Setting the precision to + `ShaderPrecision.FAST` can speed up math operations at the expense of + precision. + + Full precision mode (`ShaderPrecision.FULL`) computes all math + operations to the full width of the IEEE 32-bit floating standard and + provides consistent behavior on all platforms. In this mode, some math + operations such as trigonometric and exponential functions can be + slow. + + Fast precision mode (`ShaderPrecision.FAST`) is designed for maximum + performance but does not work consistently on different platforms and + individual CPU configurations. In many cases, this level of precision + is sufficient to create graphic effects without visible artifacts. + + The precision mode selection affects the following shader operations. + These operations are faster on an Intel processor with the SSE + instruction set: + + * `sin(x)` + * `cos(x)` + * `tan(x)` + * `asin(x)` + * `acos(x)` + * `atan(x)` + * `atan(x, y)` + * `exp(x)` + * `exp2(x)` + * `log(x)` + * `log2(x)` + * `pow(x, y)` + * `reciprocal(x)` + * `sqrt(x)` + **/ + public var precisionHint:ShaderPrecision; + + /** + The compiled Program3D if available. + + This property is not available on the Flash target. + **/ + public var program:Program3D; + + @:noCompletion private var __alpha:ShaderParameter; + @:noCompletion private var __bitmap:ShaderInput; + @:noCompletion private var __colorMultiplier:ShaderParameter; + @:noCompletion private var __colorOffset:ShaderParameter; + @:noCompletion private var __context:Context3D; + @:noCompletion private var __data:ShaderData; + @:noCompletion private var __glFragmentSource:String; + @:noCompletion private var __glSourceDirty:Bool; + @:noCompletion private var __glVertexSource:String; + @:noCompletion private var __hasColorTransform:ShaderParameter; + @:noCompletion private var __inputBitmapData:Array>; + @:noCompletion private var __isGenerated:Bool; + @:noCompletion private var __matrix:ShaderParameter; + @:noCompletion private var __numPasses:Int; + @:noCompletion private var __paramBool:Array>; + @:noCompletion private var __paramFloat:Array>; + @:noCompletion private var __paramInt:Array>; + @:noCompletion private var __position:ShaderParameter; + @:noCompletion private var __textureCoord:ShaderParameter; + @:noCompletion private var __texture:ShaderInput; + @:noCompletion private var __textureSize:ShaderParameter; + + #if openfljs + @:noCompletion private static function __init__() + { + untyped Object.defineProperties(Shader.prototype, { + "data": { + get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_data (); }"), + set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_data (v); }") + }, + "glFragmentSource": { + get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_glFragmentSource (); }"), + set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_glFragmentSource (v); }") + }, + "glVertexSource": { + get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_glVertexSource (); }"), + set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_glVertexSource (v); }") + }, + }); + } + #end + + /** + Creates a new Shader instance. + + @param code The raw shader bytecode to link to the Shader. + **/ + public function new(code:ByteArray = null) + { + byteCode = code; + precisionHint = FULL; + + __glSourceDirty = true; + __numPasses = 1; + __data = new ShaderData(code); + } + + @:noCompletion private function __clearUseArray():Void + { + for (parameter in __paramBool) + { + parameter.__useArray = false; + } + + for (parameter in __paramFloat) + { + parameter.__useArray = false; + } + + for (parameter in __paramInt) + { + parameter.__useArray = false; + } + } + + // private function __clone ():Shader { + // var classType = Type.getClass (this); + // var shader = Type.createInstance (classType, []); + // for (input in __inputBitmapData) { + // if (input.input != null) { + // var field = Reflect.field (shader.data, input.name); + // field.channels = input.channels; + // field.height = input.height; + // field.input = input.input; + // field.smoothing = input.smoothing; + // field.width = input.width; + // } + // } + // for (param in __paramBool) { + // if (param.value != null) { + // Reflect.field (shader.data, param.name).value = param.value.copy (); + // } + // } + // for (param in __paramFloat) { + // if (param.value != null) { + // Reflect.field (shader.data, param.name).value = param.value.copy (); + // } + // } + // for (param in __paramInt) { + // if (param.value != null) { + // Reflect.field (shader.data, param.name).value = param.value.copy (); + // } + // } + // return shader; + // } + @:noCompletion private function __createGLShader(source:String, type:Int):GLShader + { + var gl = __context.gl; + + var shader = gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); + + if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) == 0) + { + var message = (type == gl.VERTEX_SHADER) ? "Error compiling vertex shader" : "Error compiling fragment shader"; + message += "\n" + gl.getShaderInfoLog(shader); + message += "\n" + source; + Log.error(message); + } + + return shader; + } + + @:noCompletion private function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram + { + var gl = __context.gl; + + var vertexShader = __createGLShader(vertexSource, gl.VERTEX_SHADER); + var fragmentShader = __createGLShader(fragmentSource, gl.FRAGMENT_SHADER); + + var program = gl.createProgram(); + + // Fix support for drivers that don't draw if attribute 0 is disabled + for (param in __paramFloat) + { + if (param.name.indexOf("Position") > -1 && StringTools.startsWith(param.name, "openfl_")) + { + gl.bindAttribLocation(program, 0, param.name); + break; + } + } + + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); + + if (gl.getProgramParameter(program, gl.LINK_STATUS) == 0) + { + var message = "Unable to initialize the shader program"; + message += "\n" + gl.getProgramInfoLog(program); + Log.error(message); + } + + return program; + } + + @:noCompletion private function __disable():Void + { + if (program != null) + { + __disableGL(); + } + } + + @:noCompletion private function __disableGL():Void + { + var gl = __context.gl; + + var textureCount = 0; + + for (input in __inputBitmapData) + { + input.__disableGL(__context, textureCount); + textureCount++; + } + + for (parameter in __paramBool) + { + parameter.__disableGL(__context); + } + + for (parameter in __paramFloat) + { + parameter.__disableGL(__context); + } + + for (parameter in __paramInt) + { + parameter.__disableGL(__context); + } + + __context.__bindGLArrayBuffer(null); + + #if lime + if (__context.__context.type == OPENGL) + { + gl.disable(gl.TEXTURE_2D); + } + #end + } + + @:noCompletion private function __enable():Void + { + __init(); + + if (program != null) + { + __enableGL(); + } + } + + @:noCompletion private function __enableGL():Void + { + var textureCount = 0; + + var gl = __context.gl; + + for (input in __inputBitmapData) + { + gl.uniform1i(input.index, textureCount); + textureCount++; + } + + #if lime + if (__context.__context.type == OPENGL && textureCount > 0) + { + gl.enable(gl.TEXTURE_2D); + } + #end + } + + @:noCompletion private function __init():Void + { + if (__data == null) + { + __data = cast new ShaderData(null); + } + + if (__glFragmentSource != null && __glVertexSource != null && (program == null || __glSourceDirty)) + { + __initGL(); + } + } + + @:noCompletion private 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 gl = __context.gl; + + var 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); + + // TODO + // program.uploadSources (vertex, fragment); + 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); + } + } + } + } + } + + @:noCompletion private 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(); + input.name = name; + input.__isUniform = isUniform; + __inputBitmapData.push(input); + + switch (name) + { + case "openfl_Texture": + __texture = input; + case "bitmap": + __bitmap = input; + default: + } + + Reflect.setField(__data, name, input); + //if (__isGenerated) Reflect.setField(this, name, input); + } + 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(); + parameter.name = name; + parameter.type = parameterType; + parameter.__arrayLength = arrayLength; + parameter.__isBool = true; + parameter.__isUniform = isUniform; + parameter.__length = length; + __paramBool.push(parameter); + + if (name == "openfl_HasColorTransform") + { + __hasColorTransform = parameter; + } + + Reflect.setField(__data, name, parameter); + //if (__isGenerated) Reflect.setField(this, name, parameter); + + case INT, INT2, INT3, INT4: + var parameter = new ShaderParameter(); + parameter.name = name; + parameter.type = parameterType; + parameter.__arrayLength = arrayLength; + parameter.__isInt = true; + parameter.__isUniform = isUniform; + parameter.__length = length; + __paramInt.push(parameter); + Reflect.setField(__data, name, parameter); + //if (__isGenerated) Reflect.setField(this, name, parameter); + + default: + var parameter = new ShaderParameter(); + parameter.name = name; + parameter.type = parameterType; + parameter.__arrayLength = arrayLength; + #if lime + if (arrayLength > 0) parameter.__uniformMatrix = new Float32Array(arrayLength * arrayLength); + #end + parameter.__isFloat = true; + parameter.__isUniform = isUniform; + 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); + //if (__isGenerated) Reflect.setField(this, name, parameter); + } + } + + position = regex.matchedPos(); + lastMatch = position.pos + position.len; + } + } + + @:noCompletion private function __update():Void + { + if (program != null) + { + __updateGL(); + } + } + + @:noCompletion private function __updateFromBuffer(shaderBuffer:ShaderBuffer, bufferOffset:Int):Void + { + if (program != null) + { + __updateGLFromBuffer(shaderBuffer, bufferOffset); + } + } + + @:noCompletion private function __updateGL():Void + { + var textureCount = 0; + + for (input in __inputBitmapData) + { + input.__updateGL(__context, textureCount); + textureCount++; + } + + for (parameter in __paramBool) + { + parameter.__updateGL(__context); + } + + for (parameter in __paramFloat) + { + parameter.__updateGL(__context); + } + + for (parameter in __paramInt) + { + parameter.__updateGL(__context); + } + } + + @:noCompletion private function __updateGLFromBuffer(shaderBuffer:ShaderBuffer, bufferOffset:Int):Void + { + var textureCount = 0; + var input, inputData, inputFilter, inputMipFilter, inputWrap; + + for (i in 0...shaderBuffer.inputCount) + { + input = shaderBuffer.inputRefs[i]; + inputData = shaderBuffer.inputs[i]; + inputFilter = shaderBuffer.inputFilter[i]; + inputMipFilter = shaderBuffer.inputMipFilter[i]; + inputWrap = shaderBuffer.inputWrap[i]; + + if (inputData != null) + { + input.__updateGL(__context, textureCount, inputData, inputFilter, inputMipFilter, inputWrap); + textureCount++; + } + } + + var gl = __context.gl; + + if (shaderBuffer.paramDataLength > 0) + { + if (shaderBuffer.paramDataBuffer == null) + { + shaderBuffer.paramDataBuffer = gl.createBuffer(); + } + + // Log.verbose ("bind param data buffer (length: " + shaderBuffer.paramData.length + ") (" + shaderBuffer.paramCount + ")"); + + __context.__bindGLArrayBuffer(shaderBuffer.paramDataBuffer); + gl.bufferData(gl.ARRAY_BUFFER, shaderBuffer.paramData, gl.DYNAMIC_DRAW); + } + else + { + // Log.verbose ("bind buffer null"); + + __context.__bindGLArrayBuffer(null); + } + + var boolIndex = 0; + var floatIndex = 0; + var intIndex = 0; + + var boolCount = shaderBuffer.paramBoolCount; + var floatCount = shaderBuffer.paramFloatCount; + var paramData = shaderBuffer.paramData; + + var boolRef, floatRef, intRef, hasOverride; + var overrideBoolValue:Array = null, + overrideFloatValue:Array = null, + overrideIntValue:Array = null; + + for (i in 0...shaderBuffer.paramCount) + { + hasOverride = false; + + if (i < boolCount) + { + boolRef = shaderBuffer.paramRefs_Bool[boolIndex]; + + for (j in 0...shaderBuffer.overrideBoolCount) + { + if (boolRef.name == shaderBuffer.overrideBoolNames[j]) + { + overrideBoolValue = shaderBuffer.overrideBoolValues[j]; + hasOverride = true; + break; + } + } + + if (hasOverride) + { + boolRef.__updateGL(__context, overrideBoolValue); + } + else + { + boolRef.__updateGLFromBuffer(__context, paramData, shaderBuffer.paramPositions[i], shaderBuffer.paramLengths[i], bufferOffset); + } + + boolIndex++; + } + else if (i < boolCount + floatCount) + { + floatRef = shaderBuffer.paramRefs_Float[floatIndex]; + + for (j in 0...shaderBuffer.overrideFloatCount) + { + if (floatRef.name == shaderBuffer.overrideFloatNames[j]) + { + overrideFloatValue = shaderBuffer.overrideFloatValues[j]; + hasOverride = true; + break; + } + } + + if (hasOverride) + { + floatRef.__updateGL(__context, overrideFloatValue); + } + else + { + floatRef.__updateGLFromBuffer(__context, paramData, shaderBuffer.paramPositions[i], shaderBuffer.paramLengths[i], bufferOffset); + } + + floatIndex++; + } + else + { + intRef = shaderBuffer.paramRefs_Int[intIndex]; + + for (j in 0...shaderBuffer.overrideIntCount) + { + if (intRef.name == shaderBuffer.overrideIntNames[j]) + { + overrideIntValue = cast shaderBuffer.overrideIntValues[j]; + hasOverride = true; + break; + } + } + + if (hasOverride) + { + intRef.__updateGL(__context, overrideIntValue); + } + else + { + intRef.__updateGLFromBuffer(__context, paramData, shaderBuffer.paramPositions[i], shaderBuffer.paramLengths[i], bufferOffset); + } + + intIndex++; + } + } + } + + // Get & Set Methods + @:noCompletion private function get_data():ShaderData + { + if (__glSourceDirty || __data == null) + { + __init(); + } + + return __data; + } + + @:noCompletion private function set_data(value:ShaderData):ShaderData + { + return __data = cast value; + } + + @:noCompletion private function get_glFragmentSource():String + { + return __glFragmentSource; + } + + @:noCompletion private function set_glFragmentSource(value:String):String + { + if (value != __glFragmentSource) + { + __glSourceDirty = true; + } + + return __glFragmentSource = value; + } + + @:noCompletion private function get_glVertexSource():String + { + return __glVertexSource; + } + + @:noCompletion private function set_glVertexSource(value:String):String + { + if (value != __glVertexSource) + { + __glSourceDirty = true; + } + + return __glVertexSource = value; + } +} +#else +typedef Shader = flash.display.Shader; +#end diff --git a/source/openfl/filters/ColorMatrixFilter.hx b/source/openfl/filters/ColorMatrixFilter.hx new file mode 100644 index 000000000..a3973af3c --- /dev/null +++ b/source/openfl/filters/ColorMatrixFilter.hx @@ -0,0 +1,316 @@ +package openfl.filters; + +#if !flash +import openfl.display.BitmapData; +import openfl.display.DisplayObjectRenderer; +import openfl.display.Shader; +import openfl.geom.Point; +import openfl.geom.Rectangle; +#if lime +import lime._internal.graphics.ImageCanvasUtil; // TODO +import lime.math.RGBA; +#end + +/** + The ColorMatrixFilter class lets you apply a 4 x 5 matrix transformation + on the RGBA color and alpha values of every pixel in the input image to + produce a result with a new set of RGBA color and alpha values. It allows + saturation changes, hue rotation, luminance to alpha, and various other + effects. You can apply the filter to any display object (that is, objects + that inherit from the DisplayObject class), such as MovieClip, + SimpleButton, TextField, and Video objects, as well as to BitmapData + objects. + **Note:** For RGBA values, the most significant byte represents the red + channel value, followed by green, blue, and then alpha. + + To create a new color matrix filter, use the syntax `new + ColorMatrixFilter()`. The use of filters depends on the object to which + you apply the filter: + + * To apply filters to movie clips, text fields, buttons, and video, use + the `filters` property (inherited from DisplayObject). Setting the + `filters` property of an object does not modify the object, and you can + remove the filter by clearing the `filters` property. + * To apply filters to BitmapData objects, use the + `BitmapData.applyFilter()` method. Calling `applyFilter()` on a BitmapData + object takes the source BitmapData object and the filter object and + generates a filtered image as a result. + + If you apply a filter to a display object, the `cacheAsBitmap` property of + the display object is set to `true`. If you remove all filters, the + original value of `cacheAsBitmap` is restored. + + A filter is not applied if the resulting image exceeds the maximum + dimensions. In AIR 1.5 and Flash Player 10, the maximum is 8,191 pixels in + width or height, and the total number of pixels cannot exceed 16,777,215 + pixels. (So, if an image is 8,191 pixels wide, it can only be 2,048 pixels + high.) In Flash Player 9 and earlier and AIR 1.1 and earlier, the + limitation is 2,880 pixels in height and 2,880 pixels in width. For + example, if you zoom in on a large movie clip with a filter applied, the + filter is turned off if the resulting image reaches the maximum + dimensions. +**/ +#if !openfl_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +@:final class ColorMatrixFilter extends BitmapFilter +{ + @:noCompletion private static var __colorMatrixShader:ColorMatrixShader = new ColorMatrixShader(); + + /** + An array of 20 items for 4 x 5 color transform. The `matrix` property + cannot be changed by directly modifying its value (for example, + `myFilter.matrix[2] = 1;`). Instead, you must get a reference to the + array, make the change to the reference, and reset the value. + The color matrix filter separates each source pixel into its red, + green, blue, and alpha components as srcR, srcG, srcB, srcA. To + calculate the result of each of the four channels, the value of each + pixel in the image is multiplied by the values in the transformation + matrix. An offset, between -255 and 255, can optionally be added to + each result (the fifth item in each row of the matrix). The filter + combines each color component back into a single pixel and writes out + the result. In the following formula, a[0] through a[19] correspond to + entries 0 through 19 in the 20-item array that is passed to the + `matrix` property: + + ``` + redResult = (a[0] * srcR) + (a[1] * srcG) + (a[2] * srcB) + (a[3] * srcA) + a[4] + greenResult = (a[5] * srcR) + (a[6] * srcG) + (a[7] * srcB) + (a[8] * srcA) + a[9] + blueResult = (a[10] * srcR) + (a[11] * srcG) + (a[12] * srcB) + (a[13] * srcA) + a[14] + alphaResult = (a[15] * srcR) + (a[16] * srcG) + (a[17] * srcB) + (a[18] * srcA) + a[19] + ``` + + For each color value in the array, a value of 1 is equal to 100% of + that channel being sent to the output, preserving the value of the + color channel. + + The calculations are performed on unmultiplied color values. If the + input graphic consists of premultiplied color values, those values are + automatically converted into unmultiplied color values for this + operation. + + Two optimized modes are available: + + **Alpha only.** When you pass to the filter a matrix that adjusts only + the alpha component, as shown here, the filter optimizes its + performance: + + ``` + 1 0 0 0 0 + 0 1 0 0 0 + 0 0 1 0 0 + 0 0 0 N 0 (where N is between 0.0 and 1.0) + ``` + + **Faster version**. Available only with SSE/AltiVec + accelerator-enabled processors, such as Intel + Pentium 3 and later and Apple G4 and later. + The accelerator is used when the multiplier terms are in the range + -15.99 to 15.99 and the adder terms a[4], a[9], a[14], and a[19] are + in the range -8000 to 8000. + + @throws TypeError The Array is `null` when being set + **/ + public var matrix(get, set):Array; + + @:noCompletion private var __matrix:Array; + + #if openfljs + @:noCompletion private static function __init__() + { + untyped Object.defineProperties(ColorMatrixFilter.prototype, { + "matrix": { + get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_matrix (); }"), + set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_matrix (v); }") + }, + }); + } + #end + + /** + Initializes a new ColorMatrixFilter instance with the specified + parameters. + **/ + public function new(matrix:Array = null) + { + super(); + + this.matrix = matrix; + + __numShaderPasses = 1; + __needSecondBitmapData = false; + } + + public override function clone():BitmapFilter + { + return new ColorMatrixFilter(__matrix); + } + + @:noCompletion private override function __applyFilter(destBitmapData:BitmapData, sourceBitmapData:BitmapData, sourceRect:Rectangle, + destPoint:Point):BitmapData + { + #if lime + var sourceImage = sourceBitmapData.image; + var image = destBitmapData.image; + + #if (js && html5) + ImageCanvasUtil.convertToData(sourceImage); + ImageCanvasUtil.convertToData(image); + #end + + var sourceData = sourceImage.data; + var destData = image.data; + + var offsetX = Std.int(destPoint.x - sourceRect.x); + var offsetY = Std.int(destPoint.y - sourceRect.y); + var sourceStride = sourceBitmapData.width * 4; + var destStride = destBitmapData.width * 4; + + var sourceFormat = sourceImage.buffer.format; + var destFormat = image.buffer.format; + var sourcePremultiplied = sourceImage.buffer.premultiplied; + var destPremultiplied = image.buffer.premultiplied; + + var sourcePixel:RGBA, destPixel:RGBA = 0; + var sourceOffset:Int, destOffset:Int; + + for (row in Std.int(sourceRect.y)...Std.int(sourceRect.height)) + { + for (column in Std.int(sourceRect.x)...Std.int(sourceRect.width)) + { + sourceOffset = (row * sourceStride) + (column * 4); + destOffset = ((row + offsetX) * destStride) + ((column + offsetY) * 4); + + sourcePixel.readUInt8(sourceData, sourceOffset, sourceFormat, sourcePremultiplied); + + if (sourcePixel.a == 0) + { + destPixel = 0; + } + else + { + destPixel.r = Std.int(Math.max(0, + Math.min((__matrix[0] * sourcePixel.r) + (__matrix[1] * sourcePixel.g) + (__matrix[2] * sourcePixel.b) + + (__matrix[3] * sourcePixel.a) + __matrix[4], + 255))); + destPixel.g = Std.int(Math.max(0, + Math.min((__matrix[5] * sourcePixel.r) + (__matrix[6] * sourcePixel.g) + (__matrix[7] * sourcePixel.b) + + (__matrix[8] * sourcePixel.a) + __matrix[9], + 255))); + destPixel.b = Std.int(Math.max(0, + Math.min((__matrix[10] * sourcePixel.r) + (__matrix[11] * sourcePixel.g) + (__matrix[12] * sourcePixel.b) + + (__matrix[13] * sourcePixel.a) + __matrix[14], + 255))); + destPixel.a = Std.int(Math.max(0, + Math.min((__matrix[15] * sourcePixel.r) + (__matrix[16] * sourcePixel.g) + (__matrix[17] * sourcePixel.b) + + (__matrix[18] * sourcePixel.a) + __matrix[19], + 255))); + } + + destPixel.writeUInt8(destData, destOffset, destFormat, destPremultiplied); + } + } + + destBitmapData.image.dirty = true; + #end + return destBitmapData; + } + + @:noCompletion private override function __initShader(renderer:DisplayObjectRenderer, pass:Int, sourceBitmapData:BitmapData):Shader + { + __colorMatrixShader.init(matrix); + return __colorMatrixShader; + } + + // Get & Set Methods + @:noCompletion private function get_matrix():Array + { + return __matrix; + } + + @:noCompletion private function set_matrix(value:Array):Array + { + if (value == null) + { + value = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0]; + } + + return __matrix = value; + } +} + +#if !openfl_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +@SuppressWarnings("checkstyle:FieldDocComment") +private class ColorMatrixShader extends BitmapFilterShader +{ + @:glFragmentSource("varying vec2 openfl_TextureCoordv; + uniform sampler2D openfl_Texture; + + uniform mat4 uMultipliers; + uniform vec4 uOffsets; + + void main(void) { + + vec4 color = texture2D (openfl_Texture, openfl_TextureCoordv); + + if (color.a == 0.0) { + + gl_FragColor = vec4 (0.0, 0.0, 0.0, 0.0); + + } else { + + color = vec4 (color.rgb / color.a, color.a); + color = uOffsets + color * uMultipliers; + + gl_FragColor = vec4 (color.rgb * color.a, color.a); + + } + + }") + public function new() + { + super(); + + #if !macro + data.uMultipliers.value = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + data.uOffsets.value = [0, 0, 0, 0]; + #end + } + + public function init(matrix:Array):Void + { + #if !macro + var multipliers = data.uMultipliers.value; + var offsets = data.uOffsets.value; + + multipliers[0] = matrix[0]; + multipliers[1] = matrix[1]; + multipliers[2] = matrix[2]; + multipliers[3] = matrix[3]; + multipliers[4] = matrix[5]; + multipliers[5] = matrix[6]; + multipliers[6] = matrix[7]; + multipliers[7] = matrix[8]; + multipliers[8] = matrix[10]; + multipliers[9] = matrix[11]; + multipliers[10] = matrix[12]; + multipliers[11] = matrix[13]; + multipliers[12] = matrix[15]; + multipliers[13] = matrix[16]; + multipliers[14] = matrix[17]; + multipliers[15] = matrix[18]; + + offsets[0] = matrix[4] / 255.0; + offsets[1] = matrix[9] / 255.0; + offsets[2] = matrix[14] / 255.0; + offsets[3] = matrix[19] / 255.0; + #end + } +} +#else +typedef ColorMatrixFilter = flash.filters.ColorMatrixFilter; +#end