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 }