/agdg/ - Amateur Game Development General

Just Like Make Game

(You probably don't need to) SAVE THIS FILE (any more): Anon.cafe Fallback File v1.1 (updated 2021-12-13)

Anon.cafe will shut down as of 00:00 UTC on 15 March 2024. Announcement here.

Max message length: 20000

Drag files to upload or
click here to select them

Maximum 5 files / Maximum size: 20.00 MB

Board Rules

(used to delete files and postings)

Visual Waifu OpenGL engine from scratch Deveropa 05/18/2020 (Mon) 07:05:38 No.170
Hey there /agdg/. So I did some work learning the basics of OpenGL written by hand hoping to create a waifu simulator project. I'd kind of like to pick back up the project and figured if I posted the (far too many) details about the process I've gone through so far, it might spur some additional interest, and also maybe help other anons learn about the process of creating a game-engine-like-thing from scratch too. I also have a lot of unanswered questions still, so maybe someone here can help me get past them. I used the examples and tutorials from learnopengl. https://learnopengl.com/
I'm writing this project entirely in C++ thus far, and generally wrapping the OpenGL calls to some extent as I figure an approach I'm comfortable with. Here's the repo for the project: https://gitlab.com/Chobitsu/muh-robowaifu-simulator Here's a cap of the help display thus far. > As you can see, I'm getting 60fps with it. It's very simple scene thus far. OTOH, I'm on a little atom processor box w/ integrated graphics so yea, pretty low-end hardware. One of the important reasons I needed to resort to writing this by hand in straight OpenGL heh.
Here are two relevant code repos: The learnopengl one itself. https://github.com/JoeyDeVries/LearnOpenGL The glitter project, that makes getting all the dependencies together pretty easy. Probably should start here actually, Anon. https://github.com/Polytonic/Glitter
I guess one of the first things to address is abstracting functionality. As is commonplace with C coders who later pick up C++, de Vries creates run-on functions incorporating copious functionality all within the same scope, with not a lot of focus on either code-reasoning, composability or maintenance. (Not that's he's all that bad at it actually, at the least he does break some code out into separated functions. Heh, I've seen C software that literally has the entire program--thousands of lines--all inside main()!.) I'm not really sure why C devs tend to do this, since sub-routines were a thing literally at the very beginning of computer science (eg, Edsac), and sub-functions were stressed all the way back in Algol60, as well as K&R. So the very first order of business seems to me to be breaking down a very simple interface to the entire system inside the program's entry point. This is a general approach I follow that makes code not only simpler to compose (possibly in new ways), but also far easier to reason about in general. Basically 3 simple sections are used here: prelims, game-loop, and cleanup. >mrs_main.cpp int main() { //------------------- // Prelims // // -Anon, try this config: //----------------------------- const unsigned w{1024}, h{576}; mrs::init_win(w, h, "Muh Robowaifu Simulator"); // // -or, try this one if you have the hardware: // (comment one section out/uncomment the other in fact ofc :^) //----------------------------- // const unsigned w{1920}, h{1080}; // mrs::init_win(w, h, "Muh Robowaifu Simulator", 2); // Prepare the scene for rendering mrs::init_scene(); cout << "Begin game loop..." << std::endl; const auto start_time{glfwGetTime()}; //------------------- // Game Loop // while (!glfwWindowShouldClose(mwin)) { glfwPollEvents(); if (pause::paused) // pause sim mrs::do_pause(); // else { // run sim normally mrs::time_n_draw(); glfwSwapBuffers(mwin); } } //------------------- // Wrapups // const auto end_time{glfwGetTime()}; cout << "Clean exit from game loop\n"; mrs::clean_up(); mrs::prt_frm_stats(start_time, end_time); }
>>175 Ignoring the comments and start/end time capture this all boils down to just these things: Two statements to initialize the system: -init_win(w, h, "Muh Robowaifu Simulator") -init_scene() One loop statement: -while (!glfwWindowShouldClose(mwin)) The loop contains two possible paths, paused: -do_pause() or unpaused: -time_n_draw() -glfwSwapBuffers(mwin) Finally, cleanup & frame stats reporting. -clean_up() -prt_frm_stats(start_time, end_time) I personally find this approach far easier to begin to get my head around what's going on than just jamming all.the.things. into one very long main() function.
The statements to sort of initialize the yuge OpenGL state-machine are kind of scattered around in the various examples but I tried to consolidate them together to some degree. >muh_mrs.cpp snippet // Initialize the GLFW window and OpenGL for use // -windowed (by default), or selectable fullscreen modes supported. void init_win(const int w, const int h, const char* title, const unsigned fs_targ_mon = 0) { cout << "Obtaining OpenGL 3.3 core context" << std::endl; // Init GLFW if (!glfwInit()) { cerr << "\nERR: glfw: failed to init OGL/GLFW\n"; exit(-1); } // glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Create a GLFW window in either windowed or fullscreen mode //-------------- // if (!fs_targ_mon) // Create in windowed mode mwin = glfwCreateWindow(w, h, title, nullptr, nullptr); else { // Create in fullscreen mode, using the supplied monitor number int mon_cnt{}; auto monitors{glfwGetMonitors(&mon_cnt)}; if (static_cast<int>(fs_targ_mon) > mon_cnt) { cerr << "\nERR: glfw: selected full-screen monitor #" << fs_targ_mon << " not available\n"; exit(-1); } mwin = glfwCreateWindow(w, h, title, monitors[fs_targ_mon - 1], nullptr); } // if (!mwin) { cerr << "\nERR: glfw: failed to create GLFW window\n"; glfwTerminate(); exit(-1); } // glfwMakeContextCurrent(mwin); init_system(); } This is basically what I use to get the OpenGL context up and displayed on an GLFW window.
>>179 Beyond the basic GLFW windows itself, I tweak OpenGL settings to suit the system I'm creating. // Additional system setup beyond the basic window void init_system() { // test // cout << "vulkan supported: " << glfwVulkanSupported() << '\n'; // GLAD: load all OpenGL function pointers if (!gladLoadGLLoader(GLADloadproc(glfwGetProcAddress))) { cerr << "\nERR: glad: failed to get the OGL context process address\n"; glfwDestroyWindow(mwin); glfwTerminate(); exit(-1); } // Config global OpenGL state glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Set system callbacks glfwSetFramebufferSizeCallback(mwin, frmbuff_rsz_cb); glfwSetCursorPosCallback(mwin, mouse_cb); // glfwSetMouseButtonCallback(mwin, mouse_btn_cb); glfwSetKeyCallback(mwin, keyboard_cb); // MRS captures the mouse cursor by default glfwSetInputMode(mwin, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // Ensure the OpenGL viewport matches the actual system-assigned window // dimensions glfwGetFramebufferSize(mwin, &msys::mwin_w, &msys::mwin_h); glViewport(0, 0, msys::mwin_w, msys::mwin_h); glfwSwapInterval(1); // A value of '1' should prevent screen tearing }
I also want to initialize my own system to prepare for rendering as well. I put those call-chains inside a wrapper. >muh_mrs.cpp snippet // Wrapper to initialize the scene elements void init_scene() { // Set the global scene camera's fixed-projection frustum msys::proj = glm::perspective( 0.785398f, // 45deg fov static_cast<float>(msys::mwin_w) / static_cast<float>(msys::mwin_h), 0.1f, 100.0f); // Setup the training pavilion gym init_gym(); // Setup the toys in the gym init_toys(); // Setup the furniture in the gym // init_furniture(); // Setup the text system //-------------- // init_text_posns(msys::mwin_w, msys::mwin_h); // NOTE: set this first // // TODO: Support generating/storing multiple font glyph textures/Text_char's init_text(48, "Roboto-Bold"); // Any valid TTF font probably works. (NOTE: // the font file itself is expected to be // inside the project's 'fonts' subdirectory) init_help_posns(msys::mwin_w, msys::mwin_h); // Init runtime var mtime::prev_sec = glfwGetTime(); } This code call a number of other wrappers in turn like ones to setup the 'gym' and toys. Once this function returns, we're all set back at main(), and ready to begin the game loop itself.
The conditional for the gameloop is a flag that GLFW provides: glfwWindowShouldClose(mwin) I could have used any flag of my own devising but there are some benefits to using the GLFW one, particularly with a multiwindow setup. The mwin argument is a GLFW window pointer that's being maintained in a specific namespace. More on that later. Inside the loop is the call to the function where all the actual work gets done: >muh_mrs.hpp snippet // Wrapper to perform frame timing and draw scene elements void time_n_draw() { // Update per-frame timing & process realtime user inputs do_timing(); if (key::key_prsd) proc_RT_inputs(); // if (key::key_prsd || mouse::btn_prsd) proc_RT_inputs(); // Clear the back colorbuffer clr_buff(); // Update per-frame scene display draw_scene(); // Update per-frame props display (toys, furniture, etc.) draw_props(); // Update per-frame text displays draw_text(); ++mtime::tot_frames; } This is just a wrapper that as the name implies performs timing and drawing into the backbuffer.
>>175 > Heh, I've seen C software that literally has the entire program--thousands of lines--all inside main()!.) I'm not really sure why C devs tend to do this, since sub-routines were a thing literally at the very beginning of computer science (eg, Edsac), and sub-functions were stressed all the way back in Algol60, as well as K&R. It's likely to avoid cache/branching. A lot of C code has to squeeze out as much performance as it possibly can and subroutine have overhead + they fuck up the caching. It's ugly as fuck, yeah, but sometimes the devs work on embedded systems and have to cut literally every single corner.
>>196 >but sometimes the devs work on embedded systems and have to cut literally every single corner. Sure, that situation I totally get. But with all due respect, in this case (ie, learnopengl.com examples) that's hardly the case and makes reasoning about things difficult af for the newcomer. And after all, the entire point of his project is to teach novices the ropes with OpenGL. I very much appreciate his efforts, and I'm learning a lot from it myself but I hope it will be just a little more approachable with my code here.
>>196 Thanks for bringing that point up btw, Anon. Context switching, stack frames, shared memory, concurrency & parallelism, threads, fibers, cache-lines, Data-oriented-Design, &tc., will all come into play for our IRL robowaifus, so yea. :^)
testing codeblocks again. #include <iostream> int main() { std::cout << "Hello World\n"; }

Report/Delete/Moderation Forms

no cookies?