Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a4ef4adfc7 | |||
| 91fe3c6e8c | |||
| 6f696d377b | |||
| 78d0515e8b | |||
| 4a88c8cc06 | |||
| c3860cc1d3 | |||
| 3f78d0978d | |||
| 22d2bb1c40 | |||
| 7a81b30d32 | |||
| 40ae94788e |
@@ -43,3 +43,6 @@ imgui.ini
|
||||
|
||||
# Compiled shaders
|
||||
*.spv
|
||||
|
||||
# Screenshots
|
||||
*.png
|
||||
|
||||
@@ -34,7 +34,7 @@ To add a new dependency:
|
||||
|
||||
The project is split into static libraries:
|
||||
|
||||
- **`cbt_opengl`** — OpenGL abstraction (context, buffer, texture, vao, shader, descriptor)
|
||||
- **`cbt_opengl`** — OpenGL abstraction and window management (window, context, buffer, texture, vao, shader, descriptor)
|
||||
- **`cbt_scene`** — Base scene class
|
||||
- **`scenes_cube`** — Cube scene implementation
|
||||
|
||||
@@ -102,8 +102,10 @@ cuber/
|
||||
cbt/ # Project namespace (utilities)
|
||||
scene.hpp # Base scene class
|
||||
scene.cpp # Scene implementation
|
||||
window.hpp # GLFW window RAII wrapper
|
||||
window.cpp # Window implementation
|
||||
opengl/ # OpenGL abstraction layer
|
||||
context.hpp # OpenGL context RAII wrapper
|
||||
context.hpp # OpenGL context RAII wrapper (GLAD setup)
|
||||
context.cpp # Context implementation
|
||||
buffer.hpp # Buffer resource (VBO, EBO, UBO, SSBO)
|
||||
buffer.cpp # Buffer implementation
|
||||
|
||||
+14
-2
@@ -17,9 +17,11 @@ find_package(glfw3 REQUIRED)
|
||||
find_package(glad REQUIRED)
|
||||
find_package(asio REQUIRED)
|
||||
find_package(glm REQUIRED)
|
||||
find_package(stb REQUIRED)
|
||||
|
||||
# OpenGL abstraction library
|
||||
add_library(cbt_opengl STATIC
|
||||
"cbt/window.cpp"
|
||||
"cbt/opengl/context.cpp"
|
||||
"cbt/opengl/buffer.cpp"
|
||||
"cbt/opengl/texture.cpp"
|
||||
@@ -31,7 +33,7 @@ target_include_directories(cbt_opengl PRIVATE ".")
|
||||
target_compile_features(cbt_opengl PRIVATE cxx_std_23)
|
||||
target_compile_options(cbt_opengl PRIVATE ${BASE_OPTIONS})
|
||||
target_compile_definitions(cbt_opengl PRIVATE ${BASE_DEFINITIONS})
|
||||
target_link_libraries(cbt_opengl PUBLIC fmt::fmt glfw::glfw glad::glad)
|
||||
target_link_libraries(cbt_opengl PUBLIC fmt::fmt glfw::glfw glad::glad stb::stb)
|
||||
|
||||
# Scene base library
|
||||
add_library(cbt_scene STATIC
|
||||
@@ -53,10 +55,20 @@ target_compile_options(scenes_cube PRIVATE ${BASE_OPTIONS})
|
||||
target_compile_definitions(scenes_cube PRIVATE ${BASE_DEFINITIONS})
|
||||
target_link_libraries(scenes_cube PUBLIC cbt_scene glm::glm)
|
||||
|
||||
# Sphere scene
|
||||
add_library(scenes_sphere STATIC
|
||||
"scenes/sphere.cpp"
|
||||
)
|
||||
target_include_directories(scenes_sphere PRIVATE ".")
|
||||
target_compile_features(scenes_sphere PRIVATE cxx_std_23)
|
||||
target_compile_options(scenes_sphere PRIVATE ${BASE_OPTIONS})
|
||||
target_compile_definitions(scenes_sphere PRIVATE ${BASE_DEFINITIONS})
|
||||
target_link_libraries(scenes_sphere PUBLIC cbt_scene glm::glm)
|
||||
|
||||
# Main executable
|
||||
add_executable(cuber "cuber.cpp")
|
||||
target_include_directories(cuber PRIVATE ".")
|
||||
target_compile_features(cuber PRIVATE cxx_std_23)
|
||||
target_compile_options(cuber PRIVATE ${BASE_OPTIONS})
|
||||
target_compile_definitions(cuber PRIVATE ${BASE_DEFINITIONS})
|
||||
target_link_libraries(cuber PRIVATE cbt_scene scenes_cube asio::asio ${BASE_LIBRARIES})
|
||||
target_link_libraries(cuber PRIVATE cbt_scene scenes_cube scenes_sphere asio::asio ${BASE_LIBRARIES})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "cbt/opengl/buffer.hpp"
|
||||
|
||||
#include "glad/glad.h"
|
||||
|
||||
#include "cbt/opengl/buffer.hpp"
|
||||
|
||||
namespace cbt::opengl {
|
||||
|
||||
buffer::buffer() {
|
||||
|
||||
+15
-60
@@ -1,27 +1,15 @@
|
||||
#include <string_view>
|
||||
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include "GLFW/glfw3.h"
|
||||
|
||||
#include "fmt/std.h"
|
||||
#include "glad/glad.h"
|
||||
|
||||
#include "cbt/opengl/context.hpp"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "glad/glad.h"
|
||||
#include "fmt/std.h"
|
||||
|
||||
namespace cbt::opengl {
|
||||
|
||||
auto context::init() -> bool {
|
||||
if (!glfwInit()) {
|
||||
fmt::print("Failed to initialize GLFW\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto context::terminate() -> void {
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
auto context::setup_gl() -> bool {
|
||||
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
|
||||
fmt::print("Failed to initialize GLAD\n");
|
||||
@@ -40,58 +28,25 @@ auto context::print_info() -> void {
|
||||
fmt::print("\n");
|
||||
}
|
||||
|
||||
context::context(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(m_window);
|
||||
|
||||
context::context(window const& win) {
|
||||
glfwMakeContextCurrent(win.raw());
|
||||
if (!setup_gl()) {
|
||||
terminate();
|
||||
return;
|
||||
}
|
||||
m_valid = true;
|
||||
|
||||
// Set initial viewport to match window size
|
||||
glViewport(0, 0, win.width(), win.height());
|
||||
}
|
||||
|
||||
context::~context() {
|
||||
if (m_window) {
|
||||
glfwDestroyWindow(m_window);
|
||||
}
|
||||
if (m_initialized) {
|
||||
terminate();
|
||||
}
|
||||
}
|
||||
|
||||
auto context::should_close() const -> bool {
|
||||
return m_window && glfwWindowShouldClose(m_window);
|
||||
}
|
||||
context::~context() {}
|
||||
|
||||
auto context::valid() const -> bool {
|
||||
return m_window != nullptr;
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
auto context::swap_buffers() -> void {
|
||||
glfwSwapBuffers(m_window);
|
||||
}
|
||||
|
||||
auto context::poll_events() -> void {
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
auto context::raw() const -> GLFWwindow* {
|
||||
return m_window;
|
||||
auto context::set_size(int width, int height) -> void {
|
||||
glViewport(0, 0, width, height);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-13
@@ -1,29 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include "GLFW/glfw3.h"
|
||||
#include "cbt/window.hpp"
|
||||
|
||||
namespace cbt::opengl {
|
||||
|
||||
class context {
|
||||
public:
|
||||
explicit context(std::string title, int width, int height);
|
||||
explicit context(window const& win);
|
||||
~context();
|
||||
|
||||
auto should_close() const -> bool;
|
||||
auto valid() const -> bool;
|
||||
auto swap_buffers() -> void;
|
||||
auto poll_events() -> void;
|
||||
auto raw() const -> GLFWwindow*;
|
||||
auto set_size(int width, int height) -> void;
|
||||
|
||||
private:
|
||||
GLFWwindow* m_window = nullptr;
|
||||
bool m_initialized = false;
|
||||
bool m_valid = false;
|
||||
|
||||
static auto init() -> bool;
|
||||
static auto terminate() -> void;
|
||||
static auto setup_gl() -> bool;
|
||||
static auto print_info() -> void;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "cbt/opengl/descriptor.hpp"
|
||||
|
||||
#include "glad/glad.h"
|
||||
|
||||
#include "cbt/opengl/descriptor.hpp"
|
||||
|
||||
namespace cbt::opengl {
|
||||
|
||||
descriptor_set::descriptor_set() {}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "cbt/opengl/buffer.hpp"
|
||||
#include "cbt/opengl/texture.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "cbt/opengl/buffer.hpp"
|
||||
#include "cbt/opengl/texture.hpp"
|
||||
|
||||
namespace cbt::opengl {
|
||||
|
||||
struct descriptor_binding {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include "cbt/opengl/shader.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "fmt/std.h"
|
||||
|
||||
#include "cbt/opengl/shader.hpp"
|
||||
|
||||
namespace cbt::opengl {
|
||||
|
||||
shader::shader() {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "cbt/opengl/texture.hpp"
|
||||
|
||||
#include "glad/glad.h"
|
||||
|
||||
#include "cbt/opengl/texture.hpp"
|
||||
|
||||
namespace cbt::opengl {
|
||||
|
||||
texture::texture() {
|
||||
|
||||
+1
-1
@@ -8,6 +8,6 @@ auto scene::init() -> bool {
|
||||
|
||||
auto scene::update(float) -> void {}
|
||||
|
||||
auto scene::render() -> void {}
|
||||
auto scene::render(int, int) -> void {}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ public:
|
||||
|
||||
virtual auto init() -> bool;
|
||||
virtual auto update(float delta_time) -> void;
|
||||
virtual auto render() -> void;
|
||||
virtual auto render(int width, int height) -> void;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
#ifdef _WIN32
|
||||
#include <dwmapi.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include "GLFW/glfw3.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define GLFW_EXPOSE_NATIVE_WIN32
|
||||
#include "GLFW/glfw3native.h"
|
||||
#endif
|
||||
|
||||
#include "fmt/std.h"
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h"
|
||||
#include "glad/glad.h"
|
||||
|
||||
#include "cbt/window.hpp"
|
||||
|
||||
namespace cbt {
|
||||
|
||||
auto window::init_glfw() -> bool {
|
||||
if (!glfwInit()) {
|
||||
fmt::print("Failed to initialize GLFW\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto window::terminate_glfw() -> void {
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
window::window(std::string title, int width, int height) {
|
||||
if (!init_glfw()) {
|
||||
return;
|
||||
}
|
||||
m_glfw_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_glfw();
|
||||
m_glfw_initialized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
|
||||
glfwSetWindowUserPointer(m_window, this);
|
||||
glfwSetWindowSizeCallback(m_window, resize_callback_impl);
|
||||
|
||||
#ifdef _WIN32
|
||||
auto hwnd = glfwGetWin32Window(m_window);
|
||||
BOOL use_dark_mode = TRUE;
|
||||
DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &use_dark_mode, sizeof(use_dark_mode));
|
||||
#endif
|
||||
}
|
||||
|
||||
window::~window() {
|
||||
if (m_window) {
|
||||
glfwDestroyWindow(m_window);
|
||||
}
|
||||
if (m_glfw_initialized) {
|
||||
terminate_glfw();
|
||||
}
|
||||
}
|
||||
|
||||
auto window::should_close() const -> bool {
|
||||
return m_window && glfwWindowShouldClose(m_window);
|
||||
}
|
||||
|
||||
auto window::valid() const -> bool {
|
||||
return m_window != nullptr;
|
||||
}
|
||||
|
||||
auto window::swap_buffers() -> void {
|
||||
glfwSwapBuffers(m_window);
|
||||
}
|
||||
|
||||
auto window::poll_events() -> void {
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
auto window::raw() const -> GLFWwindow* {
|
||||
return m_window;
|
||||
}
|
||||
|
||||
auto window::stop() -> void {
|
||||
if (m_window) {
|
||||
glfwSetWindowShouldClose(m_window, GLFW_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
auto window::width() const -> int {
|
||||
return m_width;
|
||||
}
|
||||
|
||||
auto window::height() const -> int {
|
||||
return m_height;
|
||||
}
|
||||
|
||||
auto window::on_resize(resize_callback cb) -> void {
|
||||
m_resize_cb = cb;
|
||||
}
|
||||
|
||||
auto window::resize_callback_impl(GLFWwindow* glfw_window, int width, int height) -> void {
|
||||
auto* self = static_cast<window*>(glfwGetWindowUserPointer(glfw_window));
|
||||
if (self) {
|
||||
self->m_width = width;
|
||||
self->m_height = height;
|
||||
if (self->m_resize_cb) {
|
||||
self->m_resize_cb(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto window::screenshot(std::string filepath) const -> bool {
|
||||
if (!m_window || !valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
glfwGetFramebufferSize(m_window, &width, &height);
|
||||
if (width <= 0 || height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> pixels(static_cast<size_t>(width * height * 4));
|
||||
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
|
||||
// Flip vertically (OpenGL reads bottom-up; PNG is top-down)
|
||||
for (int y = 0; y < height / 2; ++y) {
|
||||
int y2 = height - 1 - y;
|
||||
for (int x = 0; x < width * 4; ++x) {
|
||||
std::swap(pixels[static_cast<size_t>(y * width * 4 + x)],
|
||||
pixels[static_cast<size_t>(y2 * width * 4 + x)]);
|
||||
}
|
||||
}
|
||||
|
||||
if (stbi_write_png(filepath.c_str(), width, height, 4, pixels.data(), width * 4) != 0) {
|
||||
fmt::print("Screenshot saved to {}\n", filepath);
|
||||
return true;
|
||||
}
|
||||
|
||||
fmt::print("Failed to write screenshot to {}\n", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include "GLFW/glfw3.h"
|
||||
|
||||
namespace cbt {
|
||||
|
||||
class window {
|
||||
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 -> GLFWwindow*;
|
||||
auto stop() -> void;
|
||||
auto screenshot(std::string filepath = "screenshot.png") const -> bool;
|
||||
auto width() const -> int;
|
||||
auto height() const -> int;
|
||||
|
||||
using resize_callback = std::function<void(int width, int height)>;
|
||||
auto on_resize(resize_callback cb) -> void;
|
||||
|
||||
private:
|
||||
GLFWwindow* m_window = nullptr;
|
||||
int m_width = 0;
|
||||
int m_height = 0;
|
||||
resize_callback m_resize_cb;
|
||||
bool m_glfw_initialized = false;
|
||||
|
||||
static auto init_glfw() -> bool;
|
||||
static auto terminate_glfw() -> void;
|
||||
static auto resize_callback_impl(GLFWwindow* glfw_window, int width, int height) -> void;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,35 +1,100 @@
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include "GLFW/glfw3.h"
|
||||
|
||||
#include "cbt/opengl/context.hpp"
|
||||
#include "scenes/cube.hpp"
|
||||
|
||||
#include <csignal>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <asio.hpp>
|
||||
#include "GLFW/glfw3.h"
|
||||
#include "asio.hpp"
|
||||
#include "asio/steady_timer.hpp"
|
||||
#include "fmt/std.h"
|
||||
|
||||
auto main(int, char const*[]) -> int {
|
||||
auto ctx = cbt::opengl::context("cuber", 1280, 720);
|
||||
#include "cbt/window.hpp"
|
||||
#include "cbt/opengl/context.hpp"
|
||||
#include "scenes/cube.hpp"
|
||||
#include "scenes/sphere.hpp"
|
||||
|
||||
auto main(int argc, char const* argv[]) -> int {
|
||||
float max_duration_seconds = 0.0f;
|
||||
std::string_view scene_name = "cube";
|
||||
bool take_screenshot = false;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string_view arg = argv[i];
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
fmt::print("Usage: {} [options]\n", argv[0]);
|
||||
fmt::print(" --duration <seconds> Auto-terminate after N seconds (for testing/CI)\n");
|
||||
fmt::print(" --scene <cube|sphere> Select initial scene (default: cube)\n");
|
||||
fmt::print(" --screenshot Render one frame, save screenshot, and exit\n");
|
||||
fmt::print(" S key Take screenshot (saved as screenshot.png)\n");
|
||||
fmt::print(" 1/2 key Switch between cube/sphere scene\n");
|
||||
return 0;
|
||||
}
|
||||
if (arg == "--duration" && i + 1 < argc) {
|
||||
max_duration_seconds = std::stof(std::string(argv[++i]));
|
||||
continue;
|
||||
}
|
||||
if (arg == "--scene" && i + 1 < argc) {
|
||||
scene_name = argv[++i];
|
||||
continue;
|
||||
}
|
||||
if (arg == "--screenshot") {
|
||||
take_screenshot = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto win = cbt::window("cuber", 1280, 720);
|
||||
|
||||
if (!win.valid()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto ctx = cbt::opengl::context(win);
|
||||
|
||||
if (!ctx.valid()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto scn = cbt::scenes::cube();
|
||||
if (!scn.init()) {
|
||||
// Wire up resize callback
|
||||
win.on_resize([&ctx](int width, int height) {
|
||||
ctx.set_size(width, height);
|
||||
});
|
||||
|
||||
auto cube_scn = cbt::scenes::cube();
|
||||
auto sphere_scn = cbt::scenes::sphere();
|
||||
|
||||
cbt::scene* active_scene = nullptr;
|
||||
if (!cube_scn.init() || !sphere_scn.init()) {
|
||||
return 1;
|
||||
}
|
||||
if (scene_name == "sphere" || scene_name == "2") {
|
||||
active_scene = &sphere_scn;
|
||||
} else {
|
||||
active_scene = &cube_scn;
|
||||
}
|
||||
|
||||
// signal handling
|
||||
// signal handling + optional duration timer (via ASIO)
|
||||
asio::io_context io;
|
||||
asio::signal_set signals(io, SIGINT, SIGTERM);
|
||||
bool quit = false;
|
||||
|
||||
signals.async_wait([&](auto, auto) {
|
||||
quit = true;
|
||||
win.stop();
|
||||
io.stop();
|
||||
});
|
||||
|
||||
asio::steady_timer duration_timer(io);
|
||||
if (max_duration_seconds > 0.0f) {
|
||||
duration_timer.expires_after(std::chrono::milliseconds(
|
||||
static_cast<long long>(max_duration_seconds * 1000.0f)));
|
||||
duration_timer.async_wait([&win](auto ec) {
|
||||
if (!ec) {
|
||||
win.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
auto process_signals = [&]() -> void {
|
||||
while (io.poll()) {}
|
||||
};
|
||||
@@ -37,22 +102,36 @@ auto main(int, char const*[]) -> int {
|
||||
// render loop
|
||||
auto prev = std::chrono::steady_clock::now();
|
||||
|
||||
while (!ctx.should_close()) {
|
||||
while (!win.should_close()) {
|
||||
process_signals();
|
||||
|
||||
if (quit || glfwGetKey(ctx.raw(), GLFW_KEY_Q) == GLFW_PRESS) {
|
||||
break;
|
||||
if (glfwGetKey(win.raw(), GLFW_KEY_Q) == GLFW_PRESS) {
|
||||
win.stop();
|
||||
}
|
||||
if (glfwGetKey(win.raw(), GLFW_KEY_S) == GLFW_PRESS) {
|
||||
win.screenshot();
|
||||
}
|
||||
if (glfwGetKey(win.raw(), GLFW_KEY_1) == GLFW_PRESS) {
|
||||
active_scene = &cube_scn;
|
||||
}
|
||||
if (glfwGetKey(win.raw(), GLFW_KEY_2) == GLFW_PRESS) {
|
||||
active_scene = &sphere_scn;
|
||||
}
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto dt = std::chrono::duration<float>(now - prev).count();
|
||||
prev = now;
|
||||
|
||||
scn.update(dt);
|
||||
scn.render();
|
||||
active_scene->update(dt);
|
||||
active_scene->render(win.width(), win.height());
|
||||
|
||||
ctx.swap_buffers();
|
||||
ctx.poll_events();
|
||||
win.swap_buffers();
|
||||
win.poll_events();
|
||||
|
||||
if (take_screenshot) {
|
||||
win.screenshot();
|
||||
win.stop();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
Vendored
+13
-39
@@ -1,28 +1,17 @@
|
||||
# ==============================================================================
|
||||
# Find stb
|
||||
# ==============================================================================
|
||||
# This module fetches the stb single-file public domain libraries.
|
||||
# This module fetches the stb single-file public domain libraries
|
||||
# (stb_image_write.h for PNG writing, etc.).
|
||||
#
|
||||
# Targets provided:
|
||||
# stb::stb - The stb library target
|
||||
# stb::stb - Interface library (add #define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
# in exactly one .cpp before including "stb_image_write.h")
|
||||
#
|
||||
# Variables set:
|
||||
# stb_FOUND - TRUE if stb is available
|
||||
# stb_LIBRARIES - The stb library target (stb::stb)
|
||||
# stb_INCLUDE_DIR - Include directories for stb
|
||||
# stb_VERSION - Version of stb (commit-based)
|
||||
#
|
||||
# Usage notes:
|
||||
# stb headers are header-only but require an implementation macro to be
|
||||
# defined in exactly ONE translation unit (.cpp file) before including the
|
||||
# header, e.g.:
|
||||
#
|
||||
# #define STB_IMAGE_IMPLEMENTATION
|
||||
# #include <stb_image.h>
|
||||
#
|
||||
# Each stb header has its own implementation macro. Do NOT define the macro
|
||||
# in header files or in more than one translation unit or you will get
|
||||
# duplicate symbol linker errors.
|
||||
# stb_VERSION - Version of stb (commit)
|
||||
# ==============================================================================
|
||||
|
||||
if (DEFINED _FINDSTB_INCLUDED)
|
||||
@@ -30,46 +19,31 @@ if (DEFINED _FINDSTB_INCLUDED)
|
||||
endif()
|
||||
set(_FINDSTB_INCLUDED TRUE)
|
||||
|
||||
# Use the version passed to find_package(), or default to 2.30
|
||||
if (DEFINED stb_FIND_VERSION AND NOT stb_FIND_VERSION STREQUAL "")
|
||||
set(STB_VERSION "${stb_FIND_VERSION}")
|
||||
else()
|
||||
set(STB_VERSION "2fb8c5a3deb2110c89669f8d6f36e5833b556b44")
|
||||
endif()
|
||||
# Pin to a recent stable commit
|
||||
set(STB_VERSION "master")
|
||||
|
||||
message(STATUS "Fetching stb ${STB_VERSION}")
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
find_program(GIT_EXECUTABLE git)
|
||||
if (GIT_EXECUTABLE)
|
||||
set(STB_FETCH_METHOD "GIT")
|
||||
else()
|
||||
message(FATAL_ERROR "Fetch with zip not supported.")
|
||||
endif()
|
||||
|
||||
if (STB_FETCH_METHOD STREQUAL "GIT")
|
||||
FetchContent_Declare(
|
||||
stb
|
||||
GIT_REPOSITORY https://github.com/nothings/stb.git
|
||||
GIT_TAG ${STB_VERSION}
|
||||
)
|
||||
endif()
|
||||
|
||||
FetchContent_MakeAvailable(stb)
|
||||
|
||||
if (NOT TARGET stb)
|
||||
add_library(stb INTERFACE)
|
||||
target_include_directories(stb SYSTEM INTERFACE "${stb_SOURCE_DIR}")
|
||||
endif()
|
||||
target_include_directories(stb INTERFACE "${stb_SOURCE_DIR}")
|
||||
|
||||
if (NOT TARGET stb::stb)
|
||||
add_library(stb::stb ALIAS stb)
|
||||
endif()
|
||||
|
||||
set(stb_FOUND TRUE)
|
||||
set(stb_LIBRARIES stb::stb)
|
||||
set(stb_VERSION "${STB_VERSION}")
|
||||
set(stb_INCLUDE_DIR "${stb_SOURCE_DIR}")
|
||||
set(stb_VERSION "${STB_VERSION}")
|
||||
|
||||
set(STB_LICENSE_FILE "${stb_SOURCE_DIR}/LICENSE" CACHE FILEPATH "Path to stb license file")
|
||||
# Mark as SYSTEM includes
|
||||
set_target_properties(stb PROPERTIES
|
||||
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${stb_SOURCE_DIR}"
|
||||
)
|
||||
|
||||
+6
-7
@@ -1,11 +1,10 @@
|
||||
#include "scenes/cube.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "glad/glad.h"
|
||||
#include "glm/gtc/matrix_transform.hpp"
|
||||
#include "glm/gtc/type_ptr.hpp"
|
||||
|
||||
#include "scenes/cube.hpp"
|
||||
|
||||
namespace cbt::scenes {
|
||||
|
||||
@@ -24,14 +23,14 @@ auto cube::init() -> bool {
|
||||
|
||||
auto cube::update(float) -> void {}
|
||||
|
||||
auto cube::render() -> void {
|
||||
auto cube::render(int width, int height) -> void {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration<float>(now - m_start).count();
|
||||
|
||||
glClearColor(0.15f, 0.15f, 0.2f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
auto aspect = 1280.0f / 720.0f;
|
||||
auto aspect = float(width) / float(height);
|
||||
auto proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f);
|
||||
auto view = glm::translate(glm::mat4{1.0f}, glm::vec3{0.0f, 0.0f, -3.0f});
|
||||
auto model = glm::rotate(glm::mat4{1.0f}, elapsed, glm::vec3{1.0f, 0.5f, 0.3f});
|
||||
|
||||
+6
-5
@@ -1,13 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "glm/glm.hpp"
|
||||
|
||||
#include "cbt/scene.hpp"
|
||||
|
||||
#include "cbt/opengl/shader.hpp"
|
||||
#include "cbt/opengl/buffer.hpp"
|
||||
#include "cbt/opengl/shader.hpp"
|
||||
#include "cbt/opengl/vao.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace cbt::scenes {
|
||||
|
||||
class cube final : public scene {
|
||||
@@ -15,7 +16,7 @@ public:
|
||||
cube();
|
||||
auto init() -> bool override;
|
||||
auto update(float delta_time) -> void override;
|
||||
auto render() -> void override;
|
||||
auto render(int width, int height) -> void override;
|
||||
|
||||
private:
|
||||
opengl::shader m_prog;
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
#include <cstddef>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "glad/glad.h"
|
||||
#include "glm/gtc/matrix_transform.hpp"
|
||||
#include "glm/gtc/type_ptr.hpp"
|
||||
|
||||
#include "scenes/sphere.hpp"
|
||||
|
||||
namespace cbt::scenes {
|
||||
|
||||
sphere::sphere() {
|
||||
m_start = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
auto sphere::init() -> bool {
|
||||
if (!build_shader()) {
|
||||
return false;
|
||||
}
|
||||
build_mesh();
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto sphere::update(float) -> void {}
|
||||
|
||||
auto sphere::render(int width, int height) -> void {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration<float>(now - m_start).count();
|
||||
|
||||
glClearColor(0.15f, 0.15f, 0.2f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
auto aspect = float(width) / float(height);
|
||||
auto proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f);
|
||||
auto view = glm::translate(glm::mat4{1.0f}, glm::vec3{0.0f, 0.0f, -4.0f});
|
||||
auto model = glm::rotate(glm::mat4{1.0f}, elapsed, glm::vec3{1.0f, 0.3f, 0.2f});
|
||||
|
||||
m_prog.use();
|
||||
glUniformMatrix4fv(m_loc_proj, 1, GL_FALSE, glm::value_ptr(proj));
|
||||
glUniformMatrix4fv(m_loc_view, 1, GL_FALSE, glm::value_ptr(view));
|
||||
glUniformMatrix4fv(m_loc_model, 1, GL_FALSE, glm::value_ptr(model));
|
||||
|
||||
m_vao.bind();
|
||||
glDrawElements(GL_TRIANGLES, m_index_count, GL_UNSIGNED_INT, nullptr);
|
||||
m_vao.unbind();
|
||||
m_prog.unuse();
|
||||
}
|
||||
|
||||
auto sphere::build_mesh() -> void {
|
||||
struct vertex {
|
||||
glm::vec3 position;
|
||||
glm::vec3 normal;
|
||||
glm::vec2 uv;
|
||||
glm::vec3 color;
|
||||
};
|
||||
|
||||
std::uint32_t const div = 32;
|
||||
std::vector<vertex> vertices;
|
||||
std::vector<std::uint32_t> indices;
|
||||
|
||||
// Distinct colors for each cube face so seams are visible
|
||||
glm::vec3 const face_colors[6] = {
|
||||
{1.0f, 0.2f, 0.2f}, // +X red
|
||||
{0.2f, 1.0f, 0.2f}, // -X green
|
||||
{0.2f, 0.2f, 1.0f}, // +Y blue
|
||||
{1.0f, 1.0f, 0.2f}, // -Y yellow
|
||||
{1.0f, 0.2f, 1.0f}, // +Z magenta
|
||||
{0.2f, 1.0f, 1.0f}, // -Z cyan
|
||||
};
|
||||
|
||||
// Generate 6 cube faces, each with div x div vertices
|
||||
auto add_face = [&](glm::vec3 const& center, glm::vec3 const& u_axis,
|
||||
glm::vec3 const& v_axis, std::uint32_t face_idx) -> void {
|
||||
for (std::uint32_t i = 0; i < div; ++i) {
|
||||
for (std::uint32_t j = 0; j < div; ++j) {
|
||||
float const s = float(i) / float(div - 1) * 2.0f - 1.0f;
|
||||
float const t = float(j) / float(div - 1) * 2.0f - 1.0f;
|
||||
|
||||
// Position on cube face
|
||||
glm::vec3 pos = center + u_axis * s + v_axis * t;
|
||||
|
||||
// FIX: normalize to project onto unit sphere
|
||||
// (the original nrz.cpp used a broken formula with p=50.0)
|
||||
float const len = glm::length(pos);
|
||||
glm::vec3 normal = pos / len;
|
||||
|
||||
vertices.push_back({normal, normal, {float(i) / float(div - 1), float(j) / float(div - 1)}, face_colors[face_idx]});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// +X face (right)
|
||||
add_face(glm::vec3{1.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 1.0f, 0.0f}, glm::vec3{0.0f, 0.0f, 1.0f}, 0);
|
||||
// -X face (left)
|
||||
add_face(glm::vec3{-1.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 1.0f, 0.0f}, glm::vec3{0.0f, 0.0f, -1.0f}, 1);
|
||||
// +Y face (top)
|
||||
add_face(glm::vec3{0.0f, 1.0f, 0.0f}, glm::vec3{1.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 0.0f, -1.0f}, 2);
|
||||
// -Y face (bottom)
|
||||
add_face(glm::vec3{0.0f, -1.0f, 0.0f}, glm::vec3{1.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 0.0f, 1.0f}, 3);
|
||||
// +Z face (front)
|
||||
add_face(glm::vec3{0.0f, 0.0f, 1.0f}, glm::vec3{1.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 1.0f, 0.0f}, 4);
|
||||
// -Z face (back)
|
||||
add_face(glm::vec3{0.0f, 0.0f, -1.0f}, glm::vec3{-1.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 1.0f, 0.0f}, 5);
|
||||
|
||||
// Generate indices for each face
|
||||
std::uint32_t offset = 0;
|
||||
for (std::uint32_t face = 0; face < 6; ++face) {
|
||||
for (std::uint32_t i = 0; i < div - 1; ++i) {
|
||||
for (std::uint32_t j = 0; j < div - 1; ++j) {
|
||||
std::uint32_t const a = offset + i * div + j;
|
||||
std::uint32_t const b = offset + (i + 1) * div + j;
|
||||
std::uint32_t const c = offset + (i + 1) * div + j + 1;
|
||||
std::uint32_t const d = offset + i * div + j + 1;
|
||||
|
||||
// Two triangles per quad (consistent winding)
|
||||
indices.push_back(a);
|
||||
indices.push_back(b);
|
||||
indices.push_back(d);
|
||||
|
||||
indices.push_back(b);
|
||||
indices.push_back(c);
|
||||
indices.push_back(d);
|
||||
}
|
||||
}
|
||||
offset += div * div;
|
||||
}
|
||||
|
||||
m_index_count = static_cast<GLsizei>(indices.size());
|
||||
|
||||
m_vbo.upload(vertices.data(), vertices.size() * sizeof(vertex));
|
||||
m_ebo.upload(indices.data(), indices.size() * sizeof(std::uint32_t));
|
||||
|
||||
m_vao.bind();
|
||||
m_vbo.bind();
|
||||
m_ebo.bind();
|
||||
|
||||
// location 0: position (vec3)
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), nullptr);
|
||||
|
||||
// location 1: normal (vec3)
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(vertex),
|
||||
reinterpret_cast<void*>(3 * sizeof(float)));
|
||||
|
||||
// location 2: uv (vec2)
|
||||
glEnableVertexAttribArray(2);
|
||||
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(vertex),
|
||||
reinterpret_cast<void*>(6 * sizeof(float)));
|
||||
|
||||
// location 3: color (vec3)
|
||||
glEnableVertexAttribArray(3);
|
||||
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(vertex),
|
||||
reinterpret_cast<void*>(8 * sizeof(float)));
|
||||
|
||||
m_vao.unbind();
|
||||
m_vbo.unbind();
|
||||
m_ebo.unbind();
|
||||
}
|
||||
|
||||
auto sphere::build_shader() -> bool {
|
||||
char const* vert_src = R"glsl(
|
||||
#version 410 core
|
||||
layout(location = 0) in vec3 a_pos;
|
||||
layout(location = 1) in vec3 a_normal;
|
||||
layout(location = 2) in vec2 a_uv;
|
||||
layout(location = 3) in vec3 a_color;
|
||||
uniform mat4 u_model;
|
||||
uniform mat4 u_view;
|
||||
uniform mat4 u_proj;
|
||||
out vec3 v_normal;
|
||||
out vec3 v_world_pos;
|
||||
out vec2 v_uv;
|
||||
out vec3 v_color;
|
||||
void main() {
|
||||
gl_Position = u_proj * u_view * u_model * vec4(a_pos, 1.0);
|
||||
v_normal = mat3(u_model) * a_normal;
|
||||
v_world_pos = (u_model * vec4(a_pos, 1.0)).xyz;
|
||||
v_uv = a_uv;
|
||||
v_color = a_color;
|
||||
}
|
||||
)glsl";
|
||||
|
||||
char const* frag_src = R"glsl(
|
||||
#version 410 core
|
||||
in vec3 v_normal;
|
||||
in vec3 v_world_pos;
|
||||
in vec2 v_uv;
|
||||
in vec3 v_color;
|
||||
out vec4 frag_color;
|
||||
void main() {
|
||||
vec3 light_dir = normalize(vec3(1.0, 1.0, 2.0));
|
||||
vec3 normal = normalize(v_normal);
|
||||
float diff = max(dot(normal, light_dir), 0.0);
|
||||
vec3 ambient = vec3(0.2);
|
||||
vec3 result = (ambient + diff * 0.8) * v_color;
|
||||
frag_color = vec4(result, 1.0);
|
||||
}
|
||||
)glsl";
|
||||
|
||||
if (!m_prog.compile_vertex(vert_src) || !m_prog.compile_fragment(frag_src) || !m_prog.link()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_loc_proj = glGetUniformLocation(m_prog.id(), "u_proj");
|
||||
m_loc_view = glGetUniformLocation(m_prog.id(), "u_view");
|
||||
m_loc_model = glGetUniformLocation(m_prog.id(), "u_model");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "glm/glm.hpp"
|
||||
|
||||
#include "cbt/scene.hpp"
|
||||
#include "cbt/opengl/buffer.hpp"
|
||||
#include "cbt/opengl/shader.hpp"
|
||||
#include "cbt/opengl/vao.hpp"
|
||||
|
||||
namespace cbt::scenes {
|
||||
|
||||
class sphere final : public scene {
|
||||
public:
|
||||
sphere();
|
||||
auto init() -> bool override;
|
||||
auto update(float delta_time) -> void override;
|
||||
auto render(int width, int height) -> void override;
|
||||
|
||||
private:
|
||||
opengl::shader m_prog;
|
||||
opengl::buffer m_vbo;
|
||||
opengl::buffer m_ebo{opengl::buffer_type::index};
|
||||
opengl::vao m_vao;
|
||||
|
||||
GLint m_loc_proj = -1;
|
||||
GLint m_loc_view = -1;
|
||||
GLint m_loc_model = -1;
|
||||
|
||||
GLsizei m_index_count = 0;
|
||||
|
||||
std::chrono::steady_clock::time_point m_start;
|
||||
|
||||
auto build_mesh() -> void;
|
||||
auto build_shader() -> bool;
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user