diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/_GLShared.h |
initial commit
Diffstat (limited to 'src/_GLShared.h')
-rw-r--r-- | src/_GLShared.h | 338 |
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); +} |