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 }