code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <GL/glew.h>

#include "../c3dlas/c3dlas.h"
#include "text.h"
#include "fcfg.h"
#include "../EACSMB/src/utilities.h"
#include "../dumpImage.h"

FT_Library ftLib = NULL;

static void makeVertices(TextRenderInfo* tri, unsigned int* colors);

/*
A Note About Charsets:

All characters specified will be packed into a texture then shoved into video ram.
There is a limit to the size of textures. A large font size and large character
set may not fit in the largest texture available. Even if it does, precious vram
is used for every character added. This is not the place to be a unicode twat;
specify only the characters you will actually use. And for God's sake, don't try
to render traditional Chinese at 64pt... you'd use 80+mb of ram.

Also note the kerning information is n^2 in size with respect to strlen(charset).
Currently this information is in system memory but eventually most operations
will be moved to the gpu. Geometry shaders are amazing.

*/

// all keys on a standard US keyboard.
char* defaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 `~!@#$%^&*()_+|-=\\{}[]:;<>?,./'\"";

// 16.16 fixed point to float conversion
static float f2f(int i) {
	return (float)(i >> 6);
}

static void blit(
	int src_x, int src_y, int dst_x, int dst_y, int w, int h,
	int src_w, int dst_w, unsigned char* src, unsigned char* dst) {

	int y, x, s, d;

	// this may be upside down...
	for(y = 0; y < h; y++) {
		for(x = 0; x < w; x++) {
			s = ((y + src_y) * src_w) + src_x + x;
			d = ((y + dst_y) * dst_w) + dst_x + x;

			dst[d] = src[s];
		}
	}
}

FontInfo* LoadFontInfo(char* fontName) {
	FT_Error err;
	FontInfo* fi;
	char* fontPath;

	if(!ftLib) {
		err = FT_Init_FreeType(&ftLib);
		if(err) {
			fprintf(stderr, "Could not initialize FreeType library.\n");
			return NULL;
		}
	}

	fontPath = getFontFile(fontName);
	if(!fontPath) {
		fprintf(stderr, "Could not load font '%s'\n", fontName);
		return NULL;
	}

	fi = calloc(1, sizeof(FontInfo));
	// if you're out of memory you have bigger problems than error checking...

	err = FT_New_Face(ftLib, fontPath, 0, &fi->fontFace);
	if(err) {
		fprintf(stderr, "Could not load font file \"%s\".\n", fontPath);
		free(fi);
		return NULL;
	}

	return fi;
}

TextRes* LoadFont(char* fontName, int size, char* chars) {

	FT_Error err;
	FT_GlyphSlot slot;
	FT_Face fontFace;
	TextRes* res;
	int i, j, charlen, width, h_above, h_below, height, padding, xoffset;
	char* fontPath;

	padding = 2;

	res = calloc(1, sizeof(TextRes));

	res->fontInfo = LoadFontInfo(fontName);

	fontFace = res->fontInfo->fontFace;

	err = FT_Set_Pixel_Sizes(fontFace, 0, size);
	if(err) {
		fprintf(stderr, "Could not set pixel size to %dpx.\n", size);
		free(res->fontInfo);
		free(res);
		return NULL;
	}

	// slot is a pointer
	slot = fontFace->glyph;

	if(!chars) chars = defaultCharset;
	res->charSet = strdup(chars);

	charlen = strlen(chars);
	res->charLen = charlen;

	h_above = 0;
	h_below = 0;
	width = 0;
	height = 0;

	// first find how wide of a texture we need
	for(i = 0; i < charlen; i++) {
		int ymin;
		err = FT_Load_Char(fontFace, chars[i], FT_LOAD_DEFAULT);

		ymin = slot->metrics.height >> 6;// - f2f(slot->metrics.horiBearingY);
/*
		printf("%c-----\nymin: %d \n", chars[i], ymin);
		printf("bearingY: %d \n", slot->metrics.horiBearingY >> 6);
		printf("width: %d \n", slot->metrics.width >> 6);
		printf("height: %d \n\n", slot->metrics.height >> 6);
*/

		width += f2f(slot->metrics.width);
		h_above = MAX(h_above, (unsigned)slot->metrics.horiBearingY >> 6);
		h_below = MAX(h_below, ymin);
	}

	width += charlen * padding;
	height = h_below + padding + padding;

	res->maxHeight = height;
	res->padding = padding / 2;

	//TODO: clean up this messy function
//	printf("width: %d, height: %d \n", width, height);

	width = nextPOT(width);
	height = nextPOT(height);
	res->texWidth = width;
	res->texHeight = height; // may not always just be one row

	res->texture = calloc(width * height * sizeof(*res->texture), 1);
	res->offsets = calloc(charlen * sizeof(*res->offsets), 1);
	res->charWidths = calloc(charlen * sizeof(*res->charWidths), 1);
	res->valign = calloc(charlen * sizeof(*res->valign), 1);

	// construct code mapping
	// shitty method for now, improve later
	res->indexLen = 128; // 7 bits for now.
	res->codeIndex = (unsigned char*)calloc(res->indexLen, 1);

	// render the glyphs into the texture

	xoffset = 0;
	for(i = 0; i < charlen; i++) {
		int paddedw, charHeight, bearingY;

		err = FT_Load_Char(fontFace, chars[i], FT_LOAD_RENDER);

		paddedw = (slot->metrics.width >> 6) + padding;
		bearingY = (unsigned)slot->metrics.horiBearingY >> 6;
		charHeight = slot->metrics.height >> 6;

		res->charWidths[i] = paddedw + padding;

/*		printf("meh: %d\n", height - charHeight);
		printf("index: %d, char: %c, xoffset: %d, pitch: %d \n", i, chars[i], xoffset, slot->bitmap.pitch);
		printf("m.width: %d, m.height: %d, hbearing: %d, habove: %d \n\n", slot->metrics.width >> 6, slot->metrics.height >> 6, slot->metrics.horiBearingY >> 6, h_above);
*/		blit(
			0, 0, // src x and y offset for the image
			xoffset + padding, padding + (h_above - bearingY), // dst offset
			slot->metrics.width >> 6, slot->metrics.height >> 6, // width and height BUG probably
			slot->bitmap.pitch, width, // src and dst row widths
			slot->bitmap.buffer, // source
			res->texture); // destination

		res->codeIndex[chars[i]] = i;
		res->offsets[i] = xoffset;
		res->valign[i] = (height - h_above + bearingY - padding);// + (slot->metrics.horiBearingY >> 6);
		xoffset += paddedw;
	}

	// kerning map
	res->kerning = (unsigned char*)malloc(charlen * charlen);
	for(i = 0; i < charlen; i++) {
		FT_UInt left, right;
		FT_Vector k;

		left = FT_Get_Char_Index(fontFace, chars[i]);

		for(j = 0; j < charlen; j++) {

			right = FT_Get_Char_Index(fontFace, chars[j]);

			FT_Get_Kerning(fontFace, left, right, FT_KERNING_DEFAULT, &k);
		//	if(k.x != 0) printf("k: (%c%c) %d, %d\n", chars[i],chars[j], k.x, k.x >> 6);
			res->kerning[(i * charlen) + j] = k.x >> 6;
		}
	}

	//////////// opengl stuff ////////////

	// TODO: error checking
	glGenTextures(1, &res->textureID);
	glBindTexture(GL_TEXTURE_2D, res->textureID);
	glerr("bind font tex");

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); // no mipmaps for this; it'll get fucked up
	glerr("param font tex");

	printf("text width: %d, height: %d \n", width, height);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, res->texture);
	glerr("load font tex");

	glBindTexture(GL_TEXTURE_2D, 0);

	return res;
}

static int bmpsort(GlyphBitmap** a, GlyphBitmap** b) {
	return (*a)->h - (*b)->h;
}

// save a pre-rendered sdf font
void SaveSDFFont(char* path, TextRes* res) {
	FILE* f;
	unsigned short version = 1;
	uint16_t n;

	f = fopen(path, "wb");

	// write version
	fwrite(&version, 2, 1, f);

	// save font info

	// sizes
	fwrite(&res->maxWidth, 2, 1, f);
	fwrite(&res->maxHeight, 2, 1, f);
	fwrite(&res->texWidth, 2, 1, f);
	fwrite(&res->texHeight, 2, 1, f);
	fwrite(&res->padding, 2, 1, f);
	fwrite(&res->charLen, 2, 1, f);
	fwrite(&res->indexLen, 2, 1, f);

	//charset
	n = res->charLen;
	fwrite(res->charSet, 1, n, f);

	fwrite(res->codeIndex, 1, res->indexLen, f);
	fwrite(res->offsets, 2, n, f);
	fwrite(res->kerning, 1, n, f);
	fwrite(res->valign, 1, n * sizeof(*res->valign), f);
	fwrite(res->charWidths, 2, n, f);

	// the data
	fwrite(res->texture, 1, res->texWidth * res->texHeight, f);

	fclose(f);
}
// load a pre-rendered sdf font
TextRes* LoadSDFFont(char* path) {
	FILE* f;
	TextRes* res;
	unsigned short version;
	int n;

	f = fopen(path, "rb");
	if(!f) {
		return NULL;
	}

	res = calloc(1, sizeof(TextRes));

	fread(&version, 2, 1, f);

	// sizes
	fread(&res->maxWidth, 2, 1, f);
	fread(&res->maxHeight, 2, 1, f);
	fread(&res->texWidth, 2, 1, f);
	fread(&res->texHeight, 2, 1, f);
	fread(&res->padding, 2, 1, f);
	fread(&res->charLen, 2, 1, f);
	fread(&res->indexLen, 2, 1, f);

	//charset
	n = res->charLen;
	res->charSet = malloc(n);
	fread(res->charSet, 1, n, f);

	res->codeIndex = malloc(res->indexLen);
	fread(res->codeIndex, 1, res->indexLen, f);

	res->offsets = malloc(2 * n);
	fread(res->offsets, 2, n, f);

	res->kerning = malloc(n);
	fread(res->kerning, 1, n, f);

	res->valign = malloc(n  * sizeof(*res->valign));
	fread(res->valign, 1, n * sizeof(*res->valign), f);

	res->charWidths = malloc(2 * n);
	fread(res->charWidths, 2, n, f);

	// the data
	res->texture = malloc(res->texWidth * res->texHeight);
	fread(res->texture, 1, res->texWidth * res->texHeight, f);

	fclose(f);

	// set up the texture
	glGenTextures(1, &res->textureID);
	glBindTexture(GL_TEXTURE_2D, res->textureID);
	glerr("bind font tex");

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); // no mipmaps for this; it'll get fucked up
	glerr("param font tex");

	printf("text width: %d, height: %d \n", res->texWidth, res->texHeight);
// 	printf("text width 3: %d, height: %d \n", width, height);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, res->texWidth, res->texHeight, 0, GL_RED, GL_UNSIGNED_BYTE, res->texture);
	glerr("load font tex");

	glBindTexture(GL_TEXTURE_2D, 0);

	return res;
}

// generate a new sdf font
TextRes* GenerateSDFFont(char* fontName, int size, char* chars) {
	FT_Error err;
	FT_GlyphSlot slot;
	FT_Face fontFace;
	TextRes* res;
	int i, j, charlen, width, h_above, h_below, height, padding, xoffset, oversample;
	char* fontPath;

	padding = 0;

	res = calloc(1, sizeof(TextRes));

	res->fontInfo = LoadFontInfo(fontName);

	fontFace = res->fontInfo->fontFace;

	//debug:
	//size = 16;
	oversample = 8; // 16 is better

	err = FT_Set_Pixel_Sizes(fontFace, 0, size * oversample);
	if(err) {
		fprintf(stderr, "Could not set pixel size to %dpx.\n", size);
		free(res->fontInfo);
		free(res);
		return NULL;
	}

	// slot is a pointer
	slot = fontFace->glyph;

	if(!chars) chars = defaultCharset;
	res->charSet = strdup(chars);

	charlen = strlen(chars);
	res->charLen = charlen;

	h_above = 0;
	h_below = 0;
	width = 4*1024;
	height = 32;
	res->texWidth = width;
	res->texHeight = height; // may not always just be one row
	res->maxHeight = height;

	res->texture = malloc(width * height * sizeof(*res->texture));
	memset(res->texture, 0xff, width * height * sizeof(*res->texture));
	res->offsets = calloc(charlen * sizeof(*res->offsets), 1);
	res->charWidths = calloc(charlen * sizeof(*res->charWidths), 1);
	res->valign = calloc(charlen * sizeof(*res->valign), 1);

	GlyphBitmap* gbs = calloc(1, charlen * sizeof(*gbs));

	res->indexLen = 128; // 7 bits for now.
	res->codeIndex = calloc(res->indexLen * sizeof(*res->codeIndex), 1);

	int max_h_bearing = 0;
	int max_w = 0;
	int bearingY = 0;

	// get character dimensions
	for(i = 0; i < charlen; i++) {
		int ymin;
		err = FT_Load_Char(fontFace, chars[i], FT_LOAD_DEFAULT | FT_LOAD_MONOCHROME);

		ymin = slot->metrics.height >> 6;// - f2f(slot->metrics.horiBearingY);

//		printf("%c-----\nymin: %d \n", chars[i], ymin);
//		printf("bearingY: %d \n", slot->metrics.horiBearingY >> 6);
//		printf("width: %d \n", slot->metrics.width >> 6);
//		printf("height: %d \n\n", slot->metrics.height >> 6);

		// this is the opposite value. it will be adjusted in the next loop
		res->valign[i] =  ((unsigned)slot->metrics.horiBearingY >> 6);
		//res->charWidths[i] = (slot->metrics.width >> 6) / oversample;
		max_h_bearing = MAX(res->valign[i], max_h_bearing);
		//printf("char: %c yoff: %d \n", chars[i], (slot->metrics.horiBearingY >> 6) / oversample);
		//width += f2f(slot->metrics.width);
		h_above = MAX(h_above, slot->metrics.horiBearingY >> 6);
		h_below = MAX(h_below, ymin);

		printf("(%c) res->valign[i]: %d, slot->metrics.height: %d \n",
			chars[i], res->valign[i], slot->metrics.height >> 6);
	}

	int tex_width = nextPOT(width);

	// render SDF's
	for(i = 0; i < charlen; i++) {
		gbs[i].oversample = oversample;
		gbs[i].magnitude = 4;
		gbs[i].code = chars[i];
		DrawGlyph(res, &gbs[i]);
	//	printf("calculating sdf for %d (%c)... ", chars[i], chars[i]);

		//writePNG("sdfpct.png", 1, gbs[i].data, gbs[i].dw, gbs[i].dh);

		CalcSDF_Software(res, &gbs[i]);

		//writePNG("sdfcalced.png", 1, gbs[i].sdfData, gbs[i].sdfw, gbs[i].sdfh);

	//	printf("done\n");

		res->valign[i] = (max_h_bearing - res->valign[i]) / oversample;
		//exit(2);

	}

	// copy sdf's to atlas texture
	GlyphBitmap** charOrdering = malloc(charlen * sizeof(GlyphBitmap*));
	for(i = 0; i < charlen; i++) charOrdering[i] = &gbs[i];

	qsort(charOrdering, charlen, sizeof(GlyphBitmap*), bmpsort);

	int yoffset = 0;
	xoffset = 0;
	for(i = 0; i < charlen; i++) {
		blit(
			0, 0, // src x and y offset for the image
			xoffset + padding, padding + 0, // dst offset
			gbs[i].sdfw, gbs[i].sdfh, // width and height BUG probably
			gbs[i].sdfw, width, // src and dst row widths
			gbs[i].sdfData, // source
			res->texture); // destination

 		res->codeIndex[chars[i]] = i;
		res->charWidths[i] = gbs[i].sdfw;
		res->offsets[i] = xoffset;
		xoffset += gbs[i].sdfw;
	}

	writePNG("sdf.png", 1, res->texture, width, height);

	// kerning map
	res->kerning = (unsigned char*)malloc(charlen * charlen);
	for(i = 0; i < charlen; i++) {
		FT_UInt left, right;
		FT_Vector k;

		left = FT_Get_Char_Index(fontFace, chars[i]);

		for(j = 0; j < charlen; j++) {

			right = FT_Get_Char_Index(fontFace, chars[j]);

			FT_Get_Kerning(fontFace, left, right, FT_KERNING_DEFAULT, &k);
		//	if(k.x != 0) printf("k: (%c%c) %d, %d\n", chars[i],chars[j], k.x, k.x >> 6);
			res->kerning[(i * charlen) + j] = k.x >> 6;
		}
	}

	///////////////////////////////////////////////////////////////////

		// TODO: error checking
	glGenTextures(1, &res->textureID);
	glBindTexture(GL_TEXTURE_2D, res->textureID);
	glerr("bind font tex");

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); // no mipmaps for this; it'll get fucked up
	glerr("param font tex");

//	printf("text width: %d, height: %d \n", tex_width, height);
//	printf("text width 3: %d, height: %d \n", width, height);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, res->texture);
	glerr("load font tex");

	glBindTexture(GL_TEXTURE_2D, 0);

	free(gbs);

	return res;
}

int DrawGlyph(TextRes* res, GlyphBitmap* gb) {

	int padding;
	FT_GlyphSlot slot;

	padding = gb->oversample * gb->magnitude;

	// have freetype draw the character
	FT_Load_Char(res->fontInfo->fontFace, gb->code, FT_LOAD_RENDER);

	slot = res->fontInfo->fontFace->glyph;

	// this is the size of the original giant character
	gb->w = slot->metrics.width >> 6;
	gb->h = slot->metrics.height >> 6;

	gb->dw = gb->w + padding + padding;
	gb->dh = gb->h + padding + padding;
//
// 	gb->dw = gb->paddedw; //nextPOT(gb->paddedw);
// 	gb->dh = gb->paddedh; //nextPOT(gb->paddedh);
//	fprintf(stdout, "glyph bitmap size [%d, %d]\n", gb->dw, gb->dh);

	gb->data = calloc(1, gb->dw * gb->dh * sizeof(uint8_t));

/*		printf("meh: %d\n", height - charHeight);
		printf("index: %d, char: %c, xoffset: %d, pitch: %d \n", i, chars[i], xoffset, slot->bitmap.pitch);
		printf("m.width: %d, m.height: %d, hbearing: %d, habove: %d \n\n", slot->metrics.width >> 6, slot->metrics.height >> 6, slot->metrics.horiBearingY >> 6, h_above);
*/	blit(
		0, 0, // src x and y offset for the image
		padding, padding, // dst offset
		gb->w, gb->h, // width and height BUG probably
		slot->bitmap.pitch, gb->dw, // src and dst row widths
		slot->bitmap.buffer, // source
		gb->data); // destination

	return 0;
}

static float dist(int a, int b) {
	return a*a + b*b;
}
static float dmin(int a, int b, float d) {
	return fmin(dist(a, b), d);
}

static int boundedOffset(int x, int y, int ox, int oy, int w, int h) {
	int x1 = x + ox;
	int y1 = y + oy;
	if(x1 < 0 || y1 < 0 || x1 >= w || y1 >= h) return -1;
	return x1 + (w * y1);
}

static uint8_t sdfEncode(float d, int inside, float maxDist) {
	int o;
	d = sqrt(d);
	float norm = d / maxDist;
	if(inside) norm = -norm;

	o = (norm * 192) + 64;

	return o < 0 ? 0 : (o > 255 ? 255 : o);
}

void CalcSDF_Software(TextRes* res, GlyphBitmap* gb) {

	int searchSize;
	int x, y, ox, oy, sx, sy;
	int dw, dh;

	uint8_t* data;
	uint8_t* output;

	float d, maxDist;

	searchSize = gb->oversample * gb->magnitude;
	maxDist = 0.5 * searchSize;
	dw = gb->dw;
	dh = gb->dh;
	data = gb->data;

	// this is wrong
	gb->sdfw = (dw / (gb->oversample));
	gb->sdfh = (dh / (gb->oversample));

	gb->sdfData = output = calloc(1, gb->sdfw * gb->sdfh * sizeof(uint8_t));

	for(y = 0; y < gb->sdfh; y++) {
		for(x = 0; x < gb->sdfw; x++) {
			int sx = x * gb->oversample;
			int sy = y * gb->oversample;
			//printf(".");
			// value right under the center of the pixel, to determine if we are inside
			// or outside the glyph
			int v = data[sx + (sy * dw)];

			d = 999999.9;

			for(oy = -searchSize / 2; oy < searchSize; oy++) {
				for(ox = -searchSize / 2; ox < searchSize; ox++) {
					int off = boundedOffset(sx, sy, ox, oy, dw, dh);
					if(off >= 0 && data[off] != v)
						d = dmin(ox, oy, d);
				}
			}

			int q = sdfEncode(d, v, maxDist);
			if(q) {
// 				gb->sdfdims.left = MIN(gb->sdfdims.left, x);
// 				gb->sdfdims.bottom = MIN(gb->sdfdims.bottom, y);
// 				gb->sdfdims.right = MAX(gb->sdfdims.right, x);
// 				gb->sdfdims.top = MAX(gb->sdfdims.top, y);
			}

			output[x + (y * gb->sdfw)] = q;
		}
	}
}

TextRenderInfo* prepareText(TextRes* font, const char* str, int len, unsigned int* colors) {

	float offset;
	int v, i;
	TextRenderInfo* tri;
	float uscale, vscale, scale;
	unsigned int color;

	unsigned int defaultColor[] = {0xBADA55FF, INT_MAX}; // you have serious problems if the string is longer than INT_MAX

	if(!colors)
		colors = &defaultColor[0];

	//TODO:
	// normalize uv's
	// investigate and fix kerning, it seems off
	// move VAO to a better spot

	if(len == -1) len = strlen(str);

	//printf("len %d\n", len);

	// create vao/vbo
	tri = (TextRenderInfo*)malloc(sizeof(TextRenderInfo));
	tri->vertices = (TextVertex*)malloc(len * 2 * 3 * sizeof(TextVertex));
	tri->font = font;
	tri->text = strdup(str);
	tri->textLen = len;

	//move this to a global
	glGenVertexArrays(1, &tri->vao);
	glBindVertexArray(tri->vao);

	// have to create and bind the buffer before specifying its format
	glGenBuffers(1, &tri->vbo);
	glBindBuffer(GL_ARRAY_BUFFER, tri->vbo);

	// vertex
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(TextVertex), (void*)0);
	glerr("pos attrib");
	// uvs
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(TextVertex), (void*)(3*4));
	glerr("uv attrib");

	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(TextVertex), (void*)(5*4));
	glerr("color attrib");

	makeVertices(tri, colors);

 	glBufferData(GL_ARRAY_BUFFER, tri->vertexCnt * sizeof(TextVertex), tri->vertices, GL_STATIC_DRAW);
//	glBufferData(GL_ARRAY_BUFFER, sizeof(testarr), testarr, GL_STATIC_DRAW);
	glerr("buffering text vertices");

	// init shaders elsewhere

	return tri;
}

// internal use.
static void makeVertices(TextRenderInfo* tri, unsigned int* colors) {
	float offset, uscale, vscale, scale;
	int v, i;
	char* str;
	TextRes* font;
	unsigned int color;

	str = tri->text;
	font = tri->font;

	uscale = 1.0 / font->texWidth;
	vscale = 1.0 / font->texHeight;
	scale = 1.0 / font->maxHeight;

	offset = 0;
	v = 0;
	for(i = 0; i < tri->textLen; i++) {
		//printf("loop\n");
		float width, valign, kerning;
		float tex_offset, to_next;
		int index, prev;

		//increment the colors pointer if we reach the next change point
		if(i == colors[1]) colors += 2;

		color = *colors;

		index = font->codeIndex[str[i]];
		prev = font->codeIndex[str[i-1]];
// 		width = font->kerning[index];
		width = font->charWidths[index] * scale;
		//printf("width: %f\n", width);
		tex_offset = (font->offsets[index] + font->padding) * uscale;
		to_next = (font->offsets[index + 1] + font->padding) * uscale; // bug at end of array

		kerning = 0;
		/*
		if(i > 0)
			kerning = (font->kerning[(index * font->charLen) + prev]) * uscale; // bug at end of array
		*/
		offset -= ((font->padding * 2) * vscale);
		offset -= kerning;
		offset -= .15;

		//printf("kerning: %f\n", kerning);

	//	printf("index: %d, char: %c\n", index, str[i]);
	//	printf("offset %f\n", tex_offset);
	//	printf("next o %f\n", (float)to_next * uscale);
	//	printf("uscale %f\n", uscale);
	//	printf("valign %d\n", font->valign[index]);
	//	printf("width %f\n\n", width);

		valign = font->valign[index] * scale;

		//tex_offset = 1;

		// add quad, set uv's
		// triangle 1
		tri->vertices[v].x = width + offset;
		tri->vertices[v].y = 1.0 + valign;
		tri->vertices[v].z = 0.0;
		tri->vertices[v].rgba = color;
		tri->vertices[v].u = to_next;
		tri->vertices[v++].v = 1.0;

		// top left
		tri->vertices[v].x = width + offset;
		tri->vertices[v].y = 0.0 + valign;
		tri->vertices[v].z = 0.0;
		tri->vertices[v].rgba = color;
		tri->vertices[v].u = to_next;
		tri->vertices[v++].v = 0.0;

		// top right
		tri->vertices[v].x = 0.0 + offset;
		tri->vertices[v].y = 1.0 + valign;
		tri->vertices[v].z = 0.0;
		tri->vertices[v].rgba = color;
		tri->vertices[v].u = tex_offset;
		tri->vertices[v++].v = 1.0;

		// triangle 2
		tri->vertices[v].x = 0.0 + offset;
		tri->vertices[v].y = 1.0 + valign;
		tri->vertices[v].z = 0.0;
		tri->vertices[v].rgba = color;
		tri->vertices[v].u = tex_offset;
		tri->vertices[v++].v = 1.0;

		// top right
		tri->vertices[v].x = width + offset;
		tri->vertices[v].y = 0.0 + valign;
		tri->vertices[v].z = 0.0;
		tri->vertices[v].rgba = color;
		tri->vertices[v].u = to_next;
		tri->vertices[v++].v = 0.0;

		// bottom right
		tri->vertices[v].x = 0.0 + offset;
		tri->vertices[v].y = 0.0 + valign;
		tri->vertices[v].z = 0.0;
		tri->vertices[v].rgba = color;
		tri->vertices[v].u = tex_offset;
		tri->vertices[v++].v = 0.0;

		// move right by kerning amount
		offset += width; //tex_offset;
	}

	tri->vertexCnt = v;

}

void FreeFont(TextRes* res) {
	free(res->texture);
	free(res->codeIndex);
	free(res->offsets);
	free(res->valign);
	free(res->kerning);
	free(res->charSet);

	free(res);
};

//// super nifty site:
//// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
//int nextPOT(int in) {

	//in--;
	//in |= in >> 1;
	//in |= in >> 2;
	//in |= in >> 4;
	//in |= in >> 8;
	//in |= in >> 16;
	//in++;

	//return in;
//}

void updateText(TextRenderInfo* tri, const char* str, int len, unsigned int* colors) {

	if(len <= 0) len = strlen(str);

	// text hasn't changed
	if(0 == strcmp(str, tri->text)) return;

	// need to check if colors changes too, but meh

	// reallocate the vertex data buffer if it's too small
	if(tri->vertices && len > tri->textLen) {
		free(tri->vertices);
		tri->vertices = (TextVertex*)malloc(len * 2 * 3 * sizeof(TextVertex));
	}

	if(tri->text) free(tri->text);
	tri->text = strdup(str);
	tri->textLen = len;

	// i've read that trying to update a vbo when opengl is rendering can force a
	// disasterous gpu<->cpu sync. here the old vbo is released (hopefully async)
	// and the next frame will get the new vbo (all hopefully async). this has not
	// been comparatively tested.
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	GLuint oldvbo = tri->vbo;

	glBindVertexArray(tri->vao);

	glGenBuffers(1, &tri->vbo);
	glBindBuffer(GL_ARRAY_BUFFER, tri->vbo);
	glexit("update text buffer creation");

	glDeleteBuffers(1, &oldvbo);

	// vertex
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(TextVertex), (void*)0);
	glerr("pos attrib");
	// uvs
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(TextVertex), (void*)(3*4));
	glerr("uv attrib");

	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(TextVertex), (void*)(5*4));
	glerr("color attrib");

	makeVertices(tri, colors);

 	glBufferData(GL_ARRAY_BUFFER, tri->vertexCnt * sizeof(TextVertex), tri->vertices, GL_STATIC_DRAW);
//	glBufferData(GL_ARRAY_BUFFER, sizeof(testarr), testarr, GL_STATIC_DRAW);
	glerr("buffering text vertices");

}

/*
void renderText(TextRenderInfo* tri) {
	glUseProgram(textProg->id);

	// text stuff
	textProj = IDENT_MATRIX;
	textModel = IDENT_MATRIX;

	mOrtho(0, 1, 0, 1, -1, 100, &textProj);
	//mScale3f(.5,.5,.5, &textProj);

	mScale3f(.06, .06, .06, &textModel);

	GLuint tp_ul = glGetUniformLocation(textProg->id, "mProj");
	GLuint tm_ul = glGetUniformLocation(textProg->id, "mModel");
	GLuint ts_ul = glGetUniformLocation(textProg->id, "fontTex");

	glUniformMatrix4fv(tp_ul, 1, GL_FALSE, textProj.m);
	glUniformMatrix4fv(tm_ul, 1, GL_FALSE, textModel.m);
	glexit("text matrix uniforms");

	glDisable(GL_CULL_FACE);

	glActiveTexture(GL_TEXTURE0);
	glexit("active texture");

	glUniform1i(ts_ul, 0);
	glexit("text sampler uniform");
	glBindTexture(GL_TEXTURE_2D, arial->textureID);
	glexit("bind texture");

	glBindVertexArray(strRI->vao);
	glexit("text vao bind");

	glBindBuffer(GL_ARRAY_BUFFER, strRI->vbo);
	glexit("text vbo bind");
	glDrawArrays(GL_TRIANGLES, 0, strRI->vertexCnt);
	glexit("text drawing");

}
*/


code:
#ifndef __C_OPENGL_TEXT_H__
#define __C_OPENGL_TEXT_H__

#include <ft2build.h>
#include FT_FREETYPE_H

typedef struct {
	FT_Face fontFace;

} FontInfo;

typedef struct {
	int code;
	int fontSize;
	int oversample, magnitude;

	// size of the original giant character in the font
	int w, h;
	int paddedw, paddedh; // deprecated

	// need kerning and offset data

	int dw, dh; // size of the oversampled padded glyph
	int sdfw, sdfh; // size of the calculated sdf image
	uint8_t* data;
	uint8_t* sdfData;

	struct {
		int left, top;
		int right, bottom;
	} sdfdims;

} GlyphBitmap;

typedef struct {
	FontInfo* fontInfo;
	char* charSet;
	uint8_t* texture;
	uint8_t* codeIndex;
	uint16_t charLen;
	uint16_t indexLen;
	uint16_t* offsets;  // texture offsets
	uint8_t* kerning;
	int16_t* valign; // vertical offset for gutters
	uint16_t* charWidths; // quad widths

	uint16_t maxWidth, maxHeight;
	uint16_t padding;

	GLuint textureID;
	uint16_t texWidth;
	uint16_t texHeight;

} TextRes;

typedef struct {
	float x,y,z;
	float u,v;
	unsigned int rgba;
} TextVertex;

typedef struct {
	TextVertex* vertices;
	int vertexCnt;

	char* text;
	int textLen;

	GLuint vao; // need to move vao to font? global?
	GLuint vbo;

	TextRes* font;

} TextRenderInfo;

// handles fontconfig and freetype loading
FontInfo* LoadFontInfo(char* fontName);

// this function is rather expensive. it rebinds textures.
TextRes* LoadFont(char* path, int size, char* chars);

TextRenderInfo* prepareText(TextRes* font, const char* str, int len, unsigned int* colors);

void FreeFont(TextRes* res);

void updateText(TextRenderInfo* tri, const char* str, int len, unsigned int* colors);

TextRes* LoadSDFFont(char* path);

TextRes* GenerateSDFFont(char* fontName, int size, char* chars);

#endif // __C_OPENGL_TEXT_H__

license: mit


code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#include <X11/X.h>
#include <X11/Xlib.h>

#include <GL/glew.h>
#include <GL/glx.h>
#include <GL/glu.h>

#include "utilities.h"
#include "texture.h"

#include <png.h>
#include <setjmp.h>

Texture* loadDataTexture(unsigned char* data, short width, short height) {

	Texture* dt;

	dt = (Texture*)malloc(sizeof(Texture));
	dt->width = width;
	dt->height = height;

	glEnable(GL_TEXTURE_2D);

	glGenTextures(1, &dt->tex_id);
	glBindTexture(GL_TEXTURE_2D, dt->tex_id);

	glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	//glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

	// squash the data in
	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	glTexImage2D(GL_TEXTURE_2D, // target
		0,  // level, 0 = base, no minimap,
		GL_RED, // internalformat
		width,
		height,
		0,  // border
		GL_RGBA,  // format
		GL_UNSIGNED_BYTE, // input type
		data);

	glBindTexture(GL_TEXTURE_2D, 0);
	return dt;
}

BitmapRGBA8* readPNG(char* path) {
	int ret;

	BitmapRGBA8* b = (BitmapRGBA8*)calloc(sizeof(BitmapRGBA8), 1);
	if(!b) return NULL;

	ret = readPNG2(path, b);
	if(ret) {
		if(b->data) free(b->data);
		free(b);
		return NULL;
	}

	return b;
}

int readPNG2(char* path, BitmapRGBA8* bmp) {

	FILE* f;
	png_byte sig[8];
	png_bytep* rowPtrs;
	int i;
	png_byte colorType;

	f = fopen(path, "rb");
	if(!f) {
		fprintf(stderr, "Could not open \"%s\" (readPNG).\n", path);
		return 1;
	}

	fread(sig, 1, 8, f);

	if(png_sig_cmp(sig, 0, 8)) {
		fprintf(stderr, "\"%s\" is not a valid PNG file.\n", path);
		fclose(f);
		return 1;
	}

	png_structp readStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!readStruct) {
		fprintf(stderr, "Failed to load \"%s\". readPNG Error 1.\n", path);
		fclose(f);
		return 1;
	}

	png_infop infoStruct = png_create_info_struct(readStruct);
	if (!infoStruct) {
		fprintf(stderr, "Failed to load \"%s\". readPNG Error 2.\n", path);
		png_destroy_read_struct(&readStruct, (png_infopp)0, (png_infopp)0);
		fclose(f);
		return 1;
	};

	bmp->path = path;

	// exceptions are evil. the alternative with libPNG is a bit worse. ever heard of return codes libPNG devs?
	if (setjmp(png_jmpbuf(readStruct))) {

		fprintf(stderr, "Failed to load \"%s\". readPNG Error 3.\n", path);
		png_destroy_read_struct(&readStruct, (png_infopp)0, (png_infopp)0);

		if(bmp->data) free(bmp->data);
		free(bmp);
		fclose(f);
		return 1;
	}

	png_init_io(readStruct, f);
	png_set_sig_bytes(readStruct, 8);

	png_read_info(readStruct, infoStruct);

	bmp->width = png_get_image_width(readStruct, infoStruct);
	bmp->height = png_get_image_height(readStruct, infoStruct);

	colorType = png_get_color_type(readStruct, infoStruct);
	if (colorType == PNG_COLOR_TYPE_RGB) {
		png_set_add_alpha(readStruct, 0xff, PNG_FILLER_AFTER);
	}

	png_read_update_info(readStruct, infoStruct);

	bmp->data = (uint32_t*)malloc(bmp->width * bmp->height * sizeof(uint32_t));

	rowPtrs = (png_bytep*)malloc(sizeof(png_bytep) * bmp->height);

	for(i = 0; i < bmp->height; i++) {
		rowPtrs[i] = (png_bytep)(bmp->data + (bmp->width * i));
	}

	png_read_image(readStruct, rowPtrs);

	free(rowPtrs);
	png_destroy_read_struct(&readStruct, (png_infopp)0, (png_infopp)0);

	fclose(f);

	printf("Loaded \"%s\".\n", path);

	return 0;
}

Texture* loadBitmapTexture(char* path) {

	BitmapRGBA8* png;

	png = readPNG(path);
	if(!png) {
		fprintf(stderr, "could not load texture %s \n", path);
		return NULL;
	}

	Texture* dt;

	dt = (Texture*)malloc(sizeof(Texture));
	dt->width = png->width;
	dt->height = png->height;

	glGenTextures(1, &dt->tex_id);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, dt->tex_id);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	// squash the data in
	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	glTexImage2D(GL_TEXTURE_2D, // target
		0,  // level, 0 = base, no minimap,
		GL_RGBA8, // internalformat
		png->width,
		png->height,
		0,  // border
		GL_RGBA,  // format
		GL_UNSIGNED_BYTE, // input type
		png->data);

	glGenerateMipmap(GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, 0);

	glexit("failed to load texture");
	return dt;
}

// actually, the argument is a void**, but the compiler complains. stupid standards...
size_t ptrlen(const void* a) {
	size_t i = 0;
	void** b = (void**)a;

	while(*b++) i++;
	return i;
}

TexArray* loadTexArray(char** files) {

	TexArray* ta;
	int len, i;
	char* source, *s;
	BitmapRGBA8** bmps;
	int w, h;

	len = ptrlen(files);

	bmps = malloc(sizeof(BitmapRGBA8*) * len);
	ta = calloc(sizeof(TexArray), 1);

	w = 0;
	h = 0;

	for(i = 0; i < len; i++) {
		bmps[i] = readPNG(files[i]);

		if(!bmps[1]) {
			printf("Failed to load %s\n", files[i]);
			continue;
		}

		w = MAX(w, bmps[i]->width);
		h = MAX(h, bmps[i]->height);
	}

	ta->width = w;
	ta->height = h;
	ta->depth = len;

	printf("len: %d\n", len);

	glGenTextures(1, &ta->tex_id);
	glBindTexture(GL_TEXTURE_2D, 0);
	glBindTexture(GL_TEXTURE_2D_ARRAY, ta->tex_id);
	glexit("failed to create texture array 1");

// 		glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_GENERATE_MIPMAP, GL_FALSE);
	glexit("failed to create texture array 2");

	glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
	glexit("failed to create texture array 3");

	// squash the data in
	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	glTexStorage3D(GL_TEXTURE_2D_ARRAY,
		1,  // mips, flat
		GL_RGBA8,
		w, h,
		len); // layers

	glexit("failed to create texture array 4");

	for(i = 0; i < len; i++) {
		if(!bmps[i]) continue;

		glTexSubImage3D(GL_TEXTURE_2D_ARRAY, // target
			0,  // mip level, 0 = base, no mipmap,
			0, 0, i,// offset
			w, h,
			1,
			GL_RGBA,  // format
			GL_UNSIGNED_BYTE, // input type
			bmps[i]->data);
		glexit("could not load tex array slice");

		free(bmps[i]->data);
		free(bmps[i]);
	}

	free(bmps);

	return ta;
}

void Texture_Release(Texture* t) {
	printf("NYI: Texture_Release(), %s:%d\n", __FILE__, __LINE__);

}


code:
#ifndef __texture_h__
#define __texture_h__

typedef struct {
	GLuint tex_id;
	short width;
	short height;

} Texture;

typedef struct {
	char* path;
	short width, height;
	uint32_t* data;
} BitmapRGBA8;

typedef struct TexArray {
	unsigned short width, height;
	int depth;

	GLuint tex_id;
} TexArray;

Texture* loadDataTexture(unsigned char* data, short width, short height);

BitmapRGBA8* readPNG(char* path);
int readPNG2(char* path, BitmapRGBA8* bmp);

Texture* loadBitmapTexture(char* path);

TexArray* loadTexArray(char** files);

void Texture_Release(Texture* t);

#endif // __texture_h__


code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <time.h>

#include <GL/glew.h>
#include <GL/glx.h>
#include <GL/glu.h>

#include <stdint.h>

#include "timing.h"

double getCurrentTime() {
	double now;
	struct timespec ts;
	static double offset = 0;

	// CLOCK_MONOTONIC_RAW is linux-specific.
	clock_gettime(CLOCK_MONOTONIC_RAW, &ts);

	now = (double)ts.tv_sec + ((double)ts.tv_nsec / 1000000000.0);
	if(offset == 0) offset = now;

	return now - offset;
}

double timeSince(double past) {
	double now = getCurrentTime();
	return now - past;
}

void query_queue_init(QueryQueue* q) {
	glGenQueries(6, q->qids);
	q->head = 0;
	q->used = 0;
}

void query_queue_start(QueryQueue* q) {
	if(q->used < 6) {
		glBeginQuery(GL_TIME_ELAPSED, q->qids[q->head]);
		q->head = (q->head + 1) % 6;
		q->used++;
	}
	else {
		fprintf(stderr, "query queue exhausted \n");
	}
}

void query_queue_stop(QueryQueue* q) {
	glEndQuery(GL_TIME_ELAPSED);
}

int query_queue_try_result(QueryQueue* q, uint64_t* time) {
	uint64_t p;
	int tail;

	if(q->used == 0) {
		return 2;
	}

	tail = (q->head - q->used + 6) % 6;

	glGetQueryObjectui64v(q->qids[tail], GL_QUERY_RESULT_AVAILABLE, &p);
	if(GL_FALSE == p) {
		return 1; // the query isn't ready yet
	}

	glGetQueryObjectui64v(q->qids[tail], GL_QUERY_RESULT, time);
	q->used--;

	return 0;
}

int tryQueryTimer(GLuint id, uint64_t* time) {
	uint64_t p;

	glGetQueryObjectui64v(id, GL_QUERY_RESULT_AVAILABLE, &p);
	if(GL_TRUE == p) {
		glGetQueryObjectui64v(id, GL_QUERY_RESULT, time);
		return 0;
	}

	return 1;
}


code:
typedef struct QueryQueue {
	GLuint qids[6];
	int head, used;
} QueryQueue;

double getCurrentTime();

double timeSince(double past);

void query_queue_init(QueryQueue* q);

void query_queue_start(QueryQueue* q);

void query_queue_stop(QueryQueue* q);

int query_queue_try_result(QueryQueue* q, uint64_t* time);

int tryQueryTimer(GLuint id, uint64_t* time);


code:
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

#include <GL/glew.h>
#include <GL/glx.h>
#include <GL/glu.h>

#include "c3dlas/c3dlas.h"

#include "utilities.h"

#include "uniformBuffer.h"

static GLuint ubo_alignment;

// terrible code, but use for now
static int waitSync(GLuint id) {
	GLenum ret;
	if(!id || !glIsSync(id)) return 1;
	while(1) {
		ret = glClientWaitSync(id, GL_SYNC_FLUSH_COMMANDS_BIT, 1);
		glexit("");
		if(ret == GL_ALREADY_SIGNALED || ret == GL_CONDITION_SATISFIED)
			return 0;
	}
}

void* uniformBuffer_begin(UniformBuffer* ub) {
	// the fence at index n protects from writing to index n.
	// it is set after commands for n - 1;
	waitSync(ub->fences[ub->next_region]);

	return &ub->data_ptr[ub->next_region * ubo_alignment];
}

void uniformBuffer_bindRange(UniformBuffer* ub) {
	glBindBuffer(GL_UNIFORM_BUFFER, ub->ubo);
	glBindBufferRange(
		GL_UNIFORM_BUFFER,
		0,
		ub->ubo,
		ub->next_region * ub->region_size,
		ub->region_size);
	glexit("");
	glBindBuffer(GL_UNIFORM_BUFFER, 0);
	glexit("");
}

void uniformBuffer_finish(UniformBuffer* ub) {

	if(ub->fences[ub->next_region]) glDeleteSync(ub->fences[ub->next_region]);
	glexit("");
	ub->fences[ub->next_region] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
	glexit("");

	ub->next_region = (ub->next_region + 1) % UNIFORM_BUFFER_DEPTH; // BUG: make sure this is the right one

}

void initUniformBuffers() {

	glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &ubo_alignment);
	glexit("");
}

void uniformBuffer_bindProg(UniformBuffer* ub, GLuint prog_id, char* name) {
	GLuint block_index;

	glBindBuffer(GL_UNIFORM_BUFFER, ub->ubo);

	block_index = glGetUniformBlockIndex(prog_id, name);
	glexit("");
	glUniformBlockBinding(prog_id, block_index, 0);
	glexit("");

	glBindBuffer(GL_UNIFORM_BUFFER, 0);
	glexit("");
}

void uniformBuffer_init(UniformBuffer* ub, size_t region_size) {

	GLbitfield flags;
	size_t ubo_size;

	// make sure it's aligned
	ub->region_size = ((region_size / ubo_alignment) * ubo_alignment) + (region_size % ubo_alignment == 0 ? 0 : ubo_alignment);

	ub->next_region = 0;
	memset(ub->fences, 0, sizeof(ub->fences));

	flags =  GL_MAP_WRITE_BIT /*| GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT */;
	ubo_size = ub->region_size * UNIFORM_BUFFER_DEPTH;

	glGenBuffers(1, &ub->ubo);
	glexit("ubo storage a");
	glBindBuffer(GL_UNIFORM_BUFFER, ub->ubo);
	glexit("ubo storage b");
// 	glBufferStorage(GL_UNIFORM_BUFFER, ubo_size, NULL, flags);
	glBufferData(GL_UNIFORM_BUFFER, ubo_size, NULL, GL_DYNAMIC_DRAW);
	glexit("ubo storage c");

	ub->data_ptr = glMapBufferRange(GL_UNIFORM_BUFFER, 0, ubo_size, flags);
	glexit("ubo persistent map");

	glBindBuffer(GL_UNIFORM_BUFFER, 0);
	glexit("");
}


code:
#ifndef __EACSMB_UNIFORMBUFFER_H__
#define __EACSMB_UNIFORMBUFFER_H__

// buffer depth. must be at least 3
#define UNIFORM_BUFFER_DEPTH 3

typedef struct UniformBuffer {

	GLuint ubo;
	GLuint fences[UNIFORM_BUFFER_DEPTH];
	int next_region;

	size_t region_size;
	void* data_ptr;

} UniformBuffer;

void initUniformBuffers();

void uniformBuffer_init(UniformBuffer* ub, size_t region_size);
void uniformBuffer_bindProg(UniformBuffer* ub, GLuint prog_id, char* name);

void* uniformBuffer_begin(UniformBuffer* ub);
void uniformBuffer_bindRange(UniformBuffer* ub);
void uniformBuffer_finish(UniformBuffer* ub);

#endif // __EACSMB_UNIFORMBUFFER_H__


code:
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <math.h>

#include <dirent.h>

#include <GL/glew.h>
#include <GL/glx.h>
#include <GL/glu.h>

#include "utilities.h"

void _glexit(char* msg, const char* file, int line, const char* func) {
	GLenum err = glGetError();
	if (err != GL_NO_ERROR) {
		fprintf(stderr, "GL ERROR at %s:%d (%s): %s: %s \n", file, line, func, msg, gluErrorString(err));
		exit(-1);
	}
}

char* _glerr(char* msg, const char* file, int line, const char* func) {
	char* errstr;
	GLenum err;

	err = glGetError();
	errstr = NULL;

	if (err != GL_NO_ERROR) {
		errstr = (char*)gluErrorString(err);
#ifndef NO_GL_GET_ERR_DEBUG
		fprintf(
			stderr,
				TERM_BOLD TERM_COLOR_RED "GL ERROR:" TERM_RESET TERM_COLOR_RED
				"GL ERROR at %s:%d (%s): %s: %s \n",
			file, line, func, msg, errstr);
#endif
	}

	return errstr;
}

void _khr_debug_callback( // i hate this stype of formatting, but this function has too many damn arguments
	GLenum source,
	GLenum type,
	GLuint id,
	GLenum severity,
	GLsizei length,
	const GLchar *message,
	GLvoid *userParam) {

	printf(TERM_BOLD TERM_COLOR_RED "GL ERROR:" TERM_RESET TERM_COLOR_RED " %s\n" TERM_RESET, message);

}

void initKHRDebug() {
	glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW, 0, NULL, GL_FALSE);
	glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, NULL, GL_FALSE);

	glDebugMessageCallback(_khr_debug_callback , NULL);

}

char* readFile(char* path, int* srcLen) {

	int fsize;
	char* contents;
	FILE* f;

	f = fopen(path, "rb");
	if(!f) {
		fprintf(stderr, "Could not open file \"%s\"\n", path);
		return NULL;
	}

	fseek(f, 0, SEEK_END);
	fsize = ftell(f);
	rewind(f);

	contents = (char*)malloc(fsize + 2);

	fread(contents+1, sizeof(char), fsize, f);
	contents[0] = '\n';
	contents[fsize] = 0;

	fclose(f);

	if(srcLen) *srcLen = fsize + 1;

	return contents;
}

int getVAOItemSize(VAOConfig* item) {
	switch(item->type) {
		case GL_FLOAT:
			return 4 * item->sz;
		case GL_SHORT:
		case GL_UNSIGNED_SHORT:
			return 2 * item->sz;
		case GL_BYTE:
		case GL_UNSIGNED_BYTE:
			return 1 * item->sz;
		default:
			fprintf(stderr, "Unsupported VAO type\n");
			exit(2);
	}
}

int getVAOStride(VAOConfig* details) {
	int i; // packed data is expected
	int stride = 0;

	for(i = 0; details[i].sz != 0; i++) {
		int type_size = 0;
		switch(details[i].type) {
			case GL_FLOAT:
				type_size = 4;
				break;
			case GL_SHORT:
			case GL_UNSIGNED_SHORT:
				type_size = 2;
				break;
			case GL_BYTE:
			case GL_UNSIGNED_BYTE:
				type_size = 1;
				break;
			default:
				fprintf(stderr, "Unsupported VAO type\n");
				exit(2);
		}

		stride += details[i].sz * type_size;
	}

	return stride;
}

GLuint makeVAO(VAOConfig* details) {
	int i; // packed data is expected
	uintptr_t offset = 0;
	int stride;
	GLuint vao;

	stride = getVAOStride(details);

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glGenVertexArrays(1, &vao);
	glBindVertexArray(vao);

	for(i = 0; details[i].sz != 0; i++) {
		GLenum t;
		int ds;

		glEnableVertexAttribArray(i);
		t = details[i].type;
		if(t == GL_FLOAT) { // works only for my usage
			glVertexAttribFormat(i, details[i].sz, t, GL_FALSE, (void*)offset);
		}
		else if(details[i].normalize) {
			glVertexAttribFormat(i, details[i].sz, t, GL_TRUE, (void*)offset);
			glexit("");
		}
		else {
			glVertexAttribIFormat(i, details[i].sz, t, (void*)offset);
		}
		glerr("vao init");

		if(t == GL_UNSIGNED_BYTE || t == GL_BYTE) ds = 1;
		else if(t == GL_UNSIGNED_SHORT || t == GL_SHORT) ds = 2;
		else ds = 4;

		offset += ds * details[i].sz;
	}
	glexit("vao init");

	return vao;
}

// length of the line, or length of the string if no \n found
size_t strlnlen(const char* s) {
	char* n;

	n = strchr(s, '\n');
	if(!n) return strlen(s);

	return n - s;
}

// strdup a line
char* strlndup(const char* s) {
	return strndup(s, strlnlen(s));
}

// line count;
int strlinecnt(const char* s) {
	int n;

	if(!*s) return 0;

	n = 1;
	while(*s) // just to make you cringe
		if(*s++ == '\n')
			n++;

	return n;
}

char* pathJoin(const char* a, const char* b) {
	int alen, blen;
	char* o;

	alen = a ? strlen(a) : 0;
	blen = b ? strlen(b) : 0;

	o = malloc(alen + blen + 2);

	strcpy(o, a ? a : "");
	o[alen] = '/'; // TODO: fix the concat here
	strcpy(o + alen + 1, b ? b : "");
	o[alen + blen + 1] = 0;

	return o;
}

void skipWhitespace(char** s) {
	char* e;
	e = *s + strspn(*s, " \n\t\r\v");

	if(e) {
		*s = e;
		return;
	}

	// no whitespace until the end of the string
	*s = *s + strlen(*s);
}


code:
#ifndef __EACSMB_UTILITIES_H__
#define __EACSMB_UTILITIES_H__

#define USE_KHR_DEBUG
#define NO_GL_GET_ERR_DEBUG

// i pronounce this one like "Grexit", greece's only smart move which they won't make cause they're greedy, short-sighted and dumb. just like the rest of us.
#define glexit(msg) _glexit(msg, __FILE__, __LINE__, __func__)

// returns NULL for no error, a human error string otherwise. the error is printed to stderr.
#define glerr(msg) _glerr(msg, __FILE__, __LINE__, __func__)

#define MAX(a,b) ({ \
	__typeof__ (a) _a = (a); \
	__typeof__ (b) _b = (b); \
	_a > _b ? _a : _b; \
})
#define MIN(a,b) ({ \
	__typeof__ (a) _a = (a); \
	__typeof__ (b) _b = (b); \
	_a < _b ? _a : _b; \
})
#define MAXE(a,b) ({ \
	__typeof__ (a) _a = (a); \
	__typeof__ (b) _b = (b); \
	_a >= _b ? _a : _b; \
})
#define MINE(a,b) ({ \
	__typeof__ (a) _a = (a); \
	__typeof__ (b) _b = (b); \
	_a <= _b ? _a : _b; \
})

#define CHECK_OOM(p) \
if(!(p)) { \
	fprintf(stderr, "OOM for %s at %s:%d. Buy more ram\n", #p, __FILE__, __LINE__); \
	exit(2); \
}

#define GOTO_OOM(p, label) \
if(!(p)) { \
	fprintf(stderr, "OOM for %s at %s:%d. Buy more ram\n", #p, __FILE__, __LINE__); \
	goto label; \
}

#ifndef NO_TERM_COLORS
	#define TERM_COLOR_BLACK   "\x1b[30m"
	#define TERM_COLOR_RED     "\x1b[31m"
	#define TERM_COLOR_GREEN   "\x1b[32m"
	#define TERM_COLOR_YELLOW  "\x1b[33m"
	#define TERM_COLOR_BLUE    "\x1b[34m"
	#define TERM_COLOR_MAGENTA "\x1b[35m"
	#define TERM_COLOR_CYAN    "\x1b[36m"
	#define TERM_COLOR_GRAY    "\x1b[37m"
	#define TERM_RESET         "\x1b[0m"
	#define TERM_BOLD          "\x1b[1m"
	#define TERM_NORMAL        "\x1b[22m"
	#define TERM_BK_BLACK      "\x1b[40m"
	#define TERM_BK_RED        "\x1b[41m"
	#define TERM_BK_GREEN      "\x1b[42m"
	#define TERM_BK_YELLOW     "\x1b[43m"
	#define TERM_BK_BLUE       "\x1b[44m"
	#define TERM_BK_MAGENTA    "\x1b[45m"
	#define TERM_BK_CYAN       "\x1b[46m"
	#define TERM_BK_GRAY       "\x1b[47m"
#else
#endif

char* readFile(char* path, int* srcLen);

typedef struct VAOConfig {
	int sz;
	GLenum type;
	char normalize;
} VAOConfig;

int getVAOItemSize(VAOConfig* item);
int getVAOStride(VAOConfig* details);
GLuint makeVAO(VAOConfig* details);

void initKHRDebug();

size_t strlnlen(const char* s);
char* strlndup(const char* s);
int   strlinecnt(const char* s);
char* pathJoin(const char* a, const char* b);

void skipWhitespace(char** s);

#endif // __EACSMB_UTILITIES_H__


code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>

#include <GL/glew.h>
#include <GL/glx.h>
#include <GL/glu.h>

#include "utilities.h"
#include "c3dlas/c3dlas.h"
#include "window.h"

void initGLEW();

typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0;

const int context_attr[] = {
	GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
	GLX_CONTEXT_MINOR_VERSION_ARB, 3,
#ifdef USE_KHR_DEBUG
	GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB,
#endif
	None
};

const int visual_attr[] = {
	GLX_X_RENDERABLE    , True,
	GLX_DRAWABLE_TYPE   , GLX_WINDOW_BIT,
	GLX_RENDER_TYPE     , GLX_RGBA_BIT,
	GLX_X_VISUAL_TYPE   , GLX_TRUE_COLOR,
	GLX_RED_SIZE        , 8,
	GLX_GREEN_SIZE      , 8,
	GLX_BLUE_SIZE       , 8,
	GLX_ALPHA_SIZE      , 8,
	GLX_DEPTH_SIZE      , 24,
	GLX_STENCIL_SIZE    , 8,
	GLX_DOUBLEBUFFER    , True,
	//GLX_SAMPLE_BUFFERS  , 1, // antialiasing
	//GLX_SAMPLES         , 4,
	None
};

XErrorEvent* xLastError = NULL;
char xLastErrorStr[1024] = {0};

int xErrorHandler(Display *dpy, XErrorEvent *ev) {

	xLastError = ev;
	XGetErrorText(dpy, ev->error_code, xLastErrorStr, 1024);

	fprintf(stderr, "X Error %d: %s", ev->error_code, xLastErrorStr);

    return 0;
}

// this function will exit() on fatal errors. what good is error handling then?
int initXWindow(XStuff* xs) {

	GLXFBConfig* fbconfigs;
	GLXFBConfig chosenFBC;
	int fbcount, i;
	int best_fbc = -1, best_num_samp = -1;
	XSetWindowAttributes setWinAttr;

	GLint att[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None };

	xs->display = XOpenDisplay(NULL);
	if(xs->display == NULL) {
		printf("Cannot connect to X server\n");
		exit(1);
	}

	xs->rootWin = DefaultRootWindow(xs->display);

	// find a framebuffer config
	fbconfigs = glXChooseFBConfig(xs->display, DefaultScreen(xs->display), visual_attr, &fbcount);
	if(!fbconfigs) {
		fprintf(stderr, "No usable framebuffer config\n" );
		exit(1);
	}

	// try to get the requested MSAA, or the closest to it without going over
	for(i = 0; i < fbcount; i++) {
		XVisualInfo* vi;
		int samp_buf, samples;

		vi = glXGetVisualFromFBConfig(xs->display, fbconfigs[i]);
		if(!vi) continue;

		glXGetFBConfigAttrib(xs->display, fbconfigs[i], GLX_SAMPLE_BUFFERS, &samp_buf);
		glXGetFBConfigAttrib(xs->display, fbconfigs[i], GLX_SAMPLES, &samples);
		glerr("samples");

		if(best_fbc < 0 || samp_buf && samples > best_num_samp && samples <= xs->targetMSAA) {
			best_fbc = i;
			best_num_samp = samples;
		}

		XFree(vi);
	}

	chosenFBC = fbconfigs[best_fbc];

	XFree(fbconfigs);

	// Get a visual
	xs->vi = glXGetVisualFromFBConfig(xs->display, chosenFBC);

	xs->colorMap = XCreateColormap(xs->display, xs->rootWin, xs->vi->visual, AllocNone);
	setWinAttr.colormap = xs->colorMap;
	setWinAttr.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask;

	xs->clientWin = XCreateWindow(xs->display, xs->rootWin, 0, 0, 600, 600, 0, xs->vi->depth, InputOutput, xs->vi->visual, CWColormap | CWEventMask, &setWinAttr);

	XMapWindow(xs->display, xs->clientWin);

	XStoreName(xs->display, xs->clientWin, xs->windowTitle);

	// don't check for supported extensions, just fail hard and fast if the computer is a piece of shit.

	// cause putting now-ubiquitous stuff in headers is lame...
	glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");
	if(!glXCreateContextAttribsARB) {
		fprintf(stderr, "glXCreateContextAttribsARB() not found. Upgrade your computer.\n" );
		exit(1);
	}

	XSetErrorHandler(&xErrorHandler);

	xs->glctx = glXCreateContextAttribsARB(xs->display, chosenFBC, 0, True, context_attr);

	// squeeze out any errors
	XSync(xs->display, False);

	glXMakeCurrent(xs->display, xs->clientWin, xs->glctx);

	// have to have a current GLX context before initializing GLEW
	initGLEW();

#ifdef USE_KHR_DEBUG
	initKHRDebug();
#endif

}

#define CLAMP(min, mid, max) MIN(max, MAX(mid, min))

void processEvents(XStuff* xs, InputState* st, int max_events) {

	XEvent xev;
	int evcnt;
	int x, y;
	float fx, fy;

	int rootX, rootY, clientX, clientY;
	Window rootReturn, clientReturn;
	unsigned int mouseMask;

	if(max_events <= 0) max_events = INT_MAX;

	// bottleneck :)
	clearInputState(st);

	if(XQueryPointer(xs->display, xs->clientWin, &rootReturn, &clientReturn, &rootX, &rootY, &clientX, &clientY, &mouseMask)) {
		if(xs->winAttr.height > 0 && xs->winAttr.width > 0) {
			st->cursorPosPixels.x = CLAMP(0, clientX, xs->winAttr.width);
			st->cursorPosPixels.y = CLAMP(0, xs->winAttr.height - clientY, xs->winAttr.height);

			st->cursorPos.x = clientX / xs->winAttr.width;
			st->cursorPos.y = 1.0 - (clientY / xs->winAttr.height); // opengl is inverted to X
		}
	}

	for(evcnt = 0; XPending(xs->display) && evcnt < max_events; evcnt++) {
		XNextEvent(xs->display, &xev);

		// capture expose events cause they're useful. fullscreen games are for wimps who can't ultratask.
		if(xev.type == Expose) {
			// update some standard numbers
			// there's currently risk of a race condition if anything tries to read winAttr before the expose event fires
			XGetWindowAttributes(xs->display, xs->clientWin, &xs->winAttr);

			glViewport(0, 0, xs->winAttr.width, xs->winAttr.height);

			if(xs->onExpose)
				(*xs->onExpose)(xs, xs->onExposeData);

			xs->ready = 1;

		}

		if(xev.type == KeyPress) {
			KeySym s;

			int keycode = ((XKeyEvent*)&xev)->keycode;
			//s = XLookupKeysym((XKeyEvent*)&xev, 0);

			//printf("key: %d %c\n", keycode, keycode);

			st->keyState[keycode] |= IS_KEYPRESSED | IS_KEYDOWN;
		}
		if(xev.type == KeyRelease) {

			int keycode = ((XKeyEvent*)&xev)->keycode;
			st->keyState[keycode] &= !IS_KEYDOWN;
		}

		// mouse events
		if(xev.type == ButtonPress) {
// 			st->clickPos.x = xev.xbutton.x / (float)xs->winAttr.width;
// 			st->clickPos.y = (xs->winAttr.height - xev.xbutton.y) / (float)xs->winAttr.height;
// 			st->clickButton = xev.xbutton.button;
			st->buttonDown = xev.xbutton.button;
		}
		if(xev.type == ButtonRelease) {
			st->clickPos.x = xev.xbutton.x / (float)xs->winAttr.width;
			st->clickPos.y = (xs->winAttr.height - xev.xbutton.y) / (float)xs->winAttr.height;
			st->clickButton = xev.xbutton.button;
			st->buttonUp = xev.xbutton.button;

		}

	}

}

void clearInputState(InputState* st) {
	int i;

	for(i = 0; i < 256; i++) {
		st->keyState[i] &= IS_KEYDOWN;
	}

	st->clickPos.y = -1;
	st->clickPos.x = -1;
	st->clickButton = 0;
	st->buttonUp = 0;
	st->buttonDown = 0;

}

void initGLEW() {
	GLenum err;

	// this is some global that GLEW declares elsewhere
	glewExperimental = GL_TRUE; // workaround for broken glew code in 1.10 and below (at least) - gl3.2+ contexts fail otherwise

	err = glewInit();
	if (GLEW_OK != err) {
		// we're fucked, just bail
		fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
		exit(1);
	}

	fprintf(stdout, "Initialized GLEW %s\n", glewGetString(GLEW_VERSION));
	glerr("existing error on glew init");
}


code:
#ifndef __EACSMB_WINDOW_H__
#define __EACSMB_WINDOW_H__

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>

#include "common_gl.h"
#include "common_math.h"

typedef struct XStuff {
	Display*     display;
	Window       rootWin;
	Window       clientWin;
	GLXContext   glctx;

	XVisualInfo* vi;
	Colormap     colorMap;

	XWindowAttributes winAttr;

	void (*onExpose)(struct XStuff*, void*);
	void* onExposeData;

	int targetMSAA;
	char* windowTitle;

	Bool ready;
} XStuff;

#define IS_KEYDOWN    0x01 // is it down now
#define IS_KEYPRESSED 0x02 // was the key pressed ever
#define IS_CONTROL    0x04
#define IS_SHIFT      0x08
#define IS_ALT        0x10
#define IS_TUX        0x20 // aka "windows key"

enum InputMode {
	CLICK_MODE,
	DRAG_MODE

};

typedef struct {

	// shitty for now
	Vector2 clickPos;
	Vector2 cursorPos;
	Vector2 cursorPosPixels;
	char clickButton;
	char buttonUp;
	char buttonDown;

	unsigned char keyState[256];

	enum InputMode mode;

} InputState;

XErrorEvent* xLastError;
char xLastErrorStr[1024];

int initXWindow(XStuff* xs);

void processEvents(XStuff* xs, InputState* st, int max_events);
void clearInputState(InputState* st);

#endif // __EACSMB_WINDOW_H__


code:
 **IPTV 频道 电视 直播 源 定期检测维护有效性**

https://raw.githubusercontent.com/zbefine/iptv/main/iptv.m3u

https://raw.githubusercontent.com/zbefine/iptv/main/iptv.txt

Gitee https://gitee.com/qingwei248/ds

![check (1)](https://user-images.githubusercontent.com/26646520/155706149-8cc26744-d0ce-4cb5-99b9-83cf278f740e.png)

 **IPTV 微信小程序版**

![ewm](https://user-images.githubusercontent.com/26646520/163784389-2a42b6c6-2862-4511-856e-caaa4ffac35e.jpg)

**正常模式**

![1](https://user-images.githubusercontent.com/26646520/174236515-0e61c51d-eea0-40dc-9b4c-00b26f519047.png)

 **彩蛋模式**

**开启彩蛋模式的方法，请在小程序里 【我的 -> 联系客服】 进行咨询*

![2](https://user-images.githubusercontent.com/26646520/174236531-e5c83792-cb8d-4c1b-a5dd-162518903478.png)

![3](https://user-images.githubusercontent.com/26646520/174236548-d27051a8-f8ae-44be-80db-4e05725de9bb.png)

![4](https://user-images.githubusercontent.com/26646520/174236551-21919a25-4b0a-447f-a307-3782c1ddee62.png)

![5](https://user-images.githubusercontent.com/26646520/174236560-2f10853a-0cf1-428d-a4e1-933033775811.png)

![6](https://user-images.githubusercontent.com/26646520/174236574-6f70c595-a501-4da3-b39e-3d79999cd3c0.png)

![7](https://user-images.githubusercontent.com/26646520/174236586-22c4e07c-fbf8-4b9a-9223-17a44ce4384b.png)

**历史版本**

> v39.0.0
一些界面优化；

> v38.0.0
优化彩蛋模式的登入；
改版影视类的子分类；

> v37.0.0
影视类视频新增倍速播放，新增一条通用播放源；
更换部分图标；

> v36.0.0
修复 导航栏些许溢出的问题，改版了导航栏；
修复 删除某个收藏后可能导致界面异常的问题；
修复 IOS端没弹幕的情况，同时升级弹幕功能；
优化上拉加载更多性能；
新增 电视直播双版本切换功能，长按【直播】可以切换电视直播版本；
新增 全新版本影视类；
新增 清除全局缓存功能；
改版 热搜；
改版 弹出提示的位置；
移除 引擎切换、引擎标识、双引擎搜索功能；（因新增全新版影视类）

> v35.0.0 调整导航栏；直播类页面里移除当前分类列表；影视类页面改版播放源选择界面；性能优化；

> v33.0.0 直播类视频新增投屏功能，目前所有视频都可以投屏了；优化影视类视频的播放源；弹幕重构，PC端也可以有弹幕了，（ios端可能会出现没弹幕的情况，随缘吧~）；HM引擎重整。

> v32.0.0 彩蛋模式。

> v31.0.0 优化。

> v30.0.0 重构小程序，全新版本。

> v29.0.0 如果HM主引擎片源更新中，请暂时使用其它备用引擎。

移除了上个版本的F2引擎；F1引擎整改为HM引擎；

新增3个片源引擎， 1个主引擎（HM），2个备用引擎（TC, SZ）；

搜索页面：新增长按搜索更换引擎，新增搜索结果引擎标识；

影视类播放页面：新增片源所属引擎标识；

我的页面：新增引擎更换功能；最近观看记录和影视类的收藏加上了片源所属引擎标识；

加快了AI解析播放源速度；

修复提示文字残留的问题;

> v28.0.0

UI优化，页面重新排版；

优化了一些功能体验；

修复了一些已知问题；

频道页面：

......新增全新影视类频道（电影、电视剧、综艺、动漫），归属为F1引擎资源；

......移动原来的影视类频道资源，进入了搜索功能里面，归属为F2引擎资源；

......新增游戏赛事直播频道（LOL、王者荣耀......）；

......新增全新体育赛事直播频道（NBA、足球、比赛回放......），删除了原来的体育赛事直播频道；

搜索页面：

......新增双引擎搜索(F1、F2引擎)，单击或长按 搜索按钮，显示不同引擎的搜索结果；

收藏页面：

......新增影视类收藏（最多收藏60个），新增直播类收藏（最多收藏60个），最近浏览历史记录只保留最近30条；

直播类页面：

......移除画面比例调节，改为播放模式切换（解决部分机型黑屏不能播放的问题）；

影视类播放页面：

......移除下一集自动播放功能；

......新增影视类收藏功能；

......新增视频刷新重载功能；

......新增换引擎搜索功能（如果当前播放的是F1引擎资源，点击搜索图标后自动启动F2引擎搜索，反之）；

......新增全自动AI解析播放源（仅限F1引擎资源）；

......新增滚动公告提醒；

......新增播放模式切换（解决部分机型黑屏不能播放的问题）；

> v25.0.0 **优化**

> v23.0.0 修复屏幕常亮的问题；调整部分组件的显示；优化详情页面体验，页面整体不能滚动，视频固定，视频下面区域滚动；优化详情页视频的自动播放下一集功能、视频里面新增标题、加载画面；在【我的】页面新增手动 版本更新;

> v22.1.0 新增了几个一级大类 【电视剧、电影、综艺、动漫】（测试中可能随时下线~）；新增搜索功能(绿色放大镜那个图标)，搜电视剧、电影、综艺、动漫~；新增最近观看记录 主要是针对【电视剧、电影、综艺、动漫】这些分类的记录；优化了页面触底加载更多视频的逻辑，还有修复了之前没发现的一些bug；优化了一些消息提醒的方式（顶部弹出提醒，自动关闭）；改版了一些界面UI；修复了一些页面分享后打开找不到路径的问题；......修复了一些已知问题；

> v21.0.0 重构频道分类，改为二级分类；个人收藏夹独立出来了在【我的】里面，长按任意一个收藏，可提示是否清空所有收藏；停用了某些分类，修复了一些bug`(*>﹏<*)′，哎呀~

