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 
43             cShader.use();
44             gopacity = cShader.getUniformLocation("opacity");
45             gMultColor = cShader.getUniformLocation("multColor");
46             gScreenColor = cShader.getUniformLocation("screenColor");
47             cShader.setUniform(cShader.getUniformLocation("albedo"), 0);
48             cShader.setUniform(cShader.getUniformLocation("emissive"), 1);
49             cShader.setUniform(cShader.getUniformLocation("bumpmap"), 2);
50 
51             cShaderMask = new Shader(
52                 import("basic/composite.vert"),
53                 import("basic/composite-mask.frag")
54             );
55             cShaderMask.use();
56             mthreshold = cShader.getUniformLocation("threshold");
57             mopacity = cShader.getUniformLocation("opacity");
58 
59             glGenVertexArrays(1, &cVAO);
60             glGenBuffers(1, &cBuffer);
61 
62             // Clip space vertex data since we'll just be superimposing
63             // Our composite framebuffer over the main framebuffer
64             float[] vertexData = [
65                 // verts
66                 -1f, -1f,
67                 -1f, 1f,
68                 1f, -1f,
69                 1f, -1f,
70                 -1f, 1f,
71                 1f, 1f,
72 
73                 // uvs
74                 0f, 0f,
75                 0f, 1f,
76                 1f, 0f,
77                 1f, 0f,
78                 0f, 1f,
79                 1f, 1f,
80             ];
81 
82             glBindVertexArray(cVAO);
83             glBindBuffer(GL_ARRAY_BUFFER, cBuffer);
84             glBufferData(GL_ARRAY_BUFFER, float.sizeof*vertexData.length, vertexData.ptr, GL_STATIC_DRAW);
85         }
86     }
87 }
88 
89 /**
90     Composite Node
91 */
92 @TypeId("Composite")
93 class Composite : Node {
94 private:
95 
96     this() { }
97 
98     void drawContents() {
99 
100         // Optimization: Nothing to be drawn, skip context switching
101         if (subParts.length == 0) return;
102 
103         inBeginComposite();
104 
105             foreach(Part child; subParts) {
106                 child.drawOne();
107             }
108 
109         inEndComposite();
110     }
111 
112     /*
113         RENDERING
114     */
115     void drawSelf() {
116         if (subParts.length == 0) return;
117 
118         glDrawBuffers(3, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2].ptr);
119         glBindVertexArray(cVAO);
120 
121         cShader.use();
122         cShader.setUniform(gopacity, clamp(offsetOpacity * opacity, 0, 1));
123         incCompositePrepareRender();
124         
125         vec3 clampedColor = tint;
126         if (!offsetTint.x.isNaN) clampedColor.x = clamp(tint.x*offsetTint.x, 0, 1);
127         if (!offsetTint.y.isNaN) clampedColor.y = clamp(tint.y*offsetTint.y, 0, 1);
128         if (!offsetTint.z.isNaN) clampedColor.z = clamp(tint.z*offsetTint.z, 0, 1);
129         cShader.setUniform(gMultColor, clampedColor);
130 
131         clampedColor = screenTint;
132         if (!offsetScreenTint.x.isNaN) clampedColor.x = clamp(screenTint.x+offsetScreenTint.x, 0, 1);
133         if (!offsetScreenTint.y.isNaN) clampedColor.y = clamp(screenTint.y+offsetScreenTint.y, 0, 1);
134         if (!offsetScreenTint.z.isNaN) clampedColor.z = clamp(screenTint.z+offsetScreenTint.z, 0, 1);
135         cShader.setUniform(gScreenColor, clampedColor);
136         inSetBlendMode(blendingMode, true);
137 
138         // Enable points array
139         glEnableVertexAttribArray(0);
140         glEnableVertexAttribArray(1);
141         glBindBuffer(GL_ARRAY_BUFFER, cBuffer);
142         glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null);
143         glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, cast(void*)(12*float.sizeof));
144 
145         // Bind the texture
146         glDrawArrays(GL_TRIANGLES, 0, 6);
147     }
148 
149     void selfSort() {
150         import std.math : cmp;
151         sort!((a, b) => cmp(
152             a.zSort, 
153             b.zSort) > 0)(subParts);
154     }
155 
156     void scanPartsRecurse(ref Node node) {
157 
158         // Don't need to scan null nodes
159         if (node is null) return;
160 
161         // Do the main check
162         if (Part part = cast(Part)node) {
163             subParts ~= part;
164             foreach(child; part.children) {
165                 scanPartsRecurse(child);
166             }
167             
168         } else {
169 
170             // Non-part nodes just need to be recursed through,
171             // they don't draw anything.
172             foreach(child; node.children) {
173                 scanPartsRecurse(child);
174             }
175         }
176     }
177 
178 protected:
179     Part[] subParts;
180     
181     void renderMask() {
182         inBeginComposite();
183 
184             // Enable writing to stencil buffer and disable writing to color buffer
185             glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
186             glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
187             glStencilFunc(GL_ALWAYS, 1, 0xFF);
188             glStencilMask(0xFF);
189 
190             foreach(Part child; subParts) {
191                 child.drawOneDirect(true);
192             }
193 
194             // Disable writing to stencil buffer and enable writing to color buffer
195             glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
196         inEndComposite();
197 
198 
199         glBindVertexArray(cVAO);
200         cShaderMask.use();
201         cShaderMask.setUniform(mopacity, opacity);
202         cShaderMask.setUniform(mthreshold, threshold);
203         glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
204 
205         // Enable points array
206         glEnableVertexAttribArray(0);
207         glEnableVertexAttribArray(1);
208         glBindBuffer(GL_ARRAY_BUFFER, cBuffer);
209         glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null);
210         glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, cast(void*)(12*float.sizeof));
211 
212         glActiveTexture(GL_TEXTURE0);
213         glBindTexture(GL_TEXTURE_2D, inGetCompositeImage());
214         glDrawArrays(GL_TRIANGLES, 0, 6);
215     }
216 
217     override
218     void serializeSelfImpl(ref InochiSerializer serializer, bool recursive=true) {
219         super.serializeSelfImpl(serializer, recursive);
220 
221         serializer.putKey("blend_mode");
222         serializer.serializeValue(blendingMode);
223 
224         serializer.putKey("tint");
225         tint.serialize(serializer);
226 
227         serializer.putKey("screenTint");
228         screenTint.serialize(serializer);
229 
230         serializer.putKey("mask_threshold");
231         serializer.putValue(threshold);
232 
233         serializer.putKey("opacity");
234         serializer.putValue(opacity);
235 
236         serializer.putKey("propagate_meshgroup");
237         serializer.serializeValue(propagateMeshGroup);
238 
239         if (masks.length > 0) {
240             serializer.putKey("masks");
241             auto state = serializer.arrayBegin();
242                 foreach(m; masks) {
243                     serializer.elemBegin;
244                     serializer.serializeValue(m);
245                 }
246             serializer.arrayEnd(state);
247 
248         }
249     }
250 
251     override
252     SerdeException deserializeFromFghj(Fghj data) {
253 
254         // Older models may not have these tags
255         if (!data["opacity"].isEmpty) data["opacity"].deserializeValue(this.opacity);
256         if (!data["mask_threshold"].isEmpty) data["mask_threshold"].deserializeValue(this.threshold);
257         if (!data["tint"].isEmpty) deserialize(this.tint, data["tint"]);
258         if (!data["screenTint"].isEmpty) deserialize(this.screenTint, data["screenTint"]);
259         if (!data["blend_mode"].isEmpty) data["blend_mode"].deserializeValue(this.blendingMode);
260         if (!data["masks"].isEmpty) data["masks"].deserializeValue(this.masks);
261         if (!data["propagate_meshgroup"].isEmpty)
262             data["propagate_meshgroup"].deserializeValue(propagateMeshGroup);
263         else // falls back to legacy default
264             propagateMeshGroup = false;
265 
266         return super.deserializeFromFghj(data);
267     }
268 
269     //
270     //      PARAMETER OFFSETS
271     //
272     float offsetOpacity = 1;
273     vec3 offsetTint = vec3(0);
274     vec3 offsetScreenTint = vec3(0);
275 
276     override
277     string typeId() { return "Composite"; }
278 
279     // TODO: Cache this
280     size_t maskCount() {
281         size_t c;
282         foreach(m; masks) if (m.mode == MaskingMode.Mask) c++;
283         return c;
284     }
285 
286     size_t dodgeCount() {
287         size_t c;
288         foreach(m; masks) if (m.mode == MaskingMode.DodgeMask) c++;
289         return c;
290     }
291 
292     override
293     void preProcess() {
294         if (!propagateMeshGroup)
295             Node.preProcess();
296     }
297 
298     override
299     void postProcess() {
300         if (!propagateMeshGroup)
301             Node.postProcess();
302     }
303 
304 public:
305     bool propagateMeshGroup = true;
306 
307     /**
308         The blending mode
309     */
310     BlendMode blendingMode;
311 
312     /**
313         The opacity of the composite
314     */
315     float opacity = 1;
316 
317     /**
318         The threshold for rendering masks
319     */
320     float threshold = 0.5;
321 
322     /**
323         Multiplicative tint color
324     */
325     vec3 tint = vec3(1, 1, 1);
326 
327     /**
328         Screen tint color
329     */
330     vec3 screenTint = vec3(0, 0, 0);
331 
332     /**
333         List of masks to apply
334     */
335     MaskBinding[] masks;
336 
337 
338     /**
339         Constructs a new mask
340     */
341     this(Node parent = null) {
342         this(inCreateUUID(), parent);
343     }
344 
345     /**
346         Constructs a new composite
347     */
348     this(uint uuid, Node parent = null) {
349         super(uuid, parent);
350     }
351 
352     override
353     bool hasParam(string key) {
354         if (super.hasParam(key)) return true;
355 
356         switch(key) {
357             case "opacity":
358             case "tint.r":
359             case "tint.g":
360             case "tint.b":
361             case "screenTint.r":
362             case "screenTint.g":
363             case "screenTint.b":
364                 return true;
365             default:
366                 return false;
367         }
368     }
369 
370     override
371     float getDefaultValue(string key) {
372         // Skip our list of our parent already handled it
373         float def = super.getDefaultValue(key);
374         if (!isNaN(def)) return def;
375 
376         switch(key) {
377             case "opacity":
378             case "tint.r":
379             case "tint.g":
380             case "tint.b":
381                 return 1;
382             case "screenTint.r":
383             case "screenTint.g":
384             case "screenTint.b":
385                 return 0;
386             default: return float();
387         }
388     }
389 
390     override
391     bool setValue(string key, float value) {
392         
393         // Skip our list of our parent already handled it
394         if (super.setValue(key, value)) return true;
395 
396         switch(key) {
397             case "opacity":
398                 offsetOpacity *= value;
399                 return true;
400             case "tint.r":
401                 offsetTint.x += value;
402                 return true;
403             case "tint.g":
404                 offsetTint.y += value;
405                 return true;
406             case "tint.b":
407                 offsetTint.z += value;
408                 return true;
409             case "screenTint.r":
410                 offsetScreenTint.x += value;
411                 return true;
412             case "screenTint.g":
413                 offsetScreenTint.y += value;
414                 return true;
415             case "screenTint.b":
416                 offsetScreenTint.z += value;
417                 return true;
418             default: return false;
419         }
420     }
421     
422     override
423     float getValue(string key) {
424         switch(key) {
425             case "opacity":         return offsetOpacity;
426             case "tint.r":          return offsetTint.x;
427             case "tint.g":          return offsetTint.y;
428             case "tint.b":          return offsetTint.z;
429             case "screenTint.r":    return offsetScreenTint.x;
430             case "screenTint.g":    return offsetScreenTint.y;
431             case "screenTint.b":    return offsetScreenTint.z;
432             default:                return super.getValue(key);
433         }
434     }
435 
436     bool isMaskedBy(Drawable drawable) {
437         foreach(mask; masks) {
438             if (mask.maskSrc.uuid == drawable.uuid) return true;
439         }
440         return false;
441     }
442 
443     ptrdiff_t getMaskIdx(Drawable drawable) {
444         if (drawable is null) return -1;
445         foreach(i, ref mask; masks) {
446             if (mask.maskSrc.uuid == drawable.uuid) return i;
447         }
448         return -1;
449     }
450 
451     ptrdiff_t getMaskIdx(uint uuid) {
452         foreach(i, ref mask; masks) {
453             if (mask.maskSrc.uuid == uuid) return i;
454         }
455         return -1;
456     }
457 
458     override
459     void beginUpdate() {
460         offsetOpacity = 1;
461         offsetTint = vec3(1, 1, 1);
462         offsetScreenTint = vec3(0, 0, 0);
463         super.beginUpdate();
464     }
465 
466     override
467     void drawOne() {
468         if (!enabled) return;
469         
470         this.selfSort();
471         this.drawContents();
472 
473         size_t cMasks = maskCount;
474 
475         if (masks.length > 0) {
476             inBeginMask(cMasks > 0);
477 
478             foreach(ref mask; masks) {
479                 mask.maskSrc.renderMask(mask.mode == MaskingMode.DodgeMask);
480             }
481 
482             inBeginMaskContent();
483 
484             // We are the content
485             this.drawSelf();
486 
487             inEndMask();
488             return;
489         }
490 
491         // No masks, draw normally
492         super.drawOne();
493         this.drawSelf();
494     }
495 
496     override
497     void draw() {
498         if (!enabled) return;
499         this.drawOne();
500     }
501 
502     override
503     void finalize() {
504         super.finalize();
505         
506         MaskBinding[] validMasks;
507         foreach(i; 0..masks.length) {
508             if (Drawable nMask = puppet.find!Drawable(masks[i].maskSrcUUID)) {
509                 masks[i].maskSrc = nMask;
510                 validMasks ~= masks[i];
511             }
512         }
513 
514         // Remove invalid masks
515         masks = validMasks;
516     }
517 
518     /**
519         Scans for parts to render
520     */
521     void scanParts() {
522         subParts.length = 0;
523         if (children.length > 0) {
524             scanPartsRecurse(children[0].parent);
525         }
526     }
527 }