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 }