Replace pixie by stb_image. Add support for 16 bit images, TGA, PSD, HDR, etc.

This commit is contained in:
Alberto Torres 2024-08-22 21:38:14 +02:00
parent 0760ab745a
commit 19ed29e806
4 changed files with 95 additions and 43 deletions

6
.gitmodules vendored
View file

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

1
libs/stb_image_nim Submodule

@ -0,0 +1 @@
Subproject commit 11e49d00741d755e5bbc940cea8c98e16713653f

View file

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

View file

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