> v20.0.0 重构视频播放列表模块、自动定位到当前视频；修改弹幕默认字体大小；修改关于页部分UI高度；

> v19.1.0 优化适配大屏幕；优化加载性能；增大弹幕字体大小设置范围；修改部分UI高度；

repo_name: sober/iptv

license: unknown


code:
/build/
/install/
/release/
/sources/
/libreboot.conf
.version
.revision
.tarfiles
.epoch
push
*.vim
/TODO/
/docs/version
/bucts/
/coreboot/
/crossgcc/
/flashrom/
/grub/
/memtest86plus/
/seabios/
/bin/
/resources/utilities/ich9deblob/*.rom
/resources/utilities/ich9deblob/*.bin
/resources/utilities/ich9deblob/ich9deblob
/resources/utilities/ich9deblob/ich9gen
/resources/utilities/ich9deblob/demefactory
/resources/utilities/ich9deblob/obj/*.o
/resources/utilities/ich9deblob/obj/*/*.o
/resources/utilities/ich9deblob/mkdescriptor*.[ch]
/resources/utilities/ich9deblob/mkgbe*.[ch]
/resources/utilities/grub-assemble/grub_*.elf
/mkgbe.[ch]
/*.bin
/resources/utilities/i945-pwm/i945-pwm
/push
/ChangeLog
/NEWS
/version
/versiondate
/gm45macaddress
tocheck*
*.html
!www/template.html
/www/temp.md

path: .gitignore


code:
Paper Build System
==================

Entry Point
-----------

The entry point for using the paper build system is the main script in the root
directory, named after the build system (paper, libreboot, etc).
Running the main script with no argument will show its general usage.

Configuration
-------------

The paper build system can be configured with a script named after the build
system (paper, libreboot, etc) with a ".conf" suffix. It is automatically loaded
by the build system when running the main script.

It contains environment variables assigned to the desired values. The list of
environment variables is shown in the main script's general usage.

Project and Tool Targets
------------------------

The build system works around the concepts of projects and tools, that define
specific components that can be used to produce either:
* sources
* systems
* images
* tools

Various actions are available for each project and tool targets, many of which
are shown in the main script's general usage. Each project and tool may provide
a usage action that allows to see the specific usage for it. Actions are
executed recursively when no targets are specified.

Each project and tool target has its own directory (either in projects or tools)
that contain a script named after the target and possible a helper script, named
after the script with a "-helper" suffix. The helper script is automatically
included by the build system. Functions in helper scripts are usually prefixed
with the name of the target, where "-" symbols are replaced with "_" symbols.

Each project and tool target's actions are functions defined in the target's
specific script, with names matching the target's actions.

Meta-Targets
------------

Meta targets are project and tool targets that apply the requested action to
individual targets, allowing to execute an action to many targets at once.
For instance, a meta-target named after the build system with a "-all" suffix
would call other meta-targets, prefixed with "-images", "-tools", etc that would
then perform the requested action to all underlying targets.

Projects Actions
----------------

Various generic actions allow preparing projects through a series of steps:
* downloading, extracting or updating the project's sources
* building the project to a build directory
* installing the project to an install directory
* releasing the project to a release directory
* cleaning the build, install and release directories

Actions can be checked by a matching project-specific function, named after the
function to check with a "_check" suffix, to determine whether it is necessary
to run them again to follow the steps. An environment variable can force actions
to be executed, by specifying a space-separated list of projects:
PROJECTS_FORCE.

Projects Configuration and Patches
----------------------------------

Configuration for each project is stored in each project's own directory.
Targets for each project are defined with a "targets" file in each directory
of the "configs" directory. Targets are read recursively, following
sub-directory names for project targets.

Each project's configuration directory can be used to hold target-specific
information and configuration.

An "install" file in each sub-directory indicated which files to grab from the
build files and where to install them in the install directory.

Projects Sources
----------------

Each project can either download specific sources or use another project's
sources, possibly providing its own revision and patches.

Sources are downloaded with the "download" action or can be extracted from
released sources (after placing the sources in the sources directory) with the
"extract" action.

Projects may also keep their sources in a "sources" directory in their
project-specific directory.

Projects Build
--------------

Each project is built to a build directory, named after the project, with the
list of targets separated by a "-" symbol.

An environment variable allows controlling the number of tasks to run at the
same time: TASKS.

Projects Install
----------------

Projects are installed from "install" files in the project directory, that
copies the selected built files to the install directory, named after the
project, with the list of targets separated by a "-" symbol. These install files
are read recursively, following sub-directory names for project targets.

Additional files to install can be specified in the "install" directory and
described in an "install" file. These install files are read recursively,
following sub-directory names for project targets.

Projects Release
----------------

Projects are released to a release directory, named after the project, with the
list of targets separated by a "-" symbol. Each project's install files are
packed into a tarball to the corresponding release directory. A checksum and a
GPG detached signature (if the RELEASE_KEY environment variable is set) are also
generated.

Tarballs are generated reproducibly, from a list of files stored in the
".tarfiles" file. They may also contain the ".epoch", git ".revision" and
".version" files.

Tools Actions
-------------

Tools are used for maintaining the build system, for performing routine tasks
that can be automated. They may have specific actions and not implement any of
the generic actions.

Actions can be checked by a matching tool-specific function, named after the
function to check with a "_check" suffix, to determine whether it is necessary
to run them again to follow the steps. An environment variable can force actions
to be executed, by specifying a space-separated list of tasks: TASKS_FORCE.

Tools Sources
-------------

Tools may keep their sources in a "sources" directory in their tool-specific
directory. These sources may be updated with the "update" action.


code:
Libreboot project

Libreboot documentation is under docs/
The website is https://libreboot.org/
License can be found in COPYING and through parts of the source tree.

Libreboot is a free BIOS or UEFI replacement (free as in freedom); libre boot
firmware that initializes the hardware and starts a bootloader for your
operating system. It's also an open source BIOS, but open source fails to
promote freedom; please call libreboot free software.

Why use libreboot?

Many people use non-free boot firmware, even if they use GNU+Linux. Non-free
BIOS/UEFI firmware often contains backdoors, can be slow and have severe bugs,
where you are left helpless at the mercy of the developers; you have no freedom
over your computing. By contrast, libreboot is building a world where
everyone can use, study, adapt and share software, with true control and
ownership over their technology. In other words, you should use Libreboot for
your freedom's sake!

Libreboot is faster, more secure and more reliable than most non-free firmware,
and can provide many advanced features (such as encrypted /boot/, GPG signature
checking before booting your kernel, ability to load an OS from the flash chip,
and more).

Libreboot's main upstream providers are coreboot (which we deblob, for hardware
initialization), depthcharge (bootloader, and default libreboot payload on ARM),
and GRUB (bootloader, and default libreboot payload on x86). We also
integrate flashrom (for installing libreboot), and several of our own utilities,
scripts and configuration files. All of this is integrated into a single,
coherent package that is easy to use. We add our own patches to the various
upstreams used, and where feasible try to merge upstream as much as possible.

Libreboot provides a fully automated build system and installation process, with
documentation written for non-technical users, in an attempt to make the
software as easy to use as possible. ROM images are provided, along with
utilities, all built from the publicly distributed source code.

This README is licensed under Creative Commons Zero 1.0:
https://creativecommons.org/publicdomain/zero/1.0/

path: README


code:
Libreboot 20160907

For existing boards, there are no new board specific changes.

This release adds one new mainboard to libreboot:
* Intel D945GCLF desktop motherboard (thanks to Arthur Heymans)

Other bugfixes:
* Various improvements to the documentation
* re-added "unset superusers" to the grub.cfg, which was needed for some users
  depending on the distros that they used

Libreboot 20160902

This is a bugfix release, based on 20160818. It contains no new board changes.
The previous 20160818 release had build errors in the _src archive, and the
_util archive was only source code.

Changes compared to 20160818:
* Fixed bug where ./build module coreboot always returned non-zero status
* Fixed missing symlink of crossgcc when building from _src (thanks Arthur
Heymans)
* Fixed building with the depthcharge payload (ASUS C201)
* Proper ChangeLog now, instead of pasted git log
* Util archive is now binaries again (source code is in the _src archive)
* Documentation is now in HTML format

Libreboot 20160818

NEW BOARDS ADDED:
* ASUS Chromebook C201 (ARM laptop) (thanks to Paul Kocialkowski)
* Gigabyte GA-G41M-ES2L motherboard (desktop) (thanks to Damien Zammit)
* Intel D510MO motherboard (desktop) (thanks to Damien Zammit)
* ASUS KCMA-D8 motherboard (desktop) (thanks to Timothy Pearson)
* ASUS KFSN4-DRE motherboard (server) (thanks to Timothy Pearson)
* ASUS KGPE-D16 motherboard (server) (thanks to Timothy Pearson)

For boards previously supported, many fixes from upstream have been merged.

Other changes (compared to libreboot 20150518):
(this is a summary. For more detailed change list, refer to the git log)

256MiB VRAM allocated on GM45 (X200, T400, T500, R400) instead of 32MiB.
This is an improvement over both Lenovo BIOS and Libreboot 20150518, allowing
video decoding at 1080p to be smoother. (thanks Arthur Heymans)
To clarify, GM45 video performance in libreboot 20160818 is better than on
the original BIOS and the previous libreboot release.

64MiB VRAM on i945 (X60, T60, MacBook2,1) now supported in coreboot-libre, and
used by default (in the previous release, it was 8MiB allocated). Thanks to
Arthur Heymans.

Higher battery life on GM45 (X200, T400, T500, R400) due to higher cstates now
being supported (thanks Arthur Heymans). C4 power states also supported.

Text mode on GM45 (X200, T400, T500, R400) now works, making it possible to
use MemTest86+ comfortably. (thanks to Nick High from coreboot)

Dual channel LVDS displays on GM45 (T400, T500) are now automatically
detected in coreboot-libre. (thanks Vladimir Serbinenko from coreboot)

Partial fix in coreboot-libre for GRUB display on GM45, for dual channel
LVDS higher resolution LCD panels (T400, T500). (thanks Arthur Heymans)

Massively improved GRUB configuration, making it easier to boot more
encrypted systems automatically, and generally a more useful menu for booting
the system (thanks go to Klemens Nanni of the autoboot project).
Libreboot now uses the grub.cfg provided by the installed GNU+Linux distribution
automatically, if present, switching to that configuration. This is done across
many partitions, where libreboot actively searches for a configuration file
(also on LVM volumes and encrypted volumes). This should make libreboot more
easy to use for non-technical users, without having to modify the GRUB
configuration used in libreboot.

Utilities archives is now source only. You will need to compile the packages
in there (build scripts included, and a script for installing build
dependencies. (binary utility archives are planned again in the next
release, when the new build system is merged)

SeaGRUB is now the default payload on all x86 boards. (SeaBIOS configured to
load a compressed GRUB payload from CBFS immediately, without providing an
interface in SeaBIOS. This way, GRUB is still used but now BIOS services are
available, so you get the best of both worlds). Thanks go to Timothy Pearson
of coreboot for this idea.

crossgcc is now downloaded and built as a separate module to coreboot-libre,
with a universal revision used to build all boards.

Individual boards now have their own coreboot revision and patches, independently
of each other board. This makes maintenance easier.

Updated all utilities, and modules (coreboot, GRUB, etc) to newer versions,
with various bugfixes and improvements upstream.

RTC century byte issue now fixed on GM45 in coreboot-libre, so the date
should now be correctly displayed when running the latest linux kernel, instead
of seeing 1970-01-01 when you boot (thanks to Alexander Couzens from coreboot)

Build system now uses multiple CPU cores when building, speeding up building
for some people. Manually specifying how many cores are needed is also possible,
for those using the build system in a chroot environment. (thanks go to
Timothy Pearson from coreboot)

In the build system (git repository), https:// is now used when cloning coreboot.
http:// is used as a fallback for GRUB, if git:// fails.

New payload, the depthcharge bootloader (free bootloader maintained by Google)
for use on the ASUS Chromebook C201. (thanks go to Paul Kocialkowski)

Various fixes to the ich9gen utility (e.g. flash component density is now
set correctly in the descriptor, gbe-less descriptors now supported)

path: RELEASE


code:
% How to install FreeBSD on a libreboot system

This section relates to preparing, booting and installing FreeBSD on
your libreboot system, using nothing more than a USB flash drive (and
*dd*). They've only been tested on a Lenovo ThinkPad x200.

It is expected that you use text mode in libreboot (txtmode images), for
the early boot process in FreeBSD. **Booting the installer results in a
red flickering text display, and doesn't boot.**

Thanks go to ioxcide in [this Reddit
post](https://www.reddit.com/r/BSD/comments/53jt70/libreboot_and_bsds/)
for the initial instructions.

TODO: test FreeBSD more extensively, and make sure it works (and fix it
if it does not). Instructions are provided here, to boot and install
FreeBSD but we're not sure whether it is currently fully compatible
with libreboot.

-   [Prepare the USB drive (in FreeBSD)](#prepare)
-   [Installing FreeBSD without full disk encryption](#noencryption)
-   [Installing FreeBSD with full disk encryption](#encryption)
-   [Booting](#booting)
-   [Configuring Grub](#configuring_grub)
-   [Troubleshooting](#troubleshooting)

**This section is only for the GRUB payload. For depthcharge (used on
CrOS devices in libreboot), instructions have yet to be written in the
libreboot documentation.**

freebsd.img is the installation image for FreeBSD. Adapt the filename
accordingly, for whatever FreeBSD version you use.

Prepare the USB drive (in FreeBSD)
----------------------------------

[This page](https://www.freebsd.org/doc/handbook/bsdinstall-pre.html) on
the FreeBSD website shows how to create a bootable USB drive for
installing FreeBSD. Use the *dd* on that page.

Prepare the USB drive (in NetBSD)
---------------------------------

[This
page](https://wiki.netbsd.org/tutorials/how_to_install_netbsd_from_an_usb_memory_stick/)
on the NetBSD website shows how to create a NetBSD bootable USB drive
from within NetBSD itself. You should use the *dd* method documented
there; you can use this with any ISO, including FreeBSD.

Prepare the USB drive (in LibertyBSD or OpenBSD)
------------------------------------------------

If you downloaded your ISO on a LibertyBSD or OpenBSD system, here is
how to create the bootable FreeBSD USB drive:

Connect the USB drive. Check dmesg:

    $ dmesg | tail

Check to confirm which drive it is, for example, if you think its sd3:

    $ disklabel sd3

Check that it wasn't automatically mounted. If it was, unmount it. For
example:

    $ doas umount /dev/sd3i

dmesg told you what device it is. Overwrite the drive, writing the
FreeBSD installer to it with dd. For example:

    $ doas dd if=freebsd.img of=/dev/rsdXc bs=1M; sync

You should now be able to boot the installer from your USB drive.
Continue reading, for information about how to do that.

Prepare the USB drive (in GNU+Linux)
------------------------------------

If you downloaded your ISO on a GNU+Linux system, here is how to create
the bootable FreeBSD USB drive:

Connect the USB drive. Check dmesg:

    $ dmesg
Check lsblk to confirm which drive it is:

    $ lsblk

Check that it wasn't automatically mounted. If it was, unmount it. For
example:

    $ sudo umount /dev/sdX\*
    # umount /dev/sdX\*

dmesg told you what device it is. Overwrite the drive, writing your
distro ISO to it with dd. For example:

    $ sudo dd if=freebsd.img of=/dev/sdX bs=8M; sync
    # dd if=freebsd.img of=/dev/sdX bs=8M; sync

You should now be able to boot the installer from your USB drive.
Continue reading, for information about how to do that.

Installing FreeBSD without full disk encryption
-----------------------------------------------

Press C in GRUB to access the command line:

    grub> kfreebsd (usb0,gpt3)/boot/kernel/kernel
    grub> set FreeBSD.vfs.mountfrom=ufs:/dev/da1p3\
    grub> boot

It will start booting into the FreeBSD installer. Follow the normal
process for installing FreeBSD.

Installing FreeBSD with full disk encryption
--------------------------------------------

TODO

Booting
-------

TODO

Configuring Grub
----------------

TODO

Troubleshooting
===============

Most of these issues occur when using libreboot with coreboot's 'text
mode' instead of the coreboot framebuffer. This mode is useful for
booting payloads like memtest86+ which expect text-mode, but for FreeBSD
it can be problematic when they are trying to switch to a framebuffer
because it doesn't exist.

In most cases, you should use the vesafb ROM images. Example filename:
libreboot\_ukdvorak\_vesafb.rom.

won't boot\...something about file not found
---------------------------------------------

Your device names (i.e. usb0, usb1, sd0, sd1, wd0, ahci0, hd0, etc) and
numbers may differ. Use TAB completion.

Copyright © 2016 Leah Rowe <info@minifree.org>\
Copyright © 2016 Scott Bonds <scott@ggr.com>\
This page is available under the [CC BY SA 4.0](../cc-by-sa-4.0.txt)


code:
% BSD projects

This section relates to dealing with BSD projects: preparing bootable
USB drives, changing the default GRUB menu and so on.

**This section is only for the \*GRUB\* payload. For depthcharge,
instructions have yet to be written.**

-   [How to install NetBSD on a libreboot system](netbsd.html)
-   [How to install LibertyBSD or OpenBSD on a libreboot
    system](openbsd.html)

-   [How to install FreeBSD on a libreboot system](freebsd.html)

Copyright © 2016 Scott Bonds <scott@ggr.com>\
This page is available under the [CC BY SA 4.0](../cc-by-sa-4.0.txt)


code:
% How to install NetBSD on a libreboot system

This section relates to preparing, booting and installing NetBSD on your
libreboot system, using nothing more than a USB flash drive (and *dd*).
They've only been tested on a librebooted ThinkPad X60.

It is expected that you use text mode in libreboot (txtmode images), for
the early boot process in NetBSD. **Suspend/hibernate is broken,
according to at least 1 user.**

Thanks go to ioxcide in [this Reddit
post](https://www.reddit.com/r/BSD/comments/53jt70/libreboot_and_bsds/)
for the initial instructions.

-   [Prepare the USB drive (in NetBSD)](#prepare)
-   [Installing NetBSD without full disk encryption](#noencryption)
-   [Installing NetBSD with full disk encryption](#encryption)
-   [Booting](#booting)
-   [Configuring Grub](#configuring_grub)
-   [Troubleshooting](#troubleshooting)

**This section is only for the GRUB payload. For depthcharge (used on
CrOS devices in libreboot), instructions have yet to be written in the
libreboot documentation.**

netbsd.iso is the installation image for NetBSD. Adapt the filename
accordingly, for your version of NetBSD.

Prepare the USB drive (in NetBSD)
---------------------------------

[This
page](https://wiki.netbsd.org/tutorials/how_to_install_netbsd_from_an_usb_memory_stick/)
on the NetBSD website shows how to create a NetBSD bootable USB drive
from within NetBSD itself. You should use the *dd* method documented
there.

Prepare the USB drive (in FreeBSD)
----------------------------------

[This page](https://www.freebsd.org/doc/handbook/bsdinstall-pre.html) on
the FreeBSD website shows how to create a bootable USB drive for
installing FreeBSD. Use the *dd* on that page. You can also use the same
instructions with a NetBSD ISO image.

Prepare the USB drive (in LibertyBSD or NetBSD)
-----------------------------------------------

If you downloaded your ISO on a LibertyBSD or NetBSD system, here is how
to create the bootable NetBSD USB drive:

Connect the USB drive. Check dmesg:

    $ dmesg | tail

Check to confirm which drive it is, for example, if you think its sd3:

    $ disklabel sd3

Check that it wasn't automatically mounted. If it was, unmount it. For
example:

    $ doas umount /dev/sd3i

dmesg told you what device it is. Overwrite the drive, writing the
NetBSD installer to it with dd. For example:

    $ doas netbsd.iso of=/dev/rsdXc bs=1M; sync

You should now be able to boot the installer from your USB drive.
Continue reading, for information about how to do that.

Prepare the USB drive (in GNU+Linux)
------------------------------------

If you downloaded your ISO on a GNU+Linux system, here is how to create
the bootable NetBSD USB drive:

Connect the USB drive. Check dmesg:

    $ dmesg
Check lsblk to confirm which drive it is:

    $ lsblk

Check that it wasn't automatically mounted. If it was, unmount it. For
example:

    $ sudo umount /dev/sdX\*
    # umount /dev/sdX\*

dmesg told you what device it is. Overwrite the drive, writing your
distro ISO to it with dd. For example:

    $ sudo dd if=install60.fs of=/dev/sdX bs=8M; sync
    # dd if=netbsd.iso of=/dev/sdX bs=8M; sync

You should now be able to boot the installer from your USB drive.
Continue reading, for information about how to do that.

Installing NetBSD without full disk encryption
----------------------------------------------

You might have to use an external USB keyboard during the installation.
Press C to access the GRUB terminal.

    grub> knetbsd -r sd0a (usb0,netbsd1)/netbsd
    grub> boot

It will start booting into the NetBSD installer. Follow the normal
process for installing NetBSD.

Installing NetBSD with full disk encryption
-------------------------------------------

TODO

Booting
-------

Press C in GRUB to access the command line:

    grub> knetbsd -r wd0a (ahci0,netbsd1)/netbsd
    grub> boot

NetBSD will start booting. Yay!

Configuring Grub
----------------

If you don't want to drop to the GRUB command line and type in a
command to boot NetBSD every time, you can create a GRUB configuration
that's aware of your NetBSD installation and that will automatically be
used by libreboot.

On your NetBSD root partition, create the **/grub** directory and add
the file **libreboot\_grub.cfg** to it. Inside the
**libreboot\_grub.cfg** add these lines:

