summary refs log tree commit diff
path: root/src/_GLShared.h
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/_GLShared.h
initial commit
Diffstat (limited to 'src/_GLShared.h')
-rw-r--r--src/_GLShared.h338
1 files changed, 338 insertions, 0 deletions
diff --git a/src/_GLShared.h b/src/_GLShared.h
new file mode 100644
index 0000000..342fed1
--- /dev/null
+++ b/src/_GLShared.h
@@ -0,0 +1,338 @@
+#define _GL_TEXTURE_MAX_LEVEL        0x813D
+#define _GL_BGRA_EXT                 0x80E1
+#define _GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
+
+#if defined CC_BUILD_WEB || defined CC_BUILD_ANDROID
+#define PIXEL_FORMAT GL_RGBA
+#else
+#define PIXEL_FORMAT _GL_BGRA_EXT
+#endif
+
+#if defined CC_BIG_ENDIAN
+/* Pixels are stored in memory as A,R,G,B but GL_UNSIGNED_BYTE will interpret as B,G,R,A */
+/* So use GL_UNSIGNED_INT_8_8_8_8_REV instead to remedy this */
+#define TRANSFER_FORMAT _GL_UNSIGNED_INT_8_8_8_8_REV
+#else
+/* Pixels are stored in memory as B,G,R,A and GL_UNSIGNED_BYTE will interpret as B,G,R,A */
+/* So fine to just use GL_UNSIGNED_BYTE here */
+#define TRANSFER_FORMAT GL_UNSIGNED_BYTE
+#endif
+
+#define uint_to_ptr(raw) ((void*)((cc_uintptr)(raw)))
+#define ptr_to_uint(raw) ((GLuint)((cc_uintptr)(raw)))
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------General---------------------------------------------------------*
+*#########################################################################################################################*/
+static void GL_UpdateVsync(void) {
+	GLContext_SetFpsLimit(gfx_vsync, gfx_minFrameMs);
+}
+
+static void GLBackend_Init(void);
+void Gfx_Create(void) {
+	GLContext_Create();
+	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &Gfx.MaxTexWidth);
+	Gfx.MaxTexHeight = Gfx.MaxTexWidth;
+	Gfx.Created      = true;
+	/* necessary for android which "loses" context when window is closed */
+	Gfx.LostContext  = false;
+
+	GLBackend_Init();
+	Gfx_RestoreState();
+	GL_UpdateVsync();
+}
+
+cc_bool Gfx_TryRestoreContext(void) {
+	return GLContext_TryRestore();
+}
+
+void Gfx_Free(void) {
+	Gfx_FreeState();
+	GLContext_Free();
+}
+
+#define gl_Toggle(cap) if (enabled) { glEnable(cap); } else { glDisable(cap); }
+static void* tmpData;
+static int tmpSize;
+
+static void* FastAllocTempMem(int size) {
+	if (size > tmpSize) {
+		Mem_Free(tmpData);
+		tmpData = Mem_Alloc(size, 1, "Gfx_AllocTempMemory");
+	}
+
+	tmpSize = size;
+	return tmpData;
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Textures--------------------------------------------------------*
+*#########################################################################################################################*/
+static void Gfx_DoMipmaps(int x, int y, struct Bitmap* bmp, int rowWidth, cc_bool partial) {
+	BitmapCol* prev = bmp->scan0;
+	BitmapCol* cur;
+
+	int lvls = CalcMipmapsLevels(bmp->width, bmp->height);
+	int lvl, width = bmp->width, height = bmp->height;
+
+	for (lvl = 1; lvl <= lvls; lvl++) {
+		x /= 2; y /= 2;
+		if (width > 1)  width /= 2;
+		if (height > 1) height /= 2;
+
+		cur = (BitmapCol*)Mem_Alloc(width * height, 4, "mipmaps");
+		GenMipmaps(width, height, cur, prev, rowWidth);
+
+		if (partial) {
+			_glTexSubImage2D(GL_TEXTURE_2D, lvl, x, y, width, height, PIXEL_FORMAT, TRANSFER_FORMAT, cur);
+		} else {
+			_glTexImage2D(GL_TEXTURE_2D, lvl, GL_RGBA, width, height, 0, PIXEL_FORMAT, TRANSFER_FORMAT, cur);
+		}
+
+		if (prev != bmp->scan0) Mem_Free(prev);
+		prev    = cur;
+		rowWidth = width;
+	}
+	if (prev != bmp->scan0) Mem_Free(prev);
+}
+
+/* TODO: Use GL_UNPACK_ROW_LENGTH for Desktop OpenGL instead */
+#define UPDATE_FAST_SIZE (64 * 64)
+static CC_NOINLINE void UpdateTextureSlow(int x, int y, struct Bitmap* part, int rowWidth, cc_bool full) {
+	BitmapCol buffer[UPDATE_FAST_SIZE];
+	void* ptr = (void*)buffer;
+	int count = part->width * part->height;
+
+	/* cannot allocate memory on the stack for very big updates */
+	if (count > UPDATE_FAST_SIZE) {
+		ptr = Mem_Alloc(count, 4, "Gfx_UpdateTexture temp");
+	}
+
+	CopyTextureData(ptr, part->width << 2, part, rowWidth << 2);
+
+	if (full) {
+		_glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, part->width, part->height, 0, PIXEL_FORMAT, TRANSFER_FORMAT, ptr);
+	} else {
+		_glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, part->width, part->height, PIXEL_FORMAT, TRANSFER_FORMAT, ptr);
+	}
+	if (count > UPDATE_FAST_SIZE) Mem_Free(ptr);
+}
+
+static GfxResourceID Gfx_AllocTexture(struct Bitmap* bmp, int rowWidth, cc_uint8 flags, cc_bool mipmaps) {
+	GfxResourceID texId = NULL;
+	_glGenTextures(1, (GLuint*)&texId);
+	_glBindTexture(GL_TEXTURE_2D, ptr_to_uint(texId));
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+	if (mipmaps) {
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
+		if (customMipmapsLevels) {
+			int lvls = CalcMipmapsLevels(bmp->width, bmp->height);
+			glTexParameteri(GL_TEXTURE_2D, _GL_TEXTURE_MAX_LEVEL, lvls);
+		}
+	} else {
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	}
+
+	if (bmp->width == rowWidth) {
+		_glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bmp->width, bmp->height, 0, PIXEL_FORMAT, TRANSFER_FORMAT, bmp->scan0);
+	} else {
+		UpdateTextureSlow(0, 0, bmp, rowWidth, true);
+	}
+
+	if (mipmaps) Gfx_DoMipmaps(0, 0, bmp, rowWidth, false);
+	return texId;
+}
+
+void Gfx_UpdateTexture(GfxResourceID texId, int x, int y, struct Bitmap* part, int rowWidth, cc_bool mipmaps) {
+	_glBindTexture(GL_TEXTURE_2D, ptr_to_uint(texId));
+
+	if (part->width == rowWidth) {
+		_glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, part->width, part->height, PIXEL_FORMAT, TRANSFER_FORMAT, part->scan0);
+	} else {
+		UpdateTextureSlow(x, y, part, rowWidth, false);
+	}
+
+	if (mipmaps) Gfx_DoMipmaps(x, y, part, rowWidth, true);
+}
+
+void Gfx_DeleteTexture(GfxResourceID* texId) {
+	GLuint id = ptr_to_uint(*texId);
+	if (id) _glDeleteTextures(1, &id);
+	*texId = 0;
+}
+
+void Gfx_EnableMipmaps(void) { }
+void Gfx_DisableMipmaps(void) { }
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------State management----------------------------------------------------*
+*#########################################################################################################################*/
+static PackedCol gfx_clearColor;
+void Gfx_SetFaceCulling(cc_bool enabled)   { gl_Toggle(GL_CULL_FACE); }
+static void SetAlphaBlend(cc_bool enabled) { gl_Toggle(GL_BLEND); }
+void Gfx_SetAlphaArgBlend(cc_bool enabled) { }
+
+static void GL_ClearColor(PackedCol color) {
+	glClearColor(PackedCol_R(color) / 255.0f, PackedCol_G(color) / 255.0f,
+				 PackedCol_B(color) / 255.0f, PackedCol_A(color) / 255.0f);
+}
+void Gfx_ClearColor(PackedCol color) {
+	if (color == gfx_clearColor) return;
+	GL_ClearColor(color);
+	gfx_clearColor = color;
+}
+
+static void SetColorWrite(cc_bool r, cc_bool g, cc_bool b, cc_bool a) {
+	glColorMask(r, g, b, a);
+}
+
+void Gfx_SetDepthWrite(cc_bool enabled) { glDepthMask(enabled); }
+void Gfx_SetDepthTest(cc_bool enabled) { gl_Toggle(GL_DEPTH_TEST); }
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Matrices--------------------------------------------------------*
+*#########################################################################################################################*/
+void Gfx_CalcOrthoMatrix(struct Matrix* matrix, float width, float height, float zNear, float zFar) {
+	/* Transposed, source https://learn.microsoft.com/en-us/windows/win32/opengl/glortho */
+	/*   The simplified calculation below uses: L = 0, R = width, T = 0, B = height */
+	*matrix = Matrix_Identity;
+
+	matrix->row1.x =  2.0f / width;
+	matrix->row2.y = -2.0f / height;
+	matrix->row3.z = -2.0f / (zFar - zNear);
+
+	matrix->row4.x = -1.0f;
+	matrix->row4.y =  1.0f;
+	matrix->row4.z = -(zFar + zNear) / (zFar - zNear);
+}
+
+static float Cotangent(float x) { return Math_CosF(x) / Math_SinF(x); }
+void Gfx_CalcPerspectiveMatrix(struct Matrix* matrix, float fov, float aspect, float zFar) {
+	float zNear = 0.1f;
+	float c = Cotangent(0.5f * fov);
+
+	/* Transposed, source https://learn.microsoft.com/en-us/windows/win32/opengl/glfrustum */
+	/* For a FOV based perspective matrix, left/right/top/bottom are calculated as: */
+	/*   left = -c * aspect, right = c * aspect, bottom = -c, top = c */
+	/* Calculations are simplified because of left/right and top/bottom symmetry */
+	*matrix = Matrix_Identity;
+
+	matrix->row1.x =  c / aspect;
+	matrix->row2.y =  c;
+	matrix->row3.z = -(zFar + zNear) / (zFar - zNear);
+	matrix->row3.w = -1.0f;
+	matrix->row4.z = -(2.0f * zFar * zNear) / (zFar - zNear);
+	matrix->row4.w =  0.0f;
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------------Misc----------------------------------------------------------*
+*#########################################################################################################################*/
+static BitmapCol* GL_GetRow(struct Bitmap* bmp, int y, void* ctx) { 
+	/* OpenGL stores bitmap in bottom-up order, so flip order when saving */
+	return Bitmap_GetRow(bmp, (bmp->height - 1) - y); 
+}
+cc_result Gfx_TakeScreenshot(struct Stream* output) {
+	struct Bitmap bmp;
+	cc_result res;
+	GLint vp[4];
+	
+	glGetIntegerv(GL_VIEWPORT, vp); /* { x, y, width, height } */
+	bmp.width  = vp[2]; 
+	bmp.height = vp[3];
+
+	bmp.scan0  = (BitmapCol*)Mem_TryAlloc(bmp.width * bmp.height, 4);
+	if (!bmp.scan0) return ERR_OUT_OF_MEMORY;
+	glReadPixels(0, 0, bmp.width, bmp.height, PIXEL_FORMAT, TRANSFER_FORMAT, bmp.scan0);
+
+	res = Png_Encode(&bmp, output, GL_GetRow, false, NULL);
+	Mem_Free(bmp.scan0);
+	return res;
+}
+
+static void AppendVRAMStats(cc_string* info) {
+	static const cc_string memExt = String_FromConst("GL_NVX_gpu_memory_info");
+	GLint totalKb, curKb;
+	float total, cur;
+
+	/* NOTE: glGetString returns UTF8, but I just treat it as code page 437 */
+	cc_string exts = String_FromReadonly((const char*)glGetString(GL_EXTENSIONS));
+	if (!String_CaselessContains(&exts, &memExt)) return;
+
+	glGetIntegerv(0x9048, &totalKb);
+	glGetIntegerv(0x9049, &curKb);
+	if (totalKb <= 0 || curKb <= 0) return;
+
+	total = totalKb / 1024.0f; cur = curKb / 1024.0f;
+	String_Format2(info, "Video memory: %f2 MB total, %f2 free\n", &total, &cur);
+}
+
+void Gfx_GetApiInfo(cc_string* info) {
+	GLint depthBits = 0;
+	int pointerSize = sizeof(void*) * 8;
+
+	glGetIntegerv(GL_DEPTH_BITS, &depthBits);
+#if CC_GFX_BACKEND == CC_GFX_BACKEND_GL2
+	String_Format1(info, "-- Using OpenGL Modern (%i bit) --\n", &pointerSize);
+#else
+	String_Format1(info, "-- Using OpenGL (%i bit) --\n", &pointerSize);
+#endif
+	String_Format1(info, "Vendor: %c\n",     glGetString(GL_VENDOR));
+	String_Format1(info, "Renderer: %c\n",   glGetString(GL_RENDERER));
+	String_Format1(info, "GL version: %c\n", glGetString(GL_VERSION));
+	AppendVRAMStats(info);
+	PrintMaxTextureInfo(info);
+	String_Format1(info, "Depth buffer bits: %i\n",      &depthBits);
+	GLContext_GetApiInfo(info);
+}
+
+void Gfx_SetFpsLimit(cc_bool vsync, float minFrameMs) {
+	gfx_minFrameMs = minFrameMs;
+	gfx_vsync      = vsync;
+	if (Gfx.Created) GL_UpdateVsync();
+}
+
+void Gfx_BeginFrame(void) { }
+void Gfx_ClearBuffers(GfxBuffers buffers) {
+	int targets = 0;
+	if (buffers & GFX_BUFFER_COLOR) targets |= GL_COLOR_BUFFER_BIT;
+	if (buffers & GFX_BUFFER_DEPTH) targets |= GL_DEPTH_BUFFER_BIT;
+	
+	glClear(targets); 
+}
+
+void Gfx_EndFrame(void) {
+#if CC_GFX_BACKEND == CC_GFX_BACKEND_GL1
+	if (Window_IsObscured()) {
+		TickReducedPerformance();
+	} else {
+		EndReducedPerformance();
+	}
+#endif
+	/* TODO always run ?? */
+
+	if (!GLContext_SwapBuffers()) Gfx_LoseContext("GLContext lost");
+	if (gfx_minFrameMs) LimitFPS();
+}
+
+void Gfx_OnWindowResize(void) {
+	Gfx_SetViewport(0, 0, Game.Width, Game.Height);
+	/* With cocoa backend, in some cases [NSOpenGLContext update] will actually */
+	/*  call glViewport with the size of the window framebuffer */
+	/*  https://github.com/glfw/glfw/issues/80 */
+	/* Normally this doesn't matter, but it does when game is compiled against recent */
+	/*  macOS SDK *and* the display is a high DPI display - where glViewport(width, height) */
+	/*  above would otherwise result in game rendering to only 1/4 of the screen */
+	/*  https://github.com/ClassiCube/ClassiCube/issues/888 */
+	GLContext_Update();
+}
+
+void Gfx_SetViewport(int x, int y, int w, int h) {
+	glViewport(x, y, w, h);
+}