diff options
Diffstat (limited to 'textures.ha')
-rw-r--r-- | textures.ha | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/textures.ha b/textures.ha new file mode 100644 index 0000000..4045c85 --- /dev/null +++ b/textures.ha @@ -0,0 +1,375 @@ +use bufio; +use gl::*; +use io; +use os; +use image::png; +use math; +use strings; +use trace; + +type Texture = struct { + Object, + width: u32, + height: u32, + pack: nullable *Pack, + gl_texture: uint, +}; + +let TEXTURES = OBJREG_EMPTY; +let TEXTURES_MAX_WIDTH = 0u16; + +fn textures_find(name: str) nullable *Texture = + objreg_find(&TEXTURES, name): nullable *Texture; + +fn textures_register(tex: *Texture) void = + objreg_register(&TEXTURES, tex); + +type TexturesIterator = struct { + inner: ObjectRegistryIterator, +}; + +fn textures_iter() TexturesIterator = + TexturesIterator { + inner = objreg_iter(&TEXTURES), + }; + +fn textures_next(it: *TexturesIterator) nullable *Texture = + objreg_next(it): nullable *Texture; + +fn load_textures(assets: *Pack) void = { + const tr = &trace::root; + trace::info(tr, "loading textures..."); + + let max_width = 0i32; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_width); + assert(max_width > 0); + // highest power of 2 representable in 16 bits + if (max_width > 1 << 15) { + max_width = 1 << 15; + }; + TEXTURES_MAX_WIDTH = max_width: u16; + + textures_register_missingno(MISSINGNO); + + const results = resource_search(assets, "textures", ".png"); + for (let i = 0z; i < len(results); i += 1) { + const (ident, ext) = results[i]; + defer free(ident); + const tr = trace::ctx(tr, "load texture info for {}", ident); + + // TODO: better error handling, obviously + match (load_texture_info(assets, ident, &tr)) { + case void => void; + case trace::failed => + textures_register_missingno(ident); + }; + }; + + let ntextures = 0z; + let npixels = 0z; + let it = textures_iter(); + for (true) match (textures_next(&it)) { + case let tex: *Texture => + ntextures += 1; + npixels += tex.width * tex.height; + case null => break; + }; + trace::debug(tr, "loaded {} textures / {} pixels / {} bytes", + ntextures, npixels, npixels * 4); +}; + +fn textures_register_missingno(name: str) void = { + textures_register(alloc(Texture { + name = strings::dup(name), + width = 16, + height = 16, + pack = null, + gl_texture = 0, + })); +}; + +fn load_texture_info( + assets: *Pack, + ident: str, + tr: *trace::tracer, +) (void | trace::failed) = { + const f = resource_open(assets, "textures", ident, ".png", tr)?; + let fclosed = false; + defer if (!fclosed) io::close(f): void; + + const reader = match (png::newreader(f)) { + case let reader: png::reader => + yield reader; + case let err: png::error => + return trace::error(tr, "png error: {}", png::strerror(err)); + }; + + match (png::nextchunk(&reader)) { + case let ctype: u32 => + if (ctype != png::IHDR) { + return trace::error(tr, "Expected IHDR"); + }; + case io::EOF => + return trace::error(tr, "Expected IHDR"); + case let err: png::error => + return trace::error(tr, "png error: {}", png::strerror(err)); + }; + + const ihdr = match (png::ihdr_read(&reader)) { + case let ihdr: png::ihdr => + yield ihdr; + case let err: png::error => + return trace::error(tr, "png error: {}", png::strerror(err)); + }; + + const res = io::close(f); + fclosed = true; + match (res) { + case void => void; + case let err: io::error => + return trace::error(tr, "close: {}", io::strerror(err)); + }; + + match (textures_find(ident)) { + case let tex: *Texture => + abort("todo stacked assets"); + case null => + textures_register(alloc(Texture { + name = strings::dup(ident), + width = ihdr.width, + height = ihdr.height, + pack = assets, + gl_texture = 0, + })); + }; +}; + +fn finish_textures() void = { + let it = textures_iter(); + for (true) match (textures_next(&it)) { + case let tex: *Texture => + free(tex.name); + case null => break; + }; + objreg_clear(&TEXTURES); +}; + +fn texture_upload(tex: *Texture, tr: *trace::tracer) uint = { + const tr = trace::ctx(tr, "upload texture {}", tex.name); + + if (tex.gl_texture != 0) { + return tex.gl_texture; + }; + + glGenTextures(1, &tex.gl_texture); + glBindTexture(GL_TEXTURE_2D, tex.gl_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_NEAREST_MIPMAP_LINEAR: i32); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + GL_NEAREST: i32); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4); + const po2width = 1 << math::bit_size_u32(tex.width - 1): i32; + const po2height = 1 << math::bit_size_u32(tex.height - 1): i32; + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8: i32, + po2width, po2height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, null); + + let pbo = 0u; + glGenBuffers(1, &pbo); + defer glDeleteBuffers(1, &pbo); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); + + const bufsize = tex.width * tex.height * 4; + + for (true) { + glBufferData(GL_PIXEL_UNPACK_BUFFER, + bufsize: uintptr, null, GL_STREAM_DRAW); + const pixels = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, + 0, bufsize: uintptr, GL_MAP_WRITE_BIT) as *opaque; + texture_load(tex, pixels: *[*]u8, tex.width * 4, &tr); + if (glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER) == GL_TRUE) { + break; + }; + trace::warn(&tr, + "glUnmapBuffer returned false; retrying upload"); + }; + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + tex.width: i32, tex.height: i32, + GL_RGBA, GL_UNSIGNED_BYTE, null); + glGenerateMipmap(GL_TEXTURE_2D); + + return tex.gl_texture; +}; + +fn texture_load(tex: *Texture, out: *[*]u8, stride: size, tr: *trace::tracer) void = { + match (texture_try_load(tex, out, stride, tr)) { + case void => void; + case trace::failed => + texture_load_missingno(tex.width, tex.height, out, stride); + }; +}; + +fn texture_try_load( + tex: *Texture, + out: *[*]u8, + stride: size, + tr: *trace::tracer, +) (void | trace::failed) = { + const assets = match (tex.pack) { + case let assets: *Pack => + yield assets; + case null => + return trace::failed; + }; + + const f = resource_open(assets, "textures", tex.name, ".png", tr)?; + let fclosed = false; + defer if (!fclosed) io::close(f): void; + + let rbuf: [os::BUFSZ]u8 = [0...]; + let buf = bufio::init(f, rbuf, []); + + // TODO: write directly to output buffer. + const png = match (png::load(&buf)) { + case let image: png::image => + yield image; + case let err: png::error => + return trace::error(tr, "png::load: {}", png::strerror(err)); + }; + defer png::image_finish(&png); + + const width = png.ihdr.width; + const height = png.ihdr.height; + + if (width != tex.width || height != tex.height) { + return trace::error(tr, "Header changed while loading"); + }; + + const end = stride * height; + const rowpad = stride - (width << 2); + const irowpad = 8 - width * png.ihdr.bitdepth & 7; + + switch (png.ihdr.colortype) { + case png::colortype::GRAYSCALE => + if (png.ihdr.bitdepth != 8) { + return trace::error(tr, + "Bit depths other than 8 are not supported"); + }; + let i = 0z; + let o = 0z; + for (o < end) { + const rowend = o + (width << 2); + for (o < rowend) { + // TODO: out[o + n] (...) o += 4; might actually + // be faster due to pipelining weirdness... + const v = png.pixels[i]; + i += 1; + out[o] = v; o += 1; + out[o] = v; o += 1; + out[o] = v; o += 1; + out[o] = 255; o += 1; + }; + o += rowpad; + }; + case png::colortype::RGB => + if (png.ihdr.bitdepth != 8) { + return trace::error(tr, + "Bit depths other than 8 are not supported"); + }; + let i = 0z; + let o = 0z; + for (o < end) { + const rowend = o + (width << 2); + for (o < rowend) { + out[o] = png.pixels[i]; i += 1; o += 1; + out[o] = png.pixels[i]; i += 1; o += 1; + out[o] = png.pixels[i]; i += 1; o += 1; + out[o] = 255; o += 1; + }; + o += rowpad; + }; + case png::colortype::PLTE => + const mask = (1 << png.ihdr.bitdepth) - 1; + let i = 0z; + let o = 0z; + for (o < end) { + const rowend = o + (width << 2); + for (o < rowend) { + const index = png.pixels[i >> 3] + >> (i & 7) & mask; + const p = png.palette[index]; + i += png.ihdr.bitdepth; + out[o] = (p >> 24): u8; o += 1; + out[o] = (p >> 16): u8; o += 1; + out[o] = (p >> 8): u8; o += 1; + out[o] = 255; o += 1; + }; + i += irowpad; + o += rowpad; + }; + case png::colortype::GRAYALPHA => + if (png.ihdr.bitdepth != 8) { + return trace::error(tr, + "Bit depths other than 8 are not supported"); + }; + let i = 0z; + let o = 0z; + for (o < end) { + const rowend = o + (width << 2); + for (o < rowend) { + const v = png.pixels[i]; + i += 1; + out[o] = v; o += 1; + out[o] = v; o += 1; + out[o] = v; o += 1; + out[o] = png.pixels[i]; i += 1; o += 1; + }; + o += rowpad; + }; + case png::colortype::RGBA => + if (png.ihdr.bitdepth != 8) { + return trace::error(tr, + "Bit depths other than 8 are not supported"); + }; + let i = 0z; + let o = 0z; + for (o < end) { + out[o..o + (width << 2)] = + png.pixels[i..i + (width << 2)]; + i += width << 2; + o += stride; + }; + case => + return trace::error(tr, "Unknown color type {}", + png.ihdr.colortype: u8); + }; + + const res = io::close(f); + fclosed = true; + match (res) { + case void => void; + case let err: io::error => + return trace::error(tr, "close: {}", io::strerror(err)); + }; + + return; +}; + +fn texture_load_missingno(width: u32, height: u32, out: *[*]u8, stride: size) + void = { + for (let y = 0u32; y < height; y += 1) + for (let x = 0u32; x < width; x += 1) { + const i = (x: size << 2) + y * stride; + if (x < width >> 1 == y < height >> 1) { + out[i] = 0; + out[i + 1] = 0; + out[i + 2] = 0; + } else { + out[i] = 255; + out[i + 1] = 0; + out[i + 2] = 255; + }; + out[i + 3] = 255; + }; +}; |