diff --git a/AGENTS.md b/AGENTS.md index 5c72649..ee4268d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,6 +44,8 @@ to the custom scripts instead of system-installed packages. - `auto` for obvious types (e.g. `auto main(...) -> int`) - `<>` includes only for system headers (std, OS, etc.) - `""` includes for third-party dependencies (e.g. `fmt`, `nlohmann/json`) +- **Naming:** `snake_case` for variables, functions, and classes +- **Naming:** `SCREAMING_SNAKE_CASE` only for macros and constants - Include order: 1. C++ standard library headers (``, ``, etc.) 2. *(blank line)* @@ -86,6 +88,9 @@ Example: cuber/ CMakeLists.txt # Build configuration cuber.cpp # Entry point + cbt/ # Project namespace + window.hpp # Window RAII wrapper + window.cpp # Window implementation deps/ # Custom Find*.cmake scripts Findfmt.cmake # fmt library ``` diff --git a/CMakeLists.txt b/CMakeLists.txt index 24c27f1..302bd41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,11 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps") # Dependencies find_package(fmt REQUIRED) +find_package(glfw3 REQUIRED) +find_package(glad REQUIRED) # Target setup -add_executable(cuber "cuber.cpp") +add_executable(cuber "cuber.cpp" "cbt/window.cpp") +target_include_directories(cuber PRIVATE ".") target_compile_features(cuber PRIVATE cxx_std_23) -target_link_libraries(cuber PRIVATE fmt::fmt) +target_link_libraries(cuber PRIVATE fmt::fmt glfw::glfw glad::glad) diff --git a/cbt/window.cpp b/cbt/window.cpp new file mode 100644 index 0000000..c65f5e7 --- /dev/null +++ b/cbt/window.cpp @@ -0,0 +1,92 @@ +#define GLFW_INCLUDE_NONE +#include "GLFW/glfw3.h" + +#include "window.hpp" + +#include + +#include "glad/glad.h" +#include "fmt/std.h" + +namespace cbt { + +auto window::init() -> bool { + if (!glfwInit()) { + fmt::print("Failed to initialize GLFW\n"); + return false; + } + return true; +} + +auto window::terminate() -> void { + glfwTerminate(); +} + +auto window::setup_opengl() -> bool { + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + fmt::print("Failed to initialize GLAD\n"); + return false; + } + info_opengl(); + return true; +} + +auto window::info_opengl() -> void { + fmt::print("OpenGL Info:\n"); + fmt::print(" vendor | {}\n", std::string_view(reinterpret_cast(glGetString(GL_VENDOR)))); + fmt::print(" renderer| {}\n", std::string_view(reinterpret_cast(glGetString(GL_RENDERER)))); + fmt::print(" version | {}\n", std::string_view(reinterpret_cast(glGetString(GL_VERSION)))); + fmt::print(" glsl | {}\n", std::string_view(reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION)))); +} + +window::window(std::string title, int width, int height) { + if (!init()) { + return; + } + m_initialized = true; + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + m_window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); + if (!m_window) { + fmt::print("Failed to create window\n"); + terminate(); + return; + } + + glfwMakeContextCurrent(static_cast(m_window)); + + if (!setup_opengl()) { + terminate(); + return; + } +} + +window::~window() { + if (m_window) { + glfwDestroyWindow(static_cast(m_window)); + } + if (m_initialized) { + terminate(); + } +} + +auto window::should_close() const -> bool { + return m_window && glfwWindowShouldClose(static_cast(m_window)); +} + +auto window::valid() const -> bool { + return m_window != nullptr; +} + +auto window::swap_buffers() -> void { + glfwSwapBuffers(static_cast(m_window)); +} + +auto window::poll_events() -> void { + glfwPollEvents(); +} + +} diff --git a/cbt/window.hpp b/cbt/window.hpp new file mode 100644 index 0000000..3a5862f --- /dev/null +++ b/cbt/window.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace cbt { + +class window { + void* m_window = nullptr; + bool m_initialized = false; + + static auto init() -> bool; + static auto terminate() -> void; + static auto setup_opengl() -> bool; + static auto info_opengl() -> void; + +public: + explicit window(std::string title, int width, int height); + ~window(); + + auto should_close() const -> bool; + auto valid() const -> bool; + auto swap_buffers() -> void; + auto poll_events() -> void; + auto raw() const -> void* { return m_window; } +}; + +} diff --git a/cuber.cpp b/cuber.cpp index 77662aa..ce97fb5 100644 --- a/cuber.cpp +++ b/cuber.cpp @@ -1,26 +1,27 @@ -#include -#include +#define GLFW_INCLUDE_NONE +#include "GLFW/glfw3.h" -#include "fmt/std.h" +#include "cbt/window.hpp" -auto main(int argc, char const* argv[]) -> int { - using namespace std::chrono; - using namespace std::literals; +#include "glad/glad.h" - auto start = steady_clock::now(); +auto main(int, char const*[]) -> int { + auto w = cbt::window("cuber", 1280, 720); - while (true) { - auto elapsed = steady_clock::now() - start; - auto totalSec = duration_cast(elapsed).count(); - auto h = totalSec / 3600; - auto m = (totalSec % 3600) / 60; - auto s = totalSec % 60; - auto ms = duration_cast(elapsed % 1s).count(); + if (!w.valid()) { + return 1; + } - fmt::print("\033[32m{:02}:{:02}:{:02}\033[0m.\033[90m{:03}\033[0m\r", - h, m, s, ms); + while (!w.should_close()) { + if (glfwGetKey(static_cast(w.raw()), GLFW_KEY_Q) == GLFW_PRESS) { + break; + } - std::this_thread::sleep_for(10ms); + glClearColor(0.6f, 0.8f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + w.swap_buffers(); + w.poll_events(); } return 0; diff --git a/deps/Findglad.cmake b/deps/Findglad.cmake new file mode 100644 index 0000000..6d1de52 --- /dev/null +++ b/deps/Findglad.cmake @@ -0,0 +1,68 @@ +# ============================================================================== +# Find glad +# ============================================================================== +# This module fetches the glad OpenGL loader library. +# +# Targets provided: +# glad::glad - The glad library target +# +# Variables set: +# glad_FOUND - TRUE if glad is available +# glad_LIBRARIES - The glad library target (glad::glad) +# glad_INCLUDE_DIR - Include directories for glad +# glad_VERSION - Version of glad (commit hash or tag) +# ============================================================================== + +if (DEFINED _FINDGLAD_INCLUDED) + return() +endif() +set(_FINDGLAD_INCLUDED TRUE) + +# Use the version passed to find_package(), or default to commit hash +if (DEFINED glad_FIND_VERSION AND NOT glad_FIND_VERSION STREQUAL "") + set(GLAD_VERSION "${glad_FIND_VERSION}") +else() + set(GLAD_VERSION "f4759d7c5143c0a23391ab05caaf43052cefdd65") +endif() + +message(STATUS "Fetching glad ${GLAD_VERSION}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (GIT_EXECUTABLE) + set(GLAD_FETCH_METHOD "GIT") +else() + message(FATAL_ERROR "Fetch with zip not supported.") +endif() + +if (GLAD_FETCH_METHOD STREQUAL "GIT") + FetchContent_Declare( + glad + GIT_REPOSITORY https://github.com/mononerv/glad.git + GIT_TAG ${GLAD_VERSION} + ) +endif() + +FetchContent_MakeAvailable(glad) + +if (NOT TARGET glad::glad) + if (TARGET glad) + add_library(glad::glad ALIAS glad) + else() + message(FATAL_ERROR "Could not fetch glad; no target glad or glad::glad available") + endif() +endif() + +set(glad_FOUND TRUE) +set(glad_LIBRARIES glad::glad) +set(glad_VERSION "${GLAD_VERSION}") +get_target_property(_glad_inc glad::glad INTERFACE_INCLUDE_DIRECTORIES) +set(glad_INCLUDE_DIR "${_glad_inc}") + +# Mark glad includes as SYSTEM to suppress warnings from its headers +if (_glad_inc AND TARGET glad) + set_target_properties(glad PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_glad_inc}" + ) +endif() diff --git a/deps/Findglfw3.cmake b/deps/Findglfw3.cmake new file mode 100644 index 0000000..dbd24e9 --- /dev/null +++ b/deps/Findglfw3.cmake @@ -0,0 +1,81 @@ +# ============================================================================== +# Find GLFW +# ============================================================================== +# This module fetches the GLFW library for window/input handling. +# +# Targets provided: +# glfw::glfw - The GLFW library target +# glfw - The GLFW library target (non-namespaced) +# +# Variables set: +# GLFW_FOUND - TRUE if GLFW is available +# GLFW_LIBRARIES - The GLFW library target (glfw::glfw) +# GLFW_INCLUDE_DIR - Include directories for GLFW +# GLFW_VERSION - Version of GLFW (if available) +# ============================================================================== + +if (DEFINED _FINDGLFW_INCLUDED) + return() +endif() +set(_FINDGLFW_INCLUDED TRUE) + +# Use the version passed to find_package(), or default to 3.4 +if (DEFINED GLFW_FIND_VERSION AND NOT GLFW_FIND_VERSION STREQUAL "") + set(GLFW_VERSION "${GLFW_FIND_VERSION}") +else() + set(GLFW_VERSION "3.4") +endif() + +message(STATUS "Fetching GLFW ${GLFW_VERSION}") + +include(FetchContent) + +find_program(GIT_EXECUTABLE git) +if (GIT_EXECUTABLE) + set(GLFW_FETCH_METHOD "GIT") +else() + message(FATAL_ERROR "Fetch with zip not supported.") +endif() + +if (GLFW_FETCH_METHOD STREQUAL "GIT") + # Disable GLFW extras to keep builds lightweight. + set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) + set(GLFW_INSTALL OFF CACHE BOOL "" FORCE) + # Environment issue where a cross-compilation script + # set(GLFW_BUILD_WAYLAND OFF CACHE BOOL "" FORCE) + + FetchContent_Declare( + glfw + GIT_REPOSITORY https://github.com/glfw/glfw.git + GIT_TAG ${GLFW_VERSION} + ) +endif() + +set(_glfw_suppress_prev ${CMAKE_SUPPRESS_DEVELOPER_WARNINGS}) +set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS TRUE CACHE INTERNAL "") +FetchContent_MakeAvailable(glfw) +set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS ${_glfw_suppress_prev} CACHE INTERNAL "") + +if (NOT TARGET glfw::glfw) + if (TARGET glfw) + add_library(glfw::glfw ALIAS glfw) + else() + message(FATAL_ERROR "Could not fetch GLFW; no target glfw or glfw::glfw available") + endif() +endif() + +set(GLFW_FOUND TRUE) +set(GLFW_LIBRARIES glfw::glfw) +get_target_property(_glfw_inc glfw::glfw INTERFACE_INCLUDE_DIRECTORIES) +set(GLFW_INCLUDE_DIR "${_glfw_inc}") + +# Mark GLFW includes as SYSTEM to suppress warnings from its headers +if (_glfw_inc AND TARGET glfw) + set_target_properties(glfw PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_glfw_inc}" + ) +endif() + +set(GLFW_LICENSE_FILE "${glfw_SOURCE_DIR}/LICENSE.md" CACHE FILEPATH "Path to GLFW license file")