#include #include #include #include #define STB_TRUETYPE_IMPLEMENTATION #include "stb_truetype.h" #include #include #include typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; typedef int8_t i8; typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; typedef u8 b8; typedef u16 b16; typedef u32 b32; typedef float f32; typedef double f64; #include "math.h" #define WIN_WIDTH 1280.0f #define WIN_HEIGHT 1024.0f #define internal static /** * Font rendering todos: * - Look into font-packing, what is that? why is it needed? * - Look into generating a single bitmap and then extracting characters from the * bitmap as needed * - SDF font rendering * */ internal i64 GlobalPerfCountFrequency; i8 PlatformDebugReadFile(char *FileName, void *FileBuffer, u32 MaxSize, LPDWORD BytesRead) { // @todo: refactor function, check for closehandle error codes HANDLE FileHandle = CreateFileA(FileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (FileHandle != INVALID_HANDLE_VALUE) { if (ReadFile(FileHandle, FileBuffer, MaxSize, BytesRead, NULL) != 0) { CloseHandle(FileHandle); return 1; } else { // @todo: logging CloseHandle(FileHandle); return -2; } } else { // @todo: logging return -1; } } void PlatformCopyMemory(void *NewMem, void *OldMem, size_t CopySize) { CopyMemory(NewMem, OldMem, CopySize); } void PlatformZeroMemory(void *Ptr, size_t Size) { ZeroMemory(Ptr, Size); } void framebuffer_size_callback(GLFWwindow *window, i32 width, i32 height) { glViewport(0, 0, width, height); } inline LARGE_INTEGER Win32GetWallClock(void) { LARGE_INTEGER Result; QueryPerformanceCounter(&Result); return Result; } inline f32 Win32GetSecondsElapsed(LARGE_INTEGER Start, LARGE_INTEGER End) { f32 Result = (f32)(End.QuadPart - Start.QuadPart)/(f32)GlobalPerfCountFrequency; return(Result); } #include "amr_memory.c" #include "gl_graphics.cpp" #include "game_main.h" typedef struct amr_glyph_info { u32 charset_start_index; u32 charset_end_index; } amr_glyph_info; u32 amr_GetGlyphIdFromCodepoint(amr_glyph_info glyph, u32 *GlyphIndexArray, u32 Codepoint) { u32 glyph_id = *(GlyphIndexArray + (Codepoint - glyph.charset_start_index)); return glyph_id; } int main() { // ==================== PROGRAM STATE INITIALISATION ===================== LARGE_INTEGER PerfCountFrequencyResult; QueryPerformanceFrequency(&PerfCountFrequencyResult); GlobalPerfCountFrequency = PerfCountFrequencyResult.QuadPart; // Note(talha): Set Windows scheduler granularity to 1ms // so that our sleep can be more granular UINT DesiredSchedulerMS = 1; b8 SleepIsGranular = (timeBeginPeriod(DesiredSchedulerMS) == TIMERR_NOERROR); // TODO(talha): how to use a library to query this i32 MonitorRefreshHz = 60; i32 GameUpdateHz = MonitorRefreshHz; f32 TargetSecondsPerFrame = 1.0f / (f32)GameUpdateHz; GameState State = {0}; State.Input.LastMouseX = WIN_WIDTH/2.0f; State.Input.LastMouseY = WIN_HEIGHT/2.0f; State.WinDimsPx.x = WIN_WIDTH; State.WinDimsPx.y = WIN_HEIGHT; // ================= GLFW WINDOW INITIALISATION ========================== glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); GLFWwindow *Window = glfwCreateWindow((i32)State.WinDimsPx.x, (i32)State.WinDimsPx.y, "LearnOpenGL", NULL, NULL); if (Window == NULL) { // todo(talha): add error logging for failed to create glfw window glfwTerminate(); printf("ERROR: Failed to create a glfw window\n"); return -1; } glfwMakeContextCurrent(Window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { // todo(talha): error logging - failed to initialize glad printf("ERROR: Failed to initialise glad\n"); return -1; } glViewport(0, 0, (GLsizei)State.WinDimsPx.x, (GLsizei)State.WinDimsPx.y); glfwSetFramebufferSizeCallback(Window, framebuffer_size_callback); //glfwSetInputMode(Window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); glfwSetCursorPos(Window, State.Input.LastMouseX, State.Input.LastMouseY); LARGE_INTEGER LastCounter = Win32GetWallClock(); u64 LastCycleCount = __rdtsc(); size_t PermanentStorageSize = MB((u64)500); void *PermanentStorage = VirtualAlloc(NULL, PermanentStorageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (PermanentStorage == NULL) { printf("ERROR: Failed to allocate game memory\n"); return -1; } State.Memory.PermanentStorage = PermanentStorage; State.Memory.PermanentStorageSize = PermanentStorageSize; amr_ArenaInit(&State.Memory.Arena, State.Memory.PermanentStorage, State.Memory.PermanentStorageSize); // ======================= SHADER PROGRAM LOADING ======== DWORD BytesRead = 0; u32 max_fsz = MB((u64)5); u8 *vs_file = (u8 *)amr_ArenaAlloc(&State.Memory.Arena, max_fsz); if (PlatformDebugReadFile("./code/shaders/text.vs.glsl", vs_file, max_fsz, &BytesRead) != 1) { printf("ERROR: failed to open text vertex shader"); return -1; } u8 *fs_file = (u8 *)amr_ArenaAlloc(&State.Memory.Arena, max_fsz); if (PlatformDebugReadFile("./code/shaders/text.fs.glsl", fs_file, max_fsz, &BytesRead) != 1) { printf("ERROR: failed to open text vertex shader"); return -1; } u32 vs = CreateVertexShader((char *)vs_file); u32 fs = CreateFragmentShader((char *)fs_file); u32 sp = CreateShaderProgram(vs, fs); amr_ArenaFreeAll(&State.Memory.Arena); // =================== FONT RENDERING =================== u32 font_fz = MB(2); u8 *ArialBuffer = (u8 *)amr_ArenaAlloc(&State.Memory.Arena, font_fz); // @todo: change to PlatformDebugFileRead fread(ArialBuffer, 1, font_fz, fopen("c:/windows/fonts/arial.ttf", "rb")); stbtt_fontinfo font_info; if (!stbtt_InitFont(&font_info, ArialBuffer, 0)) { printf("ERROR: failed to read arial font"); assert(1 == 0); } // get a SF (scale factor) f32 SF = stbtt_ScaleForPixelHeight(&font_info, 32.0f); // get vertical metrics i32 f_ascent, f_descent, f_line_gap; stbtt_GetFontVMetrics(&font_info, &f_ascent, &f_descent, &f_line_gap); // ========================= GLYPH AND FONT INFO STORAGE ================ amr_glyph_info latin_basic = {32, 126}; u32 glyph_count = latin_basic.charset_end_index - latin_basic.charset_start_index; u32 *GlyphIndexArray = (u32 *)amr_ArenaAlloc(&State.Memory.Arena, sizeof(glyph_count) * glyph_count); u32 *glyph_array_iter = GlyphIndexArray; for (u32 i = 0; i < glyph_count; i++) { *glyph_array_iter = stbtt_FindGlyphIndex(&font_info, latin_basic.charset_start_index + i); glyph_array_iter++; } const char *text = "Hello this is a\npiece of test text \nor and more on a newline."; u8 char_sz = 61; u32 fd_sz = sizeof(debug_font_details)*char_sz; debug_font_details *fd_arr = (debug_font_details *)amr_ArenaAlloc(&State.Memory.Arena, fd_sz); debug_font_details *_fi = fd_arr; f32 lx = 0.0f, max_lx = 0.0f; i32 baseline = (i32)roundf(f_ascent * SF); // convert str to font for (i32 i = 0; i < char_sz; ++i) { _fi->lx = lx; _fi->baseline = baseline; if (text[i] == '\n') { // if new line, move baseline down baseline += (i32)roundf((f_ascent - f_descent + f_line_gap) * SF); lx = 0; ++_fi; continue; } stbtt_GetGlyphHMetrics(&font_info, amr_GetGlyphIdFromCodepoint(latin_basic, GlyphIndexArray, text[i]), &_fi->advance, &_fi->lsb); stbtt_GetGlyphBitmapBox(&font_info, amr_GetGlyphIdFromCodepoint(latin_basic, GlyphIndexArray, text[i]), SF, SF, &_fi->x0, &_fi->y0, &_fi->x1, &_fi->y1); _fi->ly = (f32)(_fi->baseline + _fi->y0); lx += (_fi->advance * SF); i32 kern; if (i < char_sz - 1 && text[i+1] != '\n') { kern = stbtt_GetGlyphKernAdvance(&font_info, amr_GetGlyphIdFromCodepoint(latin_basic, GlyphIndexArray, text[i]), amr_GetGlyphIdFromCodepoint(latin_basic, GlyphIndexArray, text[i+1])); lx += roundf(kern * SF); } if (lx > max_lx) { max_lx = lx; } ++_fi; } // make all the glyphs into bitmaps. _fi = fd_arr; u32 bitmap_width_from_text_info = (u32)max_lx; u32 bitmap_height_from_text_info = (u32)(baseline - roundf(SF * f_descent)); i32 bmp_sz = sizeof(u8)*bitmap_width_from_text_info*bitmap_height_from_text_info; u8 *Bitmap = (u8 *)amr_ArenaAlloc(&State.Memory.Arena, bmp_sz); _fi = fd_arr; for (int i=0; ibyte_offset = (i32)(_fi->lx + roundf(_fi->lsb * SF) + (_fi->ly * bitmap_width_from_text_info)) ; // store image data in bitmap stbtt_MakeGlyphBitmap(&font_info, Bitmap + _fi->byte_offset, _fi->x1 - _fi->x0, _fi->y1 - _fi->y0, bitmap_width_from_text_info, SF, SF, amr_GetGlyphIdFromCodepoint(latin_basic, GlyphIndexArray, text[i])); _fi++; } // ---------- prepare bitmap texture and buffer objects ----------- glPixelStorei(GL_UNPACK_ALIGNMENT, 1); f32 bmp_vertices[] = { // positions // texture coords 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,// top right 1.0f, -1.0f, 0.0f, 1.0f, 0.0f,// bottom right -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,// bottom left -1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left }; u32 bmp_vsz = sizeof(bmp_vertices); u32 bmp_indices[] = { 0, 1, 3, 1, 2, 3 }; u32 bmp_isz = sizeof(bmp_indices); BufferO bo = CreateRectangleTextured(bmp_vertices, bmp_vsz, bmp_indices, bmp_isz, ATTR_TEX); Texture2D _tex = {0}; { _tex.width = bitmap_width_from_text_info; _tex.height = bitmap_height_from_text_info; _tex.data = Bitmap; glBindVertexArray(bo.VAO); glGenTextures(1, &bo.TexO); glBindTexture(GL_TEXTURE_2D, bo.TexO); // defining texture wrapping options glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, _tex.width, _tex.height, 0, GL_RED, GL_UNSIGNED_BYTE, _tex.data); glBindVertexArray(0); } // =============== CAMERA SETUP ================== State.Camera.Pos = {0}; State.Camera.Front = {0}; State.Camera.Front.z = -1.0f; State.Camera.Up = {0}; State.Camera.Up.y = 1.0f; State.Camera.MoveSpeed = 0.1f; State.Input.Sensitivity = 0.1f; // ======= SCREEN POSITIONING AND OBJECT SCALING SETUP ======== // recalculated each time resolution changes // store in state Vec2 pixel_span; pixel_span.x = State.WinDimsPx.x*2.0f/State.WinDimsPx.y; pixel_span.y = State.WinDimsPx.y*2.0f/State.WinDimsPx.x; Vec2 pixel_ratio; pixel_ratio.x = pixel_span.x/State.WinDimsPx.x; pixel_ratio.y = pixel_span.y/State.WinDimsPx.y; State.px_ratio = pixel_ratio; Vec2 win_dims; win_dims = State.px_ratio * State.WinDimsPx; // calculate screen mid point in view pixels and world position // @note: calculated each time player moves Vec2 ScreenMidCoords = {State.Camera.Pos.x, State.Camera.Pos.y}; Vec2 ScreenMidPx = State.WinDimsPx/2.0f; // calculate corner points of the field: // tl -> top left // br -> bot right State.ScreenCoords.tl = ScreenMidCoords - ScreenMidPx; State.ScreenCoords.br = ScreenMidCoords + ScreenMidPx; // ====================== TEXT POSITIONING AND SCALING ====== Vec2 text_dims = {((f32)bitmap_width_from_text_info)/2.0f, ((f32)bitmap_height_from_text_info)/2.0f}; Vec2 text_scaled_dims = State.px_ratio * text_dims; Vec2 text_scaled_mid = text_scaled_dims/2.0f; Vec2 text_pos = {0.0f, 0.0f}; Vec2 text_offset = {text_dims.x/2.0f, (f32)fd_arr->baseline}; text_pos = text_offset + text_pos; Vec2 text_scaled_pos = State.px_ratio*(State.ScreenCoords.tl + text_pos) + text_scaled_mid; // ===================== TEXTURE BINDING =================== glUseProgram(sp); glBindVertexArray(bo.VAO); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, bo.TexO); LoadUniformInt(sp, "tex", 0); LoadUniformVec3(sp, "TextColor", InitVec3(1.0f, 1.0f, 1.0f)); while (!glfwWindowShouldClose(Window)) { LARGE_INTEGER WorkCounter = Win32GetWallClock(); f32 WorkSecondsElapsed = Win32GetSecondsElapsed(LastCounter, WorkCounter); f32 SecondsElapsedForFrame = WorkSecondsElapsed; // @note: this caps the framerate // also prevents, for now, incorrect speeds based on timings // @todo: fix framerate capping and speed timings being affected by framerate if(SecondsElapsedForFrame < TargetSecondsPerFrame){ while(SecondsElapsedForFrame < TargetSecondsPerFrame) { if (SleepIsGranular) { DWORD SleepMS = (DWORD)(1000.0f * (TargetSecondsPerFrame - SecondsElapsedForFrame)); if (SleepMS > 0) { Sleep(SleepMS); } } SecondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter, Win32GetWallClock()); } } else { // TODO(talha): Missed frame rate // TODO(talha): Logging } glfwPollEvents(); // HandleInputs if (glfwGetKey(Window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { glfwSetWindowShouldClose(Window, 1); } if (glfwGetKey(Window, GLFW_KEY_W) == GLFW_PRESS) { State.Camera.Pos = State.Camera.Pos + (State.Camera.Front * State.Camera.MoveSpeed); } if (glfwGetKey(Window, GLFW_KEY_S) == GLFW_PRESS) { State.Camera.Pos = State.Camera.Pos + ((State.Camera.Front * State.Camera.MoveSpeed) * -1.0f); } if (glfwGetKey(Window, GLFW_KEY_A) == GLFW_PRESS) { Vec3 HorizontalVec = (UnitVec3(CrossProductVec3(State.Camera.Front, State.Camera.Up)) * State.Camera.MoveSpeed); State.Camera.Pos = (State.Camera.Pos + (HorizontalVec * -1.0f)); } if (glfwGetKey(Window, GLFW_KEY_D) == GLFW_PRESS) { Vec3 HorizontalVec = (UnitVec3(CrossProductVec3(State.Camera.Front, State.Camera.Up)) * State.Camera.MoveSpeed); State.Camera.Pos = State.Camera.Pos + HorizontalVec; } Mat4 LookAt = CreateLookAtMat4(State.Camera.Pos, State.Camera.Pos + State.Camera.Front, State.Camera.Up); Mat4 View = LookAt; // projection matrix // far value is the max z-index value. Will be useful for layering textures Mat4 Projection = CreateOrthographicWithRatio(WIN_WIDTH, WIN_HEIGHT, -5.0f, 5.0f); const f32 view[16] = {View.x0, View.x1, View.x2, View.x3, View.y0, View.y1, View.y2, View.y3, View.z0, View.z1, View.z2, View.z3, View.w0, View.w1, View.w2, View.w3}; const f32 projection[16] = {Projection.x0, Projection.x1, Projection.x2, Projection.x3, Projection.y0, Projection.y1, Projection.y2, Projection.y3, Projection.z0, Projection.z1, Projection.z2, Projection.z3, Projection.w0, Projection.w1, Projection.w2, Projection.w3}; LoadUniformMat4(sp, "View", view); LoadUniformMat4(sp, "Projection", projection); glEnable(GL_DEPTH_TEST); // glEnable(GL_BLEND); // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // ============== DRAW WOOD CONTAINER =============== Mat4 Model = IdentityMat(); #if 0 Mat4 Scale1 = CreateScaleMat(InitVec4(container_dims.x, container_dims.y, 1.0f, 0)); Model = Mul_Mat4Mat4(Scale1, Model); Mat4 Tx1 = CreateTranslationMat(InitVec4(container_pos.x, container_pos.y, -1.0f, 0)); Model = Mul_Mat4Mat4(Tx1, Model); const f32 container_model[16] = {Model.x0, Model.x1, Model.x2, Model.x3, Model.y0, Model.y1, Model.y2, Model.y3, Model.z0, Model.z1, Model.z2, Model.z3, Model.w0, Model.w1, Model.w2, Model.w3}; LoadUniformMat4(sp, "Model", container_model); DrawRectangleTextured(bo.VAO, Textures[0]); // ============ DRAW FIELD ====================== Model = IdentityMat(); Mat4 Scale2 = CreateScaleMat(InitVec4(field_dims.x, field_dims.y, 1.0f, 0)); Mat4 Tx = CreateTranslationMat(InitVec4(0, 0, -2.0f, 0)); Model = Mul_Mat4Mat4(Scale2, Model); Model = Mul_Mat4Mat4(Tx, Model); const f32 field_model[16] = {Model.x0, Model.x1, Model.x2, Model.x3, Model.y0, Model.y1, Model.y2, Model.y3, Model.z0, Model.z1, Model.z2, Model.z3, Model.w0, Model.w1, Model.w2, Model.w3}; LoadUniformMat4(sp, "Model", field_model); DrawRectangleTextured(bo.VAO, Textures[1]); #endif // =============== DRAW TEXT BITMAP RENDERING =============== { Model = IdentityMat(); Mat4 Scale = CreateScaleMat(InitVec4(text_scaled_dims.x, text_scaled_dims.y, 1, 0)); Model = Mul_Mat4Mat4(Scale, Model); Mat4 Tx = CreateTranslationMat(InitVec4(text_scaled_pos.x, text_scaled_pos.y, 0, 0)); Model = Mul_Mat4Mat4(Tx, Model); const f32 text_model[16] = {Model.x0, Model.x1, Model.x2, Model.x3, Model.y0, Model.y1, Model.y2, Model.y3, Model.z0, Model.z1, Model.z2, Model.z3, Model.w0, Model.w1, Model.w2, Model.w3}; LoadUniformMat4(sp, "Model", text_model); DrawRectangleTextured(bo.VAO, bo.TexO); } glfwSwapBuffers(Window); // TODO(talha): should these be cleared LastCounter = Win32GetWallClock(); LastCycleCount = __rdtsc(); }; VirtualFree(PermanentStorage, 0, MEM_RELEASE); glfwTerminate(); return 0; }