From a850b5d5cb5241ef71c332c144fc7d321e0100a6 Mon Sep 17 00:00:00 2001 From: ManDude <7569514+ManDude@users.noreply.github.com> Date: Tue, 10 Aug 2021 00:16:39 +0100 Subject: [PATCH] revamp the gfx+display systems a bit (#739) * revamp gfx and display systems a bit * Use some fancy c++ pointers instead of just raw pointers * Tidy some things up. * clang * clang 2 * fixes * fixesss * error detection when making display --- .vs/launch.vs.json | 7 ++ game/CMakeLists.txt | 1 + game/graphics/display.cpp | 147 ++++++++++++++++++++++------- game/graphics/display.h | 49 +++++++--- game/graphics/gfx.cpp | 79 ++++++++++------ game/graphics/gfx.h | 58 ++++++++++-- game/graphics/opengl.h | 15 --- game/graphics/pipelines/opengl.cpp | 127 +++++++++++++++++++++++++ game/graphics/pipelines/opengl.h | 20 ++++ game/runtime.cpp | 2 - game/system/vm/dmac.h | 5 - game/system/vm/vm.h | 5 - 12 files changed, 404 insertions(+), 111 deletions(-) delete mode 100644 game/graphics/opengl.h create mode 100644 game/graphics/pipelines/opengl.cpp create mode 100644 game/graphics/pipelines/opengl.h diff --git a/.vs/launch.vs.json b/.vs/launch.vs.json index f7af0801bd..4f445ef349 100644 --- a/.vs/launch.vs.json +++ b/.vs/launch.vs.json @@ -49,6 +49,13 @@ "name" : "Run - Runtime (with kernel)", "args" : [ "-fakeiso", "-debug", "-v", "-nodisplay" ] }, + { + "type" : "default", + "project" : "CMakeLists.txt", + "projectTarget" : "gk.exe (bin\\gk.exe)", + "name" : "Run - Runtime (with kernel + display)", + "args" : [ "-fakeiso", "-debug", "-v" ] + }, { "type" : "default", "project" : "CMakeLists.txt", diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 17e31fc7d3..f3346b747e 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -73,6 +73,7 @@ set(RUNTIME_SOURCE overlord/stream.cpp graphics/gfx.cpp graphics/display.cpp + graphics/pipelines/opengl.cpp system/vm/dmac.cpp system/vm/vm.cpp) diff --git a/game/graphics/display.cpp b/game/graphics/display.cpp index 668797affc..6a72b0bf24 100644 --- a/game/graphics/display.cpp +++ b/game/graphics/display.cpp @@ -4,51 +4,128 @@ */ #include "display.h" +#include "gfx.h" #include "common/log/log.h" -namespace Display { +/* ****************************** */ +/* Internal functions */ +/* ****************************** */ -GLFWwindow* display = NULL; +namespace { -void InitDisplay(int width, int height, const char* title, GLFWwindow*& d) { - if (d) { - lg::warn("InitDisplay has already created a display window"); - return; - } - - // request OpenGL 3.0 (placeholder) - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - d = glfwCreateWindow(width, height, title, NULL, NULL); - - if (!d) { - lg::error("InitDisplay failed - Could not create display window"); - return; - } - - glfwMakeContextCurrent(d); - if (!gladLoadGL()) { - lg::error("GL init fail"); - KillDisplay(d); - return; - } - - // enable vsync by default - glfwSwapInterval(1); - - lg::debug("init display #x{}", (uintptr_t)d); +bool renderer_is_correct(const GfxRendererModule* renderer, GfxPipeline pipeline) { + return renderer->pipeline == pipeline; } -void KillDisplay(GLFWwindow*& d) { - lg::debug("kill display #x{}", (uintptr_t)d); - if (!d) { - lg::warn("KillDisplay called when no display was available"); +void set_main_display(std::shared_ptr display) { + if (Display::g_displays.size() > 0) { + Display::g_displays[0] = display; + } else { + Display::g_displays.push_back(display); + } +} + +} // namespace + +/* ****************************** */ +/* GfxDisplay */ +/* ****************************** */ + +GfxDisplay::GfxDisplay(GLFWwindow* a_window) { + set_renderer(GfxPipeline::OpenGL); + set_window(a_window); +} + +GfxDisplay::~GfxDisplay() { + m_renderer->kill_display(this); + // window_generic_ptr = nullptr; +} + +void GfxDisplay::set_renderer(GfxPipeline pipeline) { + if (is_active()) { + lg::error("Can't change display's renderer while window exists."); + return; + } + if (m_renderer != nullptr) { + lg::error("A display changed renderer unexpectedly."); return; } - glfwDestroyWindow(d); - d = NULL; + m_renderer = Gfx::GetRenderer(pipeline); +} + +void GfxDisplay::set_window(GLFWwindow* window) { + if (!renderer_is_correct(m_renderer, GfxPipeline::OpenGL)) { + lg::error("Can't set OpenGL window when using {}", m_renderer->name); + return; + } + if (is_active()) { + lg::error("Already have a window. Close window first."); + return; + } + + this->window_glfw = window; +} + +void GfxDisplay::set_title(const char* title) { + if (!is_active()) { + lg::error("No window to set title `{}`.", title); + return; + } + + m_title = title; +} + +void GfxDisplay::render_graphics() { + m_renderer->render_display(this); +} + +/* ****************************** */ +/* DISPLAY */ +/* ****************************** */ + +namespace Display { + +std::vector> g_displays; +std::shared_ptr GetMainDisplay() { + if (g_displays.size() == 0) + return NULL; + return g_displays.front()->is_active() ? g_displays.front() : NULL; +} + +int InitMainDisplay(int width, int height, const char* title, GfxSettings& settings) { + if (GetMainDisplay() != NULL) { + lg::warn("InitMainDisplay called when main display already exists."); + return 1; + } + + auto display = settings.renderer->make_main_display(width, height, title, settings); + if (display == NULL) { + lg::error("Failed to make main display."); + return 1; + } + set_main_display(display); +} + +void KillDisplay(std::shared_ptr display) { + // lg::debug("kill display #x{:x}", (uintptr_t)display); + if (!display->is_active()) { + lg::warn("display #x{:x} cant be killed because it is not active"); + return; + } + + if (GetMainDisplay() == display) { + // killing the main display, kill all children displays too! + for (int i = 1; i < g_displays.size(); ++i) { + KillDisplay(g_displays.at(i)); + } + } + + // find this display in the list and remove it from there + // if everything went right the smart pointer should delete the display. + auto this_disp = std::find(g_displays.begin(), g_displays.end(), display); + g_displays.erase(this_disp); } } // namespace Display diff --git a/game/graphics/display.h b/game/graphics/display.h index 2508679388..da338e324d 100644 --- a/game/graphics/display.h +++ b/game/graphics/display.h @@ -5,21 +5,48 @@ * Display for graphics. This is the game window, distinct from the runtime console. */ -#ifndef RUNTIME_DISPLAY_H -#define RUNTIME_DISPLAY_H +#include "pipelines/opengl.h" +#include "gfx.h" +#include +#include -#include "opengl.h" +// a GfxDisplay class is equivalent to a window that displays stuff. This holds an actual internal +// window pointer used by whichever renderer. It also contains functions for setting and +// retrieving certain window parameters. +class GfxDisplay { + const char* m_title; + + const GfxRendererModule* m_renderer = nullptr; + + public: + GfxDisplay(GLFWwindow* a_window); // OpenGL window constructor + ~GfxDisplay(); // destructor - this calls the renderer's function for getting rid of a window, + // and we can then get rid of the GfxDisplay itself + + // all kinds of windows for the display + union { + void* window_generic_ptr = nullptr; + GLFWwindow* window_glfw; + }; + + bool is_active() const { return window_generic_ptr != nullptr; } + void set_renderer(GfxPipeline pipeline); + void set_window(GLFWwindow* window); + void set_title(const char* title); + const char* get_title() const { return m_title; } + + void render_graphics(); +}; namespace Display { -// TODO - eventually we might actually want to support having multiple windows and viewpoints -// so it would be nice if we didn't end up designing this system such that this MUST be -// a single window. -extern GLFWwindow* display; +// a list of displays. the first one is the "main" display, all others are spectator-like extra +// views. +extern std::vector> g_displays; -void InitDisplay(int width, int height, const char* title, GLFWwindow*& d); -void KillDisplay(GLFWwindow*& d); +int InitMainDisplay(int width, int height, const char* title, GfxSettings& settings); +void KillDisplay(std::shared_ptr display); + +std::shared_ptr GetMainDisplay(); } // namespace Display - -#endif // RUNTIME_DISPLAY_H diff --git a/game/graphics/gfx.cpp b/game/graphics/gfx.cpp index 4ddcb20a3e..07f6b86b52 100644 --- a/game/graphics/gfx.cpp +++ b/game/graphics/gfx.cpp @@ -1,6 +1,6 @@ /*! * @file gfx.cpp - * Graphics component for the runtime. Handles some low-level routines. + * Graphics component for the runtime. Abstraction layer for the main graphics routines. */ #include "gfx.h" @@ -9,62 +9,79 @@ #include "game/runtime.h" #include "display.h" -#include "opengl.h" +#include "pipelines/opengl.h" + +namespace { + +// initializes a gfx settings. +// TODO save and load from file +void InitSettings(GfxSettings& settings) { + // use opengl by default for now + settings.renderer = Gfx::GetRenderer(GfxPipeline::OpenGL); // Gfx::renderers[0]; + + // 1 screen update per frame + settings.vsync = 1; + + return; +} + +} // namespace namespace Gfx { -void GlfwErrorCallback(int err, const char* msg) { - lg::error("GLFW ERR {}: " + std::string(msg), err); +GfxVertex g_vertices_temp[VERTEX_BUFFER_LENGTH_TEMP]; + +GfxSettings g_settings; +// const std::vector renderers = {&moduleOpenGL}; + +const GfxRendererModule* GetRenderer(GfxPipeline pipeline) { + switch (pipeline) { + case GfxPipeline::Invalid: + lg::error("Requested invalid graphics pipeline!"); + return NULL; + break; + case GfxPipeline::OpenGL: + return &moduleOpenGL; + default: + lg::error("Unknown graphics pipeline {}", (u64)pipeline); + return NULL; + } } u32 Init() { - if (glfwSetErrorCallback(GlfwErrorCallback) != NULL) { - lg::warn("glfwSetErrorCallback has been re-set!"); - } + lg::info("GFX Init"); + // initialize settings + InitSettings(g_settings); - if (!glfwInit()) { - lg::error("glfwInit error"); + if (g_settings.renderer->init()) { + lg::error("Gfx::Init error"); return 1; } if (g_main_thread_id != std::this_thread::get_id()) { lg::warn("ran Gfx::Init outside main thread. Init display elsewhere?"); } else { - Display::InitDisplay(640, 480, "testy", Display::display); + Display::InitMainDisplay(640, 480, "testy", g_settings); } return 0; } void Loop(std::function f) { + lg::info("GFX Loop"); while (f()) { - // run display-specific things - if (Display::display) { + // check if we have a display + if (Display::GetMainDisplay()) { // lg::debug("run display"); - glfwMakeContextCurrent(Display::display); - - // render graphics - glClear(GL_COLOR_BUFFER_BIT); - - glfwSwapBuffers(Display::display); - - // poll events TODO integrate input with cpad - glfwPollEvents(); - - // exit if display window was closed - if (glfwWindowShouldClose(Display::display)) { - // Display::KillDisplay(Display::display); - MasterExit = 2; - } + Display::GetMainDisplay()->render_graphics(); } } } u32 Exit() { - lg::debug("gfx exit"); - Display::KillDisplay(Display::display); - glfwTerminate(); - glfwSetErrorCallback(NULL); + lg::info("GFX Exit"); + Display::KillDisplay(Display::GetMainDisplay()); + g_settings.renderer->exit(); return 0; } diff --git a/game/graphics/gfx.h b/game/graphics/gfx.h index abc1171fee..82fa9c6358 100644 --- a/game/graphics/gfx.h +++ b/game/graphics/gfx.h @@ -2,23 +2,67 @@ /*! * @file gfx.h - * Graphics component for the runtime. Handles some low-level routines. + * Graphics component for the runtime. Abstraction layer for the main graphics routines. */ -#ifndef RUNTIME_GFX_H -#define RUNTIME_GFX_H - #include +#include #include "common/common_types.h" -#include "display.h" #include "game/kernel/kboot.h" +// forward declarations +struct GfxSettings; +class GfxDisplay; + +// enum for rendering pipeline +enum class GfxPipeline { Invalid = 0, OpenGL }; + +// module for the different rendering pipelines +struct GfxRendererModule { + std::function init; + std::function(int w, int h, const char* title, GfxSettings& settings)> + make_main_display; + std::function kill_display; + std::function render_display; + std::function exit; + + GfxPipeline pipeline; + const char* name; +}; + +// store settings related to the gfx systems +struct GfxSettings { + const GfxRendererModule* renderer; // which rendering pipeline to use. + + int vsync; // (temp) number of screen update per frame +}; + +// struct for a single vertex. this should in theory be renderer-agnostic +struct GfxVertex { + // x y z + float x, y, z; + + // rgba or the full u32 thing. + union { + u32 rgba; + struct { + u8 r, g, b, a; + }; + }; +}; + namespace Gfx { +static constexpr int VERTEX_BUFFER_LENGTH_TEMP = 4096; +extern GfxVertex g_vertices_temp[VERTEX_BUFFER_LENGTH_TEMP]; + +extern GfxSettings g_settings; +// extern const std::vector renderers; + +const GfxRendererModule* GetRenderer(GfxPipeline pipeline); + u32 Init(); void Loop(std::function f); u32 Exit(); } // namespace Gfx - -#endif // RUNTIME_GFX_H diff --git a/game/graphics/opengl.h b/game/graphics/opengl.h deleted file mode 100644 index 7aa365cce5..0000000000 --- a/game/graphics/opengl.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -/*! - * @file opengl.h - * OpenGL includes. - */ - -#ifndef RUNTIME_OPENGL_H -#define RUNTIME_OPENGL_H - -#define GLFW_INCLUDE_NONE -#include -#include - -#endif // RUNTIME_OPENGL_H diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp new file mode 100644 index 0000000000..9cc6b26c09 --- /dev/null +++ b/game/graphics/pipelines/opengl.cpp @@ -0,0 +1,127 @@ +/*! + * @file opengl.cpp + * Lower-level OpenGL implementation. + */ + +#include "opengl.h" + +#include "game/graphics/gfx.h" +#include "game/graphics/display.h" + +#include "common/log/log.h" +#include + +namespace { + +void SetDisplayCallbacks(GLFWwindow* d) { + glfwSetKeyCallback(d, [](GLFWwindow* window, int key, int scancode, int action, int mods) { + if (action == GlfwKeyAction::Press) { + lg::debug("KEY PRESS: key: {} scancode: {} mods: {:X}", key, scancode, mods); + } else if (action == GlfwKeyAction::Release) { + lg::debug("KEY RELEASE: key: {} scancode: {} mods: {:X}", key, scancode, mods); + } + }); +} + +void ErrorCallback(int err, const char* msg) { + lg::error("GLFW ERR {}: " + std::string(msg), err); +} + +bool HasError() { + return glfwGetError(NULL) != GLFW_NO_ERROR; +} + +} // namespace + +static bool gl_inited = false; +static int gl_init() { + if (glfwSetErrorCallback(ErrorCallback) != NULL) { + lg::warn("glfwSetErrorCallback has been re-set!"); + } + + if (glfwInit() == GLFW_FALSE) { + lg::error("glfwInit error"); + return 1; + } + + // request OpenGL 3.0 (placeholder) + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + + return 0; +} + +static void gl_exit() { + glfwTerminate(); + glfwSetErrorCallback(NULL); +} + +static std::shared_ptr gl_make_main_display(int width, + int height, + const char* title, + GfxSettings& settings) { + GLFWwindow* window = glfwCreateWindow(width, height, title, NULL, NULL); + + if (!window) { + lg::error("gl_make_main_display failed - Could not create display window"); + return NULL; + } + + glfwMakeContextCurrent(window); + + if (!gl_inited && !gladLoadGL()) { + lg::error("GL init fail"); + return NULL; + } + gl_inited = true; + + // enable vsync by default + // glfwSwapInterval(1); + glfwSwapInterval(settings.vsync); + + SetDisplayCallbacks(window); + + if (HasError()) { + lg::error("gl_make_main_display error"); + return NULL; + } + + std::shared_ptr display = std::make_shared(window); + // lg::debug("init display #x{:x}", (uintptr_t)display); + + return display; +} + +static void gl_kill_display(GfxDisplay* display) { + glfwDestroyWindow(display->window_glfw); +} + +static void gl_render_display(GfxDisplay* display) { + GLFWwindow* window = display->window_glfw; + + glfwMakeContextCurrent(window); + + // render graphics + glClear(GL_COLOR_BUFFER_BIT); + + glfwSwapBuffers(window); + + // poll events TODO integrate input with cpad + glfwPollEvents(); + + // exit if display window was closed + if (glfwWindowShouldClose(window)) { + // Display::KillDisplay(window); + MasterExit = 2; + } +} + +const GfxRendererModule moduleOpenGL = { + gl_init, // init + gl_make_main_display, // make_main_display + gl_kill_display, // kill_display + gl_render_display, // render_display + gl_exit, // exit + GfxPipeline::OpenGL, // pipeline + "OpenGL 3.0" // name +}; diff --git a/game/graphics/pipelines/opengl.h b/game/graphics/pipelines/opengl.h new file mode 100644 index 0000000000..c4db0f8255 --- /dev/null +++ b/game/graphics/pipelines/opengl.h @@ -0,0 +1,20 @@ +#pragma once + +/*! + * @file opengl.h + * OpenGL includes. + */ + +#define GLFW_INCLUDE_NONE +#include +#include + +#include "game/graphics/gfx.h" + +enum GlfwKeyAction { + Release = GLFW_RELEASE, // falling edge of key press + Press = GLFW_PRESS, // rising edge of key press + Repeat = GLFW_REPEAT // repeated input on hold e.g. when typing something +}; + +extern const GfxRendererModule moduleOpenGL; diff --git a/game/runtime.cpp b/game/runtime.cpp index 44880fb727..4743e1940a 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -48,9 +48,7 @@ #include "game/overlord/stream.h" #include "game/overlord/sbank.h" -#include "game/graphics/opengl.h" #include "game/graphics/gfx.h" -#include "game/graphics/display.h" #include "game/system/vm/vm.h" #include "game/system/vm/dmac.h" diff --git a/game/system/vm/dmac.h b/game/system/vm/dmac.h index 3c6b0aee57..2f0c98ba33 100644 --- a/game/system/vm/dmac.h +++ b/game/system/vm/dmac.h @@ -6,9 +6,6 @@ * Not meant to work as a full DMAC emulator, just enough to inspect DMA packets. */ -#ifndef VM_DMAC_H -#define VM_DMAC_H - #include "common/common_types.h" #include "game/kernel/Ptr.h" @@ -73,5 +70,3 @@ static_assert(alignof(DmaChannelRegisters) == 0x10, "DmaChannelRegisters unalign void dmac_init_globals(); } // namespace VM - -#endif // VM_DMAC_H diff --git a/game/system/vm/vm.h b/game/system/vm/vm.h index 13c921ae9e..260e59d184 100644 --- a/game/system/vm/vm.h +++ b/game/system/vm/vm.h @@ -7,9 +7,6 @@ * Not an emulator! */ -#ifndef VM_H -#define VM_H - #include "common/common_types.h" namespace VM { @@ -33,5 +30,3 @@ void unsubscribe_component(); u64 get_vm_ptr(u32 ptr); } // namespace VM - -#endif // VM_H