BLT-With-Graphics-Template/src/blt/gfx/window.cpp

439 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Created by Brett on 28/11/23.
* Licensed under GNU General Public License V3.0
* See LICENSE file for license detail
*/
#include <blt/gfx/window.h>
#include <blt/std/assert.h>
#include <blt/logging/logging.h>
#include <blt/std/time.h>
#include <blt/gfx/input.h>
#include <blt/gfx/font/font.h>
#include <queue>
#include <imgui.h>
#include "backends/imgui_impl_opengl3.h"
#include "backends/imgui_impl_glfw.h"
#include <blt/gfx/imgui/ImGuiUtils.h>
#ifdef __EMSCRIPTEN__
#include <emscripten/html5.h>
#include <blt/std/types.h>
#endif
void error_callback(int error, const char* description)
{
BLT_ERROR("GLFW Error (%d): %s", error, description);
std::abort();
}
void gl_error_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei, const GLchar* message, const void*)
{
// do not log performance concerns
if (type == GL_DEBUG_TYPE_OTHER)
return;
if (type == GL_DEBUG_TYPE_ERROR)
{
BLT_ERROR("[OpenGL Error] message = '{}', type = 0x{:x}, severity = 0x{:x}, source = 0x{:x}, id = {}",
message, type, severity, source, id);
} else
{
BLT_WARN("[OpenGL Error] message = '{}', type = 0x{:x}, severity = 0x{:x}, source = 0x{:x}, id = {}",
message, type, severity, source, id);
}
}
namespace blt::gfx
{
struct
{
/* GLFW Window Object */
GLFWwindow* window = nullptr;
/* BLT internal input state manager, handles keyboard/mouse allocations + states */
input_manager inputManager;
/* stores any drag and dropped paths for processing */
std::queue<std::string> pendingPaths;
/* current width and height of the window */
std::int32_t width = 0;
std::int32_t height = 0;
/* Frame time information (last frame) */
std::int64_t lastTime = blt::system::nanoTime();
std::int64_t deltaTime = 0;
double nanoDelta = 0;
double millisDelta = 0;
window_data* data = nullptr;
} window_state;
void create_callbacks()
{
/* Setup keyboard callback */
glfwSetKeyCallback(window_state.window, [](GLFWwindow*, int key, int, int action, int) {
if (key < 0 || key == GLFW_KEY_UNKNOWN)
return;
KEY_STATE state;
switch (action)
{
case GLFW_PRESS:
state = KEY_STATE::PRESS;
window_state.inputManager.key_pressed = true;
break;
case GLFW_REPEAT:
state = KEY_STATE::REPEAT;
break;
default:
state = KEY_STATE::RELEASE;
window_state.inputManager.key_released = true;
break;
}
window_state.inputManager.key(key) = state;
});
/* Setup mouse button callback */
glfwSetMouseButtonCallback(window_state.window, [](GLFWwindow*, int button, int action, int) {
if (button < 0)
return;
MOUSE_STATE state;
switch (action)
{
case GLFW_PRESS:
state = MOUSE_STATE::PRESS;
window_state.inputManager.mouse_pressed = true;
break;
default:
state = MOUSE_STATE::RELEASE;
window_state.inputManager.mouse_released = true;
break;
}
window_state.inputManager.mouse(button) = state;
});
/* Setup mouse cursor callback */
glfwSetCursorPosCallback(window_state.window, [](GLFWwindow*, double x, double y) {
window_state.inputManager.updateMousePos(x, y);
window_state.inputManager.mouse_moved = true;
});
/* Setup mouse scroll callback */
glfwSetScrollCallback(window_state.window, [](GLFWwindow*, double, double s) { window_state.inputManager.updateScroll(s); });
/* Setup drop input callback */
glfwSetDropCallback(window_state.window, [](GLFWwindow*, int count, const char** paths) {
for (int i = 0; i < count; i++)
window_state.pendingPaths.emplace(paths[i]);
});
}
void setup_ImGUI()
{
const char* glsl_version = "#version 100";
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
// Setup Dear ImGui style
ImGui::StyleColorsDark();
// ImGui::Spectrum::StyleColorsSpectrum();
ImGui::Spectrum::LoadFont();
ImGui::SetupImGuiStyle(true, 1.0);
// Setup FA
ImFontConfig config;
config.MergeMode = true;
config.GlyphMinAdvanceX = 13.0f; // Use if you want to make the icon monospaced
static constexpr ImWchar icon_ranges[] = {ICON_MIN_FA, ICON_MAX_FA, 0};
io.Fonts->AddFontFromMemoryCompressedBase85TTF(fontAwesomeRegular_compressed_data_base85, 13.0f, &config, icon_ranges);
io.Fonts->AddFontFromMemoryCompressedTTF(fontAwesomeSolid_compressed_data, static_cast<int>(fontAwesomeSolid_compressed_size), 13.0, &config,
icon_ranges);
io.Fonts->AddFontFromMemoryCompressedTTF(fontAwesomeBrands_compressed_data, static_cast<int>(fontAwesomeBrands_compressed_size), 13.0,
&config,
icon_ranges);
//ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplGlfw_InitForOpenGL(window_state.window, true);
#ifdef __EMSCRIPTEN__
// ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback("#canvas");
#endif
ImGui_ImplOpenGL3_Init(glsl_version);
#ifdef __EMSCRIPTEN__
io.IniFilename = nullptr;
#endif
}
void loop(void* arg)
{
auto& data = *((window_data*) arg);
/* -- Get the current framebuffer size, update the global width/height state, along with OpenGL viewport -- */
glfwGetFramebufferSize(window_state.window, &window_state.width, &window_state.height);
glViewport(0, 0, window_state.width, window_state.height);
data.width = window_state.width;
data.height = window_state.height;
// TODO: user option for this
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
/* -- Begin the next ImGUI frame -- */
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
/* -- Call user update function -- */
data.call_update();
/* -- Render the ImGUI frame -- */
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
/* -- Update GLFW state -- */
window_state.inputManager.clear();
glfwSwapBuffers(window_state.window);
glfwPollEvents();
/* -- Update Frame Timing Information -- */
const auto current_time = system::nanoTime();
window_state.deltaTime = current_time - window_state.lastTime;
window_state.lastTime = current_time;
window_state.nanoDelta = static_cast<double>(window_state.deltaTime) / 1e9f;
window_state.millisDelta = static_cast<double>(window_state.deltaTime) / 1e6f;
}
#ifdef __EMSCRIPTEN__
EM_BOOL emscripten_resize_callback(int, const EmscriptenUiEvent* event, void* data)
{
int width = event->documentBodyClientWidth;
int height = event->documentBodyClientHeight;
glfwSetWindowSize(window_state.window, width, height);
return false;
}
EM_JS(int, get_screen_width, (), {
return document.body.clientWidth;
});
EM_JS(int, get_screen_height, (), {
return document.body.clientHeight;
});
#endif
void init(window_data data)
{
window_state.data = &data; // NOLINT
#ifdef __EMSCRIPTEN__
blt::logging::setLogOutputFormat("[${{TIME}}] [${{LOG_LEVEL}}] (${{FILE}}:${{LINE}}) ${{STR}}\n");
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false,
emscripten_resize_callback);
data.width = get_screen_width();
data.height = get_screen_height();
#endif
/* -- Set up Error Callback -- */
glfwSetErrorCallback(error_callback);
BLT_ASSERT(glfwInit() && "Unable to init GLFW. Aborting.");
/* -- Set up Window Context -- */
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, data.context.GL_MAJOR);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, data.context.GL_MINOR);
glfwWindowHint(GLFW_DOUBLEBUFFER, data.context.DOUBLE_BUFFER);
glfwWindowHint(GLFW_OPENGL_PROFILE, data.context.GL_PROFILE);
glfwWindowHint(GLFW_SAMPLES, data.context.SAMPLES);
glfwWindowHint(GLFW_MAXIMIZED, data.maximized);
/* -- Create the Window -- */
window_state.window = glfwCreateWindow(data.width, data.height, data.title.c_str(), data.monitor, data.share);
data.window = window_state.window;
BLT_ASSERT(window_state.window && "Unable to create GLFW window.");
/* -- Set Window Specifics + OpenGL -- */
glfwMakeContextCurrent(window_state.window);
#ifndef __EMSCRIPTEN__
glfwSwapInterval(data.sync_interval);
gladLoadGL(glfwGetProcAddress);
#endif
#ifndef __EMSCRIPTEN__
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(gl_error_callback, nullptr);
#endif
/* -- Set up our local callbacks, ImGUI will then call these -- */
create_callbacks();
/* -- Set up ImGUI -- */
setup_ImGUI();
#ifdef GL_MULTISAMPLE
if (data.context.SAMPLES > 0)
glEnable(GL_MULTISAMPLE);
#endif
window_state.width = data.width;
window_state.height = data.height;
/* -- Call User Provided post-window-init function -- */
data.call_init();
#ifdef __EMSCRIPTEN__
/*
* "setting 0 or a negative value as the fps will use the browsers requestAnimationFrame mechanism to call the main loop function.
* This is HIGHLY recommended if you are doing rendering, as the browsers requestAnimationFrame will
* make sure you render at a proper smooth rate that lines up properly with the browser and monitor."
* https://emscripten.org/docs/api_reference/emscripten.h.html
*/
emscripten_set_main_loop_arg(loop, (void*) &data, 0, true);
#else
/* -- General Loop -- */
while (!glfwWindowShouldClose(window_state.window))
loop((void*) &data);
#endif
/* -- Call User provided post-loop destroy function. Added for consistency -- */
data.call_destroy();
}
void cleanup()
{
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window_state.window);
glfwTerminate();
}
double getMouseX()
{ return window_state.inputManager.mouseX; }
double getMouseY()
{ return window_state.inputManager.mouseY; }
double getMouseDX()
{ return window_state.inputManager.deltaX; }
double getMouseDY()
{ return window_state.inputManager.deltaY; }
void lockCursor()
{ glfwSetInputMode(window_state.window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); }
void unlockCursor()
{ glfwSetInputMode(window_state.window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); }
bool isCursorLocked()
{ return glfwGetInputMode(window_state.window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED; }
bool isCursorInWindow()
{
#ifdef __EMSCRIPTEN__
// TODO:
return true;
#else
return glfwGetWindowAttrib(window_state.window, GLFW_HOVERED);
#endif
}
void setRawInput(bool state)
{
#ifdef __EMSCRIPTEN__
// TODO?
#else
if (glfwRawMouseMotionSupported())
glfwSetInputMode(window_state.window, GLFW_RAW_MOUSE_MOTION, state ? GLFW_TRUE : GLFW_FALSE);
#endif
}
bool isRawInput()
{
#ifdef __EMSCRIPTEN__
return false;
#else
return glfwGetInputMode(window_state.window, GLFW_RAW_MOUSE_MOTION);
#endif
}
void setClipboard(const std::string& str)
{ glfwSetClipboardString(window_state.window, str.c_str()); }
std::string getClipboard()
{ return glfwGetClipboardString(window_state.window); }
bool isMousePressed(int button)
{ return window_state.inputManager.isMousePressed(button); }
bool isKeyPressed(int key)
{ return window_state.inputManager.isKeyPressed(key); }
double getFrameDeltaSeconds()
{ return window_state.nanoDelta; }
double getFrameDeltaMilliseconds()
{ return window_state.millisDelta; }
i64 getFrameDelta()
{ return window_state.deltaTime; }
bool mouseMovedLastFrame()
{ return window_state.inputManager.mouse_moved; }
bool mousePressedLastFrame()
{ return window_state.inputManager.mouse_pressed; }
bool keyPressedLastFrame()
{ return window_state.inputManager.key_pressed; }
i32 getWindowHeight()
{ return window_state.height; }
i32 getWindowWidth()
{ return window_state.width; }
bool keyReleasedLastFrame()
{ return window_state.inputManager.key_released; }
bool mouseReleaseLastFrame()
{ return window_state.inputManager.mouse_released; }
void setWindowSize(const i32 width, const i32 height)
{
window_state.data->setWindowSize(width, height);
}
window_data& window_data::setWindowSize(int32_t new_width, int32_t new_height)
{
width = new_width;
height = new_height;
glfwSetWindowSize(window_state.window, width, height);
return *this;
}
window_data& window_data::setMonitor(GLFWmonitor* m)
{
window_data::monitor = m;
return *this;
}
window_data& window_data::setShare(GLFWwindow* s)
{
window_data::share = s;
return *this;
}
window_data& window_data::setMaximized(bool b)
{
if (b)
window_data::maximized = GLFW_TRUE;
else
window_data::maximized = GLFW_FALSE;
return *this;
}
}