1 /**
2     Inochi2D Composite Node
3 
4     Copyright: 
5         Copyright © 2020-2026, Inochi2D Project
6     
7     License:
8         $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License)
9     
10     Authors:
11         Luna Nielsen
12 */
13 module inochi2d.nodes.visual.composite;
14 import inochi2d.nodes.visual;
15 import inochi2d.common;
16 import inochi2d.nodes;
17 import inochi2d.core;
18 import numem;
19 import nulib;
20 
21 public import inochi2d.core.render.state;
22 
23 struct CompositeVars {
24     vec3 tint;
25     vec3 screenTint;
26     private void[4] __dummy0;
27     float opacity;
28 }
29 
30 /**
31     Composite Node
32 */
33 @TypeId("Composite", IN_MAKE_TAG!(3, 1))
34 class Composite : Visual {
35 private:
36 @nogc:
37     DrawListAlloc* ssDrawList_;
38     weak_vector!Visual visuals_;
39 
40 protected:
41 
42     /**
43         Serializes this node to a DataNode.
44 
45         Params:
46             object =    The DataNode to serialize to.
47     */
48     override
49     void onSerialize(ref DataNode object) {
50         super.onSerialize(object);
51         object["blend_mode"] = cast(uint)blendingMode;
52         object["tint"] = tint.serialize();
53         object["screenTint"] = screenTint.serialize();
54         object["opacity"] = opacity;
55     }
56 
57     /**
58         Deserializes this node from a DataNode.
59 
60         Params:
61             object =    The DataNode to deserialize from.
62             state =     The state of the deserializer.
63     */
64     override
65     void onDeserialize(ref DataNode object, ref ModelState state) {
66         super.onDeserialize(object, state);
67 
68         object.tryGetRef(state, opacity, "opacity");
69         object.tryGetRef(state, tint, "tint");
70         object.tryGetRef(state, screenTint, "screenTint");
71 
72         if ("blend_mode" in object && object["blend_mode"].isNumber)
73             blendingMode = cast(BlendMode)object.tryGet!uint(state, "blend_mode", blendingMode.normal);
74         else
75             blendingMode = object.tryGet!string(state, "blend_mode", "Normal").toBlendMode();
76     }
77 
78     /**
79         Called when the node is to finalize its deserialization from disk.
80 
81         Params:
82             state =     The state of the deserializer.
83     */
84     override
85     void onFinalize(ref ModelState state) {
86         this.notifyVisualsChanged();
87     }
88 
89     /**
90         Called when the node is to define its properties.
91 
92         Call $(D propList.define) with a quark to do this.
93 
94         Params:
95             propList = The property list to populate.
96     */
97     override
98     void onDefineProperties(ref PropertyStore propList) {
99         super.onDefineProperties(propList);
100 
101         propList.define!float(PROP_SCREEN_R, 0);
102         propList.define!float(PROP_SCREEN_G, 0);
103         propList.define!float(PROP_SCREEN_B, 0);
104         propList.define!float(PROP_TINT_R, 1);
105         propList.define!float(PROP_TINT_G, 1);
106         propList.define!float(PROP_TINT_B, 1);
107         propList.define!float(PROP_OPACITY, 1);
108 
109         // Define combined overlays.
110         propList.defineOverlay!vec3(PROP_SCREEN_RGB, propList.offsetOf(PROP_SCREEN_R));
111         propList.defineOverlay!vec3(PROP_TINT_RGB, propList.offsetOf(PROP_TINT_R));
112     }
113 
114     /**
115         Called during the early update phase of a new frame.
116         
117         Params:
118             drawList =  The drawlist for the active scene.
119     */
120     override
121     void onPreUpdate(DrawList drawList) {
122         super.onPreUpdate(drawList);
123         ssDrawList_ = null;
124     }
125 
126     /**
127         Called during the update phase of a new frame.
128         
129         Params:
130             delta =     Time since the last frame.
131             drawList =  The drawlist for the active scene.
132     */
133     override
134     void onUpdate(float delta, DrawList drawList) {
135         super.onUpdate(delta, drawList);
136 
137         // Avoid over allocating a single screenspace
138         // rect.
139         if (!ssDrawList_)
140             ssDrawList_ = drawList.allocate(__screenSpaceMesh.vertices, __screenSpaceMesh.indices);
141     }
142 
143     /**
144         Called when the node is to be redrawn.
145         
146         Params:
147             delta =     Time since the last frame.
148             drawList =  The drawlist for the active scene.
149     */
150     override
151     void onDraw(float delta, DrawList drawList) {
152         if (visuals_.length == 0)
153             return;
154 
155         CompositeVars compositeVars = CompositeVars(
156                 tint: tint * props.get!vec3(PROP_TINT_RGB),
157                 screenTint: screenTint * props.get!vec3(PROP_SCREEN_RGB),
158                 opacity: opacity * props.get!float(PROP_OPACITY)
159         );
160 
161         visuals_.sortNodes();
162 
163         // Push sub render area.
164         drawList.beginComposite();
165         foreach (ref visual; visuals_) {
166             visual.draw(delta, drawList);
167         }
168         drawList.endComposite();
169 
170         // Then blit it to the main framebuffer
171         drawList.setVariables!CompositeVars(nid, compositeVars);
172         drawList.setMesh(ssDrawList_);
173         drawList.setBlending(blendingMode);
174         drawList.blit();
175     }
176 
177     /**
178         Called when the node should be drawn to a mask.
179         
180         Params:
181             delta =     Time since the last frame.
182             drawList =  The drawlist for the active scene.
183             mode =      The masking mode to draw with.
184     */
185     override
186     void onDrawMask(float delta, DrawList drawList, MaskingMode mode) {
187         visuals_.sortNodes();
188         foreach (ref visual; visuals_) {
189             if (visual.isMasking)
190                 visual.drawMask(delta, drawList, mode);
191         }
192     }
193 
194     /**
195         Requests that the list gather sub-visuals to be rendered, if applicable.
196 
197         Params:
198             visuals =           The list to write to, the list may be resized by the
199                                 implementation.
200             recurseDelegates =  Whether to recurse through delegate visuals.
201             append =            Whether to append to the visuals list.
202     */
203     override
204     void onDelegateFindVisuals(ref weak_vector!Visual visuals, bool recurseDelegates, bool append) {
205         this.notifyVisualsChanged();
206     }
207 
208 public:
209 
210     /**
211         Whether the renderer should delegate rendering logic
212         to the visual node.
213     */
214     override @property bool isDelegated() @nogc => true;
215 
216     /**
217         Whether the node can be used as a source of masking operations.
218     */
219     override @property bool isMasking() @nogc nothrow pure => true;
220 
221     /**
222         The blending mode
223     */
224     BlendMode blendingMode;
225 
226     /**
227         The opacity of the composite
228     */
229     float opacity = 1;
230 
231     /**
232         Multiplicative tint color
233     */
234     vec3 tint = vec3(1, 1, 1);
235 
236     /**
237         Screen tint color
238     */
239     vec3 screenTint = vec3(0, 0, 0);
240 
241     /// Destructor
242     ~this() {
243     }
244 
245     /**
246         Constructs a new mask
247     */
248     this(Node parent = null) {
249         this(inNewGUID(), parent);
250     }
251 
252     /**
253         Constructs a new composite
254     */
255     this(GUID guid, Node parent = null) {
256         super(guid, parent);
257     }
258 
259     /**
260         Notifies the composite that the visuals have changed and
261         that it should re-index them.
262     */
263     void notifyVisualsChanged() {
264         // dfmt off
265         .findVisuals(this, visuals_, false, false, false);
266         // dfmt on
267     }
268 }
269 
270 mixin Register!(Composite, in_node_registry);
271 // dfmt off
272 
273 
274 
275 
276 //
277 //              IMPLEMENTATION DETAILS
278 //
279 private:
280 __gshared Mesh __screenSpaceMesh;
281 
282 // We are allocating extra data library-wide here.
283 pragma(crt_constructor)
284 extern (C) void __in_setup_composite() {
285     if (!__screenSpaceMesh) {
286         uint[6] indices = [
287             0, 1, 2,
288             2, 1, 3
289         ];
290         vec2[4] uvs = [
291             vec2(0, 0),
292             vec2(0, 1),
293             vec2(1, 0),
294             vec2(1, 1)
295         ];
296         vec2[4] vertices = [
297             vec2(-1, -1),
298             vec2(-1, 1),
299             vec2(1, -1),
300             vec2(1, 1)
301         ];
302         auto meshData = MeshData(vertices, uvs, indices);
303         __screenSpaceMesh = Mesh.fromMeshData(meshData);
304     }
305 }
306 
307 // And deallocating it again
308 pragma(crt_destructor)
309 extern (C) void __in_cleanup_composite() {
310     __screenSpaceMesh.release();
311 }