From 878137db71dfe4ec02b37397107468672ad617c2 Mon Sep 17 00:00:00 2001 From: Alberto Torres Date: Tue, 3 Sep 2024 15:59:15 +0200 Subject: [PATCH] Load textures in a thread and optionally compress them (in ASTC only for now). --- libs/dds_ktx/dds_ktx.nim | 2 +- src/gpu_formats/texture_decode.nim | 44 ++++- src/gpu_formats/texture_optimize.nim | 241 +++++++++++++++++++++++++++ src/graphics/texture.nim | 57 ++++--- src/myou_engine.nim | 2 + src/platform/glfw_wrap.nim | 4 + src/types.nim | 2 + 7 files changed, 319 insertions(+), 33 deletions(-) create mode 100644 src/gpu_formats/texture_optimize.nim diff --git a/libs/dds_ktx/dds_ktx.nim b/libs/dds_ktx/dds_ktx.nim index fc09789..086bf52 100644 --- a/libs/dds_ktx/dds_ktx.nim +++ b/libs/dds_ktx/dds_ktx.nim @@ -249,7 +249,7 @@ proc ParseDdsKtx*(p: pointer, len: int): seq[KtxPart] = let err_msg = $cast[cstring](err.msg.addr) raise ValueError.newException err_msg -func get_ASTC_internal_format*(blk_size: (int,int), is_sRGB: bool): int32 = +func get_ASTC_internal_format*(blk_size: (SomeInteger,SomeInteger), is_sRGB: bool): int32 = result = if is_sRGB: case blk_size[0]*100 or blk_size[1]: of 04_04: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR diff --git a/src/gpu_formats/texture_decode.nim b/src/gpu_formats/texture_decode.nim index 0259ef0..ab40565 100644 --- a/src/gpu_formats/texture_decode.nim +++ b/src/gpu_formats/texture_decode.nim @@ -33,7 +33,7 @@ import ../types # Forward declarations -proc swap_lines(p: pointer, line_stride, line_count: int) +proc swap_lines(p: pointer, line_stride, line_count: int) {.gcsafe.} # End forward declarations import std/strformat @@ -78,6 +78,22 @@ proc channel_count*(format: TextureFormat): int = of SRGB_u8, RGB_u8, RGB_u16, RGB_f16, RGB_f32: 3 else: 4 +func component_type*(format: TextureFormat): DataType = + case format: + of SRGB_u8, SRGB_Alpha_u8, R_u8, RG_u8, RGB_u8, RGBA_u8: UByte + of R_f16, RG_f16, RGB_f16, RGBA_f16: HalfFloat + of R_f32, RG_f32, RGB_f32, RGBA_f32, Depth_f32: Float + of R_u16, RG_u16, RGB_u16, RGBA_u16, Depth_u16: UShort + of Depth_u24: UInt + +func resize*(format: TextureFormat, channel_count: int): TextureFormat = + if format in [SRGB_u8, SRGB_Alpha_u8] and channel_count < 3: + raise ValueError.newException "Can't resize sRGB to " & $channel_count + if format in [Depth_u16, Depth_u24, Depth_f32]: + raise ValueError.newException "Can't resize depth format" + let f = format.int + TextureFormat(f - format.channel_count + channel_count) + template toOpenArrayByte(p: pointer, a,b: untyped): untyped = cast[ptr UncheckedArray[byte]](p).toOpenArray(a,b) @@ -88,7 +104,7 @@ proc f32_to_f16(source: ptr UncheckedArray[float32], dest: ptr UncheckedArray[Fl for i in 0 ..< len: dest[i] = source[i].tofloat16(clamp=true) -proc getDimensionsFormat*(p: pointer, len: int): (int, int, TextureFormat) = +proc getDimensionsFormat*(p: pointer, len: int, min_channels=0): (int, int, TextureFormat) = when not defined(nimdoc): if isEXR(p, len): let dims = getEXRDimensions(p, len) @@ -97,9 +113,12 @@ proc getDimensionsFormat*(p: pointer, len: int): (int, int, TextureFormat) = let dims = decodeImageDimensions(p, len) return (dims.width, dims.height, RGBA_u8) else: + # TODO, IMPORTANT: 2 channels for stb_image means grey+alpha + # We should handle those cases properly var width, height, channels = 0 if not infoFromMemory(p.toOpenArrayByte(0, len-1), width, height, channels): raise ValueError.newException "Could not read image" + channels = max(min_channels, channels) let hdr = isHDRFromMemory(p.toOpenArrayByte(0, len-1)) let is16 = is16BitFromMemory(p.toOpenArrayByte(0, len-1)) # Calculate format with channels, and whether it's hdr or 16 bit @@ -110,11 +129,16 @@ proc getDimensionsFormat*(p: pointer, len: int): (int, int, TextureFormat) = return (width, height, format) proc loadFileFromSlices*(tex: Texture, slices: seq[SliceMem[byte]], - callback: proc(tex: Texture, data: SliceMem[byte]), flip = true) = + callback: proc(tex: Texture, data: SliceMem[byte]), + flip = true, min_channels = 0) {.gcsafe.} = when not defined(nimdoc): assert tex.tex_type != TexCube, "Loading a cube texture from file is not supported yet" - let layer_stride = tex.width * tex.height * tex.format.stride + let format = if min_channels == 0: + tex.format + else: + tex.format.resize(min_channels) + let layer_stride = tex.width * tex.height * format.stride var multilayer_buffer: ArrRef[byte] assert tex.depth == slices.len if tex.depth > 1: @@ -152,7 +176,7 @@ proc loadFileFromSlices*(tex: Texture, slices: seq[SliceMem[byte]], flip = false var w,h,c = 0 if isHDRFromMemory(slice.toOpenArrayByte): - image_f = loadFFromMemory(slice.toOpenArrayByte, w,h,c,0) + image_f = loadFFromMemory(slice.toOpenArrayByte, w,h,c, min_channels) pixels_ptr = image_f.data pixels_len = image_f.byteLen when myouConvertHdrToFloat16: @@ -162,17 +186,17 @@ proc loadFileFromSlices*(tex: Texture, slices: seq[SliceMem[byte]], image_f.len) pixels_len = pixels_len div 2 elif is16BitFromMemory(slice.toOpenArrayByte): - image_16 = load16FromMemory(slice.toOpenArrayByte, w,h,c,0) + image_16 = load16FromMemory(slice.toOpenArrayByte, w,h,c, min_channels) pixels_ptr = image_16.data pixels_len = image_16.byteLen else: - image = loadFromMemory(slice.toOpenArrayByte, w,h,c,0) + 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 flip: - swap_lines(pixels_ptr, tex.width * tex.format.stride, tex.height) + swap_lines(pixels_ptr, tex.width * format.stride, tex.height) if tex.depth == 1: callback(tex, newSliceMem(pixels_ptr, pixels_len, destructor)) return @@ -181,7 +205,7 @@ proc loadFileFromSlices*(tex: Texture, slices: seq[SliceMem[byte]], callback(tex, multilayer_buffer.toSliceMem) -proc swap_lines(p: pointer, line_stride, line_count: int) = +proc swap_lines(p: pointer, line_stride, line_count: int) {.gcsafe.} = template `+`(p: pointer, i: Natural): pointer = cast[pointer](cast[int](p) +% cast[int](i)) template `-`(p: pointer, i: Natural): pointer = cast[pointer](cast[int](p) -% cast[int](i)) let int_stride = line_stride div sizeof(int) @@ -201,3 +225,5 @@ proc swap_lines(p: pointer, line_stride, line_count: int) = swap(b1[j], b2[j]) p1 = p1 + line_stride p2 = p2 - line_stride + + diff --git a/src/gpu_formats/texture_optimize.nim b/src/gpu_formats/texture_optimize.nim new file mode 100644 index 0000000..82021bd --- /dev/null +++ b/src/gpu_formats/texture_optimize.nim @@ -0,0 +1,241 @@ + +import ../types +import ./texture_decode +from dds_ktx import KtxInfo, KtxPart, get_ASTC_internal_format +import astc +export astc +import arr_ref +import stb_image_resize +import std/monotimes +import std/bitops + +const myouEngineNumTextureThreads {.intdefine.} = 4 +const myouEngineCompressTextures {.booldefine.} = true + +const GL_COMPRESSED_RGBA_ASTC_12x12_KHR = 0x93BD'i32 +const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR = 0x93DD'i32 + +template u32(x: untyped): uint32 = cast[uint32](x) + +proc astc_compress_layer*(context: ptr AstcencContext, config: AstcencConfig, + format: TextureFormat; width, height: int, slices: seq[pointer]): ArrRef[byte] = + let depth = slices.len + let data_type = case format: + of SRGB_u8, SRGB_Alpha_u8, R_u8, RG_u8, RGB_u8, RGBA_u8: ASTCENC_TYPE_U8 + of R_f16, RG_f16, RGB_f16, RGBA_f16: ASTCENC_TYPE_F16 + of R_f32, RG_f32, RGB_f32, RGBA_f32: ASTCENC_TYPE_F32 + else: raise ValueError.newException "Unsupported data type of " & $format + let swizzle = case format.channel_count: + # TODO: more optimal usage for 1/2 channels, normals, etc. + of 1: AstcencSwizzle(r: SWZ_R, g: SWZ_R, b: SWZ_R, a: SWZ_1) + of 2: AstcencSwizzle(r: SWZ_R, g: SWZ_G, b: SWZ_0, a: SWZ_1) + of 3: AstcencSwizzle(r: SWZ_R, g: SWZ_G, b: SWZ_B, a: SWZ_1) + else: AstcencSwizzle(r: SWZ_R, g: SWZ_G, b: SWZ_B, a: SWZ_A) + let blocks_x = (width.u32 + config.block_x - 1) div config.block_x + let blocks_y = (height.u32 + config.block_y - 1) div config.block_y + let blocks_z = (depth.u32 + config.block_z - 1) div config.block_z + let buffer_size = blocks_x * blocks_y * blocks_z * 16 + result = newArrRef[byte](buffer_size) + let img = AstcencImage( + dim_x: width.u32, + dim_y: height.u32, + dim_z: depth.u32, + data_type: data_type, + data: slices[0].addr, + ) + const thread_index = 0 # TODO + let err = astcenc_compress_image(context, img.addr, swizzle.addr, + result[0].addr, buffer_size, thread_index) + assert err == ASTCENC_SUCCESS, "ASTC encoding error: " & $err + +proc atsc_compress*(tex: Texture, pixels: SliceMem[byte], + callback: proc(self: Texture, info: KtxInfo, data: seq[KtxPart], refdata: seq[ArrRef[byte]])) = + var width = tex.width.int32 + var height = tex.height.int32 + let depth = if tex.tex_type == TexCube: 1'i32 else: tex.depth.int32 + # TODO: when do we need ASTCENC_PRF_HDR? + # maybe when A is connected to displacement? + var profile: AstcencProfile + var stbir_data_type: StbirDatatype + case tex.format.component_type: + of UByte: + if tex.is_sRGB: + profile = ASTCENC_PRF_LDR_SRGB + stbir_data_type = STBIR_TYPE_UINT8_SRGB + else: + profile = ASTCENC_PRF_LDR + stbir_data_type = STBIR_TYPE_UINT8 + of HalfFloat: + profile = ASTCENC_PRF_HDR_RGB_LDR_A + stbir_data_type = STBIR_TYPE_HALF_FLOAT + of Float: + profile = ASTCENC_PRF_HDR_RGB_LDR_A + stbir_data_type = STBIR_TYPE_FLOAT + of UShort: + profile = ASTCENC_PRF_HDR_RGB_LDR_A + stbir_data_type = STBIR_TYPE_UINT16 + else: + assert false, "unreachable" + # 4 because we're forcing 4 channels for the encoder + let pixel_stride = 4 * (tex.format.stride div tex.format.channel_count) + let quality = 10.0 + let flags = 0'u32 + var config: AstcencConfig + let blk_size = (6,6) + # TODO: 3D texture block size + astc_assert astcenc_config_init(profile, + blk_size[0].uint32, blk_size[1].uint32, 1'u32, + quality, flags, config.addr) + # config.progress_callback = proc(p: float32) {.cdecl.} = + # echo "progress ", p + var ctx: ptr AstcencContext + astc_assert astcenc_context_alloc(config.addr, 1, ctx) + let time = getmonotime().ticks.float/1000000000 + var parts: seq[KtxPart] + var data_refs: seq[ArrRef[byte]] + var mip_level = 0'i32 + var w, prev_w = width + var h, prev_h = height + # round down to POT (TODO: make configurable) + w = 1'i32 shl fastLog2(prev_w) + h = 1'i32 shl fastLog2(prev_h) + let resize_level_0 = w != prev_w or h != prev_h + var current_level, last_level: ArrRef[byte] + var last_src: pointer = pixels.data + while (w > 4 and h > 4) or parts.len == 0: + last_src = if mip_level != 0 or resize_level_0: + current_level = newArrRef[byte](w*h*pixel_stride) + stbir_resize( + last_src, prev_w, prev_h, 0, + current_level.toPointer, w, h, 0, + StbirRgba, stbir_data_type, + STBIR_EDGE_CLAMP, # TODO: handle other cases + STBIR_FILTER_BOX, + ) + last_level = current_level + current_level.toPointer + else: + pixels.data + let data = astc_compress_layer(ctx, config, tex.format, w, h, @[last_src]) + parts.add KtxPart( + width: w, height: h, data: data.toPointer, + len: data.byte_len, mip_level: mip_level, + ) + data_refs.add data + prev_w = w; prev_h = h + w = max(1, w shr 1) + h = max(1, h shr 1) + mip_level.inc + let time2 = getmonotime().ticks.float/1000000000 + echo "time: ", time2-time, " ", tex.name + let info = KtxInfo( + width: width, height: height, depth: depth, + num_layers: 1, num_mipmaps: mip_level, has_alpha: tex.format.channel_count == 4, + is_sRGB: tex.is_sRGB, is_astc: true, + internal_format: get_ASTC_internal_format(blk_size, tex.is_sRGB) + ) + callback(tex, info, parts, data_refs) + astc_assert astcenc_compress_reset(ctx) + astcenc_context_free(ctx) + +type CallbackUncompressed = proc(tex: Texture, data: SliceMem[byte]) {.gcsafe.} +type CallbackCompressed = proc(tex: Texture, info: KtxInfo, data: seq[KtxPart], refdata: seq[ArrRef[byte]]) {.gcsafe.} + +proc loadOptimized*(tex: Texture, slices: seq[SliceMem[byte]], + callback_uncompressed: CallbackUncompressed = nil, + callback_compressed: CallbackCompressed = nil, + flip = true, min_channels = 0) {.gcsafe.} = + + var min_channels = min_channels + var will_compress = myouEngineCompressTextures + var will_load_uncompressed_first = false + + will_compress = will_compress and + callback_compressed != nil and + tex.format.component_type != UShort + + if will_compress: + min_channels = 4 + + tex.loadFileFromSlices(slices, proc(tex: Texture, pixels: SliceMem[byte]) {.gcsafe.} = + if callback_uncompressed != nil and + (will_load_uncompressed_first or not will_compress): + callback_uncompressed(tex, pixels) + if will_compress: + atsc_compress(tex, pixels, callback_compressed) + , 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, info: KtxInfo, data: seq[KtxPart], refdata: seq[ArrRef[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) = + + decode_chan.send((tex: tex, slices: slices, + callback_uncompressed: callback_uncompressed, + 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, info: KtxInfo, data: seq[KtxPart], refdata: seq[ArrRef[byte]]) = + compressed_return_chan.send((callback: to_decode.callback_compressed, tex: tex, + info: info, 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) + +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, info, data, refdata) = tried.msg + cb(tex, info, data, refdata) + +proc terminateTextureWorkerThreads*() = + for worker in workers: + decode_chan.send(DecodeChanMsg.default) + for worker in workers: + worker.joinThread() diff --git a/src/graphics/texture.nim b/src/graphics/texture.nim index ae7914d..561ee77 100644 --- a/src/graphics/texture.nim +++ b/src/graphics/texture.nim @@ -35,7 +35,6 @@ import vmath except Quat import arr_ref # import tinyre import std/tables -import ../gpu_formats/texture_decode import ../platform/gl # Forward declarations @@ -50,7 +49,7 @@ proc unbind*(texture: Texture) proc unbindAllTextures*() proc destroy*(texture: Texture) proc loadFromPixelsPointer*(self: Texture, pixels: pointer) -proc loadFromPixels*[T](self: Texture, pixels: SliceMem[T]) +proc loadFromPixels*[T](self: Texture, pixels: SliceMem[T]) {.gcsafe.} proc loadCubeSideFromPixels*(self: Texture, pixels: pointer, side: int32 = 0) proc setFilter*(self: Texture, filter: TextureFilter) proc newTexture*(engine: MyouEngine, name: string, width, height: int, depth: int = 1, @@ -68,7 +67,7 @@ proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: b ): Texture proc setExtrapolation*(self: Texture, ext: TextureExtrapolation) proc getTexturePixels*(self: Texture): TexturePixels -proc setMipmapRange*(self: Texture, first = 0, last = 1000) +proc setMipmapRange*(self: Texture, first = 0, last = 1000) {.gcsafe.} func vec3size*(self: Texture, mip_level = -1): Vec3 func toInternalFormat*(format: TextureFormat): GLenum # End forward declarations @@ -78,18 +77,23 @@ func toInternalFormat*(format: TextureFormat): GLenum import std/bitops import std/strformat import loadable -import ddx_ktx +import dds_ktx +import ../gpu_formats/texture_decode +import ../gpu_formats/texture_optimize # TODO: use and test destructor # NOTE: we moved this from the render manager to global state # (if we ever need multiple instances with different context # we should move them back) -var bound_textures: seq[Texture] -var active_texture: int32 = -1 -var next_texture: int32 -var max_textures: int32 -var reserved: int32 +# NOTE: set as threadvar for optimization (and for having procs gcsafe +# implicitely), we only use them in the main thread +var bound_textures {.threadvar.}: seq[Texture] +var active_texture {.threadvar.}: int32 +var next_texture {.threadvar.}: int32 +var max_textures {.threadvar.}: int32 +var reserved {.threadvar.}: int32 +active_texture = -1 if defined(android): # I don't know why slot 0 makes the mesh not render at all @@ -345,7 +349,7 @@ proc loadFromPixelsPointer*(self: Texture, pixels: pointer) = if self.needsMipmap: glGenerateMipmap(ts.target) -proc loadFromPixels*[T](self: Texture, pixels: SliceMem[T]) = +proc loadFromPixels*[T](self: Texture, pixels: SliceMem[T]) {.gcsafe.} = self.loadFromPixelsPointer(pixels.data) proc loadCubeSideFromPixels*(self: Texture, pixels: pointer, side: int32 = 0) = @@ -358,7 +362,7 @@ proc loadCubeSideFromPixels*(self: Texture, pixels: pointer, side: int32 = 0) = self.width.GLsizei, self.height.GLsizei, 0, ts.format, ts.gltype, pixels) -proc loadCompressedData*(self: Texture, info: KtxInfo, data: seq[KtxPart]) = +proc loadCompressedData*(self: Texture, info: KtxInfo, data: seq[KtxPart], refdata: seq[ArrRef[byte]]) {.gcsafe.} = assert info.depth == 1 and info.num_layers == 1, "Compressed array and 3D textures not supported yet" let ts = self.storage @@ -368,8 +372,9 @@ proc loadCompressedData*(self: Texture, info: KtxInfo, data: seq[KtxPart]) = else: ts.target.GLuint.int32 for part in data: glCompressedTexImage2D(cast[GLenum](target+part.face), part.mip_level, - info.internal_format.GLenum, self.width.GLsizei, self.height.GLsizei, + info.internal_format.GLenum, part.width.GLsizei, part.height.GLsizei, 0, part.len.GLsizei, part.data) + self.setMipmapRange(0, info.num_mipmaps - 1) proc setFilter*(self: Texture, filter: TextureFilter) = self.filter = filter @@ -441,14 +446,17 @@ func to_sRGB*(format: TextureFormat): TextureFormat = else: raise newException(ValueError, "There's no sRGB version of " & $format) proc newTexture*(engine: MyouEngine, name: string, data: SliceMem[byte], is_sRGB: bool, filter: TextureFilter = Trilinear, depth=1, flip=true): Texture = - # TODO: replace this function by one taking a LoadableResource var (width, height, format) = getDimensionsFormat(data.data, data.byte_len) if is_sRGB: - format = format.to_sRGB + if format in [RGB_u8, RGBA_u8]: + format = format.to_sRGB + else: + # TODO: sRGB shader for 1 and 2 channel textures + echo &"WARNING: Texture {name} is sRGB but has format {format} _" let self = engine.newTexture(name, width, height, depth, format, filter=filter) + self.is_sRGB = is_sRGB engine.renderer.enqueue proc() = - self.loadFileFromSlices(@[data], loadFromPixels, flip=flip) - self.loaded = true + self.loadOptimizedThreaded(@[data], loadFromPixels, loadCompressedData) return self proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: bool, @@ -460,6 +468,7 @@ proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: b # TODO: Api that stores the LoadableResource so it can be re-loaded later # note: format does not matter at this point, will be replaced later let self = engine.newTexture(name, 0, 0, filter=filter, format=RGBA_u8) + self.is_sRGB = is_sRGB var res: LoadableResource proc load(ok: bool, err: string, data: SliceMem[byte]) = assert ok, &"Error loading texture '{self.name}' from '{file_name}': {err}" @@ -476,8 +485,7 @@ proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: b engine.renderer.enqueue proc()= try: self.ensure_storage() - self.loadCompressedData(ktx_info, ParseDdsKtx(data.data, data.byte_len)) - self.setMipmapRange(0, ktx_info.num_mipmaps - 1) + self.loadCompressedData(ktx_info, ParseDdsKtx(data.data, data.byte_len), @[]) self.loaded = true # except Exception as e: except: @@ -487,17 +495,20 @@ proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: b echo getCurrentExceptionMsg() echo "Error loading texture " & file_name else: - var (width, height, format) = getDimensionsFormat(data.data, data.byte_len) + var (width, height, format) = getDimensionsFormat(data.data, data.byte_len, 3) if is_sRGB: - format = format.to_sRGB + if format in [RGB_u8, RGBA_u8]: + format = format.to_sRGB + else: + # TODO: sRGB shader for 1 and 2 channel textures + echo &"WARNING: Texture {self.name} is sRGB but has format {format}" self.width = width self.height = height self.format = format engine.renderer.enqueue proc()= try: self.ensure_storage() - self.loadFileFromSlices(@[data], loadFromPixels) - self.loaded = true + self.loadOptimizedThreaded(@[data], loadFromPixels, loadCompressedData) except: # TODO: use logging echo getCurrentExceptionMsg() @@ -542,7 +553,7 @@ proc getTexturePixels*(self: Texture): TexturePixels = # TODO: limit the amount of mip maps to save some draw calls # (e.g. minimum resolution 16x16 for blurred mipmaps) -proc setMipmapRange*(self: Texture, first = 0, last = 1000) = +proc setMipmapRange*(self: Texture, first = 0, last = 1000) {.gcsafe.} = self.bind_it(needs_active_texture=true) self.mipmap_range = (first, last) glTexParameteri(self.storage.target.GLenum, GL_TEXTURE_BASE_LEVEL, first.GLint); diff --git a/src/myou_engine.nim b/src/myou_engine.nim index edb81d2..04320b1 100644 --- a/src/myou_engine.nim +++ b/src/myou_engine.nim @@ -78,6 +78,7 @@ import ./platform/platform import ./loaders/blend import ./util from loadable import updateLoadableWorkerThreads +from ./gpu_formats/texture_optimize import updateTextureWorkerThreads import arr_ref export arr_ref @@ -166,6 +167,7 @@ proc myou_main_loop*(self: MyouEngine) = ## ## You usually don't need to call this. Use `run <#run,MyouEngine>`_ ## instead. + 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 diff --git a/src/platform/glfw_wrap.nim b/src/platform/glfw_wrap.nim index 16cdcb1..1cc9c82 100644 --- a/src/platform/glfw_wrap.nim +++ b/src/platform/glfw_wrap.nim @@ -46,6 +46,8 @@ import ../screen import ../util import ../graphics/render import ../input +from loadable import terminateLoadableWorkerThreads +from ../gpu_formats/texture_optimize import terminateTextureWorkerThreads proc screen*(window: Window): Screen {.inline.} = cast[Screen](window.getWindowUserPointer) @@ -232,6 +234,8 @@ 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() glfw.terminate() proc myouAndroidGetActivity*(): pointer = diff --git a/src/types.nim b/src/types.nim index 290439a..802bb1f 100644 --- a/src/types.nim +++ b/src/types.nim @@ -652,6 +652,8 @@ type format*: TextureFormat filter*: TextureFilter is_framebuffer_active*: bool ## private + is_sRGB*: bool + is_compressed*: bool mipmap_range*: (int, int) # framebuffer.nim