1 /* 2 Inochi2D Composite Node 3 4 Copyright © 2020, Inochi2D Project 5 Distributed under the 2-Clause BSD License, see LICENSE file. 6 7 Authors: Luna Nielsen 8 */ 9 module inochi2d.core.nodes.composite; 10 import inochi2d.core.nodes.common; 11 import inochi2d.core.nodes; 12 import inochi2d.fmt; 13 import inochi2d.core; 14 import inochi2d.math; 15 import bindbc.opengl; 16 import std.exception; 17 import std.algorithm.sorting; 18 19 private { 20 GLuint cVAO; 21 GLuint cBuffer; 22 Shader cShader; 23 Shader cShaderMask; 24 25 GLint gopacity; 26 GLint gtint; 27 28 GLint mthreshold; 29 GLint mopacity; 30 } 31 32 package(inochi2d) { 33 void inInitComposite() { 34 inRegisterNodeType!Composite; 35 36 cShader = new Shader( 37 import("basic/composite.vert"), 38 import("basic/composite.frag") 39 ); 40 gopacity = cShader.getUniformLocation("opacity"); 41 gtint = cShader.getUniformLocation("tint"); 42 43 cShaderMask = new Shader( 44 import("basic/composite.vert"), 45 import("basic/composite-mask.frag") 46 ); 47 mthreshold = cShader.getUniformLocation("threshold"); 48 mopacity = cShader.getUniformLocation("opacity"); 49 50 glGenVertexArrays(1, &cVAO); 51 glGenBuffers(1, &cBuffer); 52 53 // Clip space vertex data since we'll just be superimposing 54 // Our composite framebuffer over the main framebuffer 55 float[] vertexData = [ 56 // verts 57 -1f, -1f, 58 -1f, 1f, 59 1f, -1f, 60 1f, -1f, 61 -1f, 1f, 62 1f, 1f, 63 64 // uvs 65 0f, 0f, 66 0f, 1f, 67 1f, 0f, 68 1f, 0f, 69 0f, 1f, 70 1f, 1f, 71 ]; 72 73 glBindVertexArray(cVAO); 74 glBindBuffer(GL_ARRAY_BUFFER, cBuffer); 75 glBufferData(GL_ARRAY_BUFFER, float.sizeof*vertexData.length, vertexData.ptr, GL_STATIC_DRAW); 76 } 77 } 78 79 /** 80 Composite Node 81 */ 82 @TypeId("Composite") 83 class Composite : Node { 84 private: 85 86 this() { } 87 88 /* 89 RENDERING 90 */ 91 void drawSelf() { 92 inBeginComposite(); 93 94 foreach(Part child; subParts) { 95 child.drawOne(); 96 } 97 98 inEndComposite(); 99 100 glBindVertexArray(cVAO); 101 cShader.use(); 102 cShader.setUniform(gopacity, clamp(offsetOpacity * opacity, 0, 1)); 103 104 vec3 clampedColor = tint; 105 if (!offsetTint.x.isNaN) clampedColor.x = clamp(tint.x*offsetTint.x, 0, 1); 106 if (!offsetTint.y.isNaN) clampedColor.y = clamp(tint.y*offsetTint.y, 0, 1); 107 if (!offsetTint.z.isNaN) clampedColor.z = clamp(tint.z*offsetTint.z, 0, 1); 108 cShader.setUniform(gtint, clampedColor); 109 inSetBlendMode(blendingMode); 110 111 // Enable points array 112 glEnableVertexAttribArray(0); 113 glEnableVertexAttribArray(1); 114 glBindBuffer(GL_ARRAY_BUFFER, cBuffer); 115 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null); 116 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, cast(void*)(12*float.sizeof)); 117 118 glActiveTexture(GL_TEXTURE0); 119 glBindTexture(GL_TEXTURE_2D, inGetCompositeImage()); 120 glDrawArrays(GL_TRIANGLES, 0, 6); 121 } 122 123 void selfSort() { 124 import std.math : cmp; 125 sort!((a, b) => cmp( 126 a.zSort, 127 b.zSort) > 0)(subParts); 128 } 129 130 void scanPartsRecurse(ref Node node) { 131 132 // Don't need to scan null nodes 133 if (node is null) return; 134 135 // Do the main check 136 if (Part part = cast(Part)node) { 137 subParts ~= part; 138 foreach(child; part.children) { 139 scanPartsRecurse(child); 140 } 141 142 } else { 143 144 // Non-part nodes just need to be recursed through, 145 // they don't draw anything. 146 foreach(child; node.children) { 147 scanPartsRecurse(child); 148 } 149 } 150 } 151 152 protected: 153 Part[] subParts; 154 155 void renderMask() { 156 inBeginComposite(); 157 158 // Enable writing to stencil buffer and disable writing to color buffer 159 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 160 glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); 161 glStencilFunc(GL_ALWAYS, 1, 0xFF); 162 glStencilMask(0xFF); 163 164 foreach(Part child; subParts) { 165 child.drawOneDirect(true); 166 } 167 168 // Disable writing to stencil buffer and enable writing to color buffer 169 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 170 inEndComposite(); 171 172 173 glBindVertexArray(cVAO); 174 cShaderMask.use(); 175 cShaderMask.setUniform(mopacity, opacity); 176 cShaderMask.setUniform(mthreshold, threshold); 177 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 178 179 // Enable points array 180 glEnableVertexAttribArray(0); 181 glEnableVertexAttribArray(1); 182 glBindBuffer(GL_ARRAY_BUFFER, cBuffer); 183 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null); 184 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, cast(void*)(12*float.sizeof)); 185 186 glActiveTexture(GL_TEXTURE0); 187 glBindTexture(GL_TEXTURE_2D, inGetCompositeImage()); 188 glDrawArrays(GL_TRIANGLES, 0, 6); 189 } 190 191 override 192 void serializeSelf(ref InochiSerializer serializer) { 193 super.serializeSelf(serializer); 194 195 serializer.putKey("blend_mode"); 196 serializer.serializeValue(blendingMode); 197 198 serializer.putKey("tint"); 199 tint.serialize(serializer); 200 201 serializer.putKey("mask_threshold"); 202 serializer.putValue(threshold); 203 204 serializer.putKey("opacity"); 205 serializer.putValue(opacity); 206 } 207 208 override 209 void serializeSelf(ref InochiSerializerCompact serializer) { 210 super.serializeSelf(serializer); 211 212 serializer.putKey("blend_mode"); 213 serializer.serializeValue(blendingMode); 214 215 serializer.putKey("tint"); 216 tint.serialize(serializer); 217 218 serializer.putKey("mask_threshold"); 219 serializer.putValue(threshold); 220 221 serializer.putKey("opacity"); 222 serializer.putValue(opacity); 223 } 224 225 override 226 SerdeException deserializeFromFghj(Fghj data) { 227 228 // Older models may not have these tags 229 if (!data["opacity"].isEmpty) data["opacity"].deserializeValue(this.opacity); 230 if (!data["mask_threshold"].isEmpty) data["mask_threshold"].deserializeValue(this.threshold); 231 if (!data["tint"].isEmpty) deserialize(this.tint, data["tint"]); 232 if (!data["blend_mode"].isEmpty) data["blend_mode"].deserializeValue(this.blendingMode); 233 234 return super.deserializeFromFghj(data); 235 } 236 237 // 238 // PARAMETER OFFSETS 239 // 240 float offsetOpacity = 1; 241 vec3 offsetTint = vec3(0); 242 243 override 244 string typeId() { return "Composite"; } 245 246 public: 247 248 /** 249 The blending mode 250 */ 251 BlendMode blendingMode; 252 253 /** 254 The opacity of the composite 255 */ 256 float opacity = 1; 257 258 /** 259 The threshold for rendering masks 260 */ 261 float threshold = 0.5; 262 263 /** 264 The tint of the composite 265 */ 266 vec3 tint = vec3(1, 1, 1); 267 268 269 /** 270 Constructs a new mask 271 */ 272 this(Node parent = null) { 273 this(inCreateUUID(), parent); 274 } 275 276 /** 277 Constructs a new composite 278 */ 279 this(uint uuid, Node parent = null) { 280 super(uuid, parent); 281 } 282 283 override 284 bool hasParam(string key) { 285 if (super.hasParam(key)) return true; 286 287 switch(key) { 288 case "opacity": 289 case "tint.r": 290 case "tint.g": 291 case "tint.b": 292 return true; 293 default: 294 return false; 295 } 296 } 297 298 override 299 float getDefaultValue(string key) { 300 // Skip our list of our parent already handled it 301 float def = super.getDefaultValue(key); 302 if (!isNaN(def)) return def; 303 304 switch(key) { 305 case "opacity": 306 case "tint.r": 307 case "tint.g": 308 case "tint.b": 309 return 1; 310 default: return float(); 311 } 312 } 313 314 override 315 bool setValue(string key, float value) { 316 317 // Skip our list of our parent already handled it 318 if (super.setValue(key, value)) return true; 319 320 switch(key) { 321 case "opacity": 322 offsetOpacity = value; 323 return true; 324 case "tint.r": 325 offsetTint.x = value; 326 return true; 327 case "tint.g": 328 offsetTint.y = value; 329 return true; 330 case "tint.b": 331 offsetTint.z = value; 332 return true; 333 default: return false; 334 } 335 } 336 337 override 338 void beginUpdate() { 339 offsetOpacity = 1; 340 offsetTint = vec3(1, 1, 1); 341 super.beginUpdate(); 342 } 343 344 override 345 void drawOne() { 346 super.drawOne(); 347 348 this.selfSort(); 349 this.drawSelf(); 350 } 351 352 override 353 void draw() { 354 if (!enabled) return; 355 this.drawOne(); 356 } 357 358 /** 359 Scans for parts to render 360 */ 361 void scanParts() { 362 subParts.length = 0; 363 if (children.length > 0) { 364 scanPartsRecurse(children[0].parent); 365 } 366 } 367 }