Load textures in a thread and optionally compress them (in ASTC only for now).

This commit is contained in:
Alberto Torres 2024-09-03 15:59:15 +02:00
parent 0949e1bf99
commit 878137db71
7 changed files with 319 additions and 33 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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