From 8075edf6190552da6c624c5154f2750c28dfb32e Mon Sep 17 00:00:00 2001 From: Alberto Torres Date: Fri, 13 Sep 2024 02:37:02 +0200 Subject: [PATCH] Prevent mem leaks with destructors for all GPU objects and other measures. --- src/graphics/framebuffer.nim | 6 ++--- src/graphics/material.nim | 24 ++++++++++--------- src/graphics/render.nim | 42 ++++++++++++++++---------------- src/graphics/texture.nim | 24 +++++++++---------- src/graphics/ubo.nim | 27 +++++++++++---------- src/loaders/blend.nim | 1 + src/loaders/blend_format.nim | 44 +++++++++++++++++----------------- src/myou_engine.nim | 1 - src/objects/gameobject.nim | 5 ++++ src/objects/light.nim | 2 -- src/objects/mesh.nim | 46 +++++++++++++++--------------------- src/platform/gl.nim | 45 +++++++++++++++++++++++++++++++++++ src/scene.nim | 22 ++++++++++++----- src/types.nim | 29 ++++++++++++----------- 14 files changed, 186 insertions(+), 132 deletions(-) diff --git a/src/graphics/framebuffer.nim b/src/graphics/framebuffer.nim index b3caebc..5f337df 100644 --- a/src/graphics/framebuffer.nim +++ b/src/graphics/framebuffer.nim @@ -164,15 +164,15 @@ proc set_attachments(self: Framebuffer; layer, mipmap_level: int) = if tex.tex_type == Tex2DArray: assert layer >= 0, "Texture array layer must be specified" glFramebufferTextureLayer(GL_FRAMEBUFFER, attachments[i], - tex.storage.tex, mipmap_level.GLint, layer.GLint) + tex.storage.tex.GLuint, mipmap_level.GLint, layer.GLint) elif tex.tex_type == TexCube: assert layer >= 0, "Texture cube side must be specified" glFramebufferTexture2D(GL_FRAMEBUFFER, attachments[i], (GL_TEXTURE_CUBE_MAP_POSITIVE_X.int + layer).GLenum, - tex.storage.tex, mipmap_level.GLint) + tex.storage.tex.GLuint, mipmap_level.GLint) elif self.current_mipmap_level != mipmap_level: glFramebufferTexture2D(GL_FRAMEBUFFER, attachments[i], - tex.storage.target, tex.storage.tex, mipmap_level.GLint) + tex.storage.target, tex.storage.tex.GLuint, mipmap_level.GLint) proc enable*(self: Framebuffer, rect = none((int32,int32,int32,int32)), layer = -1, mipmap_level = 0, mark_textures = true): Framebuffer {.discardable.} = diff --git a/src/graphics/material.nim b/src/graphics/material.nim index af05cdc..6a21bba 100644 --- a/src/graphics/material.nim +++ b/src/graphics/material.nim @@ -186,8 +186,8 @@ proc delete_all_shaders*(self: Material, destroy: bool = true) = self.last_shader = nil proc destroy*(self: Material) = - for shader in values(self.shaders): - shader.destroy() + self.delete_all_shaders() + self.textures.clear() var id = 0 @@ -389,12 +389,14 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material, when not defined(release): self.vs_code = vs let vertex_shader = glCreateShader(GL_VERTEX_SHADER) + defer: glDeleteShader(vertex_shader) # TODO: make a pointer array from the unjoined strings, interleaved with "\n" # instead of concatenating and converting var vs2 = vs.cstring glShaderSource(vertex_shader, 1, cast[cstringArray](addr vs2), nil) glCompileShader(vertex_shader) var fragment_shader: GLuint = 0 + defer: glDeleteShader(fragment_shader) template fragment:string = fragment_lines.join("\n") if has_fragment_shader: fragment_shader = glCreateShader(GL_FRAGMENT_SHADER) @@ -404,6 +406,10 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material, glShaderSource(fragment_shader, 1, cast[cstringArray](addr fragment2), nil) glCompileShader(fragment_shader) let prog = glCreateProgram() + defer: + if prog != self.program.GLuint: + echo "deleting program" + glDeleteProgram(prog) glAttachShader(prog, vertex_shader) if fragment_shader != 0: glAttachShader(prog, fragment_shader) @@ -425,7 +431,6 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material, echo vs.add_line_numbers(1) let error_msg = dedent &"""Error compiling vertex shader of material {material.name} {get_shader_info_log(vertex_shader)}""" - glDeleteShader(vertex_shader) console_error(error_msg) return if fragment_shader != 0: @@ -435,7 +440,6 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material, let error_msg = &"Error compiling fragment shader of material {material.name}\n{gl_log}" # console_error fragment let lines = fragment.split("\n") - glDeleteShader(fragment_shader) if "ERROR: 0:" in gl_log: # Show engine for first error let line = try: error_msg.split(":")[2].parseInt except: 0 @@ -463,9 +467,6 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material, # console_error fragment console_error("================") console_error error_msg - glDeleteProgram(prog) - glDeleteShader(vertex_shader) - glDeleteShader(fragment_shader) return self.camera_render_ubo_index = glGetUniformBlockIndex(prog, "CameraRenderUniform") @@ -517,12 +518,13 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material, assert self.texture_locations.len + self.cubemap_locations.len + extra_location_count <= self.engine.renderer.max_textures - self.program = prog + self.program = prog.GPUProgram proc use*(self: Shader): GLuint {.inline,discardable.} = - glUseProgram(self.program) - return self.program + glUseProgram(self.program.GLuint) + return self.program.GLuint proc destroy*(self: Shader) = - glDeleteProgram(self.program) + self.program = 0.GPUProgram + diff --git a/src/graphics/render.nim b/src/graphics/render.nim index 60fa5d2..b3ca5d0 100644 --- a/src/graphics/render.nim +++ b/src/graphics/render.nim @@ -255,23 +255,22 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren # TODO: Also check with cam_data.clipping_plane! # TODO: Select alternative mesh / LoD - var amesh = mesh + var amesh {.cursor.} = mesh if not (amesh.data != nil and amesh.data.loaded): return self.set_flip_normals mesh.flip - let data = amesh.data - - # for ubo in self.bound_ubos: - # if ubo != nil: - # ubo.unbind() - # self.next_ubo = 0 + let data {.cursor.} = amesh.data # unbindAllTextures() # Main routine for each submesh - # (vao may be null but that's handled later) - for submesh_idx, vao in data.vaos: - if vao == 0: + + # vaos have a destructor, and `pairs` copied the value, + # so we either use `mpairs` or keep track of submesh_idx separately. + var submesh_idx = -1 + for vao in data.vaos: + submesh_idx.inc + if vao.GLuint == 0.GLuint: continue if not (pass == -1 or mesh.passes[submesh_idx] == pass): @@ -282,10 +281,10 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren else: material_override - let shader = mat.get_shader(mesh) - if shader.program == 0: + let shader {.cursor.} = mat.get_shader(mesh) + let program = shader.use() + if program == 0: continue - shader.use() self.set_cull_face(not mat.double_sided) var ubos_to_bind: seq[UBO] @@ -313,7 +312,7 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren # TODO: move this UBO, consolidate with cameradata # TODO: update only in draw_viewport if shader.camera_render_ubo_index.is_valid: - let ubo = self.camera_render_ubo + let ubo {.cursor.} = self.camera_render_ubo ubos_to_bind.add ubo ubo_indices.add shader.camera_render_ubo_index ubo.storage(CameraRenderUniform)[0] = CameraRenderUniform( @@ -327,7 +326,7 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren ubos_to_update.add ubo if shader.object_ubo_index.is_valid: - let ubo = mesh.object_ubo + let ubo {.cursor.} = mesh.object_ubo ubos_to_bind.add ubo ubo_indices.add shader.object_ubo_index const diag = vec3(1).normalize @@ -344,13 +343,13 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren # UBOs for i,idx in shader.ubo_indices: - let ubo = shader.ubos[i] + let ubo {.cursor.} = shader.ubos[i] ubos_to_bind.add ubo ubo_indices.add idx ubos_to_bind.bind_all() for i,ubo in ubos_to_bind: - ubo.set_prog_index(shader.program, ubo_indices[i]) + ubo.set_prog_index(program, ubo_indices[i]) for ubo in ubos_to_update: ubo.update() @@ -364,8 +363,9 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren textures_to_bind.add texture else: textures_to_bind.add self.blank_texture + assert texture_locations.len == textures_to_bind.len - let scene = mesh.scene + let scene {.cursor.} = mesh.scene if scene != nil: let loc = shader.shadowmap_location let fb = scene.shadow_maps @@ -389,9 +389,9 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren bind_all(textures_to_bind, texture_locations) - let index_buffer = data.index_buffers[submesh_idx] + let index_buffer = data.index_buffers[submesh_idx].GLuint let num_indices = data.num_indices[submesh_idx] - glBindVertexArray(vao) + glBindVertexArray(vao.GLuint) if not data.use_tf: if index_buffer != 0: glDrawElements(data.draw_method.GLenum, num_indices.GLsizei, data.index_types[submesh_idx].GLenum, cast[pointer](0)) @@ -399,7 +399,7 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren glDrawArrays(data.draw_method.GLenum, 0, num_indices) else: assert index_buffer == 0, "Loopback transform feedback is not compatible with indexed meshes" - glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, data.tf_vbos[1][submesh_idx]) + glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, data.tf_vbos[1][submesh_idx].GLuint) glBeginTransformFeedback(data.draw_method.GLenum) glDrawArrays(data.draw_method.GLenum, 0, num_indices) glEndTransformFeedback() diff --git a/src/graphics/texture.nim b/src/graphics/texture.nim index 11a470e..fffbf06 100644 --- a/src/graphics/texture.nim +++ b/src/graphics/texture.nim @@ -194,11 +194,13 @@ proc newTextureStorage*(ttype: TextureType, width, height, depth: int, format: T # of Depth_u24_s8: GL_UNSIGNED_INT ts.layer = 0 ts.tile_size = vec2(1,1) - glGenTextures(1, addr ts.tex) + var tex: GLuint + glGenTextures(1, addr tex) + ts.tex = tex.GPUTexture return ts proc preallocate(self: Texture) = - let ts = self.storage + let ts {.cursor.} = self.storage case self.tex_type: of Tex2D: # TODO: only do this if necessary @@ -232,9 +234,7 @@ proc preallocate(self: Texture) = discard proc freeTextureStorage(self: Texture) = - if self.storage != nil: - glDeleteTextures(1, addr self.storage.tex) - self.storage = nil + self.storage = nil proc bind_it*(texture: Texture, reserve_slot: static[int32] = -1, needs_active_texture: static[bool] = false) = # TODO: rewrite for using texture arrays and/or bindless @@ -252,7 +252,7 @@ proc bind_it*(texture: Texture, reserve_slot: static[int32] = -1, needs_active_t if active_texture != bound_unit: active_texture = bound_unit glActiveTexture(cast[GLenum](GL_TEXTURE0.uint32 + bound_unit.uint32)) - var old_tex = bound_textures[bound_unit] + var old_tex {.cursor.} = bound_textures[bound_unit] if old_tex != nil: old_tex.storage.unit = -1 if old_tex.storage.target.uint32 != texture.storage.target.uint32: @@ -260,7 +260,7 @@ proc bind_it*(texture: Texture, reserve_slot: static[int32] = -1, needs_active_t # if old_tex.sampler_object != 0: # glBindSampler(cast[GLuint](bound_unit), 0) bound_textures[bound_unit] = nil - glBindTexture(texture.storage.target, texture.storage.tex) + glBindTexture(texture.storage.target, texture.storage.tex.GLuint) # if texture.sampler_object != 0: # glBindSampler(bound_unit.GLuint, texture.sampler_object) bound_textures[bound_unit] = texture @@ -277,7 +277,7 @@ proc unbind*(texture: Texture) = if texture.storage.unit == -1: return let bound_unit = texture.storage.unit - var old_tex = bound_textures[bound_unit] + var old_tex {.cursor.} = bound_textures[bound_unit] assert old_tex == texture, "unexpected bound texture" if old_tex == texture: bound_textures[bound_unit] = nil @@ -308,19 +308,17 @@ proc bind_all*(textures: seq[Texture], locations: seq[GLint]) = active_texture = unit texture.storage.unit = unit glActiveTexture(cast[GLenum](GL_TEXTURE0.uint32 + unit.uint32)) - let old_tex = bound_textures[unit] + let old_tex {.cursor.} = bound_textures[unit] if old_tex != nil: old_tex.storage.unit = -1 if old_tex.storage.target != texture.storage.target: glBindTexture(old_tex.storage.target, 0) - glBindTexture(texture.storage.target, texture.storage.tex) + glBindTexture(texture.storage.target, texture.storage.tex.GLuint) bound_textures[unit] = texture inc(next_texture) if next_texture == max_textures: next_texture = 0 glUniform1i(locations[i], unit) - - proc unbindAllTextures*() = for tex in bound_textures: @@ -428,7 +426,7 @@ proc newTexture*(engine: MyouEngine, name: string, when defined(myouUseRenderdoc): # Note: when we switch to arrays we'll have to store resolutions # instead of names - glObjectLabel(GL_TEXTURE, self.storage.tex, + glObjectLabel(GL_TEXTURE, self.storage.tex.GLuint, GLsizei(self.name.len), self.name.cstring) proc generateMipmap*(self: Texture) = diff --git a/src/graphics/ubo.nim b/src/graphics/ubo.nim index 818015f..0c58643 100644 --- a/src/graphics/ubo.nim +++ b/src/graphics/ubo.nim @@ -61,12 +61,14 @@ proc initUBO*[T](self: UBO, renderer: RenderManager, name: string, utype: typede self.binding_point = -1 renderer.enqueue proc() = - glGenBuffers 1, addr self.buffer - glBindBuffer(GL_UNIFORM_BUFFER, self.buffer) + var buffer: GLuint + glGenBuffers 1, addr buffer + self.buffer = GPUBuffer(buffer) + glBindBuffer(GL_UNIFORM_BUFFER, buffer) glBufferData(GL_UNIFORM_BUFFER, cast[GLsizeiptr](self.byte_storage.byte_len), nil, GL_DYNAMIC_DRAW) # glBindBuffer(GL_UNIFORM_BUFFER, 0) when defined(myouUseRenderdoc): - glObjectLabel(GL_BUFFER, self.buffer, GLsizei(name.len), name.cstring) + glObjectLabel(GL_BUFFER, buffer, GLsizei(name.len), name.cstring) return self template storage*[T](self: UBO, utype: typedesc[T]): ArrRef[T] = self.byte_storage.to(T) @@ -83,16 +85,17 @@ proc resize*[T](self: UBO, utype: typedesc[T], count: int) = # but I'm deleting and unbinding the buffer just in case self.unbind() self.byte_storage = newArrRef[T](count).to(byte) - glDeleteBuffers 1, addr self.buffer - glGenBuffers 1, addr self.buffer - glBindBuffer(GL_UNIFORM_BUFFER, self.buffer) + var buffer: GLuint + glGenBuffers 1, addr buffer + self.buffer = GPUBuffer(buffer) + glBindBuffer(GL_UNIFORM_BUFFER, buffer) # TODO: set nil instead of the actual buffer, # and avoid using it until it's updated for the first time glBufferData(GL_UNIFORM_BUFFER, cast[GLsizeiptr](self.byte_storage.len), self.byte_storage.toPointer, GL_DYNAMIC_DRAW) # glBindBuffer(GL_UNIFORM_BUFFER, 0) when defined(myouUseRenderdoc): let name = self.name & "*" # asterisk = it was resized - glObjectLabel(GL_BUFFER, self.buffer, GLsizei(name.len), name.cstring) + glObjectLabel(GL_BUFFER, buffer, GLsizei(name.len), name.cstring) proc newUBO*[T](renderer: RenderManager, name: string, utype: typedesc[T], count: int = 1): UBO = var ubo = new UBO @@ -126,9 +129,9 @@ proc bind_it*(self: UBO, rebind = false) = if next_ubo == self.renderer.max_uniform_buffer_bindings: next_ubo = 0 self.renderer.next_ubo = next_ubo - glBindBufferBase(GL_UNIFORM_BUFFER, self.binding_point.GLuint, self.buffer) + glBindBufferBase(GL_UNIFORM_BUFFER, self.binding_point.GLuint, self.buffer.GLuint) elif rebind: - glBindBufferBase(GL_UNIFORM_BUFFER, self.binding_point.GLuint, self.buffer) + glBindBufferBase(GL_UNIFORM_BUFFER, self.binding_point.GLuint, self.buffer.GLuint) proc unbind*(self: UBO) = if self.binding_point != -1: @@ -158,7 +161,7 @@ proc bind_all*(ubos: seq[UBO]) = old_ubo.binding_point = -1 renderer.bound_ubos[next_ubo] = ubo ubo.binding_point = next_ubo - glBindBufferBase(GL_UNIFORM_BUFFER, next_ubo.GLuint, ubo.buffer) + glBindBufferBase(GL_UNIFORM_BUFFER, next_ubo.GLuint, ubo.buffer.GLuint) inc(next_ubo) if next_ubo == maxubos: next_ubo = 0 @@ -179,7 +182,7 @@ proc update*(self: UBO) {.inline.} = # NOTE: if this doesn't work in webgl, set a flag let len = self.byte_storage.len if len != 0: - glBindBuffer(GL_UNIFORM_BUFFER, self.buffer) + glBindBuffer(GL_UNIFORM_BUFFER, self.buffer.GLuint) glBufferData(GL_UNIFORM_BUFFER, len.GLsizeiptr, self.byte_storage.toPointer, GL_DYNAMIC_DRAW) # glBindBuffer(GL_UNIFORM_BUFFER, 0) @@ -189,7 +192,7 @@ template is_valid*(block_index: GLuint): bool = proc destroy*(self: UBO) = self.unbind() self.byte_storage = nil - glDeleteBuffers 1, addr self.buffer + self.buffer = 0.GPUBuffer # TODO: make a function/template/macro that generates GLSL code to declare the UBO from an object diff --git a/src/loaders/blend.nim b/src/loaders/blend.nim index 78b0ea3..80f6be3 100644 --- a/src/loaders/blend.nim +++ b/src/loaders/blend.nim @@ -108,6 +108,7 @@ proc loadAsync(self: BlendLoader, callback: proc()) = self.close() self.resource = loadUri(self.blend_file_path, proc(ok, err, data: auto) = + self.resource = nil if ok: self.blend_file = openBlendFile(self.blend_file_path, data.data, data.byte_len) callback() diff --git a/src/loaders/blend_format.nim b/src/loaders/blend_format.nim index d7dfe20..9e9576e 100644 --- a/src/loaders/blend_format.nim +++ b/src/loaders/blend_format.nim @@ -87,11 +87,10 @@ type TypeLengths = ptr UncheckedArray[uint16] FNode* = object - dna: TypeAttrsRef - blocks: FNodesByAddress + blend_file {.cursor.}: BlendFile # we have to keep a reference to this otherwise it will be GC'd - # TODO: remove the two pointers above to use less memory? - blend_file: BlendFile + # (but only in FNodes not referenced in the original, to avoid cycles) + blend_file_ref: BlendFile p: pointer count: uint32 @@ -285,8 +284,6 @@ proc openBlendFile*(path: string, data: pointer, len: int): BlendFile = p: blk.data.addr, tid: tid, count: if blk.count == 1: real_count else: blk.count, - dna: type_attrs, - blocks: node_blocks, blend_file: result) node_blocks[address] = node if blk.code[2] == '\0': @@ -294,6 +291,7 @@ proc openBlendFile*(path: string, data: pointer, len: int): BlendFile = if t notin result.named_blocks: result.named_blocks[t] = @[] result.named_blocks[t].add(node) + result.dna = type_attrs result.blocks = node_blocks result.type_names = type_names result.struct_to_type = struct_to_type @@ -375,25 +373,25 @@ template get_fblock_of_node(n: FNode): ptr FBlock64 = proc contains*(n: FNode, name: string): bool = if n.p == nil: return - return name in n.dna[n.tid] + return name in n.blend_file.dna[n.tid] proc `[]`*(n: FNode, name: string): FNode = if n.p == nil: return assert not n.count.testBit(31), "This is an array, expected index instead of \"" & name & "\"" - let a = n.dna[n.tid][name] + let a = n.blend_file.dna[n.tid][name] if a.atype != Value: let ptri = cast[ptr uint64](n.p+a.offset)[] if ptri == 0: return when defined(disallow_missing_blocks): - assert ptri in n.blocks, "Missing block pointing to: " & name - if ptri notin n.blocks: + assert ptri in n.blend_file.blocks, "Missing block pointing to: " & name + if ptri notin n.blend_file.blocks: # This may happen if e.g. the user has deleted an image used by a node # so we'll just return an invalid node echo "Missing block pointing to: " & name return - let blk = n.blocks[ptri] + let blk = n.blend_file.blocks[ptri] var count: uint32 = a.count if a.atype == PointerArray: # the sdna_index of this block is 0, which would give an incorrect struct size @@ -403,13 +401,15 @@ proc `[]`*(n: FNode, name: string): FNode = count.setBit 31 # use blk.count? var node = FNode(p: blk.p, tid: a.tid, count: count, - dna: n.dna, blocks: n.blocks, blend_file: n.blend_file) + blend_file: n.blend_file, + blend_file_ref: n.blend_file) if a.tid == B_void.uint16: node.tid = n.blend_file.struct_to_type[get_fblock_of_node(node).sdna_index] return node else: return FNode(p: n.p+a.offset, tid: a.tid, count: a.count, - dna: n.dna, blocks: n.blocks, blend_file: n.blend_file) + blend_file: n.blend_file, + blend_file_ref: n.blend_file) proc `[]`*(n: FNode, index: Natural): FNode = var count = n.count @@ -419,16 +419,17 @@ proc `[]`*(n: FNode, index: Natural): FNode = let ptri = cast[ptr UncheckedArray[uint64]](n.p)[index] if ptri == 0: return - assert ptri in n.blocks, "Missing block" - let blk = n.blocks[ptri] + assert ptri in n.blend_file.blocks, "Missing block" + let blk = n.blend_file.blocks[ptri] return FNode(p: blk.p, tid: n.tid, count: 1, - dna: n.dna, blocks: n.blocks, blend_file: n.blend_file) + blend_file: n.blend_file, + blend_file_ref: n.blend_file) else: var n = n let size = if n.tid < BASIC_TYPE_LENGTHS.len: BASIC_TYPE_LENGTHS[n.tid] else: - # n.dna[n.tid]["(size)"].offset.int + # n.blend_file.dna[n.tid]["(size)"].offset.int n.blend_file.type_lengths[n.tid].int n.p = n.p + index * size n.count = 1 @@ -444,10 +445,9 @@ iterator items*(n: FNode, stride=0): FNode = if ptri == 0: yield FNode() continue - assert ptri in n.blocks, "Missing block" - let blk = n.blocks[ptri] - yield FNode(p: blk.p, tid: n.tid, count: 1, - dna: n.dna, blocks: n.blocks) + assert ptri in n.blend_file.blocks, "Missing block" + let blk = n.blend_file.blocks[ptri] + yield FNode(p: blk.p, tid: n.tid, count: 1) else: var n = n let stride = if stride == 0: @@ -538,7 +538,7 @@ proc `$`*(n: FNode, depth=0, max_depth=3, just_name=false, dump_pointers=false): let kkstr = `$`(kk, depth, max_depth) ret.add indent & "[" & $i & "]: " & kkstr return ret.join "\n" - var attrs = toSeq(n.dna[n.tid].pairs) + var attrs = toSeq(n.blend_file.dna[n.tid].pairs) try: let name = n.id.name.str ret.add indent & "id.name: " & name diff --git a/src/myou_engine.nim b/src/myou_engine.nim index 875c336..fed32b3 100644 --- a/src/myou_engine.nim +++ b/src/myou_engine.nim @@ -168,7 +168,6 @@ proc myou_main_loop*(self: MyouEngine) = ## ## You usually don't need to call this. Use `run <#run,MyouEngine>`_ ## instead. - self.renderer.unbind_all_ubos() when compileOption("threads"): updateTextureWorkerThreads() updateLoadableWorkerThreads() diff --git a/src/objects/gameobject.nim b/src/objects/gameobject.nim index 9d4134e..00b0952 100644 --- a/src/objects/gameobject.nim +++ b/src/objects/gameobject.nim @@ -486,6 +486,9 @@ proc remove*(self: GameObject, recursive: bool = true) = self.scene.remove_object(self, recursive) proc destroy*(self: GameObject, recursive: bool = true) = + if self.is_mesh and self.get_mesh.data.nonNil: + self.get_mesh.data.gpu_buffers_delete() + self.get_mesh.materials.setLen 0 self.body.destroy() self.remove(recursive) if recursive: @@ -494,7 +497,9 @@ proc destroy*(self: GameObject, recursive: bool = true) = child.destroy(true) self.engine.objects.del(self.name) self.object_render_ubo.unbind() + self.object_render_ubo.destroy() self.object_ubo.unbind() + self.object_ubo.destroy() proc convert_bone_child_to_bone_parent*(self: GameObject) = assert self.parent == nil or self.parent.is_armature, "An armature parent is required" diff --git a/src/objects/light.nim b/src/objects/light.nim index 6d89c32..e69e202 100644 --- a/src/objects/light.nim +++ b/src/objects/light.nim @@ -136,8 +136,6 @@ proc configure_shadow*(self: Light, for ob in self.scene.children: if not (ob.is_mesh and ob.visible): continue - echo "adding ", ob.name, " otype ", ob.otype - # discard ob.get_world_matrix let me = ob.get_mesh let bb = me.bound_box let world_dim = (ob.world_matrix * vec4(bb[1] - bb[0], 0.0)).xyz diff --git a/src/objects/mesh.nim b/src/objects/mesh.nim index 6d0ab7d..1cc3049 100644 --- a/src/objects/mesh.nim +++ b/src/objects/mesh.nim @@ -91,23 +91,14 @@ proc remove*(self: MeshData, ob: Mesh, delete_buffers: bool = true) = self.users.delete(idx) idx = self.users.find(ob) if self.users.len == 0: - # if delete_buffers: - # glBindBuffer(GL_ARRAY_BUFFER, 0) - # for buf in self.vertex_buffers: - # glDeleteBuffers(1, addr buf) - # for buf in self.index_buffers: - # glDeleteBuffers(1, addr buf) - # glBindVertexArray(0) - # for vao in self.vaos: - # glDeleteVertexArrays(1, addr vao) self.engine.mesh_datas.del(self.hash) ob.data = nil proc write_vaos*(self: MeshData) = for i,vao in self.vaos: - if vao == 0: + if vao.int == 0: continue - glBindVertexArray(vao) + glBindVertexArray(vao.GLuint) for vb_attrs in self.vao_specs[i].vbs.mitems: glBindBuffer(GL_ARRAY_BUFFER, vb_attrs.vb) for a in vb_attrs.attribs: @@ -117,7 +108,7 @@ proc write_vaos*(self: MeshData) = glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.vao_specs[i].ib) when defined(myouUseRenderdoc): let name = &"{self.name} {i}" - glObjectLabel(GL_VERTEX_ARRAY, vao, GLsizei(name.len), name.cstring) + glObjectLabel(GL_VERTEX_ARRAY, vao.GLuint, GLsizei(name.len), name.cstring) glBindVertexArray(0) proc gpu_buffers_upload*(self: MeshData) = @@ -130,9 +121,9 @@ proc gpu_buffers_upload*(self: MeshData) = for idx,va in self.varrays: if va.len == 0: self.num_indices.add(0) - self.vertex_buffers.add(0) - self.index_buffers.add(0) - self.vaos.add(0) + self.vertex_buffers.add(0.GPUBuffer) + self.index_buffers.add(0.GPUBuffer) + self.vaos.add(0.GPUVao) self.vao_specs.add VaoSpec() continue var vb: GLuint @@ -143,7 +134,7 @@ proc gpu_buffers_upload*(self: MeshData) = # TODO: initialize without uploading when we know that the array is just zeroes glBufferData(GL_ARRAY_BUFFER, buffer_size.GLsizeiptr, addr va[0], GL_DYNAMIC_DRAW) glBindBuffer(GL_ARRAY_BUFFER, 0) - self.vertex_buffers.add(vb) + self.vertex_buffers.add(vb.GPUBuffer) var ib: GLuint = 0 var num_indices: Natural if idx < self.iarrays.len: @@ -167,7 +158,7 @@ proc gpu_buffers_upload*(self: MeshData) = self.num_indices.add(num_indices.int32) elif not had_indices: self.num_indices.add((buffer_size div self.stride).int32) - self.index_buffers.add(ib) + self.index_buffers.add(ib.GPUBuffer) if self.use_tf: self.tf_vbos.setLen 2 glGenBuffers(2, addr tf[0]) @@ -180,8 +171,8 @@ proc gpu_buffers_upload*(self: MeshData) = glBindBuffer(GL_ARRAY_BUFFER, tf[1]) glBufferData(GL_ARRAY_BUFFER, tf_buffer_size.GLsizeiptr, nil, GL_DYNAMIC_DRAW) glBindBuffer(GL_ARRAY_BUFFER, 0) - self.tf_vbos[idx].add tf[0] - self.tf_vbos[idx].add tf[1] + self.tf_vbos[idx].add tf[0].GPUBuffer + self.tf_vbos[idx].add tf[1].GPUBuffer # If transform feedback is enabled, we create two VAOs that have two # parts: # * The regular mesh, which has the same buffers and attributes in each @@ -203,7 +194,7 @@ proc gpu_buffers_upload*(self: MeshData) = ) var vao: GLuint glGenVertexArrays(1, addr vao) - self.vaos.add(vao) + self.vaos.add(vao.GPUVao) if self.use_tf: var vao_tf = vao_spec vao_spec.vbs.add VertexBufferAttribs(vb: tf[0], attribs: self.tf_layout) @@ -218,14 +209,15 @@ proc gpu_buffers_upload*(self: MeshData) = # set loaded to true after that proc gpu_buffers_delete*(self: MeshData) = + glBindVertexArray(0) + self.vaos = @[] + self.tf_vaos = @[] + self.vao_specs = @[] + self.tf_vao_specs = @[] + glBindBuffer(GL_ARRAY_BUFFER, 0) self.vertex_buffers = @[] self.index_buffers = @[] self.loaded = false - glBindVertexArray(0) - for i in 0 ..< self.vaos.len: - glDeleteVertexArrays(1, addr self.vaos[i]) - # TODO! delete individual buffers? - self.vaos = @[] if self.users.len == 0: self.engine.mesh_datas.del(self.hash) @@ -233,14 +225,14 @@ proc update_varray*(self: MeshData) = if self.vertex_buffers.len == 0: return for i,va in self.varrays: - glBindBuffer(GL_ARRAY_BUFFER, self.vertex_buffers[i]) + glBindBuffer(GL_ARRAY_BUFFER, self.vertex_buffers[i].GLuint) glBufferSubData(GL_ARRAY_BUFFER, 0.GLintptr, cast[GLsizeiptr](va.bytelen), addr va[0]) proc update_iarray*(self: MeshData) = if self.index_buffers.len == 0: return for i,ia in self.iarrays: - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.index_buffers[i]) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.index_buffers[i].GLuint) glBufferData(GL_ELEMENT_ARRAY_BUFFER, cast[GLsizeiptr](ia.bytelen), addr ia[0], GL_STATIC_DRAW) proc clone*(self: MeshData): MeshData = diff --git a/src/platform/gl.nim b/src/platform/gl.nim index 88ff6b7..8de3247 100644 --- a/src/platform/gl.nim +++ b/src/platform/gl.nim @@ -20,4 +20,49 @@ proc get_program_info_log*(program: GLuint): string = glGetProgramInfoLog(program, logSize.GLsizei, nil, s.cstring) return s +let main_thread_id = getThreadID() + +template handleError(x: untyped, t: string) = + assert main_thread_id == getThreadID() + try: + x + except: + echo "Error when deleting " & t + echo getCurrentExceptionMsg() + +type GPUBuffer* = distinct GLuint +type GPUTexture* = distinct GLuint +type GPUVao* = distinct GLuint +type GPUFramebuffer* = distinct GLuint +type GPURenderbuffer* = distinct GLuint +type GPUProgram* = distinct GLuint +type GPUShader* = distinct GLuint + +proc `=destroy`(x: var GPUBuffer) = + if x.GLuint != 0.GLuint: + handleError glDeleteBuffers(1, x.GLuint.addr), "Buffer" + +proc `=destroy`(x: var GPUTexture) = + if x.GLuint != 0.GLuint: + handleError glDeleteTextures(1, x.GLuint.addr), "Texture" + +proc `=destroy`(x: var GPUVao) = + if x.GLuint != 0.GLuint: + handleError glDeleteVertexArrays(1, x.GLuint.addr), "VertexArray" + +proc `=destroy`(x: var GPUFramebuffer) = + if x.GLuint != 0.GLuint: + handleError glDeleteFramebuffers(1, x.GLuint.addr), "Framebuffer" + +proc `=destroy`(x: var GPURenderbuffer) = + if x.GLuint != 0.GLuint: + handleError glDeleteRenderbuffers(1, x.GLuint.addr), "Renderbuffer" + +proc `=destroy`(x: var GPUProgram) = + if x.GLuint != 0.GLuint: + handleError glDeleteProgram(x.GLuint), "Program" + +proc `=destroy`(x: var GPUShader) = + if x.GLuint != 0.GLuint: + handleError glDeleteShader(x.GLuint), "Shader" diff --git a/src/scene.nim b/src/scene.nim index ef66bb4..81104dd 100644 --- a/src/scene.nim +++ b/src/scene.nim @@ -343,7 +343,6 @@ proc set_objects_auto_update_matrix*(self: Scene, objects: seq[GameObject], auto proc destroy*(self: Scene) = for ob in reversed(self.children): - var ob = ob ob.destroy(recursive=false) self.world.destroy() for mat in self.materials.values: @@ -353,6 +352,17 @@ proc destroy*(self: Scene) = for ubo in self.lighting_UBOs: ubo.destroy() self.engine.scenes.del(self.name) + # bound textures can linger + unbindAllTextures() + # probably none of this is actually necessary + # (needs testing without this) + self.children = @[] + self.auto_updated_children = @[] + self.objects.clear() + self.parents.clear() + self.materials.clear() + self.textures.clear() + self.mesh_passes = @[] proc enable_render*(self: Scene) = self.enabled = true @@ -417,9 +427,9 @@ proc calculate_max_lights_and_cubemaps*(self: Scene) = for _,mat in self.materials: if mat.ubos.anyIt it in self.lighting_UBOs: mat.delete_all_shaders - echo &"calculated max lights {self.max_point_lights} {self.max_sun_lights}" - echo &"calculated max shadow maps {self.max_shadow_maps}" - echo &"calculated max cubemaps {self.max_cubemaps}" + # echo &"calculated max lights {self.max_point_lights} {self.max_sun_lights}" + # echo &"calculated max shadow maps {self.max_shadow_maps}" + # echo &"calculated max cubemaps {self.max_cubemaps}" proc get_lighting_UBOs*(self: Scene): seq[UBO] = # echo &"getting UBOs {self.max_point_lights} {self.max_sun_lights}" @@ -620,7 +630,7 @@ proc sort_cubemaps*(self: Scene) = # or share depth buffers? proc ensure_cubemaps*(self: Scene) = - echo "ensuring cubemaps" + # echo "ensuring cubemaps" let res = self.cubemap_resolution if res == 0: return @@ -650,7 +660,7 @@ proc ensure_cubemaps*(self: Scene) = ) probe.ubo_index = self.cubemaps.len.int32 self.cubemaps.add(probe.cubemap) - echo &"made {self.cubemaps.len} cubemaps" + # echo &"made {self.cubemaps.len} cubemaps" proc render_all_cubemaps*(self: Scene, use_roughness_prefiltering: bool, mipmap_shader: Material = nil) = if self.cubemap_UBO == nil: diff --git a/src/types.nim b/src/types.nim index 53436fb..3349a89 100644 --- a/src/types.nim +++ b/src/types.nim @@ -167,11 +167,11 @@ type varrays*: seq[ArrRef[float32]] iarrays*: seq[ArrRef[uint16]] loaded*: bool - vertex_buffers*: seq[GLuint] ## private + vertex_buffers*: seq[GPUBuffer] ## private vertex_starts*: seq[int32] ## private - index_buffers*: seq[GLuint] ## private + index_buffers*: seq[GPUBuffer] ## private num_indices*: seq[int32] ## private - vaos*: seq[GLuint] ## private + vaos*: seq[GPUVao] ## private vao_specs*: seq[VaoSpec] ## private index_types*: seq[DataType] ## private layout*: AttributeList ## private @@ -183,8 +183,8 @@ type # load_promise: unknown # load_resolve: unknown use_tf*: bool ## private - tf_vbos*: seq[seq[GLuint]] ## private - tf_vaos*: seq[GLuint] ## private + tf_vbos*: seq[seq[GPUBuffer]] ## private + tf_vaos*: seq[GPUVao] ## private tf_vao_specs*: seq[VaoSpec] ## private tf_layout*: AttributeList ## private when defined(myouUseRenderdoc): @@ -504,7 +504,7 @@ type name*: string size32*: int byte_storage*: ArrRef[byte] - buffer*: GLuint + buffer*: GPUBuffer binding_point*: int32 # needs_update*: bool @@ -555,14 +555,14 @@ type defines*: Table[string, string] ### - scene*: Scene + scene* {.cursor.}: Scene shaders*: Table[AttributeList, Shader] ## private users*: seq[GameObject] ## private - render_scene*: Scene ## private + render_scene* {.cursor.}: Scene ## private has_texture_list_checked*: bool ## private shader_library*: string ## private use_debug_shaders*: bool ## private - last_shader*: Shader ## private + last_shader* {.cursor.}: Shader ## private feedback_varyings*: seq[string] @@ -570,8 +570,8 @@ type Shader* = ref object engine* {.cursor.}: MyouEngine # do we need this? id*: int - program*: GLuint - material*: Material # do we need this? + program*: GPUProgram + material* {.cursor.}: Material # do we need this? # TODO: combine in single seq? ubos*: seq[UBO] @@ -638,7 +638,7 @@ type TextureStorage* = ref object ## private unit*: GLint target*: GLenum - tex*: GLuint + tex*: GPUTexture iformat*: GLenum format*, gltype*: GLenum # index of the first layer (or only layer if not using tiles) @@ -651,7 +651,7 @@ type name*: string storage*: TextureStorage ## private loaded*: bool - last_used_shader*: Shader ## private + last_used_shader* {.cursor.}: Shader ## private sampler_object*: GLuint ## private width*, height*, depth*: int tex_type*: TextureType @@ -915,4 +915,5 @@ template enqueue*(renderer: RenderManager, fun: untyped) = when not defined(release): from sugar import dump - export sugar + export sugar.dump +