├── .gitattributes ├── .gitignore ├── README.md ├── haxelib.json └── shaderblox ├── Shader.hx ├── ShaderBase.hx ├── attributes ├── Attribute.hx └── FloatAttribute.hx ├── example └── SimpleShader.hx ├── macro └── ShaderBuilder.hx └── uniforms ├── IAppliable.hx ├── UBool.hx ├── UColor3.hx ├── UColor4.hx ├── UFloat.hx ├── UInt.hx ├── UMatrix.hx ├── UMatrixTransposed.hx ├── UTexture.hx ├── UVec2.hx ├── UVec3.hx ├── UVec4.hx └── UniformBase.hx /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Shaderblox 2 | ========== 3 | 4 | Compile-time GLSL shader tools for Haxe/Lime 5 | 6 | ---------- 7 | 8 | Shaderblox is an alternative approach to building and maintaining GLSL shaders for use with Haxe and Lime, and probably OpenFL as well with some typedef magic. 9 | 10 | It stems from an urge to strong type the bindings between a shader as seen by the GPU and the Haxe software layer that interacts with it. For instance, in our game engine we had a renderer interface that needed to rely on certain vertex attributes and uniforms to exist within each shader, and without strong typing and compile time errors, this caused some heartache for me. 11 | 12 | Shaderblox is intended to meet the following goals: 13 | 14 | - Compile-time parsing of shader source into strong typed classes with typed fields for attributes and uniforms. 15 | - Inheritance-based shader building, with one shader inheriting attributes, uniforms, methods etc from another. 16 | - #pragma tools for including external files (since writing inline shader source isn't super comfortable). 17 | - Generated methods for uploading uniforms and setting vertex attribute pointers. 18 | 19 | With shaderblox, a shader incompatible with the rendering framework will cause compile time exceptions, meaning shader authors and framework engineers should have radically fewer ways to screw with one another. 20 | 21 | ## Caveats ## 22 | 1. Compiletime means swapping out shaders after the build is done is no longer possible: Shaders are as much a part of the compiled code as any class, rather than an external resource. 23 | 2. Shader source validation is only done once the source is actually compiled on the GPU itself, which can only occur runtime. 24 | 3. GLSL parsing is currently *very* unsophisticated, looking specifically for attribute and uniform declarations and not much else. 25 | 4. Work in progress.. Open source.. etc etc. This is a personal project and it's constantly liable to change. 26 | 5. Shader A extends Shader B merges uniform/attribute declarations, but unfortunately allows duplicate function declarations, such as two mains and so forth. Looking to improve this, obviously. 27 | 28 | ## Example ## 29 | 30 | Shader types are defined with metadata, and built with macros. This simple shader is included with the source. 31 | 32 | package shaderblox.example; 33 | import shaderblox.ShaderBase; 34 | 35 | /** 36 | * "Hello world" shader example 37 | * @author Andreas Rønning 38 | */ 39 | 40 | @:vert(' 41 | attribute vec2 aVertexPosition; 42 | void main(void) { 43 | gl_Position = vec4(aVertexPosition,0.0,1.0); 44 | } 45 | ') 46 | @:frag(' 47 | uniform vec3 uColor; 48 | void main(void) 49 | { 50 | gl_FragColor = vec4(uColor,1.0); 51 | } 52 | ') 53 | class SimpleShader extends ShaderBase {} 54 | 55 | Note that instead of writing the source inline, as in this case, you could use this approach: 56 | 57 | @:vert('#pragma include("path/to/my/shader.vert")') 58 | 59 | This external file will be loaded in at compiletime (as well as display-time, giving dynamic code completion as the external source is updated). You can of course liberally use the pragma include to build shaders whichever way you want. 60 | 61 | The following source is a variation of the typical "hello triangle" using the SimpleShader example included with the source. 62 | 63 | package ; 64 | import haxe.Timer; 65 | import lime.gl.GL; 66 | import lime.gl.GLBuffer; 67 | import lime.Lime; 68 | import lime.utils.Float32Array; 69 | import shaderblox.example.SimpleShader; 70 | 71 | 72 | class Main { 73 | var lime:Lime; 74 | var shader:SimpleShader; 75 | var vbuf:GLBuffer; 76 | 77 | public function new () { 78 | } 79 | 80 | public function ready (lime:Lime):Void { 81 | this.lime = lime; 82 | 83 | vbuf = GL.createBuffer(); 84 | var vertices:Array = [ 85 | 0.0, 0.5, 86 | 0.5, -0.5, 87 | -0.5, -0.5 88 | ]; 89 | 90 | GL.bindBuffer(GL.ARRAY_BUFFER, vbuf); 91 | GL.bufferData(GL.ARRAY_BUFFER, new Float32Array(vertices), GL.STATIC_DRAW); 92 | GL.bindBuffer(GL.ARRAY_BUFFER, null); 93 | 94 | shader = new SimpleShader(); 95 | shader.create(); //Create builds, validates attribs/uniforms and uploads. Should do this as part of load, or when context is lost. 96 | } 97 | 98 | inline function uSin(t:Float):Float { 99 | return Math.sin(t) * 0.5 + 0.5; 100 | } 101 | 102 | public function render ():Void { 103 | GL.viewport (0, 0, lime.config.width, lime.config.height); 104 | GL.clearColor (1.0, 1.0, 1.0, 1.0); 105 | GL.clear (GL.COLOR_BUFFER_BIT); 106 | 107 | //Set some wobbly color values on our vec3 uniform 108 | shader.uColor.data.x = uSin(Timer.stamp()); 109 | shader.uColor.data.y = uSin(Timer.stamp()*2); 110 | shader.uColor.data.z = uSin(Timer.stamp()*3); 111 | 112 | //Set our shader as current. 113 | shader.activate(); 114 | //Note that by default, activate uploads uniform values, but does not set vertex attribute pointers (which needs to be repeated every buffer bind anyway) 115 | 116 | GL.bindBuffer(GL.ARRAY_BUFFER, vbuf); //Bind our vbo.. 117 | 118 | //Set the vertex attribute pointers into the current vertex buffer object... 119 | shader.setAttributes(); 120 | 121 | //Draw our triangle... 122 | GL.drawArrays(GL.TRIANGLE_FAN, 0, 3); 123 | 124 | //..and clean up (important) 125 | shader.deactivate(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shaderblox", 3 | "url": "", 4 | "license": "MIT", 5 | "tags": [], 6 | "description": "GLSL tools for Haxe/Lime", 7 | "version": "1.0.0", 8 | "releasenote": "", 9 | "contributors": ["decept404"] 10 | } -------------------------------------------------------------------------------- /shaderblox/Shader.hx: -------------------------------------------------------------------------------- 1 | package shaderblox; 2 | import shaderblox.attributes.Attribute; 3 | import snow.platform.native.render.opengl.GL.GLProgram; 4 | import snow.platform.native.render.opengl.GL.GLShader; 5 | import snow.render.opengl.GL; 6 | 7 | /** 8 | * ... 9 | * @author Andreas Rønning 10 | */ 11 | class Shader 12 | { 13 | public var uniformLocations:Map; 14 | public var attributes:Array; 15 | public var aStride:Int; 16 | public var vert:GLShader; 17 | public var frag:GLShader; 18 | public var prog:GLProgram; 19 | 20 | public var active:Bool; 21 | public var ready:Bool; 22 | 23 | public var vertSource:String; 24 | public var fragSource:String; 25 | 26 | public var numReferences:Int; 27 | public var name:String; 28 | public function new() 29 | { 30 | attributes = []; 31 | } 32 | 33 | public function rebuild() { 34 | initFromSource(vertSource, fragSource); 35 | } 36 | 37 | public function initFromSource(vertSource:String, fragSource:String) { 38 | this.vertSource = vertSource; 39 | this.fragSource = fragSource; 40 | var vertexShader = GL.createShader (GL.VERTEX_SHADER); 41 | GL.shaderSource (vertexShader, vertSource); 42 | GL.compileShader (vertexShader); 43 | 44 | if (GL.getShaderParameter (vertexShader, GL.COMPILE_STATUS) == 0) { 45 | trace("Error compiling vertex shader: " + GL.getShaderInfoLog(vertexShader)); 46 | trace("\n"+vertSource); 47 | throw "Error compiling vertex shader"; 48 | 49 | } 50 | 51 | var fragmentShader = GL.createShader (GL.FRAGMENT_SHADER); 52 | GL.shaderSource (fragmentShader, fragSource); 53 | GL.compileShader (fragmentShader); 54 | 55 | if (GL.getShaderParameter (fragmentShader, GL.COMPILE_STATUS) == 0) { 56 | trace("Error compiling fragment shader: " + GL.getShaderInfoLog(fragmentShader)+"\n"); 57 | var lines = fragSource.split("\n"); 58 | var i = 0; 59 | for (l in lines) { 60 | trace((i++) + " - " + l); 61 | } 62 | throw "Error compiling fragment shader"; 63 | 64 | } 65 | 66 | var shaderProgram = GL.createProgram (); 67 | GL.attachShader (shaderProgram, vertexShader); 68 | GL.attachShader (shaderProgram, fragmentShader); 69 | GL.linkProgram (shaderProgram); 70 | 71 | if (GL.getProgramParameter (shaderProgram, GL.LINK_STATUS) == 0) { 72 | throw "Unable to link the shader program."; 73 | } 74 | 75 | var numUniforms = GL.getProgramParameter(shaderProgram, GL.ACTIVE_UNIFORMS); 76 | uniformLocations = new Map(); 77 | while (numUniforms-->0) { 78 | var uInfo = GL.getActiveUniform(shaderProgram, numUniforms); 79 | var loc:Int = cast GL.getUniformLocation(shaderProgram, uInfo.name); 80 | uniformLocations[uInfo.name] = loc; 81 | } 82 | var numAttributes = GL.getProgramParameter(shaderProgram, GL.ACTIVE_ATTRIBUTES); 83 | var attributeLocations:Map = new Map(); 84 | while (numAttributes-->0) { 85 | var aInfo = GL.getActiveAttrib(shaderProgram, numAttributes); 86 | var loc:Int = cast GL.getAttribLocation(shaderProgram, aInfo.name); 87 | attributeLocations[aInfo.name] = loc; 88 | } 89 | 90 | vert = vertexShader; 91 | frag = fragmentShader; 92 | prog = shaderProgram; 93 | 94 | //TODO: Graceful handling of unused sampler uniforms. 95 | /** 96 | * 1. Find every sampler/samplerCube uniform 97 | * 2. For each sampler, assign a sampler index from 0 and up 98 | * 3. Go through uniform locations, remove inactive samplers 99 | * 4. Pack remaining active sampler 100 | */ 101 | 102 | //Validate attribute locations 103 | for (a in attributes) { 104 | var loc = attributeLocations.get(a.name); 105 | a.location = loc == null? -1:loc; 106 | if (a.location == -1) trace("WARNING(" + name + "): unused attribute '" + a.name +"'"); 107 | #if (debug && !display) trace("Defined attribute "+a.name+" at "+a.location); #end 108 | } 109 | ready = true; 110 | } 111 | 112 | public function bind() { 113 | if (active) return; 114 | GL.useProgram(prog); 115 | setAttributes(); 116 | active = true; 117 | } 118 | public function release() { 119 | if (!active) return; 120 | disableAttributes(); 121 | active = false; 122 | } 123 | public function dispose() { 124 | GL.deleteShader(vert); 125 | GL.deleteShader(frag); 126 | GL.deleteProgram(prog); 127 | prog = null; 128 | vert = null; 129 | frag = null; 130 | ready = active = false; 131 | } 132 | public function setAttributes() 133 | { 134 | var offset:Int = 0; 135 | for (i in 0...attributes.length) { 136 | var att = attributes[i]; 137 | var location = att.location; 138 | if (location != -1) { 139 | GL.enableVertexAttribArray(location); 140 | GL.vertexAttribPointer (location, att.itemCount, att.type, false, aStride, offset); 141 | } 142 | offset += att.byteSize; 143 | } 144 | } 145 | public function disableAttributes() 146 | { 147 | for (i in 0...attributes.length) { 148 | var idx = attributes[i].location; 149 | if (idx == -1) continue; 150 | GL.disableVertexAttribArray(idx); 151 | } 152 | } 153 | 154 | } -------------------------------------------------------------------------------- /shaderblox/ShaderBase.hx: -------------------------------------------------------------------------------- 1 | package shaderblox; 2 | #if snow 3 | import snow.render.opengl.GL; 4 | #elseif lime 5 | import lime.gl.GL; 6 | import lime.gl.GLProgram; 7 | import lime.gl.GLShader; 8 | #end 9 | import shaderblox.attributes.Attribute; 10 | import shaderblox.uniforms.IAppliable; 11 | import shaderblox.uniforms.UTexture; 12 | 13 | /** 14 | * Base shader type. Extend this to define new shader objects. 15 | * Subclasses of ShaderBase must define shader source metadata. 16 | * See example/SimpleShader.hx. 17 | * @author Andreas Rønning 18 | */ 19 | 20 | @:autoBuild(shaderblox.macro.ShaderBuilder.build()) 21 | class ShaderBase 22 | { 23 | public var active:Bool; 24 | var ready:Bool; 25 | public var textures:Array; 26 | 27 | var uniforms:Array; 28 | var attributes:Array; 29 | var name:String; 30 | var numTextures:Int; 31 | var aStride:Int; 32 | 33 | function new() { 34 | init(); 35 | } 36 | 37 | function init() { 38 | textures = []; 39 | uniforms = []; 40 | attributes = []; 41 | name = ("" + Type.getClass(this)).split(".").pop(); 42 | createProperties(); 43 | } 44 | 45 | function createProperties() { } 46 | 47 | public function destroy() { } 48 | 49 | function validateUniformLocations(shdr:Shader) { 50 | //Validate uniform locations 51 | var count = uniforms.length; 52 | var removeList:Array = []; 53 | numTextures = 0; 54 | textures = []; 55 | for (u in uniforms) { 56 | var loc = shdr.uniformLocations.get(u.name); 57 | if (Std.is(u, UTexture)) { 58 | var t:UTexture = cast u; 59 | t.samplerIndex = numTextures++; 60 | textures[t.samplerIndex] = t; 61 | } 62 | if (loc != null) { 63 | u.location = loc; 64 | #if (debug && !display) trace("Defined uniform "+u.name+" at "+u.location); #end 65 | }else { 66 | removeList.push(u); 67 | trace("WARNING(" + name + "): unused uniform '" + u.name +"'"); 68 | } 69 | } 70 | while (removeList.length > 0) { 71 | uniforms.remove(removeList.pop()); 72 | } 73 | ready = true; 74 | } 75 | 76 | public function activate() {} 77 | 78 | public function deactivate() {} 79 | 80 | public function setAttributes() { } 81 | 82 | public function setUniforms() 83 | { 84 | for (u in uniforms) { 85 | u.apply(); 86 | } 87 | } 88 | 89 | public function toString():String { 90 | return "[Shader(" + name+", attributes:" + attributes.length + ", uniforms:" + uniforms.length + ")]"; 91 | } 92 | } -------------------------------------------------------------------------------- /shaderblox/attributes/Attribute.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.attributes; 2 | 3 | /** 4 | * Base attribute type with byte size, program location and attribute name 5 | * @author Andreas Rønning 6 | */ 7 | class Attribute 8 | { 9 | public var byteSize:Int; 10 | public var itemCount:Int; 11 | public var location:Int; 12 | public var name:String; 13 | public var type:Int; 14 | 15 | } -------------------------------------------------------------------------------- /shaderblox/attributes/FloatAttribute.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.attributes; 2 | #if snow 3 | import snow.render.opengl.GL; 4 | #elseif lime 5 | import lime.gl.GL; 6 | #end 7 | 8 | /** 9 | * 4-byte float vertex attribute occupying a variable number of floats 10 | * @author Andreas Rønning 11 | */ 12 | class FloatAttribute extends Attribute 13 | { 14 | public function new(name:String, location:Int, nFloats:Int = 1) 15 | { 16 | this.name = name; 17 | this.location = location; 18 | byteSize = nFloats * 4; 19 | itemCount = nFloats; 20 | type = GL.FLOAT; 21 | } 22 | public function toString():String 23 | { 24 | return "[FloatAttribute itemCount=" + itemCount + " byteSize=" + byteSize + " location=" + location + " name=" + name + "]"; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /shaderblox/example/SimpleShader.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.example; 2 | import shaderblox.ShaderBase; 3 | 4 | /** 5 | * "Hello world" shader example 6 | * @author Andreas Rønning 7 | */ 8 | 9 | @:vert(' 10 | attribute vec2 aVertexPosition; 11 | void main(void) { 12 | gl_Position = vec4(aVertexPosition,0.0,1.0); 13 | } 14 | ') 15 | @:frag(' 16 | uniform vec3 uColor; 17 | void main(void) 18 | { 19 | gl_FragColor = vec4(uColor,1.0); 20 | } 21 | ') 22 | class SimpleShader extends ShaderBase {} -------------------------------------------------------------------------------- /shaderblox/macro/ShaderBuilder.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.macro; 2 | import haxe.macro.Context; 3 | import haxe.macro.Expr; 4 | import haxe.macro.Expr.Field; 5 | import haxe.macro.Type.ClassType; 6 | import haxe.macro.Type.Ref; 7 | import haxe.rtti.Meta; 8 | using Lambda; 9 | 10 | /** 11 | * ... 12 | * @author Andreas Rønning 13 | */ 14 | private typedef FieldDef = {index:Int, typeName:String, fieldName:String, extrainfo:Dynamic }; 15 | private typedef AttribDef = {index:Int, typeName:String, fieldName:String, itemCount:Int }; 16 | class ShaderBuilder 17 | { 18 | #if macro 19 | 20 | static var uniformFields:Array; 21 | static var attributeFields:Array; 22 | static var vertSource:String; 23 | static var fragSource:String; 24 | static var asTemplate:Bool; 25 | static inline var GL_FLOAT:Int = 0x1406; 26 | static inline var GL_INT:Int = 0x1404; 27 | 28 | public static function build():Array { 29 | var type = Context.getLocalClass().get(); 30 | asTemplate = false; 31 | for (f in type.meta.get().array()) { 32 | if (f.name == ":shaderNoBuild") return null; 33 | if (f.name == ":shaderTemplate") { 34 | asTemplate = true; 35 | } 36 | } 37 | 38 | uniformFields = []; 39 | attributeFields = []; 40 | var position = haxe.macro.Context.currentPos(); 41 | var fields = Context.getBuildFields(); 42 | var newFields:Array = []; 43 | var sources:Array> = []; 44 | var t2 = type; 45 | vertSource = ""; 46 | fragSource = ""; 47 | 48 | #if debug 49 | trace("Building " + Context.getLocalClass()); 50 | #end 51 | 52 | //static fields 53 | 54 | var ct = TPath( { pack:["shaderblox"], name:"Shader" } ); 55 | 56 | var f = { 57 | name:"instance", 58 | kind:FVar(ct), 59 | access:[APublic, AStatic], 60 | pos:position 61 | } 62 | fields.push(f); 63 | 64 | // 65 | 66 | while (t2.superClass != null) { 67 | t2 = t2.superClass.t.get(); 68 | if (t2.superClass != null) { 69 | #if debug 70 | trace("\tIncluding: " + t2.name); 71 | #end 72 | sources.unshift(getSources(t2)); 73 | } 74 | } 75 | sources.push(getSources(Context.getLocalClass().get())); 76 | for (i in sources) { 77 | 78 | if (i[0] != null) vertSource += i[0] + "\n"; 79 | if (i[1] != null) fragSource += i[1] + "\n"; 80 | } 81 | 82 | if(vertSource!=""){ 83 | vertSource = pragmas(unifyLineEndings(vertSource)); 84 | var lines:Array = vertSource.split("\n"); 85 | buildUniforms(position, newFields, lines); 86 | buildAttributes(position, newFields, lines); 87 | vertSource = lines.join("\n"); 88 | }else { 89 | throw "No vert source"; 90 | } 91 | if(fragSource!=""){ 92 | fragSource = pragmas(unifyLineEndings(fragSource)); 93 | var lines:Array = fragSource.split("\n"); 94 | buildUniforms(position, newFields, lines); 95 | fragSource = lines.join("\n"); 96 | }else { 97 | throw "No frag source"; 98 | } 99 | 100 | buildOverrides(fields); 101 | 102 | return complete(newFields.concat(fields)); 103 | } 104 | 105 | static function getSources(type:ClassType):Array { 106 | var meta = type.meta.get(); 107 | var out = []; 108 | var foundVert:Bool, foundFrag:Bool; 109 | var str:String; 110 | for (i in meta.array()) { 111 | switch(i.name) { 112 | case ":vert": 113 | foundVert = true; 114 | str = getString(i.params[0]); 115 | str = pragmas(unifyLineEndings(str)); 116 | out[0] = str; 117 | case ":frag": 118 | foundFrag = true; 119 | str = getString(i.params[0]); 120 | str = pragmas(unifyLineEndings(str)); 121 | out[1] = str; 122 | } 123 | } 124 | return out; 125 | } 126 | 127 | 128 | 129 | static function buildAttributes(position, fields:Array, lines:Array) { 130 | for (l in lines) { 131 | if (l.indexOf("attribute") > -1) { 132 | buildAttribute(position, fields, l); 133 | } 134 | } 135 | } 136 | static function buildUniforms(position, fields:Array, lines:Array) { 137 | for (i in 0...lines.length) { 138 | var l = lines[i]; 139 | if (l.indexOf("uniform") > -1) { 140 | l = buildUniform(position, fields, l); 141 | } 142 | lines[i] = l; 143 | } 144 | } 145 | 146 | static function checkIfFieldDefined(name:String):Bool { 147 | var type:ClassType = Context.getLocalClass().get(); 148 | while (type != null) { 149 | for (fld in type.fields.get()) { 150 | if (fld.name == name) { 151 | return true; 152 | } 153 | } 154 | if (type.superClass != null) { 155 | type = type.superClass.t.get(); 156 | }else { 157 | type = null; 158 | } 159 | } 160 | return false; 161 | } 162 | 163 | static function buildAttribute(position, fields, source:String):Void { 164 | source = StringTools.trim(source); 165 | var args = source.split(" ").slice(1); 166 | var name = StringTools.trim(args[1].split(";").join("")); 167 | 168 | //Avoid field redefinitions 169 | if (checkIfFieldDefined(name)) return; 170 | 171 | for (existing in attributeFields) { 172 | if (existing.fieldName == name) return; 173 | } 174 | var pack = ["shaderblox", "attributes"]; 175 | var itemCount:Int = 0; 176 | var itemType:Int = -1; 177 | switch(args[0]) { 178 | case "float": 179 | itemCount = 1; 180 | itemType = GL_FLOAT; 181 | case "vec2": 182 | itemCount = 2; 183 | itemType = GL_FLOAT; 184 | case "vec3": 185 | itemCount = 3; 186 | itemType = GL_FLOAT; 187 | case "vec4": 188 | itemCount = 4; 189 | itemType = GL_FLOAT; 190 | default: 191 | throw "Unknown attribute type: " + args[0]; 192 | } 193 | var attribClassName:String = switch(itemType) { 194 | case GL_FLOAT: 195 | "FloatAttribute"; 196 | default: 197 | throw "Unknown attribute type: " + itemType; 198 | } 199 | var type = { pack : pack, name : attribClassName, params : [], sub : null }; 200 | var fld = { 201 | name : name, 202 | doc : null, 203 | meta : [], 204 | access : [APublic], 205 | kind : FVar(TPath(type), null), 206 | pos : position 207 | }; 208 | fields.push(fld); 209 | var f = { index:attributeFields.length, fieldName:name, typeName:pack.join(".") + "." + attribClassName, itemCount:itemCount }; 210 | attributeFields.push(f); 211 | } 212 | static function buildUniform(position, fields, source:String):String { 213 | source = StringTools.trim(source); 214 | 215 | //check annotations 216 | var annotation:Null; 217 | var ai = source.indexOf("@"); 218 | if (ai > -1) { 219 | //annotation found, which? 220 | annotation = source.substr(ai, source.indexOf(" ", ai)); 221 | source = { var s = source.split(" "); s.shift(); s.join(" "); }; 222 | switch(annotation) { 223 | case "@color": 224 | default: throw "Unknown annotation: " + annotation; 225 | } 226 | } 227 | 228 | var args = source.split(" ").slice(1); 229 | var name = StringTools.trim(args[1].split(";").join("")); 230 | 231 | if (checkIfFieldDefined(name)) return source; 232 | 233 | for (existing in uniformFields) { 234 | if (existing.fieldName == name) return source; 235 | } 236 | 237 | var pack = ["shaderblox", "uniforms"]; 238 | var type = { pack : pack, name : "UMatrix", params : [], sub : null }; 239 | var extrainfo:Dynamic = null; 240 | switch(args[0]) { 241 | case "samplerCube": 242 | type.name = "UTexture"; 243 | extrainfo = true; 244 | case "sampler2D": 245 | type.name = "UTexture"; 246 | extrainfo = false; 247 | case "mat4": 248 | type.name = "UMatrix"; 249 | case "float": 250 | type.name = "UFloat"; 251 | case "vec2": 252 | type.name = "UVec2"; 253 | case "vec3": 254 | if (annotation == "@color") { 255 | type.name = "UColor3"; 256 | }else { 257 | type.name = "UVec3"; 258 | } 259 | case "vec4": 260 | if (annotation == "@color") { 261 | type.name = "UColor4"; 262 | }else { 263 | type.name = "UVec4"; 264 | } 265 | default: 266 | throw "Unknown uniform type: " + args[0]; 267 | } 268 | var f = { 269 | name : name, 270 | doc : null, 271 | meta : [], 272 | access : [APublic], 273 | kind : FVar(TPath(type), null), 274 | pos : position 275 | }; 276 | fields.push(f); 277 | uniformFields.push( 278 | {index:-1, fieldName:f.name, typeName:pack.join(".") + "." + type.name, extrainfo:extrainfo } 279 | ); 280 | return source; 281 | } 282 | 283 | static function getString(e:Expr):String { 284 | switch( e.expr ) { 285 | case EConst(c): 286 | switch( c ) { 287 | case CString(s): return s; 288 | case _: 289 | } 290 | case EField(e, f): 291 | 292 | case _: 293 | } 294 | throw("No const"); 295 | } 296 | 297 | static function unifyLineEndings(src:String):String { 298 | return StringTools.trim(src.split("\r").join("\n").split("\n\n").join("\n")); 299 | } 300 | 301 | static function pragmas(src:String):String { 302 | var lines = src.split("\n"); 303 | var found:Bool = true; 304 | for (i in 0...lines.length) { 305 | var l = lines[i]; 306 | if (l.indexOf("#pragma include") > -1) { 307 | var info = l.substring(l.indexOf('"') + 1, l.lastIndexOf('"')); 308 | lines[i] = pragmas(sys.io.File.getContent(info)); 309 | } 310 | } 311 | return lines.join("\n"); 312 | } 313 | 314 | static function buildOverrides(fields:Array) 315 | { 316 | 317 | var func = { 318 | name : "setAttributes", 319 | doc : null, 320 | meta : [], 321 | access : [AOverride, APublic], 322 | kind : FFun( { args:[], params:[], ret:null, expr:macro { 323 | instance.setAttributes(); 324 | }}), 325 | pos : Context.currentPos() 326 | }; 327 | fields.push(func); 328 | 329 | var func = { 330 | name : "createProperties", 331 | doc : null, 332 | meta : [], 333 | access : [AOverride, APrivate], 334 | kind : FFun( { args:[], params:[], ret:null, expr:macro { super.createProperties(); }} ), 335 | pos : Context.currentPos() 336 | }; 337 | fields.push(func); 338 | 339 | 340 | var func = { 341 | name : "destroy", 342 | doc : null, 343 | meta : [], 344 | access : [AOverride, APublic], 345 | kind : FFun( { args:[], params:[], ret:null, expr:macro { 346 | if (instance != null) { 347 | instance.dispose(); 348 | instance = null; 349 | } 350 | }} ), 351 | pos : Context.currentPos() 352 | }; 353 | fields.push(func); 354 | 355 | var func = { 356 | name : "activate", 357 | doc : null, 358 | meta : [], 359 | access : [AOverride, APublic], 360 | kind : FFun( { args:[], params:[], ret:null, expr:macro { 361 | if (instance == null) { 362 | trace("Creating shader instance"); 363 | instance = new shaderblox.Shader(); 364 | instance.aStride = aStride; 365 | instance.attributes = attributes; 366 | instance.name = name; 367 | instance.initFromSource($v { vertSource }, $v { fragSource } ); 368 | validateUniformLocations(instance); 369 | } 370 | if (!instance.ready) { 371 | instance.rebuild(); 372 | validateUniformLocations(instance); 373 | } 374 | if (!ready) { 375 | validateUniformLocations(instance); 376 | } 377 | 378 | if (active) { 379 | instance.setAttributes(); 380 | setUniforms(); 381 | return; 382 | } 383 | instance.bind(); 384 | setUniforms(); 385 | active = true; 386 | }} ), 387 | pos : Context.currentPos() 388 | }; 389 | fields.push(func); 390 | 391 | var func = { 392 | name : "deactivate", 393 | doc : null, 394 | meta : [], 395 | access : [AOverride, APublic], 396 | kind : FFun( { args:[], params:[], ret:null, expr:macro { 397 | if (!active) return; 398 | instance.disableAttributes(); 399 | instance.release(); 400 | active = false; 401 | }} ), 402 | pos : Context.currentPos() 403 | }; 404 | fields.push(func); 405 | } 406 | 407 | static function complete(allFields:Array) 408 | { 409 | for (f in allFields) { 410 | switch(f.name) { 411 | case "createProperties": 412 | switch(f.kind) { 413 | case FFun(func): 414 | switch(func.expr.expr) { 415 | case EBlock(exprs): 416 | //Populate our variables 417 | //Create an array of uniforms 418 | for (uni in uniformFields) { 419 | var name:String = uni.fieldName; 420 | var i:Expr = null; 421 | if (uni.typeName.split(".").pop() == "UTexture") { 422 | i = instantiation(uni.typeName, [macro $v{uni.fieldName}, macro $v{uni.index}, macro $v{uni.extrainfo}]); 423 | }else { 424 | i = instantiation(uni.typeName, [macro $v{uni.fieldName}, macro $v{uni.index}]); 425 | } 426 | exprs.push( 427 | macro { 428 | uniforms.push($i { name } = $ { i }); 429 | } 430 | ); 431 | } 432 | var stride:Int = 0; 433 | for (att in attributeFields) { 434 | var name:String = att.fieldName; 435 | var numItems:Int = att.itemCount; 436 | stride += numItems * 4; 437 | var i:Expr = instantiation(att.typeName, [macro $v{att.fieldName}, macro $v{att.index}, macro $v{numItems}]); 438 | exprs.push( 439 | macro { 440 | attributes.push($ { i }); 441 | } 442 | ); 443 | } 444 | exprs.push( 445 | macro { 446 | aStride += $v { stride }; 447 | } 448 | ); 449 | default: 450 | } 451 | default: 452 | } 453 | } 454 | } 455 | 456 | uniformFields = null; 457 | attributeFields = null; 458 | return allFields; 459 | } 460 | 461 | static function instantiation(name:String, ?args:Array):Expr { 462 | if (args == null) args = []; 463 | var s = name.split("."); 464 | name = s.pop(); 465 | return { expr:ENew( { name:name, pack:s }, args), pos:Context.currentPos() }; 466 | } 467 | #end 468 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/IAppliable.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | 3 | /** 4 | * All Uniforms are IAppliable. 5 | * "apply()" is used to upload updated uniform values to the GPU. 6 | * @author Andreas Rønning 7 | */ 8 | 9 | interface IAppliable 10 | { 11 | var location:Int; 12 | var name:String; 13 | function apply():Void; 14 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/UBool.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | import snow.render.opengl.GL; 3 | 4 | /** 5 | * Bool uniform 6 | * @author Andreas Rønning 7 | */ 8 | class UBool extends UniformBase implements IAppliable { 9 | public function new(name:String, index:Int, f:Bool = false) { 10 | super(name, index, f); 11 | } 12 | public inline function apply():Void { 13 | GL.uniform1i(location, data ? 1 : 0); 14 | dirty = false; 15 | } 16 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/UColor3.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | 3 | /** 4 | * ... 5 | * @author Andreas Rønning 6 | */ 7 | abstract UColor3(UVec3) from UVec3 to UVec3 8 | { 9 | 10 | public inline function new(name:String, index:Int) 11 | { 12 | this = new UVec3(name, index); 13 | } 14 | public inline function apply():Void { 15 | this.apply(); 16 | } 17 | 18 | public var r(get, set):Float; 19 | public var g(get, set):Float; 20 | public var b(get, set):Float; 21 | 22 | inline function get_r():Float { return this.data.x; }; 23 | inline function get_g():Float { return this.data.y; }; 24 | inline function get_b():Float { return this.data.z; }; 25 | 26 | inline function set_r(v:Float):Float { return this.data.x = v; }; 27 | inline function set_g(v:Float):Float { return this.data.y = v; }; 28 | inline function set_b(v:Float):Float { return this.data.z = v; }; 29 | 30 | public inline function setFromHex(color:Int):UColor3 { 31 | r = (color >> 16) / 255; 32 | g = (color >> 8 & 0xFF) / 255; 33 | b = (color & 0xFF) / 255; 34 | return this; 35 | } 36 | public inline function toHex():Int { 37 | return Std.int(r * 255) << 16 | Std.int(g * 255) << 8 | Std.int(b * 255); 38 | } 39 | public inline function copyFrom(other:UColor3):Void { 40 | r = other.r; 41 | g = other.g; 42 | b = other.b; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/UColor4.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | 3 | /** 4 | * ... 5 | * @author Andreas Rønning 6 | */ 7 | abstract UColor4(UVec4) from UVec4 to UVec4 8 | { 9 | 10 | public inline function new(name:String, index:Int) 11 | { 12 | this = new UVec4(name, index); 13 | } 14 | 15 | public inline function apply():Void { 16 | this.apply(); 17 | } 18 | 19 | public var r(get, set):Float; 20 | public var g(get, set):Float; 21 | public var b(get, set):Float; 22 | public var a(get, set):Float; 23 | 24 | inline function get_r():Float { return this.data.x; }; 25 | inline function get_g():Float { return this.data.y; }; 26 | inline function get_b():Float { return this.data.z; }; 27 | inline function get_a():Float { return this.data.w; }; 28 | 29 | inline function set_r(v:Float):Float { return this.data.x=v; }; 30 | inline function set_g(v:Float):Float { return this.data.y=v; }; 31 | inline function set_b(v:Float):Float { return this.data.z=v; }; 32 | inline function set_a(v:Float):Float { return this.data.w=v; }; 33 | 34 | public inline function setFromHex(color:Int):UColor4 { 35 | a = (color >> 24) / 255; 36 | r = (color >> 16 & 0xFF) / 255; 37 | g = (color >> 8 & 0xFF) / 255; 38 | b = (color & 0xFF) / 255; 39 | return this; 40 | } 41 | public inline function toHex():Int { 42 | return Std.int(a * 255) << 24 | Std.int(r * 255) << 16 | Std.int(g * 255) << 8 | Std.int(b * 255); 43 | } 44 | public inline function copyFrom(other:UColor4):Void { 45 | r = other.r; 46 | g = other.g; 47 | b = other.b; 48 | a = 1; 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/UFloat.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | #if snow 3 | import snow.render.opengl.GL; 4 | #elseif lime 5 | import lime.gl.GL; 6 | #end 7 | 8 | /** 9 | * Float uniform 10 | * @author Andreas Rønning 11 | */ 12 | class UFloat extends UniformBase implements IAppliable { 13 | public function new(name:String, index:Int, f:Float = 0.0) { 14 | super(name, index, f); 15 | } 16 | public inline function apply():Void { 17 | if (location != -1) { 18 | GL.uniform1f(location, data); 19 | dirty = false; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/UInt.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | import snow.render.opengl.GL; 3 | 4 | /** 5 | * Int uniform 6 | * @author Andreas Rønning 7 | */ 8 | class UInt extends UniformBase implements IAppliable { 9 | public function new(name:String, index:Int, f:Int = 0) { 10 | super(name, index, f); 11 | } 12 | public inline function apply():Void { 13 | GL.uniform1i(location, data); 14 | dirty = false; 15 | } 16 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/UMatrix.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | #if snow 3 | import snow.utils.Float32Array; 4 | import falconer.utils.Matrix3D; 5 | import snow.render.opengl.GL; 6 | #elseif lime 7 | import lime.gl.GL; 8 | import lime.utils.Matrix3D; 9 | #else 10 | throw "Requires lime or snow"; 11 | #end 12 | 13 | /** 14 | * Matrix3D uniform (not transposed) 15 | * @author Andreas Rønning 16 | */ 17 | class UMatrix extends UniformBase implements IAppliable { 18 | public function new(name:String, index:Int, ?m:Matrix3D) { 19 | if (m == null) m = new Matrix3D(); 20 | super(name, index, m); 21 | } 22 | public inline function apply():Void { 23 | #if lime 24 | if (location != -1) { 25 | GL.uniformMatrix3D(location, false, data); 26 | dirty = false; 27 | } 28 | #elseif snow 29 | if (location != -1) { 30 | GL.uniformMatrix4fv(location, false, new Float32Array(data.rawData)); 31 | dirty = false; 32 | } 33 | #end 34 | } 35 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/UMatrixTransposed.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | #if lime 3 | import lime.utils.Matrix3D; 4 | import lime.gl.GL; 5 | #elseif snow 6 | import falconer.utils.Matrix3D; 7 | import snow.render.opengl.GL; 8 | #end 9 | 10 | /** 11 | * Transposed Matrix3D uniform 12 | * @author Andreas Rønning 13 | */ 14 | class UMatrixTransposed extends UniformBase implements IAppliable { 15 | public function new(index:Int, ?m:Matrix3D) { 16 | if (m == null) m = new Matrix3D(); 17 | super(index, m); 18 | } 19 | public inline function apply():Void { 20 | if (location != -1) { 21 | GL.uniformMatrix3D(location, true, data); 22 | dirty = false; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/UTexture.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | #if snow 3 | import snow.render.opengl.GL; 4 | #elseif lime 5 | import lime.gl.GL; 6 | import lime.gl.GLTexture; 7 | #end 8 | 9 | /** 10 | * GLTexture uniform 11 | * @author Andreas Rønning 12 | */ 13 | class UTexture extends UniformBase implements IAppliable { 14 | public var samplerIndex:Int; 15 | static var lastActiveTexture:Int = -1; 16 | var cube:Bool; 17 | public var type:Int; 18 | public function new(name:String, index:Int, cube:Bool = false) { 19 | this.cube = cube; 20 | type = cube?GL.TEXTURE_CUBE_MAP:GL.TEXTURE_2D; 21 | super(name, index, null); 22 | } 23 | public inline function apply():Void { 24 | if (data == null || location==-1 ) return; 25 | var idx = GL.TEXTURE0 + samplerIndex; 26 | if (lastActiveTexture != idx) { 27 | GL.activeTexture(lastActiveTexture = idx); 28 | } 29 | GL.uniform1i(location, samplerIndex); 30 | GL.bindTexture(type, data); 31 | dirty = false; 32 | } 33 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/UVec2.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | #if snow 3 | import snow.render.opengl.GL; 4 | #elseif lime 5 | import lime.gl.GL; 6 | #end 7 | 8 | /** 9 | * Vector2 float uniform 10 | * @author Andreas Rønning 11 | */ 12 | #if flywheel 13 | private typedef Pt = com.furusystems.flywheel.geom.Vector2D; 14 | #else 15 | private class Pt { 16 | public var x:Float; 17 | public var y:Float; 18 | public inline function new() { } 19 | } 20 | #end 21 | class UVec2 extends UniformBase implements IAppliable { 22 | public function new(name:String, index:Int, x:Float = 0, y:Float = 0) { 23 | var p = new Pt(); 24 | p.x = x; 25 | p.y = y; 26 | super(name, index, p); 27 | } 28 | public inline function apply():Void { 29 | if (location != -1) { 30 | GL.uniform2f(location, data.x, data.y); 31 | dirty = false; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/UVec3.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | #if snow 3 | import snow.render.opengl.GL; 4 | import falconer.utils.Vector3D; 5 | #elseif lime 6 | import lime.gl.GL; 7 | import lime.utils.Vector3D; 8 | #end 9 | 10 | /** 11 | * Vector3 float uniform 12 | * @author Andreas Rønning 13 | */ 14 | class UVec3 extends UniformBase implements IAppliable { 15 | public function new(name:String, index:Int, x:Float = 0, y:Float = 0, z:Float = 0) { 16 | super(name, index, new Vector3D(x, y, z)); 17 | } 18 | public inline function apply():Void { 19 | if (location != -1) { 20 | GL.uniform3f(location, data.x, data.y, data.z); 21 | dirty = false; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/UVec4.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | #if snow 3 | import snow.render.opengl.GL; 4 | import falconer.utils.Vector3D; 5 | #elseif lime 6 | import lime.gl.GL; 7 | import lime.utils.Vector3D; 8 | #end 9 | 10 | /** 11 | * Vector4 float uniform 12 | * @author Andreas Rønning 13 | */ 14 | class UVec4 extends UniformBase implements IAppliable { 15 | public function new(name:String, index:Int, x:Float = 0, y:Float = 0, z:Float = 0, w:Float = 0) { 16 | super(name, index, new Vector3D(x, y, z, w)); 17 | } 18 | public inline function apply():Void { 19 | if (location != -1) { 20 | GL.uniform4f(location, data.x, data.y, data.z, data.w); 21 | dirty = false; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /shaderblox/uniforms/UniformBase.hx: -------------------------------------------------------------------------------- 1 | package shaderblox.uniforms; 2 | 3 | /** 4 | * Generic uniform type 5 | * @author Andreas Rønning 6 | */ 7 | @:generic @:remove class UniformBase { 8 | public var name:String; 9 | public var location:Int; 10 | public var data:T; 11 | public var dirty:Bool; 12 | function new(name:String, index:Int, data:T) { 13 | this.name = name; 14 | this.location = index; 15 | this.data = data; 16 | dirty = true; 17 | } 18 | public inline function set(data:T):T { 19 | setDirty(); 20 | return this.data = data; 21 | } 22 | public inline function setDirty() { 23 | dirty = true; 24 | } 25 | } --------------------------------------------------------------------------------