feat: add RAII window class with OpenGL 4.1 context
- Add cbt::window class in cbt/ directory with RAII lifecycle - Add setup_opengl and info_opengl for glad init and GL info - Add Q key to quit the application - Update CMakeLists.txt with glfw3 and glad dependencies - Update AGENTS.md with snake_case naming and shell conventions
This commit is contained in:
@@ -44,6 +44,8 @@ to the custom scripts instead of system-installed packages.
|
|||||||
- `auto` for obvious types (e.g. `auto main(...) -> int`)
|
- `auto` for obvious types (e.g. `auto main(...) -> int`)
|
||||||
- `<>` includes only for system headers (std, OS, etc.)
|
- `<>` includes only for system headers (std, OS, etc.)
|
||||||
- `""` includes for third-party dependencies (e.g. `fmt`, `nlohmann/json`)
|
- `""` 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:
|
- Include order:
|
||||||
1. C++ standard library headers (`<chrono>`, `<vector>`, etc.)
|
1. C++ standard library headers (`<chrono>`, `<vector>`, etc.)
|
||||||
2. *(blank line)*
|
2. *(blank line)*
|
||||||
@@ -86,6 +88,9 @@ Example:
|
|||||||
cuber/
|
cuber/
|
||||||
CMakeLists.txt # Build configuration
|
CMakeLists.txt # Build configuration
|
||||||
cuber.cpp # Entry point
|
cuber.cpp # Entry point
|
||||||
|
cbt/ # Project namespace
|
||||||
|
window.hpp # Window RAII wrapper
|
||||||
|
window.cpp # Window implementation
|
||||||
deps/ # Custom Find*.cmake scripts
|
deps/ # Custom Find*.cmake scripts
|
||||||
Findfmt.cmake # fmt library
|
Findfmt.cmake # fmt library
|
||||||
```
|
```
|
||||||
|
|||||||
+5
-2
@@ -9,8 +9,11 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps")
|
|||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
find_package(fmt REQUIRED)
|
find_package(fmt REQUIRED)
|
||||||
|
find_package(glfw3 REQUIRED)
|
||||||
|
find_package(glad REQUIRED)
|
||||||
|
|
||||||
# Target setup
|
# 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_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)
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#define GLFW_INCLUDE_NONE
|
||||||
|
#include "GLFW/glfw3.h"
|
||||||
|
|
||||||
|
#include "window.hpp"
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#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<const char*>(glGetString(GL_VENDOR))));
|
||||||
|
fmt::print(" renderer| {}\n", std::string_view(reinterpret_cast<const char*>(glGetString(GL_RENDERER))));
|
||||||
|
fmt::print(" version | {}\n", std::string_view(reinterpret_cast<const char*>(glGetString(GL_VERSION))));
|
||||||
|
fmt::print(" glsl | {}\n", std::string_view(reinterpret_cast<const char*>(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<GLFWwindow*>(m_window));
|
||||||
|
|
||||||
|
if (!setup_opengl()) {
|
||||||
|
terminate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window::~window() {
|
||||||
|
if (m_window) {
|
||||||
|
glfwDestroyWindow(static_cast<GLFWwindow*>(m_window));
|
||||||
|
}
|
||||||
|
if (m_initialized) {
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::should_close() const -> bool {
|
||||||
|
return m_window && glfwWindowShouldClose(static_cast<GLFWwindow*>(m_window));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::valid() const -> bool {
|
||||||
|
return m_window != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::swap_buffers() -> void {
|
||||||
|
glfwSwapBuffers(static_cast<GLFWwindow*>(m_window));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window::poll_events() -> void {
|
||||||
|
glfwPollEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,26 +1,27 @@
|
|||||||
#include <chrono>
|
#define GLFW_INCLUDE_NONE
|
||||||
#include <thread>
|
#include "GLFW/glfw3.h"
|
||||||
|
|
||||||
#include "fmt/std.h"
|
#include "cbt/window.hpp"
|
||||||
|
|
||||||
auto main(int argc, char const* argv[]) -> int {
|
#include "glad/glad.h"
|
||||||
using namespace std::chrono;
|
|
||||||
using namespace std::literals;
|
|
||||||
|
|
||||||
auto start = steady_clock::now();
|
auto main(int, char const*[]) -> int {
|
||||||
|
auto w = cbt::window("cuber", 1280, 720);
|
||||||
|
|
||||||
while (true) {
|
if (!w.valid()) {
|
||||||
auto elapsed = steady_clock::now() - start;
|
return 1;
|
||||||
auto totalSec = duration_cast<seconds>(elapsed).count();
|
}
|
||||||
auto h = totalSec / 3600;
|
|
||||||
auto m = (totalSec % 3600) / 60;
|
|
||||||
auto s = totalSec % 60;
|
|
||||||
auto ms = duration_cast<milliseconds>(elapsed % 1s).count();
|
|
||||||
|
|
||||||
fmt::print("\033[32m{:02}:{:02}:{:02}\033[0m.\033[90m{:03}\033[0m\r",
|
while (!w.should_close()) {
|
||||||
h, m, s, ms);
|
if (glfwGetKey(static_cast<GLFWwindow*>(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;
|
return 0;
|
||||||
|
|||||||
Vendored
+68
@@ -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()
|
||||||
Vendored
+81
@@ -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")
|
||||||
Reference in New Issue
Block a user