diff --git a/src/gpu_formats/texture_decode.nim b/src/gpu_formats/texture_decode.nim new file mode 100644 index 0000000..8981150 --- /dev/null +++ b/src/gpu_formats/texture_decode.nim @@ -0,0 +1,200 @@ +# The contents of this file are subject to the Common Public Attribution License +# Version 1.0 (the “License”); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# https://myou.dev/licenses/LICENSE-CPAL. The License is based on the Mozilla +# Public License Version 1.1 but Sections 14 and 15 have been added to cover use +# of software over a computer network and provide for limited attribution for +# the Original Developer. In addition, Exhibit A has been modified to be +# consistent with Exhibit B. +# +# Software distributed under the License is distributed on an “AS IS” basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for +# the specific language governing rights and limitations under the License. +# +# The Original Code is Myou Engine. +# +# the Original Developer is the Initial Developer. +# +# The Initial Developer of the Original Code is the Myou Engine developers. +# All portions of the code written by the Myou Engine developers are Copyright +# (c) 2024. All Rights Reserved. +# +# Alternatively, the contents of this file may be used under the terms of the +# GNU Affero General Public License version 3 (the [AGPL-3] License), in which +# case the provisions of [AGPL-3] License are applicable instead of those above. +# +# If you wish to allow use of your version of this file only under the terms of +# the [AGPL-3] License and not to allow others to use your version of this file +# under the CPAL, indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by the [AGPL-3] +# License. If you do not delete the provisions above, a recipient may use your +# version of this file under either the CPAL or the [AGPL-3] License. + +import ../types + +# Forward declarations +proc swap_lines(p: pointer, line_stride, line_count: int) +# End forward declarations + +import std/strformat +import vmath except Quat +import arr_ref +import float16 + +when defined(myouUsePixie): + import pixie +else: + import stb_image/read as stbi + const myouConvertHdrToFloat16 {.booldefine.} = true + +when not defined(nimdoc): + import tinyexr + +proc stride*(format: TextureFormat): int = + case format: + of SRGB_u8: 3 + of SRGB_Alpha_u8: 4 + of R_u8: 1 + of RG_u8: 2 + of RGB_u8: 3 + of RGBA_u8: 4 + 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 + of RGBA_f32: 16 + of Depth_u16: 2 + of Depth_u24: 3 # TODO: are you sure? + # of Depth_u24_s8: 4 + of Depth_f32: 4 + +proc channel_count*(format: TextureFormat): int = + case format: + 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 + +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 getDimensionsFormat*(p: pointer, len: int): (int, int, TextureFormat) = + when not defined(nimdoc): + 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 + return (width, height, format) + +proc loadFileFromPointersLen*(tex: Texture, pointers: seq[(pointer, int)], + callback: proc(tex: Texture, p: pointer), flip = true) = + + 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 + var multilayer_buffer: ArrRef[byte] + assert tex.depth == pointers.len + if tex.depth > 1: + multilayer_buffer = newArrRef[byte](layer_stride * tex.depth) + var pos = 0 + for (p, len) in pointers: + 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 == tex.width and height == tex.height, "Image size mismatch" + buffer = pixels.to byte + pixels_ptr = buffer[0].addr + pixels_len = buffer.len + else: + when defined(myouUsePixie): + image = decodeImage(p, len) + assert image.width == tex.width and image.height == tex.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 '{tex.name}' has a length of {pixels_len}, expected {layer_stride}" + if flip: + swap_lines(pixels_ptr, tex.width * tex.format.stride, tex.height) + if tex.depth == 1: + callback(tex, pixels_ptr) + return + copyMem(multilayer_buffer[pos].addr, pixels_ptr, layer_stride) + pos += layer_stride + callback(tex, multilayer_buffer[0].addr) + + +proc swap_lines(p: pointer, line_stride, line_count: int) = + 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) + let int_stride_bytes = int_stride * sizeof(int) + var p1 = p + var p2 = p + line_stride*(line_count-1) + var a1, a2: ptr UncheckedArray[int] + var b1, b2: ptr UncheckedArray[byte] + for i in 0 ..< line_count div 2: + a1 = cast[ptr UncheckedArray[int]](p1) + a2 = cast[ptr UncheckedArray[int]](p2) + b1 = cast[ptr UncheckedArray[byte]](p1) + b2 = cast[ptr UncheckedArray[byte]](p2) + for j in 0 ..< int_stride: + swap(a1[j], a2[j]) + for j in int_stride_bytes ..< line_stride: + swap(b1[j], b2[j]) + p1 = p1 + line_stride + p2 = p2 - line_stride diff --git a/src/graphics/texture.nim b/src/graphics/texture.nim index 24da706..528e444 100644 --- a/src/graphics/texture.nim +++ b/src/graphics/texture.nim @@ -35,11 +35,10 @@ import vmath except Quat import arr_ref # import tinyre import std/tables +import ../gpu_formats/texture_decode import ../platform/gl # Forward declarations -proc stride*(format: TextureFormat): int -proc channel_count*(format: TextureFormat): int func mipmapHigh*(self: Texture): int proc needsMipmap*(self: Texture): bool proc setMaxTextures*(count: int32) @@ -52,7 +51,6 @@ proc unbindAllTextures*() proc destroy*(texture: Texture) proc loadFromPixels*(self: Texture, pixels: pointer) proc loadCubeSideFromPixels*(self: Texture, pixels: pointer, side: int32 = 0) -proc loadFileFromPointersLen*(self: Texture, pointers: seq[(pointer, int)], flip = true) proc setFilter*(self: Texture, filter: TextureFilter) proc newTexture*(engine: MyouEngine, name: string, width, height: int, depth: int = 1, format: TextureFormat, @@ -60,7 +58,6 @@ proc newTexture*(engine: MyouEngine, name: string, width, height: int, depth: in filter: TextureFilter = Trilinear, pixels: ArrRef[float32] = nil): Texture proc generateMipmap*(self: Texture) -proc getDimensionsFormat(p: pointer, len: int): (int, int, TextureFormat) 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 proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: bool, @@ -69,7 +66,6 @@ proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: b flip = true, ): Texture proc setExtrapolation*(self: Texture, ext: TextureExtrapolation) -proc swap_lines(p: pointer, line_stride, line_count: int) proc getTexturePixels*(self: Texture): TexturePixels proc setMipmapRange*(self: Texture, first = 0, last = 1000) func vec3size*(self: Texture, mip_level = -1): Vec3 @@ -78,18 +74,8 @@ func toInternalFormat*(format: TextureFormat): GLenum # import sugar -when defined(myouUsePixie): - import pixie -else: - 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 loadable import ddx_ktx @@ -110,34 +96,6 @@ if defined(android): # even if it's not used, we should test it reserved = 1 -proc stride*(format: TextureFormat): int = - case format: - of SRGB_u8: 3 - of SRGB_Alpha_u8: 4 - of R_u8: 1 - of RG_u8: 2 - of RGB_u8: 3 - of RGBA_u8: 4 - 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 - of RGBA_f32: 16 - of Depth_u16: 2 - of Depth_u24: 3 # TODO: are you sure? - # of Depth_u24_s8: 4 - of Depth_f32: 4 - -proc channel_count*(format: TextureFormat): int = - case format: - 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 = case tex_type: of Tex2D: "sampler2D" @@ -409,82 +367,6 @@ 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" - let layer_stride = self.width * self.height * self.format.stride - var multilayer_buffer: ArrRef[byte] - assert self.depth == pointers.len - if self.depth > 1: - multilayer_buffer = newArrRef[byte](layer_stride * self.depth) - var pos = 0 - for (p, len) in pointers: - 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" - buffer = pixels.to byte - pixels_ptr = buffer[0].addr - pixels_len = buffer.len - else: - 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: - swap_lines(pixels_ptr, self.width * self.format.stride, self.height) - if self.depth == 1: - self.loadFromPixels pixels_ptr - return - copyMem(multilayer_buffer[pos].addr, pixels_ptr, layer_stride) - pos += layer_stride - self.loadFromPixels multilayer_buffer[0].addr - proc setFilter*(self: Texture, filter: TextureFilter) = self.filter = filter self.engine.renderer.enqueue proc()= @@ -548,27 +430,6 @@ proc generateMipmap*(self: Texture) = self.bind_it(needs_active_texture=true) glGenerateMipmap(self.storage.target) -proc getDimensionsFormat(p: pointer, len: int): (int, int, TextureFormat) = - when not defined(nimdoc): - 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 - return (width, height, format) - func to_sRGB*(format: TextureFormat): TextureFormat = return case format: of RGBA_u8: SRGB_Alpha_u8 @@ -582,7 +443,7 @@ proc newTexture*(engine: MyouEngine, name: string, p: pointer, len: int, 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.loadFileFromPointersLen(@[(p, len)], loadFromPixels, flip=flip) self.loaded = true return self @@ -633,7 +494,7 @@ proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: b engine.renderer.enqueue proc()= try: self.ensure_storage() - self.loadFileFromPointersLen(@[(p, len)]) + self.loadFileFromPointersLen(@[(p, len)], loadFromPixels) self.loaded = true except: # TODO: use logging @@ -657,27 +518,6 @@ proc setExtrapolation*(self: Texture, ext: TextureExtrapolation) = glTexParameteri(self.storage.target, GL_TEXTURE_WRAP_T, e.GLint) glTexParameteri(self.storage.target, GL_TEXTURE_WRAP_R, e.GLint) -proc swap_lines(p: pointer, line_stride, line_count: int) = - 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) - let int_stride_bytes = int_stride * sizeof(int) - var p1 = p - var p2 = p + line_stride*(line_count-1) - var a1, a2: ptr UncheckedArray[int] - var b1, b2: ptr UncheckedArray[byte] - for i in 0 ..< line_count div 2: - a1 = cast[ptr UncheckedArray[int]](p1) - a2 = cast[ptr UncheckedArray[int]](p2) - b1 = cast[ptr UncheckedArray[byte]](p1) - b2 = cast[ptr UncheckedArray[byte]](p2) - for j in 0 ..< int_stride: - swap(a1[j], a2[j]) - for j in int_stride_bytes ..< line_stride: - swap(b1[j], b2[j]) - p1 = p1 + line_stride - p2 = p2 - line_stride - proc getTexturePixels*(self: Texture): TexturePixels = when false: var len_bytes, cube_stride = self.width * self.height * self.format.stride