Browse Source

implement naive (half-broken) ETC1 compression for RPi, fix #2

Ivan Avdeev 6 years ago
parent
commit
7e2b7f6992
8 changed files with 227 additions and 27 deletions
  1. 1 0
      Makefile
  2. 1 1
      src/OpenSource.c
  3. 1 1
      src/bsp.c
  4. 98 0
      src/etcpack.c
  5. 8 0
      src/etcpack.h
  6. 64 21
      src/render.c
  7. 5 2
      src/render.h
  8. 49 2
      src/texture.c

+ 1 - 0
Makefile

@@ -41,6 +41,7 @@ ifeq ($(RASPBERRY), 1)
 	LIBS += -lGLESv2 -lEGL -lbcm_host -lvcos -lvchiq_arm -L$(RPI_VCDIR)/lib -lrt -lm
 
 	SOURCES += \
+		src/etcpack.c \
 		src/atto/src/app_linux.c \
 		src/atto/src/app_rpi.c
 

+ 1 - 1
src/OpenSource.c

@@ -691,7 +691,7 @@ void attoAppInit(struct AAppProctable *proctable) {
 			g.collection_chain = addToCollectionChain(g.collection_chain, collectionCreateFilesystem(&mem, value));
 		} else if (strcmp(argv, "-n") == 0) {
 			if (i == a_app_state->argc - 1) {
-				aAppDebugPrintf("-p requires an argument");
+				aAppDebugPrintf("-n requires an argument");
 				goto print_usage_and_exit;
 			}
 			const char *value = a_app_state->argv[++i];

+ 1 - 1
src/bsp.c

@@ -357,7 +357,7 @@ static enum BSPLoadResult bspLoadModelLightmaps(struct LoadModelContext *ctx) {
 	upload.height = atlas_context.height;
 	upload.format = RTexFormat_RGB565;
 	upload.pixels = pixels;
-	upload.generate_mipmaps = 0;
+	upload.mip_level = -2;
 	upload.type = RTexType_2D;
 	upload.wrap = RTexWrap_Clamp;
 	renderTextureInit(&ctx->lightmap.texture);

+ 98 - 0
src/etcpack.c

@@ -0,0 +1,98 @@
+#include "etcpack.h"
+#include "libc.h"
+
+static const int etc1_mod_table[8][4] = {
+	{2, 8, -2, -8},
+	{5, 17, -5, -17},
+	{9, 29, -9, -29},
+	{13, 42, -13, -42},
+	{18, 60, -18, -60},
+	{24, 80, -24, -80},
+	{33, 106, -33, -106},
+	{47, 183, -47, -183},
+};
+
+typedef struct {
+	ETC1Color base;
+	int table;
+	int error;
+	uint8_t msb, lsb;
+} ETC1SubblockPacked;
+
+static int clamp8(int i) { return (i < 0) ? 0 : (i > 255) ? 255 : i; }
+
+#define ETC1_PIXEL_ERROR_MAX 1000
+static int etc1PixelError(ETC1Color a, ETC1Color b) {
+	return abs(a.r - b.r) + abs(a.g - b.g) + abs(a.b - b.b);
+}
+
+static ETC1SubblockPacked etc1PackSubblock2x4(const ETC1Color *in4x2) {
+	ETC1Color average = {.r = 0, .g = 0, .b = 0};
+
+	for (int i = 0; i < 8; ++i) {
+		average.r += in4x2[i].r;
+		average.g += in4x2[i].g;
+		average.b += in4x2[i].b;
+	}
+
+	average.r >>= 3;
+	average.g >>= 3;
+	average.b >>= 3;
+
+	ETC1SubblockPacked packed = {
+		.error = ETC1_PIXEL_ERROR_MAX * 8,
+	};
+	for (int itbl = 0; itbl < 8; ++itbl) {
+		const int *const pmod = etc1_mod_table[itbl];
+		ETC1SubblockPacked variant = {
+			.base = average,
+			.table = itbl,
+			.error = 0,
+			.msb = 0, .lsb = 0,
+		};
+		for (int ip = 0; ip < 8; ++ip) {
+			const ETC1Color c = in4x2[ip];
+			int best_pixel_error = ETC1_PIXEL_ERROR_MAX;
+			int best_pixel_imod = 0;
+			for (int im = 0; im < 4; ++im) {
+				const int mod = pmod[im];
+				const ETC1Color mc = {
+					.r = clamp8(variant.base.r + mod),
+					.g = clamp8(variant.base.g + mod),
+					.b = clamp8(variant.base.b + mod)
+				};
+				const int perr = etc1PixelError(c, mc);
+
+				if (perr < best_pixel_error) {
+					best_pixel_error = perr;
+					best_pixel_imod = im;
+				}
+			}
+
+			variant.lsb >>= 1;
+			variant.msb >>= 1;
+			variant.lsb |= (best_pixel_imod & 1) << 7; 
+			variant.msb |= (best_pixel_imod & 2) << 7; 
+
+			variant.error += best_pixel_error;
+		}
+
+		if (variant.error < packed.error)
+			packed = variant;
+	}
+	return packed;
+}
+
+void etc1PackBlock(const ETC1Color *in4x4, uint8_t *out) {
+	const ETC1SubblockPacked sub1 = etc1PackSubblock2x4(in4x4);
+	const ETC1SubblockPacked sub2 = etc1PackSubblock2x4(in4x4 + 8);
+
+	out[0] = (sub1.base.r & 0xf0) | (sub2.base.r >> 4);
+	out[1] = (sub1.base.g & 0xf0) | (sub2.base.g >> 4);
+	out[2] = (sub1.base.b & 0xf0) | (sub2.base.b >> 4);
+	out[3] = (sub1.table << 5) | (sub2.table << 2) | 0x00; // diffbit = 0, flipbit = 0
+	out[4] = sub2.msb;
+	out[5] = sub1.msb;
+	out[6] = sub2.lsb;
+	out[7] = sub1.lsb;
+}

+ 8 - 0
src/etcpack.h

@@ -0,0 +1,8 @@
+#pragma once
+
+#include <stdint.h>
+
+typedef struct { int r, g, b; } ETC1Color;
+
+// in4x4 layout is column-major
+void etc1PackBlock(const ETC1Color *in4x4, uint8_t *out);

+ 64 - 21
src/render.c

@@ -58,10 +58,8 @@
 #define RENDER_GL_PROFILE_FUNC(...)
 #endif
 
-#if 0 //ndef RENDER_GL_DEBUG
-#define GL_CALL(f) (f)
-#else
-#if 0
+//#define RENDER_GL_DEBUG
+#ifdef RENDER_GL_DEBUG
 static void a__GlPrintError(const char *message, int error) {
 	const char *errstr = "UNKNOWN";
 	switch (error) {
@@ -104,7 +102,6 @@ static void a__GlPrintError(const char *message, int error) {
 		RENDER_GL_PROFILE_FUNC(#f, aAppTime() - profile_time_start__); \
 		RENDER_GL_GETERROR(f) \
 	} while(0)
-#endif /* RENDER_GL_DEBUG */
 
 #ifdef _WIN32
 #define WGL__FUNCLIST \
@@ -143,6 +140,20 @@ WGL__FUNCLIST
 #undef WGL__FUNCLIST_DO
 #endif /* ifdef _WIN32 */
 
+static struct {
+	int textures_count;
+	int textures_size;
+	int buffers_count;
+	int buffers_size;
+} stats;
+
+static void renderPrintMemUsage() {
+	PRINTF("Render Tc: %u, Ts: %uMiB, Bc: %u, Bs: %uMiB, Total: %uMiB",
+		stats.textures_count, stats.textures_size >> 20,
+		stats.buffers_count, stats.buffers_size >> 20,
+		(stats.buffers_size + stats.textures_size) >> 20);
+}
+
 static GLint render_ShaderCreate(GLenum type, const char *sources[]) {
 	int n;
 	GLuint shader = glCreateShader(type);
@@ -170,23 +181,18 @@ static GLint render_ShaderCreate(GLenum type, const char *sources[]) {
 }
 
 void renderTextureUpload(RTexture *texture, RTextureUploadParams params) {
+	int pixel_bits = 0;
 	GLenum internal, format, type;
 
 	if (texture->gl_name == -1) {
 		GL_CALL(glGenTextures(1, (GLuint*)&texture->gl_name));
 		texture->type_flags = 0;
-	}
-
-	switch (params.format) {
-		case RTexFormat_RGB565:
-			internal = format = GL_RGB; type = GL_UNSIGNED_SHORT_5_6_5; break;
-		default:
-			ATTO_ASSERT(!"Impossible texture format");
+		++stats.textures_count;
 	}
 
 	const GLenum binding = (params.type == RTexType_2D) ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP;
 	const GLint wrap = (params.type == RTexType_2D && params.wrap == RTexWrap_Repeat)
-		? GL_REPEAT : GL_CLAMP;
+		? GL_REPEAT : GL_CLAMP_TO_EDGE;
 
 	GL_CALL(glBindTexture(binding, texture->gl_name));
 
@@ -201,20 +207,50 @@ void renderTextureUpload(RTexture *texture, RTextureUploadParams params) {
 		case RTexType_CubeNZ: upload_binding = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; break;
 	}
 
-	GL_CALL(glTexImage2D(upload_binding, 0, internal, params.width, params.height, 0,
-			format, type, params.pixels));
+	int compressed = 0;
+	switch (params.format) {
+		case RTexFormat_RGB565:
+			pixel_bits = 16;
+			internal = format = GL_RGB; type = GL_UNSIGNED_SHORT_5_6_5;
+			break;
+#ifdef ATTO_PLATFORM_RPI
+		case RTexFormat_Compressed_ETC1:
+			pixel_bits = 4;
+			internal = GL_ETC1_RGB8_OES;
+			compressed = 1;
+			break;
+#endif
+		default:
+			ATTO_ASSERT(!"Impossible texture format");
+	}
+
+	const int image_size = params.width * params.height * pixel_bits / 8;
+
+	if (!compressed) {
+		GL_CALL(glTexImage2D(upload_binding, params.mip_level < 0 ? 0 : params.mip_level, internal, params.width, params.height, 0,
+				format, type, params.pixels));
+	} else {
+		GL_CALL(glCompressedTexImage2D(upload_binding, params.mip_level < 0 ? 0 : params.mip_level, internal, params.width, params.height,
+					0, image_size, params.pixels));
+	}
+
+	stats.textures_size += image_size;
+	renderPrintMemUsage();
 
-	if (params.generate_mipmaps)
+	if (params.mip_level == -1)
 		GL_CALL(glGenerateMipmap(binding));
 
-	GL_CALL(glTexParameteri(binding, GL_TEXTURE_MIN_FILTER, params.generate_mipmaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR));
+	GL_CALL(glTexParameteri(binding, GL_TEXTURE_MIN_FILTER, params.mip_level >= -1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR));
 	GL_CALL(glTexParameteri(binding, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
 
 	GL_CALL(glTexParameteri(binding, GL_TEXTURE_WRAP_S, wrap));
 	GL_CALL(glTexParameteri(binding, GL_TEXTURE_WRAP_T, wrap));
 
-	texture->width = params.width;
-	texture->height = params.height;
+	if (params.mip_level < 1) {
+		texture->width = params.width;
+		texture->height = params.height;
+	}
+
 	texture->format = params.format;
 	texture->type_flags |= params.type;
 }
@@ -226,8 +262,12 @@ void renderBufferCreate(RBuffer *buffer, RBufferType type, int size, const void
 	default: ASSERT(!"Invalid buffer type");
 	}
 	GL_CALL(glGenBuffers(1, (GLuint*)&buffer->gl_name));
+	++stats.buffers_count;
+
 	GL_CALL(glBindBuffer(buffer->type, (GLuint)buffer->gl_name));
 	GL_CALL(glBufferData(buffer->type, size, data, GL_STATIC_DRAW));
+	stats.buffers_size += size;
+	renderPrintMemUsage();
 }
 
 typedef struct {
@@ -540,6 +580,7 @@ static int render_ProgramInit(RProgram *prog) {
 }
 
 int renderInit() {
+	PRINTF("GL extensions: %s", glGetString(GL_EXTENSIONS));
 #ifdef _WIN32
 #define WGL__FUNCLIST_DO(T, N) \
 	gl##N = (T)wglGetProcAddress("gl" #N); \
@@ -549,6 +590,8 @@ int renderInit() {
 #undef WGL__FUNCLIST_DO
 #endif
 
+	memset(&stats, 0, sizeof(stats));
+
 	r.current_program = NULL;
 	r.current_tex0 = NULL;
 	r.uniforms.mvp = NULL;
@@ -567,7 +610,7 @@ int renderInit() {
 	params.width = 2;
 	params.height = 2;
 	params.pixels = (uint16_t[]){0xffffu, 0, 0, 0xffffu};
-	params.generate_mipmaps = 0;
+	params.mip_level = -2;
 	params.wrap = RTexWrap_Clamp;
 	renderTextureInit(&default_texture.texture);
 	renderTextureUpload(&default_texture.texture, params);
@@ -694,7 +737,7 @@ void renderModelDraw(const RDrawParams *params, const struct BSPModel *model) {
 		GL_CALL(glBlendEquation(GL_FUNC_ADD));
 	}
 
-	if (distance < 5000.f)
+	if (distance < 0.f)
 		renderDrawSet(model, &model->detailed);
 	else
 		renderDrawSet(model, &model->coarse);

+ 5 - 2
src/render.h

@@ -2,7 +2,10 @@
 #include "atto/math.h"
 
 typedef enum {
-	RTexFormat_RGB565
+	RTexFormat_RGB565,
+#ifdef ATTO_PLATFORM_RPI
+	RTexFormat_Compressed_ETC1,
+#endif
 } RTexFormat;
 
 typedef enum {
@@ -32,7 +35,7 @@ typedef struct {
 	int width, height;
 	RTexFormat format;
 	const void *pixels;
-	int generate_mipmaps;
+	int mip_level; // -1 means generate; -2 means don't need
 	RTexWrap wrap;
 } RTextureUploadParams;
 

+ 49 - 2
src/texture.c

@@ -1,4 +1,5 @@
 #include "texture.h"
+#include "etcpack.h"
 #include "dxt.h"
 #include "vtf.h"
 #include "cache.h"
@@ -223,6 +224,46 @@ static int textureUploadMipmapType(struct Stack *tmp, struct IFile *file, size_t
 		return 0;
 	}
 
+#ifdef ATTO_PLATFORM_RPI
+	{
+		const uint16_t *p565 = dst_texture;
+		uint8_t *etc1_data = stackAlloc(tmp, hdr->width * hdr->height / 2);
+		uint8_t *block = etc1_data;
+
+		// FIXME assumes w and h % 4 == 0
+		for (int by = 0; by < hdr->height; by += 4) {
+			for (int bx = 0; bx < hdr->width; bx += 4) {
+				const uint16_t *bp = p565 + bx + by * hdr->width;
+				ETC1Color ec[16];
+				for (int x = 0; x < 4; ++x) {
+					for (int y = 0; y < 4; ++y) {
+						const unsigned p = bp[x + y * hdr->width];
+						ec[x*4+y].r = (p & 0xf800u) >> 8;
+						ec[x*4+y].g = (p & 0x07e0u) >> 3;
+						ec[x*4+y].b = (p & 0x001fu) << 3;
+					}
+				}
+				
+				etc1PackBlock(ec, block);
+				block += 8;
+			}
+		}
+
+		const RTextureUploadParams params = {
+			.type = tex_type,
+			.width = hdr->width,
+			.height = hdr->height,
+			.format = RTexFormat_Compressed_ETC1,
+			.pixels = etc1_data,
+			.mip_level = -2,//miplevel,
+			.wrap =  RTexWrap_Repeat
+		};
+
+		renderTextureUpload(tex, params);
+
+		stackFreeUpToPosition(tmp, etc1_data);
+	}
+#else
 
 	const RTextureUploadParams params = {
 		.type = tex_type,
@@ -230,11 +271,13 @@ static int textureUploadMipmapType(struct Stack *tmp, struct IFile *file, size_t
 		.height = hdr->height,
 		.format = RTexFormat_RGB565,
 		.pixels = dst_texture,
-		.generate_mipmaps = 1,
+		.mip_level = -1,//miplevel,
 		.wrap =  RTexWrap_Repeat
 	};
 
 	renderTextureUpload(tex, params);
+#endif
+
 	return 1;
 }
 
@@ -305,7 +348,11 @@ static int textureLoad(struct IFile *file, Texture *tex, struct Stack *tmp, RTex
 		hdr.lores_width, hdr.lores_height, vtfFormatStr(hdr.lores_format), hdr.mipmap_count, hdr.header_size);
 	*/
 
-	retval = textureUploadMipmapType(tmp, file, cursor, &hdr, 0, &tex->texture, type);
+	for (int mip = 0; mip <= 0/*< hdr.mipmap_count*/; ++mip) {
+		retval = textureUploadMipmapType(tmp, file, cursor, &hdr, mip, &tex->texture, type);
+		if (retval != 1)
+			break;
+	}
 	stackFreeUpToPosition(tmp, pre_alloc_cursor);
 
 	return retval;