Compare commits

...

4 commits

19 changed files with 303 additions and 233 deletions

View file

@ -75,15 +75,16 @@ proc newLoadableResource*[T: LoadableResource](
result.str = proc(): string = ""
result.use_threads = use_threads
# main -> thread channels
var to_start: Channel[LoadableResource]
# main <- thread channels
var to_return: Channel[(LoadableResource, bool, string, SliceMem[byte])]
when compileOption("threads"):
# main -> thread channels
var to_start: Channel[LoadableResource]
# main <- thread channels
var to_return: Channel[(LoadableResource, bool, string, SliceMem[byte])]
proc start*[T: LoadableResource](self: T) =
self.status = Started
if self.use_threads:
to_start.send self
when compileOption("threads"): to_start.send self
else:
self.start_func(self)
@ -97,7 +98,8 @@ proc onload*[T: LoadableResource](self: T, ok: bool, err: string, data = SliceMe
# self.result[] = (ok, err, data)
if self.onload_func != nil:
if self.use_threads:
to_return.send((self.LoadableResource, ok, err, data))
when compileOption("threads"):
to_return.send((self.LoadableResource, ok, err, data))
else:
self.onload_func(ok, err, data)
@ -124,30 +126,31 @@ proc cancel*[T: LoadableResource](self: T) =
# results.add res.result[]
# res.result = nil
var worker: Thread[void]
proc workerThreadProc() {.thread.} =
while true:
let res = to_start.recv()
if res == nil:
break
cast[proc(self: LoadableResource) {.gcsafe.}](res.start_func)(res)
when compileOption("threads"):
var worker: Thread[void]
proc workerThreadProc() {.thread.} =
while true:
let res = to_start.recv()
if res == nil:
break
cast[proc(self: LoadableResource) {.gcsafe.}](res.start_func)(res)
worker.createThread(workerThreadProc)
to_start.open()
to_return.open()
worker.createThread(workerThreadProc)
to_start.open()
to_return.open()
proc updateLoadableWorkerThreads*() =
while true:
let tried = to_return.tryRecv()
if not tried.dataAvailable:
break
let (res, ok, err, data) = tried.msg
res.onload_func(ok, err, data)
proc updateLoadableWorkerThreads*() =
while true:
let tried = to_return.tryRecv()
if not tried.dataAvailable:
break
let (res, ok, err, data) = tried.msg
res.onload_func(ok, err, data)
proc terminateLoadableWorkerThreads*() =
# TODO: test this
to_start.send(nil.LoadableResource)
worker.joinThread()
proc terminateLoadableWorkerThreads*() =
# TODO: test this
to_start.send(nil.LoadableResource)
worker.joinThread()
type Fetch* = ref object of LoadableResource
custom: pointer
@ -246,7 +249,10 @@ proc loadUri*(
var start_func: proc(self: LoadableResource)
var self: Fetch
var uri = uri
var use_threads = use_threads
when compileOption("threads"):
var use_threads = use_threads
else:
var use_threads = false
when not defined(onlyLocalFiles):
when defined(emscripten):
const is_remote = true

View file

@ -142,6 +142,10 @@ proc loadFileFromSlices*(tex: Texture, slices: seq[SliceMem[byte]],
tex.format
else:
tex.format.resize(min_channels)
# TODO: Don't do this!!
# Change the format when the decoder has detected less channels!!
let min_channels = format.channel_count
let layer_stride = tex.width * tex.height * format.stride
var multilayer_buffer: ArrRef[byte]
assert tex.depth == slices.len
@ -197,8 +201,10 @@ proc loadFileFromSlices*(tex: Texture, slices: seq[SliceMem[byte]],
image = loadFromMemory(slice.toOpenArrayByte, w,h,c, min_channels)
pixels_ptr = image.data
pixels_len = image.len
assert layer_stride == pixels_len,
&"Image '{tex.name}' has a length of {pixels_len}, expected {layer_stride}"
if layer_stride != pixels_len:
echo "Format: ", format
raise Defect.newException &"Image '{tex.name}' has a length" &
&" of {pixels_len}, expected {layer_stride}"
if flip:
swap_lines(pixels_ptr, tex.width * format.stride, tex.height)
if tex.depth == 1:

View file

@ -367,32 +367,33 @@ proc loadOptimized*(tex: Texture, slices: seq[SliceMem[byte]],
, flip = flip, min_channels = min_channels)
# main -> thread channels
type DecodeChanMsg = tuple[
tex: Texture, slices: seq[SliceMem[byte]],
callback_uncompressed: CallbackUncompressed,
callback_compressed: CallbackCompressed,
flip: bool, min_channels: int,
]
var decode_chan: Channel[DecodeChanMsg]
# main <- thread channels
type DecodeReturnChanMsg = tuple[
callback: CallbackUncompressed,
tex: Texture, data: SliceMem[byte],
]
var decode_return_chan: Channel[DecodeReturnChanMsg]
type CompressedReturnChanMsg = tuple[
callback: CallbackCompressed,
tex: Texture, data: KtxInfoParts, refdata: seq[SliceMem[byte]],
]
var compressed_return_chan: Channel[CompressedReturnChanMsg]
when compileOption("threads"):
# main -> thread channels
type DecodeChanMsg = tuple[
tex: Texture, slices: seq[SliceMem[byte]],
callback_uncompressed: CallbackUncompressed,
callback_compressed: CallbackCompressed,
flip: bool, min_channels: int,
]
var decode_chan: Channel[DecodeChanMsg]
# main <- thread channels
type DecodeReturnChanMsg = tuple[
callback: CallbackUncompressed,
tex: Texture, data: SliceMem[byte],
]
var decode_return_chan: Channel[DecodeReturnChanMsg]
type CompressedReturnChanMsg = tuple[
callback: CallbackCompressed,
tex: Texture, data: KtxInfoParts, refdata: seq[SliceMem[byte]],
]
var compressed_return_chan: Channel[CompressedReturnChanMsg]
proc loadOptimizedThreaded*(tex: Texture, slices: seq[SliceMem[byte]],
callback_uncompressed: CallbackUncompressed = nil,
callback_compressed: CallbackCompressed = nil,
flip = true, min_channels = 0) =
when false:
when not compileOption("threads"):
loadOptimized(tex, slices, callback_uncompressed, callback_compressed, flip, min_channels)
else:
decode_chan.send((tex: tex, slices: slices,
@ -400,45 +401,46 @@ proc loadOptimizedThreaded*(tex: Texture, slices: seq[SliceMem[byte]],
callback_compressed: callback_compressed,
flip: flip, min_channels: min_channels))
var workers = newSeq[Thread[void]](myouEngineNumTextureThreads)
proc workerThreadProc() {.thread.} =
# TODO: handle errors
while true:
let to_decode = decode_chan.recv()
if to_decode.tex == nil:
break
proc cb(tex: Texture, data: SliceMem[byte]) =
decode_return_chan.send((callback: to_decode.callback_uncompressed, tex: tex, data: data))
proc cbc(tex: Texture, data: KtxInfoParts, refdata: seq[SliceMem[byte]]) =
compressed_return_chan.send((callback: to_decode.callback_compressed, tex: tex, data: data, refdata: refdata))
let cb_out = if to_decode.callback_uncompressed != nil: cb else: nil
let cbc_out = if to_decode.callback_compressed != nil: cbc else: nil
loadOptimized(to_decode.tex, to_decode.slices, cb_out, cbc_out,
to_decode.flip, to_decode.min_channels)
when compileOption("threads"):
var workers = newSeq[Thread[void]](myouEngineNumTextureThreads)
proc workerThreadProc() {.thread.} =
# TODO: handle errors
while true:
let to_decode = decode_chan.recv()
if to_decode.tex == nil:
break
proc cb(tex: Texture, data: SliceMem[byte]) =
decode_return_chan.send((callback: to_decode.callback_uncompressed, tex: tex, data: data))
proc cbc(tex: Texture, data: KtxInfoParts, refdata: seq[SliceMem[byte]]) =
compressed_return_chan.send((callback: to_decode.callback_compressed, tex: tex, data: data, refdata: refdata))
let cb_out = if to_decode.callback_uncompressed != nil: cb else: nil
let cbc_out = if to_decode.callback_compressed != nil: cbc else: nil
loadOptimized(to_decode.tex, to_decode.slices, cb_out, cbc_out,
to_decode.flip, to_decode.min_channels)
decode_chan.open()
decode_return_chan.open()
compressed_return_chan.open()
for worker in workers.mitems:
worker.createThread(workerThreadProc)
decode_chan.open()
decode_return_chan.open()
compressed_return_chan.open()
for worker in workers.mitems:
worker.createThread(workerThreadProc)
proc updateTextureWorkerThreads*() =
# TODO: handle errors
while true:
let tried = decode_return_chan.tryRecv()
if not tried.dataAvailable:
break
let (cb, tex, data) = tried.msg
cb(tex, data)
while true:
let tried = compressed_return_chan.tryRecv()
if not tried.dataAvailable:
break
let (cb, tex, data, refdata) = tried.msg
cb(tex, data, refdata)
proc updateTextureWorkerThreads*() =
# TODO: handle errors
while true:
let tried = decode_return_chan.tryRecv()
if not tried.dataAvailable:
break
let (cb, tex, data) = tried.msg
cb(tex, data)
while true:
let tried = compressed_return_chan.tryRecv()
if not tried.dataAvailable:
break
let (cb, tex, data, refdata) = tried.msg
cb(tex, data, refdata)
proc terminateTextureWorkerThreads*() =
for worker in workers:
decode_chan.send(DecodeChanMsg.default)
for worker in workers:
worker.joinThread()
proc terminateTextureWorkerThreads*() =
for worker in workers:
decode_chan.send(DecodeChanMsg.default)
for worker in workers:
worker.joinThread()

View file

@ -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.} =

View file

@ -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

View file

@ -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()

View file

@ -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) =

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -77,8 +77,9 @@ import ./screen
import ./platform/platform
import ./loaders/blend
import ./util
from loadable import updateLoadableWorkerThreads
from ./gpu_formats/texture_optimize import updateTextureWorkerThreads
when compileOption("threads"):
from loadable import updateLoadableWorkerThreads
from ./gpu_formats/texture_optimize import updateTextureWorkerThreads
import arr_ref
export arr_ref
@ -167,8 +168,9 @@ proc myou_main_loop*(self: MyouEngine) =
##
## You usually don't need to call this. Use `run <#run,MyouEngine>`_
## instead.
updateTextureWorkerThreads()
updateLoadableWorkerThreads()
when compileOption("threads"):
updateTextureWorkerThreads()
updateLoadableWorkerThreads()
# TODO: make a table object that can be iterated while changing, e.g. with a
# seq and a dirty flag to update the seq
if self.new_scenes.len != 0:

View file

@ -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"

View file

@ -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

View file

@ -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 =

View file

@ -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"

View file

@ -46,8 +46,9 @@ import ../screen
import ../util
import ../graphics/render
import ../input
from loadable import terminateLoadableWorkerThreads
from ../gpu_formats/texture_optimize import terminateTextureWorkerThreads
when compileOption("threads"):
from loadable import terminateLoadableWorkerThreads
from ../gpu_formats/texture_optimize import terminateTextureWorkerThreads
proc screen*(window: Window): Screen {.inline.} =
cast[Screen](window.getWindowUserPointer)
@ -234,8 +235,9 @@ proc start_platform_main_loop*(engine: MyouEngine, main_loop: proc(self: MyouEng
for i in 0 ..< window.screen.frame_interval:
window.swapBuffers()
glfw.pollEvents()
terminateLoadableWorkerThreads()
terminateTextureWorkerThreads()
when compileOption("threads"):
terminateLoadableWorkerThreads()
terminateTextureWorkerThreads()
glfw.terminate()
proc myouAndroidGetActivity*(): pointer =

View file

@ -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:

View file

@ -126,8 +126,6 @@ proc renderShadow*(self: SimpleShadowManager, scene: Scene,
min_z: float32 = 0) =
# Find the projection matrix that fits all bounding points and sphere
# TODO: option to rotate proj to favor vertical lines?
when not defined(release):
debug_mesh.clear_vertices()
let rotm = self.light.world_matrix.to_mat3_rotation
let rotmi = rotm.inverse
var pmin = vec3(Inf)
@ -182,9 +180,6 @@ proc renderShadowWithCamera*(self: SimpleShadowManager, camera: Camera) =
# it assumes that all world matrices are already up to date
# TODO: accept a cameradata instead?
when not defined(release):
debug_mesh.clear_vertices()
template depth_to_screen(d: float32, proj: Mat4): float32 =
# TODO: make more efficient and put in Camera
@ -220,6 +215,8 @@ proc renderShadowWithCamera*(self: SimpleShadowManager, camera: Camera) =
self.renderShadow(camera.scene, points, min_z=zrange*4)
method renderShadow*(self: SimpleShadowManager, camera: Camera): bool {.locks:"unknown".} =
when defined(myouDebugShadows):
debug_mesh.clear_vertices()
if self.use_camera:
self.renderShadowWithCamera(
camera = camera)

View file

@ -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