From 19ed29e806c5e4d219131015d2ddd10dc9c50967 Mon Sep 17 00:00:00 2001 From: Alberto Torres Date: Thu, 22 Aug 2024 21:38:14 +0200 Subject: [PATCH] Replace pixie by stb_image. Add support for 16 bit images, TGA, PSD, HDR, etc. --- .gitmodules | 6 +- libs/stb_image_nim | 1 + src/graphics/texture.nim | 125 +++++++++++++++++++++++++++------------ src/types.nim | 6 +- 4 files changed, 95 insertions(+), 43 deletions(-) create mode 160000 libs/stb_image_nim diff --git a/.gitmodules b/.gitmodules index baff0de..34896e7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ [submodule "libs/vmath"] path = libs/vmath url = https://git.myou.dev/MyouProject/vmath -[submodule "libs/pixie"] - path = libs/pixie - url = https://git.myou.dev/MyouProject/pixie [submodule "libs/nim_zstd"] path = libs/nim_zstd url = https://github.com/DiThi/nim_zstd +[submodule "libs/stb_image_nim"] + path = libs/stb_image_nim + url = https://gitlab.com/kungfoobar/stb_image-Nim.git diff --git a/libs/stb_image_nim b/libs/stb_image_nim new file mode 160000 index 0000000..11e49d0 --- /dev/null +++ b/libs/stb_image_nim @@ -0,0 +1 @@ +Subproject commit 11e49d00741d755e5bbc940cea8c98e16713653f diff --git a/src/graphics/texture.nim b/src/graphics/texture.nim index b9902b3..f8826a6 100644 --- a/src/graphics/texture.nim +++ b/src/graphics/texture.nim @@ -78,17 +78,18 @@ func toInternalFormat*(format: TextureFormat): GLenum # import sugar -when not defined(no_pixie): +when defined(myouUsePixie): import pixie else: - type PixieError = Exception + import stb_image/read as stbi + const myouConvertHdrToFloat16 {.booldefine.} = true when not defined(nimdoc): import tinyexr import std/bitops import std/strformat -# import float16 +import float16 import loadable import ddx_ktx @@ -117,10 +118,10 @@ proc stride*(format: TextureFormat): int = of RG_u8: 2 of RGB_u8: 3 of RGBA_u8: 4 - of R_f16: 2 - of RG_f16: 4 - of RGB_f16: 6 - of RGBA_f16: 8 + of R_u16, R_f16: 2 + of RG_u16, RG_f16: 4 + of RGB_u16, RGB_f16: 6 + of RGBA_u16, RGBA_f16: 8 of R_f32: 4 of RG_f32: 8 of RGB_f32: 12 @@ -132,9 +133,9 @@ proc stride*(format: TextureFormat): int = proc channel_count*(format: TextureFormat): int = case format: - of R_u8, R_f16, R_f32, Depth_u16, Depth_u24, Depth_f32: 1 - of RG_u8, RG_f16, RG_f32: 2 - of SRGB_u8, RGB_u8, RGB_f16, RGB_f32: 3 + of R_u8, R_u16, R_f16, R_f32, Depth_u16, Depth_u24, Depth_f32: 1 + of RG_u8, RG_u16, RG_f16, RG_f32: 2 + of SRGB_u8, RGB_u8, RGB_u16, RGB_f16, RGB_f32: 3 else: 4 func samplerType*(tex_type: TextureType): string = @@ -155,6 +156,10 @@ func toInternalFormat*(format: TextureFormat): GLenum = of RG_u8: GL_RG8 of RGB_u8: GL_RGB8 of RGBA_u8: GL_RGBA8 + of R_u16: GL_R16 + of RG_u16: GL_RG16 + of RGB_u16: GL_RGB16 + of RGBA_u16: GL_RGBA16 of R_f16: GL_R16F of RG_f16: GL_RG16F of RGB_f16: GL_RGB16F @@ -209,10 +214,10 @@ proc newTextureStorage*(ttype: TextureType, width, height, depth: int, format: T of TexExternal: GL_TEXTURE_EXTERNAL_OES ts.iformat = format.toInternalFormat ts.format = case format: - of R_u8, R_f16, R_f32: GL_RED - of RG_u8, RG_f16, RG_f32: GL_RG - of SRGB_u8, RGB_u8, RGB_f16, RGB_f32: GL_RGB - of SRGB_Alpha_u8, RGBA_u8, RGBA_f16, RGBA_f32: GL_RGBA + of R_u8, R_u16, R_f16, R_f32: GL_RED + of RG_u8, RG_u16, RG_f16, RG_f32: GL_RG + of SRGB_u8, RGB_u8, RGB_u16, RGB_f16, RGB_f32: GL_RGB + of SRGB_Alpha_u8, RGBA_u8, RGBA_u16, RGBA_f16, RGBA_f32: GL_RGBA of Depth_u16, Depth_u24, Depth_f32: GL_DEPTH_COMPONENT # of Depth_u24_s8: GL_DEPTH_COMPONENT # else: @@ -221,7 +226,7 @@ proc newTextureStorage*(ttype: TextureType, width, height, depth: int, format: T of SRGB_u8, SRGB_Alpha_u8, R_u8, RG_u8, RGB_u8, RGBA_u8: GL_UNSIGNED_BYTE of R_f16, RG_f16, RGB_f16, RGBA_f16: GL_HALF_FLOAT of R_f32, RG_f32, RGB_f32, RGBA_f32, Depth_f32: cGL_FLOAT - of Depth_u16: GL_UNSIGNED_SHORT + of R_u16, RG_u16, RGB_u16, RGBA_u16, Depth_u16: GL_UNSIGNED_SHORT of Depth_u24: GL_UNSIGNED_INT # of Depth_u24_s8: GL_UNSIGNED_INT ts.layer = 0 @@ -404,6 +409,16 @@ proc loadCompressedData*(self: Texture, info: KtxInfo, data: seq[KtxPart]) = info.internal_format.GLenum, self.width.GLsizei, self.height.GLsizei, 0, part.len.GLsizei, part.data) +template toOpenArrayByte(p: pointer, a,b: untyped): untyped = + cast[ptr UncheckedArray[byte]](p).toOpenArray(a,b) + +template toOpenArray[T](p: pointer, a,b: untyped): untyped = + cast[ptr UncheckedArray[T]](p).toOpenArray(a,b) + +proc f32_to_f16(source: ptr UncheckedArray[float32], dest: ptr UncheckedArray[Float16], len: int) = + for i in 0 ..< len: + dest[i] = source[i].tofloat16(clamp=true) + proc loadFileFromPointersLen*(self: Texture, pointers: seq[(pointer, int)], flip = true) = when not defined(nimdoc): assert self.tex_type != TexCube, "Loading a cube texture from file is not supported yet" @@ -414,12 +429,17 @@ proc loadFileFromPointersLen*(self: Texture, pointers: seq[(pointer, int)], flip multilayer_buffer = newArrRef[byte](layer_stride * self.depth) var pos = 0 for (p, len) in pointers: - when not defined(no_pixie): + when defined(myouUsePixie): var image: Image + else: + var image: imagePixelData[byte] + var image_16: imagePixelData[uint16] + var image_f: imagePixelData[float32] var buffer: ArrRef[byte] # a reference to this pointer is kept with one of the vars above var pixels_ptr: pointer var pixels_len: int + var flip = flip if isEXR(p, len): let (width, height, pixels) = decodeEXR(p, len) assert width == self.width and height == self.height, "Image size mismatch" @@ -427,11 +447,33 @@ proc loadFileFromPointersLen*(self: Texture, pointers: seq[(pointer, int)], flip pixels_ptr = buffer[0].addr pixels_len = buffer.len else: - when not defined(no_pixie): + when defined(myouUsePixie): image = decodeImage(p, len) assert image.width == self.width and image.height == self.height, "Image size mismatch" pixels_ptr = image.data[0].addr pixels_len = image.data.len * sizeof image.data[0] + else: + setFlipVerticallyOnLoad(flip) + flip = false + var w,h,c = 0 + if isHDRFromMemory(p.toOpenArrayByte(0, len-1)): + image_f = loadFFromMemory(p.toOpenArrayByte(0, len-1), w,h,c,0) + pixels_ptr = image_f.data + pixels_len = image_f.byteLen + when myouConvertHdrToFloat16: + f32_to_f16( + cast[ptr UncheckedArray[float32]](pixels_ptr), + cast[ptr UncheckedArray[Float16]](pixels_ptr), + image_f.len) + pixels_len = pixels_len div 2 + elif is16BitFromMemory(p.toOpenArrayByte(0, len-1)): + image_16 = load16FromMemory(p.toOpenArrayByte(0, len-1), w,h,c,0) + pixels_ptr = image_16.data + pixels_len = image_16.byteLen + else: + image = loadFromMemory(p.toOpenArrayByte(0, len-1), w,h,c,0) + pixels_ptr = image.data + pixels_len = image.len assert layer_stride == pixels_len, &"Image '{self.name}' has a length of {pixels_len}, expected {layer_stride}" if flip: @@ -508,19 +550,25 @@ proc generateMipmap*(self: Texture) = proc getDimensionsFormat(p: pointer, len: int): (int, int, TextureFormat) = when not defined(nimdoc): - try: - when not defined(no_pixie): - let dims = decodeImageDimensions(p, len) - return (dims.width, dims.height, RGBA_u8) - else: raise PixieError.newException("Pixie not compiled in") - except PixieError as e: - # TODO: abstract these functions to e.g. be able to replace these by oiio, etc - # and avoid repetitions - if isEXR(p, len): - let dims = getEXRDimensions(p, len) - return (dims[0], dims[1], RGBA_f16) - else: - raise e + if isEXR(p, len): + let dims = getEXRDimensions(p, len) + return (dims[0], dims[1], RGBA_f16) + when defined(myouUsePixie): + let dims = decodeImageDimensions(p, len) + return (dims.width, dims.height, RGBA_u8) + else: + var width, height, channels = 0 + if not infoFromMemory(p.toOpenArrayByte(0, len-1), width, height, channels): + raise ValueError.newException "Could not read image" + 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 + assert (RG_u8.int - R_u8.int) == 1 # (just in case someone changes the enum) + const toHDR = when myouConvertHdrToFloat16: (R_f16.int-R_u8.int) else: (R_f32.int-R_u8.int) + let format = (R_u8.int - 1 + channels + + hdr.int * toHDR + is16.int * (R_u16.int-R_u8.int)).TextureFormat + dump format + return (width, height, format) func to_sRGB*(format: TextureFormat): TextureFormat = return case format: @@ -530,15 +578,14 @@ func to_sRGB*(format: TextureFormat): TextureFormat = proc newTexture*(engine: MyouEngine, name: string, p: pointer, len: int, is_sRGB: bool, filter: TextureFilter = Trilinear, depth=1, flip=true): Texture = # TODO: replace this function by one taking a LoadableResource - when not defined(no_pixie): - var (width, height, format) = getDimensionsFormat(p, len) - if is_sRGB: - format = format.to_sRGB - let self = engine.newTexture(name, width, height, depth, format, filter=filter) - engine.renderer.enqueue proc() = - self.loadFileFromPointersLen(@[(p, len)], flip=flip) - self.loaded = true - return self + var (width, height, format) = getDimensionsFormat(p, len) + if is_sRGB: + format = format.to_sRGB + let self = engine.newTexture(name, width, height, depth, format, filter=filter) + engine.renderer.enqueue proc() = + self.loadFileFromPointersLen(@[(p, len)], flip=flip) + self.loaded = true + return self proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: bool, filter: TextureFilter = Trilinear, diff --git a/src/types.nim b/src/types.nim index 1872e40..d4f3959 100644 --- a/src/types.nim +++ b/src/types.nim @@ -97,6 +97,7 @@ type TArmature GameObject* = ref object of RootObj + # TODO: remove otype otype*: ObjectType ## private engine*: MyouEngine ## private debug*: bool ## private @@ -590,7 +591,6 @@ type TextureFormat* = enum # TODO: signed normalized formats - # TODO: Float16 # TODO: uncommon formats SRGB_u8 SRGB_Alpha_u8 @@ -598,6 +598,10 @@ type RG_u8 RGB_u8 RGBA_u8 + R_u16 + RG_u16 + RGB_u16 + RGBA_u16 R_f16 RG_f16 RGB_f16