summaryrefslogtreecommitdiff
path: root/textures.ha
diff options
context:
space:
mode:
Diffstat (limited to 'textures.ha')
-rw-r--r--textures.ha375
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;
+ };
+};