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