1 module inochi2d.godot.nodes.puppet; 2 import inochi2d.godot.resources.puppet; 3 import inochi2d.godot.render; 4 import inochi2d.core.render; 5 import inochi2d.core.mesh; 6 import inochi2d.puppet; 7 import godot.rendering_server; 8 import godot.rendering_device; 9 import godot.viewport; 10 import godot.node2d; 11 import godot.image; 12 import godot.world2d; 13 import godot; 14 import numem; 15 16 /** 17 An Inochi2D Puppet in the godot scene tree. 18 */ 19 @class_name("Inochi2DPuppet") 20 class Puppet2D : Node2D { 21 private: 22 @nogc: 23 Viewport viewport_; 24 Puppet2DState state_; 25 PuppetResource resource_; 26 Puppet puppet_; 27 28 protected: 29 30 /** 31 Updates and renders the puppet to its internal draw list. 32 */ 33 override void process_(double delta) { 34 state_.update(delta); 35 } 36 37 public: 38 39 /// Destructor 40 ~this() { 41 if (resource_) 42 gd_delete(resource_); 43 44 if (state_) 45 nogc_delete(state_); 46 } 47 48 /** 49 Name of the loaded puppet. 50 */ 51 @gd_export_custom(PROPERTY_HINT_ONESHOT, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY) 52 final @property String name() => String(puppet_ ? puppet_.properties.name : null); 53 54 /** 55 Author of the loaded puppet. 56 */ 57 @gd_export_custom(PROPERTY_HINT_ONESHOT, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY) 58 final @property String author() => String(puppet_ ? puppet_.properties.author : null); 59 60 /** 61 The puppet resource 62 */ 63 @gd_export final @property PuppetResource puppet() => resource_; 64 @gd_export final @property void puppet(PuppetResource value) { 65 if (state_) 66 nogc_delete(state_); 67 68 if (puppet_) 69 nogc_delete(puppet_); 70 71 this.resource_ = gde_refswap(resource_, value); 72 if (resource_) { 73 this.puppet_ = resource_.realize(); 74 this.state_ = nogc_new!Puppet2DState(this); 75 } 76 } 77 78 /** 79 The actual loaded puppet instance. 80 */ 81 @gd_hide final @property Puppet instance() => puppet_; 82 } 83 mixin GodotClass!Puppet2D; 84 85 /** 86 The internal rendering state of the puppet. 87 88 This wraps all the memory managment needed from godot. 89 */ 90 class Puppet2DState : NuObject { 91 private: 92 @nogc: 93 // 94 // VARIABLES 95 // 96 Puppet2D puppet; 97 RenderingDevice device; 98 99 100 101 102 // 103 // TEXTURE MANAGMENT 104 // 105 RDTexture[] textures; 106 107 void loadTextures() { 108 import godot.core.gdextension; 109 import core.stdc.stdio : printf; 110 printf("%p %p\n", device, device.ptr); 111 112 TextureCache cache = puppet.instance.textureCache; 113 this.textures = nu_malloca!RDTexture(cache.cache.length); 114 foreach(i, Texture texture; cache.cache) { 115 116 // Only load formats that Godot understands. 117 if (texture.format.toGodotFormat() != TextureDataFormat.DATA_FORMAT_MAX) { 118 119 // Create texture format info. 120 RDTextureFormat p_format = gd_new!RDTextureFormat(); 121 p_format.textureType = TextureType.TEXTURE_TYPE_2D; 122 p_format.format = texture.format.toGodotFormat(); 123 p_format.samples = TextureSamples.TEXTURE_SAMPLES_1; 124 p_format.usageBits = TextureUsageFlags.TEXTURE_USAGE_SAMPLING_BIT; 125 p_format.width = texture.width; 126 p_format.height = texture.height; 127 p_format.depth = 1; 128 p_format.arrayLayers = 1; 129 p_format.mipmaps = 1; 130 p_format.addShareableFormat(texture.format.toGodotFormat()); 131 132 // Create texture view with default settings. 133 RDTextureView p_view = gd_new!RDTextureView(); 134 gde_ref(p_view); 135 136 // Create texture. 137 this.textures[i] = nogc_new!RDTexture(device, p_format, p_view, cast(ubyte[])texture.data.data); 138 } 139 } 140 } 141 142 void freeTextures() { 143 if (textures.length > 0) { 144 nu_freea(textures); 145 } 146 } 147 148 149 150 151 // 152 // SHADERS 153 // 154 RDShader baseShader; 155 156 157 158 // 159 // MESH MANAGMENT 160 // 161 RDVertexBuffer vertexBuffer; 162 RDIndexBuffer indexBuffer; 163 164 void updateVertexData() { 165 auto vertices = puppet.instance.drawList.vertices; 166 auto indices = puppet.instance.drawList.indices; 167 168 // Create or resize vertex buffer if needed. 169 if (!vertexBuffer || vertexBuffer.size < vertices.length*VtxData.sizeof) { 170 if (vertexBuffer) 171 nogc_delete(vertexBuffer); 172 173 vertexBuffer = nogc_new!RDVertexBuffer(device, vertices.length*VtxData.sizeof, cast(BufferCreationFlags)0); 174 } 175 vertexBuffer.update(cast(void[])vertices, 0); 176 177 // Create or resize index buffer if needed. 178 if (!indexBuffer || indexBuffer.size < indices.length*uint.sizeof) { 179 if (indexBuffer) 180 nogc_delete(indexBuffer); 181 182 indexBuffer = nogc_new!RDIndexBuffer(device, IndexBufferFormat.INDEX_BUFFER_FORMAT_UINT32, cast(uint)indices.length, cast(BufferCreationFlags)0); 183 } 184 indexBuffer.update(cast(void[])indices, 0); 185 } 186 187 188 189 190 // 191 // RENDERING 192 // 193 void drawToGodot() { 194 195 // Update all vertex data mappings before rendering. 196 this.updateVertexData(); 197 198 // Go through the commands 199 foreach(ref DrawCmd command; puppet.instance.drawList.commands) { 200 201 } 202 } 203 204 205 public: 206 207 /** 208 The viewport being rendered to. 209 */ 210 final @property Viewport viewport() => puppet.getViewport(); 211 212 // Destructor 213 ~this() { 214 this.freeTextures(); 215 } 216 217 /** 218 Constructs a puppet rendering state object. 219 220 Params: 221 puppet = The puppet to wrap state for. 222 */ 223 this(Puppet2D puppet) { 224 this.puppet = puppet; 225 this.device = RenderingServer.instance.getRenderingDevice(); 226 this.loadTextures(); 227 } 228 229 /** 230 Updates and renders the puppet. 231 232 Params: 233 delta = Time since last frame. 234 */ 235 void update(float delta) { 236 puppet.instance.update(delta); 237 puppet.instance.draw(delta); 238 this.drawToGodot(); 239 } 240 } 241 242 /** 243 A managed godot framebuffer, basically a collection of a 244 Viewport and Canvas. 245 */ 246 struct GodotFramebuffer { 247 public: 248 @nogc: 249 RID viewport; 250 RID canvas; 251 RID canvasItem; 252 253 /** 254 Creates a new framebuffer. 255 */ 256 static GodotFramebuffer create() { 257 GodotFramebuffer fbo; 258 fbo.viewport = RenderingServer.instance.viewportCreate(); 259 fbo.canvas = RenderingServer.instance.canvasCreate(); 260 fbo.canvasItem = RenderingServer.instance.canvasItemCreate(); 261 RenderingServer.instance.viewportAttachCanvas(fbo.viewport, fbo.canvas); 262 RenderingServer.instance.viewportSetDisable3d(fbo.viewport, true); 263 return fbo; 264 } 265 266 /** 267 Frees this framebuffer. 268 */ 269 void free() { 270 RenderingServer.instance.freeRid(viewport); 271 RenderingServer.instance.freeRid(canvas); 272 273 viewport = RID.init; 274 canvas = RID.init; 275 } 276 } 277 278 TextureDataFormat toGodotFormat(TextureFormat format) @nogc nothrow pure { 279 final switch(format) with(TextureFormat) { 280 case none: 281 return TextureDataFormat.DATA_FORMAT_MAX; 282 283 case rgba8Unorm: 284 return TextureDataFormat.DATA_FORMAT_R8G8B8A8_UNORM; 285 286 case r8: 287 return TextureDataFormat.DATA_FORMAT_R8_UNORM; 288 289 case depthStencil: 290 return TextureDataFormat.DATA_FORMAT_D24_UNORM_S8_UINT; 291 } 292 }