**default=0 timeout=3 menuentry "NetBSD" {\
    knetbsd -r wd0a (ahci0,netbsd1)/netbsd\
}\
**

The next time you boot, you'll see the old Grub menu for a few seconds,
then you'll see the a new menu with only NetBSD on the list. After 3
seconds NetBSD will boot, or you can hit enter to boot.

Troubleshooting
===============

Most of these issues occur when using libreboot with coreboot's 'text
mode' instead of the coreboot framebuffer. This mode is useful for
booting payloads like memtest86+ which expect text-mode, but for NetBSD
it can be problematic when they are trying to switch to a framebuffer
because it doesn't exist.

In most cases, you should use the vesafb ROM images. Example filename:
libreboot\_ukdvorak\_vesafb.rom.

won't boot\...something about file not found
---------------------------------------------

Your device names (i.e. usb0, usb1, sd0, sd1, wd0, ahci0, hd0, etc) and
numbers may differ. Use TAB completion.

Copyright © 2016 Leah Rowe <info@minifree.org>\
Copyright © 2016 Scott Bonds <scott@ggr.com>\
This page is available under the [CC BY SA 4.0](../cc-by-sa-4.0.txt)


code:
% How to install LibertyBSD or OpenBSD on a libreboot system

NOTE: This guide was written for OpenBSD by the person who contributed
it, but the libreboot project recommends LibertyBSD. LibertyBSD is a
version of OpenBSD without proprietary software in the repositories
(OpenBSD distributes firmware blobs for devices inside its kernel). Go
to the [LibertyBSD website](http://libertybsd.net/) -- TODO: test on
LibertyBSD and prioritise that in this guide.

This section relates to preparing, booting and installing OpenBSD on
your libreboot system, using nothing more than a USB flash drive (and
*dd*). They've only been tested on a Lenovo ThinkPad x200.

-   [Prepare the USB drive (in OpenBSD)](#prepare)
-   [Installing OpenBSD without full disk encryption](#noencryption)
-   [Installing OpenBSD with full disk encryption](#encryption)
-   [Booting](#booting)
-   [Configuring Grub](#configuring_grub)
-   [Troubleshooting](#troubleshooting)

**This section is only for the GRUB payload. For depthcharge (used on
CrOS devices in libreboot), instructions have yet to be written in the
libreboot documentation.**

install60.fs is the installation image for OpenBSD 6.0. Adapt the
filename accordingly, for a different OpenBSD version or LibertyBSD.

Prepare the USB drive (in LibertyBSD or OpenBSD)
------------------------------------------------

If you downloaded your ISO on a LibertyBSD or OpenBSD system, here is
how to create the bootable LibertyBSD/OpenBSD USB drive:

Connect the USB drive. Check dmesg:

    $ dmesg | tail

Check to confirm which drive it is, for example, if you think its sd3:

    $ disklabel sd3

Check that it wasn't automatically mounted. If it was, unmount it. For
example:

    $ doas umount /dev/sd3i

dmesg told you what device it is. Overwrite the drive, writing the
OpenBSD installer to it with dd. For example:

    $ doas dd if=install60.fs of=/dev/rsdXc bs=1M; sync

You should now be able to boot the installer from your USB drive.
Continue reading, for information about how to do that.

Prepare the USB drive (in NetBSD)
---------------------------------

[This
page](https://wiki.netbsd.org/tutorials/how_to_install_netbsd_from_an_usb_memory_stick/)
on the NetBSD website shows how to create a NetBSD bootable USB drive
from within NetBSD itself. You should use the *dd* method documented
there. This will also work with the OpenBSD image.

Prepare the USB drive (in FreeBSD)
----------------------------------

[This page](https://www.freebsd.org/doc/handbook/bsdinstall-pre.html) on
the FreeBSD website shows how to create a bootable USB drive for
installing FreeBSD. Use the *dd* on that page. You can also use the same
instructions with a OpenBSD ISO image.

Prepare the USB drive (in GNU+Linux)
------------------------------------

If you downloaded your ISO on a GNU+Linux system, here is how to create
the bootable OpenBSD USB drive:

Connect the USB drive. Check dmesg:

    $ dmesg
Check lsblk to confirm which drive it is:

    $ lsblk

Check that it wasn't automatically mounted. If it was, unmount it. For
example:

    $ sudo umount /dev/sdX\*
    # umount /dev/sdX\*

dmesg told you what device it is. Overwrite the drive, writing your
distro ISO to it with dd. For example:

    $ sudo dd if=install60.fs of=/dev/sdX bs=8M; sync
    # dd if=install60.fs of=/dev/sdX bs=8M; sync

You should now be able to boot the installer from your USB drive.
Continue reading, for information about how to do that.

Installing OpenBSD without full disk encryption
-----------------------------------------------

Press C in GRUB to access the command line:

    grub> kopenbsd (usb0,openbsd1)/6.0/amd64/bsd.rd
    grub> boot

It will start booting into the OpenBSD installer. Follow the normal
process for installing OpenBSD.

Installing OpenBSD with full disk encryption
--------------------------------------------

Not working. You can modify the above procedure (installation w/o
encryption) to install OpenBSD using full disk encryption, and it
appears to work, except that its not yet clear how to actually *boot* an
OpenBSD+FDE installation using libreboot+Grub2. If you get it working,
please let us know.

If booting in text mode (framebuffer mode might also work), it might be
possible to chainload the OpenBSD or LibertyBSD bootloader from the MBR
section on the internal storage device. This way, it would be possible
to boot with an encrypted OpenBSD or LibertyBSD installation. Please let
us know (contact details are on the libreboot homepage) if you get it
working this way.

Alternatively, it would be good to port OpenBSD either natively as a
coreboot payload, or port it to libpayload (payload library in coreboot;
it has a basic C library and a few functions for certain operations e.g.
text/bitmap). **This would be ideal, because then it would be possible
to boot a truly fully encrypted OpenBSD or LibertyBSD installation, by
putting everything in the flash chip.**

Alternatively, modifying GRUB to support booting fully encrypted OpenBSD
installations would be possible, but probably not feasible; it's an
alien codebase to the OpenBSD project, not tightly integrated and the
OpenBSD bootloader already works.

Booting
-------

Press C in GRUB to access the command line:

    grub> kopenbsd -r sd0a (ahci0,openbsd1)/bsd
    grub> boot

OpenBSD will start booting. Yay!

Configuring Grub
----------------

If you don't want to drop to the GRUB command line and type in a
command to boot OpenBSD every time, you can create a GRUB configuration
that's aware of your OpenBSD installation and that will automatically
be used by libreboot.

On your OpenBSD root partition, create the **/grub** directory and add
the file **libreboot\_grub.cfg** to it. Inside the
**libreboot\_grub.cfg** add these lines:

**default=0 timeout=3 menuentry "OpenBSD" {\
    kopenbsd -r sd0a (ahci0,openbsd1)/bsd\
}\
**

The next time you boot, you'll see the old Grub menu for a few seconds,
then you'll see the a new menu with only OpenBSD on the list. After 3
seconds OpenBSD will boot, or you can hit enter to boot.

Troubleshooting
===============

Most of these issues occur when using libreboot with coreboot's 'text
mode' instead of the coreboot framebuffer. This mode is useful for
booting payloads like memtest86+ which expect text-mode, but for OpenBSD
it can be problematic when they are trying to switch to a framebuffer
because it doesn't exist.

In most cases, you should use the vesafb ROM images. Example filename:
libreboot\_ukdvorak\_vesafb.rom.

won't boot\...something about file not found
---------------------------------------------

Your device names (i.e. usb0, usb1, sd0, sd1, wd0, ahci0, hd0, etc) and
numbers may differ. Use TAB completion.

Copyright © 2016 Scott Bonds <scott@ggr.com>\
Copyright © 2016 Leah Rowe <info@minifree.org>\
This page is available under the [CC BY SA 4.0](../cc-by-sa-4.0.txt)