1 /** 2 Inochi2D DrawList Interface 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.core.render.drawlist; 14 import inochi2d.core.render.state; 15 import inochi2d.core.render.texture; 16 import inochi2d.core.mesh; 17 import numath; 18 import nulib; 19 import numem; 20 21 /** 22 A draw list containing the rendering state and commands 23 to submit to the GPU. 24 */ 25 final 26 class DrawList : NuObject { 27 private: 28 @nogc: 29 30 // Working set 31 DrawListAlloc _call; 32 DrawCmd _ccmd; 33 34 // Draw Commands 35 vector!DrawCmd _cmds; 36 uint _cmdp; 37 38 // Vertex Data 39 vector!VtxData _vtxs; 40 uint _vtxp; 41 42 // Index Data 43 vector!uint _idxs; 44 uint _idxp; 45 46 // Buffer allocations 47 vector!DrawListAlloc _allocs; 48 uint _allp; 49 50 // Stacks 51 stack!(Texture[IN_MAX_ATTACHMENTS]) _targetsStack; 52 53 public: 54 55 // Destructor 56 ~this() { 57 _cmds.clear(); 58 _vtxs.clear(); 59 _idxs.clear(); 60 _allocs.clear(); 61 _targetsStack.clear(); 62 } 63 64 /** 65 Whether to use base vertex specification. 66 */ 67 bool useBaseVertex = true; 68 69 /** 70 Command Buffer 71 */ 72 @property DrawCmd[] commands() => _cmds[0 .. _cmdp]; 73 74 /** 75 Vertex data 76 */ 77 @property VtxData[] vertices() => _vtxs[0 .. _vtxp]; 78 79 /** 80 Index data 81 */ 82 @property uint[] indices() => _idxs[0 .. _idxp]; 83 84 /** 85 Allocated meshes 86 */ 87 @property DrawListAlloc[] allocations() => _allocs[0 .. _allp]; 88 89 /** 90 Allocates the given mesh in the draw list, allowing its 91 contents to be reused in draw commands. 92 93 Params: 94 vtx = Vertex data to push. 95 idx = Index data to push. 96 97 Returns: 98 A reference to the drawlist allocation on success, 99 $(D null) otherwise. 100 */ 101 DrawListAlloc* allocate(VtxData[] vtx, uint[] idx) { 102 103 // Invalid vertex buffer check. 104 if (vtx.length < 3) 105 return null; 106 107 // Invalid index buffer check. 108 if (idx.length < 3) 109 return null; 110 111 // Resize if stuff doesn't fit. 112 if (_vtxp + vtx.length >= _vtxs.length) 113 _vtxs.resize(_vtxp + vtx.length + 1); 114 if (_idxp + idx.length >= _idxs.length) 115 _idxs.resize(_idxp + idx.length + 1); 116 117 // Meshes supply their own index data, as such 118 // we offset it here to fit within our buffer. 119 if (!useBaseVertex) 120 idx[0 .. $] += _idxp; 121 122 _vtxs[_vtxp .. _vtxp + vtx.length] = vtx[0 .. $]; 123 _idxs[_idxp .. _idxp + idx.length] = idx[0 .. $]; 124 _vtxp += vtx.length; 125 _idxp += idx.length; 126 127 _call.allocId = _allp; 128 _call.idxCount = cast(uint)idx.length; 129 _call.vtxCount = cast(uint)vtx.length; 130 131 // Set up allocation. 132 if (_allp >= _allocs.length) 133 _allocs ~= _call; 134 else 135 _allocs[_allp] = _call; 136 137 // prepare next alloc 138 _call = DrawListAlloc.init; 139 _call.idxOffset = _idxp; 140 _call.vtxOffset = _vtxp; 141 return &_allocs[_allp++]; 142 } 143 144 /** 145 Pushes render targets to the draw list's stack. 146 */ 147 void beginComposite() { 148 _ccmd.state = DrawState.compositeBegin; 149 this.next(); 150 } 151 152 /** 153 Pops the top render target from the list's stack. 154 */ 155 void endComposite() { 156 _ccmd.state = DrawState.compositeEnd; 157 this.next(); 158 } 159 160 /** 161 Enqueues a composite blit for a recently ended composite. 162 */ 163 void blit() { 164 _ccmd.state = DrawState.compositeBlit; 165 this.next(); 166 } 167 168 /** 169 Pushes render targets to the draw list's stack. 170 */ 171 void pushMask(MaskingMode mode) { 172 _ccmd.state = DrawState.pushMask; 173 _ccmd.maskMode = mode; 174 this.next(); 175 } 176 177 /** 178 Pops the top render target from the list's stack. 179 */ 180 void popMask() { 181 _ccmd.state = DrawState.popMask; 182 this.next(); 183 } 184 185 /** 186 Sets sources for the current draw call. 187 */ 188 void setSources(Texture[IN_MAX_ATTACHMENTS] sources) { 189 _ccmd.sources = sources; 190 } 191 192 /** 193 Sets the blending mode for the current draw call. 194 */ 195 void setBlending(BlendMode value) { 196 _ccmd.blendMode = value; 197 } 198 199 /** 200 Sets the blending mode for the current draw call. 201 */ 202 void setVariables(T)(uint nid, T value) 203 if (T.sizeof <= _ccmd.variables.sizeof) { 204 _ccmd.typeId = nid; 205 nu_memcpy(_ccmd.variables.ptr, cast(void*)&value, T.sizeof); 206 } 207 208 /** 209 Sets the masking mode for the current draw call. 210 */ 211 void setMasking(MaskingMode value) { 212 _ccmd.state = DrawState.defineMask; 213 _ccmd.maskMode = value; 214 } 215 216 /** 217 Sets the mesh data for the current draw command. 218 219 Params: 220 alloc = The vertex allocation cookie. 221 */ 222 void setMesh(DrawListAlloc* alloc) { 223 if (!alloc) 224 return; 225 226 _ccmd.allocId = alloc.allocId; 227 _ccmd.idxOffset = alloc.idxOffset; 228 _ccmd.vtxOffset = alloc.vtxOffset; 229 _ccmd.elemCount = alloc.idxCount; 230 } 231 232 /** 233 Pushes the next draw command 234 */ 235 void next() { 236 if (_cmdp >= _cmds.length) 237 _cmds ~= _ccmd; 238 else 239 _cmds[_cmdp] = _ccmd; 240 241 _cmdp++; 242 _ccmd = DrawCmd.init; 243 } 244 245 /** 246 Clears the draw list, making it ready for a new pass. 247 */ 248 void clear() { 249 _vtxp = 0; 250 _idxp = 0; 251 _cmdp = 0; 252 _allp = 0; 253 _ccmd = DrawCmd.init; 254 _call = DrawListAlloc.init; 255 _targetsStack.clear(); 256 } 257 } 258 259 /** 260 Maximum number of texture attachments. 261 */ 262 enum IN_MAX_ATTACHMENTS = 8; 263 264 /** 265 An allocation within the drawlist 266 */ 267 struct DrawListAlloc { 268 269 /** 270 Vertex offset. 271 */ 272 uint vtxOffset; 273 274 /** 275 Index offset. 276 */ 277 uint idxOffset; 278 279 /** 280 Number of indices. 281 */ 282 uint idxCount; 283 284 /** 285 Number of vertices. 286 */ 287 uint vtxCount; 288 289 /** 290 Allocation ID. 291 */ 292 uint allocId; 293 } 294 295 /** 296 Draw state flags. 297 */ 298 enum DrawState : uint { 299 300 /** 301 Normal drawing. 302 */ 303 normal = 0, 304 305 /** 306 Draws a mask source to the top level mask buffer. 307 */ 308 defineMask = 1, 309 310 /** 311 Finalizes the current mask, then pushes it onto the stack. 312 */ 313 pushMask = 2, 314 315 /** 316 Pops the current mask from the stack, restoring the higher level 317 mask, or exits mask mode. 318 */ 319 popMask = 3, 320 321 /** 322 A composition into composition textures 323 has begun. 324 */ 325 compositeBegin = 4, 326 327 /** 328 A composition into composition textures 329 has ended. 330 */ 331 compositeEnd = 5, 332 333 /** 334 Sources should be drawn to targets using 335 the given blending mode. 336 */ 337 compositeBlit = 6, 338 } 339 340 /** 341 A drawing command that is sent to the GPU. 342 */ 343 struct DrawCmd { 344 @nogc: 345 346 /** 347 Source textures 348 */ 349 Texture[IN_MAX_ATTACHMENTS] sources; 350 351 /** 352 The current state of the drawing command. 353 */ 354 DrawState state; 355 356 /** 357 Blending mode to apply 358 */ 359 BlendMode blendMode; 360 361 /** 362 Masking mode to apply. 363 */ 364 MaskingMode maskMode; 365 366 /** 367 Allocation ID. 368 */ 369 uint allocId; 370 371 /** 372 Vertex offset. 373 */ 374 uint vtxOffset; 375 376 /** 377 Index offset. 378 */ 379 uint idxOffset; 380 381 /** 382 Number of indices. 383 */ 384 uint elemCount; 385 386 /** 387 Type ID of the node being drawn. 388 */ 389 uint typeId; 390 391 /** 392 Variables passed to the draw list. 393 */ 394 void[64] variables; 395 396 /** 397 Whether the command is empty. 398 */ 399 @property bool isEmpty() => elemCount == 0; 400 }