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; }; };