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