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