diff --git a/CMakeLists.txt b/CMakeLists.txt index 73e1a79..4ff6e58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.28) option(APPLICATION_NAME "Application name" "Antkeeper") diff --git a/README.md b/README.md index bb58531..5306e9a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Antkeeper Source -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/ec1d9f614fdf4d5b8effa6b7b72b3d5e)](https://www.codacy.com/gh/antkeeper/antkeeper-source/dashboard?utm_source=github.com&utm_medium=referral&utm_content=antkeeper/antkeeper-source&utm_campaign=Badge_Grade) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/ec1d9f614fdf4d5b8effa6b7b72b3d5e)](https://app.codacy.com/gh/antkeeper/antkeeper-source/dashboard) [![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/antkeeper/antkeeper-source)](https://www.tickgit.com/browse?repo=github.com/antkeeper/antkeeper-source) [![Documentation](https://img.shields.io/badge/docs-doxygen-blue)](https://docs.antkeeper.com/latest/index) [![Discord](https://img.shields.io/discord/547138509610156036?logo=discord)](https://discord.gg/ptwHV4T) @@ -11,9 +11,7 @@ Head over to [antkeeper.com](https://antkeeper.com/) if you're interested in fol ## Building -Download the Antkeeper source code, build system, and all dependencies via the [Antkeeper superbuild](https://github.com/antkeeper/antkeeper-superbuild) repository: - - git clone --recursive https://github.com/antkeeper/antkeeper-superbuild.git +Download the Antkeeper source code, build system, and all dependencies via the [Antkeeper superbuild](https://github.com/antkeeper/antkeeper-superbuild) repository. Detailed configuration and build instructions can be found in the [README](https://github.com/antkeeper/antkeeper-superbuild/blob/master/README.md) of the superbuild repository. diff --git a/src/engine/app/sdl/sdl-input-manager.cpp b/src/engine/app/sdl/sdl-input-manager.cpp index 34b57a1..7359efa 100644 --- a/src/engine/app/sdl/sdl-input-manager.cpp +++ b/src/engine/app/sdl/sdl-input-manager.cpp @@ -30,15 +30,15 @@ namespace app { sdl_input_manager::sdl_input_manager() { // Init SDL joystick and controller subsystems - debug::log::trace("Initializing SDL joystick and controller subsystems..."); + debug::log_trace("Initializing SDL joystick and controller subsystems..."); if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0) { - debug::log::error("Failed to initialize SDL joystick and controller subsytems: {}", SDL_GetError()); + debug::log_error("Failed to initialize SDL joystick and controller subsytems: {}", SDL_GetError()); throw std::runtime_error("Failed to initialize SDL joystick and controller subsytems"); } else { - debug::log::trace("Initialized SDL joystick and controller subsystems"); + debug::log_trace("Initialized SDL joystick and controller subsystems"); } // Register keyboard and mouse @@ -53,9 +53,9 @@ sdl_input_manager::sdl_input_manager() sdl_input_manager::~sdl_input_manager() { // Quit SDL joystick and controller subsystems - debug::log::trace("Quitting SDL joystick and controller subsystems..."); + debug::log_trace("Quitting SDL joystick and controller subsystems..."); SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); - debug::log::trace("Quit SDL joystick and controller subsystems..."); + debug::log_trace("Quit SDL joystick and controller subsystems..."); } void sdl_input_manager::update() @@ -80,14 +80,14 @@ void sdl_input_manager::update() } else if (status < 0) { - debug::log::error("Failed to peep SDL events: {}", SDL_GetError()); + debug::log_error("Failed to peep SDL events: {}", SDL_GetError()); throw std::runtime_error("Failed to peep SDL events"); } switch (event.type) { case SDL_QUIT: - debug::log::debug("Application quit requested"); + debug::log_debug("Application quit requested"); this->m_event_dispatcher.dispatch({}); break; @@ -109,7 +109,7 @@ void sdl_input_manager::update() } else if (status < 0) { - debug::log::error("Failed to peep SDL events: {}", SDL_GetError()); + debug::log_error("Failed to peep SDL events: {}", SDL_GetError()); throw std::runtime_error("Failed to peep SDL events"); } @@ -254,7 +254,7 @@ void sdl_input_manager::update() if (auto it = m_gamepad_map.find(event.cdevice.which); it != m_gamepad_map.end()) { // Gamepad reconnected - debug::log::info("Reconnected gamepad {}", event.cdevice.which); + debug::log_info("Reconnected gamepad {}", event.cdevice.which); it->second->connect(); } else @@ -267,7 +267,7 @@ void sdl_input_manager::update() ::uuid gamepad_uuid; std::memcpy(gamepad_uuid.data.data(), sdl_guid.data, gamepad_uuid.data.size()); - debug::log::info("Connected gamepad {}; name: \"{}\"; UUID: {}", event.cdevice.which, controller_name, gamepad_uuid.string()); + debug::log_info("Connected gamepad {}; name: \"{}\"; UUID: {}", event.cdevice.which, controller_name, gamepad_uuid.string()); // Allocate gamepad auto& gamepad = m_gamepad_map[event.cdevice.which]; @@ -283,7 +283,7 @@ void sdl_input_manager::update() } else { - debug::log::error("Failed to connect gamepad {}: {}", event.cdevice.which, SDL_GetError()); + debug::log_error("Failed to connect gamepad {}: {}", event.cdevice.which, SDL_GetError()); SDL_ClearError(); } } @@ -303,7 +303,7 @@ void sdl_input_manager::update() it->second->disconnect(); } - debug::log::info("Disconnected gamepad {}", event.cdevice.which); + debug::log_info("Disconnected gamepad {}", event.cdevice.which); } break; @@ -322,7 +322,7 @@ void sdl_input_manager::set_cursor_visible(bool visible) { if (SDL_ShowCursor((visible) ? SDL_ENABLE : SDL_DISABLE) < 0) { - debug::log::error("Failed to set cursor visibility: \"{}\"", SDL_GetError()); + debug::log_error("Failed to set cursor visibility: \"{}\"", SDL_GetError()); SDL_ClearError(); } } @@ -331,7 +331,7 @@ void sdl_input_manager::set_relative_mouse_mode(bool enabled) { if (SDL_SetRelativeMouseMode((enabled) ? SDL_TRUE : SDL_FALSE) < 0) { - debug::log::error("Failed to set relative mouse mode: \"{}\"", SDL_GetError()); + debug::log_error("Failed to set relative mouse mode: \"{}\"", SDL_GetError()); SDL_ClearError(); } } diff --git a/src/engine/app/sdl/sdl-input-manager.hpp b/src/engine/app/sdl/sdl-input-manager.hpp index 4f20726..bdc0238 100644 --- a/src/engine/app/sdl/sdl-input-manager.hpp +++ b/src/engine/app/sdl/sdl-input-manager.hpp @@ -41,7 +41,7 @@ public: /** * Destructs an SDL input manager. */ - virtual ~sdl_input_manager(); + ~sdl_input_manager() override; void update() override; void set_cursor_visible(bool visible) override; diff --git a/src/engine/app/sdl/sdl-window-manager.cpp b/src/engine/app/sdl/sdl-window-manager.cpp index 8f86ac4..7814c8a 100644 --- a/src/engine/app/sdl/sdl-window-manager.cpp +++ b/src/engine/app/sdl/sdl-window-manager.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace app { @@ -28,25 +29,25 @@ namespace app { sdl_window_manager::sdl_window_manager() { // Init SDL events and video subsystems - debug::log::trace("Initializing SDL events and video subsystems..."); + debug::log_trace("Initializing SDL events and video subsystems..."); if (SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO) != 0) { - debug::log::fatal("Failed to initialize SDL events and video subsystems: {}", SDL_GetError()); + debug::log_fatal("Failed to initialize SDL events and video subsystems: {}", SDL_GetError()); throw std::runtime_error("Failed to initialize SDL events and video subsystems"); } - debug::log::trace("Initialized SDL events and video subsystems"); + debug::log_trace("Initialized SDL events and video subsystems"); // Query displays const int display_count = SDL_GetNumVideoDisplays(); if (display_count < 1) { - debug::log::warning("No displays detected: {}", SDL_GetError()); + debug::log_warning("No displays detected: {}", SDL_GetError()); } else { // Allocate displays m_displays.resize(display_count); - debug::log::info("Display count: {}", display_count); + debug::log_info("Display count: {}", display_count); for (int i = 0; i < display_count; ++i) { @@ -56,25 +57,31 @@ sdl_window_manager::sdl_window_manager() // Log display information display& display = m_displays[i]; const auto display_resolution = display.get_bounds().size(); - debug::log::info("Display {} name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", i, display.get_name(), display_resolution.x(), display_resolution.y(), display.get_refresh_rate(), display.get_dpi()); + debug::log_info("Display {} name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", i, display.get_name(), display_resolution.x(), display_resolution.y(), display.get_refresh_rate(), display.get_dpi()); } } // Load OpenGL library - debug::log::trace("Loading OpenGL library..."); + debug::log_trace("Loading OpenGL library..."); if (SDL_GL_LoadLibrary(nullptr) != 0) { - debug::log::fatal("Failed to load OpenGL library: {}", SDL_GetError()); + debug::log_fatal("Failed to load OpenGL library: {}", SDL_GetError()); throw std::runtime_error("Failed to load OpenGL library"); } - debug::log::trace("Loaded OpenGL library"); + debug::log_trace("Loaded OpenGL library"); // Set OpenGL-related window creation hints SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config::opengl_version_major); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config::opengl_version_minor); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + + #if defined(DEBUG) + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG | SDL_GL_CONTEXT_DEBUG_FLAG); + #else + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + #endif + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, config::opengl_min_red_size); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, config::opengl_min_green_size); @@ -87,9 +94,9 @@ sdl_window_manager::sdl_window_manager() sdl_window_manager::~sdl_window_manager() { // Quit SDL video subsystem - debug::log::trace("Quitting SDL video subsystem..."); + debug::log_trace("Quitting SDL video subsystem..."); SDL_QuitSubSystem(SDL_INIT_VIDEO); - debug::log::trace("Quit SDL video subsystem"); + debug::log_trace("Quit SDL video subsystem"); } std::shared_ptr sdl_window_manager::create_window @@ -136,7 +143,7 @@ void sdl_window_manager::update() } else if (status < 0) { - debug::log::error("Failed to peep SDL events: {}", SDL_GetError()); + debug::log_error("Failed to peep SDL events: {}", SDL_GetError()); throw std::runtime_error("Failed to peep SDL events"); } @@ -159,10 +166,16 @@ void sdl_window_manager::update() window->m_windowed_size = window->m_size; } SDL_GL_GetDrawableSize(internal_window, &window->m_viewport_size.x(), &window->m_viewport_size.y()); - window->m_rasterizer->context_resized(window->m_viewport_size.x(), window->m_viewport_size.y()); + + // Change reported dimensions of graphics pipeline default framebuffer + window->m_graphics_pipeline->defaut_framebuffer_resized + ( + static_cast(window->m_viewport_size.x()), + static_cast(window->m_viewport_size.y()) + ); // Log window resized event - debug::log::debug("Window {} resized to {}x{}", event.window.windowID, event.window.data1, event.window.data2); + debug::log_debug("Window {} resized to {}x{}", event.window.windowID, event.window.data1, event.window.data2); // Publish window resized event window->m_resized_publisher.publish({window, window->m_size}); @@ -184,7 +197,7 @@ void sdl_window_manager::update() } // Log window moved event - debug::log::debug("Window {} moved to ({}, {})", event.window.windowID, event.window.data1, event.window.data2); + debug::log_debug("Window {} moved to ({}, {})", event.window.windowID, event.window.data1, event.window.data2); // Publish window moved event window->m_moved_publisher.publish({window, window->m_position}); @@ -198,7 +211,7 @@ void sdl_window_manager::update() app::sdl_window* window = get_window(internal_window); // Log window focus gained event - debug::log::debug("Window {} gained focus", event.window.windowID); + debug::log_debug("Window {} gained focus", event.window.windowID); // Publish window focus gained event window->m_focus_changed_publisher.publish({window, true}); @@ -212,7 +225,7 @@ void sdl_window_manager::update() app::sdl_window* window = get_window(internal_window); // Log window focus lost event - debug::log::debug("Window {} lost focus", event.window.windowID); + debug::log_debug("Window {} lost focus", event.window.windowID); // Publish window focus lost event window->m_focus_changed_publisher.publish({window, false}); @@ -229,7 +242,7 @@ void sdl_window_manager::update() window->m_maximized = true; // Log window focus gained event - debug::log::debug("Window {} maximized", event.window.windowID); + debug::log_debug("Window {} maximized", event.window.windowID); // Publish window maximized event window->m_maximized_publisher.publish({window}); @@ -246,7 +259,7 @@ void sdl_window_manager::update() window->m_maximized = false; // Log window restored event - debug::log::debug("Window {} restored", event.window.windowID); + debug::log_debug("Window {} restored", event.window.windowID); // Publish window restored event window->m_restored_publisher.publish({window}); @@ -260,7 +273,7 @@ void sdl_window_manager::update() app::sdl_window* window = get_window(internal_window); // Log window focus gained event - debug::log::debug("Window {} minimized", event.window.windowID); + debug::log_debug("Window {} minimized", event.window.windowID); // Publish window minimized event window->m_minimized_publisher.publish({window}); @@ -274,7 +287,7 @@ void sdl_window_manager::update() app::sdl_window* window = get_window(internal_window); // Log window closed event - debug::log::debug("Window {} closed", event.window.windowID); + debug::log_debug("Window {} closed", event.window.windowID); // Publish window closed event window->m_closed_publisher.publish({window}); @@ -299,7 +312,7 @@ void sdl_window_manager::update() display.m_connected = true; // Log display (re)connected event - debug::log::info("Reconnected display {}", event.display.display); + debug::log_info("Reconnected display {}", event.display.display); // Publish display (re)connected event display.m_connected_publisher.publish({&display}); @@ -315,14 +328,14 @@ void sdl_window_manager::update() // Log display info const auto display_resolution = display.get_bounds().size(); - debug::log::info("Connected display {}; name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", event.display.display, display.get_name(), display_resolution.x(), display_resolution.y(), display.get_refresh_rate(), display.get_dpi()); + debug::log_info("Connected display {}; name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", event.display.display, display.get_name(), display_resolution.x(), display_resolution.y(), display.get_refresh_rate(), display.get_dpi()); // Publish display connected event display.m_connected_publisher.publish({&display}); } else { - debug::log::error("Index of connected display ({}) out of range", event.display.display); + debug::log_error("Index of connected display ({}) out of range", event.display.display); } break; @@ -336,14 +349,14 @@ void sdl_window_manager::update() display.m_connected = false; // Log display disconnected event - debug::log::info("Disconnected display {}", event.display.display); + debug::log_info("Disconnected display {}", event.display.display); // Publish display disconnected event display.m_disconnected_publisher.publish({&display}); } else { - debug::log::error("Index of disconnected display ({}) out of range", event.display.display); + debug::log_error("Index of disconnected display ({}) out of range", event.display.display); } break; @@ -374,14 +387,14 @@ void sdl_window_manager::update() } // Log display orientation changed event - debug::log::info("Display {} orientation changed", event.display.display); + debug::log_info("Display {} orientation changed", event.display.display); // Publish display orientation changed event display.m_orientation_changed_publisher.publish({&display, display.get_orientation()}); } else { - debug::log::error("Index of orientation-changed display ({}) out of range", event.display.display); + debug::log_error("Index of orientation-changed display ({}) out of range", event.display.display); } break; @@ -422,7 +435,7 @@ void sdl_window_manager::update_display(int sdl_display_index) SDL_DisplayMode sdl_display_mode; if (SDL_GetDesktopDisplayMode(sdl_display_index, &sdl_display_mode) != 0) { - debug::log::error("Failed to get mode of display {}: {}", sdl_display_index, SDL_GetError()); + debug::log_error("Failed to get mode of display {}: {}", sdl_display_index, SDL_GetError()); SDL_ClearError(); sdl_display_mode = {0, 0, 0, 0, nullptr}; } @@ -431,7 +444,7 @@ void sdl_window_manager::update_display(int sdl_display_index) const char* sdl_display_name = SDL_GetDisplayName(sdl_display_index); if (!sdl_display_name) { - debug::log::warning("Failed to get name of display {}: {}", sdl_display_index, SDL_GetError()); + debug::log_warning("Failed to get name of display {}: {}", sdl_display_index, SDL_GetError()); SDL_ClearError(); sdl_display_name = nullptr; } @@ -440,7 +453,7 @@ void sdl_window_manager::update_display(int sdl_display_index) SDL_Rect sdl_display_bounds; if (SDL_GetDisplayBounds(sdl_display_index, &sdl_display_bounds) != 0) { - debug::log::warning("Failed to get bounds of display {}: {}", sdl_display_index, SDL_GetError()); + debug::log_warning("Failed to get bounds of display {}: {}", sdl_display_index, SDL_GetError()); SDL_ClearError(); sdl_display_bounds = {0, 0, sdl_display_mode.w, sdl_display_mode.h}; } @@ -449,7 +462,7 @@ void sdl_window_manager::update_display(int sdl_display_index) SDL_Rect sdl_display_usable_bounds; if (SDL_GetDisplayUsableBounds(sdl_display_index, &sdl_display_usable_bounds) != 0) { - debug::log::warning("Failed to get usable bounds of display {}: {}", sdl_display_index, SDL_GetError()); + debug::log_warning("Failed to get usable bounds of display {}: {}", sdl_display_index, SDL_GetError()); SDL_ClearError(); sdl_display_usable_bounds = sdl_display_bounds; } @@ -458,7 +471,7 @@ void sdl_window_manager::update_display(int sdl_display_index) float sdl_display_dpi; if (SDL_GetDisplayDPI(sdl_display_index, &sdl_display_dpi, nullptr, nullptr) != 0) { - debug::log::warning("Failed to get DPI of display {}: {}", sdl_display_index, SDL_GetError()); + debug::log_warning("Failed to get DPI of display {}: {}", sdl_display_index, SDL_GetError()); SDL_ClearError(); sdl_display_dpi = 0.0f; } diff --git a/src/engine/app/sdl/sdl-window-manager.hpp b/src/engine/app/sdl/sdl-window-manager.hpp index 32123e1..79c9452 100644 --- a/src/engine/app/sdl/sdl-window-manager.hpp +++ b/src/engine/app/sdl/sdl-window-manager.hpp @@ -44,12 +44,12 @@ public: /** * Destructs an SDL window manager. */ - virtual ~sdl_window_manager(); + ~sdl_window_manager() override; - virtual void update(); + void update() override; /// @copydoc window::window() - [[nodiscard]] virtual std::shared_ptr create_window + [[nodiscard]] std::shared_ptr create_window ( const std::string& title, const math::ivec2& windowed_position, @@ -57,10 +57,10 @@ public: bool maximized, bool fullscreen, bool v_sync - ); + ) override; - [[nodiscard]] virtual std::size_t get_display_count() const; - [[nodiscard]] virtual const display& get_display(std::size_t index) const; + [[nodiscard]] std::size_t get_display_count() const override; + [[nodiscard]] const display& get_display(std::size_t index) const override; private: sdl_window* get_window(SDL_Window* internal_window); diff --git a/src/engine/app/sdl/sdl-window.cpp b/src/engine/app/sdl/sdl-window.cpp index ffe2447..11de90d 100644 --- a/src/engine/app/sdl/sdl-window.cpp +++ b/src/engine/app/sdl/sdl-window.cpp @@ -20,7 +20,8 @@ #include #include #include -#include +#include +#include #include namespace app { @@ -47,7 +48,7 @@ sdl_window::sdl_window } // Create SDL window - debug::log::trace("Creating SDL window..."); + debug::log_trace("Creating SDL window..."); m_internal_window = SDL_CreateWindow ( title.c_str(), @@ -59,20 +60,20 @@ sdl_window::sdl_window ); if (!m_internal_window) { - debug::log::fatal("Failed to create SDL window: {}", SDL_GetError()); + debug::log_fatal("Failed to create SDL window: {}", SDL_GetError()); throw std::runtime_error("Failed to create SDL window"); } - debug::log::trace("Created SDL window"); + debug::log_trace("Created SDL window"); // Create OpenGL context - debug::log::trace("Creating OpenGL context..."); + debug::log_trace("Creating OpenGL context..."); m_internal_context = SDL_GL_CreateContext(m_internal_window); if (!m_internal_context) { - debug::log::fatal("Failed to create OpenGL context: {}", SDL_GetError()); + debug::log_fatal("Failed to create OpenGL context: {}", SDL_GetError()); throw std::runtime_error("Failed to create OpenGL context"); } - debug::log::trace("Created OpenGL context"); + debug::log_trace("Created OpenGL context"); // Query OpenGL context info int opengl_context_version_major = -1; @@ -93,7 +94,7 @@ sdl_window::sdl_window SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &opengl_context_stencil_size); // Log OpenGL context info - debug::log::info + debug::log_info ( "OpenGL context version: {}.{}; format: R{}G{}B{}A{}D{}S{}", opengl_context_version_major, @@ -110,7 +111,7 @@ sdl_window::sdl_window if (opengl_context_version_major != config::opengl_version_major || opengl_context_version_minor != config::opengl_version_minor) { - debug::log::warning("Requested OpenGL context version {}.{} but got version {}.{}", config::opengl_version_major, config::opengl_version_minor, opengl_context_version_major, opengl_context_version_minor); + debug::log_warning("Requested OpenGL context version {}.{} but got version {}.{}", config::opengl_version_major, config::opengl_version_minor, opengl_context_version_major, opengl_context_version_minor); } // Compare OpenGL context format with requested format @@ -121,7 +122,7 @@ sdl_window::sdl_window opengl_context_depth_size < config::opengl_min_depth_size || opengl_context_stencil_size < config::opengl_min_stencil_size) { - debug::log::warning + debug::log_warning ( "OpenGL context format (R{}G{}B{}A{}D{}S{}) does not meet minimum requested format (R{}G{}B{}A{}D{}S{})", opengl_context_red_size, @@ -140,16 +141,16 @@ sdl_window::sdl_window } // Load OpenGL functions via GLAD - debug::log::trace("Loading OpenGL functions..."); - if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) + debug::log_trace("Loading OpenGL functions..."); + if (!gladLoadGL(reinterpret_cast(SDL_GL_GetProcAddress))) { - debug::log::fatal("Failed to load OpenGL functions", SDL_GetError()); + debug::log_fatal("Failed to load OpenGL functions", SDL_GetError()); throw std::runtime_error("Failed to load OpenGL functions"); } - debug::log::trace("Loaded OpenGL functions"); + debug::log_trace("Loaded OpenGL functions"); // Log OpenGL information - debug::log::info + debug::log_info ( "OpenGL vendor: {}; renderer: {}; version: {}; shading language version: {}", reinterpret_cast(glGetString(GL_VENDOR)), @@ -158,9 +159,11 @@ sdl_window::sdl_window reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION)) ); - // Fill window with color - //glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); + // Allocate graphics pipeline + m_graphics_pipeline = std::make_unique(); + + // Clear default framebuffer to black + m_graphics_pipeline->clear_attachments(gl::color_clear_bit, {{0.0f, 0.0f, 0.0f, 0.0f}}); swap_buffers(); // Enable or disable v-sync @@ -177,15 +180,12 @@ sdl_window::sdl_window SDL_GetWindowMinimumSize(m_internal_window, &this->m_minimum_size.x(), &this->m_minimum_size.y()); SDL_GetWindowMaximumSize(m_internal_window, &this->m_maximum_size.x(), &this->m_maximum_size.y()); SDL_GL_GetDrawableSize(m_internal_window, &this->m_viewport_size.x(), &this->m_viewport_size.y()); - - // Allocate m_rasterizer - this->m_rasterizer = std::make_unique(); } sdl_window::~sdl_window() { - // Deallocate m_rasterizer - m_rasterizer.reset(); + // Deallocate graphics pipeline + m_graphics_pipeline.reset(); // Destruct the OpenGL context SDL_GL_DeleteContext(m_internal_context); @@ -246,37 +246,37 @@ void sdl_window::set_v_sync(bool v_sync) { if (v_sync) { - debug::log::trace("Enabling adaptive v-sync..."); + debug::log_trace("Enabling adaptive v-sync..."); if (SDL_GL_SetSwapInterval(-1) != 0) { - debug::log::error("Failed to enable adaptive v-sync: {}", SDL_GetError()); - debug::log::trace("Enabling synchronized v-sync..."); + debug::log_error("Failed to enable adaptive v-sync: {}", SDL_GetError()); + debug::log_trace("Enabling synchronized v-sync..."); if (SDL_GL_SetSwapInterval(1) != 0) { - debug::log::error("Failed to enable synchronized v-sync: {}", SDL_GetError()); + debug::log_error("Failed to enable synchronized v-sync: {}", SDL_GetError()); v_sync = false; } else { - debug::log::debug("Enabled synchronized v-sync"); + debug::log_debug("Enabled synchronized v-sync"); } } else { - debug::log::debug("Enabled adaptive v-sync"); + debug::log_debug("Enabled adaptive v-sync"); } } else { - debug::log::trace("Disabling v-sync..."); + debug::log_trace("Disabling v-sync..."); if (SDL_GL_SetSwapInterval(0) != 0) { - debug::log::error("Failed to disable v-sync: {}", SDL_GetError()); + debug::log_error("Failed to disable v-sync: {}", SDL_GetError()); v_sync = true; } else { - debug::log::debug("Disabled v-sync"); + debug::log_debug("Disabled v-sync"); } } diff --git a/src/engine/app/sdl/sdl-window.hpp b/src/engine/app/sdl/sdl-window.hpp index fad71cb..ce3b755 100644 --- a/src/engine/app/sdl/sdl-window.hpp +++ b/src/engine/app/sdl/sdl-window.hpp @@ -48,20 +48,25 @@ public: sdl_window& operator=(sdl_window&&) = delete; virtual ~sdl_window(); - virtual void set_title(const std::string& title); - virtual void set_position(const math::ivec2& position); - virtual void set_size(const math::ivec2& size); - virtual void set_minimum_size(const math::ivec2& size); - virtual void set_maximum_size(const math::ivec2& size); - virtual void set_maximized(bool maximized); - virtual void set_fullscreen(bool fullscreen); - virtual void set_v_sync(bool v_sync); - virtual void make_current(); - virtual void swap_buffers(); + void set_title(const std::string& title) override; + void set_position(const math::ivec2& position) override; + void set_size(const math::ivec2& size) override; + void set_minimum_size(const math::ivec2& size) override; + void set_maximum_size(const math::ivec2& size) override; + void set_maximized(bool maximized) override; + void set_fullscreen(bool fullscreen) override; + void set_v_sync(bool v_sync) override; + void make_current() override; + void swap_buffers() override; - [[nodiscard]] inline virtual gl::rasterizer* get_rasterizer() noexcept + [[nodiscard]] inline const gl::pipeline& get_graphics_pipeline() const noexcept override { - return m_rasterizer.get(); + return *m_graphics_pipeline; + } + + [[nodiscard]] inline gl::pipeline& get_graphics_pipeline() noexcept override + { + return *m_graphics_pipeline; } private: @@ -69,7 +74,7 @@ private: SDL_Window* m_internal_window; SDL_GLContext m_internal_context; - std::unique_ptr m_rasterizer; + std::unique_ptr m_graphics_pipeline; }; } // namespace app diff --git a/src/engine/app/window.hpp b/src/engine/app/window.hpp index eab5306..800bba1 100644 --- a/src/engine/app/window.hpp +++ b/src/engine/app/window.hpp @@ -23,9 +23,14 @@ #include #include #include -#include #include +namespace gl { + + class pipeline; + +} + namespace app { class window_manager; @@ -168,8 +173,11 @@ public: return m_v_sync; } - /// Returns the rasterizer associated with this window. - [[nodiscard]] virtual gl::rasterizer* get_rasterizer() noexcept = 0; + /// Returns the graphics pipeline associated with this window. + /// @{ + [[nodiscard]] virtual const gl::pipeline& get_graphics_pipeline() const noexcept = 0; + [[nodiscard]] virtual gl::pipeline& get_graphics_pipeline() noexcept = 0; + /// @} /// Returns the channel through which window closed events are published. [[nodiscard]] inline event::channel& get_closed_channel() noexcept diff --git a/src/engine/color/aces.hpp b/src/engine/color/aces.hpp index 6c7692d..4b35a4d 100644 --- a/src/engine/color/aces.hpp +++ b/src/engine/color/aces.hpp @@ -79,11 +79,11 @@ template /// ACES AP1 RRT saturation adjustment matrix. template -constexpr math::mat3 aces_ap1_rrt_sat = aces_adjust_saturation(T{0.96}, ap1.to_y); +constexpr math::mat3 aces_ap1_rrt_sat = aces_adjust_saturation(T{0.96}, aces_ap1.to_y); /// ACES AP1 ODT saturation adjustment matrix. template -constexpr math::mat3 aces_ap1_odt_sat = aces_adjust_saturation(T{0.93}, ap1.to_y); +constexpr math::mat3 aces_ap1_odt_sat = aces_adjust_saturation(T{0.93}, aces_ap1.to_y); /// @} diff --git a/src/engine/config.hpp.in b/src/engine/config.hpp.in index 1eb55c7..1a7750c 100644 --- a/src/engine/config.hpp.in +++ b/src/engine/config.hpp.in @@ -68,10 +68,10 @@ inline constexpr std::size_t debug_log_archive_capacity = 5; /// @{ /// OpenGL major version number, used when creating OpenGL contexts. -inline constexpr int opengl_version_major = 3; +inline constexpr int opengl_version_major = 4; /// OpenGL minor version number, used when creating OpenGL contexts. -inline constexpr int opengl_version_minor = 3; +inline constexpr int opengl_version_minor = 6; /// Minimum number of bits in the red channel of the color attachment of the OpenGL default framebuffer. inline constexpr int opengl_min_red_size = 8; diff --git a/src/engine/debug/log.cpp b/src/engine/debug/log.cpp index 82cc21a..7ed560a 100644 --- a/src/engine/debug/log.cpp +++ b/src/engine/debug/log.cpp @@ -20,7 +20,6 @@ #include namespace debug { -namespace log { logger& default_logger() noexcept { @@ -28,5 +27,4 @@ logger& default_logger() noexcept return instance; } -} // namespace log } // namespace debug diff --git a/src/engine/debug/log.hpp b/src/engine/debug/log.hpp index 59df307..028cdf9 100644 --- a/src/engine/debug/log.hpp +++ b/src/engine/debug/log.hpp @@ -21,7 +21,7 @@ #define ANTKEEPER_DEBUG_LOG_HPP #include -#include +#include #include #include #include @@ -34,10 +34,8 @@ namespace debug { -/** - * Debug message logging. - */ -namespace log { +/// @name Debug logging +/// @{ /** * Returns the default logger. @@ -50,8 +48,8 @@ namespace log { * @tparam Severity Message severity. A message will not log itself if @p Severity is less than the user-defined macro `ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY`. * @tparam Args Types of arguments to be formatted. */ -template -struct message +template +struct log_message { /** * Formats and logs a message. @@ -62,14 +60,14 @@ struct message * @param args Arguments to be formatted. * @param location Source location from which the message was sent. */ - message + log_message ( [[maybe_unused]] std::string_view format, [[maybe_unused]] Args&&... args, [[maybe_unused]] std::source_location&& location = std::source_location::current() ) { - if constexpr (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= static_cast>(Severity)) + if constexpr (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= static_cast>(Severity)) { default_logger().log(std::vformat(format, std::make_format_args(std::forward(args)...)), Severity, std::forward(location)); } @@ -77,8 +75,8 @@ struct message }; // Use class template argument deduction (CTAD) to capture source location as a default argument following variadic format arguments. -template -message(std::string_view, Args&&...) -> message; +template +log_message(std::string_view, Args&&...) -> log_message; #if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_TRACE) /** @@ -87,11 +85,11 @@ message(std::string_view, Args&&...) -> message; * @tparam Args Types of arguments to be formatted. */ template - using trace = message; + using log_trace = log_message; #else // Disable trace message logging. template - inline void trace([[maybe_unused]] Args&&...) noexcept {}; + inline void log_trace([[maybe_unused]] Args&&...) noexcept {}; #endif #if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_DEBUG) @@ -101,11 +99,11 @@ message(std::string_view, Args&&...) -> message; * @tparam Args Types of arguments to be formatted. */ template - using debug = message; + using log_debug = log_message; #else // Disable debug message logging. template - inline void debug([[maybe_unused]] Args&&...) noexcept {}; + inline void log_debug([[maybe_unused]] Args&&...) noexcept {}; #endif #if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_INFO) @@ -115,11 +113,11 @@ message(std::string_view, Args&&...) -> message; * @tparam Args Types of arguments to be formatted. */ template - using info = message; + using log_info = log_message; #else // Disable info message logging. template - inline void info([[maybe_unused]] Args&&...) noexcept {}; + inline void log_info([[maybe_unused]] Args&&...) noexcept {}; #endif #if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_WARNING) @@ -129,11 +127,11 @@ message(std::string_view, Args&&...) -> message; * @tparam Args Types of arguments to be formatted. */ template - using warning = message; + using log_warning = log_message; #else // Disable warning message logging. template - inline void warning([[maybe_unused]] Args&&...) noexcept {}; + inline void log_warning([[maybe_unused]] Args&&...) noexcept {}; #endif #if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_ERROR) @@ -143,11 +141,11 @@ message(std::string_view, Args&&...) -> message; * @tparam Args Types of arguments to be formatted. */ template - using error = message; + using log_error = log_message; #else // Disable error message logging. template - inline void error([[maybe_unused]] Args&&...) noexcept {}; + inline void log_error([[maybe_unused]] Args&&...) noexcept {}; #endif #if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_FATAL) @@ -157,14 +155,15 @@ message(std::string_view, Args&&...) -> message; * @tparam Args Types of arguments to be formatted. */ template - using fatal = message; + using log_fatal = log_message; #else // Disable fatal error message logging. template - inline void fatal([[maybe_unused]] Args&&...) noexcept {}; + inline void log_fatal([[maybe_unused]] Args&&...) noexcept {}; #endif -} // namespace log +/// @} + } // namespace debug #endif // ANTKEEPER_DEBUG_LOG_HPP diff --git a/src/engine/debug/log/event.hpp b/src/engine/debug/log/log-events.hpp similarity index 80% rename from src/engine/debug/log/event.hpp rename to src/engine/debug/log/log-events.hpp index 87abcff..016ca7c 100644 --- a/src/engine/debug/log/event.hpp +++ b/src/engine/debug/log/log-events.hpp @@ -17,32 +17,29 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_DEBUG_LOG_EVENT_HPP -#define ANTKEEPER_DEBUG_LOG_EVENT_HPP +#ifndef ANTKEEPER_DEBUG_LOG_EVENTS_HPP +#define ANTKEEPER_DEBUG_LOG_EVENTS_HPP -#include +#include #include #include #include #include namespace debug { -namespace log { class logger; -/** - * Debug logging events. - */ -namespace event { +/// @name Debug logging +/// @{ /** * Event generated when a message has been logged. */ -struct message_logged +struct message_logged_event { /// Logger which received the message. - log::logger* logger; + debug::logger* logger; /// Time at which the message was sent. std::chrono::time_point time; @@ -54,14 +51,14 @@ struct message_logged std::source_location location; /// Severity of the message. - message_severity severity; + log_message_severity severity; /// Message contents. std::string message; }; -} // namespace event -} // namespace log +/// @} + } // namespace debug -#endif // ANTKEEPER_DEBUG_LOG_EVENT_HPP +#endif // ANTKEEPER_DEBUG_LOG_EVENTS_HPP diff --git a/src/engine/debug/log/message-severity.hpp b/src/engine/debug/log/log-message-severity.hpp similarity index 96% rename from src/engine/debug/log/message-severity.hpp rename to src/engine/debug/log/log-message-severity.hpp index f70ac23..fed48c0 100644 --- a/src/engine/debug/log/message-severity.hpp +++ b/src/engine/debug/log/log-message-severity.hpp @@ -22,6 +22,11 @@ #include +namespace debug { + +/// @name Debug logging +/// @{ + /// Trace message severity level. #define ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_TRACE 0 @@ -40,11 +45,8 @@ /// Fatal error message severity level. #define ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_FATAL 5 -namespace debug { -namespace log { - /// Log message severity levels. -enum class message_severity: std::uint8_t +enum class log_message_severity: std::uint8_t { /// Trace message severity. trace = ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_TRACE, @@ -65,7 +67,8 @@ enum class message_severity: std::uint8_t fatal = ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_FATAL, }; -} // namespace log +/// @} + } // namespace debug #endif // ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_HPP diff --git a/src/engine/debug/log/logger.cpp b/src/engine/debug/log/logger.cpp index 5c02a12..c93ceeb 100644 --- a/src/engine/debug/log/logger.cpp +++ b/src/engine/debug/log/logger.cpp @@ -22,12 +22,11 @@ #include namespace debug { -namespace log { -void logger::log(std::string&& message, message_severity severity, std::source_location&& location) +void logger::log(std::string&& message, log_message_severity severity, std::source_location&& location) { // Generate message logged event - message_logged_publisher.publish + m_message_logged_publisher.publish ( { this, @@ -40,5 +39,4 @@ void logger::log(std::string&& message, message_severity severity, std::source_l ); } -} // namespace log } // namespace debug diff --git a/src/engine/debug/log/logger.hpp b/src/engine/debug/log/logger.hpp index b029483..f5ca685 100644 --- a/src/engine/debug/log/logger.hpp +++ b/src/engine/debug/log/logger.hpp @@ -17,17 +17,19 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_DEBUG_LOG_LOGGER_HPP -#define ANTKEEPER_DEBUG_LOG_LOGGER_HPP +#ifndef ANTKEEPER_DEBUG_LOGGER_HPP +#define ANTKEEPER_DEBUG_LOGGER_HPP -#include -#include +#include +#include #include #include #include namespace debug { -namespace log { + +/// @name Debug logging +/// @{ /** * Generates an event each time a message logged. @@ -45,21 +47,22 @@ public: void log ( std::string&& message, - message_severity severity = message_severity::info, + log_message_severity severity = log_message_severity::info, std::source_location&& location = std::source_location::current() ); /// Returns the channel through which message logged events are published. - [[nodiscard]] inline ::event::channel& get_message_logged_channel() noexcept + [[nodiscard]] inline ::event::channel& get_message_logged_channel() noexcept { - return message_logged_publisher.channel(); + return m_message_logged_publisher.channel(); } private: - ::event::publisher message_logged_publisher; + ::event::publisher m_message_logged_publisher; }; -} // namespace log +/// @} + } // namespace debug -#endif // ANTKEEPER_DEBUG_LOG_LOGGER_HPP +#endif // ANTKEEPER_DEBUG_LOGGER_HPP diff --git a/src/engine/geom/brep/brep-operations.cpp b/src/engine/geom/brep/brep-operations.cpp index 778fbf3..b54c544 100644 --- a/src/engine/geom/brep/brep-operations.cpp +++ b/src/engine/geom/brep/brep-operations.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include #include @@ -156,40 +156,36 @@ std::unique_ptr generate_model(const brep_mesh& mesh, std::shared auto& bounds = model->get_bounds(); bounds = {math::fvec3::infinity(), -math::fvec3::infinity()}; - // Get model VBO and VAO - auto& vbo = model->get_vertex_buffer(); - auto& vao = model->get_vertex_array(); - - // Build vertex format - std::size_t vertex_size = 0; - - gl::vertex_attribute position_attribute; + // Construct model VAO + std::size_t vertex_stride = 0; + std::vector vertex_attributes; + gl::vertex_input_attribute position_attribute{}; if (vertex_positions) { - position_attribute.buffer = vbo.get(); - position_attribute.offset = vertex_size; - position_attribute.type = gl::vertex_attribute_type::float_32; - position_attribute.components = 3; + position_attribute.location = render::vertex_attribute_location::position; + position_attribute.binding = 0; + position_attribute.format = gl::format::r32g32b32_sfloat; + position_attribute.offset = 0; + vertex_attributes.emplace_back(position_attribute); - vertex_size += position_attribute.components * sizeof(float); + vertex_stride += 3 * sizeof(float); } - - gl::vertex_attribute normal_attribute; + gl::vertex_input_attribute normal_attribute{}; if (vertex_normals) { - normal_attribute.buffer = vbo.get(); - normal_attribute.offset = vertex_size; - normal_attribute.type = gl::vertex_attribute_type::float_32; - normal_attribute.components = 3; + normal_attribute.location = render::vertex_attribute_location::normal; + normal_attribute.binding = 0; + normal_attribute.format = gl::format::r32g32b32_sfloat; + normal_attribute.offset = static_cast(vertex_stride); + vertex_attributes.emplace_back(normal_attribute); - vertex_size += normal_attribute.components * sizeof(float); + vertex_stride += 3 * sizeof(float); } - - position_attribute.stride = vertex_size; - normal_attribute.stride = vertex_size; + auto& vao = model->get_vertex_array(); + vao = std::make_unique(vertex_attributes); // Interleave vertex data - std::vector vertex_data(mesh.faces().size() * 3 * vertex_size); + std::vector vertex_data(mesh.faces().size() * 3 * vertex_stride); if (vertex_positions) { std::byte* v = vertex_data.data() + position_attribute.offset; @@ -199,7 +195,7 @@ std::unique_ptr generate_model(const brep_mesh& mesh, std::shared { const auto& position = (*vertex_positions)[loop->vertex()->index()]; std::memcpy(v, position.data(), sizeof(float) * 3); - v += position_attribute.stride; + v += vertex_stride; // Extend model bounds bounds.extend(position); @@ -215,36 +211,26 @@ std::unique_ptr generate_model(const brep_mesh& mesh, std::shared { const auto& normal = (*vertex_normals)[loop->vertex()->index()]; std::memcpy(v, normal.data(), sizeof(float) * 3); - v += normal_attribute.stride; + v += vertex_stride; } } } - // Resize model VBO and upload interleaved vertex data - vbo->resize(vertex_data.size(), vertex_data); - - // Free interleaved vertex data - vertex_data.clear(); - - // Bind vertex attributes to VAO - if (vertex_positions) - { - vao->bind(render::vertex_attribute::position, position_attribute); - } - if (vertex_normals) - { - vao->bind(render::vertex_attribute::normal, normal_attribute); - } + // Construct model VBO + auto& vbo = model->get_vertex_buffer(); + vbo = std::make_unique(gl::buffer_usage::static_draw, vertex_data); + model->set_vertex_offset(0); + model->set_vertex_stride(vertex_stride); // Create material group model->get_groups().resize(1); render::model_group& model_group = model->get_groups().front(); - model_group.id = "default"; + model_group.id = {}; model_group.material = material; - model_group.drawing_mode = gl::drawing_mode::triangles; - model_group.start_index = 0; - model_group.index_count = static_cast(mesh.faces().size() * 3); + model_group.primitive_topology = gl::primitive_topology::triangle_list; + model_group.first_vertex = 0; + model_group.vertex_count = static_cast(mesh.faces().size() * 3); return model; } diff --git a/src/engine/gl/blend-factor.hpp b/src/engine/gl/blend-factor.hpp new file mode 100644 index 0000000..2bcee33 --- /dev/null +++ b/src/engine/gl/blend-factor.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_BLEND_FACTOR_HPP +#define ANTKEEPER_GL_BLEND_FACTOR_HPP + +#include + +namespace gl { + +/// Source and destination color and alpha blending factors. +enum class blend_factor: std::uint8_t +{ + zero, + one, + src_color, + one_minus_src_color, + dst_color, + one_minus_dst_color, + src_alpha, + one_minus_src_alpha, + dst_alpha, + one_minus_dst_alpha, + constant_color, + one_minus_constant_color, + constant_alpha, + one_minus_constant_alpha, + src_alpha_saturate, + src1_color, + one_minus_src1_color, + src1_alpha, + one_minus_src1_alpha +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_BLEND_FACTOR_HPP diff --git a/src/engine/gl/texture-wrapping.hpp b/src/engine/gl/blend-op.hpp similarity index 77% rename from src/engine/gl/texture-wrapping.hpp rename to src/engine/gl/blend-op.hpp index 73f0437..ac8fd86 100644 --- a/src/engine/gl/texture-wrapping.hpp +++ b/src/engine/gl/blend-op.hpp @@ -17,21 +17,23 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GL_TEXTURE_WRAPPING_HPP -#define ANTKEEPER_GL_TEXTURE_WRAPPING_HPP +#ifndef ANTKEEPER_GL_BLEND_OP_HPP +#define ANTKEEPER_GL_BLEND_OP_HPP #include namespace gl { -enum class texture_wrapping: std::uint8_t +/// Framebuffer blending operations. +enum class blend_op: std::uint8_t { - clip, - extend, - repeat, - mirrored_repeat + add, + subtract, + reverse_subtract, + min, + max, }; } // namespace gl -#endif // ANTKEEPER_GL_TEXTURE_WRAPPING_HPP +#endif // ANTKEEPER_GL_BLEND_OP_HPP diff --git a/src/engine/gl/clear-bits.hpp b/src/engine/gl/clear-bits.hpp new file mode 100644 index 0000000..dc69c0d --- /dev/null +++ b/src/engine/gl/clear-bits.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_CLEAR_BITS_HPP +#define ANTKEEPER_GL_CLEAR_BITS_HPP + +#include + +namespace gl { + +/// Clear mask bits. +enum: std::uint8_t +{ + /// Indicates the color buffer should be cleared. + color_clear_bit = 0b001, + + /// Indicates the depth buffer should be cleared. + depth_clear_bit = 0b010, + + /// Indicates the stencil buffer should be cleared. + stencil_clear_bit = 0b100, +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_CLEAR_BITS_HPP diff --git a/src/engine/gl/clear-value.hpp b/src/engine/gl/clear-value.hpp new file mode 100644 index 0000000..22b6966 --- /dev/null +++ b/src/engine/gl/clear-value.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_CLEAR_VALUE_HPP +#define ANTKEEPER_GL_CLEAR_VALUE_HPP + +#include +#include + + +namespace gl { + +/** + * Clear value. + */ +struct clear_value +{ + /// Color clear values. + std::array color{}; + + /// Depth clear value. + float depth{}; + + /// Stencil clear value. + std::uint32_t stencil{}; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_CLEAR_VALUE_HPP diff --git a/src/engine/gl/color-blend-equation.hpp b/src/engine/gl/color-blend-equation.hpp new file mode 100644 index 0000000..64695c6 --- /dev/null +++ b/src/engine/gl/color-blend-equation.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_COLOR_BLEND_EQUATION_HPP +#define ANTKEEPER_GL_COLOR_BLEND_EQUATION_HPP + +#include +#include + +namespace gl { + +/// Color blend factors and operations. +struct color_blend_equation +{ + /// Selects which blend factor is used to determine the RGB source factors. + blend_factor src_color_blend_factor; + + /// Selects which blend factor is used to determine the RGB destination factors. + blend_factor dst_color_blend_factor; + + /// Selects which blend operation is used to calculate the RGB values to write to the color attachment. + blend_op color_blend_op; + + /// Selects which blend factor is used to determine the alpha source factor. + blend_factor src_alpha_blend_factor; + + /// Selects which blend factor is used to determine the alpha destination factor. + blend_factor dst_alpha_blend_factor; + + /// Selects which blend operation is used to calculate the alpha values to write to the color attachment. + blend_op alpha_blend_op; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_COLOR_BLEND_EQUATION_HPP diff --git a/src/engine/gl/color-component-bits.hpp b/src/engine/gl/color-component-bits.hpp new file mode 100644 index 0000000..86dd8ea --- /dev/null +++ b/src/engine/gl/color-component-bits.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_COLOR_COMPONENT_BITS_HPP +#define ANTKEEPER_GL_COLOR_COMPONENT_BITS_HPP + +#include + +namespace gl { + +/// Bits controlling which components are written to the framebuffer. +enum: std::uint8_t +{ + /// Indicates that the R value is written to the color attachment for the appropriate sample. + color_component_r_bit = 0b0001, + + /// Indicates that the G value is written to the color attachment for the appropriate sample. + color_component_g_bit = 0b0010, + + /// Indicates that the B value is written to the color attachment for the appropriate sample. + color_component_b_bit = 0b0100, + + /// Indicates that the A value is written to the color attachment for the appropriate sample. + color_component_a_bit = 0b1000, +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_COLOR_COMPONENT_BITS_HPP diff --git a/src/engine/gl/compare-op.hpp b/src/engine/gl/compare-op.hpp new file mode 100644 index 0000000..80571e0 --- /dev/null +++ b/src/engine/gl/compare-op.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_COMPARE_OP_HPP +#define ANTKEEPER_GL_COMPARE_OP_HPP + +#include + +namespace gl { + +/// Comparison operators. +enum class compare_op: std::uint8_t +{ + /// Comparison always evaluates `false`. + never, + + /// Comparison evaluates `reference` < `test`. + less, + + /// Comparison evaluates `reference` == `test`. + equal, + + /// Comparison evaluates `reference` <= `test`. + less_or_equal, + + /// Comparison evaluates `reference` > `test`. + greater, + + /// Comparison evaluates `reference` != `test`. + not_equal, + + /// Comparison evaluates `reference` >= `test`. + greater_or_equal, + + /// Comparison always evaluates `true`. + always +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_COMPARE_OP_HPP diff --git a/src/engine/gl/cube-map.cpp b/src/engine/gl/cube-map.cpp new file mode 100644 index 0000000..08c745b --- /dev/null +++ b/src/engine/gl/cube-map.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include + +namespace gl { + +cube_map_layout infer_cube_map_layout(std::uint32_t width, std::uint32_t height) noexcept +{ + if (width == height / 6) + { + return cube_map_layout::column; + } + else if (width == height * 6) + { + return cube_map_layout::row; + } + else if (width == (height / 4) * 3) + { + return cube_map_layout::vertical_cross; + } + else if (width == (height * 4) / 3) + { + return cube_map_layout::horizontal_cross; + } + else if (width == height * 2) + { + return cube_map_layout::equirectangular; + } + else if (width == height) + { + return cube_map_layout::spherical; + } + + return cube_map_layout::unknown; +} + +std::uint32_t infer_cube_map_face_width(std::uint32_t width, std::uint32_t height, cube_map_layout layout) noexcept +{ + switch (layout) + { + case cube_map_layout::column: + case cube_map_layout::spherical: + return width; + + case cube_map_layout::row: + return height; + + case cube_map_layout::vertical_cross: + return height / 4; + + case cube_map_layout::horizontal_cross: + case cube_map_layout::equirectangular: + return width / 4; + + default: + return 0; + } +} + +} // namespace gl diff --git a/src/engine/gl/cube-map.hpp b/src/engine/gl/cube-map.hpp new file mode 100644 index 0000000..58b0aed --- /dev/null +++ b/src/engine/gl/cube-map.hpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_CUBE_MAP_HPP +#define ANTKEEPER_GL_CUBE_MAP_HPP + +#include + +namespace gl { + +/// Cube map layouts. +enum class cube_map_layout: std::uint8_t +{ + /// Unknown layout. + unknown, + + /// Faces are stored consecutively in a single column. + column, + + /// Faces are stored consecutively in a single row. + row, + + /// Faces are stored in a vertical cross. + vertical_cross, + + /// Faces are stored in a horizontal cross. + horizontal_cross, + + /// Faces are stored in an equirectangular projection. + equirectangular, + + /// Faces are stored in a spherical projection. + spherical +}; + +/** + * Infers the layout of a cube map from its dimensions. + * + * @param width Cube map width. + * @param height Cube map height. + * + * @return Inferred cube map layout. + */ +[[nodiscard]] cube_map_layout infer_cube_map_layout(std::uint32_t width, std::uint32_t height) noexcept; + +/** + * Infers the width of a cube map face from its dimensons and layout. + * + * @param width Cube map width. + * @param height Cube map height. + * @param layout Cube map layout. + * + * @return Inferred cube map face width. + */ +[[nodiscard]] std::uint32_t infer_cube_map_face_width(std::uint32_t width, std::uint32_t height, cube_map_layout layout) noexcept; + +} // namespace gl + +#endif // ANTKEEPER_GL_CUBE_MAP_HPP diff --git a/src/engine/gl/cull-mode.hpp b/src/engine/gl/cull-mode.hpp new file mode 100644 index 0000000..2d7f7a7 --- /dev/null +++ b/src/engine/gl/cull-mode.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_CULL_MODE_HPP +#define ANTKEEPER_GL_CULL_MODE_HPP + +#include + +namespace gl { + +/// Triangle culling mode. +enum class cull_mode: std::uint8_t +{ + /// No triangles are discarded. + none, + + /// Front-facing triangles are discarded. + front, + + /// Back-facing triangles are discarded. + back, + + /// All triangles are discarded. + front_and_back +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_CULL_MODE_HPP diff --git a/src/engine/gl/fill-mode.hpp b/src/engine/gl/fill-mode.hpp new file mode 100644 index 0000000..7bfb65e --- /dev/null +++ b/src/engine/gl/fill-mode.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_FILL_MODE_HPP +#define ANTKEEPER_GL_FILL_MODE_HPP + +#include + +namespace gl { + +/// Polygon rasterization mode. +enum class fill_mode: std::uint8_t +{ + /// Polygons are filled. + fill, + + /// Polygons edges are drawn as line segments. + line, + + /// Polygons vertices are drawn as points. + point +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_FILL_MODE_HPP diff --git a/src/engine/gl/format.hpp b/src/engine/gl/format.hpp new file mode 100644 index 0000000..09c00e3 --- /dev/null +++ b/src/engine/gl/format.hpp @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_FORMAT_HPP +#define ANTKEEPER_GL_FORMAT_HPP + +#include + +namespace gl { + +/// Image and vertex formats. +enum class format: std::uint32_t +{ + undefined, + r4g4_unorm_pack8, + r4g4b4a4_unorm_pack16, + b4g4r4a4_unorm_pack16, + r5g6b5_unorm_pack16, + b5g6r5_unorm_pack16, + r5g5b5a1_unorm_pack16, + b5g5r5a1_unorm_pack16, + a1r5g5b5_unorm_pack16, + r8_unorm, + r8_snorm, + r8_uscaled, + r8_sscaled, + r8_uint, + r8_sint, + r8_srgb, + r8g8_unorm, + r8g8_snorm, + r8g8_uscaled, + r8g8_sscaled, + r8g8_uint, + r8g8_sint, + r8g8_srgb, + r8g8b8_unorm, + r8g8b8_snorm, + r8g8b8_uscaled, + r8g8b8_sscaled, + r8g8b8_uint, + r8g8b8_sint, + r8g8b8_srgb, + b8g8r8_unorm, + b8g8r8_snorm, + b8g8r8_uscaled, + b8g8r8_sscaled, + b8g8r8_uint, + b8g8r8_sint, + b8g8r8_srgb, + r8g8b8a8_unorm, + r8g8b8a8_snorm, + r8g8b8a8_uscaled, + r8g8b8a8_sscaled, + r8g8b8a8_uint, + r8g8b8a8_sint, + r8g8b8a8_srgb, + b8g8r8a8_unorm, + b8g8r8a8_snorm, + b8g8r8a8_uscaled, + b8g8r8a8_sscaled, + b8g8r8a8_uint, + b8g8r8a8_sint, + b8g8r8a8_srgb, + a8b8g8r8_unorm_pack32, + a8b8g8r8_snorm_pack32, + a8b8g8r8_uscaled_pack32, + a8b8g8r8_sscaled_pack32, + a8b8g8r8_uint_pack32, + a8b8g8r8_sint_pack32, + a8b8g8r8_srgb_pack32, + a2r10g10b10_unorm_pack32, + a2r10g10b10_snorm_pack32, + a2r10g10b10_uscaled_pack32, + a2r10g10b10_sscaled_pack32, + a2r10g10b10_uint_pack32, + a2r10g10b10_sint_pack32, + a2b10g10r10_unorm_pack32, + a2b10g10r10_snorm_pack32, + a2b10g10r10_uscaled_pack32, + a2b10g10r10_sscaled_pack32, + a2b10g10r10_uint_pack32, + a2b10g10r10_sint_pack32, + r16_unorm, + r16_snorm, + r16_uscaled, + r16_sscaled, + r16_uint, + r16_sint, + r16_sfloat, + r16g16_unorm, + r16g16_snorm, + r16g16_uscaled, + r16g16_sscaled, + r16g16_uint, + r16g16_sint, + r16g16_sfloat, + r16g16b16_unorm, + r16g16b16_snorm, + r16g16b16_uscaled, + r16g16b16_sscaled, + r16g16b16_uint, + r16g16b16_sint, + r16g16b16_sfloat, + r16g16b16a16_unorm, + r16g16b16a16_snorm, + r16g16b16a16_uscaled, + r16g16b16a16_sscaled, + r16g16b16a16_uint, + r16g16b16a16_sint, + r16g16b16a16_sfloat, + r32_uint, + r32_sint, + r32_sfloat, + r32g32_uint, + r32g32_sint, + r32g32_sfloat, + r32g32b32_uint, + r32g32b32_sint, + r32g32b32_sfloat, + r32g32b32a32_uint, + r32g32b32a32_sint, + r32g32b32a32_sfloat, + r64_uint, + r64_sint, + r64_sfloat, + r64g64_uint, + r64g64_sint, + r64g64_sfloat, + r64g64b64_uint, + r64g64b64_sint, + r64g64b64_sfloat, + r64g64b64a64_uint, + r64g64b64a64_sint, + r64g64b64a64_sfloat, + b10g11r11_ufloat_pack32, + e5b9g9r9_ufloat_pack32, + d16_unorm, + x8_d24_unorm_pack32, + d32_sfloat, + s8_uint, + d16_unorm_s8_uint, + d24_unorm_s8_uint, + d32_sfloat_s8_uint, + bc1_rgb_unorm_block, + bc1_rgb_srgb_block, + bc1_rgba_unorm_block, + bc1_rgba_srgb_block, + bc2_unorm_block, + bc2_srgb_block, + bc3_unorm_block, + bc3_srgb_block, + bc4_unorm_block, + bc4_snorm_block, + bc5_unorm_block, + bc5_snorm_block, + bc6h_ufloat_block, + bc6h_sfloat_block, + bc7_unorm_block, + bc7_srgb_block, + etc2_r8g8b8_unorm_block, + etc2_r8g8b8_srgb_block, + etc2_r8g8b8a1_unorm_block, + etc2_r8g8b8a1_srgb_block, + etc2_r8g8b8a8_unorm_block, + etc2_r8g8b8a8_srgb_block, + eac_r11_unorm_block, + eac_r11_snorm_block, + eac_r11g11_unorm_block, + eac_r11g11_snorm_block, + astc_4x4_unorm_block, + astc_4x4_srgb_block, + astc_5x4_unorm_block, + astc_5x4_srgb_block, + astc_5x5_unorm_block, + astc_5x5_srgb_block, + astc_6x5_unorm_block, + astc_6x5_srgb_block, + astc_6x6_unorm_block, + astc_6x6_srgb_block, + astc_8x5_unorm_block, + astc_8x5_srgb_block, + astc_8x6_unorm_block, + astc_8x6_srgb_block, + astc_8x8_unorm_block, + astc_8x8_srgb_block, + astc_10x5_unorm_block, + astc_10x5_srgb_block, + astc_10x6_unorm_block, + astc_10x6_srgb_block, + astc_10x8_unorm_block, + astc_10x8_srgb_block, + astc_10x10_unorm_block, + astc_10x10_srgb_block, + astc_12x10_unorm_block, + astc_12x10_srgb_block, + astc_12x12_unorm_block, + astc_12x12_srgb_block +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_FORMAT_HPP diff --git a/src/engine/gl/framebuffer-attachment.hpp b/src/engine/gl/framebuffer-attachment.hpp new file mode 100644 index 0000000..f668df3 --- /dev/null +++ b/src/engine/gl/framebuffer-attachment.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_FRAMEBUFFER_ATTACHMENT_HPP +#define ANTKEEPER_GL_FRAMEBUFFER_ATTACHMENT_HPP + +#include +#include +#include + +namespace gl { + +/// Framebuffer attachment. +struct framebuffer_attachment +{ + /// Attachment usage bit mask. + std::uint8_t usage_mask{}; + + /// Attached image view. + std::shared_ptr image_view; + + /// Mip level of attached image view. + std::uint32_t level{}; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_FRAMEBUFFER_ATTACHMENT_HPP diff --git a/src/engine/gl/framebuffer-usage-bits.hpp b/src/engine/gl/framebuffer-usage-bits.hpp new file mode 100644 index 0000000..0498390 --- /dev/null +++ b/src/engine/gl/framebuffer-usage-bits.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_FRAMEBUFFER_USAGE_BITS_HPP +#define ANTKEEPER_GL_FRAMEBUFFER_USAGE_BITS_HPP + +#include + +namespace gl { + +/// Framebuffer attachment usage bits. +enum: std::uint8_t +{ + /// Framebuffer color attachment. + color_attachment_bit = 0b001, + + /// Framebuffer depth attachment. + depth_attachment_bit = 0b010, + + /// Framebuffer stencil attachment. + stencil_attachment_bit = 0b100, + + /// Framebuffer depth/stencil attachment. + depth_stencil_attachment_bits = 0b110, +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_FRAMEBUFFER_USAGE_BITS_HPP diff --git a/src/engine/gl/framebuffer.cpp b/src/engine/gl/framebuffer.cpp index 1640d96..6d2eab4 100644 --- a/src/engine/gl/framebuffer.cpp +++ b/src/engine/gl/framebuffer.cpp @@ -18,73 +18,108 @@ */ #include -#include -#include +#include +#include namespace gl { -static constexpr GLenum attachment_lut[] = +framebuffer::framebuffer(std::span attachments, std::uint32_t width, std::uint32_t height) { - GL_COLOR_ATTACHMENT0, - GL_DEPTH_ATTACHMENT, - GL_STENCIL_ATTACHMENT -}; - -framebuffer::framebuffer(int width, int height): - m_dimensions{width, height} -{ - glGenFramebuffers(1, &m_gl_framebuffer_id); -} - -framebuffer::framebuffer(): - framebuffer(0, 0) -{} - -framebuffer::~framebuffer() -{ - if (m_gl_framebuffer_id) - { - glDeleteFramebuffers(1, &m_gl_framebuffer_id); - } -} - -void framebuffer::resize(const std::array& dimensions) -{ - m_dimensions = dimensions; -} - -void framebuffer::attach(framebuffer_attachment_type attachment_type, texture* texture, std::uint8_t level) -{ - glBindFramebuffer(GL_FRAMEBUFFER, m_gl_framebuffer_id); + m_dimensions = {width, height}; + m_attachments.assign(attachments.begin(), attachments.end()); + + // Generate framebuffer + glCreateFramebuffers(1, &m_gl_named_framebuffer); - GLenum gl_attachment = attachment_lut[static_cast(attachment_type)]; - glFramebufferTexture(GL_FRAMEBUFFER, gl_attachment, texture->m_gl_texture_id, level); + GLenum gl_color_attachment = GL_COLOR_ATTACHMENT0; + std::vector gl_draw_buffers; - if (attachment_type == framebuffer_attachment_type::color) + // Attach textures to framebuffer + for (const auto& attachment: m_attachments) { - m_color_attachment = texture; + if (attachment.usage_mask & gl::color_attachment_bit) + { + glNamedFramebufferTexture + ( + m_gl_named_framebuffer, + gl_color_attachment, + attachment.image_view->m_gl_texture_name, + static_cast(attachment.level) + ); + + gl_draw_buffers.emplace_back(gl_color_attachment); + ++gl_color_attachment; + } + + if (attachment.usage_mask & gl::depth_attachment_bit) + { + if (attachment.usage_mask & gl::stencil_attachment_bit) + { + glNamedFramebufferTexture + ( + m_gl_named_framebuffer, + GL_DEPTH_STENCIL_ATTACHMENT, + attachment.image_view->m_gl_texture_name, + static_cast(attachment.level) + ); + } + else + { + glNamedFramebufferTexture + ( + m_gl_named_framebuffer, + GL_DEPTH_ATTACHMENT, + attachment.image_view->m_gl_texture_name, + static_cast(attachment.level) + ); + } + } + else if (attachment.usage_mask & gl::stencil_attachment_bit) + { + glNamedFramebufferTexture + ( + m_gl_named_framebuffer, + GL_STENCIL_ATTACHMENT, + attachment.image_view->m_gl_texture_name, + static_cast(attachment.level) + ); + } } - else if (attachment_type == framebuffer_attachment_type::depth) + + // Specify read and draw buffers + if (!gl_draw_buffers.empty()) { - m_depth_attachment = texture; + glNamedFramebufferReadBuffer(m_gl_named_framebuffer, GL_COLOR_ATTACHMENT0); + glNamedFramebufferDrawBuffers + ( + m_gl_named_framebuffer, + static_cast(gl_draw_buffers.size()), + gl_draw_buffers.data() + ); } - else if (attachment_type == framebuffer_attachment_type::stencil) + else { - m_stencil_attachment = texture; + glNamedFramebufferReadBuffer(m_gl_named_framebuffer, GL_NONE); + glNamedFramebufferDrawBuffer(m_gl_named_framebuffer, GL_NONE); } - if (!m_color_attachment) + if (glCheckNamedFramebufferStatus(m_gl_named_framebuffer, GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); + throw std::runtime_error("OpenGL framebuffer incomplete."); } - else +} + +framebuffer::~framebuffer() +{ + if (m_gl_named_framebuffer) { - glDrawBuffer(GL_COLOR_ATTACHMENT0); - glReadBuffer(GL_COLOR_ATTACHMENT0); + glDeleteFramebuffers(1, &m_gl_named_framebuffer); } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void framebuffer::resize(std::uint32_t width, std::uint32_t height) +{ + m_dimensions = {width, height}; } } // namespace gl diff --git a/src/engine/gl/framebuffer.hpp b/src/engine/gl/framebuffer.hpp index 9814c4f..54aee8f 100644 --- a/src/engine/gl/framebuffer.hpp +++ b/src/engine/gl/framebuffer.hpp @@ -20,100 +20,73 @@ #ifndef ANTKEEPER_GL_FRAMEBUFFER_HPP #define ANTKEEPER_GL_FRAMEBUFFER_HPP +#include +#include #include #include +#include +#include namespace gl { -class rasterizer; -class texture; - -enum class framebuffer_attachment_type: std::uint8_t -{ - color, - depth, - stencil -}; - +/** + * + */ class framebuffer { public: /** - * Creates a framebuffer. + * Constructs a framebuffer. + * + * @param attachments Framebuffer attachments. + * @param width Width of the framebuffer. + * @param height Height of the framebuffer. */ - framebuffer(int width, int height); - framebuffer(); + framebuffer(std::span attachments, std::uint32_t width, std::uint32_t height); /// Destroys a framebuffer. ~framebuffer(); /** - * Resizes the framebuffer. Note: This does not resize any attached textures. + * Resizes the framebuffer. * * @param width New width of the framebuffer. * @param height New height of the framebuffer. - */ - void resize(const std::array& dimensions); - - /** - * Attaches a color, depth, or stencil texture to the framebuffer. * - * @param attachment_type Type of attachment. - * @param texture Texture to attach. - * @param level Mip level of the texture to attach. + * @warning Does not resize framebuffer attachments. */ - void attach(framebuffer_attachment_type attachment_type, texture* texture, std::uint8_t level = 0); + void resize(std::uint32_t width, std::uint32_t height); - /// Returns the dimensions of the framebuffer, in pixels. - [[nodiscard]] inline const std::array& get_dimensions() const noexcept + /// Returns the framebuffer attachments. + [[nodiscard]] inline constexpr const std::vector& attachments() const noexcept { - return m_dimensions; + return m_attachments; } - /// Returns the attached color texture, if any. - /// @{ - [[nodiscard]] inline const texture* get_color_attachment() const noexcept + /// Returns the dimensions of the framebuffer. + [[nodiscard]] inline constexpr const std::array& dimensions() const noexcept { - return m_color_attachment; - } - [[nodiscard]] inline texture* get_color_attachment() noexcept - { - return m_color_attachment; + return m_dimensions; } - /// @} - /// Returns the attached depth texture, if any. - /// @{ - [[nodiscard]] inline const texture* get_depth_attachment() const noexcept + /// Returns the width of the framebuffer. + [[nodiscard]] inline constexpr std::uint32_t width() const noexcept { - return m_depth_attachment; + return m_dimensions[0]; } - [[nodiscard]] inline texture* get_depth_attachment() noexcept - { - return m_depth_attachment; - } - /// @} - /// Returns the attached stencil texture, if any. - /// @{ - [[nodiscard]] inline const texture* get_stencil_attachment() const noexcept - { - return m_stencil_attachment; - } - [[nodiscard]] inline texture* get_stencil_attachment() noexcept + /// Returns the height of the framebuffer. + [[nodiscard]] inline constexpr std::uint32_t height() const noexcept { - return m_stencil_attachment; + return m_dimensions[1]; } - /// @} private: - friend class rasterizer; + friend class pipeline; - unsigned int m_gl_framebuffer_id{0}; - std::array m_dimensions{0, 0}; - texture* m_color_attachment{nullptr}; - texture* m_depth_attachment{nullptr}; - texture* m_stencil_attachment{nullptr}; + std::vector m_attachments; + std::array m_dimensions{0, 0}; + unsigned int m_gl_named_framebuffer{0}; }; } // namespace gl diff --git a/src/engine/gl/drawing-mode.hpp b/src/engine/gl/front-face.hpp similarity index 71% rename from src/engine/gl/drawing-mode.hpp rename to src/engine/gl/front-face.hpp index 0c0ec7b..4904025 100644 --- a/src/engine/gl/drawing-mode.hpp +++ b/src/engine/gl/front-face.hpp @@ -17,28 +17,23 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GL_DRAWING_MODE_HPP -#define ANTKEEPER_GL_DRAWING_MODE_HPP +#ifndef ANTKEEPER_GL_FRONT_FACE_HPP +#define ANTKEEPER_GL_FRONT_FACE_HPP #include namespace gl { -enum class drawing_mode: std::uint8_t +/// Polygon front-facing orientation. +enum class front_face: std::uint8_t { - points, - line_strip, - line_loop, - lines, - line_strip_adjacency, - lines_adjacency, - triangle_strip, - triangle_fan, - triangles, - triangle_strip_adjacency, - triangles_adjacency + /// Triangle with positive area is considered front-facing. + counter_clockwise, + + /// Triangle with negative area is considered front-facing. + clockwise }; } // namespace gl -#endif // ANTKEEPER_GL_DRAWING_MODE_HPP +#endif // ANTKEEPER_GL_FRONT_FACE_HPP diff --git a/src/engine/gl/element-array-type.hpp b/src/engine/gl/image-flag.hpp similarity index 78% rename from src/engine/gl/element-array-type.hpp rename to src/engine/gl/image-flag.hpp index fd77208..505e23f 100644 --- a/src/engine/gl/element-array-type.hpp +++ b/src/engine/gl/image-flag.hpp @@ -17,20 +17,20 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GL_ELEMENT_ARRAY_TYPE_HPP -#define ANTKEEPER_GL_ELEMENT_ARRAY_TYPE_HPP +#ifndef ANTKEEPER_GL_IMAGE_FLAG_HPP +#define ANTKEEPER_GL_IMAGE_FLAG_HPP #include namespace gl { -enum class element_array_type: std::uint8_t +/// Image flags. +enum class image_flag: std::uint8_t { - uint_8, - uint_16, - uint_32 + /// Cube map view compatible image. + cube_compatible = 0b00000001 }; } // namespace gl -#endif // ANTKEEPER_GL_ELEMENT_ARRAY_TYPE_HPP +#endif // ANTKEEPER_GL_IMAGE_FLAG_HPP diff --git a/src/engine/gl/transfer-function.hpp b/src/engine/gl/image-view-flag.hpp similarity index 74% rename from src/engine/gl/transfer-function.hpp rename to src/engine/gl/image-view-flag.hpp index 6c42821..1397c79 100644 --- a/src/engine/gl/transfer-function.hpp +++ b/src/engine/gl/image-view-flag.hpp @@ -17,25 +17,23 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GL_TRANSFER_FUNCTION_HPP -#define ANTKEEPER_GL_TRANSFER_FUNCTION_HPP +#ifndef ANTKEEPER_GL_IMAGE_VIEW_FLAG_HPP +#define ANTKEEPER_GL_IMAGE_VIEW_FLAG_HPP #include namespace gl { -/** - * Texture sampling transfer function. - */ -enum class transfer_function: std::uint8_t +/// Image flags. +enum class image_view_flag: std::uint8_t { - /// Linear transfer function. - linear, + /// Image array view. + array = 0b00000001, - /// sRGB transfer function. - srgb + /// Cube map view. + cube = 0b00000010 }; } // namespace gl -#endif // ANTKEEPER_GL_TRANSFER_FUNCTION_HPP +#endif // ANTKEEPER_GL_IMAGE_VIEW_FLAG_HPP diff --git a/src/engine/gl/image-view.cpp b/src/engine/gl/image-view.cpp new file mode 100644 index 0000000..6b55a42 --- /dev/null +++ b/src/engine/gl/image-view.cpp @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include +#include +#include +#include + +namespace gl { + +image_view::image_view +( + std::shared_ptr image, + std::uint8_t dimensionality, + gl::format format, + std::uint32_t first_mip_level, + std::uint32_t mip_level_count, + std::uint32_t first_array_layer, + std::uint32_t array_layer_count, + std::uint8_t flags +) +{ + if (!image) + { + throw std::invalid_argument("Image view has null image."); + } + + if (format == gl::format::undefined) + { + format = image->get_format(); + } + + const auto format_index = std::to_underlying(format); + const auto gl_internal_format = gl_format_lut[format_index][0]; + + if (!gl_internal_format) + { + throw std::invalid_argument("Image view has unsupported format."); + } + + if (!mip_level_count) + { + throw std::invalid_argument("Image view has zero mip levels."); + } + + if (first_mip_level + mip_level_count > image->get_mip_levels()) + { + throw std::out_of_range("Image view mip range out of image mip range."); + } + + if (!array_layer_count) + { + throw std::invalid_argument("Image view has zero array layers."); + } + + if (first_array_layer + array_layer_count > image->get_array_layers()) + { + throw std::out_of_range("Image view array layer range out of image array layer range."); + } + + if (dimensionality != image->get_dimensionality()) + { + throw std::invalid_argument("Image view dimensionality must match image dimensionality."); + } + + if (flags & std::to_underlying(image_view_flag::cube)) + { + if (!image->is_cube_compatible()) + { + throw std::invalid_argument("Cube image views must be constructed from cube-compatible images."); + } + + if (array_layer_count % 6 != 0) + { + throw std::invalid_argument("Cube image views array layer count must be a multiple of 6."); + } + } + + m_image = image; + m_dimensionality = dimensionality; + m_format = format; + m_first_mip_level = first_mip_level; + m_mip_level_count = mip_level_count; + m_first_array_layer = first_array_layer; + m_array_layer_count = array_layer_count; + m_flags = flags; + + unsigned int gl_target = 0; + switch (dimensionality) + { + case 1: + gl_target = is_array() ? GL_TEXTURE_1D_ARRAY : GL_TEXTURE_1D; + break; + + case 2: + if (is_cube()) + { + gl_target = is_array() ? GL_TEXTURE_CUBE_MAP_ARRAY : GL_TEXTURE_CUBE_MAP; + } + else + { + gl_target = is_array() ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D; + } + break; + + case 3: + gl_target = GL_TEXTURE_3D; + break; + + default: + break; + } + + glGenTextures(1, &m_gl_texture_name); + glTextureView + ( + m_gl_texture_name, + gl_target, + m_image->m_gl_texture_name, + gl_internal_format, + m_first_mip_level, + m_mip_level_count, + m_first_array_layer, + m_array_layer_count + ); +} + +image_view::~image_view() +{ + glDeleteTextures(1, &m_gl_texture_name); +} + +image_view_1d::image_view_1d +( + std::shared_ptr image, + gl::format format, + std::uint32_t first_mip_level, + std::uint32_t mip_level_count, + std::uint32_t first_array_layer +): + image_view + ( + image, + 1, + format, + first_mip_level, + mip_level_count, + first_array_layer, + 1, + 0 + ) +{} + +image_view_1d_array::image_view_1d_array +( + std::shared_ptr image, + gl::format format, + std::uint32_t first_mip_level, + std::uint32_t mip_level_count, + std::uint32_t first_array_layer, + std::uint32_t array_layer_count +): + image_view + ( + image, + 1, + format, + first_mip_level, + mip_level_count, + first_array_layer, + array_layer_count, + std::to_underlying(image_view_flag::array) + ) +{} + +image_view_2d::image_view_2d +( + std::shared_ptr image, + gl::format format, + std::uint32_t first_mip_level, + std::uint32_t mip_level_count, + std::uint32_t first_array_layer +): + image_view + ( + image, + 2, + format, + first_mip_level, + mip_level_count, + first_array_layer, + 1, + 0 + ) +{} + +image_view_2d_array::image_view_2d_array +( + std::shared_ptr image, + gl::format format, + std::uint32_t first_mip_level, + std::uint32_t mip_level_count, + std::uint32_t first_array_layer, + std::uint32_t array_layer_count +): + image_view + ( + image, + 2, + format, + first_mip_level, + mip_level_count, + first_array_layer, + array_layer_count, + std::to_underlying(image_view_flag::array) + ) +{} + +image_view_3d::image_view_3d +( + std::shared_ptr image, + gl::format format, + std::uint32_t first_mip_level, + std::uint32_t mip_level_count +): + image_view + ( + image, + 3, + format, + first_mip_level, + mip_level_count, + 0, + 1, + 0 + ) +{} + +image_view_cube::image_view_cube +( + std::shared_ptr image, + gl::format format, + std::uint32_t first_mip_level, + std::uint32_t mip_level_count, + std::uint32_t first_array_layer +): + image_view + ( + image, + 2, + format, + first_mip_level, + mip_level_count, + first_array_layer, + 6, + std::to_underlying(image_view_flag::cube) + ) +{} + +image_view_cube_array::image_view_cube_array +( + std::shared_ptr image, + gl::format format, + std::uint32_t first_mip_level, + std::uint32_t mip_level_count, + std::uint32_t first_array_layer, + std::uint32_t array_layer_count +): + image_view + ( + image, + 2, + format, + first_mip_level, + mip_level_count, + first_array_layer, + array_layer_count, + std::to_underlying(image_view_flag::array) | std::to_underlying(image_view_flag::cube) + ) +{} + +} // namespace gl diff --git a/src/engine/gl/image-view.hpp b/src/engine/gl/image-view.hpp new file mode 100644 index 0000000..1484199 --- /dev/null +++ b/src/engine/gl/image-view.hpp @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_IMAGE_VIEW_HPP +#define ANTKEEPER_GL_IMAGE_VIEW_HPP + +#include +#include +#include +#include + +namespace gl { + +/** + * Image view. + */ +class image_view +{ +public: + /// Destructs an image view. + virtual ~image_view() = 0; + + image_view(const image_view&) = delete; + image_view(image_view&&) = delete; + image_view& operator=(const image_view&) = delete; + image_view& operator=(image_view&&) = delete; + + /// Returns the image on which the view was created. + [[nodiscard]] inline constexpr const std::shared_ptr& get_image() const noexcept + { + return m_image; + } + + /// Returns the format and type used to interpret texel blocks of the image. + [[nodiscard]] inline constexpr format get_format() const noexcept + { + return m_format; + } + + /// Returns the first mipmap level accessible to the view. + [[nodiscard]] inline constexpr std::uint32_t get_first_mip_level() const noexcept + { + return m_first_mip_level; + } + + /// Returns the number of mipmap levels accessible to the view. + [[nodiscard]] inline constexpr std::uint32_t get_mip_level_count() const noexcept + { + return m_mip_level_count; + } + + /// Returns the first array layer accessible to the view. + [[nodiscard]] inline constexpr std::uint32_t get_first_array_layer() const noexcept + { + return m_first_array_layer; + } + + /// Returns the number of array layers accessible to the view. + [[nodiscard]] inline constexpr std::uint32_t get_array_layer_count() const noexcept + { + return m_array_layer_count; + } + + /// Returns the dimensionality of the image view. + [[nodiscard]] inline constexpr std::uint8_t get_dimensionality() const noexcept + { + return m_dimensionality; + } + + /// Returns `true` if the image view is 1D, `false` otherwise. + [[nodiscard]] inline constexpr bool is_1d() const noexcept + { + return m_dimensionality == 1; + } + + /// Returns `true` if the image view is 2D, `false` otherwise. + [[nodiscard]] inline constexpr bool is_2d() const noexcept + { + return m_dimensionality == 2; + } + + /// Returns `true` if the image view is 3D, `false` otherwise. + [[nodiscard]] inline constexpr bool is_3d() const noexcept + { + return m_dimensionality == 3; + } + + /// Returns `true` if the image view is an array view, `false` otherwise. + [[nodiscard]] inline constexpr bool is_array() const noexcept + { + return m_flags & std::to_underlying(image_view_flag::array); + } + + /// Returns `true` if the image view is a cube map view, `false` otherwise. + [[nodiscard]] inline constexpr bool is_cube() const noexcept + { + return m_flags & std::to_underlying(image_view_flag::cube); + } + +protected: + /** + * Constructs an image view from an image. + * + * @param image Image on which the view will be created. + * @param dimensionality Image view dimensionality, on `[1, 3]`. + * @param format Format and type used to interpret texel blocks of the image. If gl::format::undefined, the format will be set to the format of the image. + * @param first_mip_level First mipmap level accessible to the view. + * @param mip_level_count Number of mipmap levels accessible to the view. + * @param first_array_layer First array layer accessible to the view. + * @param array_layer Number of array layers accessible to the view. + * @param flags Image view flags. + * + * @except std::invalid_argument Image view has null image. + * @except std::invalid_argument Image view has unsupported format. + * @except std::invalid_argument Image view has zero mip levels. + * @except std::out_of_range Image view mip range out of image mip range. + * @except std::invalid_argument Image view has zero array layers. + * @except std::out_of_range Image view array layer range out of image array layer range. + * @except std::invalid_argument Image view dimensionality must match image dimensionality. + * @except std::invalid_argument Cube image views must be constructed from cube-compatible images. + * @except std::invalid_argument Cube image views array layer count must be a multiple of 6. + */ + image_view + ( + std::shared_ptr image, + std::uint8_t dimensionality, + gl::format format, + std::uint32_t first_mip_level, + std::uint32_t mip_level_count, + std::uint32_t first_array_layer, + std::uint32_t array_layer_count, + std::uint8_t flags + ); + +private: + friend class framebuffer; + friend class gl_shader_texture_1d; + friend class gl_shader_texture_2d; + friend class gl_shader_texture_3d; + friend class gl_shader_texture_cube; + + unsigned int m_gl_texture_name{0}; + std::shared_ptr m_image; + std::uint8_t m_dimensionality{0}; + format m_format{format::undefined}; + std::uint32_t m_first_mip_level{0}; + std::uint32_t m_mip_level_count{0}; + std::uint32_t m_first_array_layer{0}; + std::uint32_t m_array_layer_count{0}; + std::uint8_t m_flags{0}; +}; + +/** + * 1D image view. + */ +class image_view_1d: public image_view +{ +public: + /// @copydoc image_view::image_view + image_view_1d + ( + std::shared_ptr image, + gl::format format = gl::format::undefined, + std::uint32_t first_mip_level = 0, + std::uint32_t mip_level_count = 1, + std::uint32_t first_array_layer = 0 + ); +}; + +/** + * 1D image array view. + */ +class image_view_1d_array: public image_view +{ +public: + /// @copydoc image_view::image_view + image_view_1d_array + ( + std::shared_ptr image, + gl::format format = gl::format::undefined, + std::uint32_t first_mip_level = 0, + std::uint32_t mip_level_count = 1, + std::uint32_t first_array_layer = 0, + std::uint32_t array_layer_count = 1 + ); +}; + +/** + * 2D image view. + */ +class image_view_2d: public image_view +{ +public: + /// @copydoc image_view::image_view + image_view_2d + ( + std::shared_ptr image, + gl::format format = gl::format::undefined, + std::uint32_t first_mip_level = 0, + std::uint32_t mip_level_count = 1, + std::uint32_t first_array_layer = 0 + ); +}; + +/** + * 2D image array view. + */ +class image_view_2d_array: public image_view +{ +public: + /// @copydoc image_view::image_view + image_view_2d_array + ( + std::shared_ptr image, + gl::format format = gl::format::undefined, + std::uint32_t first_mip_level = 0, + std::uint32_t mip_level_count = 1, + std::uint32_t first_array_layer = 0, + std::uint32_t array_layer_count = 1 + ); +}; + +/** + * 3D image view. + */ +class image_view_3d: public image_view +{ +public: + /// @copydoc image_view::image_view + image_view_3d + ( + std::shared_ptr image, + gl::format format = gl::format::undefined, + std::uint32_t first_mip_level = 0, + std::uint32_t mip_level_count = 1 + ); +}; + +/** + * Cube image view. + */ +class image_view_cube: public image_view +{ +public: + /// @copydoc image_view::image_view + image_view_cube + ( + std::shared_ptr image, + gl::format format = gl::format::undefined, + std::uint32_t first_mip_level = 0, + std::uint32_t mip_level_count = 1, + std::uint32_t first_array_layer = 0 + ); +}; + +/** + * Cube image array view. + */ +class image_view_cube_array: public image_view +{ +public: + /// @copydoc image_view::image_view + image_view_cube_array + ( + std::shared_ptr image, + gl::format format = gl::format::undefined, + std::uint32_t first_mip_level = 0, + std::uint32_t mip_level_count = 1, + std::uint32_t first_array_layer = 0, + std::uint32_t array_layer_count = 6 + ); +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_IMAGE_VIEW_HPP diff --git a/src/engine/gl/image.cpp b/src/engine/gl/image.cpp new file mode 100644 index 0000000..a7d410d --- /dev/null +++ b/src/engine/gl/image.cpp @@ -0,0 +1,1064 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace gl { + +image::image +( + std::uint8_t dimensionality, + gl::format format, + std::uint32_t width, + std::uint32_t height, + std::uint32_t depth, + std::uint32_t mip_levels, + std::uint32_t array_layers, + std::uint32_t flags +) +{ + const auto format_index = std::to_underlying(format); + const auto gl_internal_format = gl_format_lut[format_index][0]; + const auto gl_type = gl_format_lut[format_index][2]; + + if (gl_internal_format == 0 || gl_type == 0) + { + throw std::invalid_argument("Image construction used unsupported format."); + } + + if (!width || !height || !depth) + { + throw std::invalid_argument("Image dimensions must be nonzero."); + } + + if (!mip_levels) + { + throw std::invalid_argument("Image mip levels must be nonzero."); + } + + if (mip_levels > static_cast(std::bit_width(std::max(std::max(width, height), depth)))) + { + throw std::out_of_range("Image mip levels exceed `1 + log2(max(width, height, depth))`."); + } + + if (!array_layers) + { + throw std::invalid_argument("Image array layers must be nonzero."); + } + + if (dimensionality == 1) + { + if (height > 1 || depth > 1) + { + throw std::invalid_argument("1D image must have a height and depth of `1`."); + } + } + else if (dimensionality == 2) + { + if (depth > 1) + { + throw std::invalid_argument("2D image must have a depth of `1`."); + } + } + else if (dimensionality == 3) + { + if (array_layers > 1) + { + throw std::invalid_argument("3D image arrays not supported."); + } + } + + if (flags & std::to_underlying(image_flag::cube_compatible)) + { + if (dimensionality != 2) + { + throw std::invalid_argument("Cube compatible image must be 2D."); + } + + if (width != height) + { + throw std::invalid_argument("Cube compatible image width and height must be equal."); + } + + if (array_layers % 6 != 0) + { + throw std::invalid_argument("Cube compatible image array layers must be a multiple of 6."); + } + } + + m_dimensionality = dimensionality; + m_format = format; + m_dimensions = {width, height, depth}; + m_mip_levels = mip_levels; + m_array_layers = array_layers; + m_flags = flags; + + if (m_array_layers == 1) + { + switch (m_dimensionality) + { + case 1: + m_gl_texture_target = GL_TEXTURE_1D; + glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name); + glTextureStorage1D + ( + m_gl_texture_name, + static_cast(m_mip_levels), + gl_internal_format, + static_cast(m_dimensions[0]) + ); + break; + + case 2: + m_gl_texture_target = GL_TEXTURE_2D; + glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name); + glTextureStorage2D + ( + m_gl_texture_name, + static_cast(m_mip_levels), + gl_internal_format, + static_cast(m_dimensions[0]), + static_cast(m_dimensions[1]) + ); + break; + + case 3: + m_gl_texture_target = GL_TEXTURE_3D; + glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name); + glTextureStorage3D + ( + m_gl_texture_name, + static_cast(m_mip_levels), + gl_internal_format, + static_cast(m_dimensions[0]), + static_cast(m_dimensions[1]), + static_cast(m_dimensions[2]) + ); + break; + + default: + break; + } + } + else + { + switch (m_dimensionality) + { + case 1: + m_gl_texture_target = GL_TEXTURE_1D_ARRAY; + glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name); + glTextureStorage2D + ( + m_gl_texture_name, + static_cast(m_mip_levels), + gl_internal_format, + static_cast(m_dimensions[0]), + static_cast(m_array_layers) + ); + break; + + case 2: + if (is_cube_compatible()) + { + if (m_array_layers == 6) + { + m_gl_texture_target = GL_TEXTURE_CUBE_MAP; + glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name); + glTextureStorage2D + ( + m_gl_texture_name, + static_cast(m_mip_levels), + gl_internal_format, + static_cast(m_dimensions[0]), + static_cast(m_dimensions[1]) + ); + } + else + { + m_gl_texture_target = GL_TEXTURE_CUBE_MAP_ARRAY; + glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name); + glTextureStorage3D + ( + m_gl_texture_name, + static_cast(m_mip_levels), + gl_internal_format, + static_cast(m_dimensions[0]), + static_cast(m_dimensions[1]), + static_cast(m_array_layers) + ); + } + } + else + { + m_gl_texture_target = GL_TEXTURE_2D_ARRAY; + glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name); + glTextureStorage3D + ( + m_gl_texture_name, + static_cast(m_mip_levels), + gl_internal_format, + static_cast(m_dimensions[0]), + static_cast(m_dimensions[1]), + static_cast(m_array_layers) + ); + } + break; + + default: + break; + } + } +} + +image::~image() +{ + glDeleteTextures(1, &m_gl_texture_name); +} + +void image::read +( + std::uint32_t mip_level, + std::uint32_t offset_x, + std::uint32_t offset_y, + std::uint32_t offset_z, + std::uint32_t width, + std::uint32_t height, + std::uint32_t depth, + gl::format format, + std::span data +) const +{ + if (mip_level >= m_mip_levels) + { + throw std::out_of_range("Image read operation mip level out of range."); + } + + const auto format_index = std::to_underlying(format); + const auto gl_base_format = gl_format_lut[format_index][1]; + const auto gl_type = gl_format_lut[format_index][2]; + + if (gl_base_format == 0 || gl_type == 0) + { + throw std::invalid_argument("Image read operation used unsupported format."); + } + + glGetTextureSubImage + ( + m_gl_texture_name, + static_cast(mip_level), + static_cast(offset_x), + static_cast(offset_y), + static_cast(offset_z), + static_cast(width), + static_cast(height), + static_cast(depth), + gl_base_format, + gl_type, + static_cast(data.size()), + data.data() + ); +} + +void image::write +( + std::uint32_t mip_level, + std::uint32_t offset_x, + std::uint32_t offset_y, + std::uint32_t offset_z, + std::uint32_t width, + std::uint32_t height, + std::uint32_t depth, + gl::format format, + std::span data +) +{ + if (mip_level >= m_mip_levels) + { + throw std::out_of_range("Image write operation mip level out of range."); + } + + const auto format_index = std::to_underlying(format); + const auto gl_base_format = gl_format_lut[format_index][1]; + const auto gl_type = gl_format_lut[format_index][2]; + + if (gl_base_format == 0 || gl_type == 0) + { + throw std::invalid_argument("Image write operation used unsupported format."); + } + + if (m_array_layers == 1) + { + if ((offset_x + width > std::max(1, m_dimensions[0] >> mip_level)) || + (offset_y + height > std::max(1, m_dimensions[1] >> mip_level)) || + (offset_z + depth > std::max(1, m_dimensions[2] >> mip_level))) + { + throw std::out_of_range("Image write operation exceeded image bounds."); + } + + switch (m_dimensionality) + { + case 1: + glTextureSubImage1D + ( + m_gl_texture_name, + static_cast(mip_level), + static_cast(offset_x), + static_cast(width), + gl_base_format, + gl_type, + data.data() + ); + break; + + case 2: + glTextureSubImage2D + ( + m_gl_texture_name, + static_cast(mip_level), + static_cast(offset_x), + static_cast(offset_y), + static_cast(width), + static_cast(height), + gl_base_format, + gl_type, + data.data() + ); + break; + + case 3: + glTextureSubImage3D + ( + m_gl_texture_name, + static_cast(mip_level), + static_cast(offset_x), + static_cast(offset_y), + static_cast(offset_z), + static_cast(width), + static_cast(height), + static_cast(depth), + gl_base_format, + gl_type, + data.data() + ); + break; + + default: + break; + } + } + else + { + switch (m_dimensionality) + { + case 1: + if ((offset_x + width > std::max(1, m_dimensions[0] >> mip_level)) || + (offset_y + height > m_array_layers) || + (offset_z + depth > 1)) + { + throw std::out_of_range("Image write operation exceeded image dimensions."); + } + + glTextureSubImage2D + ( + m_gl_texture_name, + static_cast(mip_level), + static_cast(offset_x), + static_cast(offset_y), + static_cast(width), + static_cast(height), + gl_base_format, + gl_type, + data.data() + ); + break; + + case 2: + if ((offset_x + width > std::max(1, m_dimensions[0] >> mip_level)) || + (offset_y + height > std::max(1, m_dimensions[1] >> mip_level)) || + (offset_z + depth > m_array_layers)) + { + throw std::out_of_range("Image write operation exceeded image bounds."); + } + + glTextureSubImage3D + ( + m_gl_texture_name, + static_cast(mip_level), + static_cast(offset_x), + static_cast(offset_y), + static_cast(offset_z), + static_cast(width), + static_cast(height), + static_cast(depth), + gl_base_format, + gl_type, + data.data() + ); + break; + + default: + break; + } + } +} + +void image::copy +( + std::uint32_t src_mip_level, + std::uint32_t src_x, + std::uint32_t src_y, + std::uint32_t src_z, + image& dst_image, + std::uint32_t dst_mip_level, + std::uint32_t dst_x, + std::uint32_t dst_y, + std::uint32_t dst_z, + std::uint32_t width, + std::uint32_t height, + std::uint32_t depth +) const +{ + glCopyImageSubData + ( + m_gl_texture_name, + m_gl_texture_target, + static_cast(src_mip_level), + static_cast(src_x), + static_cast(src_y), + static_cast(src_z), + dst_image.m_gl_texture_name, + dst_image.m_gl_texture_target, + static_cast(dst_mip_level), + static_cast(dst_x), + static_cast(dst_y), + static_cast(dst_z), + static_cast(width), + static_cast(height), + static_cast(depth) + ); +} + +void image::generate_mipmaps() +{ + if (m_mip_levels > 1) + { + glGenerateTextureMipmap(m_gl_texture_name); + } +} + +image_1d::image_1d +( + gl::format format, + std::uint32_t width, + std::uint32_t mip_levels, + std::uint32_t array_layers, + std::uint32_t flags +): + image + ( + 1, + format, + width, + 1, + 1, + mip_levels, + array_layers, + flags + ) +{} + +image_2d::image_2d +( + gl::format format, + std::uint32_t width, + std::uint32_t height, + std::uint32_t mip_levels, + std::uint32_t array_layers, + std::uint32_t flags +): + image + ( + 2, + format, + width, + height, + 1, + mip_levels, + array_layers, + flags + ) +{} + +image_3d::image_3d +( + gl::format format, + std::uint32_t width, + std::uint32_t height, + std::uint32_t depth, + std::uint32_t mip_levels, + std::uint32_t flags +): + image + ( + 3, + format, + width, + height, + depth, + mip_levels, + 1, + flags + ) +{} + +image_cube::image_cube +( + gl::format format, + std::uint32_t width, + std::uint32_t mip_levels, + std::uint32_t array_layers +): + image_2d + ( + format, + width, + width, + mip_levels, + array_layers, + std::to_underlying(image_flag::cube_compatible) + ) +{} + +} // namespace gl + +namespace { + + int stb_io_read(void* user, char* data, int size) + { + deserialize_context& ctx = *static_cast(user); + return static_cast(ctx.read8(reinterpret_cast(data), static_cast(size))); + } + + void stb_io_skip(void* user, int n) + { + deserialize_context& ctx = *static_cast(user); + ctx.seek(ctx.tell() + n); + } + + int stb_io_eof(void* user) + { + deserialize_context& ctx = *static_cast(user); + return static_cast(ctx.eof()); + } + + struct stb_image_deleter + { + void operator()(void* p) const + { + stbi_image_free(p); + } + }; + + [[nodiscard]] std::unique_ptr load_image_stb_image(deserialize_context& ctx, std::uint8_t dimensionality, std::uint32_t mip_levels) + { + // Setup IO callbacks + const stbi_io_callbacks io_callbacks + { + &stb_io_read, + &stb_io_skip, + &stb_io_eof + }; + + // Determine image bit depth + std::size_t component_size = stbi_is_16_bit_from_callbacks(&io_callbacks, &ctx) ? sizeof(std::uint16_t) : sizeof(std::uint8_t); + ctx.seek(0); + + // Set vertical flip on load in order to correctly upload pixel data to OpenGL + stbi_set_flip_vertically_on_load(true); + + // Load image data + std::unique_ptr data; + int width; + int height; + int components; + gl::format format; + if (component_size == sizeof(std::uint16_t)) + { + // Load 16-bit image data + data = std::unique_ptr(stbi_load_16_from_callbacks(&io_callbacks, &ctx, &width, &height, &components, 0)); + + // Determine 16-bit image format + format = [components]() + { + switch (components) + { + case 1: + return gl::format::r16_unorm; + case 2: + return gl::format::r16g16_unorm; + case 3: + return gl::format::r16g16b16_unorm; + case 4: + return gl::format::r16g16b16a16_unorm; + default: + return gl::format::undefined; + } + }(); + } + else + { + // Load 8-bit image data + data = std::unique_ptr(stbi_load_from_callbacks(&io_callbacks, &ctx, &width, &height, &components, 0)); + + // Determine 8-bit image format + format = [components]() + { + switch (components) + { + case 1: + return gl::format::r8_unorm; + case 2: + return gl::format::r8g8_unorm; + case 3: + return gl::format::r8g8b8_unorm; + case 4: + return gl::format::r8g8b8a8_unorm; + default: + return gl::format::undefined; + } + }(); + } + + // Check if image data was loaded + if (!data) + { + throw deserialize_error(stbi_failure_reason()); + } + + // Determine number mip levels + if (!mip_levels) + { + mip_levels = static_cast(std::bit_width(static_cast(std::max(width, height)))); + } + + // Allocate image + std::unique_ptr image; + switch (dimensionality) + { + case 1: + image = std::make_unique + ( + format, + static_cast(std::max(width, height)), + mip_levels + ); + break; + + case 2: + image = std::make_unique + ( + format, + static_cast(width), + static_cast(height), + mip_levels + ); + break; + + case 3: + image = std::make_unique + ( + format, + static_cast(width), + static_cast(height), + 1, + mip_levels + ); + break; + + default: + break; + } + + // Upload image data to image + image->write + ( + 0, + 0, + 0, + 0, + image->get_dimensions()[0], + image->get_dimensions()[1], + image->get_dimensions()[2], + format, + { + reinterpret_cast(data.get()), + image->get_dimensions()[0] * + image->get_dimensions()[1] * + image->get_dimensions()[2] * + static_cast(components) * + component_size + } + ); + + // Generate mipmaps + image->generate_mipmaps(); + + return image; + } + + [[nodiscard]] std::unique_ptr load_image_tinyexr(deserialize_context& ctx, std::uint8_t dimensionality, std::uint32_t mip_levels) + { + const char* error = nullptr; + auto tinyexr_error = [&error]() + { + const std::string error_message(error); + FreeEXRErrorMessage(error); + throw deserialize_error(error_message); + }; + + // Read data into file buffer + std::vector file_buffer(ctx.size()); + ctx.read8(reinterpret_cast(file_buffer.data()), file_buffer.size()); + + // Read EXR version + EXRVersion exr_version; + if (ParseEXRVersionFromMemory(&exr_version, file_buffer.data(), file_buffer.size()) != TINYEXR_SUCCESS) + { + tinyexr_error(); + } + + // Check if image is multipart + if (exr_version.multipart) + { + throw deserialize_error("OpenEXR multipart images not supported."); + } + + // Load image header + EXRHeader exr_header; + InitEXRHeader(&exr_header); + if (ParseEXRHeaderFromMemory(&exr_header, &exr_version, file_buffer.data(), file_buffer.size(), &error) != TINYEXR_SUCCESS) + { + tinyexr_error(); + } + + // Check if image is tiled + if (exr_header.tiled) + { + FreeEXRHeader(&exr_header); + throw deserialize_error("OpenEXR tiled images not supported."); + } + + // Check if image has a supported number of channels + if (exr_header.num_channels < 1 || exr_header.num_channels > 4) + { + FreeEXRHeader(&exr_header); + throw deserialize_error("OpenEXR images must have 1-4 channels."); + } + + // Check if all channels have the same format + for (int i = 1; i < exr_header.num_channels; ++i) + { + if (exr_header.pixel_types[i] != exr_header.pixel_types[i - 1]) + { + FreeEXRHeader(&exr_header); + throw deserialize_error("OpenEXR images must have the same pixel type per channel."); + } + } + + // Load image data + EXRImage exr_image; + InitEXRImage(&exr_image); + if (LoadEXRImageFromMemory(&exr_image, &exr_header, file_buffer.data(), file_buffer.size(), &error) != TINYEXR_SUCCESS) + { + FreeEXRHeader(&exr_header); + tinyexr_error(); + } + + // Free file buffer + file_buffer.clear(); + + // Determine image format + constexpr gl::format uint_formats[4] = + { + gl::format::r32_uint, + gl::format::r32g32_uint, + gl::format::r32g32b32_uint, + gl::format::r32g32b32a32_uint + }; + constexpr gl::format half_formats[4] = + { + gl::format::r16_sfloat, + gl::format::r16g16_sfloat, + gl::format::r16g16b16_sfloat, + gl::format::r16g16b16a16_sfloat + }; + constexpr gl::format float_formats[4] = + { + gl::format::r32_sfloat, + gl::format::r32g32_sfloat, + gl::format::r32g32b32_sfloat, + gl::format::r32g32b32a32_sfloat + }; + gl::format format; + int component_size; + switch (exr_header.pixel_types[0]) + { + case TINYEXR_PIXELTYPE_UINT: + format = uint_formats[exr_header.num_channels - 1]; + component_size = static_cast(sizeof(std::uint32_t)); + break; + + case TINYEXR_PIXELTYPE_HALF: + format = half_formats[exr_header.num_channels - 1]; + component_size = static_cast(sizeof(std::uint16_t));//sizeof(float16_t) + break; + + case TINYEXR_PIXELTYPE_FLOAT: + format = float_formats[exr_header.num_channels - 1]; + component_size = static_cast(sizeof(float));//sizeof(float32_t) + break; + + default: + format = gl::format::undefined; + component_size = 0; + break; + } + + // Allocate interleaved image data + std::vector data(static_cast(exr_image.width * exr_image.height * exr_header.num_channels * component_size)); + + // Interleave image data from layers + std::byte* component = data.data(); + for (auto y = exr_image.height - 1; y >= 0; --y) + { + const auto row_offset = y * exr_image.width; + + for (auto x = 0; x < exr_image.width; ++x) + { + const auto byte_offset = (row_offset + x) * component_size; + + for (auto c = exr_image.num_channels - 1; c >= 0; --c) + { + std::memcpy(component, exr_image.images[c] + byte_offset, static_cast(component_size)); + component += component_size; + } + } + } + + // Store image dimensions + const auto width = static_cast(exr_image.width); + const auto height = static_cast(exr_image.height); + + // Free loaded image data and image header + FreeEXRImage(&exr_image); + FreeEXRHeader(&exr_header); + + // Determine number mip levels + if (!mip_levels) + { + mip_levels = static_cast(std::bit_width(std::max(width, height))); + } + + // Allocate image + std::unique_ptr image; + switch (dimensionality) + { + case 1: + image = std::make_unique + ( + format, + std::max(width, height), + mip_levels + ); + break; + + case 2: + image = std::make_unique + ( + format, + width, + height, + mip_levels + ); + break; + + case 3: + image = std::make_unique + ( + format, + width, + height, + 1, + mip_levels + ); + break; + + default: + break; + } + + // Upload interleaved image data to image + image->write + ( + 0, + 0, + 0, + 0, + image->get_dimensions()[0], + image->get_dimensions()[1], + image->get_dimensions()[2], + format, + data + ); + + // Generate mipmaps + image->generate_mipmaps(); + + return image; + } + + [[nodiscard]] std::unique_ptr load_image(deserialize_context& ctx, std::uint8_t dimensionality, std::uint32_t mip_levels) + { + // Select loader according to file extension + if (ctx.path().extension() == ".exr") + { + // Load EXR images with TinyEXR + return load_image_tinyexr(ctx, dimensionality, mip_levels); + } + else + { + // Load other image formats with stb_image + return load_image_stb_image(ctx, dimensionality, mip_levels); + } + } +} + +template <> +std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) +{ + return std::unique_ptr(static_cast(load_image(ctx, 1, 0).release())); +} + +template <> +std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) +{ + return std::unique_ptr(static_cast(load_image(ctx, 2, 0).release())); +} + +template <> +std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) +{ + return std::unique_ptr(static_cast(load_image(ctx, 3, 0).release())); +} + +template <> +std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) +{ + // Load cube map + auto cube_map = std::unique_ptr(static_cast(load_image(ctx, 2, 1).release())); + + // Determine cube map layout + const auto layout = gl::infer_cube_map_layout(cube_map->get_dimensions()[0], cube_map->get_dimensions()[1]); + if (layout == gl::cube_map_layout::unknown) + { + throw deserialize_error("Failed to load cube image from cube map with unknown layout."); + } + else if (layout == gl::cube_map_layout::equirectangular || layout == gl::cube_map_layout::spherical) + { + throw deserialize_error("Failed to load cube image from cube map with unsupported layout."); + } + + // Determine cube map face width + const auto face_width = gl::infer_cube_map_face_width(cube_map->get_dimensions()[0], cube_map->get_dimensions()[1], layout); + + // Allocate cube image + auto image = std::make_unique + ( + cube_map->get_format(), + face_width, + static_cast(std::bit_width(face_width)) + ); + + // Vertical cross layout face offsets + constexpr std::uint32_t vcross_offsets[6][2] = + { + {2, 2}, {0, 2}, // -x, +x + {1, 3}, {1, 1}, // -y, +y + {1, 0}, {1, 2} // -z, +z + }; + + // Horizontal cross layout face offsets + constexpr std::uint32_t hcross_offsets[6][2] = + { + {2, 1}, {0, 1}, // -x, +x + {1, 2}, {1, 0}, // -y, +y + {3, 1}, {1, 1} // -z, +z + }; + + // Copy cube map faces to cube image + switch (layout) + { + case gl::cube_map_layout::column: + for (std::uint32_t i = 0; i < 6; ++i) + { + cube_map->copy(0, 0, face_width * i, 0, *image, 0, 0, 0, i, face_width, face_width, 1); + } + break; + + case gl::cube_map_layout::row: + for (std::uint32_t i = 0; i < 6; ++i) + { + cube_map->copy(0, face_width * i, 0, 0, *image, 0, 0, 0, i, face_width, face_width, 1); + } + break; + + case gl::cube_map_layout::vertical_cross: + for (std::uint32_t i = 0; i < 6; ++i) + { + cube_map->copy(0, face_width * vcross_offsets[i][0], face_width * vcross_offsets[i][1], 0, *image, 0, 0, 0, i, face_width, face_width, 1); + } + break; + + case gl::cube_map_layout::horizontal_cross: + for (std::uint32_t i = 0; i < 6; ++i) + { + cube_map->copy(0, face_width * hcross_offsets[i][0], face_width * hcross_offsets[i][1], 0, *image, 0, 0, 0, i, face_width, face_width, 1); + } + break; + + default: + break; + } + + // Generate mipmaps + image->generate_mipmaps(); + + return image; +} diff --git a/src/engine/gl/image.hpp b/src/engine/gl/image.hpp new file mode 100644 index 0000000..30082f2 --- /dev/null +++ b/src/engine/gl/image.hpp @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_IMAGE_HPP +#define ANTKEEPER_GL_IMAGE_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace gl { + +/** + * + */ +class image +{ +public: + /// Destructs an image. + virtual ~image() = 0; + + image(const image&) = delete; + image(image&&) = delete; + image& operator=(const image&) = delete; + image& operator=(image&&) = delete; + + /** + * Reads pixel data from the image. + * + * @param mip_level Level-of-detail number. Level `0` is the base image level. Level `n` is the nth mipmap reduction image. + * @param offset_x Texel offset in the X-direction. + * @param offset_y Texel offset in the Y-direction. + * @param offset_z Texel offset in the Z-direction. + * @param width Width of the subimage. + * @param height Height of the subimage. + * @param depth Depth of the subimage. + * @param format Format of the image data. + * @param data Buffer into which image data will be read. + * + * @except std::out_of_range Image read operation mip level out of range. + * @except std::invalid_argument Image read operation used unsupported format. + */ + void read + ( + std::uint32_t mip_level, + std::uint32_t offset_x, + std::uint32_t offset_y, + std::uint32_t offset_z, + std::uint32_t width, + std::uint32_t height, + std::uint32_t depth, + gl::format format, + std::span data + ) const; + + /** + * Writes pixel data to the image. + * + * @param mip_level Level-of-detail number. Level `0` is the base image level. Level `n` is the nth mipmap reduction image. + * @param offset_x Texel offset in the X-direction. + * @param offset_y Texel offset in the Y-direction. + * @param offset_z Texel offset in the Z-direction. + * @param width Width of the subimage. + * @param height Height of the subimage. + * @param depth Depth of the subimage. + * @param format Format of the image data. + * @param data Image data to write. + * + * @except std::out_of_range Image write operation mip level out of range. + * @except std::invalid_argument Image write operation used unsupported format. + * @except std::out_of_range Image write operation exceeded image bounds. + */ + void write + ( + std::uint32_t mip_level, + std::uint32_t offset_x, + std::uint32_t offset_y, + std::uint32_t offset_z, + std::uint32_t width, + std::uint32_t height, + std::uint32_t depth, + gl::format format, + std::span data + ); + + /** + * Copies pixel data from this image into another the image. + * + * @param src_mip_level Source image level-of-detail number. Level `0` is the base image level. Level `n` is the nth mipmap reduction image. + * @param src_x Source image texel offset in the X-direction. + * @param src_y Source image texel offset in the Y-direction. + * @param src_z Source image texel offset in the Z-direction. + * @param dst_image Destination image. + * @param dst_mip_level Destination image level-of-detail number. Level `0` is the base image level. Level `n` is the nth mipmap reduction image. + * @param dst_x Destination image texel offset in the X-direction. + * @param dst_y Destination image texel offset in the Y-direction. + * @param dst_z Destination image texel offset in the Z-direction. + * @param width Width of the subimage to copy. + * @param height Height of the subimage to copy. + * @param depth Depth of the subimage to copy. + */ + void copy + ( + std::uint32_t src_mip_level, + std::uint32_t src_x, + std::uint32_t src_y, + std::uint32_t src_z, + image& dst_image, + std::uint32_t dst_mip_level, + std::uint32_t dst_x, + std::uint32_t dst_y, + std::uint32_t dst_z, + std::uint32_t width, + std::uint32_t height, + std::uint32_t depth + ) const; + + /** + * Generates mip subimages. + */ + void generate_mipmaps(); + + /// Returns the dimensionality of the image. + [[nodiscard]] inline constexpr std::uint8_t get_dimensionality() const noexcept + { + return m_dimensionality; + } + + /// Returns `true` if the image is 1D, `false` otherwise. + [[nodiscard]] inline constexpr bool is_1d() const noexcept + { + return m_dimensionality == 1; + } + + /// Returns `true` if the image is 2D, `false` otherwise. + [[nodiscard]] inline constexpr bool is_2d() const noexcept + { + return m_dimensionality == 2; + } + + /// Returns `true` if the image is 3D, `false` otherwise. + [[nodiscard]] inline constexpr bool is_3d() const noexcept + { + return m_dimensionality == 3; + } + + /// Returns the format and type of the texel blocks contained in the image. + [[nodiscard]] inline constexpr format get_format() const noexcept + { + return m_format; + } + + /// Returns the dimensions of the image. + [[nodiscard]] inline constexpr const std::array& get_dimensions() const noexcept + { + return m_dimensions; + } + + /// Returns the number of levels of detail available for minified sampling of the image. + [[nodiscard]] inline constexpr std::uint32_t get_mip_levels() const noexcept + { + return m_mip_levels; + } + + /// Returns the number of layers in the image. + [[nodiscard]] inline constexpr std::uint32_t get_array_layers() const noexcept + { + return m_array_layers; + } + + /// Returns the image flags. + [[nodiscard]] inline constexpr std::uint8_t get_flags() const noexcept + { + return m_flags; + } + + /// Returns `true` if the image is cube map compatible, `false` otherwise. + [[nodiscard]] inline constexpr bool is_cube_compatible() const noexcept + { + return m_flags & std::to_underlying(image_flag::cube_compatible); + } + +protected: + /** + * Constructs an image. + * + * @param dimensionality Image dimensionality, on `[1, 3]`. + * @param format Format and type of the texel blocks that will be contained in the image. + * @param width Width of the image. + * @param height Height of the image. + * @param depth Depth of the image. + * @param mip_levels Number of levels of detail available for minified sampling of the image. + * @param array_layers Number of layers in the image. + * @param flags Image flags. + * + * @except std::invalid_argument Image constructed with unsupported format. + * @except std::invalid_argument Image dimensions must be nonzero. + * @except std::invalid_argument Image mip levels must be nonzero. + * @except std::out_of_range Image mip levels exceed `1 + log2(max(width, height, depth))`. + * @except std::invalid_argument Image array layers must be nonzero. + * @except std::invalid_argument 1D image must have a height and depth of `1`. + * @except std::invalid_argument 2D image must have a depth of `1`. + * @except std::invalid_argument 3D image arrays not supported. + * @except std::invalid_argument Cube compatible image must be 2D. + * @except std::invalid_argument Cube compatible image width and height must be equal. + * @except std::invalid_argument Cube compatible image array layers must be a multiple of 6. + */ + image + ( + std::uint8_t dimensionality, + gl::format format, + std::uint32_t width, + std::uint32_t height, + std::uint32_t depth, + std::uint32_t mip_levels, + std::uint32_t array_layers, + std::uint32_t flags + ); + +private: + unsigned int m_gl_texture_target{0}; + unsigned int m_gl_texture_name{0}; + std::uint8_t m_dimensionality{0}; + format m_format{format::undefined}; + std::array m_dimensions{0, 0, 0}; + std::uint32_t m_mip_levels{0}; + std::uint32_t m_array_layers{0}; + std::uint8_t m_flags{0}; + + friend class image_view; +}; + +/** + * 1D image. + */ +class image_1d: public image +{ +public: + /// @copydoc image::image + image_1d + ( + gl::format format, + std::uint32_t width, + std::uint32_t mip_levels = 1, + std::uint32_t array_layers = 1, + std::uint32_t flags = 0 + ); +}; + +/** + * 2D image. + */ +class image_2d: public image +{ +public: + /// @copydoc image::image + image_2d + ( + gl::format format, + std::uint32_t width, + std::uint32_t height, + std::uint32_t mip_levels = 1, + std::uint32_t array_layers = 1, + std::uint32_t flags = 0 + ); +}; + +/** + * 3D image. + */ +class image_3d: public image +{ +public: + /// @copydoc image::image + image_3d + ( + gl::format format, + std::uint32_t width, + std::uint32_t height, + std::uint32_t depth, + std::uint32_t mip_levels = 1, + std::uint32_t flags = 0 + ); +}; + +/** + * Cube-compatible 2D image. + */ +class image_cube: public image_2d +{ +public: + /// @copydoc image_2d::image_2d + image_cube + ( + gl::format format, + std::uint32_t width, + std::uint32_t mip_levels = 1, + std::uint32_t array_layers = 6 + ); +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_IMAGE_HPP diff --git a/src/engine/gl/index-type.hpp b/src/engine/gl/index-type.hpp new file mode 100644 index 0000000..07bc06f --- /dev/null +++ b/src/engine/gl/index-type.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_INDEX_TYPE_HPP +#define ANTKEEPER_GL_INDEX_TYPE_HPP + +#include + +namespace gl { + +/// Index types for index buffers. +enum class index_type: std::uint8_t +{ + /// 8-bit unsigned integer values. + uint8, + + /// 16-bit unsigned integer values. + uint16, + + /// 32-bit unsigned integer values. + uint32 +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_INDEX_TYPE_HPP diff --git a/src/engine/gl/logic-op.hpp b/src/engine/gl/logic-op.hpp new file mode 100644 index 0000000..bf81a3b --- /dev/null +++ b/src/engine/gl/logic-op.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_LOGIC_OP_HPP +#define ANTKEEPER_GL_LOGIC_OP_HPP + +#include + +namespace gl { + +/// Framebuffer logical operations. +enum class logic_op: std::uint8_t +{ + bitwise_clear, + bitwise_and, + bitwise_and_reverse, + bitwise_copy, + bitwise_and_inverted, + bitwise_no_op, + bitwise_xor, + bitwise_or, + bitwise_nor, + bitwise_equivalent, + bitwise_invert, + bitwise_or_reverse, + bitwise_copy_inverted, + bitwise_or_inverted, + bitwise_nand, + bitwise_set, +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_LOGIC_OP_HPP diff --git a/src/engine/gl/opengl/gl-format-lut.hpp b/src/engine/gl/opengl/gl-format-lut.hpp new file mode 100644 index 0000000..87c164d --- /dev/null +++ b/src/engine/gl/opengl/gl-format-lut.hpp @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_GL_FORMAT_LUT_HPP +#define ANTKEEPER_GL_GL_FORMAT_LUT_HPP + +#include + +namespace gl { + +/// Maps gl::format to OpenGL internal format, base format, and pixel type. +constexpr GLenum gl_format_lut[][3] = +{ + {0 , 0 , 0 }, // undefined + {0 , GL_RG , 0 }, // r4g4_unorm_pack8 + {GL_RGBA4 , GL_RGBA , GL_UNSIGNED_SHORT_4_4_4_4 }, // r4g4b4a4_unorm_pack16 + {GL_RGBA4 , GL_BGRA , GL_UNSIGNED_SHORT_4_4_4_4 }, // b4g4r4a4_unorm_pack16 + {GL_RGB565 , GL_RGB , GL_UNSIGNED_SHORT_5_6_5 }, // r5g6b5_unorm_pack16 + {GL_RGB565 , GL_BGR , GL_UNSIGNED_SHORT_5_6_5 }, // b5g6r5_unorm_pack16 + {GL_RGB5_A1 , GL_RGBA , GL_UNSIGNED_SHORT_5_5_5_1 }, // r5g5b5a1_unorm_pack16 + {GL_RGB5_A1 , GL_BGRA , GL_UNSIGNED_SHORT_5_5_5_1 }, // b5g5r5a1_unorm_pack16 + {GL_RGB5_A1 , GL_BGRA , GL_UNSIGNED_SHORT_1_5_5_5_REV }, // a1r5g5b5_unorm_pack16 + {GL_R8 , GL_RED , GL_UNSIGNED_BYTE }, // r8_unorm + {GL_R8_SNORM , GL_RED , GL_BYTE }, // r8_snorm + {0 , GL_RED , GL_UNSIGNED_BYTE }, // r8_uscaled + {0 , GL_RED , GL_BYTE }, // r8_sscaled + {GL_R8UI , GL_RED_INTEGER , GL_UNSIGNED_BYTE }, // r8_uint + {GL_R8I , GL_RED_INTEGER , GL_BYTE }, // r8_sint + {0 , GL_RED , GL_UNSIGNED_BYTE }, // r8_srgb + {GL_RG8 , GL_RG , GL_UNSIGNED_BYTE }, // r8g8_unorm + {GL_RG8_SNORM , GL_RG , GL_BYTE }, // r8g8_snorm + {0 , GL_RED , GL_UNSIGNED_BYTE }, // r8g8_uscaled + {0 , GL_RED , GL_BYTE }, // r8g8_sscaled + {GL_RG8UI , GL_RG_INTEGER , GL_UNSIGNED_BYTE }, // r8g8_uint + {GL_RG8I , GL_RG_INTEGER , GL_BYTE }, // r8g8_sint + {0 , GL_RG , GL_UNSIGNED_BYTE }, // r8g8_srgb + {GL_RGB8 , GL_RGB , GL_UNSIGNED_BYTE }, // r8g8b8_unorm + {GL_RGB8_SNORM , GL_RGB , GL_BYTE }, // r8g8b8_snorm + {0 , GL_RED , GL_UNSIGNED_BYTE }, // r8g8b8_uscaled + {0 , GL_RED , GL_BYTE }, // r8g8b8_sscaled + {GL_RGB8UI , GL_RGB_INTEGER , GL_UNSIGNED_BYTE }, // r8g8b8_uint + {GL_RGB8I , GL_RGB_INTEGER , GL_BYTE }, // r8g8b8_sint + {GL_SRGB8 , GL_RGB , GL_UNSIGNED_BYTE }, // r8g8b8_srgb + {GL_RGB8 , GL_BGR , GL_UNSIGNED_BYTE }, // b8g8r8_unorm + {GL_RGB8_SNORM , GL_BGR , GL_BYTE }, // b8g8r8_snorm + {0 , GL_BGR , GL_UNSIGNED_BYTE }, // b8g8r8_uscaled + {0 , GL_BGR , GL_BYTE }, // b8g8r8_sscaled + {GL_RGB8UI , GL_BGR_INTEGER , GL_UNSIGNED_BYTE }, // b8g8r8_uint + {GL_RGB8I , GL_BGR_INTEGER , GL_BYTE }, // b8g8r8_sint + {GL_SRGB8 , GL_BGR , GL_UNSIGNED_BYTE }, // b8g8r8_srgb + {GL_RGBA8 , GL_RGBA , GL_UNSIGNED_BYTE }, // r8g8b8a8_unorm + {GL_RGBA8_SNORM , GL_RGBA , GL_BYTE }, // r8g8b8a8_snorm + {0 , GL_RGBA , GL_UNSIGNED_BYTE }, // r8g8b8a8_uscaled + {0 , GL_RGBA , GL_BYTE }, // r8g8b8a8_sscaled + {GL_RGBA8UI , GL_RGBA_INTEGER , GL_UNSIGNED_BYTE }, // r8g8b8a8_uint + {GL_RGBA8I , GL_RGBA_INTEGER , GL_BYTE }, // r8g8b8a8_sint + {GL_SRGB8_ALPHA8 , GL_RGBA , GL_UNSIGNED_BYTE }, // r8g8b8a8_srgb + {GL_RGBA8 , GL_BGRA , GL_UNSIGNED_BYTE }, // b8g8r8a8_unorm + {GL_RGBA8_SNORM , GL_BGRA , GL_BYTE }, // b8g8r8a8_snorm + {0 , GL_BGRA , GL_UNSIGNED_BYTE }, // b8g8r8a8_uscaled + {0 , GL_BGRA , GL_BYTE }, // b8g8r8a8_sscaled + {GL_RGBA8UI , GL_BGRA_INTEGER , GL_UNSIGNED_BYTE }, // b8g8r8a8_uint + {GL_RGBA8I , GL_BGRA_INTEGER , GL_BYTE }, // b8g8r8a8_sint + {GL_SRGB8_ALPHA8 , GL_BGRA , GL_UNSIGNED_BYTE }, // b8g8r8a8_srgb + {GL_RGBA8 , GL_RGBA , GL_UNSIGNED_INT_8_8_8_8_REV }, // a8b8g8r8_unorm_pack32 + {GL_RGBA8_SNORM , GL_RGBA , 0 }, // a8b8g8r8_snorm_pack32 + {0 , GL_RGBA , GL_UNSIGNED_INT_8_8_8_8_REV }, // a8b8g8r8_uscaled_pack32 + {0 , GL_RGBA , 0 }, // a8b8g8r8_sscaled_pack32 + {GL_RGBA8UI , GL_RGBA_INTEGER , GL_UNSIGNED_INT_8_8_8_8_REV }, // a8b8g8r8_uint_pack32 + {GL_RGBA8I , GL_RGBA_INTEGER , 0 }, // a8b8g8r8_sint_pack32 + {GL_SRGB8_ALPHA8 , GL_RGBA , GL_UNSIGNED_INT_8_8_8_8_REV }, // a8b8g8r8_srgb_pack32 + {GL_RGB10_A2 , GL_BGRA , GL_UNSIGNED_INT_2_10_10_10_REV }, // a2r10g10b10_unorm_pack32 + {0 , GL_BGRA , 0 }, // a2r10g10b10_snorm_pack32 + {0 , GL_BGRA , GL_UNSIGNED_INT_2_10_10_10_REV }, // a2r10g10b10_uscaled_pack32 + {0 , GL_BGRA , 0 }, // a2r10g10b10_sscaled_pack32 + {GL_RGB10_A2UI , GL_BGRA_INTEGER , GL_UNSIGNED_INT_2_10_10_10_REV }, // a2r10g10b10_uint_pack32 + {0 , GL_BGRA_INTEGER , 0 }, // a2r10g10b10_sint_pack32 + {GL_RGB10_A2 , GL_RGBA , GL_UNSIGNED_INT_2_10_10_10_REV }, // a2b10g10r10_unorm_pack32 + {0 , GL_RGBA , 0 }, // a2b10g10r10_snorm_pack32 + {0 , GL_RGBA , GL_UNSIGNED_INT_2_10_10_10_REV }, // a2b10g10r10_uscaled_pack32 + {0 , GL_RGBA , 0 }, // a2b10g10r10_sscaled_pack32 + {GL_RGB10_A2UI , GL_RGBA_INTEGER , GL_UNSIGNED_INT_2_10_10_10_REV }, // a2b10g10r10_uint_pack32 + {0 , GL_RGBA_INTEGER , 0 }, // a2b10g10r10_sint_pack32 + {GL_R16 , GL_RED , GL_UNSIGNED_SHORT }, // r16_unorm + {GL_R16_SNORM , GL_RED , GL_SHORT }, // r16_snorm + {0 , GL_RED , GL_UNSIGNED_SHORT }, // r16_uscaled + {0 , GL_RED , GL_SHORT }, // r16_sscaled + {GL_R16UI , GL_RED_INTEGER , GL_UNSIGNED_SHORT }, // r16_uint + {GL_R16I , GL_RED_INTEGER , GL_SHORT }, // r16_sint + {GL_R16F , GL_RED , GL_HALF_FLOAT }, // r16_sfloat + {GL_RG16 , GL_RG , GL_UNSIGNED_SHORT }, // r16g16_unorm + {GL_RG16_SNORM , GL_RG , GL_SHORT }, // r16g16_snorm + {0 , GL_RG , GL_UNSIGNED_SHORT }, // r16g16_uscaled + {0 , GL_RG , GL_SHORT }, // r16g16_sscaled + {GL_RG16UI , GL_RG_INTEGER , GL_UNSIGNED_SHORT }, // r16g16_uint + {GL_RG16I , GL_RG_INTEGER , GL_SHORT }, // r16g16_sint + {GL_RG16F , GL_RG , GL_HALF_FLOAT }, // r16g16_sfloat + {GL_RGB16 , GL_RGB , GL_UNSIGNED_SHORT }, // r16g16b16_unorm + {GL_RGB16_SNORM , GL_RGB , GL_SHORT }, // r16g16b16_snorm + {0 , GL_RGB , GL_UNSIGNED_SHORT }, // r16g16b16_uscaled + {0 , GL_RGB , GL_SHORT }, // r16g16b16_sscaled + {GL_RGB16UI , GL_RGB_INTEGER , GL_UNSIGNED_SHORT }, // r16g16b16_uint + {GL_RGB16I , GL_RGB_INTEGER , GL_SHORT }, // r16g16b16_sint + {GL_RGB16F , GL_RGB , GL_HALF_FLOAT }, // r16g16b16_sfloat + {GL_RGBA16 , GL_RGBA , GL_UNSIGNED_SHORT }, // r16g16b16a16_unorm + {GL_RGBA16_SNORM , GL_RGBA , GL_SHORT }, // r16g16b16a16_snorm + {0 , GL_RGBA , GL_UNSIGNED_SHORT }, // r16g16b16a16_uscaled + {0 , GL_RGBA , GL_SHORT }, // r16g16b16a16_sscaled + {GL_RGBA16UI , GL_RGBA_INTEGER , GL_UNSIGNED_SHORT }, // r16g16b16a16_uint + {GL_RGBA16I , GL_RGBA_INTEGER , GL_SHORT }, // r16g16b16a16_sint + {GL_RGBA16F , GL_RGBA , GL_HALF_FLOAT }, // r16g16b16a16_sfloat + {GL_R32UI , GL_RED_INTEGER , GL_UNSIGNED_INT }, // r32_uint + {GL_R32I , GL_RED_INTEGER , GL_INT }, // r32_sint + {GL_R32F , GL_RED , GL_FLOAT }, // r32_sfloat + {GL_RG32UI , GL_RG_INTEGER , GL_UNSIGNED_INT }, // r32g32_uint + {GL_RG32I , GL_RG_INTEGER , GL_INT }, // r32g32_sint + {GL_RG32F , GL_RG , GL_FLOAT }, // r32g32_sfloat + {GL_RGB32UI , GL_RGB_INTEGER , GL_UNSIGNED_INT }, // r32g32b32_uint + {GL_RGB32I , GL_RGB_INTEGER , GL_INT }, // r32g32b32_sint + {GL_RGB32F , GL_RGB , GL_FLOAT }, // r32g32b32_sfloat + {GL_RGBA32UI , GL_RGBA_INTEGER , GL_UNSIGNED_INT }, // r32g32b32a32_uint + {GL_RGBA32I , GL_RGBA_INTEGER , GL_INT }, // r32g32b32a32_sint + {GL_RGBA32F , GL_RGBA , GL_FLOAT }, // r32g32b32a32_sfloat + {0 , GL_RED_INTEGER , 0 }, // r64_uint + {0 , GL_RED_INTEGER , 0 }, // r64_sint + {0 , GL_RED , GL_DOUBLE }, // r64_sfloat + {0 , GL_RG_INTEGER , 0 }, // r64g64_uint + {0 , GL_RG_INTEGER , 0 }, // r64g64_sint + {0 , GL_RG , GL_DOUBLE }, // r64g64_sfloat + {0 , GL_RGB_INTEGER , 0 }, // r64g64b64_uint + {0 , GL_RGB_INTEGER , 0 }, // r64g64b64_sint + {0 , GL_RGB , GL_DOUBLE }, // r64g64b64_sfloat + {0 , GL_RGBA_INTEGER , 0 }, // r64g64b64a64_uint + {0 , GL_RGBA_INTEGER , 0 }, // r64g64b64a64_sint + {0 , GL_RGBA , GL_DOUBLE }, // r64g64b64a64_sfloat + {GL_R11F_G11F_B10F , GL_BGR , GL_UNSIGNED_INT_10F_11F_11F_REV }, // b10g11r11_ufloat_pack32 + {GL_RGB9_E5 , GL_BGR , GL_UNSIGNED_INT_5_9_9_9_REV }, // e5b9g9r9_ufloat_pack32 + {GL_DEPTH_COMPONENT16 , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT }, // d16_unorm + {GL_DEPTH_COMPONENT24 , GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }, // x8_d24_unorm_pack32 + {GL_DEPTH_COMPONENT32F , GL_DEPTH_COMPONENT, GL_FLOAT }, // d32_sfloat + {GL_STENCIL_INDEX8 , GL_STENCIL_INDEX , GL_UNSIGNED_BYTE }, // s8_uint + {0 , GL_DEPTH_STENCIL , 0 }, // d16_unorm_s8_uint + {GL_DEPTH24_STENCIL8 , GL_DEPTH_STENCIL , GL_UNSIGNED_INT_24_8 }, // d24_unorm_s8_uint + {GL_DEPTH32F_STENCIL8 , GL_DEPTH_STENCIL , GL_FLOAT_32_UNSIGNED_INT_24_8_REV}, // d32_sfloat_s8_uint + {0 , GL_RGB , 0 }, // bc1_rgb_unorm_block, + {0 , GL_RGB , 0 }, // bc1_rgb_srgb_block, + {0 , GL_RGBA , 0 }, // bc1_rgba_unorm_block, + {0 , GL_RGBA , 0 }, // bc1_rgba_srgb_block, + {0 , GL_RGBA , 0 }, // bc2_unorm_block, + {0 , GL_RGBA , 0 }, // bc2_srgb_block, + {0 , GL_RGBA , 0 }, // bc3_unorm_block, + {0 , GL_RGBA , 0 }, // bc3_srgb_block, + {GL_COMPRESSED_RED_RGTC1 , GL_RED , 0 }, // bc4_unorm_block, + {GL_COMPRESSED_SIGNED_RED_RGTC1 , GL_RED , 0 }, // bc4_snorm_block, + {GL_COMPRESSED_RG_RGTC2 , GL_RG , 0 }, // bc5_unorm_block, + {GL_COMPRESSED_SIGNED_RG_RGTC2 , GL_RG , 0 }, // bc5_snorm_block, + {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT , GL_RGB , 0 }, // bc6h_ufloat_block, + {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT , GL_RGB , 0 }, // bc6h_sfloat_block, + {GL_COMPRESSED_RGBA_BPTC_UNORM , GL_RGBA , 0 }, // bc7_unorm_block, + {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM , GL_RGBA , 0 }, // bc7_srgb_block, + {GL_COMPRESSED_RGB8_ETC2 , GL_RGB , 0 }, // etc2_r8g8b8_unorm_block, + {GL_COMPRESSED_SRGB8_ETC2 , GL_RGB , 0 }, // etc2_r8g8b8_srgb_block, + {GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 , GL_RGBA , 0 }, // etc2_r8g8b8a1_unorm_block, + {GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, GL_RGBA , 0 }, // etc2_r8g8b8a1_srgb_block, + {GL_COMPRESSED_RGBA8_ETC2_EAC , GL_RGBA , 0 }, // etc2_r8g8b8a8_unorm_block, + {GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC , GL_RGBA , 0 }, // etc2_r8g8b8a8_srgb_block, + {GL_COMPRESSED_R11_EAC , GL_RED , 0 }, // eac_r11_unorm_block, + {GL_COMPRESSED_SIGNED_R11_EAC , GL_RED , 0 }, // eac_r11_snorm_block, + {GL_COMPRESSED_RG11_EAC , GL_RG , 0 }, // eac_r11g11_unorm_block, + {GL_COMPRESSED_SIGNED_RG11_EAC , GL_RG , 0 }, // eac_r11g11_snorm_block, + {0 , GL_RGBA , 0 }, // astc_4x4_unorm_block, + {0 , GL_RGBA , 0 }, // astc_4x4_srgb_block, + {0 , GL_RGBA , 0 }, // astc_5x4_unorm_block, + {0 , GL_RGBA , 0 }, // astc_5x4_srgb_block, + {0 , GL_RGBA , 0 }, // astc_5x5_unorm_block, + {0 , GL_RGBA , 0 }, // astc_5x5_srgb_block, + {0 , GL_RGBA , 0 }, // astc_6x5_unorm_block, + {0 , GL_RGBA , 0 }, // astc_6x5_srgb_block, + {0 , GL_RGBA , 0 }, // astc_6x6_unorm_block, + {0 , GL_RGBA , 0 }, // astc_6x6_srgb_block, + {0 , GL_RGBA , 0 }, // astc_8x5_unorm_block, + {0 , GL_RGBA , 0 }, // astc_8x5_srgb_block, + {0 , GL_RGBA , 0 }, // astc_8x6_unorm_block, + {0 , GL_RGBA , 0 }, // astc_8x6_srgb_block, + {0 , GL_RGBA , 0 }, // astc_8x8_unorm_block, + {0 , GL_RGBA , 0 }, // astc_8x8_srgb_block, + {0 , GL_RGBA , 0 }, // astc_10x5_unorm_block, + {0 , GL_RGBA , 0 }, // astc_10x5_srgb_block, + {0 , GL_RGBA , 0 }, // astc_10x6_unorm_block, + {0 , GL_RGBA , 0 }, // astc_10x6_srgb_block, + {0 , GL_RGBA , 0 }, // astc_10x8_unorm_block, + {0 , GL_RGBA , 0 }, // astc_10x8_srgb_block, + {0 , GL_RGBA , 0 }, // astc_10x10_unorm_block, + {0 , GL_RGBA , 0 }, // astc_10x10_srgb_block, + {0 , GL_RGBA , 0 }, // astc_12x10_unorm_block, + {0 , GL_RGBA , 0 }, // astc_12x10_srgb_block, + {0 , GL_RGBA , 0 }, // astc_12x12_unorm_block, + {0 , GL_RGBA , 0 } // astc_12x12_srgb_block +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_GL_FORMAT_LUT_HPP diff --git a/src/engine/gl/opengl/gl-shader-variables.cpp b/src/engine/gl/opengl/gl-shader-variables.cpp index 0cde5bb..1bea854 100644 --- a/src/engine/gl/opengl/gl-shader-variables.cpp +++ b/src/engine/gl/opengl/gl-shader-variables.cpp @@ -18,10 +18,7 @@ */ #include -#include -#include -#include -#include +#include #include namespace gl { @@ -450,8 +447,8 @@ gl_shader_texture_1d::gl_shader_texture_1d(std::size_t size, GLint gl_uniform_lo void gl_shader_texture_1d::update(const texture_1d& value) const noexcept { // Bind texture to texture unit - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_unit_indices.front())); - glBindTexture(GL_TEXTURE_1D, value.m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_unit_indices.front()), value.get_image_view() ? value.get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_unit_indices.front()), value.get_sampler() ? value.get_sampler()->m_gl_named_sampler : 0); // Pass texture unit index to shader glUniform1i(gl_uniform_location, gl_texture_unit_indices.front()); @@ -462,8 +459,8 @@ void gl_shader_texture_1d::update(const texture_1d& value, std::size_t index) co const GLint gl_texture_index = gl_texture_unit_indices[index]; // Bind texture to texture unit - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_index)); - glBindTexture(GL_TEXTURE_1D, value.m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_index), value.get_image_view() ? value.get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_index), value.get_sampler() ? value.get_sampler()->m_gl_named_sampler : 0); // Pass texture unit index to shader glUniform1i(gl_uniform_location + static_cast(index), gl_texture_index); @@ -474,8 +471,8 @@ void gl_shader_texture_1d::update(std::span values, std // Bind textures for (std::size_t i = 0; i < values.size(); ++i) { - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_unit_indices[index + i])); - glBindTexture(GL_TEXTURE_1D, values[i]->m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_image_view() ? values[i]->get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_sampler() ? values[i]->get_sampler()->m_gl_named_sampler : 0); } // Pass texture unit indices to shader @@ -487,8 +484,8 @@ void gl_shader_texture_1d::update(std::span> v // Bind textures for (std::size_t i = 0; i < values.size(); ++i) { - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_unit_indices[index + i])); - glBindTexture(GL_TEXTURE_1D, values[i]->m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_image_view() ? values[i]->get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_sampler() ? values[i]->get_sampler()->m_gl_named_sampler : 0); } // Pass texture unit indices to shader @@ -506,8 +503,8 @@ gl_shader_texture_2d::gl_shader_texture_2d(std::size_t size, GLint gl_uniform_lo void gl_shader_texture_2d::update(const texture_2d& value) const noexcept { // Bind texture to texture unit - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_unit_indices.front())); - glBindTexture(GL_TEXTURE_2D, value.m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_unit_indices.front()), value.get_image_view() ? value.get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_unit_indices.front()), value.get_sampler() ? value.get_sampler()->m_gl_named_sampler : 0); // Pass texture unit index to shader glUniform1i(gl_uniform_location, gl_texture_unit_indices.front()); @@ -518,8 +515,8 @@ void gl_shader_texture_2d::update(const texture_2d& value, std::size_t index) co const GLint gl_texture_index = gl_texture_unit_indices[index]; // Bind texture to texture unit - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_index)); - glBindTexture(GL_TEXTURE_2D, value.m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_index), value.get_image_view() ? value.get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_index), value.get_sampler() ? value.get_sampler()->m_gl_named_sampler : 0); // Pass texture unit index to shader glUniform1i(gl_uniform_location + static_cast(index), gl_texture_index); @@ -530,8 +527,8 @@ void gl_shader_texture_2d::update(std::span values, std // Bind textures for (std::size_t i = 0; i < values.size(); ++i) { - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_unit_indices[index + i])); - glBindTexture(GL_TEXTURE_2D, values[i]->m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_image_view() ? values[i]->get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_sampler() ? values[i]->get_sampler()->m_gl_named_sampler : 0); } // Pass texture unit indices to shader @@ -543,8 +540,8 @@ void gl_shader_texture_2d::update(std::span> v // Bind textures for (std::size_t i = 0; i < values.size(); ++i) { - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_unit_indices[index + i])); - glBindTexture(GL_TEXTURE_2D, values[i]->m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_image_view() ? values[i]->get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_sampler() ? values[i]->get_sampler()->m_gl_named_sampler : 0); } // Pass texture unit indices to shader @@ -562,8 +559,8 @@ gl_shader_texture_3d::gl_shader_texture_3d(std::size_t size, GLint gl_uniform_lo void gl_shader_texture_3d::update(const texture_3d& value) const noexcept { // Bind texture to texture unit - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_unit_indices.front())); - glBindTexture(GL_TEXTURE_3D, value.m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_unit_indices.front()), value.get_image_view() ? value.get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_unit_indices.front()), value.get_sampler() ? value.get_sampler()->m_gl_named_sampler : 0); // Pass texture unit index to shader glUniform1i(gl_uniform_location, gl_texture_unit_indices.front()); @@ -574,8 +571,8 @@ void gl_shader_texture_3d::update(const texture_3d& value, std::size_t index) co const GLint gl_texture_index = gl_texture_unit_indices[index]; // Bind texture to texture unit - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_index)); - glBindTexture(GL_TEXTURE_3D, value.m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_index), value.get_image_view() ? value.get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_index), value.get_sampler() ? value.get_sampler()->m_gl_named_sampler : 0); // Pass texture unit index to shader glUniform1i(gl_uniform_location + static_cast(index), gl_texture_index); @@ -586,8 +583,8 @@ void gl_shader_texture_3d::update(std::span values, std // Bind textures for (std::size_t i = 0; i < values.size(); ++i) { - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_unit_indices[index + i])); - glBindTexture(GL_TEXTURE_3D, values[i]->m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_image_view() ? values[i]->get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_sampler() ? values[i]->get_sampler()->m_gl_named_sampler : 0); } // Pass texture unit indices to shader @@ -599,8 +596,8 @@ void gl_shader_texture_3d::update(std::span> v // Bind textures for (std::size_t i = 0; i < values.size(); ++i) { - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_unit_indices[index + i])); - glBindTexture(GL_TEXTURE_3D, values[i]->m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_image_view() ? values[i]->get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_sampler() ? values[i]->get_sampler()->m_gl_named_sampler : 0); } // Pass texture unit indices to shader @@ -618,8 +615,8 @@ gl_shader_texture_cube::gl_shader_texture_cube(std::size_t size, GLint gl_unifor void gl_shader_texture_cube::update(const texture_cube& value) const noexcept { // Bind texture to texture unit - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_unit_indices.front())); - glBindTexture(GL_TEXTURE_CUBE_MAP, value.m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_unit_indices.front()), value.get_image_view() ? value.get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_unit_indices.front()), value.get_sampler() ? value.get_sampler()->m_gl_named_sampler : 0); // Pass texture unit index to shader glUniform1i(gl_uniform_location, gl_texture_unit_indices.front()); @@ -630,8 +627,8 @@ void gl_shader_texture_cube::update(const texture_cube& value, std::size_t index const GLint gl_texture_index = gl_texture_unit_indices[index]; // Bind texture to texture unit - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_index)); - glBindTexture(GL_TEXTURE_CUBE_MAP, value.m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_index), value.get_image_view() ? value.get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_index), value.get_sampler() ? value.get_sampler()->m_gl_named_sampler : 0); // Pass texture unit index to shader glUniform1i(gl_uniform_location + static_cast(index), gl_texture_index); @@ -642,8 +639,8 @@ void gl_shader_texture_cube::update(std::span values, // Bind textures for (std::size_t i = 0; i < values.size(); ++i) { - glActiveTexture(GL_TEXTURE0 + static_cast(gl_texture_unit_indices[index + i])); - glBindTexture(GL_TEXTURE_CUBE_MAP, values[i]->m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_image_view() ? values[i]->get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_sampler() ? values[i]->get_sampler()->m_gl_named_sampler : 0); } // Pass texture unit indices to shader @@ -655,8 +652,8 @@ void gl_shader_texture_cube::update(std::span(gl_texture_unit_indices[index + i])); - glBindTexture(GL_TEXTURE_CUBE_MAP, values[i]->m_gl_texture_id); + glBindTextureUnit(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_image_view() ? values[i]->get_image_view()->m_gl_texture_name : 0); + glBindSampler(static_cast(gl_texture_unit_indices[index + i]), values[i]->get_sampler() ? values[i]->get_sampler()->m_gl_named_sampler : 0); } // Pass texture unit indices to shader diff --git a/src/engine/gl/opengl/gl-shader-variables.hpp b/src/engine/gl/opengl/gl-shader-variables.hpp index 0b555f5..99f5073 100644 --- a/src/engine/gl/opengl/gl-shader-variables.hpp +++ b/src/engine/gl/opengl/gl-shader-variables.hpp @@ -21,7 +21,7 @@ #define ANTKEEPER_GL_GL_SHADER_VARIABLES_HPP #include -#include +#include #include namespace gl { diff --git a/src/engine/gl/pipeline-color-blend-state.hpp b/src/engine/gl/pipeline-color-blend-state.hpp new file mode 100644 index 0000000..98308b7 --- /dev/null +++ b/src/engine/gl/pipeline-color-blend-state.hpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_PIPELINE_COLOR_BLEND_STATE_HPP +#define ANTKEEPER_GL_PIPELINE_COLOR_BLEND_STATE_HPP + +#include +#include +#include +#include + +namespace gl { + +/// Pipeline color blend state. +struct pipeline_color_blend_state +{ + /// Controls whether to apply logical operations. + bool logic_op_enabled{false}; + + /// Selects which logical operation to apply. + gl::logic_op logic_op{gl::logic_op::bitwise_copy}; + + /// Controls whether blending is enabled for the corresponding color attachment. + bool blend_enabled{false}; + + /// Color blend factors and operations. + gl::color_blend_equation color_blend_equation + { + blend_factor::one, + blend_factor::zero, + blend_op::add, + blend_factor::one, + blend_factor::zero, + blend_op::add + }; + + /// Bitmask indicating which of the RGBA components are enabled for writing. + std::uint8_t color_write_mask{0b1111}; + + /// RGBA components of the blend constant that are used in blending, depending on the blend factor. + std::array blend_constants{0.0f, 0.0f, 0.0f, 0.0f}; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_PIPELINE_COLOR_BLEND_STATE_HPP diff --git a/src/engine/gl/pipeline-depth-stencil-state.hpp b/src/engine/gl/pipeline-depth-stencil-state.hpp new file mode 100644 index 0000000..f1d9601 --- /dev/null +++ b/src/engine/gl/pipeline-depth-stencil-state.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_PIPELINE_DEPTH_STENCIL_STATE_HPP +#define ANTKEEPER_GL_PIPELINE_DEPTH_STENCIL_STATE_HPP + +#include +#include +#include + +namespace gl { + +/// Pipeline depth/stencil state. +struct pipeline_depth_stencil_state +{ + /// `true` if depth testing is enabled, `false` otherwise. + bool depth_test_enabled{true}; + + /** + * `true` if depth writes are enabled when depth testing is enabled, `false` otherwise. + * + * @note Depth writes are always disabled when depth testing is disabled. + */ + bool depth_write_enabled{true}; + + /// Comparison operator to use in the depth comparison step of the depth test. + compare_op depth_compare_op{compare_op::less}; + + /// `true` if stencil testing is enabled, `false` otherwise. + bool stencil_test_enabled{false}; + + /// Stencil testing parameters for front faces. + stencil_op_state stencil_front; + + /// Stencil testing parameters for back faces. + stencil_op_state stencil_back; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_PIPELINE_DEPTH_STENCIL_STATE_HPP diff --git a/src/engine/gl/pipeline-input-assembly-state.hpp b/src/engine/gl/pipeline-input-assembly-state.hpp new file mode 100644 index 0000000..ab3d58d --- /dev/null +++ b/src/engine/gl/pipeline-input-assembly-state.hpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_PIPELINE_INPUT_ASSEMBLY_STATE_HPP +#define ANTKEEPER_GL_PIPELINE_INPUT_ASSEMBLY_STATE_HPP + +#include +#include + +namespace gl { + +/// Pipeline input assembly state. +struct pipeline_input_assembly_state +{ + /// Primitive topology. + primitive_topology topology{primitive_topology::triangle_list}; + + /// Controls whether a special vertex index value is treated as restarting the assembly of primitives. + bool primitive_restart_enabled{false}; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_PIPELINE_INPUT_ASSEMBLY_STATE_HPP diff --git a/src/engine/gl/pipeline-rasterization-state.hpp b/src/engine/gl/pipeline-rasterization-state.hpp new file mode 100644 index 0000000..bf688c3 --- /dev/null +++ b/src/engine/gl/pipeline-rasterization-state.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_PIPELINE_RASTERIZATION_STATE_HPP +#define ANTKEEPER_GL_PIPELINE_RASTERIZATION_STATE_HPP + +#include +#include +#include +#include +#include + +namespace gl { + +/// Pipeline rasterization state. +struct pipeline_rasterization_state +{ + /// `true` if rasterizer discard should be enabled, `false` otherwise. + bool rasterizer_discard_enabled{false}; + + /// Polygon rasterization mode. + gl::fill_mode fill_mode{gl::fill_mode::fill}; + + /// Triangle culling mode. + gl::cull_mode cull_mode{gl::cull_mode::back}; + + /// Polygon front-facing orientation. + gl::front_face front_face{gl::front_face::counter_clockwise}; + + /// `true` if depth bias should be enabled, `false` otherwise. + bool depth_bias_enabled{false}; + + /// Depth bias constant factor. + float depth_bias_constant_factor{0.0f}; + + /// Depth bias slope factor. + float depth_bias_slope_factor{0.0f}; + + /// `true` if depth clamp should be enabled, `false` otherwise. + bool depth_clamp_enabled{false}; + + /// `true` if scissor testing should be enabled, `false` otherwise. + bool scissor_test_enabled{false}; + + /// Vertex to be used as the source of data for flat-shaded varyings. + gl::provoking_vertex_mode provoking_vertex_mode{gl::provoking_vertex_mode::last}; + + /// Diameter of rasterized points. + float point_size{1.0f}; + + /// Width of rasterized line segments. + float line_width{1.0f}; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_PIPELINE_RASTERIZATION_STATE_HPP diff --git a/src/engine/gl/pipeline-vertex-input-state.hpp b/src/engine/gl/pipeline-vertex-input-state.hpp new file mode 100644 index 0000000..e84bb5f --- /dev/null +++ b/src/engine/gl/pipeline-vertex-input-state.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_PIPELINE_VERTEX_INPUT_STATE_HPP +#define ANTKEEPER_GL_PIPELINE_VERTEX_INPUT_STATE_HPP + +#include +#include +#include + +namespace gl { + +/// Pipeline input assembly state. +struct pipeline_vertex_input_state +{ + /// Vertex bindings. + std::vector vertex_bindings; + + /// Vertex attributes. + std::vector vertex_attributes; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_PIPELINE_VERTEX_INPUT_STATE_HPP diff --git a/src/engine/gl/pipeline-viewport-state.hpp b/src/engine/gl/pipeline-viewport-state.hpp new file mode 100644 index 0000000..a61ec0f --- /dev/null +++ b/src/engine/gl/pipeline-viewport-state.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_PIPELINE_VIEWPORT_STATE_HPP +#define ANTKEEPER_GL_PIPELINE_VIEWPORT_STATE_HPP + +#include +#include +#include + +namespace gl { + +/// Pipeline viewport state. +struct pipeline_viewport_state +{ + /// Active viewports. + std::vector viewports; + + /// Active scissor regions. + std::vector scissors; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_PIPELINE_VIEWPORT_STATE_HPP diff --git a/src/engine/gl/pipeline.cpp b/src/engine/gl/pipeline.cpp new file mode 100644 index 0000000..9749174 --- /dev/null +++ b/src/engine/gl/pipeline.cpp @@ -0,0 +1,1494 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + + static constexpr GLenum stencil_face_lut[] = + { + GL_NONE, // 0 + GL_FRONT, // stencil_face_front_bit + GL_BACK, // stencil_face_back_bit + GL_FRONT_AND_BACK // stencil_face_front_and_back + }; + + static constexpr GLenum stencil_op_lut[] = + { + GL_KEEP, // stencil_op::keep + GL_ZERO, // stencil_op::zero + GL_REPLACE, // stencil_op::replace + GL_INCR, // stencil_op::increment_and_clamp + GL_DECR, // stencil_op::decrement_and_clamp + GL_INVERT, // stencil_op::invert + GL_INCR_WRAP, // stencil_op::increment_and_wrap + GL_DECR_WRAP // stencil_op::decrement_and_wrap + }; + + static constexpr GLenum compare_op_lut[] = + { + GL_NEVER, // compare_op::never + GL_LESS, // compare_op::less + GL_EQUAL, // compare_op::equal + GL_LEQUAL, // compare_op::less_or_equal + GL_GREATER, // compare_op::greater + GL_NOTEQUAL, // compare_op::not_equal + GL_GEQUAL, // compare_op::greater_or_equal + GL_ALWAYS // compare_op::always + }; + + static constexpr GLenum provoking_vertex_mode_lut[] = + { + GL_FIRST_VERTEX_CONVENTION, // provoking_vertex_mode::first + GL_LAST_VERTEX_CONVENTION // provoking_vertex_mode::last + }; + + static constexpr GLenum primitive_topology_lut[] = + { + GL_POINTS, // primitive_topology::point_list + GL_LINES, // primitive_topology::line_list + GL_LINE_STRIP, // primitive_topology::line_strip, + GL_TRIANGLES, // primitive_topology::triangle_list + GL_TRIANGLE_STRIP, // primitive_topology::triangle_strip + GL_TRIANGLE_FAN, // primitive_topology::triangle_fan + GL_LINES_ADJACENCY, // primitive_topology::line_list_with_adjacency + GL_LINE_STRIP_ADJACENCY, // primitive_topology::line_strip_with_adjacency + GL_TRIANGLES_ADJACENCY, // primitive_topology::triangle_list_with_adjacency + GL_TRIANGLE_STRIP_ADJACENCY, // primitive_topology::triangle_strip_with_adjacency + GL_PATCHES // primitive_topology::patch_list + }; + + static constexpr GLenum logic_op_lut[] = + { + GL_CLEAR , // logic_op::bitwise_clear + GL_AND , // logic_op::bitwise_and + GL_AND_REVERSE , // logic_op::bitwise_and_reverse + GL_COPY , // logic_op::bitwise_copy + GL_AND_INVERTED , // logic_op::bitwise_and_inverted + GL_NOOP , // logic_op::bitwise_no_op + GL_XOR , // logic_op::bitwise_xor + GL_OR , // logic_op::bitwise_or + GL_NOR , // logic_op::bitwise_nor + GL_EQUIV , // logic_op::bitwise_equivalent + GL_INVERT , // logic_op::bitwise_invert + GL_OR_REVERSE , // logic_op::bitwise_or_reverse + GL_COPY_INVERTED, // logic_op::bitwise_copy_inverted + GL_OR_INVERTED , // logic_op::bitwise_or_inverted + GL_NAND , // logic_op::bitwise_nand + GL_SET // logic_op::bitwise_set + }; + + static constexpr GLenum blend_factor_lut[] = + { + GL_ZERO , // blend_factor::zero + GL_ONE , // blend_factor::one + GL_SRC_COLOR , // blend_factor::src_color + GL_ONE_MINUS_SRC_COLOR , // blend_factor::one_minus_src_color + GL_DST_COLOR , // blend_factor::dst_color + GL_ONE_MINUS_DST_COLOR , // blend_factor::one_minus_dst_color + GL_SRC_ALPHA , // blend_factor::src_alpha + GL_ONE_MINUS_SRC_ALPHA , // blend_factor::one_minus_src_alpha + GL_DST_ALPHA , // blend_factor::dst_alpha + GL_ONE_MINUS_DST_ALPHA , // blend_factor::one_minus_dst_alpha + GL_CONSTANT_COLOR , // blend_factor::constant_color + GL_ONE_MINUS_CONSTANT_COLOR, // blend_factor::one_minus_constant_color + GL_CONSTANT_ALPHA , // blend_factor::constant_alpha + GL_ONE_MINUS_CONSTANT_ALPHA, // blend_factor::one_minus_constant_alpha + GL_SRC_ALPHA_SATURATE , // blend_factor::src_alpha_saturate + GL_SRC1_COLOR , // blend_factor::src1_color + GL_ONE_MINUS_SRC1_COLOR , // blend_factor::one_minus_src1_color + GL_SRC1_ALPHA , // blend_factor::src1_alpha + GL_ONE_MINUS_SRC1_ALPHA // blend_factor::one_minus_src1_alpha + }; + + static constexpr GLenum blend_op_lut[] = + { + GL_FUNC_ADD , // blend_op::add + GL_FUNC_SUBTRACT , // blend_op::subtract + GL_FUNC_REVERSE_SUBTRACT, // blend_op::reverse_subtract + GL_MIN , // blend_op::min + GL_MAX // blend_op::max + }; + + void gl_debug_message_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* user_param) + { + const auto src_str = [source]() -> const char* + { + switch (source) + { + case GL_DEBUG_SOURCE_API: + return "API"; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: + return "window system"; + case GL_DEBUG_SOURCE_SHADER_COMPILER: + return "shader compiler"; + case GL_DEBUG_SOURCE_THIRD_PARTY: + return "third party"; + case GL_DEBUG_SOURCE_APPLICATION: + return "application"; + case GL_DEBUG_SOURCE_OTHER: + default: + return "other"; + } + }(); + + const auto type_str = [type]() -> const char* + { + switch (type) + { + case GL_DEBUG_TYPE_ERROR: + return "error"; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + return "deprecated behavior"; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + return "undefined behavior"; + case GL_DEBUG_TYPE_PORTABILITY: + return "portability"; + case GL_DEBUG_TYPE_PERFORMANCE: + return "performance"; + case GL_DEBUG_TYPE_MARKER: + return "marker"; + case GL_DEBUG_TYPE_OTHER: + default: + return "message"; + } + }(); + + const auto severity_str = [severity]() -> const char* + { + switch (severity) + { + case GL_DEBUG_SEVERITY_LOW: + return "low severity"; + case GL_DEBUG_SEVERITY_MEDIUM: + return "medium severity"; + case GL_DEBUG_SEVERITY_HIGH: + return "high severity"; + case GL_DEBUG_SEVERITY_NOTIFICATION: + default: + return "notification"; + } + }(); + + switch (type) + { + case GL_DEBUG_TYPE_ERROR: + { + std::string formatted_message; + std::format_to(std::back_inserter(formatted_message), "OpenGL {} {} ({}) {}: {}", src_str, type_str, severity_str, id, message); + debug::log_fatal("{}", formatted_message); + throw std::runtime_error(formatted_message); + break; + } + + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + debug::log_error("OpenGL {} {} ({}) {}: {}", src_str, type_str, severity_str, id, message); + break; + + case GL_DEBUG_TYPE_PORTABILITY: + case GL_DEBUG_TYPE_PERFORMANCE: + debug::log_warning("OpenGL {} {} ({}) {}: {}", src_str, type_str, severity_str, id, message); + break; + + case GL_DEBUG_TYPE_MARKER: + case GL_DEBUG_TYPE_OTHER: + default: + debug::log_debug("OpenGL {} {} ({}) {}: {}", src_str, type_str, severity_str, id, message); + break; + } + } +} + +namespace gl { + +pipeline::pipeline() +{ + #if defined(DEBUG) + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(gl_debug_message_callback, nullptr); + #endif + + // Fetch limitations + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &m_max_sampler_anisotropy); + + // Fetch dimensions of default framebuffer + GLint gl_scissor_box[4] = {0, 0, 0, 0}; + glGetIntegerv(GL_SCISSOR_BOX, gl_scissor_box); + m_default_framebuffer_dimensions[0] = static_cast(gl_scissor_box[2]); + m_default_framebuffer_dimensions[1] = static_cast(gl_scissor_box[3]); + + // Enable seamless cubemap filtering + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + + // Set clip control to lower left, 0 to 1 + glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); + + // Disable multisampling + glDisable(GL_MULTISAMPLE); + + // Set byte-alignment for packing and unpacking pixel rows + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + // Fetch pipeline state + fetch_vertex_input_state(); + fetch_input_assembly_state(); + fetch_viewport_state(); + fetch_rasterization_state(); + fetch_depth_stencil_state(); + fetch_color_blend_state(); + fetch_clear_value(); +} + +// void pipeline::set_vertex_input(std::span vertex_bindings, std::span vertex_attributes) +// { + // m_vertex_input_state.vertex_bindings.assign(vertex_bindings.begin(), vertex_bindings.end()); + // m_vertex_input_state.vertex_attributes.assign(vertex_attributes.begin(), vertex_attributes.end()); +// } + +void pipeline::bind_framebuffer(const gl::framebuffer* framebuffer) +{ + if (m_framebuffer != framebuffer) + { + m_framebuffer = framebuffer; + + if (m_framebuffer) + { + glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer->m_gl_named_framebuffer); + } + else + { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + } +} + +void pipeline::bind_shader_program(const gl::shader_program* shader_program) +{ + if (m_shader_program != shader_program) + { + m_shader_program = shader_program; + + if (m_shader_program) + { + glUseProgram(m_shader_program->gl_program_id); + } + else + { + glUseProgram(0); + } + } +} + +void pipeline::bind_vertex_array(const vertex_array* array) +{ + if (m_vertex_array != array) + { + m_vertex_array = array; + + if (m_vertex_array) + { + glBindVertexArray(m_vertex_array->m_gl_named_array); + } + else + { + glBindVertexArray(0); + } + } +} + +void pipeline::bind_vertex_buffers(std::uint32_t first_binding, std::span buffers, std::span offsets, std::span strides) +{ + if (!m_vertex_array) + { + throw std::runtime_error("Failed to bind vertex buffer: no vertex array bound."); + } + + if (offsets.size() < buffers.size()) + { + throw std::out_of_range("Vertex binding offset out of range."); + } + + if (strides.size() < buffers.size()) + { + throw std::out_of_range("Vertex binding stride out of range."); + } + + for (std::size_t i = 0; i < buffers.size(); ++i) + { + glVertexArrayVertexBuffer + ( + m_vertex_array->m_gl_named_array, + static_cast(first_binding + i), + buffers[i]->m_gl_named_buffer, + static_cast(offsets[i]), + static_cast(strides[i]) + ); + } +} + +void pipeline::set_primitive_topology(primitive_topology topology) +{ + if (m_input_assembly_state.topology != topology) + { + m_input_assembly_state.topology = topology; + } +} + +void pipeline::set_primitive_restart_enabled(bool enabled) +{ + if (m_input_assembly_state.primitive_restart_enabled != enabled) + { + if (enabled) + { + glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); + } + else + { + glDisable(GL_PRIMITIVE_RESTART_FIXED_INDEX); + } + + m_input_assembly_state.primitive_restart_enabled = enabled; + } +} + +void pipeline::set_viewport(std::uint32_t first_viewport, std::span viewports) +{ + // Bounds check + if (first_viewport + viewports.size() > m_max_viewports) + { + throw std::out_of_range("Viewport index out of range."); + } + + // Ignore empty commands + if (!viewports.size()) + { + return; + } + + const auto& active_viewport = m_viewport_state.viewports.front(); + const auto& viewport = viewports.front(); + + // Update viewport position and dimensions + if (active_viewport.width != viewport.width || + active_viewport.height != viewport.height || + active_viewport.x != viewport.x || + active_viewport.y != viewport.y) + { + glViewport + ( + static_cast(viewport.x), + static_cast(viewport.y), + std::max(0, static_cast(viewport.width)), + std::max(0, static_cast(viewport.height)) + ); + } + + // Update viewport depth range + if (active_viewport.min_depth != viewport.min_depth || + active_viewport.max_depth != viewport.max_depth) + { + glDepthRange(viewport.min_depth, viewport.max_depth); + } + + // Update viewport state + std::copy(viewports.begin(), viewports.end(), m_viewport_state.viewports.begin() + first_viewport); +} + +void pipeline::set_scissor(std::uint32_t first_scissor, std::span scissors) +{ + // Bounds check + if (first_scissor + scissors.size() > m_max_viewports) + { + throw std::out_of_range("Scissor region index out of range."); + } + + // Ignore empty commands + if (scissors.empty()) + { + return; + } + + const auto& active_scissor = m_viewport_state.scissors.front(); + const auto& scissor = scissors.front(); + + // Update scissor region + if (active_scissor.width != scissor.width || + active_scissor.height != scissor.height || + active_scissor.x != scissor.x || + active_scissor.y != scissor.y) + { + glScissor + ( + static_cast(scissor.x), + static_cast(scissor.y), + std::max(0, static_cast(scissor.width)), + std::max(0, static_cast(scissor.height)) + ); + } + + // Update viewport state + std::copy(scissors.begin(), scissors.end(), m_viewport_state.scissors.begin() + first_scissor); +} + +void pipeline::set_rasterizer_discard_enabled(bool enabled) +{ + if (m_rasterization_state.rasterizer_discard_enabled != enabled) + { + if (enabled) + { + glEnable(GL_RASTERIZER_DISCARD); + } + else + { + glDisable(GL_RASTERIZER_DISCARD); + } + + m_rasterization_state.rasterizer_discard_enabled = enabled; + } +} + +void pipeline::set_fill_mode(fill_mode mode) +{ + if (m_rasterization_state.fill_mode != mode) + { + switch (mode) + { + case fill_mode::fill: + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + break; + + case fill_mode::line: + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + break; + + case fill_mode::point: + glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); + break; + + default: + break; + } + + m_rasterization_state.fill_mode = mode; + } +} + +void pipeline::set_cull_mode(cull_mode mode) +{ + if (m_rasterization_state.cull_mode != mode) + { + if (mode == cull_mode::none) + { + glDisable(GL_CULL_FACE); + } + else + { + if (m_rasterization_state.cull_mode == cull_mode::none) + { + glEnable(GL_CULL_FACE); + } + + switch (mode) + { + case cull_mode::back: + glCullFace(GL_BACK); + break; + + case cull_mode::front: + glCullFace(GL_FRONT); + break; + + case cull_mode::front_and_back: + glCullFace(GL_FRONT_AND_BACK); + break; + + default: + break; + } + } + + m_rasterization_state.cull_mode = mode; + } +} + +void pipeline::set_front_face(front_face face) +{ + if (m_rasterization_state.front_face != face) + { + glFrontFace(face == front_face::counter_clockwise ? GL_CCW : GL_CW); + + m_rasterization_state.front_face = face; + } +} + +void pipeline::set_depth_bias_enabled(bool enabled) +{ + if (m_rasterization_state.depth_bias_enabled != enabled) + { + if (enabled) + { + glEnable(GL_POLYGON_OFFSET_FILL); + glEnable(GL_POLYGON_OFFSET_LINE); + glEnable(GL_POLYGON_OFFSET_POINT); + } + else + { + glDisable(GL_POLYGON_OFFSET_FILL); + glDisable(GL_POLYGON_OFFSET_LINE); + glDisable(GL_POLYGON_OFFSET_POINT); + } + + m_rasterization_state.depth_bias_enabled = enabled; + } +} + +void pipeline::set_depth_bias_factors(float constant_factor, float slope_factor) +{ + // Update depth bias factors + if (m_rasterization_state.depth_bias_constant_factor != constant_factor || + m_rasterization_state.depth_bias_slope_factor != slope_factor) + { + glPolygonOffset(slope_factor, constant_factor); + + m_rasterization_state.depth_bias_constant_factor = constant_factor; + m_rasterization_state.depth_bias_slope_factor = slope_factor; + } +} + +void pipeline::set_depth_clamp_enabled(bool enabled) +{ + if (m_rasterization_state.depth_clamp_enabled != enabled) + { + if (enabled) + { + glEnable(GL_DEPTH_CLAMP); + } + else + { + glDisable(GL_DEPTH_CLAMP); + } + + m_rasterization_state.depth_clamp_enabled = enabled; + } +} + +void pipeline::set_scissor_test_enabled(bool enabled) +{ + if (m_rasterization_state.scissor_test_enabled != enabled) + { + if (enabled) + { + glEnable(GL_SCISSOR_TEST); + } + else + { + glDisable(GL_SCISSOR_TEST); + } + + m_rasterization_state.scissor_test_enabled = enabled; + } +} + +void pipeline::set_provoking_vertex_mode(provoking_vertex_mode mode) +{ + if (m_rasterization_state.provoking_vertex_mode != mode) + { + const auto gl_provoking_vertex_mode = provoking_vertex_mode_lut[std::to_underlying(mode)]; + glProvokingVertex(gl_provoking_vertex_mode); + m_rasterization_state.provoking_vertex_mode = mode; + } +} + +void pipeline::set_point_size(float size) +{ + if (m_rasterization_state.point_size != size) + { + glPointSize(size); + + m_rasterization_state.point_size = size; + } +} + +void pipeline::set_line_width(float width) +{ + if (m_rasterization_state.line_width != width) + { + glLineWidth(width); + + m_rasterization_state.line_width = width; + } +} + +void pipeline::set_depth_test_enabled(bool enabled) +{ + if (m_depth_stencil_state.depth_test_enabled != enabled) + { + m_depth_stencil_state.depth_test_enabled = enabled; + + if (enabled) + { + glEnable(GL_DEPTH_TEST); + } + else + { + glDisable(GL_DEPTH_TEST); + } + } +} + +void pipeline::set_depth_write_enabled(bool enabled) +{ + if (m_depth_stencil_state.depth_write_enabled != enabled) + { + m_depth_stencil_state.depth_write_enabled = enabled; + glDepthMask(enabled); + } +} + +void pipeline::set_depth_compare_op(gl::compare_op compare_op) +{ + if (m_depth_stencil_state.depth_compare_op != compare_op) + { + m_depth_stencil_state.depth_compare_op = compare_op; + const auto gl_compare_op = compare_op_lut[std::to_underlying(compare_op)]; + glDepthFunc(gl_compare_op); + } +} + +void pipeline::set_stencil_test_enabled(bool enabled) +{ + if (m_depth_stencil_state.stencil_test_enabled != enabled) + { + m_depth_stencil_state.stencil_test_enabled = enabled; + + if (enabled) + { + glEnable(GL_STENCIL_TEST); + } + else + { + glDisable(GL_STENCIL_TEST); + } + } +} + +void pipeline::set_stencil_op(std::uint8_t face_mask, stencil_op fail_op, stencil_op pass_op, stencil_op depth_fail_op, gl::compare_op compare_op) +{ + bool stencil_op_updated = false; + bool compare_op_updated = false; + + if (face_mask & stencil_face_front_bit) + { + if (m_depth_stencil_state.stencil_front.fail_op != fail_op || + m_depth_stencil_state.stencil_front.pass_op != pass_op || + m_depth_stencil_state.stencil_front.depth_fail_op != depth_fail_op) + { + m_depth_stencil_state.stencil_front.fail_op = fail_op; + m_depth_stencil_state.stencil_front.pass_op = pass_op; + m_depth_stencil_state.stencil_front.depth_fail_op = depth_fail_op; + stencil_op_updated = true; + } + + if (m_depth_stencil_state.stencil_front.compare_op != compare_op) + { + m_depth_stencil_state.stencil_front.compare_op = compare_op; + compare_op_updated = true; + } + } + + if (face_mask & stencil_face_back_bit) + { + if (m_depth_stencil_state.stencil_back.fail_op != fail_op || + m_depth_stencil_state.stencil_back.pass_op != pass_op || + m_depth_stencil_state.stencil_back.depth_fail_op != depth_fail_op) + { + m_depth_stencil_state.stencil_back.fail_op = fail_op; + m_depth_stencil_state.stencil_back.pass_op = pass_op; + m_depth_stencil_state.stencil_back.depth_fail_op = depth_fail_op; + stencil_op_updated = true; + } + + if (m_depth_stencil_state.stencil_back.compare_op != compare_op) + { + m_depth_stencil_state.stencil_back.compare_op = compare_op; + compare_op_updated = true; + } + } + + if (stencil_op_updated || compare_op_updated) + { + const auto gl_face = stencil_face_lut[face_mask]; + + if (stencil_op_updated) + { + const auto gl_fail_op = stencil_op_lut[std::to_underlying(fail_op)]; + const auto gl_pass_op = stencil_op_lut[std::to_underlying(pass_op)]; + const auto gl_depth_fail_op = stencil_op_lut[std::to_underlying(depth_fail_op)]; + glStencilOpSeparate(gl_face, gl_fail_op, gl_depth_fail_op, gl_pass_op); + } + + if (compare_op_updated) + { + const auto gl_compare_op = compare_op_lut[std::to_underlying(compare_op)]; + + if (face_mask == stencil_face_front_and_back) + { + if (m_depth_stencil_state.stencil_front.reference == m_depth_stencil_state.stencil_back.reference && + m_depth_stencil_state.stencil_front.compare_mask == m_depth_stencil_state.stencil_back.compare_mask) + { + glStencilFuncSeparate(gl_face, gl_compare_op, std::bit_cast(m_depth_stencil_state.stencil_front.reference), std::bit_cast(m_depth_stencil_state.stencil_front.compare_mask)); + } + else + { + glStencilFuncSeparate(GL_FRONT, gl_compare_op, std::bit_cast(m_depth_stencil_state.stencil_front.reference), std::bit_cast(m_depth_stencil_state.stencil_front.compare_mask)); + glStencilFuncSeparate(GL_BACK, gl_compare_op, std::bit_cast(m_depth_stencil_state.stencil_back.reference), std::bit_cast(m_depth_stencil_state.stencil_back.compare_mask)); + } + } + else if (face_mask == stencil_face_front_bit) + { + glStencilFuncSeparate(gl_face, gl_compare_op, std::bit_cast(m_depth_stencil_state.stencil_front.reference), std::bit_cast(m_depth_stencil_state.stencil_front.compare_mask)); + } + else + { + glStencilFuncSeparate(gl_face, gl_compare_op, std::bit_cast(m_depth_stencil_state.stencil_back.reference), std::bit_cast(m_depth_stencil_state.stencil_back.compare_mask)); + } + } + } +} + +void pipeline::set_stencil_compare_mask(std::uint8_t face_mask, std::uint32_t compare_mask) +{ + bool compare_mask_updated = false; + + if (face_mask & stencil_face_front_bit) + { + if (m_depth_stencil_state.stencil_front.compare_mask != compare_mask) + { + m_depth_stencil_state.stencil_front.compare_mask = compare_mask; + compare_mask_updated = true; + } + } + + if (face_mask & stencil_face_back_bit) + { + if (m_depth_stencil_state.stencil_back.compare_mask != compare_mask) + { + m_depth_stencil_state.stencil_back.compare_mask = compare_mask; + compare_mask_updated = true; + } + } + + if (compare_mask_updated) + { + const auto gl_face = stencil_face_lut[face_mask]; + + if (face_mask == stencil_face_front_and_back) + { + if (m_depth_stencil_state.stencil_front.reference == m_depth_stencil_state.stencil_back.reference && + m_depth_stencil_state.stencil_front.compare_op == m_depth_stencil_state.stencil_back.compare_op) + { + const auto gl_compare_op = compare_op_lut[std::to_underlying(m_depth_stencil_state.stencil_front.compare_op)]; + glStencilFuncSeparate(gl_face, gl_compare_op, std::bit_cast(m_depth_stencil_state.stencil_front.reference), static_cast(compare_mask)); + } + else + { + const auto gl_compare_op_front = compare_op_lut[std::to_underlying(m_depth_stencil_state.stencil_front.compare_op)]; + const auto gl_compare_op_back = compare_op_lut[std::to_underlying(m_depth_stencil_state.stencil_back.compare_op)]; + glStencilFuncSeparate(GL_FRONT, gl_compare_op_front, std::bit_cast(m_depth_stencil_state.stencil_front.reference), std::bit_cast(compare_mask)); + glStencilFuncSeparate(GL_BACK, gl_compare_op_back, std::bit_cast(m_depth_stencil_state.stencil_back.reference), std::bit_cast(compare_mask)); + } + } + else if (face_mask == stencil_face_front_bit) + { + const auto gl_compare_op = compare_op_lut[std::to_underlying(m_depth_stencil_state.stencil_front.compare_op)]; + glStencilFuncSeparate(gl_face, gl_compare_op, std::bit_cast(m_depth_stencil_state.stencil_front.reference), std::bit_cast(compare_mask)); + } + else + { + const auto gl_compare_op = compare_op_lut[std::to_underlying(m_depth_stencil_state.stencil_back.compare_op)]; + glStencilFuncSeparate(gl_face, gl_compare_op, std::bit_cast(m_depth_stencil_state.stencil_back.reference), std::bit_cast(compare_mask)); + } + } +} + +void pipeline::set_stencil_reference(std::uint8_t face_mask, std::uint32_t reference) +{ + bool reference_updated = false; + + if (face_mask & stencil_face_front_bit) + { + if (m_depth_stencil_state.stencil_front.reference != reference) + { + m_depth_stencil_state.stencil_front.reference = reference; + reference_updated = true; + } + } + + if (face_mask & stencil_face_back_bit) + { + if (m_depth_stencil_state.stencil_back.reference != reference) + { + m_depth_stencil_state.stencil_back.reference = reference; + reference_updated = true; + } + } + + if (reference_updated) + { + const auto gl_face = stencil_face_lut[face_mask]; + + if (face_mask == stencil_face_front_and_back) + { + if (m_depth_stencil_state.stencil_front.compare_mask == m_depth_stencil_state.stencil_back.compare_mask && + m_depth_stencil_state.stencil_front.compare_op == m_depth_stencil_state.stencil_back.compare_op) + { + const auto gl_compare_op = compare_op_lut[std::to_underlying(m_depth_stencil_state.stencil_front.compare_op)]; + glStencilFuncSeparate(gl_face, gl_compare_op, std::bit_cast(reference), std::bit_cast(m_depth_stencil_state.stencil_front.compare_mask)); + } + else + { + const auto gl_compare_op_front = compare_op_lut[std::to_underlying(m_depth_stencil_state.stencil_front.compare_op)]; + const auto gl_compare_op_back = compare_op_lut[std::to_underlying(m_depth_stencil_state.stencil_back.compare_op)]; + glStencilFuncSeparate(GL_FRONT, gl_compare_op_front, std::bit_cast(reference), std::bit_cast(m_depth_stencil_state.stencil_front.compare_mask)); + glStencilFuncSeparate(GL_BACK, gl_compare_op_back, std::bit_cast(reference), std::bit_cast(m_depth_stencil_state.stencil_back.compare_mask)); + } + } + else if (face_mask == stencil_face_front_bit) + { + const auto gl_compare_op = compare_op_lut[std::to_underlying(m_depth_stencil_state.stencil_front.compare_op)]; + glStencilFuncSeparate(gl_face, gl_compare_op, std::bit_cast(reference), std::bit_cast(m_depth_stencil_state.stencil_front.compare_mask)); + } + else + { + const auto gl_compare_op = compare_op_lut[std::to_underlying(m_depth_stencil_state.stencil_back.compare_op)]; + glStencilFuncSeparate(gl_face, gl_compare_op, std::bit_cast(reference), std::bit_cast(m_depth_stencil_state.stencil_back.compare_mask)); + } + } +} + +void pipeline::set_stencil_write_mask(std::uint8_t face_mask, std::uint32_t write_mask) +{ + bool write_mask_updated = false; + + if (face_mask & stencil_face_front_bit) + { + if (m_depth_stencil_state.stencil_front.write_mask != write_mask) + { + m_depth_stencil_state.stencil_front.write_mask = write_mask; + write_mask_updated = true; + } + } + + if (face_mask & stencil_face_back_bit) + { + if (m_depth_stencil_state.stencil_back.write_mask != write_mask) + { + m_depth_stencil_state.stencil_back.write_mask = write_mask; + write_mask_updated = true; + } + } + + if (write_mask_updated) + { + const auto gl_face = stencil_face_lut[face_mask]; + glStencilMaskSeparate(gl_face, std::bit_cast(write_mask)); + } +} + +void pipeline::set_logic_op_enabled(bool enabled) +{ + if (m_color_blend_state.logic_op_enabled != enabled) + { + m_color_blend_state.logic_op_enabled = enabled; + + if (enabled) + { + glEnable(GL_COLOR_LOGIC_OP); + } + else + { + glDisable(GL_COLOR_LOGIC_OP); + } + } +} + +void pipeline::set_logic_op(gl::logic_op logic_op) +{ + if (m_color_blend_state.logic_op != logic_op) + { + m_color_blend_state.logic_op = logic_op; + + const auto gl_logic_op = logic_op_lut[std::to_underlying(logic_op)]; + glLogicOp(gl_logic_op); + } +} + +void pipeline::set_color_blend_enabled(bool enabled) +{ + if (m_color_blend_state.blend_enabled != enabled) + { + m_color_blend_state.blend_enabled = enabled; + + if (enabled) + { + glEnable(GL_BLEND); + } + else + { + glDisable(GL_BLEND); + } + } +} + +void pipeline::set_color_blend_equation(const color_blend_equation& equation) +{ + if (m_color_blend_state.color_blend_equation.src_color_blend_factor != equation.src_color_blend_factor || + m_color_blend_state.color_blend_equation.dst_color_blend_factor != equation.dst_color_blend_factor || + m_color_blend_state.color_blend_equation.src_alpha_blend_factor != equation.src_alpha_blend_factor || + m_color_blend_state.color_blend_equation.dst_alpha_blend_factor != equation.dst_alpha_blend_factor) + { + m_color_blend_state.color_blend_equation.src_color_blend_factor = equation.src_color_blend_factor; + m_color_blend_state.color_blend_equation.dst_color_blend_factor = equation.dst_color_blend_factor; + m_color_blend_state.color_blend_equation.src_alpha_blend_factor = equation.src_alpha_blend_factor; + m_color_blend_state.color_blend_equation.dst_alpha_blend_factor = equation.dst_alpha_blend_factor; + + const auto gl_src_rgb = blend_factor_lut[std::to_underlying(equation.src_color_blend_factor)]; + const auto gl_dst_rgb = blend_factor_lut[std::to_underlying(equation.dst_color_blend_factor)]; + const auto gl_src_alpha = blend_factor_lut[std::to_underlying(equation.src_alpha_blend_factor)]; + const auto gl_dst_alpha = blend_factor_lut[std::to_underlying(equation.dst_alpha_blend_factor)]; + + glBlendFuncSeparate(gl_src_rgb, gl_dst_rgb, gl_src_alpha, gl_dst_alpha); + } + + if (m_color_blend_state.color_blend_equation.color_blend_op != equation.color_blend_op || + m_color_blend_state.color_blend_equation.alpha_blend_op != equation.alpha_blend_op) + { + m_color_blend_state.color_blend_equation.color_blend_op = equation.color_blend_op; + m_color_blend_state.color_blend_equation.alpha_blend_op = equation.alpha_blend_op; + + const auto gl_mode_rgb = blend_op_lut[std::to_underlying(equation.color_blend_op)]; + const auto gl_mode_alpha = blend_op_lut[std::to_underlying(equation.alpha_blend_op)]; + + glBlendEquationSeparate(gl_mode_rgb, gl_mode_alpha); + } +} + +void pipeline::set_color_write_mask(std::uint8_t mask) +{ + if (m_color_blend_state.color_write_mask != mask) + { + m_color_blend_state.color_write_mask = mask; + + glColorMask + ( + mask & color_component_r_bit, + mask & color_component_g_bit, + mask & color_component_b_bit, + mask & color_component_a_bit + ); + } +} + +void pipeline::set_blend_constants(const std::array& blend_constants) +{ + if (m_color_blend_state.blend_constants != blend_constants) + { + m_color_blend_state.blend_constants = blend_constants; + glBlendColor(blend_constants[0], blend_constants[1], blend_constants[2], blend_constants[3]); + } +} + +void pipeline::draw(std::uint32_t vertex_count, std::uint32_t instance_count, std::uint32_t first_vertex, std::uint32_t first_instance) +{ + glDrawArraysInstancedBaseInstance + ( + primitive_topology_lut[std::to_underlying(m_input_assembly_state.topology)], + static_cast(first_vertex), + static_cast(vertex_count), + static_cast(instance_count), + static_cast(first_instance) + ); +} + +void pipeline::draw_indexed(std::uint32_t index_count, std::uint32_t instance_count, std::uint32_t first_index, std::int32_t vertex_offset, std::uint32_t first_instance) +{ + glDrawElementsInstancedBaseInstance + ( + primitive_topology_lut[std::to_underlying(m_input_assembly_state.topology)], + static_cast(instance_count), + GL_UNSIGNED_INT,// GL_UNSIGNED_SHORT, GL_UNSIGNED_BYTE + reinterpret_cast(first_index * sizeof(unsigned int)), + static_cast(instance_count), + static_cast(first_instance) + ); +} + +void pipeline::clear_attachments(std::uint8_t mask, const clear_value& value) +{ + GLbitfield gl_clear_mask = 0; + + if (mask & color_clear_bit) + { + // Add color attachment to OpenGL clear mask + gl_clear_mask |= GL_COLOR_BUFFER_BIT; + + if (m_clear_value.color != value.color) + { + // Update color clear value + glClearColor(value.color[0], value.color[1], value.color[2], value.color[3]); + m_clear_value.color = value.color; + } + } + + if (mask & depth_clear_bit) + { + // Add depth attachment to OpenGL clear mask + gl_clear_mask |= GL_DEPTH_BUFFER_BIT; + + if (m_clear_value.depth != value.depth) + { + // Update depth clear value + glClearDepth(value.depth); + m_clear_value.depth = value.depth; + } + } + + if (mask & stencil_clear_bit) + { + // Add stencil attachment to OpenGL clear mask + gl_clear_mask |= GL_STENCIL_BUFFER_BIT; + + if (m_clear_value.stencil != value.stencil) + { + // Update stencil clear value + glClearStencil(static_cast(value.stencil)); + m_clear_value.stencil = value.stencil; + } + } + + // Clear attachments + glClear(gl_clear_mask); +} + +void pipeline::defaut_framebuffer_resized(std::uint32_t width, std::uint32_t height) noexcept +{ + m_default_framebuffer_dimensions = {width, height}; +} + +void pipeline::fetch_vertex_input_state() +{ +} + +void pipeline::fetch_input_assembly_state() +{ + m_input_assembly_state.primitive_restart_enabled = glIsEnabled(GL_PRIMITIVE_RESTART); +} + +void pipeline::fetch_viewport_state() +{ + // Query viewport position and dimensions + GLint gl_viewport[4]; + glGetIntegerv(GL_VIEWPORT, gl_viewport); + + // Query viewport depth range + GLfloat gl_depth_range[2]; + glGetFloatv(GL_DEPTH_RANGE, gl_depth_range); + + // Query scissor box + GLint gl_scissor_box[4] = {0, 0, 0, 0}; + glGetIntegerv(GL_SCISSOR_BOX, gl_scissor_box); + + // Match viewport state + m_viewport_state.viewports = + {{ + static_cast(gl_viewport[0]), + static_cast(gl_viewport[1]), + static_cast(gl_viewport[2]), + static_cast(gl_viewport[3]), + gl_depth_range[0], + gl_depth_range[1] + }}; + m_viewport_state.scissors = + {{ + static_cast(gl_scissor_box[0]), + static_cast(gl_scissor_box[1]), + static_cast(std::max(0, gl_scissor_box[2])), + static_cast(std::max(0, gl_scissor_box[3])) + }}; +} + +void pipeline::fetch_rasterization_state() +{ + // Query rasterizer discard + bool gl_rasterizer_discard_enabled = glIsEnabled(GL_RASTERIZER_DISCARD); + + // Query fill mode + GLint gl_fill_mode; + glGetIntegerv(GL_POLYGON_MODE, &gl_fill_mode); + + // Query cull mode + bool gl_cull_enabled = glIsEnabled(GL_CULL_FACE); + GLint gl_cull_mode; + glGetIntegerv(GL_CULL_FACE_MODE, &gl_cull_mode); + + // Query front face + GLint gl_front_face; + glGetIntegerv(GL_FRONT_FACE, &gl_front_face); + + // Query depth bias + bool gl_depth_bias_enabled = glIsEnabled(GL_POLYGON_OFFSET_FILL) && + glIsEnabled(GL_POLYGON_OFFSET_LINE) && + glIsEnabled(GL_POLYGON_OFFSET_POINT); + float gl_depth_bias_constant_factor; + float gl_depth_bias_slope_factor; + glGetFloatv(GL_POLYGON_OFFSET_UNITS, &gl_depth_bias_constant_factor); + glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &gl_depth_bias_slope_factor); + + // Query depth clamp + bool gl_depth_clamp_enabled = glIsEnabled(GL_DEPTH_CLAMP); + + // Query scissor test + bool gl_scissor_test_enabled = glIsEnabled(GL_SCISSOR_TEST); + + // Query provoking vertex + GLint gl_provoking_vertex; + glGetIntegerv(GL_PROVOKING_VERTEX, &gl_provoking_vertex); + + // Query point size + float gl_point_size; + glGetFloatv(GL_POINT_SIZE, &gl_point_size); + + // Query line width + float gl_line_width; + glGetFloatv(GL_LINE_WIDTH, &gl_line_width); + + // Match rasterizer state + m_rasterization_state.rasterizer_discard_enabled = gl_rasterizer_discard_enabled; + m_rasterization_state.fill_mode = (gl_fill_mode == GL_POINT ? fill_mode::point : gl_fill_mode == GL_LINE ? fill_mode::line : fill_mode::fill); + if (gl_cull_enabled) + { + m_rasterization_state.cull_mode = (gl_cull_mode == GL_FRONT_AND_BACK ? cull_mode::front_and_back : gl_fill_mode == GL_FRONT ? cull_mode::front : cull_mode::back); + } + else + { + m_rasterization_state.cull_mode = cull_mode::none; + } + m_rasterization_state.front_face = (gl_front_face == GL_CW ? front_face::clockwise : front_face::counter_clockwise); + m_rasterization_state.depth_bias_enabled = gl_depth_bias_enabled; + m_rasterization_state.depth_bias_constant_factor = gl_depth_bias_constant_factor; + m_rasterization_state.depth_bias_slope_factor = gl_depth_bias_slope_factor; + m_rasterization_state.depth_clamp_enabled = gl_depth_clamp_enabled; + m_rasterization_state.scissor_test_enabled = gl_scissor_test_enabled; + m_rasterization_state.provoking_vertex_mode = (gl_provoking_vertex == GL_FIRST_VERTEX_CONVENTION ? provoking_vertex_mode::first : provoking_vertex_mode::last); + m_rasterization_state.point_size = gl_point_size; + m_rasterization_state.line_width = gl_line_width; +} + +void pipeline::fetch_depth_stencil_state() +{ + auto inv_compare_op_lut = [](GLint func) -> gl::compare_op + { + switch (func) + { + case GL_NEVER: + return compare_op::never; + case GL_LESS: + return compare_op::less; + case GL_EQUAL: + return compare_op::equal; + case GL_LEQUAL: + return compare_op::less_or_equal; + case GL_GREATER: + return compare_op::greater; + case GL_NOTEQUAL: + return compare_op::not_equal; + case GL_GEQUAL: + return compare_op::greater_or_equal; + case GL_ALWAYS: + default: + return compare_op::always; + } + }; + + auto inv_stencil_op_lut = [](GLint op) -> gl::stencil_op + { + switch (op) + { + case GL_KEEP: + return stencil_op::keep; + case GL_ZERO: + return stencil_op::zero; + case GL_REPLACE: + return stencil_op::replace; + case GL_INCR: + return stencil_op::increment_and_clamp; + case GL_DECR: + return stencil_op::decrement_and_clamp; + case GL_INVERT: + return stencil_op::invert; + case GL_INCR_WRAP: + return stencil_op::increment_and_wrap; + case GL_DECR_WRAP: + default: + return stencil_op::decrement_and_wrap; + } + }; + + m_depth_stencil_state.depth_test_enabled = glIsEnabled(GL_DEPTH_TEST); + + GLboolean gl_depth_write_enabled; + glGetBooleanv(GL_DEPTH_WRITEMASK, &gl_depth_write_enabled); + m_depth_stencil_state.depth_write_enabled = gl_depth_write_enabled; + + GLint gl_depth_compare_op; + glGetIntegerv(GL_DEPTH_FUNC, &gl_depth_compare_op); + m_depth_stencil_state.depth_compare_op = inv_compare_op_lut(gl_depth_compare_op); + + m_depth_stencil_state.stencil_test_enabled = glIsEnabled(GL_STENCIL_TEST); + + // Stencil front + { + GLint gl_stencil_front_fail; + GLint gl_stencil_front_pass_depth_pass; + GLint gl_stencil_front_pass_depth_fail; + GLint gl_stencil_front_func; + GLint gl_stencil_front_value_mask; + GLint gl_stencil_front_write_mask; + GLint gl_stencil_front_ref; + + glGetIntegerv(GL_STENCIL_FAIL, &gl_stencil_front_fail); + glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &gl_stencil_front_pass_depth_pass); + glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &gl_stencil_front_pass_depth_fail); + glGetIntegerv(GL_STENCIL_FUNC, &gl_stencil_front_func); + glGetIntegerv(GL_STENCIL_VALUE_MASK, &gl_stencil_front_value_mask); + glGetIntegerv(GL_STENCIL_WRITEMASK, &gl_stencil_front_write_mask); + glGetIntegerv(GL_STENCIL_REF, &gl_stencil_front_ref); + + m_depth_stencil_state.stencil_front.fail_op = inv_stencil_op_lut(gl_stencil_front_fail); + m_depth_stencil_state.stencil_front.pass_op = inv_stencil_op_lut(gl_stencil_front_pass_depth_pass); + m_depth_stencil_state.stencil_front.depth_fail_op = inv_stencil_op_lut(gl_stencil_front_pass_depth_fail); + m_depth_stencil_state.stencil_front.compare_op = inv_compare_op_lut(gl_stencil_front_func); + m_depth_stencil_state.stencil_front.compare_mask = std::bit_cast(gl_stencil_front_value_mask); + m_depth_stencil_state.stencil_front.write_mask = std::bit_cast(gl_stencil_front_write_mask); + m_depth_stencil_state.stencil_front.reference = std::bit_cast(gl_stencil_front_ref); + } + + // Stencil back + { + GLint gl_stencil_back_fail; + GLint gl_stencil_back_pass_depth_pass; + GLint gl_stencil_back_pass_depth_fail; + GLint gl_stencil_back_func; + GLint gl_stencil_back_value_mask; + GLint gl_stencil_back_write_mask; + GLint gl_stencil_back_ref; + + glGetIntegerv(GL_STENCIL_BACK_FAIL, &gl_stencil_back_fail); + glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_PASS, &gl_stencil_back_pass_depth_pass); + glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_FAIL, &gl_stencil_back_pass_depth_fail); + glGetIntegerv(GL_STENCIL_BACK_FUNC, &gl_stencil_back_func); + glGetIntegerv(GL_STENCIL_BACK_VALUE_MASK, &gl_stencil_back_value_mask); + glGetIntegerv(GL_STENCIL_BACK_WRITEMASK, &gl_stencil_back_write_mask); + glGetIntegerv(GL_STENCIL_BACK_REF, &gl_stencil_back_ref); + + m_depth_stencil_state.stencil_back.fail_op = inv_stencil_op_lut(gl_stencil_back_fail); + m_depth_stencil_state.stencil_back.pass_op = inv_stencil_op_lut(gl_stencil_back_pass_depth_pass); + m_depth_stencil_state.stencil_back.depth_fail_op = inv_stencil_op_lut(gl_stencil_back_pass_depth_fail); + m_depth_stencil_state.stencil_back.compare_op = inv_compare_op_lut(gl_stencil_back_func); + m_depth_stencil_state.stencil_back.compare_mask = std::bit_cast(gl_stencil_back_value_mask); + m_depth_stencil_state.stencil_back.write_mask = std::bit_cast(gl_stencil_back_write_mask); + m_depth_stencil_state.stencil_back.reference = std::bit_cast(gl_stencil_back_ref); + } +} + +void pipeline::fetch_color_blend_state() +{ + auto inv_logic_op_lut = [](GLint op) -> gl::logic_op + { + switch (op) + { + case GL_CLEAR: + return gl::logic_op::bitwise_clear; + case GL_AND: + return gl::logic_op::bitwise_and; + case GL_AND_REVERSE: + return gl::logic_op::bitwise_and_reverse; + case GL_COPY: + return gl::logic_op::bitwise_copy; + case GL_AND_INVERTED: + return gl::logic_op::bitwise_and_inverted; + case GL_NOOP: + return gl::logic_op::bitwise_no_op; + case GL_XOR: + return gl::logic_op::bitwise_xor; + case GL_OR: + return gl::logic_op::bitwise_or; + case GL_NOR: + return gl::logic_op::bitwise_nor; + case GL_EQUIV: + return gl::logic_op::bitwise_equivalent; + case GL_INVERT: + return gl::logic_op::bitwise_invert; + case GL_OR_REVERSE: + return gl::logic_op::bitwise_or_reverse; + case GL_COPY_INVERTED: + return gl::logic_op::bitwise_copy_inverted; + case GL_OR_INVERTED: + return gl::logic_op::bitwise_or_inverted; + case GL_NAND: + return gl::logic_op::bitwise_nand; + case GL_SET: + default: + return gl::logic_op::bitwise_set; + } + }; + + auto inv_blend_factor_lut = [](GLint func) -> gl::blend_factor + { + switch (func) + { + case GL_ZERO: + return blend_factor::zero; + case GL_ONE: + return blend_factor::one; + case GL_SRC_COLOR: + return blend_factor::src_color; + case GL_ONE_MINUS_SRC_COLOR: + return blend_factor::one_minus_src_color; + case GL_DST_COLOR: + return blend_factor::dst_color; + case GL_ONE_MINUS_DST_COLOR: + return blend_factor::one_minus_dst_color; + case GL_SRC_ALPHA: + return blend_factor::src_alpha; + case GL_ONE_MINUS_SRC_ALPHA: + return blend_factor::one_minus_src_alpha; + case GL_DST_ALPHA: + return blend_factor::dst_alpha; + case GL_ONE_MINUS_DST_ALPHA: + return blend_factor::one_minus_dst_alpha; + case GL_CONSTANT_COLOR: + return blend_factor::constant_color; + case GL_ONE_MINUS_CONSTANT_COLOR: + return blend_factor::one_minus_constant_color; + case GL_CONSTANT_ALPHA: + return blend_factor::constant_alpha; + case GL_ONE_MINUS_CONSTANT_ALPHA: + return blend_factor::one_minus_constant_alpha; + case GL_SRC_ALPHA_SATURATE: + return blend_factor::src_alpha_saturate; + case GL_SRC1_COLOR: + return blend_factor::src1_color; + case GL_ONE_MINUS_SRC1_COLOR: + return blend_factor::one_minus_src1_color; + case GL_SRC1_ALPHA: + return blend_factor::src1_alpha; + case GL_ONE_MINUS_SRC1_ALPHA: + default: + return blend_factor::one_minus_src1_alpha; + } + }; + + auto inv_blend_op_lut = [](GLint mode) -> gl::blend_op + { + switch (mode) + { + case GL_FUNC_ADD: + return blend_op::add; + case GL_FUNC_SUBTRACT: + return blend_op::subtract; + case GL_FUNC_REVERSE_SUBTRACT: + return blend_op::reverse_subtract; + case GL_MIN: + return blend_op::min; + case GL_MAX: + default: + return blend_op::max; + } + }; + + m_color_blend_state.logic_op_enabled = glIsEnabled(GL_COLOR_LOGIC_OP); + + GLint gl_logic_op; + glGetIntegerv(GL_LOGIC_OP_MODE, &gl_logic_op); + + m_color_blend_state.logic_op = inv_logic_op_lut(gl_logic_op); + + m_color_blend_state.blend_enabled = glIsEnabled(GL_BLEND); + + GLint gl_blend_src_rgb; + GLint gl_blend_dst_rgb; + GLint gl_blend_equation_rgb; + GLint gl_blend_src_alpha; + GLint gl_blend_dst_alpha; + GLint gl_blend_equation_alpha; + glGetIntegerv(GL_BLEND_SRC_RGB, &gl_blend_src_rgb); + glGetIntegerv(GL_BLEND_DST_RGB, &gl_blend_dst_rgb); + glGetIntegerv(GL_BLEND_EQUATION_RGB, &gl_blend_equation_rgb); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &gl_blend_src_alpha); + glGetIntegerv(GL_BLEND_DST_ALPHA, &gl_blend_dst_alpha); + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &gl_blend_equation_alpha); + + m_color_blend_state.color_blend_equation.src_color_blend_factor = inv_blend_factor_lut(gl_blend_src_rgb); + m_color_blend_state.color_blend_equation.dst_color_blend_factor = inv_blend_factor_lut(gl_blend_dst_rgb); + m_color_blend_state.color_blend_equation.color_blend_op = inv_blend_op_lut(gl_blend_equation_rgb); + m_color_blend_state.color_blend_equation.src_alpha_blend_factor = inv_blend_factor_lut(gl_blend_src_alpha); + m_color_blend_state.color_blend_equation.dst_alpha_blend_factor = inv_blend_factor_lut(gl_blend_dst_alpha); + m_color_blend_state.color_blend_equation.alpha_blend_op = inv_blend_op_lut(gl_blend_equation_alpha); + + GLboolean gl_color_writemask[4]; + glGetBooleanv(GL_COLOR_WRITEMASK, gl_color_writemask); + + m_color_blend_state.color_write_mask = + static_cast(gl_color_writemask[0]) | + (static_cast(gl_color_writemask[1]) << 1) | + (static_cast(gl_color_writemask[2]) << 2) | + (static_cast(gl_color_writemask[3]) << 3); + + glGetFloatv(GL_BLEND_COLOR, m_color_blend_state.blend_constants.data()); +} + +void pipeline::fetch_clear_value() +{ + // Query clear values + GLfloat gl_color_clear[4]; + GLfloat gl_depth_clear; + GLint gl_stencil_clear; + glGetFloatv(GL_COLOR_CLEAR_VALUE, gl_color_clear); + glGetFloatv(GL_DEPTH_CLEAR_VALUE, &gl_depth_clear); + glGetIntegerv(GL_STENCIL_CLEAR_VALUE, &gl_stencil_clear); + + // Match clear state + m_clear_value.color = {gl_color_clear[0], gl_color_clear[1], gl_color_clear[2], gl_color_clear[3]}; + m_clear_value.depth = gl_depth_clear; + m_clear_value.stencil = static_cast(gl_stencil_clear); +} + +} // namespace gl diff --git a/src/engine/gl/pipeline.hpp b/src/engine/gl/pipeline.hpp new file mode 100644 index 0000000..02727f8 --- /dev/null +++ b/src/engine/gl/pipeline.hpp @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_PIPELINE_HPP +#define ANTKEEPER_GL_PIPELINE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace app { class sdl_window_manager; } + +namespace gl { + +/** + * Graphics pipeline interface. + */ +class pipeline +{ +public: + /** + * Constructs a pipeline. + */ + pipeline(); + + /// @name Vertex input state + /// @{ + + /** + * Sets the vertex input. + * + * @param vertex_bindings Vertex bindings. + * @param vertex_attributes Vertex attributes. + */ + // void set_vertex_input(std::span vertex_bindings, std::span vertex_attributes); + + void bind_framebuffer(const gl::framebuffer* framebuffer); + void bind_shader_program(const gl::shader_program* shader_program); + + /** + * Binds a vertex array. + * + * @param array Vertex array to bind. + */ + void bind_vertex_array(const vertex_array* array); + + /** + * Binds vertex buffers. + * + * @param first_binding Index of the first vertex input binding. + * @param buffers Sequence of buffers to bind. + * @param offsets Sequence of byte offsets into each buffer. + * @param strides Sequence of byte strides between consecutive elements within each buffer. + */ + void bind_vertex_buffers(std::uint32_t first_binding, std::span buffers, std::span offsets, std::span strides); + + /// @} + + /// @name Input assembly state + /// @{ + + /** + * Sets the primitive topology to use for drawing. + * + * @param topology Primitive topology to use for drawing. + */ + void set_primitive_topology(primitive_topology topology); + + /** + * Controls whether a special vertex index value is treated as restarting the assembly of primitives. + * + * @param enabled `true` if a special vertex index value should restart the assembly of primitives, `false` otherwise. + */ + void set_primitive_restart_enabled(bool enabled); + + /// @} + + /// @name Viewport state + /// @{ + + /** + * Sets one or more viewports. + * + * @param first_viewport Index of the first viewport to set. + * @param viewports Sequence of viewports. + * + * @except std::out_of_range Viewport index out of range. + * + * @warning Currently only a single viewport is supported. + */ + void set_viewport(std::uint32_t first_viewport, std::span viewports); + + /** + * Sets one or more scissor regions. + * + * @param first_scissor Index of the first scissor region to set. + * @param scissors Sequence of scissor regions. + * + * @except std::out_of_range Scissor region index out of range. + * + * @warning Currently only a single scissor region is supported. + */ + void set_scissor(std::uint32_t first_scissor, std::span scissors); + + /// @} + + /// @name Rasterizer state + /// @{ + + /** + * Controls whether primitives are discarded before the rasterization stage. + * + * @param enabled `true` if primitives should be discarded before the rasterization stage, `false` otherwise. + */ + void set_rasterizer_discard_enabled(bool enabled); + + /** + * Sets the polygon rasterization mode. + * + * @param mode Polygon rasterization mode. + */ + void set_fill_mode(fill_mode mode); + + /** + * Sets the triangle culling mode. + * + * @param mode Triangle culling mode. + */ + void set_cull_mode(cull_mode mode); + + /** + * Sets the front-facing triangle orientation. + * + * @param face Front-facing triangle orientation. + */ + void set_front_face(front_face face); + + /** + * Controls whether to bias fragment depth values. + * + * @param enabled `true` if fragment depth values should be biased, `false` otherwise. + */ + void set_depth_bias_enabled(bool enabled); + + /** + * Sets depth bias factors. + * + * @param constant_factor Scalar factor controlling the constant depth value added to each fragment. + * @param slope_factor Scalar factor applied to a fragment's slope in depth bias calculations. + */ + void set_depth_bias_factors(float constant_factor, float slope_factor); + + /** + * Controls whether depth clamping is enabled. + * + * @param enabled `true` if depth clamping should be enabled, `false` otherwise. + */ + void set_depth_clamp_enabled(bool enabled); + + /** + * Enables or disables scissor testing. + * + * @param enabled `true` if scissor testing should be enabled, `false` otherwise. + */ + void set_scissor_test_enabled(bool enabled); + + /** + * Sets the vertex to be used as the source of data for flat-shaded varyings. + * + * @param mode Provoking vertex mode. + */ + void set_provoking_vertex_mode(provoking_vertex_mode mode); + + /** + * Sets the the diameter of rasterized points. + * + * @param size Point size. + */ + void set_point_size(float size); + + /** + * Sets the width of rasterized lines. + * + * @param width Width of rasterized line segments. + */ + void set_line_width(float width); + + /// @} + + /// @name Depth/stencil state + /// @{ + + /** + * Controls whether depth testing is enabled. + * + * @param enabled `true` if depth testing should be enabled, `false` otherwise. + */ + void set_depth_test_enabled(bool enabled); + + /** + * Controls whether depth writes are enabled. + * + * @param enabled `true` if depth writes should be enabled when depth testing is enabled, `false` otherwise. + * + * @note Depth writes are always disabled when depth testing is disabled. + */ + void set_depth_write_enabled(bool enabled); + + /** + * Sets the depth comparison operator. + * + * @param compare_op Comparison operator to use in the depth comparison step of the depth test. + */ + void set_depth_compare_op(gl::compare_op compare_op); + + /** + * Controls whether stencil testing is enabled. + * + * @param enabled `true` if stencil testing should be enabled, `false` otherwise. + */ + void set_stencil_test_enabled(bool enabled); + + /** + * Sets the stencil operations. + * + * @param face_mask Bit mask specifying the set of stencil states for which to update the stencil operation. + * @param fail_op Action performed on samples that fail the stencil test. + * @param pass_op Action performed on samples that pass both the depth and stencil tests. + * @param depth_fail_op Action performed on samples that pass the stencil test and fail the depth test. + * @param compare_op Comparison operator used in the stencil test. + */ + void set_stencil_op(std::uint8_t face_mask, stencil_op fail_op, stencil_op pass_op, stencil_op depth_fail_op, gl::compare_op compare_op); + + /** + * Sets the stencil compare mask. + * + * @param face_mask Bit mask specifying the set of stencil states for which to update the compare mask. + * @param compare_mask New value to use as the stencil compare mask. + */ + void set_stencil_compare_mask(std::uint8_t face_mask, std::uint32_t compare_mask); + + /** + * Sets the stencil reference value. + * + * @param face_mask Bit mask specifying the set of stencil states for which to update the reference value. + * @param reference New value to use as the stencil reference value. + */ + void set_stencil_reference(std::uint8_t face_mask, std::uint32_t reference); + + /** + * Sets the stencil write mask. + * + * @param face_mask Bit mask specifying the set of stencil states for which to update the write mask. + * @param write_mask New value to use as the stencil write mask. + */ + void set_stencil_write_mask(std::uint8_t face_mask, std::uint32_t write_mask); + + /// @} + + /// @name Color blend state + /// @{ + + /** + * Controls whether whether logical operations are enabled. + * + * @param enabled `true` if logical operations should be enabled, `false` otherwise. + */ + void set_logic_op_enabled(bool enabled); + + /** + * Selects which logical operation to apply. + * + * @param logic_op Logical operation to apply. + */ + void set_logic_op(gl::logic_op logic_op); + + /** + * Controls whether blending is enabled for the corresponding color attachment. + * + * @param enabled `true` if color blending should be enabled, `false` otherwise. + */ + void set_color_blend_enabled(bool enabled); + + /** + * Sets the color blend factors and operations. + * + * @param equation Color blend factors and operations. + */ + void set_color_blend_equation(const color_blend_equation& equation); + + /** + * Sets the color write mask. + * + * @param mask Bitmask indicating which of the RGBA components are enabled for writing. + */ + void set_color_write_mask(std::uint8_t mask); + + /** + * Sets the values of the blend constants. + * + * @param blend_constants RGBA components of the blend constant that are used in blending, depending on the blend factor. + */ + void set_blend_constants(const std::array& blend_constants); + + /// @} + + /// @name Drawing + /// @{ + + /** + * Draws primitives. + * + * @param vertex_count Number of vertices to draw. + * @param instance_count Number of instances to draw. + * @param first_vertex Index of the first vertex to draw. + * @param first_instance Instance ID of the first instance to draw. (WARNING: not currently supported) + * + * @warning @p first_instance currently not supported. + */ + void draw(std::uint32_t vertex_count, std::uint32_t instance_count, std::uint32_t first_vertex, std::uint32_t first_instance); + + /** + * Draws primitives with indexed vertices. + * + * @param index_count Number of vertices to draw. + * @param instance_count Number of instances to draw. + * @param first_index Base index within the index buffer. + * @param vertex_offset Value added to the vertex index before indexing into the vertex buffer. + * @param first_instance Instance ID of the first instance to draw. (WARNING: not currently supported) + * + * @warning @p first_instance currently not supported. + */ + void draw_indexed(std::uint32_t index_count, std::uint32_t instance_count, std::uint32_t first_index, std::int32_t vertex_offset, std::uint32_t first_instance); + + /// @} + + /// @name Clear + /// @{ + + /** + * Clears the color, depth, or stencil buffers of current attachments. + * + * @param mask Bit mask indicating which buffers should be cleared. + * @param value Color, depth, and stencil values with which to fill the cleared attachments. + */ + void clear_attachments(std::uint8_t mask, const clear_value& value); + + /// @} + + /// @name Limitations + /// @{ + + /// Returns the dimensions of the default framebuffer. + [[nodiscard]] inline constexpr const std::array& get_default_framebuffer_dimensions() const noexcept + { + return m_default_framebuffer_dimensions; + } + + /// Returns the maximum number of supported viewports. + [[nodiscard]] inline constexpr std::uint32_t get_max_viewports() const noexcept + { + return m_max_viewports; + } + + /// Returns the maximum supported degree of sampler anisotropy. + [[nodiscard]] inline constexpr float get_max_sampler_anisotropy() const noexcept + { + return m_max_sampler_anisotropy; + } + + /// @} + +private: + friend class app::sdl_window_manager; + + /// Changes the reported dimensions of the default framebuffer. + void defaut_framebuffer_resized(std::uint32_t width, std::uint32_t height) noexcept; + + void fetch_vertex_input_state(); + void fetch_input_assembly_state(); + void fetch_viewport_state(); + void fetch_rasterization_state(); + void fetch_depth_stencil_state(); + void fetch_color_blend_state(); + void fetch_clear_value(); + + std::uint32_t m_max_viewports{1}; + float m_max_sampler_anisotropy{0.0f}; + std::array m_default_framebuffer_dimensions{0, 0}; + + pipeline_vertex_input_state m_vertex_input_state; + pipeline_input_assembly_state m_input_assembly_state; + pipeline_viewport_state m_viewport_state; + pipeline_rasterization_state m_rasterization_state; + pipeline_depth_stencil_state m_depth_stencil_state; + pipeline_color_blend_state m_color_blend_state; + clear_value m_clear_value; + + const framebuffer* m_framebuffer{}; + const shader_program* m_shader_program{}; + const vertex_array* m_vertex_array{}; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_PIPELINE_HPP diff --git a/src/engine/gl/primitive-topology.hpp b/src/engine/gl/primitive-topology.hpp new file mode 100644 index 0000000..19ebc6f --- /dev/null +++ b/src/engine/gl/primitive-topology.hpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_PRIMITIVE_TOPOLOGY_HPP +#define ANTKEEPER_GL_PRIMITIVE_TOPOLOGY_HPP + +#include + +namespace gl { + +/// Primitive topologies. +enum class primitive_topology: std::uint8_t +{ + /// Separate point primitives. + point_list, + + /// Separate line primitives. + line_list, + + /// Connected line primitives with consecutive lines sharing a vertex. + line_strip, + + /// Separate triangle primitives. + triangle_list, + + /// Connected triangle primitives with consecutive triangles sharing an edge. + triangle_strip, + + /// Connected triangle primitives with all triangles sharing a common vertex. + triangle_fan, + + /// Separate line primitives with adjacency. + line_list_with_adjacency, + + /// Connected line primitives with adjacency, with consecutive primitives sharing three vertices. + line_strip_with_adjacency, + + /// Separate triangle primitives with adjacency. + triangle_list_with_adjacency, + + /// Connected triangle primitives with adjacency, with consecutive triangles sharing an edge. + triangle_strip_with_adjacency, + + /// Separate patch primitives. + patch_list +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_PRIMITIVE_TOPOLOGY_HPP diff --git a/src/engine/gl/provoking-vertex-mode.hpp b/src/engine/gl/provoking-vertex-mode.hpp new file mode 100644 index 0000000..bf45120 --- /dev/null +++ b/src/engine/gl/provoking-vertex-mode.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_PROVOKING_VERTEX_MODE_HPP +#define ANTKEEPER_GL_PROVOKING_VERTEX_MODE_HPP + +#include + +namespace gl { + +/// Vertex to be used as the source of data for flat-shaded varyings. +enum class provoking_vertex_mode: std::uint8_t +{ + /// Provoking vertex is the first non-adjacency vertex. + first, + + /// Provoking vertex is the last non-adjacency vertex. + last +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_PROVOKING_VERTEX_MODE_HPP diff --git a/src/engine/gl/rasterizer.cpp b/src/engine/gl/rasterizer.cpp deleted file mode 100644 index 764570c..0000000 --- a/src/engine/gl/rasterizer.cpp +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include - -namespace gl { - -static constexpr GLenum drawing_mode_lut[] = -{ - GL_POINTS, - GL_LINE_STRIP, - GL_LINE_LOOP, - GL_LINES, - GL_LINE_STRIP_ADJACENCY, - GL_LINES_ADJACENCY, - GL_TRIANGLE_STRIP, - GL_TRIANGLE_FAN, - GL_TRIANGLES, - GL_TRIANGLE_STRIP_ADJACENCY, - GL_TRIANGLES_ADJACENCY -}; - -static constexpr GLenum element_array_type_lut[] = -{ - GL_UNSIGNED_BYTE, - GL_UNSIGNED_SHORT, - GL_UNSIGNED_INT -}; - -rasterizer::rasterizer(): - bound_vao(nullptr), - bound_shader_program(nullptr) -{ - // Determine dimensions of default framebuffer - GLint scissor_box[4] = {0, 0, 0, 0}; - glGetIntegerv(GL_SCISSOR_BOX, scissor_box); - - // Setup default framebuffer - default_framebuffer = std::make_unique(); - default_framebuffer->m_gl_framebuffer_id = 0; - default_framebuffer->m_dimensions = {scissor_box[2], scissor_box[3]}; - - // Bind default framebuffer - bound_framebuffer = default_framebuffer.get(); - - // Enable seamless filtering across cubemap faces - glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); - - if (GLAD_GL_ARB_clip_control) - { - // Improve depth buffer precision by setting depth range to `[0, 1]`. - glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); - } - else - { - debug::log::error("glClipControl not supported"); - throw std::runtime_error("glClipControl not supported"); - } - - // glClipControl alternative for OpenGL < v4.5 (need to adjust shadow scale-bias matrix too) - // glDepthRange(-1.0f, 1.0f); - - // Set clear depth to `0` for reversed depth - glClearDepth(0.0f); - - glDisable(GL_MULTISAMPLE); - - dummy_vao = std::make_unique(); -} - -rasterizer::~rasterizer() -{} - -void rasterizer::context_resized(int width, int height) -{ - default_framebuffer->m_dimensions = {width, height}; -} - -void rasterizer::use_framebuffer(const gl::framebuffer& framebuffer) -{ - if (bound_framebuffer != &framebuffer) - { - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.m_gl_framebuffer_id); - bound_framebuffer = &framebuffer; - } -} - -void rasterizer::set_clear_color(float r, float g, float b, float a) -{ - glClearColor(r, g, b, a); -} - -void rasterizer::set_clear_depth(float depth) -{ - glClearDepth(depth); -} - -void rasterizer::set_clear_stencil(int s) -{ - glClearStencil(s); -} - -void rasterizer::clear_framebuffer(bool color, bool depth, bool stencil) -{ - GLbitfield mask = 0; - - if (color) - mask |= GL_COLOR_BUFFER_BIT; - if (depth) - mask |= GL_DEPTH_BUFFER_BIT; - if (stencil) - mask |= GL_STENCIL_BUFFER_BIT; - - glClear(mask); -} - -void rasterizer::set_viewport(int x, int y, int width, int height) -{ - glViewport(x, y, static_cast(width), static_cast(height)); -} - -void rasterizer::use_program(const shader_program& program) -{ - if (bound_shader_program != &program) - { - glUseProgram(program.gl_program_id); - bound_shader_program = &program; - } -} - -void rasterizer::draw_arrays(const vertex_array& vao, drawing_mode mode, std::size_t offset, std::size_t count) -{ - GLenum gl_mode = drawing_mode_lut[static_cast(mode)]; - - if (bound_vao != &vao) - { - glBindVertexArray(vao.gl_array_id); - bound_vao = &vao; - } - - glDrawArrays(gl_mode, static_cast(offset), static_cast(count)); -} - -void rasterizer::draw_arrays(drawing_mode mode, std::size_t offset, std::size_t count) -{ - GLenum gl_mode = drawing_mode_lut[static_cast(mode)]; - - if (bound_vao != dummy_vao.get()) - { - glBindVertexArray(dummy_vao->gl_array_id); - bound_vao = dummy_vao.get(); - } - - glDrawArrays(gl_mode, static_cast(offset), static_cast(count)); -} - -void rasterizer::draw_arrays_instanced(const vertex_array& vao, drawing_mode mode, std::size_t offset, std::size_t count, std::size_t instance_count) -{ - GLenum gl_mode = drawing_mode_lut[static_cast(mode)]; - - if (bound_vao != &vao) - { - glBindVertexArray(vao.gl_array_id); - bound_vao = &vao; - } - - glDrawArraysInstanced(gl_mode, static_cast(offset), static_cast(count), static_cast(instance_count)); -} - -void rasterizer::draw_elements(const vertex_array& vao, drawing_mode mode, std::size_t offset, std::size_t count, element_array_type type) -{ - GLenum gl_mode = drawing_mode_lut[static_cast(mode)]; - GLenum gl_type = element_array_type_lut[static_cast(type)]; - - if (bound_vao != &vao) - { - glBindVertexArray(vao.gl_array_id); - bound_vao = &vao; - } - - glDrawElements(gl_mode, static_cast(count), gl_type, reinterpret_cast(offset)); -} - -void rasterizer::draw_elements(drawing_mode mode, std::size_t offset, std::size_t count, element_array_type type) -{ - GLenum gl_mode = drawing_mode_lut[static_cast(mode)]; - GLenum gl_type = element_array_type_lut[static_cast(type)]; - - if (bound_vao != dummy_vao.get()) - { - glBindVertexArray(dummy_vao->gl_array_id); - bound_vao = dummy_vao.get(); - } - - glDrawElements(gl_mode, static_cast(count), gl_type, reinterpret_cast(offset)); -} - -} // namespace gl diff --git a/src/engine/gl/rasterizer.hpp b/src/engine/gl/rasterizer.hpp deleted file mode 100644 index 5ce1964..0000000 --- a/src/engine/gl/rasterizer.hpp +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_GL_RASTERIZER_HPP -#define ANTKEEPER_GL_RASTERIZER_HPP - -#include -#include -#include -#include - -namespace gl { - -class framebuffer; -class vertex_array; -class shader_program; - -/** - * Interface to the OpenGL state and drawing functions. - */ -class rasterizer -{ -public: - /** - * Creates a rasterizer. Warning: This must be called after an OpenGL context has been created. - */ - rasterizer(); - - /// Destroys a rasterizer. - ~rasterizer(); - - /** - * This should be called when the window associated with the OpenGL context is resized, and will effectively changed the reported dimensions of the default framebuffer. - */ - void context_resized(int width, int height); - - /** - * Sets the active framebuffer. - * - * @param framebuffer Framebuffer to use. - */ - void use_framebuffer(const framebuffer& framebuffer); - - /** - * Sets the color to be used when the color buffer of a framebuffer is cleared. - * - * @param r Red color component. - * @param g Green color component. - * @param b Blue color component. - * @param a Alpha color component. - */ - void set_clear_color(float r, float g, float b, float a); - - /** - * Sets the depth value to be used when the depth buffer of a framebuffer is cleared. - * - * @param depth Depth value. - */ - void set_clear_depth(float depth); - - /** - * Sets the stencil value to be used when the stencil buffer of a framebuffer is cleared. - * - * @param s Stencil value. - */ - void set_clear_stencil(int s); - - /** - * Clears the buffers attached to a framebuffer. - * - * @param color Specifies whether the color buffer should be cleared. - * @param depth Specifies whether the depth buffer should be cleared. - * @param stencil Specifies whether the stencil buffer should be cleared. - */ - void clear_framebuffer(bool color, bool depth, bool stencil); - - /** - * Sets the active viewport. - * - * @param x X-coordinate of the viewport. - * @param y Y-coordinate of the viewport. - * @param width Width of the viewport. - * @param height Height of the viewport. - */ - void set_viewport(int x, int y, int width, int height); - - /** - * Binds a shader program. - * - * @param program Shader program to bind. - */ - void use_program(const shader_program& program); - - /** - * - */ - void draw_arrays(const vertex_array& vao, drawing_mode mode, std::size_t offset, std::size_t count); - void draw_arrays(drawing_mode mode, std::size_t offset, std::size_t count); - - void draw_arrays_instanced(const vertex_array& vao, drawing_mode mode, std::size_t offset, std::size_t count, std::size_t instance_count); - - /** - * - */ - void draw_elements(const vertex_array& vao, drawing_mode mode, std::size_t offset, std::size_t count, element_array_type type); - void draw_elements(drawing_mode mode, std::size_t offset, std::size_t count, element_array_type type); - - /** - * Returns the default framebuffer associated with the OpenGL context of a window. - */ - [[nodiscard]] inline const framebuffer& get_default_framebuffer() const noexcept - { - return *default_framebuffer; - } - -private: - std::unique_ptr default_framebuffer; - std::unique_ptr dummy_vao; - const framebuffer* bound_framebuffer{nullptr}; - const vertex_array* bound_vao{nullptr}; - const shader_program* bound_shader_program{nullptr}; -}; - -} // namespace gl - -#endif // ANTKEEPER_GL_RASTERIZER_HPP diff --git a/src/engine/gl/sampler-address-mode.hpp b/src/engine/gl/sampler-address-mode.hpp new file mode 100644 index 0000000..1f93a25 --- /dev/null +++ b/src/engine/gl/sampler-address-mode.hpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_SAMPLER_ADDRESS_MODE_HPP +#define ANTKEEPER_GL_SAMPLER_ADDRESS_MODE_HPP + +#include + +namespace gl { + +/// Behaviors of sampling with texture coordinates outside an image. +enum class sampler_address_mode: std::uint8_t +{ + /// Repeat wrap mode. + repeat, + + /// Mirrored repeat wrap mode. + mirrored_repeat, + + /// Clamp to edge wrap mode. + clamp_to_edge, + + /// Clamp to border wrap mode. + clamp_to_border, + + /// Mirror clamp to edge wrap mode. + mirror_clamp_to_edge +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_SAMPLER_ADDRESS_MODE_HPP diff --git a/src/engine/gl/texture-type.hpp b/src/engine/gl/sampler-filter.hpp similarity index 73% rename from src/engine/gl/texture-type.hpp rename to src/engine/gl/sampler-filter.hpp index 71320c9..922b0e9 100644 --- a/src/engine/gl/texture-type.hpp +++ b/src/engine/gl/sampler-filter.hpp @@ -17,29 +17,23 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GL_TEXTURE_TYPE_HPP -#define ANTKEEPER_GL_TEXTURE_TYPE_HPP +#ifndef ANTKEEPER_GL_SAMPLER_FILTER_HPP +#define ANTKEEPER_GL_SAMPLER_FILTER_HPP #include namespace gl { -/// Texture types. -enum class texture_type: std::uint8_t +/// Filters used for texture lookups. +enum class sampler_filter: std::uint8_t { - /// 1D texture. - one_dimensional, + /// Nearest filtering. + nearest, - /// 2D texture. - two_dimensional, - - /// 3D texture. - three_dimensional, - - /// Cube texture. - cube, + /// Linear filtering. + linear }; } // namespace gl -#endif // ANTKEEPER_GL_TEXTURE_TYPE_HPP +#endif // ANTKEEPER_GL_SAMPLER_FILTER_HPP diff --git a/src/engine/gl/sampler-mipmap-mode.hpp b/src/engine/gl/sampler-mipmap-mode.hpp new file mode 100644 index 0000000..f162c9d --- /dev/null +++ b/src/engine/gl/sampler-mipmap-mode.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_SAMPLER_MIPMAP_MODE_HPP +#define ANTKEEPER_GL_SAMPLER_MIPMAP_MODE_HPP + +#include + +namespace gl { + +/// Mipmap modes used for texture lookups. +enum class sampler_mipmap_mode: std::uint8_t +{ + /// Nearest filtering. + nearest, + + /// Linear filtering. + linear +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_SAMPLER_MIPMAP_MODE_HPP diff --git a/src/engine/gl/sampler.cpp b/src/engine/gl/sampler.cpp new file mode 100644 index 0000000..c10105c --- /dev/null +++ b/src/engine/gl/sampler.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include +#include + +namespace gl { + +namespace +{ + static constexpr GLenum mag_filter_lut[] = + { + GL_NEAREST, // sampler_filter::nearest + GL_LINEAR // sampler_filter::linear + }; + + static constexpr GLenum min_filter_lut[][2] = + { + { + GL_NEAREST_MIPMAP_NEAREST, // sampler_filter::nearest, sampler_mipmap_mode::nearest + GL_NEAREST_MIPMAP_LINEAR // sampler_filter::nearest, sampler_mipmap_mode::linear + }, + { + GL_LINEAR_MIPMAP_NEAREST, // sampler_filter::linear, sampler_mipmap_mode::nearest + GL_LINEAR_MIPMAP_LINEAR // sampler_filter::linear, sampler_mipmap_mode::linear + }, + }; + + static constexpr GLenum wrap_lut[] = + { + GL_REPEAT, // sampler_address_mode::repeat + GL_MIRRORED_REPEAT, // sampler_address_mode::mirrored_repeat + GL_CLAMP_TO_EDGE, // sampler_address_mode::clamp_to_edge + GL_CLAMP_TO_BORDER, // sampler_address_mode::clamp_to_border + GL_MIRROR_CLAMP_TO_EDGE // sampler_address_mode::mirror_clamp_to_edge + }; + + static constexpr GLenum compare_func_lut[] = + { + GL_NEVER, // compare_op::never + GL_LESS, // compare_op::less + GL_EQUAL, // compare_op::equal + GL_LEQUAL, // compare_op::less_or_equal + GL_GREATER, // compare_op::greater + GL_NOTEQUAL, // compare_op::not_equal + GL_GEQUAL, // compare_op::greater_or_equal + GL_ALWAYS // compare_op::always + }; +} + +sampler::sampler +( + sampler_filter mag_filter, + sampler_filter min_filter, + sampler_mipmap_mode mipmap_mode, + sampler_address_mode address_mode_u, + sampler_address_mode address_mode_v, + sampler_address_mode address_mode_w, + float mip_lod_bias, + float max_anisotropy, + bool compare_enabled, + gl::compare_op compare_op, + float min_lod, + float max_lod, + const std::array& border_color +) +{ + glCreateSamplers(1, &m_gl_named_sampler); + + set_mag_filter(mag_filter); + set_min_filter(min_filter); + set_mipmap_mode(mipmap_mode); + set_address_mode_u(address_mode_u); + set_address_mode_v(address_mode_v); + set_address_mode_w(address_mode_w); + set_mip_lod_bias(mip_lod_bias); + set_max_anisotropy(max_anisotropy); + set_compare_enabled(compare_enabled); + set_compare_op(compare_op); + set_min_lod(min_lod); + set_max_lod(max_lod); + set_border_color(border_color); +} + +sampler::~sampler() +{ + glDeleteSamplers(1, &m_gl_named_sampler); +} + +void sampler::set_mag_filter(sampler_filter filter) +{ + if (m_mag_filter != filter) + { + m_mag_filter = filter; + const auto gl_mag_filter = mag_filter_lut[std::to_underlying(m_mag_filter)]; + glSamplerParameteri(m_gl_named_sampler, GL_TEXTURE_MAG_FILTER, gl_mag_filter); + } +} + +void sampler::set_min_filter(sampler_filter filter) +{ + if (m_min_filter != filter) + { + m_min_filter = filter; + const auto gl_min_filter = min_filter_lut[std::to_underlying(m_min_filter)][std::to_underlying(m_mipmap_mode)]; + glSamplerParameteri(m_gl_named_sampler, GL_TEXTURE_MIN_FILTER, gl_min_filter); + } +} + +void sampler::set_mipmap_mode(sampler_mipmap_mode mode) +{ + if (m_mipmap_mode != mode) + { + m_mipmap_mode = mode; + const auto gl_min_filter = min_filter_lut[std::to_underlying(m_min_filter)][std::to_underlying(m_mipmap_mode)]; + glSamplerParameteri(m_gl_named_sampler, GL_TEXTURE_MIN_FILTER, gl_min_filter); + } +} + +void sampler::set_address_mode_u(sampler_address_mode mode) +{ + if (m_address_mode_u != mode) + { + m_address_mode_u = mode; + const auto gl_wrap_s = wrap_lut[std::to_underlying(m_address_mode_u)]; + glSamplerParameteri(m_gl_named_sampler, GL_TEXTURE_WRAP_S, gl_wrap_s); + } +} + +void sampler::set_address_mode_v(sampler_address_mode mode) +{ + if (m_address_mode_v != mode) + { + m_address_mode_v = mode; + const auto gl_wrap_t = wrap_lut[std::to_underlying(m_address_mode_v)]; + glSamplerParameteri(m_gl_named_sampler, GL_TEXTURE_WRAP_T, gl_wrap_t); + } +} + +void sampler::set_address_mode_w(sampler_address_mode mode) +{ + if (m_address_mode_w != mode) + { + m_address_mode_w = mode; + const auto gl_wrap_r = wrap_lut[std::to_underlying(m_address_mode_w)]; + glSamplerParameteri(m_gl_named_sampler, GL_TEXTURE_WRAP_R, gl_wrap_r); + } +} + +void sampler::set_mip_lod_bias(float bias) +{ + if (m_mip_lod_bias != bias) + { + m_mip_lod_bias = bias; + glSamplerParameterf(m_gl_named_sampler, GL_TEXTURE_LOD_BIAS, m_mip_lod_bias); + } +} + +void sampler::set_max_anisotropy(float anisotropy) +{ + if (m_max_anisotropy != anisotropy) + { + m_max_anisotropy = anisotropy; + glSamplerParameterf(m_gl_named_sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, m_max_anisotropy); + } +} + +void sampler::set_compare_enabled(bool enabled) +{ + if (m_compare_enabled != enabled) + { + m_compare_enabled = enabled; + glSamplerParameteri(m_gl_named_sampler, GL_TEXTURE_COMPARE_MODE, (m_compare_enabled) ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE); + } +} + +void sampler::set_compare_op(gl::compare_op op) +{ + if (m_compare_op != op) + { + m_compare_op = op; + const auto gl_compare_func = compare_func_lut[std::to_underlying(m_compare_op)]; + glSamplerParameteri(m_gl_named_sampler, GL_TEXTURE_COMPARE_FUNC, gl_compare_func); + } +} + +void sampler::set_min_lod(float lod) +{ + if (m_min_lod != lod) + { + m_min_lod = lod; + glSamplerParameterf(m_gl_named_sampler, GL_TEXTURE_MIN_LOD, m_min_lod); + } +} + +void sampler::set_max_lod(float lod) +{ + if (m_max_lod != lod) + { + m_max_lod = lod; + glSamplerParameterf(m_gl_named_sampler, GL_TEXTURE_MAX_LOD, m_max_lod); + } +} + +void sampler::set_border_color(const std::array& color) +{ + if (m_border_color != color) + { + m_border_color = color; + glSamplerParameterfv(m_gl_named_sampler, GL_TEXTURE_BORDER_COLOR, m_border_color.data()); + } +} + +} // namespace gl diff --git a/src/engine/gl/sampler.hpp b/src/engine/gl/sampler.hpp new file mode 100644 index 0000000..8c1f310 --- /dev/null +++ b/src/engine/gl/sampler.hpp @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_SAMPLER_HPP +#define ANTKEEPER_GL_SAMPLER_HPP + +#include +#include +#include +#include +#include + +namespace gl { + +/** + * Sampler object. + */ +class sampler +{ +public: + /** + * Constructs a sampler object. + * + * @param mag_filter Magnification filter to apply to lookups. + * @param min_filter Minification filter to apply to lookups. + * @param mipmap_mode Mipmap filter to apply to lookups. + * @param address_mode_u Addressing mode for U-coordinates outside `[0, 1)`. + * @param address_mode_v Addressing mode for V-coordinates outside `[0, 1)`. + * @param address_mode_w Addressing mode for W-coordinates outside `[0, 1)`. + * @param mip_lod_bias Bias to be added to mipmap LOD calculation. + * @param max_anisotropy Anisotropy clamp value. + * @param compare_enabled `true` to enable comparison against a reference value during lookups, `false` otherwise. + * @param compare_op Comparison operator to apply to fetched data, if compare is enabled. + * @param min_lod Minimum clamp value of the computed LOD. + * @param max_lod Maximum clamp value of the computed LOD. + * @param border_color Border color used for texture lookups. + */ + explicit sampler + ( + sampler_filter mag_filter = sampler_filter::linear, + sampler_filter min_filter = sampler_filter::nearest, + sampler_mipmap_mode mipmap_mode = sampler_mipmap_mode::linear, + sampler_address_mode address_mode_u = sampler_address_mode::repeat, + sampler_address_mode address_mode_v = sampler_address_mode::repeat, + sampler_address_mode address_mode_w = sampler_address_mode::repeat, + float mip_lod_bias = 0.0f, + float max_anisotropy = 0.0f, + bool compare_enabled = false, + gl::compare_op compare_op = gl::compare_op::less, + float min_lod = -1000.0f, + float max_lod = 1000.0f, + const std::array& border_color = {0.0f, 0.0f, 0.0f, 0.0f} + ); + + /// Destroys a sampler object. + ~sampler(); + + sampler(const sampler&) = delete; + sampler(sampler&&) = delete; + sampler& operator=(const sampler&) = delete; + sampler& operator=(sampler&&) = delete; + + /** + * Sets the magnification filter to apply to lookups. + * + * @param filter Magnification filter to apply to lookups. + */ + void set_mag_filter(sampler_filter filter); + + /** + * Sets the minification filter to apply to lookups. + * + * @param filter Minification filter to apply to lookups. + */ + void set_min_filter(sampler_filter filter); + + /** + * Sets the mipmap filter to apply to lookups. + * + * @param mode Mipmap filter to apply to lookups. + */ + void set_mipmap_mode(sampler_mipmap_mode mode); + + /** + * Sets the addressing mode for U-coordinates outside `[0, 1)`. + * + * @param Addressing mode for U-coordinates outside `[0, 1)`. + */ + void set_address_mode_u(sampler_address_mode mode); + + /** + * Sets the addressing mode for V-coordinates outside `[0, 1)`. + * + * @param Addressing mode for V-coordinates outside `[0, 1)`. + */ + void set_address_mode_v(sampler_address_mode mode); + + /** + * Sets the addressing mode for W-coordinates outside `[0, 1)`. + * + * @param Addressing mode for W-coordinates outside `[0, 1)`. + */ + void set_address_mode_w(sampler_address_mode mode); + + /** + * Sets the bias to be added to mipmap LOD calculation. + * + * @param bias Bias to be added to mipmap LOD calculation. + */ + void set_mip_lod_bias(float bias); + + /** + * Sets the anisotropy clamp value. + * + * @param anisotropy Anisotropy clamp value. + */ + void set_max_anisotropy(float anisotropy); + + /** + * Enables or disables a comparison against a reference value during lookups. + * + * @param enabled `true` to enable comparison against a reference value during lookups, `false` otherwise. + */ + void set_compare_enabled(bool enabled); + + /** + * Sets the comparison operator to apply to fetched data, if compare is enabled. + * + * @param op Comparison operator to apply to fetched data, if compare is enabled. + */ + void set_compare_op(gl::compare_op op); + + /** + * Sets the minimum clamp value of the computed LOD. + * + * @param lod Minimum clamp value of the computed LOD. + */ + void set_min_lod(float lod); + + /** + * Sets the maximum clamp value of the computed LOD. + * + * @param lod Maximum clamp value of the computed LOD. + */ + void set_max_lod(float lod); + + /** + * Sets the border color used for texture lookups. + * + * @param color Border color used for texture lookups. + */ + void set_border_color(const std::array& color); + + /// Returns the magnification filter to apply to lookups. + [[nodiscard]] inline constexpr sampler_filter get_mag_filter() const noexcept + { + return m_mag_filter; + } + + /// Returns the minification filter to apply to lookups. + [[nodiscard]] inline constexpr sampler_filter get_min_filter() const noexcept + { + return m_min_filter; + } + + /// Returns the mipmap filter to apply to lookups. + [[nodiscard]] inline constexpr sampler_mipmap_mode get_mipmap_mode() const noexcept + { + return m_mipmap_mode; + } + + /// Returns the addressing mode for U-coordinates outside `[0, 1)`. + [[nodiscard]] inline constexpr sampler_address_mode get_address_mode_u() const noexcept + { + return m_address_mode_u; + } + + /// Returns the addressing mode for V-coordinates outside `[0, 1)`. + [[nodiscard]] inline constexpr sampler_address_mode get_address_mode_v() const noexcept + { + return m_address_mode_v; + } + + /// Returns the addressing mode for W-coordinates outside `[0, 1)`. + [[nodiscard]] inline constexpr sampler_address_mode get_address_mode_w() const noexcept + { + return m_address_mode_w; + } + + /// Returns the bias to be added to mipmap LOD calculation. + [[nodiscard]] inline constexpr float get_mip_lod_bias() const noexcept + { + return m_mip_lod_bias; + } + + /// Returns the anisotropy clamp value. + [[nodiscard]] inline constexpr float get_max_anisotropy() const noexcept + { + return m_max_anisotropy; + } + + /// Returns `true` if comparison against a reference value during lookups is enabled, `false` otherwise. + [[nodiscard]] inline constexpr float get_compare_enabled() const noexcept + { + return m_compare_enabled; + } + + /// Returns the comparison operator to apply to fetched data, if compare is enabled. + [[nodiscard]] inline constexpr compare_op get_compare_op() const noexcept + { + return m_compare_op; + } + + /// Returns the minimum clamp value of the computed LOD. + [[nodiscard]] inline constexpr float get_min_lod() const noexcept + { + return m_min_lod; + } + + /// Returns the maximum clamp value of the computed LOD. + [[nodiscard]] inline constexpr float get_max_lod() const noexcept + { + return m_max_lod; + } + + /// Returns the border color used for texture lookups. + [[nodiscard]] inline constexpr const std::array& get_border_color() const noexcept + { + return m_border_color; + } + +private: + friend class pipeline; + friend class gl_shader_texture_1d; + friend class gl_shader_texture_2d; + friend class gl_shader_texture_3d; + friend class gl_shader_texture_cube; + + unsigned int m_gl_named_sampler{0}; + + sampler_filter m_mag_filter{sampler_filter::linear}; + sampler_filter m_min_filter{sampler_filter::nearest}; + sampler_mipmap_mode m_mipmap_mode{sampler_mipmap_mode::linear}; + sampler_address_mode m_address_mode_u{sampler_address_mode::repeat}; + sampler_address_mode m_address_mode_v{sampler_address_mode::repeat}; + sampler_address_mode m_address_mode_w{sampler_address_mode::repeat}; + float m_mip_lod_bias{0.0f}; + float m_max_anisotropy{0.0f}; + bool m_compare_enabled{false}; + gl::compare_op m_compare_op{gl::compare_op::less}; + float m_min_lod{-1000.0f}; + float m_max_lod{1000.0f}; + std::array m_border_color{0.0f, 0.0f, 0.0f, 0.0f}; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_SAMPLER_HPP diff --git a/src/engine/gl/texture-filter.hpp b/src/engine/gl/scissor-region.hpp similarity index 66% rename from src/engine/gl/texture-filter.hpp rename to src/engine/gl/scissor-region.hpp index 43a2c21..2f7eecd 100644 --- a/src/engine/gl/texture-filter.hpp +++ b/src/engine/gl/scissor-region.hpp @@ -17,35 +17,31 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GL_TEXTURE_FILTER_HPP -#define ANTKEEPER_GL_TEXTURE_FILTER_HPP +#ifndef ANTKEEPER_GL_SCISSOR_REGION_HPP +#define ANTKEEPER_GL_SCISSOR_REGION_HPP #include namespace gl { /** - * Texture minification filter modes. + * Scissor region offset and extents. */ -enum class texture_min_filter: std::uint8_t +struct scissor_region { - nearest, - linear, - nearest_mipmap_nearest, - linear_mipmap_nearest, - nearest_mipmap_linear, - linear_mipmap_linear -}; - -/** - * Texture magnification filter modes. - */ -enum class texture_mag_filter: std::uint8_t -{ - nearest, - linear + /// X-coordinate offset of the scissor region. + std::int32_t x{}; + + /// Y-coordinate offset of the scissor region. + std::int32_t y{}; + + /// Width of the scissor region. + std::uint32_t width; + + /// Height of the scissor region. + std::uint32_t height; }; } // namespace gl -#endif // ANTKEEPER_GL_TEXTURE_FILTER_HPP +#endif // ANTKEEPER_GL_SCISSOR_REGION_HPP diff --git a/src/engine/gl/shader-object.cpp b/src/engine/gl/shader-object.cpp index 81c01c9..c5c1933 100644 --- a/src/engine/gl/shader-object.cpp +++ b/src/engine/gl/shader-object.cpp @@ -18,7 +18,7 @@ */ #include -#include +#include #include namespace gl { @@ -55,18 +55,6 @@ void shader_object::source(std::string_view source_code) const GLint gl_length = static_cast(source_code.length()); const GLchar* gl_string = source_code.data(); glShaderSource(gl_shader_id, 1, &gl_string, &gl_length); - - // Handle OpenGL errors - switch (glGetError()) - { - case GL_INVALID_VALUE: - throw std::runtime_error("OpenGL shader object handle is not a value generated by OpenGL."); - break; - - case GL_INVALID_OPERATION: - throw std::runtime_error("OpenGL shader object handle is not a shader object."); - break; - } } bool shader_object::compile() @@ -77,18 +65,6 @@ bool shader_object::compile() // Compile OpenGL shader object glCompileShader(gl_shader_id); - // Handle OpenGL errors - switch (glGetError()) - { - case GL_INVALID_VALUE: - throw std::runtime_error("OpenGL shader object handle is not a value generated by OpenGL."); - break; - - case GL_INVALID_OPERATION: - throw std::runtime_error("OpenGL shader object handle is not a shader object."); - break; - } - // Get OpenGL shader object compilation status GLint gl_compile_status; glGetShaderiv(gl_shader_id, GL_COMPILE_STATUS, &gl_compile_status); diff --git a/src/engine/gl/shader-program.cpp b/src/engine/gl/shader-program.cpp index ed2fdef..f226ab2 100644 --- a/src/engine/gl/shader-program.cpp +++ b/src/engine/gl/shader-program.cpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include @@ -68,14 +68,6 @@ void shader_program::attach(const shader_object& object) // Attach the OpenGL shader object to the OpenGL shader program glAttachShader(gl_program_id, object.gl_shader_id); - // Handle OpenGL errors - switch (glGetError()) - { - case GL_INVALID_OPERATION: - throw std::runtime_error("OpenGL shader object is already attached to the shader program."); - break; - } - // Add shader object to set of attached objects attached_objects.insert(&object); } @@ -102,14 +94,6 @@ void shader_program::detach(const shader_object& object) // Detach the OpenGL shader object from the OpenGL shader program glDetachShader(gl_program_id, object.gl_shader_id); - // Handle OpenGL errors - switch (glGetError()) - { - case GL_INVALID_OPERATION: - throw std::runtime_error("OpenGL shader object is not attached to the shader program."); - break; - } - // Remove shader object from set of attached objects attached_objects.erase(&object); } @@ -138,14 +122,6 @@ bool shader_program::link() // Link OpenGL shader program glLinkProgram(gl_program_id); - // Handle OpenGL errors - switch (glGetError()) - { - case GL_INVALID_OPERATION: - throw std::runtime_error("OpenGL shader program is the currently active program object and transform feedback mode is active."); - break; - } - // Get OpenGL shader program linking status GLint gl_link_status; glGetProgramiv(gl_program_id, GL_LINK_STATUS, &gl_link_status); diff --git a/src/engine/gl/shader-program.hpp b/src/engine/gl/shader-program.hpp index 662d68c..4c26c1f 100644 --- a/src/engine/gl/shader-program.hpp +++ b/src/engine/gl/shader-program.hpp @@ -30,7 +30,6 @@ namespace gl { class shader_object; -class rasterizer; class shader_variable; /** @@ -149,7 +148,7 @@ public: } private: - friend class rasterizer; + friend class pipeline; void load_variables(); diff --git a/src/engine/gl/shader-template.cpp b/src/engine/gl/shader-template.cpp index f6c152d..da04776 100644 --- a/src/engine/gl/shader-template.cpp +++ b/src/engine/gl/shader-template.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -126,7 +126,7 @@ std::unique_ptr shader_template::build(const dictionary_type if (has_geometry_directive()) { - // Compile fragment shader object and attach to shader program + // Compile geometry shader object and attach to shader program geometry_object = compile(gl::shader_stage::geometry, definitions); program->attach(*geometry_object); } @@ -189,7 +189,7 @@ void shader_template::rehash() m_hash = 0; for (const auto& line: m_template_source.lines) { - m_hash = hash::combine(m_hash, std::hash{}(line)); + m_hash = hash_combine(m_hash, std::hash{}(line)); } } diff --git a/src/engine/gl/stencil-face-bits.hpp b/src/engine/gl/stencil-face-bits.hpp new file mode 100644 index 0000000..21d1e53 --- /dev/null +++ b/src/engine/gl/stencil-face-bits.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_STENCIL_FACE_BITS_HPP +#define ANTKEEPER_GL_STENCIL_FACE_BITS_HPP + +#include + +namespace gl { + +/// Stencil face mask bits specifying which stencil state(s) to update. +enum: std::uint8_t +{ + /// Only the front set of stencil state is updated. + stencil_face_front_bit = 0b01, + + /// Only the back set of stencil state is updated. + stencil_face_back_bit = 0b10, + + /// Both sets of stencil state are updated. + stencil_face_front_and_back = 0b11, +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_STENCIL_FACE_BITS_HPP diff --git a/src/engine/gl/stencil-op-state.hpp b/src/engine/gl/stencil-op-state.hpp new file mode 100644 index 0000000..4b6946d --- /dev/null +++ b/src/engine/gl/stencil-op-state.hpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_STENCIL_OP_STATE_HPP +#define ANTKEEPER_GL_STENCIL_OP_STATE_HPP + +#include +#include +#include + +namespace gl { + +/// Stencil operation state. +struct stencil_op_state +{ + /// Action performed on samples that fail the stencil test. + stencil_op fail_op{stencil_op::keep}; + + /// Action performed on samples that pass both the depth and stencil tests. + stencil_op pass_op{stencil_op::keep}; + + /// Action performed on samples that pass the stencil test and fail the depth test. + stencil_op depth_fail_op{stencil_op::keep}; + + /// Comparison operator used in the stencil test. + gl::compare_op compare_op{gl::compare_op::always}; + + /// Bits of the unsigned integer stencil values participating in the stencil test. + std::uint32_t compare_mask{0xffffffff}; + + /// Bits of the unsigned integer stencil values updated by the stencil test in the stencil framebuffer attachment. + std::uint32_t write_mask{0xffffffff}; + + /// Stencil reference value that is used in the unsigned stencil comparison. + std::uint32_t reference{0}; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_STENCIL_OP_STATE_HPP diff --git a/src/engine/gl/stencil-op.hpp b/src/engine/gl/stencil-op.hpp new file mode 100644 index 0000000..d498cfc --- /dev/null +++ b/src/engine/gl/stencil-op.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_STENCIL_OP_HPP +#define ANTKEEPER_GL_STENCIL_OP_HPP + +#include + +namespace gl { + +/// Stencil comparison functions. +enum class stencil_op: std::uint8_t +{ + /// Keeps the current value. + keep, + + /// Sets the value to `0`. + zero, + + /// Sets the value to `reference`. + replace, + + /// Increments the current value and clamps to the maximum representable unsigned value. + increment_and_clamp, + + /// Decrements the current value and clamps to `0`. + decrement_and_clamp, + + /// Bitwise-inverts the current value. + invert, + + /// Increments the current value and wraps to `0` when the maximum value would have been exceeded. + increment_and_wrap, + + /// Decrements the current value and wraps to the maximum possible value when the value would go below `0`. + decrement_and_wrap +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_STENCIL_OP_HPP diff --git a/src/engine/gl/texture-1d.cpp b/src/engine/gl/texture-1d.cpp deleted file mode 100644 index 23d77ed..0000000 --- a/src/engine/gl/texture-1d.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include - -namespace gl { - -texture_1d::texture_1d(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data): - texture(width, false, type, format, transfer_function, data) -{} - -void texture_1d::resize(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) -{ - texture::resize(width, type, format, transfer_function, data); -} - -void texture_1d::set_wrapping(gl::texture_wrapping wrap_s) -{ - texture::set_wrapping(wrap_s); -} - -} // namespace gl diff --git a/src/engine/gl/texture-1d.hpp b/src/engine/gl/texture-1d.hpp deleted file mode 100644 index 66d0d84..0000000 --- a/src/engine/gl/texture-1d.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_GL_TEXTURE_1D_HPP -#define ANTKEEPER_GL_TEXTURE_1D_HPP - -#include - -namespace gl { - -/** - * A 1D texture which can be uploaded to shaders via shader inputs. - */ -class texture_1d: public texture -{ -public: - explicit texture_1d(std::uint16_t width, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); - - [[nodiscard]] inline constexpr texture_type get_texture_type() const noexcept override - { - return texture_type::one_dimensional; - } - - /// @copydoc texture::resize(std::uint16_t, gl::pixel_type, gl::pixel_format, gl::transfer_function, const std::byte*) - virtual void resize(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data); - - /// @copydoc texture::set_wrapping(gl::texture_wrapping) - virtual void set_wrapping(gl::texture_wrapping wrap_s); -}; - -} // namespace gl - -#endif // ANTKEEPER_GL_TEXTURE_1D_HPP diff --git a/src/engine/gl/texture-2d.cpp b/src/engine/gl/texture-2d.cpp deleted file mode 100644 index 1c3ab79..0000000 --- a/src/engine/gl/texture-2d.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include - -namespace gl { - -texture_2d::texture_2d(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data): - texture(width, height, false, type, format, transfer_function, data) -{} - -void texture_2d::resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) -{ - texture::resize(width, height, type, format, transfer_function, data); -} - -void texture_2d::resize(std::uint16_t width, std::uint16_t height, const std::byte* data) -{ - texture::resize(width, height, get_pixel_type(), get_pixel_format(), get_transfer_function(), data); -} - -void texture_2d::set_wrapping(gl::texture_wrapping wrap_s, texture_wrapping wrap_t) -{ - texture::set_wrapping(wrap_s, wrap_t); -} - -} // namespace gl diff --git a/src/engine/gl/texture-2d.hpp b/src/engine/gl/texture-2d.hpp deleted file mode 100644 index 3dba229..0000000 --- a/src/engine/gl/texture-2d.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_GL_TEXTURE_2D_HPP -#define ANTKEEPER_GL_TEXTURE_2D_HPP - -#include - -namespace gl { - -/** - * A 2D texture which can be uploaded to shaders via shader inputs. - */ -class texture_2d: public texture -{ -public: - texture_2d(std::uint16_t width, std::uint16_t height, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); - - [[nodiscard]] inline constexpr texture_type get_texture_type() const noexcept override - { - return texture_type::two_dimensional; - } - - /// @copydoc texture::resize(std::uint16_t, std::uint16_t, gl::pixel_type, gl::pixel_format, gl::transfer_function, const std::byte*) - void resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) override; - - /** - * Resizes the texture. - * - * @param width Texture width, in pixels. - * @param height Texture height, in pixels. - * @param data Pointer to pixel data. - */ - void resize(std::uint16_t width, std::uint16_t height, const std::byte* data); - - /// @copydoc texture::set_wrapping(gl::texture_wrapping, gl::texture_wrapping) - void set_wrapping(gl::texture_wrapping wrap_s, texture_wrapping wrap_t) override; -}; - -} // namespace gl - -#endif // ANTKEEPER_GL_TEXTURE_2D_HPP diff --git a/src/engine/gl/texture-3d.cpp b/src/engine/gl/texture-3d.cpp deleted file mode 100644 index fda2957..0000000 --- a/src/engine/gl/texture-3d.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include - -namespace gl { - -texture_3d::texture_3d(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data): - texture(width, height, depth, false, type, format, transfer_function, data) -{} - -void texture_3d::resize(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) -{ - texture::resize(width, height, depth, type, format, transfer_function, data); -} - -void texture_3d::set_wrapping(gl::texture_wrapping wrap_s, texture_wrapping wrap_t, texture_wrapping wrap_r) -{ - texture::set_wrapping(wrap_s, wrap_t, wrap_r); -} - -} // namespace gl diff --git a/src/engine/gl/texture-3d.hpp b/src/engine/gl/texture-3d.hpp deleted file mode 100644 index 0c24830..0000000 --- a/src/engine/gl/texture-3d.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_GL_TEXTURE_3D_HPP -#define ANTKEEPER_GL_TEXTURE_3D_HPP - -#include - -namespace gl { - -/** - * A 3D texture which can be uploaded to shaders via shader inputs. - */ -class texture_3d: public texture -{ -public: - texture_3d(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); - - [[nodiscard]] inline constexpr texture_type get_texture_type() const noexcept override - { - return texture_type::three_dimensional; - } - - /// @copydoc texture::resize(std::uint16_t, std::uint16_t, std::uint16_t, gl::pixel_type, gl::pixel_format, gl::transfer_function, const std::byte*) - virtual void resize(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data); - - /// @copydoc texture::set_wrapping(gl::texture_wrapping, gl::texture_wrapping, gl::texture_wrapping) - virtual void set_wrapping(gl::texture_wrapping wrap_s, texture_wrapping wrap_t, texture_wrapping wrap_r); -}; - -} // namespace gl - -#endif // ANTKEEPER_GL_TEXTURE_3D_HPP diff --git a/src/engine/gl/texture-cube.cpp b/src/engine/gl/texture-cube.cpp deleted file mode 100644 index 14ba5a8..0000000 --- a/src/engine/gl/texture-cube.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include -#include - -namespace gl { - -cube_map_layout texture_cube::infer_cube_map_layout(std::uint16_t w, std::uint16_t h) noexcept -{ - if (h == w * 6) - { - return cube_map_layout::column; - } - else if (w == h * 6) - { - return cube_map_layout::row; - } - else if (w == (h / 4) * 3) - { - return cube_map_layout::vertical_cross; - } - else if (h == (w / 4) * 3) - { - return cube_map_layout::horizontal_cross; - } - else if (w == h * 2) - { - return cube_map_layout::equirectangular; - } - else if (w == h) - { - return cube_map_layout::spherical; - } - - return {}; -} - -std::uint16_t texture_cube::infer_cube_map_face_size(cube_map_layout layout, std::uint16_t w, std::uint16_t h) noexcept -{ - switch (layout) - { - case cube_map_layout::column: - case cube_map_layout::spherical: - return w; - - case cube_map_layout::row: - return h; - - case cube_map_layout::vertical_cross: - return h / 4; - - case cube_map_layout::horizontal_cross: - case cube_map_layout::equirectangular: - return w / 4; - - default: - return 0; - } -} - -texture_cube::texture_cube(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data): - texture(width, height, true, type, format, transfer_function, data) -{ - resized(); -} - -void texture_cube::resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) -{ - texture::resize(width, height, type, format, transfer_function, data); - resized(); -} - -void texture_cube::resize(std::uint16_t width, std::uint16_t height, const std::byte* data) -{ - texture::resize(width, height, get_pixel_type(), get_pixel_format(), get_transfer_function(), data); - resized(); -} - -void texture_cube::set_wrapping(gl::texture_wrapping wrap_s, texture_wrapping wrap_t, texture_wrapping wrap_r) -{ - texture::set_wrapping(wrap_s, wrap_t, wrap_r); -} - -void texture_cube::resized() -{ - const auto w = get_width(); - const auto h = get_height(); - const auto layout = infer_cube_map_layout(w, h); - m_face_size = infer_cube_map_face_size(layout, w, h); - m_mip_count = 1 + static_cast(std::log2(m_face_size)); -} - -} // namespace gl diff --git a/src/engine/gl/texture-cube.hpp b/src/engine/gl/texture-cube.hpp deleted file mode 100644 index 5cfe75a..0000000 --- a/src/engine/gl/texture-cube.hpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_GL_TEXTURE_CUBE_HPP -#define ANTKEEPER_GL_TEXTURE_CUBE_HPP - -#include -#include - -namespace gl { - -/** - * A cube texture which can be uploaded to shaders via shader inputs. - */ -class texture_cube: public texture -{ -public: - /** - * Infers the layout of a cube map from its aspect ratio. - * - * @param w Width of the cube map, in pixels. - * @param h Height of the cube map, in pixels. - * - * @return Inferred cube map layout. - */ - [[nodiscard]] static cube_map_layout infer_cube_map_layout(std::uint16_t w, std::uint16_t h) noexcept; - - /** - * Infers the edge length of a cube map face from its layout and resolution. - * - * @param layout Layout of the cube map. - * @param w Width of the cube map, in pixels. - * @param h Height of the cube map, in pixels. - * - * @return Edge length of the cube map faces, in pixels. - */ - [[nodiscard]] static std::uint16_t infer_cube_map_face_size(cube_map_layout layout, std::uint16_t w, std::uint16_t h) noexcept; - - /// Constructs a cube texture. - texture_cube(std::uint16_t width, std::uint16_t height, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); - - [[nodiscard]] inline constexpr texture_type get_texture_type() const noexcept override - { - return texture_type::cube; - } - - /// @copydoc texture::resize(std::uint16_t, std::uint16_t, gl::pixel_type, gl::pixel_format, gl::transfer_function, const std::byte*) - void resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) override; - - /** - * Resizes the texture. - * - * @param width Texture width, in pixels. - * @param height Texture height, in pixels. - * @param data Pointer to pixel data. - */ - void resize(std::uint16_t width, std::uint16_t height, const std::byte* data); - - /// @copydoc texture::set_wrapping(gl::texture_wrapping, gl::texture_wrapping, gl::texture_wrapping) - virtual void set_wrapping(gl::texture_wrapping wrap_s, texture_wrapping wrap_t, texture_wrapping wrap_r); - - /// Returns the edge length of the cube texture faces, in pixels. - [[nodiscard]] inline std::uint16_t get_face_size() const noexcept - { - return m_face_size; - } - -private: - void resized(); - - std::uint16_t m_face_size{}; -}; - -} // namespace gl - -#endif // ANTKEEPER_GL_TEXTURE_CUBE_HPP diff --git a/src/engine/gl/texture.cpp b/src/engine/gl/texture.cpp index 28e9bbb..e494ac5 100644 --- a/src/engine/gl/texture.cpp +++ b/src/engine/gl/texture.cpp @@ -18,869 +18,239 @@ */ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include -#include #include #include -#include -#include -namespace gl { - -static constexpr GLenum pixel_format_lut[] = -{ - GL_DEPTH_COMPONENT, - GL_DEPTH_STENCIL, - GL_RED, - GL_RG, - GL_RGB, - GL_BGR, - GL_RGBA, - GL_BGRA -}; - -static constexpr GLenum pixel_type_lut[] = -{ - GL_BYTE, - GL_UNSIGNED_BYTE, - GL_SHORT, - GL_UNSIGNED_SHORT, - GL_INT, - GL_UNSIGNED_INT, - GL_HALF_FLOAT, - GL_FLOAT -}; - -static constexpr GLenum linear_internal_format_lut[][8] = -{ - {GL_NONE, GL_NONE, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT32, GL_NONE, GL_DEPTH_COMPONENT32F}, - - // Note: GL_DEPTH32F_STENCIL8 is actually a 64-bit format, 32 depth bits, 8 stencil bits, and 24 alignment bits. - {GL_NONE, GL_NONE, GL_NONE, GL_NONE, GL_DEPTH24_STENCIL8, GL_DEPTH24_STENCIL8, GL_NONE, GL_DEPTH32F_STENCIL8}, - - {GL_R8, GL_R8, GL_R16, GL_R16, GL_R32F, GL_R32F, GL_R16F, GL_R32F}, - {GL_RG8, GL_RG8, GL_RG16, GL_RG16, GL_RG32F, GL_RG32F, GL_RG16F, GL_RG32F}, - {GL_RGB8, GL_RGB8, GL_RGB16, GL_RGB16, GL_RGB32F, GL_RGB32F, GL_RGB16F, GL_RGB32F}, - {GL_RGB8, GL_RGB8, GL_RGB16, GL_RGB16, GL_RGB32F, GL_RGB32F, GL_RGB16F, GL_RGB32F}, - {GL_RGBA8, GL_RGBA8, GL_RGBA16, GL_RGBA16, GL_RGBA32F, GL_RGBA32F, GL_RGBA16F, GL_RGBA32F}, - {GL_RGBA8, GL_RGBA8, GL_RGBA16, GL_RGBA16, GL_RGBA32F, GL_RGBA32F, GL_RGBA16F, GL_RGBA32F} -}; - -static constexpr GLenum srgb_internal_format_lut[][8] = -{ - {GL_NONE, GL_NONE, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT32, GL_NONE, GL_DEPTH_COMPONENT32F}, - {GL_NONE, GL_NONE, GL_NONE, GL_NONE, GL_DEPTH24_STENCIL8, GL_DEPTH24_STENCIL8, GL_NONE, GL_DEPTH32F_STENCIL8}, - {GL_SRGB8, GL_SRGB8, GL_R16, GL_R16, GL_R32F, GL_R32F, GL_R16F, GL_R32F}, - {GL_SRGB8, GL_SRGB8, GL_RG16, GL_RG16, GL_RG32F, GL_RG32F, GL_RG16F, GL_RG32F}, - {GL_SRGB8, GL_SRGB8, GL_RGB16, GL_RGB16, GL_RGB32F, GL_RGB32F, GL_RGB16F, GL_RGB32F}, - {GL_SRGB8, GL_SRGB8, GL_RGB16, GL_RGB16, GL_RGB32F, GL_RGB32F, GL_RGB16F, GL_RGB32F}, - {GL_SRGB8_ALPHA8, GL_SRGB8_ALPHA8, GL_RGBA16, GL_RGBA16, GL_RGBA32F, GL_RGBA32F, GL_RGBA16F, GL_RGBA32F}, - {GL_SRGB8_ALPHA8, GL_SRGB8_ALPHA8, GL_RGBA16, GL_RGBA16, GL_RGBA32F, GL_RGBA32F, GL_RGBA16F, GL_RGBA32F} -}; - -static constexpr GLint swizzle_mask_lut[][4] = -{ - {GL_RED, GL_RED, GL_RED, GL_ONE}, - {GL_RED, GL_GREEN, GL_ZERO, GL_ONE}, - {GL_RED, GL_RED, GL_RED, GL_ONE}, - {GL_RED, GL_RED, GL_RED, GL_GREEN}, - {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}, - {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}, - {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}, - {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA} -}; - -static constexpr GLenum wrapping_lut[] = -{ - GL_CLAMP_TO_BORDER, - GL_CLAMP_TO_EDGE, - GL_REPEAT, - GL_MIRRORED_REPEAT -}; - -static constexpr GLenum min_filter_lut[] = -{ - GL_NEAREST, - GL_LINEAR, - GL_NEAREST_MIPMAP_NEAREST, - GL_LINEAR_MIPMAP_NEAREST, - GL_NEAREST_MIPMAP_LINEAR, - GL_LINEAR_MIPMAP_LINEAR -}; - -static constexpr GLenum mag_filter_lut[] = -{ - GL_NEAREST, - GL_LINEAR -}; - -texture::texture(std::uint16_t width, std::uint16_t height, std::uint16_t depth, bool cube, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) -{ - m_gl_texture_target = static_cast(cube ? GL_TEXTURE_CUBE_MAP : (depth) ? GL_TEXTURE_3D : (height) ? GL_TEXTURE_2D : GL_TEXTURE_1D); - - glGenTextures(1, &m_gl_texture_id); - resize(width, height, depth, type, format, transfer_function, data); - set_wrapping(m_wrapping[0], m_wrapping[1], m_wrapping[2]); - set_filters(std::get<0>(m_filters), std::get<1>(m_filters)); - set_max_anisotropy(m_max_anisotropy); -} - -texture::texture(std::uint16_t width, std::uint16_t height, bool cube, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data): - texture(width, height, 0, cube, type, format, transfer_function, data) -{} - -texture::texture(std::uint16_t width, bool cube, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data): - texture(width, 0, 0, cube, type, format, transfer_function, data) -{} - -texture::~texture() -{ - glDeleteTextures(1, &m_gl_texture_id); -} - -void texture::read(std::span data, gl::pixel_type type, gl::pixel_format format, std::uint8_t level) const -{ - const GLenum gl_format = pixel_format_lut[std::to_underlying(format)]; - const GLenum gl_type = pixel_type_lut[std::to_underlying(type)]; - - glBindTexture(m_gl_texture_target, m_gl_texture_id); - glGetTexImage(m_gl_texture_target, static_cast(level), gl_format, gl_type, data.data()); -} - -void texture::set_filters(texture_min_filter min_filter, texture_mag_filter mag_filter) -{ - m_filters = {min_filter, mag_filter}; - - const GLenum gl_min_filter = min_filter_lut[std::to_underlying(min_filter)]; - const GLenum gl_mag_filter = mag_filter_lut[std::to_underlying(mag_filter)]; - - glBindTexture(m_gl_texture_target, m_gl_texture_id); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_MIN_FILTER, gl_min_filter); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_MAG_FILTER, gl_mag_filter); -} - -void texture::set_min_filter(texture_min_filter filter) -{ - const GLenum gl_min_filter = min_filter_lut[std::to_underlying(filter)]; - - glBindTexture(m_gl_texture_target, m_gl_texture_id); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_MIN_FILTER, gl_min_filter); -} - -void texture::set_mag_filter(texture_mag_filter filter) -{ - const GLenum gl_mag_filter = mag_filter_lut[std::to_underlying(filter)]; - - glBindTexture(m_gl_texture_target, m_gl_texture_id); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_MAG_FILTER, gl_mag_filter); -} - -void texture::set_base_level(std::uint8_t level) -{ - m_base_level = level; - - glBindTexture(m_gl_texture_target, m_gl_texture_id); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_BASE_LEVEL, static_cast(m_base_level)); -} - -void texture::set_max_level(std::uint8_t level) -{ - m_max_level = level; - - glBindTexture(m_gl_texture_target, m_gl_texture_id); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_MAX_LEVEL, static_cast(m_max_level)); -} - -void texture::set_mip_range(std::uint8_t base_level, std::uint8_t max_level) -{ - m_base_level = base_level; - m_max_level = max_level; - - glBindTexture(m_gl_texture_target, m_gl_texture_id); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_BASE_LEVEL, static_cast(m_base_level)); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_MAX_LEVEL, static_cast(m_max_level)); -} - -void texture::set_max_anisotropy(float anisotropy) -{ - m_max_anisotropy = std::max(0.0f, std::min(1.0f, anisotropy)); - - // Get the maximum supported anisotropy value - float gl_max_texture_max_anisotropy; - glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gl_max_texture_max_anisotropy); - - // Lerp between 1.0 and GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT - float gl_max_anisotropy = 1.0f + m_max_anisotropy * (gl_max_texture_max_anisotropy - 1.0f); - - glBindTexture(m_gl_texture_target, m_gl_texture_id); - glTexParameterf(m_gl_texture_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_max_anisotropy); -} - -void texture::set_wrapping(gl::texture_wrapping wrap_s, gl::texture_wrapping wrap_t, gl::texture_wrapping wrap_r) -{ - m_wrapping = {wrap_s, wrap_t, wrap_r}; - - GLenum gl_wrap_s = wrapping_lut[std::to_underlying(wrap_s)]; - GLenum gl_wrap_t = wrapping_lut[std::to_underlying(wrap_t)]; - GLenum gl_wrap_r = wrapping_lut[std::to_underlying(wrap_r)]; - - glBindTexture(m_gl_texture_target, m_gl_texture_id); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_WRAP_S, gl_wrap_s); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_WRAP_T, gl_wrap_t); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_WRAP_R, gl_wrap_r); -} - -void texture::set_wrapping(gl::texture_wrapping wrap_s, gl::texture_wrapping wrap_t) -{ - std::get<0>(m_wrapping) = wrap_s; - std::get<1>(m_wrapping) = wrap_t; - - GLenum gl_wrap_s = wrapping_lut[std::to_underlying(wrap_s)]; - GLenum gl_wrap_t = wrapping_lut[std::to_underlying(wrap_t)]; - - glBindTexture(m_gl_texture_target, m_gl_texture_id); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_WRAP_S, gl_wrap_s); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_WRAP_T, gl_wrap_t); -} - -void texture::set_wrapping(gl::texture_wrapping wrap_s) -{ - std::get<0>(m_wrapping) = wrap_s; +namespace { - GLenum gl_wrap_s = wrapping_lut[std::to_underlying(wrap_s)]; - - glBindTexture(m_gl_texture_target, m_gl_texture_id); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_WRAP_S, gl_wrap_s); -} - -void texture::resize(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) -{ - m_dimensions = {width, height, depth}; - m_pixel_type = type; - m_pixel_format = format; - m_transfer_function = transfer_function; - - GLenum gl_internal_format; - if (m_transfer_function == gl::transfer_function::srgb) + enum texture_type: std::uint8_t { - gl_internal_format = srgb_internal_format_lut[std::to_underlying(format)][std::to_underlying(type)]; - } - else - { - gl_internal_format = linear_internal_format_lut[std::to_underlying(format)][std::to_underlying(type)]; - } - - GLenum gl_format = pixel_format_lut[std::to_underlying(format)]; - const GLint* gl_swizzle_mask = swizzle_mask_lut[std::to_underlying(format)]; - - GLenum gl_type = pixel_type_lut[std::to_underlying(type)]; - - // Special cases for depth + stencil pixel formats - if (gl_internal_format == GL_DEPTH24_STENCIL8) - { - gl_type = GL_UNSIGNED_INT_24_8; - } - else if (gl_internal_format == GL_DEPTH32F_STENCIL8) - { - gl_type = GL_FLOAT_32_UNSIGNED_INT_24_8_REV; - } - - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - glBindTexture(m_gl_texture_target, m_gl_texture_id); + texture_type_1d, + texture_type_1d_array, + texture_type_2d, + texture_type_2d_array, + texture_type_3d, + texture_type_cube, + texture_type_cube_array + }; - switch (m_gl_texture_target) + [[nodiscard]] std::unique_ptr load_texture(::resource_manager& resource_manager, deserialize_context& ctx, std::uint8_t texture_type) { - case GL_TEXTURE_1D: - glTexImage1D(m_gl_texture_target, 0, gl_internal_format, width, 0, gl_format, gl_type, data); - break; - - case GL_TEXTURE_2D: - glTexImage2D(m_gl_texture_target, 0, gl_internal_format, width, height, 0, gl_format, gl_type, data); - break; + // Read file format identifier + std::uint32_t format_identifier{0}; + ctx.read32_le(reinterpret_cast(&format_identifier), 1); - case GL_TEXTURE_3D: - glTexImage3D(m_gl_texture_target, 0, gl_internal_format, width, height, depth, 0, gl_format, gl_type, data); - break; + // Validate file format identifier (U+1F5BC = framed picture) + if (format_identifier != 0xbc969ff0) + { + throw deserialize_error("Invalid texture file."); + } - case GL_TEXTURE_CUBE_MAP: - update_cube_faces(gl_internal_format, gl_format, gl_type, data); - break; + // Read file format version + std::uint32_t format_version{0}; + ctx.read32_le(reinterpret_cast(&format_version), 1); - default: - break; - } - - m_mip_count = 1 + static_cast(std::log2(std::max(std::max(width, height), depth))); - glGenerateMipmap(m_gl_texture_target); - - glTexParameteriv(m_gl_texture_target, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle_mask); - - /// @TODO: remove this - if (format == pixel_format::d) - { - glTexParameteri(m_gl_texture_target, GL_TEXTURE_COMPARE_FUNC, GL_GREATER); - glTexParameteri(m_gl_texture_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); - } -} - -void texture::resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) -{ - resize(width, height, 0, type, format, transfer_function, data); -} - -void texture::resize(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) -{ - resize(width, 0, 0, type, format, transfer_function, data); -} - -void texture::update_cube_faces(unsigned int gl_internal_format, unsigned int gl_format, unsigned int gl_type, const std::byte* data) -{ - const auto width = get_width(); - const auto height = get_height(); - const auto layout = texture_cube::infer_cube_map_layout(width, height); - const auto face_size = texture_cube::infer_cube_map_face_size(layout, width, height); - - if (!data) - { - for (int i = 0; i < 6; ++i) + // Validate file format version + if (format_version != 1) { - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl_internal_format, face_size, face_size, 0, gl_format, gl_type, nullptr); + throw deserialize_error("Unsupported texture file version."); } - return; - } - - std::size_t channels = 0; - switch (m_pixel_format) - { - case pixel_format::d: - case pixel_format::r: - channels = 1; - break; + // Read image path + std::uint32_t image_path_length{0}; + ctx.read32_le(reinterpret_cast(&image_path_length), 1); + std::string image_path(image_path_length, '\0'); + ctx.read8(reinterpret_cast(image_path.data()), image_path_length); - case pixel_format::ds: - case pixel_format::rg: - channels = 2; - break; + // Load image + std::shared_ptr image; + switch (texture_type) + { + case texture_type_1d: + case texture_type_1d_array: + image = resource_manager.load(image_path); + break; + + case texture_type_2d: + case texture_type_2d_array: + image = resource_manager.load(image_path); + break; + + case texture_type_3d: + image = resource_manager.load(image_path); + break; + + case texture_type_cube: + case texture_type_cube_array: + image = resource_manager.load(image_path); + break; + + default: + throw deserialize_error("Invalid texture type."); + } - case pixel_format::rgb: - case pixel_format::bgr: - channels = 3; - break; + // Read image view parameters + gl::format format; + std::uint32_t first_mip_level; + std::uint32_t mip_level_count; + std::uint32_t first_array_layer; + std::uint32_t array_layer_count; + ctx.read32_le(reinterpret_cast(&format), 1); + ctx.read32_le(reinterpret_cast(&first_mip_level), 1); + ctx.read32_le(reinterpret_cast(&mip_level_count), 1); + ctx.read32_le(reinterpret_cast(&first_array_layer), 1); + ctx.read32_le(reinterpret_cast(&array_layer_count), 1); - case pixel_format::rgba: - case pixel_format::bgra: - channels = 4; - break; + // Handle automatic mip level count (`0`) + if (!mip_level_count) + { + mip_level_count = image->get_mip_levels(); + } - default: - break; - } - - std::size_t channel_size = 0; - switch (m_pixel_type) - { - case pixel_type::int_8: - case pixel_type::uint_8: - channel_size = 1; - break; + // Handle automatic array layer count (`0`) + if (!array_layer_count) + { + array_layer_count = image->get_array_layers(); + } - case pixel_type::int_16: - case pixel_type::uint_16: - case pixel_type::float_16: - channel_size = 2; - break; + // Read sampler parameters + gl::sampler_filter mag_filter; + gl::sampler_filter min_filter; + gl::sampler_mipmap_mode mipmap_mode; + gl::sampler_address_mode address_mode_u; + gl::sampler_address_mode address_mode_v; + gl::sampler_address_mode address_mode_w; + float mip_lod_bias; + float max_anisotropy; + std::uint8_t compare_enabled; + gl::compare_op compare_op; + float min_lod; + float max_lod; + std::array border_color; + ctx.read8(reinterpret_cast(&mag_filter), 1); + ctx.read8(reinterpret_cast(&min_filter), 1); + ctx.read8(reinterpret_cast(&mipmap_mode), 1); + ctx.read8(reinterpret_cast(&address_mode_u), 1); + ctx.read8(reinterpret_cast(&address_mode_v), 1); + ctx.read8(reinterpret_cast(&address_mode_w), 1); + ctx.read32_le(reinterpret_cast(&mip_lod_bias), 1); + ctx.read32_le(reinterpret_cast(&max_anisotropy), 1); + ctx.read8(reinterpret_cast(&compare_enabled), 1); + ctx.read8(reinterpret_cast(&compare_op), 1); + ctx.read32_le(reinterpret_cast(&min_lod), 1); + ctx.read32_le(reinterpret_cast(&max_lod), 1); + ctx.read32_le(reinterpret_cast(border_color.data()), 4); - case pixel_type::int_32: - case pixel_type::uint_32: - case pixel_type::float_32: - channel_size = 4; - break; + // Construct sampler + std::shared_ptr sampler = std::make_shared + ( + mag_filter, + min_filter, + mipmap_mode, + address_mode_u, + address_mode_v, + address_mode_w, + mip_lod_bias, + max_anisotropy, + compare_enabled, + compare_op, + min_lod, + max_lod, + border_color + ); - default: - break; - } - - const std::size_t pixel_stride = channels * channel_size; - const std::size_t row_stride = static_cast(face_size) * pixel_stride; - const std::size_t face_stride = static_cast(face_size) * row_stride; - - constexpr std::uint16_t vcross_offsets[6][2] = - { - {2, 2}, {0, 2}, - {1, 3}, {1, 1}, - {1, 0}, {1, 2} - }; - constexpr std::uint16_t hcross_offsets[6][2] = - { - {2, 1}, {0, 1}, - {1, 2}, {1, 0}, - {3, 1}, {1, 1} - }; - - std::vector face_buffer(face_stride); - - switch (layout) - { - case cube_map_layout::column: - for (std::uint16_t i = 0; i < 6; ++i) + // Construct image view and texture + switch (texture_type) + { + case texture_type_1d: { - const std::byte* face_data = data + face_stride * (5 - i); - - for (std::uint16_t y = 0; y < face_size; ++y) - { - for (std::uint16_t x = 0; x < face_size; ++x) - { - if (i < 2 || i > 3) - { - std::memcpy(face_buffer.data() + y * row_stride + x * pixel_stride, face_data + (face_size - y - 1) * row_stride + (face_size - x - 1) * pixel_stride, pixel_stride); - } - else - { - std::memcpy(face_buffer.data() + y * row_stride + x * pixel_stride, face_data + y * row_stride + x * pixel_stride, pixel_stride); - } - } - } - - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl_internal_format, face_size, face_size, 0, gl_format, gl_type, face_buffer.data()); + auto image_view = std::make_shared(image, format, first_mip_level, mip_level_count, first_array_layer); + return std::make_unique(std::move(image_view), std::move(sampler)); } - break; - - case cube_map_layout::row: - for (std::uint16_t i = 0; i < 6; ++i) + + case texture_type_1d_array: { - for (std::uint16_t y = 0; y < face_size; ++y) - { - for (std::uint16_t x = 0; x < face_size; ++x) - { - if (i < 2 || i > 3) - { - std::memcpy(face_buffer.data() + y * row_stride + x * pixel_stride, data + row_stride * (face_size - y - 1) * 6 + row_stride * i + (face_size - x - 1) * pixel_stride, pixel_stride); - } - else - { - std::memcpy(face_buffer.data() + y * row_stride + x * pixel_stride, data + row_stride * y * 6 + row_stride * i + x * pixel_stride, pixel_stride); - } - } - } + auto image_view = std::make_shared(image, format, first_mip_level, mip_level_count, first_array_layer, array_layer_count); + return std::make_unique(std::move(image_view), std::move(sampler)); + } - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl_internal_format, face_size, face_size, 0, gl_format, gl_type, face_buffer.data()); + case texture_type_2d: + { + auto image_view = std::make_shared(image, format, first_mip_level, mip_level_count, first_array_layer); + return std::make_unique(std::move(image_view), std::move(sampler)); } - break; - - case cube_map_layout::vertical_cross: - for (std::uint16_t i = 0; i < 6; ++i) + + case texture_type_2d_array: { - const std::byte* face_data = data + vcross_offsets[i][1] * row_stride * face_size * 3 + vcross_offsets[i][0] * row_stride; - - for (std::uint16_t y = 0; y < face_size; ++y) - { - for (std::uint16_t x = 0; x < face_size; ++x) - { - if (i < 2 || i > 3) - { - std::memcpy(face_buffer.data() + y * row_stride + x * pixel_stride, face_data + (face_size - y - 1) * row_stride * 3 + (face_size - x - 1) * pixel_stride, pixel_stride); - } - else - { - std::memcpy(face_buffer.data() + y * row_stride + x * pixel_stride, face_data + y * row_stride * 3 + x * pixel_stride, pixel_stride); - } - } - } + auto image_view = std::make_shared(image, format, first_mip_level, mip_level_count, first_array_layer, array_layer_count); + return std::make_unique(std::move(image_view), std::move(sampler)); + } - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl_internal_format, face_size, face_size, 0, gl_format, gl_type, face_buffer.data()); + case texture_type_3d: + { + auto image_view = std::make_shared(image, format, first_mip_level, mip_level_count); + return std::make_unique(std::move(image_view), std::move(sampler)); } - break; - - case cube_map_layout::horizontal_cross: - for (std::uint16_t i = 0; i < 6; ++i) + + case texture_type_cube: { - const std::byte* face_data = data + hcross_offsets[i][1] * row_stride * face_size * 4 + hcross_offsets[i][0] * row_stride; - - for (std::uint16_t y = 0; y < face_size; ++y) - { - for (std::uint16_t x = 0; x < face_size; ++x) - { - if (i < 2 || i > 3) - { - std::memcpy(face_buffer.data() + y * row_stride + x * pixel_stride, face_data + (face_size - y - 1) * row_stride * 4 + (face_size - x - 1) * pixel_stride, pixel_stride); - } - else - { - std::memcpy(face_buffer.data() + y * row_stride + x * pixel_stride, face_data + y * row_stride * 4 + x * pixel_stride, pixel_stride); - } - } - } - - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl_internal_format, face_size, face_size, 0, gl_format, gl_type, face_buffer.data()); + auto image_view = std::make_shared(image, format, first_mip_level, mip_level_count, first_array_layer); + return std::make_unique(std::move(image_view), std::move(sampler)); } - break; - - default: - throw std::runtime_error("Unsupported cube map layout"); + + case texture_type_cube_array: + { + auto image_view = std::make_shared(image, format, first_mip_level, mip_level_count, first_array_layer, array_layer_count); + return std::make_unique(std::move(image_view), std::move(sampler)); + } + + default: + return nullptr; + } } -} -} // namespace gl +} template <> std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) { - // Load JSON data - auto json_data = resource_loader::load(resource_manager, ctx); - - // Read image filename - std::string image_filename; - if (auto element = json_data->find("image"); element != json_data->end()) - { - image_filename = element.value().get(); - } - - // Load image - auto image = resource_manager.load<::image>(image_filename); - - // Read transfer function - gl::transfer_function transfer_function = gl::transfer_function::linear; - if (auto element = json_data->find("transfer_function"); element != json_data->end()) - { - std::string value = element.value().get(); - if (value == "srgb") - { - transfer_function = gl::transfer_function::srgb; - } - } - - // Read extension mode - gl::texture_wrapping wrapping = gl::texture_wrapping::repeat; - if (auto element = json_data->find("extension"); element != json_data->end()) - { - std::string value = element.value().get(); - if (value == "clip") - { - wrapping = gl::texture_wrapping::clip; - } - else if (value == "extend") - { - wrapping = gl::texture_wrapping::extend; - } - else if (value == "repeat") - { - wrapping = gl::texture_wrapping::repeat; - } - else if (value == "mirrored_repeat") - { - wrapping = gl::texture_wrapping::mirrored_repeat; - } - } - - // Read interpolation mode - gl::texture_min_filter min_filter = gl::texture_min_filter::linear_mipmap_linear; - gl::texture_mag_filter mag_filter = gl::texture_mag_filter::linear; - if (auto element = json_data->find("interpolation"); element != json_data->end()) - { - std::string value = element.value().get(); - if (value == "linear") - { - min_filter = gl::texture_min_filter::linear_mipmap_linear; - mag_filter = gl::texture_mag_filter::linear; - } - else if (value == "closest") - { - min_filter = gl::texture_min_filter::nearest_mipmap_nearest; - mag_filter = gl::texture_mag_filter::nearest; - } - } - - // Read max anisotropy - float max_anisotropy = 0.0f; - if (auto element = json_data->find("max_anisotropy"); element != json_data->end()) - { - max_anisotropy = element.value().get(); - } - - // Determine pixel type - gl::pixel_type type = (image->bit_depth() >> 3 == sizeof(float)) ? gl::pixel_type::float_32 : gl::pixel_type::uint_8; + return std::unique_ptr(static_cast(load_texture(resource_manager, ctx, texture_type_1d).release())); +} - // Determine pixel format - gl::pixel_format format; - if (image->channels() == 1) - { - format = gl::pixel_format::r; - } - else if (image->channels() == 2) - { - format = gl::pixel_format::rg; - } - else if (image->channels() == 3) - { - format = gl::pixel_format::rgb; - } - else if (image->channels() == 4) - { - format = gl::pixel_format::rgba; - } - else - { - throw std::runtime_error(std::format("Texture image has unsupported number of channels ({})", image->channels())); - } - - // Create texture - auto texture = std::make_unique(static_cast(image->size().x()), type, format, transfer_function, image->data()); - texture->set_wrapping(wrapping); - texture->set_filters(min_filter, mag_filter); - texture->set_max_anisotropy(max_anisotropy); - - return texture; +template <> +std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) +{ + return std::unique_ptr(static_cast(load_texture(resource_manager, ctx, texture_type_1d_array).release())); } template <> std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) { - // Load JSON data - auto json_data = resource_loader::load(resource_manager, ctx); - - // Read image filename - std::string image_filename; - if (auto element = json_data->find("image"); element != json_data->end()) - { - image_filename = element.value().get(); - } - - // Load image - auto image = resource_manager.load<::image>(image_filename); - - // Read color space - gl::transfer_function transfer_function = gl::transfer_function::linear; - if (auto element = json_data->find("transfer_function"); element != json_data->end()) - { - std::string value = element.value().get(); - if (value == "srgb") - { - transfer_function = gl::transfer_function::srgb; - } - } - - // Read extension mode - gl::texture_wrapping wrapping = gl::texture_wrapping::repeat; - if (auto element = json_data->find("extension"); element != json_data->end()) - { - std::string value = element.value().get(); - if (value == "clip") - { - wrapping = gl::texture_wrapping::clip; - } - else if (value == "extend") - { - wrapping = gl::texture_wrapping::extend; - } - else if (value == "repeat") - { - wrapping = gl::texture_wrapping::repeat; - } - else if (value == "mirrored_repeat") - { - wrapping = gl::texture_wrapping::mirrored_repeat; - } - } - - // Read interpolation mode - gl::texture_min_filter min_filter = gl::texture_min_filter::linear_mipmap_linear; - gl::texture_mag_filter mag_filter = gl::texture_mag_filter::linear; - if (auto element = json_data->find("interpolation"); element != json_data->end()) - { - std::string value = element.value().get(); - if (value == "linear") - { - min_filter = gl::texture_min_filter::linear_mipmap_linear; - mag_filter = gl::texture_mag_filter::linear; - } - else if (value == "closest") - { - min_filter = gl::texture_min_filter::nearest_mipmap_nearest; - mag_filter = gl::texture_mag_filter::nearest; - } - } - - // Read max anisotropy - float max_anisotropy = 0.0f; - if (auto element = json_data->find("max_anisotropy"); element != json_data->end()) - { - max_anisotropy = element.value().get(); - } - - // Determine pixel type - gl::pixel_type type = (image->bit_depth() >> 3 == sizeof(float)) ? gl::pixel_type::float_32 : gl::pixel_type::uint_8; + return std::unique_ptr(static_cast(load_texture(resource_manager, ctx, texture_type_2d).release())); +} - // Determine pixel format - gl::pixel_format format; - if (image->channels() == 1) - { - format = gl::pixel_format::r; - } - else if (image->channels() == 2) - { - format = gl::pixel_format::rg; - } - else if (image->channels() == 3) - { - format = gl::pixel_format::rgb; - } - else if (image->channels() == 4) - { - format = gl::pixel_format::rgba; - } - else - { - throw std::runtime_error(std::format("Texture image has unsupported number of channels ({})", image->channels())); - } - - // Create texture - auto texture = std::make_unique(static_cast(image->size().x()), static_cast(image->size().y()), type, format, transfer_function, image->data()); - texture->set_wrapping(wrapping, wrapping); - texture->set_filters(min_filter, mag_filter); - texture->set_max_anisotropy(max_anisotropy); - - return texture; +template <> +std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) +{ + return std::unique_ptr(static_cast(load_texture(resource_manager, ctx, texture_type_2d_array).release())); } template <> std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) { - throw std::runtime_error("3D texture loading not yet supported"); + return std::unique_ptr(static_cast(load_texture(resource_manager, ctx, texture_type_3d).release())); } template <> std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) { - // Load JSON data - auto json_data = resource_loader::load(resource_manager, ctx); - - // Read image filename - std::string image_filename; - if (auto element = json_data->find("image"); element != json_data->end()) - { - image_filename = element.value().get(); - } - - // Load image - auto image = resource_manager.load<::image>(image_filename); - - // Read color space - gl::transfer_function transfer_function = gl::transfer_function::linear; - if (auto element = json_data->find("transfer_function"); element != json_data->end()) - { - std::string value = element.value().get(); - if (value == "srgb") - { - transfer_function = gl::transfer_function::srgb; - } - } - - // Read extension mode - gl::texture_wrapping wrapping = gl::texture_wrapping::repeat; - if (auto element = json_data->find("extension"); element != json_data->end()) - { - std::string value = element.value().get(); - if (value == "clip") - { - wrapping = gl::texture_wrapping::clip; - } - else if (value == "extend") - { - wrapping = gl::texture_wrapping::extend; - } - else if (value == "repeat") - { - wrapping = gl::texture_wrapping::repeat; - } - else if (value == "mirrored_repeat") - { - wrapping = gl::texture_wrapping::mirrored_repeat; - } - } - - // Read interpolation mode - gl::texture_min_filter min_filter = gl::texture_min_filter::linear_mipmap_linear; - gl::texture_mag_filter mag_filter = gl::texture_mag_filter::linear; - if (auto element = json_data->find("interpolation"); element != json_data->end()) - { - std::string value = element.value().get(); - if (value == "linear") - { - min_filter = gl::texture_min_filter::linear_mipmap_linear; - mag_filter = gl::texture_mag_filter::linear; - } - else if (value == "closest") - { - min_filter = gl::texture_min_filter::nearest_mipmap_nearest; - mag_filter = gl::texture_mag_filter::nearest; - } - } - - // Read max anisotropy - float max_anisotropy = 0.0f; - if (auto element = json_data->find("max_anisotropy"); element != json_data->end()) - { - max_anisotropy = element.value().get(); - } - - // Determine pixel type - gl::pixel_type type; - switch (image->bit_depth()) - { - case 32u: - type = gl::pixel_type::float_32; - break; - case 16u: - type = gl::pixel_type::uint_16; - break; - case 8u: - default: - type = gl::pixel_type::uint_8; - break; - } - - // Determine pixel format - gl::pixel_format format; - if (image->channels() == 1) - { - format = gl::pixel_format::r; - } - else if (image->channels() == 2) - { - format = gl::pixel_format::rg; - } - else if (image->channels() == 3) - { - format = gl::pixel_format::rgb; - } - else if (image->channels() == 4) - { - format = gl::pixel_format::rgba; - } - else - { - throw std::runtime_error(std::format("Texture image has unsupported number of channels ({})", image->channels())); - } - - // Create texture - auto texture = std::make_unique(static_cast(image->size().x()), static_cast(image->size().y()), type, format, transfer_function, image->data()); - texture->set_wrapping(wrapping, wrapping, wrapping); - texture->set_filters(min_filter, mag_filter); - texture->set_max_anisotropy(max_anisotropy); - - return texture; + return std::unique_ptr(static_cast(load_texture(resource_manager, ctx, texture_type_cube).release())); +} + +template <> +std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) +{ + return std::unique_ptr(static_cast(load_texture(resource_manager, ctx, texture_type_cube_array).release())); } diff --git a/src/engine/gl/texture.hpp b/src/engine/gl/texture.hpp index effbd51..31f5d13 100644 --- a/src/engine/gl/texture.hpp +++ b/src/engine/gl/texture.hpp @@ -20,254 +20,288 @@ #ifndef ANTKEEPER_GL_TEXTURE_HPP #define ANTKEEPER_GL_TEXTURE_HPP -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include namespace gl { -class framebuffer; -class gl_shader_texture_1d; -class gl_shader_texture_2d; -class gl_shader_texture_3d; -class gl_shader_texture_cube; - /** - * Abstract base class for 1D, 2D, 3D, and cube textures which can be uploaded to shaders via shader inputs. + * Image view and sampler object pair. */ class texture { public: /** - * Destructs a texture. - */ - virtual ~texture(); - - /** - * Reads texture pixel data from the GPU. - * - * @param[out] data Pixel data buffer. - * @param[in] type Returned pixel component data type. - * @param[in] format Returned pixel format. - * @param[in] level Mip level to read. - */ - void read(std::span data, gl::pixel_type type, gl::pixel_format format, std::uint8_t level = 0) const; - - /** - * Sets the texture filter modes. + * Constructs a texture. * - * @param min_filter Texture minification filter mode. - * @param mag_filter Texture magnification filter mode. + * @param image_view Texture image view. + * @param sampler Texture sampler. */ - void set_filters(texture_min_filter min_filter, texture_mag_filter mag_filter); + /// @{ + inline texture(std::shared_ptr sampler) noexcept: + m_sampler(sampler) + {} - /** - * Sets the texture minification filter mode. - * - * @param filter Texture minification filter mode. - */ - void set_min_filter(texture_min_filter filter); + constexpr texture() noexcept = default; + /// @} /** - * Sets the texture magnification filter mode. + * Sets the sampler object. * - * @param filter Texture magnification filter mode. + * @param sampler Sampler object. */ - void set_mag_filter(texture_mag_filter filter); + inline void set_sampler(std::shared_ptr sampler) + { + m_sampler = sampler; + } - /** - * Sets the index of lowest accessible mip level. - * - * @param level Index of the lowest accessible mip level. - */ - void set_base_level(std::uint8_t level); + /// Returns the sampler object. + [[nodiscard]] inline constexpr const std::shared_ptr& get_sampler() const noexcept + { + return m_sampler; + } - /** - * Sets the index of highest accessible mip level. - * - * @param level Index of the highest accessible mip level. - */ - void set_max_level(std::uint8_t level); +private: + std::shared_ptr m_sampler; +}; + +/** + * 1D texture. + */ +class texture_1d: public texture +{ +public: + /// @copydoc texture::texture + /// @{ + inline texture_1d(std::shared_ptr image_view, std::shared_ptr sampler) noexcept: + texture(sampler), + m_image_view(image_view) + {} - /** - * Sets the range of accessible mip levels. - * - * @param base Index of the lowest accessible mip level. - * @param max Index of the highest accessible mip level. - */ - void set_mip_range(std::uint8_t base_level, std::uint8_t max_level); + constexpr texture_1d() noexcept = default; + /// @} /** - * Sets the maximum anisotropy. - * - * @param level Max anisotropy on `[0.0, 1.0]`, with `0.0` indicating normal filtering, and `1.0` indicating maximum anisotropic filtering. + * Sets the image view. */ - void set_max_anisotropy(float anisotropy); - - /// Returns the texture type. - [[nodiscard]] virtual constexpr texture_type get_texture_type() const noexcept = 0; - - /// Returns the dimensions of the texture, in pixels. - [[nodiscard]] inline const std::array& get_dimensions() const noexcept + inline void set_image_view(std::shared_ptr image_view) { - return m_dimensions; + m_image_view = image_view; } - /// Returns the width of the texture, in pixels. - [[nodiscard]] inline std::uint16_t get_width() const noexcept + /// Returns the image view. + [[nodiscard]] inline constexpr const std::shared_ptr& get_image_view() const noexcept { - return m_dimensions[0]; + return m_image_view; } - /// Returns the height of the texture, in pixels. - [[nodiscard]] inline std::uint16_t get_height() const noexcept - { - return m_dimensions[1]; - } +private: + std::shared_ptr m_image_view; +}; + +/** + * 1D texture array. + */ +class texture_1d_array: public texture +{ +public: + /// @copydoc texture::texture + /// @{ + inline texture_1d_array(std::shared_ptr image_view, std::shared_ptr sampler) noexcept: + texture(sampler), + m_image_view(image_view) + {} - /// Returns the depth of the texture, in pixels. - [[nodiscard]] inline std::uint16_t get_depth() const noexcept - { - return m_dimensions[2]; - } + constexpr texture_1d_array() noexcept = default; + /// @} - /// Returns the pixel type enumeration. - [[nodiscard]] inline pixel_type get_pixel_type() const noexcept + /** + * Sets the image view. + */ + inline void set_image_view(std::shared_ptr image_view) { - return m_pixel_type; + m_image_view = image_view; } - /// Returns the pixel format enumeration. - [[nodiscard]] inline pixel_format get_pixel_format() const noexcept + /// Returns the image view. + [[nodiscard]] inline constexpr const std::shared_ptr& get_image_view() const noexcept { - return m_pixel_format; + return m_image_view; } - /// Returns the transfer function. - [[nodiscard]] inline transfer_function get_transfer_function() const noexcept - { - return m_transfer_function; - } +private: + std::shared_ptr m_image_view; +}; + +/** + * 2D texture. + */ +class texture_2d: public texture +{ +public: + /// @copydoc texture::texture + /// @{ + inline texture_2d(std::shared_ptr image_view, std::shared_ptr sampler) noexcept: + texture(sampler), + m_image_view(image_view) + {} + + constexpr texture_2d() noexcept = default; + /// @} - /// Returns the wrapping modes of the texture. - [[nodiscard]] inline const std::array& get_wrapping() const noexcept + /** + * Sets the image view. + */ + inline void set_image_view(std::shared_ptr image_view) { - return m_wrapping; + m_image_view = image_view; } - /// Returns the filtering modes of the texture. - [[nodiscard]] inline const std::tuple& get_filters() const noexcept + /// Returns the image view. + [[nodiscard]] inline constexpr const std::shared_ptr& get_image_view() const noexcept { - return m_filters; + return m_image_view; } - /// Returns the number of available mip levels. - [[nodiscard]] inline std::uint16_t get_mip_count() const noexcept +private: + std::shared_ptr m_image_view; +}; + +/** + * 2D texture array. + */ +class texture_2d_array: public texture +{ +public: + /// @copydoc texture::texture + /// @{ + inline texture_2d_array(std::shared_ptr image_view, std::shared_ptr sampler) noexcept: + texture(sampler), + m_image_view(image_view) + {} + + constexpr texture_2d_array() noexcept = default; + /// @} + + /** + * Sets the image view. + */ + inline void set_image_view(std::shared_ptr image_view) { - return m_mip_count; + m_image_view = image_view; } - /// Returns the index of the lowest accessible mip level. - [[nodiscard]] inline std::uint8_t get_base_level() const noexcept + /// Returns the image view. + [[nodiscard]] inline constexpr const std::shared_ptr& get_image_view() const noexcept { - return m_base_level; + return m_image_view; } - /// Returns the index of the highest accessible mip level. - [[nodiscard]] inline std::uint8_t get_max_level() const noexcept +private: + std::shared_ptr m_image_view; +}; + +/** + * 3D texture. + */ +class texture_3d: public texture +{ +public: + /// @copydoc texture::texture + /// @{ + inline texture_3d(std::shared_ptr image_view, std::shared_ptr sampler) noexcept: + texture(sampler), + m_image_view(image_view) + {} + + constexpr texture_3d() noexcept = default; + /// @} + + /** + * Sets the image view. + */ + inline void set_image_view(std::shared_ptr image_view) { - return m_max_level; + m_image_view = image_view; } - /// Returns the maximum anisotropy. - [[nodiscard]] inline float get_max_anisotropy() const noexcept + /// Returns the image view. + [[nodiscard]] inline constexpr const std::shared_ptr& get_image_view() const noexcept { - return m_max_anisotropy; + return m_image_view; } -protected: - /** - * Constructs a texture. - * - * @param width Texture width, in pixels. - * @param height Texture height, in pixels. For 2D or 3D textures. - * @param depth Texture depth, in pixels. For 3D textures only. - * @param type Pixel component data type. - * @param format Pixel format. - * @param transfer_function Transfer function of the texture data. - * @param data Pointer to pixel data. - * - * @warning If the sRGB color space is specified, pixel data will be stored internally as 8 bits per channel, and automatically converted to linear space before reading. - */ +private: + std::shared_ptr m_image_view; +}; + +/** + * Cube texture. + */ +class texture_cube: public texture +{ +public: + /// @copydoc texture::texture /// @{ - texture(std::uint16_t width, std::uint16_t height, std::uint16_t depth, bool cube = false, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); - texture(std::uint16_t width, std::uint16_t height, bool cube = false, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); - explicit texture(std::uint16_t width, bool cube = false, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); + inline texture_cube(std::shared_ptr image_view, std::shared_ptr sampler) noexcept: + texture(sampler), + m_image_view(image_view) + {} + + constexpr texture_cube() noexcept = default; /// @} /** - * Sets the texture wrapping modes. - * - * @param wrap_s Wrapping mode for s-coordinates. - * @param wrap_t Wrapping mode for t-coordinates. - * @param wrap_r Wrapping mode for r-coordinates. + * Sets the image view. */ + inline void set_image_view(std::shared_ptr image_view) + { + m_image_view = image_view; + } + + /// Returns the image view. + [[nodiscard]] inline constexpr const std::shared_ptr& get_image_view() const noexcept + { + return m_image_view; + } + +private: + std::shared_ptr m_image_view; +}; + +/** + * Cube texture array. + */ +class texture_cube_array: public texture +{ +public: + /// @copydoc texture::texture /// @{ - virtual void set_wrapping(gl::texture_wrapping wrap_s, gl::texture_wrapping wrap_t, gl::texture_wrapping wrap_r); - virtual void set_wrapping(gl::texture_wrapping wrap_s, gl::texture_wrapping wrap_t); - virtual void set_wrapping(gl::texture_wrapping wrap_s); + inline texture_cube_array(std::shared_ptr image_view, std::shared_ptr sampler) noexcept: + texture(sampler), + m_image_view(image_view) + {} + + constexpr texture_cube_array() noexcept = default; /// @} /** - * Resizes the texture. - * - * @param width Texture width, in pixels. - * @param height Texture height, in pixels. For 2D or 3D textures. - * @param depth Texture depth, in pixels. For 3D textures only. - * @param type Pixel component data type. - * @param format Pixel format. - * @param transfer_function Transfer_function the pixel data. - * @param data Pointer to pixel data. - * - * @warning If the sRGB color space is specified, pixel data will be stored internally as 8 bits per channel, and automatically converted to linear space before reading. + * Sets the image view. */ - /// @{ - virtual void resize(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data); - virtual void resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data); - virtual void resize(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data); - /// @} - + inline void set_image_view(std::shared_ptr image_view) + { + m_image_view = image_view; + } + + /// Returns the image view. + [[nodiscard]] inline constexpr const std::shared_ptr& get_image_view() const noexcept + { + return m_image_view; + } + private: - void update_cube_faces(unsigned int gl_internal_format, unsigned int gl_format, unsigned int gl_type, const std::byte* data); - - friend class framebuffer; - friend class gl_shader_texture_1d; - friend class gl_shader_texture_2d; - friend class gl_shader_texture_3d; - friend class gl_shader_texture_cube; - - unsigned int m_gl_texture_target{}; - unsigned int m_gl_texture_id{}; - std::array m_dimensions{}; - gl::pixel_type m_pixel_type{}; - gl::pixel_format m_pixel_format{}; - gl::transfer_function m_transfer_function{gl::transfer_function::linear}; - std::array m_wrapping{texture_wrapping::repeat, texture_wrapping::repeat, texture_wrapping::repeat}; - std::tuple m_filters{texture_min_filter::linear_mipmap_linear, texture_mag_filter::linear}; - std::uint8_t m_base_level{}; - std::uint8_t m_max_level{255}; - float m_max_anisotropy{}; - -protected: - std::uint16_t m_mip_count{}; + std::shared_ptr m_image_view; }; } // namespace gl diff --git a/src/engine/gl/vertex-array.cpp b/src/engine/gl/vertex-array.cpp index 53ec5ff..9a878f8 100644 --- a/src/engine/gl/vertex-array.cpp +++ b/src/engine/gl/vertex-array.cpp @@ -18,93 +18,315 @@ */ #include -#include -#include +#include +#include #include -#include -namespace gl { - -static constexpr GLenum vertex_attribute_type_lut[] = -{ - GL_BYTE, - GL_UNSIGNED_BYTE, - GL_SHORT, - GL_UNSIGNED_SHORT, - GL_INT, - GL_UNSIGNED_INT, - GL_HALF_FLOAT, - GL_FLOAT, - GL_DOUBLE -}; - -vertex_array::vertex_array() -{ - glGenVertexArrays(1, &gl_array_id); +namespace { + + // 0 = unscaled, 1 = normalized, 2 = scaled + static constexpr std::uint8_t format_scale_lut[] = + { + 0, // undefined + 1, // r4g4_unorm_pack8 + 1, // r4g4b4a4_unorm_pack16 + 1, // b4g4r4a4_unorm_pack16 + 1, // r5g6b5_unorm_pack16 + 1, // b5g6r5_unorm_pack16 + 1, // r5g5b5a1_unorm_pack16 + 1, // b5g5r5a1_unorm_pack16 + 1, // a1r5g5b5_unorm_pack16 + 1, // r8_unorm + 1, // r8_snorm + 2, // r8_uscaled + 2, // r8_sscaled + 0, // r8_uint + 0, // r8_sint + 0, // r8_srgb + 1, // r8g8_unorm + 1, // r8g8_snorm + 2, // r8g8_uscaled + 2, // r8g8_sscaled + 0, // r8g8_uint + 0, // r8g8_sint + 0, // r8g8_srgb + 1, // r8g8b8_unorm + 1, // r8g8b8_snorm + 2, // r8g8b8_uscaled + 2, // r8g8b8_sscaled + 0, // r8g8b8_uint + 0, // r8g8b8_sint + 0, // r8g8b8_srgb + 1, // b8g8r8_unorm + 1, // b8g8r8_snorm + 2, // b8g8r8_uscaled + 2, // b8g8r8_sscaled + 0, // b8g8r8_uint + 0, // b8g8r8_sint + 0, // b8g8r8_srgb + 1, // r8g8b8a8_unorm + 1, // r8g8b8a8_snorm + 2, // r8g8b8a8_uscaled + 2, // r8g8b8a8_sscaled + 0, // r8g8b8a8_uint + 0, // r8g8b8a8_sint + 0, // r8g8b8a8_srgb + 1, // b8g8r8a8_unorm + 1, // b8g8r8a8_snorm + 2, // b8g8r8a8_uscaled + 2, // b8g8r8a8_sscaled + 0, // b8g8r8a8_uint + 0, // b8g8r8a8_sint + 0, // b8g8r8a8_srgb + 1, // a8b8g8r8_unorm_pack32 + 1, // a8b8g8r8_snorm_pack32 + 2, // a8b8g8r8_uscaled_pack32 + 2, // a8b8g8r8_sscaled_pack32 + 0, // a8b8g8r8_uint_pack32 + 0, // a8b8g8r8_sint_pack32 + 0, // a8b8g8r8_srgb_pack32 + 1, // a2r10g10b10_unorm_pack32 + 1, // a2r10g10b10_snorm_pack32 + 2, // a2r10g10b10_uscaled_pack32 + 2, // a2r10g10b10_sscaled_pack32 + 0, // a2r10g10b10_uint_pack32 + 0, // a2r10g10b10_sint_pack32 + 1, // a2b10g10r10_unorm_pack32 + 1, // a2b10g10r10_snorm_pack32 + 2, // a2b10g10r10_uscaled_pack32 + 2, // a2b10g10r10_sscaled_pack32 + 0, // a2b10g10r10_uint_pack32 + 0, // a2b10g10r10_sint_pack32 + 1, // r16_unorm + 1, // r16_snorm + 2, // r16_uscaled + 2, // r16_sscaled + 0, // r16_uint + 0, // r16_sint + 0, // r16_sfloat + 1, // r16g16_unorm + 1, // r16g16_snorm + 2, // r16g16_uscaled + 2, // r16g16_sscaled + 0, // r16g16_uint + 0, // r16g16_sint + 0, // r16g16_sfloat + 1, // r16g16b16_unorm + 1, // r16g16b16_snorm + 2, // r16g16b16_uscaled + 2, // r16g16b16_sscaled + 0, // r16g16b16_uint + 0, // r16g16b16_sint + 0, // r16g16b16_sfloat + 1, // r16g16b16a16_unorm + 1, // r16g16b16a16_snorm + 2, // r16g16b16a16_uscaled + 2, // r16g16b16a16_sscaled + 0, // r16g16b16a16_uint + 0, // r16g16b16a16_sint + 0, // r16g16b16a16_sfloat + 0, // r32_uint + 0, // r32_sint + 0, // r32_sfloat + 0, // r32g32_uint + 0, // r32g32_sint + 0, // r32g32_sfloat + 0, // r32g32b32_uint + 0, // r32g32b32_sint + 0, // r32g32b32_sfloat + 0, // r32g32b32a32_uint + 0, // r32g32b32a32_sint + 0, // r32g32b32a32_sfloat + 0, // r64_uint + 0, // r64_sint + 0, // r64_sfloat + 0, // r64g64_uint + 0, // r64g64_sint + 0, // r64g64_sfloat + 0, // r64g64b64_uint + 0, // r64g64b64_sint + 0, // r64g64b64_sfloat + 0, // r64g64b64a64_uint + 0, // r64g64b64a64_sint + 0, // r64g64b64a64_sfloat + 0, // b10g11r11_ufloat_pack32 + 0, // e5b9g9r9_ufloat_pack32 + 1, // d16_unorm + 1, // x8_d24_unorm_pack32 + 0, // d32_sfloat + 0, // s8_uint + 1, // d16_unorm_s8_uint + 1, // d24_unorm_s8_uint + 0, // d32_sfloat_s8_uint + 1, // bc1_rgb_unorm_block, + 0, // bc1_rgb_srgb_block, + 1, // bc1_rgba_unorm_block, + 0, // bc1_rgba_srgb_block, + 1, // bc2_unorm_block, + 0, // bc2_srgb_block, + 1, // bc3_unorm_block, + 0, // bc3_srgb_block, + 1, // bc4_unorm_block, + 1, // bc4_snorm_block, + 1, // bc5_unorm_block, + 1, // bc5_snorm_block, + 2, // bc6h_ufloat_block, + 2, // bc6h_sfloat_block, + 1, // bc7_unorm_block, + 0, // bc7_srgb_block, + 1, // etc2_r8g8b8_unorm_block, + 0, // etc2_r8g8b8_srgb_block, + 1, // etc2_r8g8b8a1_unorm_block, + 0, // etc2_r8g8b8a1_srgb_block, + 1, // etc2_r8g8b8a8_unorm_block, + 0, // etc2_r8g8b8a8_srgb_block, + 1, // eac_r11_unorm_block, + 1, // eac_r11_snorm_block, + 1, // eac_r11g11_unorm_block, + 1, // eac_r11g11_snorm_block, + 1, // astc_4x4_unorm_block, + 0, // astc_4x4_srgb_block, + 1, // astc_5x4_unorm_block, + 0, // astc_5x4_srgb_block, + 1, // astc_5x5_unorm_block, + 0, // astc_5x5_srgb_block, + 1, // astc_6x5_unorm_block, + 0, // astc_6x5_srgb_block, + 1, // astc_6x6_unorm_block, + 0, // astc_6x6_srgb_block, + 1, // astc_8x5_unorm_block, + 0, // astc_8x5_srgb_block, + 1, // astc_8x6_unorm_block, + 0, // astc_8x6_srgb_block, + 1, // astc_8x8_unorm_block, + 0, // astc_8x8_srgb_block, + 1, // astc_10x5_unorm_block, + 0, // astc_10x5_srgb_block, + 1, // astc_10x6_unorm_block, + 0, // astc_10x6_srgb_block, + 1, // astc_10x8_unorm_block, + 0, // astc_10x8_srgb_block, + 1, // astc_10x10_unorm_block, + 0, // astc_10x10_srgb_block, + 1, // astc_12x10_unorm_block, + 0, // astc_12x10_srgb_block, + 1, // astc_12x12_unorm_block, + 0, // astc_12x12_srgb_block + }; } -vertex_array::~vertex_array() -{ - glDeleteVertexArrays(1, &gl_array_id); -} +namespace gl { -void vertex_array::bind(attribute_location_type location, const vertex_attribute& attribute) +vertex_array::vertex_array(std::span attributes) { - if (attribute.buffer == nullptr) - { - throw std::invalid_argument("Cannot bind vertex attribute that has a null vertex buffer."); - } - - if (attribute.components < 1 || attribute.components > 4) - { - throw std::invalid_argument("Cannot bind vertex attribute that has an unsupported number of components (" + std::to_string(attribute.components) + ")"); - } - - m_attributes[location] = attribute; + m_attributes.assign(attributes.begin(), attributes.end()); - GLenum gl_type = vertex_attribute_type_lut[static_cast(attribute.type)]; - glBindVertexArray(gl_array_id); - glBindBuffer(GL_ARRAY_BUFFER, attribute.buffer->gl_buffer_id); + glCreateVertexArrays(1, &m_gl_named_array); - if (attribute.normalized || gl_type == GL_FLOAT || gl_type == GL_HALF_FLOAT || gl_type == GL_DOUBLE) + for (const auto& attribute: m_attributes) { - glVertexAttribPointer - ( - static_cast(location), - static_cast(attribute.components), - gl_type, - attribute.normalized ? GL_TRUE : GL_FALSE, - static_cast(attribute.stride), - reinterpret_cast(attribute.offset) - ); - } - else - { - glVertexAttribIPointer + // Enable attribute + glEnableVertexArrayAttrib(m_gl_named_array, static_cast(attribute.location)); + + // Set attribute vertex binding index + glVertexArrayAttribBinding ( - static_cast(location), - static_cast(attribute.components), - gl_type, - static_cast(attribute.stride), - reinterpret_cast(attribute.offset) - ); + m_gl_named_array, + static_cast(attribute.location), + static_cast(attribute.binding) + ); + + const auto format_index = std::to_underlying(attribute.format); + const auto gl_base_format = gl_format_lut[format_index][1]; + const auto gl_type = gl_format_lut[format_index][2]; + const auto format_scale = format_scale_lut[format_index]; + + // Determine number of values per vertex + GLint gl_size; + switch (gl_base_format) + { + case GL_RED: + case GL_RED_INTEGER: + case GL_DEPTH_COMPONENT: + case GL_STENCIL_INDEX: + gl_size = 1; + break; + + case GL_RG: + case GL_RG_INTEGER: + case GL_DEPTH_STENCIL: + gl_size = 2; + break; + + case GL_BGR: + case GL_BGR_INTEGER: + case GL_RGB: + case GL_RGB_INTEGER: + gl_size = 3; + break; + + case GL_BGRA: + case GL_BGRA_INTEGER: + case GL_RGBA: + case GL_RGBA_INTEGER: + gl_size = 4; + break; + + default: + gl_size = 0; + break; + } + + if (gl_size == 0 || gl_type == 0) + { + throw std::invalid_argument("Vertex input attribute has unsupported format."); + } + + if (format_scale > 0 || gl_type == GL_FLOAT || gl_type == GL_HALF_FLOAT) + { + glVertexArrayAttribFormat + ( + m_gl_named_array, + static_cast(attribute.location), + gl_size, + gl_type, + (format_scale == 1), + static_cast(attribute.offset) + ); + } + else if (gl_type == GL_DOUBLE) + { + glVertexArrayAttribLFormat + ( + m_gl_named_array, + static_cast(attribute.location), + gl_size, + gl_type, + static_cast(attribute.offset) + ); + } + else + { + glVertexArrayAttribIFormat + ( + m_gl_named_array, + static_cast(attribute.location), + gl_size, + gl_type, + static_cast(attribute.offset) + ); + } } - glEnableVertexAttribArray(static_cast(location)); } -void vertex_array::unbind(attribute_location_type location) +vertex_array::vertex_array() +{ + glCreateVertexArrays(1, &m_gl_named_array); +} + +vertex_array::~vertex_array() { - if (auto it = m_attributes.find(location); it != m_attributes.end()) - { - glBindVertexArray(gl_array_id); - glDisableVertexAttribArray(static_cast(location)); - - m_attributes.erase(it); - } - else - { - throw std::invalid_argument("Non-existent vertex attribute cannot be unbound."); - } + glDeleteVertexArrays(1, &m_gl_named_array); } } // namespace gl diff --git a/src/engine/gl/vertex-array.hpp b/src/engine/gl/vertex-array.hpp index 6c66b62..c0d6ec3 100644 --- a/src/engine/gl/vertex-array.hpp +++ b/src/engine/gl/vertex-array.hpp @@ -20,72 +20,49 @@ #ifndef ANTKEEPER_GL_VERTEX_ARRAY_HPP #define ANTKEEPER_GL_VERTEX_ARRAY_HPP -#include -#include -#include +#include +#include +#include namespace gl { -class rasterizer; -class vertex_buffer; - /** - * Vertex array object (VAO), which describes how vertex attributes are stored in vertex buffer objects (VBOs). - * - * @see gl::vertex_attribute - * @see gl::vertex_buffer + * Vertex arrays describes how vertex input attributes are stored in vertex buffers. */ class vertex_array { public: - /// Vertex attribute binding location type. - typedef unsigned int attribute_location_type; - - /// Maps vertex attribute to binding locations. - typedef std::unordered_map attribute_map_type; - - /// Constructs a vertex array. - vertex_array(); - - /// Destructs a vertex array. - ~vertex_array(); - - vertex_array(const vertex_array&) = delete; - vertex_array(vertex_array&&) = delete; - vertex_array& operator=(const vertex_array&) = delete; - vertex_array& operator=(vertex_array&&) = delete; - /** - * Binds a vertex attribute to the vertex array. + * Constructs a vertex array. * - * @param location Location to which the vertex attribute should be bound. - * @param attribute Vertex attribute to bind. + * @param attributes Vertex input attributes. * - * @except std::invalid_argument Cannot bind vertex attribute that has a null vertex buffer. - * @except std::invalid_argument Cannot bind vertex attribute that has an unsupported number of components. + * @except std::invalid_argument Vertex input attribute has unsupported format. */ - void bind(attribute_location_type location, const vertex_attribute& attribute); + /// @{ + explicit vertex_array(std::span attributes); + vertex_array(); + /// @} - /** - * Unbinds a vertex attribute from the vertex array. - * - * @param location Location of the vertex attribute to unbind. - * - * @except std::invalid_argument Non-existent vertex attribute cannot be unbound. - */ - void unbind(attribute_location_type location); + /// Destructs a vertex array. + ~vertex_array(); - /// Returns a const reference to the map of vertex attributes bound to this vertex array. - [[nodiscard]] inline const attribute_map_type& attributes() const noexcept + /// Returns the vertex array's vertex input attributes. + [[nodiscard]] inline constexpr const std::vector& attributes() const noexcept { return m_attributes; } + vertex_array(const vertex_array&) = delete; + vertex_array(vertex_array&&) = delete; + vertex_array& operator=(const vertex_array&) = delete; + vertex_array& operator=(vertex_array&&) = delete; + private: - friend class rasterizer; + friend class pipeline; - unsigned int gl_array_id{0}; - attribute_map_type m_attributes; + std::vector m_attributes; + unsigned int m_gl_named_array{0}; }; } // namespace gl diff --git a/src/engine/gl/vertex-attribute.hpp b/src/engine/gl/vertex-attribute.hpp deleted file mode 100644 index bef0ec7..0000000 --- a/src/engine/gl/vertex-attribute.hpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_GL_VERTEX_ATTRIBUTE_HPP -#define ANTKEEPER_GL_VERTEX_ATTRIBUTE_HPP - -#include -#include - -namespace gl { - -class vertex_buffer; - -enum class vertex_attribute_type: std::uint8_t -{ - int_8, - uint_8, - int_16, - uint_16, - int_32, - uint_32, - float_16, - float_32, - float_64 -}; - -/** - * Describes a vertex attribute within a vertex buffer. - * - * @see gl::vertex_buffer - * @see gl::vertex_array - */ -struct vertex_attribute -{ - /// Pointer to the vertex buffer containing vertex attribute data. - const vertex_buffer* buffer{nullptr}; - - /// Offset to the first component of the first instance of this attribute in the vertex buffer, in bytes. - std::size_t offset{0}; - - /// Number of bytes between consecutive instances of this attribute in the vertex buffer. A value of `0` indicates attribute instances are tightly packed. - std::size_t stride{0}; - - /// Data type of each component in the attribute. - vertex_attribute_type type{0}; - - /// Number of components per attribute instance. Supported values are `1`, `2`, `3`, and `4`. - std::uint8_t components{0}; - - /// `true` if fixed point data should be normalized, `false` otherwise. - bool normalized{}; -}; - -} // namespace gl - -#endif // ANTKEEPER_GL_VERTEX_ATTRIBUTE_HPP diff --git a/src/engine/gl/vertex-buffer.cpp b/src/engine/gl/vertex-buffer.cpp index 4ea9dea..cde12f2 100644 --- a/src/engine/gl/vertex-buffer.cpp +++ b/src/engine/gl/vertex-buffer.cpp @@ -19,22 +19,26 @@ #include #include -#include +#include +#include namespace gl { -static constexpr GLenum buffer_usage_lut[] = +namespace { - GL_STREAM_DRAW, - GL_STREAM_READ, - GL_STREAM_COPY, - GL_STATIC_DRAW, - GL_STATIC_READ, - GL_STATIC_COPY, - GL_DYNAMIC_DRAW, - GL_DYNAMIC_READ, - GL_DYNAMIC_COPY -}; + static constexpr GLenum buffer_usage_lut[] = + { + GL_STREAM_DRAW, + GL_STREAM_READ, + GL_STREAM_COPY, + GL_STATIC_DRAW, + GL_STATIC_READ, + GL_STATIC_COPY, + GL_DYNAMIC_DRAW, + GL_DYNAMIC_READ, + GL_DYNAMIC_COPY + }; +} vertex_buffer::vertex_buffer(buffer_usage usage, std::size_t size, std::span data): m_usage{usage}, @@ -45,23 +49,55 @@ vertex_buffer::vertex_buffer(buffer_usage usage, std::size_t size, std::span(m_usage)]; - glGenBuffers(1, &gl_buffer_id); - glBindBuffer(GL_ARRAY_BUFFER, gl_buffer_id); - glBufferData(GL_ARRAY_BUFFER, static_cast(m_size), data.empty() ? nullptr : data.data(), gl_usage); + const GLenum gl_usage = buffer_usage_lut[std::to_underlying(m_usage)]; + glCreateBuffers(1, &m_gl_named_buffer); + glNamedBufferData(m_gl_named_buffer, static_cast(m_size), data.empty() ? nullptr : data.data(), gl_usage); +} + +vertex_buffer::vertex_buffer(const vertex_buffer& buffer): + vertex_buffer(buffer.m_usage, buffer.m_size) +{ + copy(buffer, m_size); } -vertex_buffer::vertex_buffer(): - vertex_buffer(buffer_usage::static_draw, 0) +vertex_buffer::vertex_buffer(vertex_buffer&& buffer): + m_gl_named_buffer{std::exchange(buffer.m_gl_named_buffer, 0)}, + m_usage{std::move(buffer.m_usage)}, + m_size{std::exchange(buffer.m_size, 0)} {} vertex_buffer::~vertex_buffer() { - glDeleteBuffers(1, &gl_buffer_id); + if (m_gl_named_buffer) + { + glDeleteBuffers(1, &m_gl_named_buffer); + } +} + +vertex_buffer& vertex_buffer::operator=(const vertex_buffer& buffer) +{ + repurpose(buffer.m_usage, buffer.m_size); + copy(buffer, m_size); + + return *this; +} + +vertex_buffer& vertex_buffer::operator=(vertex_buffer&& buffer) +{ + if (m_gl_named_buffer) + { + glDeleteBuffers(1, &m_gl_named_buffer); + } + + m_gl_named_buffer = std::exchange(buffer.m_gl_named_buffer, 0); + m_usage = std::move(buffer.m_usage); + m_size = std::exchange(buffer.m_size, 0); + + return *this; } void vertex_buffer::repurpose(buffer_usage usage, std::size_t size, std::span data) @@ -78,22 +114,11 @@ void vertex_buffer::repurpose(buffer_usage usage, std::size_t size, std::span(m_usage)]; - glBindBuffer(GL_ARRAY_BUFFER, gl_buffer_id); - glBufferData(GL_ARRAY_BUFFER, static_cast(m_size), data.empty() ? nullptr : data.data(), gl_usage); -} - -void vertex_buffer::repurpose(buffer_usage usage, std::span data) -{ - repurpose(usage, m_size, data); -} - -void vertex_buffer::resize(std::size_t size, std::span data) -{ - repurpose(m_usage, size, data); + const auto gl_usage = buffer_usage_lut[std::to_underlying(m_usage)]; + glNamedBufferData(m_gl_named_buffer, static_cast(m_size), data.empty() ? nullptr : data.data(), gl_usage); } -void vertex_buffer::write(std::span data, std::size_t offset) +void vertex_buffer::write(std::size_t offset, std::span data) { // Ignore empty write operations if (data.empty()) @@ -107,13 +132,12 @@ void vertex_buffer::write(std::span data, std::size_t offset) throw std::out_of_range("Vertex buffer write operation exceeded buffer bounds."); } - glBindBuffer(GL_ARRAY_BUFFER, gl_buffer_id); - glBufferSubData(GL_ARRAY_BUFFER, static_cast(offset), static_cast(data.size()), data.data()); + glNamedBufferSubData(m_gl_named_buffer, static_cast(offset), static_cast(data.size()), data.data()); } -void vertex_buffer::read(std::span data, std::size_t offset) const +void vertex_buffer::read(std::size_t offset, std::span data) const { - // Abort empty read operations + // Ignore empty read operations if (data.empty()) { return; @@ -125,8 +149,7 @@ void vertex_buffer::read(std::span data, std::size_t offset) const throw std::out_of_range("Vertex buffer read operation exceeded buffer bounds."); } - glBindBuffer(GL_ARRAY_BUFFER, gl_buffer_id); - glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(offset), static_cast(data.size()), data.data()); + glGetNamedBufferSubData(m_gl_named_buffer, static_cast(offset), static_cast(data.size()), data.data()); } void vertex_buffer::copy(const vertex_buffer& read_buffer, std::size_t copy_size, std::size_t read_offset, std::size_t write_offset) @@ -141,9 +164,7 @@ void vertex_buffer::copy(const vertex_buffer& read_buffer, std::size_t copy_size throw std::out_of_range("Vertex buffer copy operation exceeded write buffer bounds."); } - glBindBuffer(GL_COPY_READ_BUFFER, read_buffer.gl_buffer_id); - glBindBuffer(GL_COPY_WRITE_BUFFER, gl_buffer_id); - glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, static_cast(read_offset), static_cast(write_offset), static_cast(copy_size)); + glCopyNamedBufferSubData(read_buffer.m_gl_named_buffer, m_gl_named_buffer, static_cast(read_offset), static_cast(write_offset), static_cast(copy_size)); } } // namespace gl diff --git a/src/engine/gl/vertex-buffer.hpp b/src/engine/gl/vertex-buffer.hpp index 8ab9a55..e242e95 100644 --- a/src/engine/gl/vertex-buffer.hpp +++ b/src/engine/gl/vertex-buffer.hpp @@ -20,14 +20,12 @@ #ifndef ANTKEEPER_GL_VERTEX_BUFFER_HPP #define ANTKEEPER_GL_VERTEX_BUFFER_HPP -#include #include +#include #include namespace gl { -class vertex_array; - /** * Vertex buffer object (VBO). */ @@ -41,22 +39,54 @@ public: * @param size Buffer size, in bytes. * @param data Buffer data. If empty, buffer data will not be set. * - * @except std::out_of_range Vertex buffer creation operation exceeded data bounds. + * @except std::out_of_range Vertex buffer construct operation exceeded data bounds. */ + /// @{ vertex_buffer(buffer_usage usage, std::size_t size, std::span data = {}); + inline explicit vertex_buffer(buffer_usage usage, std::span data = {}): + vertex_buffer(usage, data.size(), data) + {} + + inline vertex_buffer(): + vertex_buffer(buffer_usage::static_draw, 0) + {} + /// @} + + /** + * Constructs a copy of a vertex buffer. + * + * @param buffer Buffer to copy. + */ + vertex_buffer(const vertex_buffer& buffer); + /** - * Constructs an empty vertex buffer. + * Move-constructs a vertex buffer. + * + * @param buffer Buffer to move. */ - vertex_buffer(); + vertex_buffer(vertex_buffer&& buffer); /// Destroys a vertex buffer. ~vertex_buffer(); - vertex_buffer(const vertex_buffer&) = delete; - vertex_buffer(vertex_buffer&&) = delete; - vertex_buffer& operator=(const vertex_buffer&) = delete; - vertex_buffer& operator=(vertex_buffer&&) = delete; + /** + * Copies another vertex buffer. + * + * @param buffer Buffer to copy. + * + * @return Reference to this vertex buffer. + */ + vertex_buffer& operator=(const vertex_buffer& buffer); + + /** + * Moves another vertex buffer into this buffer. + * + * @param buffer Buffer to move. + * + * @return Reference to this vertex buffer. + */ + vertex_buffer& operator=(vertex_buffer&& buffer); /** * Repurposes the vertex buffer, changing its usage hint, size, and updating its data. @@ -69,7 +99,16 @@ public: */ /// @{ void repurpose(buffer_usage usage, std::size_t size, std::span data = {}); - void repurpose(buffer_usage usage, std::span data = {}); + + inline void repurpose(buffer_usage usage, std::span data) + { + repurpose(usage, data.size(), data); + } + + inline void repurpose(buffer_usage usage) + { + repurpose(usage, m_size, {}); + } /// @} /** @@ -80,7 +119,17 @@ public: * * @except std::out_of_range Vertex buffer resize operation exceeded data bounds. */ - void resize(std::size_t size, std::span data = {}); + /// @{ + inline void resize(std::size_t size, std::span data = {}) + { + repurpose(m_usage, size, data); + } + + inline void resize(std::span data) + { + repurpose(m_usage, data.size(), data); + } + /// @} /** * Writes data into the vertex buffer. @@ -90,7 +139,14 @@ public: * * @except std::out_of_range Vertex buffer write operation exceeded buffer bounds. */ - void write(std::span data, std::size_t offset = 0); + /// @{ + void write(std::size_t offset, std::span data); + + inline void write(std::span data) + { + write(0, data); + } + /// @} /** * Reads a subset of the buffer's data from the GL and returns it to the application. @@ -100,7 +156,14 @@ public: * * @except std::out_of_range Vertex buffer read operation exceeded buffer bounds. */ - void read(std::span data, std::size_t offset = 0) const; + /// @{ + void read(std::size_t offset, std::span data) const; + + inline void read(std::span data) const + { + read(0, data); + } + /// @} /** * Copies a subset of another vertex buffer's data into this vertex buffer. @@ -116,21 +179,21 @@ public: void copy(const vertex_buffer& read_buffer, std::size_t copy_size, std::size_t read_offset = 0, std::size_t write_offset = 0); /// Returns the size of the buffer's data, in bytes. - [[nodiscard]] inline std::size_t size() const noexcept + [[nodiscard]] inline constexpr std::size_t size() const noexcept { return m_size; } /// Return's the buffer's usage hint. - [[nodiscard]] inline buffer_usage usage() const noexcept + [[nodiscard]] inline constexpr buffer_usage usage() const noexcept { return m_usage; } private: - friend class vertex_array; + friend class pipeline; - unsigned int gl_buffer_id{0}; + unsigned int m_gl_named_buffer{0}; buffer_usage m_usage{buffer_usage::static_draw}; std::size_t m_size{0}; }; diff --git a/src/engine/gl/cube-map-layout.hpp b/src/engine/gl/vertex-input-attribute.hpp similarity index 57% rename from src/engine/gl/cube-map-layout.hpp rename to src/engine/gl/vertex-input-attribute.hpp index d0cbb46..f702e3d 100644 --- a/src/engine/gl/cube-map-layout.hpp +++ b/src/engine/gl/vertex-input-attribute.hpp @@ -17,35 +17,30 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GL_CUBE_MAP_LAYOUT_HPP -#define ANTKEEPER_GL_CUBE_MAP_LAYOUT_HPP +#ifndef ANTKEEPER_GL_VERTEX_INPUT_ATTRIBUTE_HPP +#define ANTKEEPER_GL_VERTEX_INPUT_ATTRIBUTE_HPP +#include #include namespace gl { -/// Cube map layout types. -enum class cube_map_layout: std::uint8_t +/// Vertex input attribute. +struct vertex_input_attribute { - /// Faces are stored consecutively in a vertical column. - column = 1, + /// Shader input location number for this attribute. + std::uint32_t location{0}; - /// Faces are stored consecutively in a horizontal row. - row, + /// Binding number which this attribute takes its data from. + std::uint32_t binding{0}; - /// Faces are stored in a vertical cross. - vertical_cross, + /// Size and type of the vertex attribute data. + gl::format format{gl::format::undefined}; - /// Faces are stored in a horizontal cross. - horizontal_cross, - - /// Faces are stored in an equirectangular projection. - equirectangular, - - /// Faces are stored in a spherical projection. - spherical + /// Byte offset of this attribute relative to the start of an element in the vertex input binding. + std::uint32_t offset{0}; }; } // namespace gl -#endif // ANTKEEPER_GL_CUBE_MAP_LAYOUT_HPP +#endif // ANTKEEPER_GL_VERTEX_INPUT_ATTRIBUTE_HPP diff --git a/src/engine/gl/vertex-input-binding.hpp b/src/engine/gl/vertex-input-binding.hpp new file mode 100644 index 0000000..97ba35d --- /dev/null +++ b/src/engine/gl/vertex-input-binding.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_VERTEX_INPUT_BINDING_HPP +#define ANTKEEPER_GL_VERTEX_INPUT_BINDING_HPP + +#include +#include + +namespace gl { + +/// Vertex input binding. +struct vertex_input_binding +{ + /// Binding number that this structure describes. + std::uint32_t binding{0}; + + /// Byte stride between consecutive elements within the buffer. + std::uint32_t stride{0}; + + /// Specifies whether vertex attribute addressing is a function of the vertex index or of the instance index. + vertex_input_rate input_rate{vertex_input_rate::vertex}; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_VERTEX_INPUT_BINDING_HPP diff --git a/src/engine/gl/vertex-input-rate.hpp b/src/engine/gl/vertex-input-rate.hpp new file mode 100644 index 0000000..04db1f5 --- /dev/null +++ b/src/engine/gl/vertex-input-rate.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_VERTEX_INPUT_RATE_HPP +#define ANTKEEPER_GL_VERTEX_INPUT_RATE_HPP + +#include + +namespace gl { + +/// Rate at which vertex attributes are pulled from buffers. +enum class vertex_input_rate: std::uint8_t +{ + /// Vertex attribute addressing is a function of the vertex index. + vertex, + + /// Vertex attribute addressing is a function of the instance index. + instance +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_VERTEX_INPUT_RATE_HPP diff --git a/src/engine/gl/viewport.hpp b/src/engine/gl/viewport.hpp new file mode 100644 index 0000000..2391755 --- /dev/null +++ b/src/engine/gl/viewport.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GL_VIEWPORT_HPP +#define ANTKEEPER_GL_VIEWPORT_HPP + +#include + +namespace gl { + +/** + * Viewport position, dimensions, and depth range. + */ +struct viewport +{ + /// X-coordinate of the viewport's lower left corner. + float x{0.0f}; + + /// Y-coordinate of the viewport's lower left corner. + float y{0.0f}; + + /// Width of the viewport. + float width{0.0f}; + + /// Height of the viewport. + float height{0.0f}; + + /// Minimum depth range of the viewport. + float min_depth{0.0f}; + + /// Maximum depth range of the viewport. + float max_depth{1.0f}; +}; + +} // namespace gl + +#endif // ANTKEEPER_GL_VIEWPORT_HPP diff --git a/src/engine/math/vector.hpp b/src/engine/math/vector.hpp index 2f696b0..ce08b63 100644 --- a/src/engine/math/vector.hpp +++ b/src/engine/math/vector.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -1788,6 +1789,49 @@ inline constexpr vector& operator/=(vector& x, T y) noexcept } /// @} +/** + * Tests two vector for equality + * + * @param x First value. + * @param y Second value. + * + * @return `true` if the vectors are identical, `false` otherwise. + */ +template +inline constexpr bool operator==(const vector& x, const vector& y) noexcept +{ + // if consteval + // { + for (std::size_t i = 0; i < N; ++i) + { + if (x[i] != y[i]) + { + return false; + } + } + + return true; + // } + // else + // { + // !std::memcmp(x.data(), y.data(), N * sizeof(T)); + // } +} + +/** + * Tests two vector for inequality + * + * @param x First value. + * @param y Second value. + * + * @return `false` if the vectors are identical, `true` otherwise. + */ +template +inline constexpr bool operator!=(const vector& x, const vector& y) noexcept +{ + return !(x == y); +} + } // namespace operators } // namespace math diff --git a/src/engine/render/material-variable.hpp b/src/engine/render/material-variable.hpp index 6e10839..d38363f 100644 --- a/src/engine/render/material-variable.hpp +++ b/src/engine/render/material-variable.hpp @@ -22,10 +22,7 @@ #include #include -#include -#include -#include -#include +#include #include #include #include diff --git a/src/engine/render/material.cpp b/src/engine/render/material.cpp index 9760a8f..3df2a2e 100644 --- a/src/engine/render/material.cpp +++ b/src/engine/render/material.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -115,10 +115,10 @@ void material::rehash() noexcept m_hash = shader_template->hash(); } - m_hash = hash::combine(m_hash, std::hash{}(two_sided)); - m_hash = hash::combine(m_hash, std::hash{}(blend_mode)); - m_hash = hash::combine(m_hash, std::hash{}(shadow_mode)); - m_hash = hash::combine(m_hash, std::hash{}(flags)); + m_hash = hash_combine(m_hash, std::hash{}(two_sided)); + m_hash = hash_combine(m_hash, std::hash{}(blend_mode)); + m_hash = hash_combine(m_hash, std::hash{}(shadow_mode)); + m_hash = hash_combine(m_hash, std::hash{}(flags)); } } // namespace render diff --git a/src/engine/render/model.cpp b/src/engine/render/model.cpp index 0c05f2e..4f4688a 100644 --- a/src/engine/render/model.cpp +++ b/src/engine/render/model.cpp @@ -20,32 +20,20 @@ #include #include #include -#include -#include -#include +#include #include #include +#include #include -namespace render { - -model::model() -{ - vertex_array = std::make_shared(); - vertex_buffer = std::make_shared(); -} - -} // namespace render - inline constexpr std::uint16_t vertex_attribute_position = 0b0000000000000001; inline constexpr std::uint16_t vertex_attribute_uv = 0b0000000000000010; inline constexpr std::uint16_t vertex_attribute_normal = 0b0000000000000100; inline constexpr std::uint16_t vertex_attribute_tangent = 0b0000000000001000; inline constexpr std::uint16_t vertex_attribute_color = 0b0000000000010000; -inline constexpr std::uint16_t vertex_attribute_bone = 0b0000000000100000; -inline constexpr std::uint16_t vertex_attribute_barycentric = 0b0000000001000000; +inline constexpr std::uint16_t vertex_attribute_bone_index = 0b0000000000100000; +inline constexpr std::uint16_t vertex_attribute_bone_weight = 0b0000000001000000; inline constexpr std::uint16_t vertex_attribute_morph_target = 0b0000000010000000; -inline constexpr std::uint16_t vertex_attribute_index = 0b0000000100000000; template <> std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) @@ -54,9 +42,9 @@ std::unique_ptr resource_loader::load(::resource_m std::uint16_t vertex_format_flags = 0; ctx.read16(reinterpret_cast(&vertex_format_flags), 1); - // Read bone per vertex (if any) + // Read bones per vertex (if any) std::uint8_t bones_per_vertex = 0; - if (vertex_format_flags & vertex_attribute_bone) + if ((vertex_format_flags & vertex_attribute_bone_index) || (vertex_format_flags & vertex_attribute_bone_weight)) { ctx.read8(reinterpret_cast(&bones_per_vertex), 1); } @@ -65,49 +53,48 @@ std::unique_ptr resource_loader::load(::resource_m std::uint32_t vertex_count = 0; ctx.read32(reinterpret_cast(&vertex_count), 1); - // Determine vertex size - std::size_t vertex_size = 0; + // Determine vertex stride + std::size_t vertex_stride = 0; if (vertex_format_flags & vertex_attribute_position) { - vertex_size += sizeof(float) * 3; + vertex_stride += sizeof(float) * 3; } if (vertex_format_flags & vertex_attribute_uv) { - vertex_size += sizeof(float) * 2; + vertex_stride += sizeof(float) * 2; } if (vertex_format_flags & vertex_attribute_normal) { - vertex_size += sizeof(float) * 3; + vertex_stride += sizeof(float) * 3; } if (vertex_format_flags & vertex_attribute_tangent) { - vertex_size += sizeof(float) * 4; + vertex_stride += sizeof(float) * 4; } if (vertex_format_flags & vertex_attribute_color) { - vertex_size += sizeof(float) * 4; + vertex_stride += sizeof(float) * 4; } - if (vertex_format_flags & vertex_attribute_bone) + if (vertex_format_flags & vertex_attribute_bone_index) { - vertex_size += sizeof(std::uint16_t) * bones_per_vertex; - vertex_size += sizeof(float) * bones_per_vertex; + vertex_stride += sizeof(std::uint16_t) * bones_per_vertex; } - if (vertex_format_flags & vertex_attribute_barycentric) + if (vertex_format_flags & vertex_attribute_bone_weight) { - vertex_size += sizeof(float) * 3; + vertex_stride += sizeof(float) * bones_per_vertex; } if (vertex_format_flags & vertex_attribute_morph_target) { - vertex_size += sizeof(float) * 3; + vertex_stride += sizeof(float) * 3; } // Allocate vertex data - std::vector vertex_data(vertex_count * vertex_size); + std::vector vertex_data(vertex_count * vertex_stride); // Read vertices if constexpr (std::endian::native == std::endian::little) { - ctx.read8(vertex_data.data(), vertex_count * vertex_size); + ctx.read8(vertex_data.data(), vertex_count * vertex_stride); } else { @@ -139,18 +126,15 @@ std::unique_ptr resource_loader::load(::resource_m ctx.read32(vertex_data_offset, 4); vertex_data_offset += sizeof(float) * 4; } - if (vertex_format_flags & vertex_attribute_bone) + if (vertex_format_flags & vertex_attribute_bone_index) { - ctx.read32(vertex_data_offset, bones_per_vertex); - ctx.read32(vertex_data_offset, bones_per_vertex); - + ctx.read16(vertex_data_offset, bones_per_vertex); vertex_data_offset += sizeof(std::uint16_t) * bones_per_vertex; - vertex_data_offset += sizeof(float) * bones_per_vertex; } - if (vertex_format_flags & vertex_attribute_barycentric) + if (vertex_format_flags & vertex_attribute_bone_weight) { - ctx.read32(vertex_data_offset, 3); - vertex_data_offset += sizeof(float) * 3; + ctx.read32(vertex_data_offset, bones_per_vertex); + vertex_data_offset += sizeof(float) * bones_per_vertex; } if (vertex_format_flags & vertex_attribute_morph_target) { @@ -163,81 +147,146 @@ std::unique_ptr resource_loader::load(::resource_m // Allocate model std::unique_ptr model = std::make_unique(); - // Resize model VBO and upload vertex data - gl::vertex_buffer& vbo = *model->get_vertex_buffer(); - vbo.resize(vertex_data.size(), vertex_data); + // Build model vertex buffer + model->get_vertex_buffer() = std::make_shared(gl::buffer_usage::static_draw, vertex_data); + model->set_vertex_offset(0); + model->set_vertex_stride(vertex_stride); // Free vertex data vertex_data.clear(); - // Bind vertex attributes to VAO - gl::vertex_array& vao = *model->get_vertex_array(); - gl::vertex_attribute attribute; - attribute.buffer = &vbo; - attribute.offset = 0; - attribute.stride = vertex_size; + // Build vertex input attributes + std::vector attributes(std::popcount(vertex_format_flags)); + + std::uint32_t vertex_offset = 0; + std::uint32_t attribute_index = 0; + if (vertex_format_flags & vertex_attribute_position) { - attribute.type = gl::vertex_attribute_type::float_32; - attribute.components = 3; - vao.bind(render::vertex_attribute::position, attribute); - attribute.offset += sizeof(float) * attribute.components; + auto& attribute = attributes[attribute_index]; + attribute.location = render::vertex_attribute_location::position; + attribute.binding = 0; + attribute.format = gl::format::r32g32b32_sfloat; + attribute.offset = vertex_offset; + + vertex_offset += 3 * sizeof(float); + ++attribute_index; } if (vertex_format_flags & vertex_attribute_uv) { - attribute.type = gl::vertex_attribute_type::float_32; - attribute.components = 2; - vao.bind(render::vertex_attribute::uv, attribute); - attribute.offset += sizeof(float) * attribute.components; + auto& attribute = attributes[attribute_index]; + attribute.location = render::vertex_attribute_location::uv; + attribute.binding = 0; + attribute.format = gl::format::r32g32_sfloat; + attribute.offset = vertex_offset; + + vertex_offset += 2 * sizeof(float); + ++attribute_index; } if (vertex_format_flags & vertex_attribute_normal) { - attribute.type = gl::vertex_attribute_type::float_32; - attribute.components = 3; - vao.bind(render::vertex_attribute::normal, attribute); - attribute.offset += sizeof(float) * attribute.components; + auto& attribute = attributes[attribute_index]; + attribute.location = render::vertex_attribute_location::normal; + attribute.binding = 0; + attribute.format = gl::format::r32g32b32_sfloat; + attribute.offset = vertex_offset; + + vertex_offset += 3 * sizeof(float); + ++attribute_index; } if (vertex_format_flags & vertex_attribute_tangent) { - attribute.type = gl::vertex_attribute_type::float_32; - attribute.components = 4; - vao.bind(render::vertex_attribute::tangent, attribute); - attribute.offset += sizeof(float) * attribute.components; + auto& attribute = attributes[attribute_index]; + attribute.location = render::vertex_attribute_location::tangent; + attribute.binding = 0; + attribute.format = gl::format::r32g32b32a32_sfloat; + attribute.offset = vertex_offset; + + vertex_offset += 4 * sizeof(float); + ++attribute_index; } if (vertex_format_flags & vertex_attribute_color) { - attribute.type = gl::vertex_attribute_type::float_32; - attribute.components = 4; - vao.bind(render::vertex_attribute::color, attribute); - attribute.offset += sizeof(float) * attribute.components; + auto& attribute = attributes[attribute_index]; + attribute.location = render::vertex_attribute_location::color; + attribute.binding = 0; + attribute.format = gl::format::r32g32b32a32_sfloat; + attribute.offset = vertex_offset; + + vertex_offset += 4 * sizeof(float); + ++attribute_index; } - if (vertex_format_flags & vertex_attribute_bone) + if (vertex_format_flags & vertex_attribute_bone_index) { - attribute.type = gl::vertex_attribute_type::uint_16; - attribute.components = bones_per_vertex; - vao.bind(render::vertex_attribute::bone_index, attribute); - attribute.offset += sizeof(std::uint16_t) * attribute.components; + auto& attribute = attributes[attribute_index]; + attribute.location = render::vertex_attribute_location::bone_index; + attribute.binding = 0; + switch (bones_per_vertex) + { + case 1: + attribute.format = gl::format::r16_uint; + break; + case 2: + attribute.format = gl::format::r16g16_uint; + break; + case 3: + attribute.format = gl::format::r16g16b16_uint; + break; + case 4: + attribute.format = gl::format::r16g16b16a16_uint; + break; + default: + attribute.format = gl::format::undefined; + break; + } + attribute.offset = vertex_offset; - attribute.type = gl::vertex_attribute_type::float_32; - attribute.components = bones_per_vertex; - vao.bind(render::vertex_attribute::bone_weight, attribute); - attribute.offset += sizeof(float) * attribute.components; + vertex_offset += bones_per_vertex * sizeof(std::uint16_t); + ++attribute_index; } - if (vertex_format_flags & vertex_attribute_barycentric) + if (vertex_format_flags & vertex_attribute_bone_weight) { - attribute.type = gl::vertex_attribute_type::float_32; - attribute.components = 3; - vao.bind(render::vertex_attribute::barycentric, attribute); - attribute.offset += sizeof(float) * attribute.components; + auto& attribute = attributes[attribute_index]; + attribute.location = render::vertex_attribute_location::bone_weight; + attribute.binding = 0; + switch (bones_per_vertex) + { + case 1: + attribute.format = gl::format::r32_sfloat; + break; + case 2: + attribute.format = gl::format::r32g32_sfloat; + break; + case 3: + attribute.format = gl::format::r32g32b32_sfloat; + break; + case 4: + attribute.format = gl::format::r32g32b32a32_sfloat; + break; + default: + attribute.format = gl::format::undefined; + break; + } + attribute.offset = vertex_offset; + + vertex_offset += bones_per_vertex * sizeof(float); + ++attribute_index; } if (vertex_format_flags & vertex_attribute_morph_target) { - attribute.type = gl::vertex_attribute_type::float_32; - attribute.components = 3; - vao.bind(render::vertex_attribute::target, attribute); - //attribute.offset += sizeof(float) * attribute.components; + auto& attribute = attributes[attribute_index]; + attribute.location = render::vertex_attribute_location::target; + attribute.binding = 0; + attribute.format = gl::format::r32g32b32_sfloat; + attribute.offset = vertex_offset; + + // vertex_offset += 3 * sizeof(float); + // ++attribute_index; } + // Build model vertex array + model->get_vertex_array() = std::make_shared(attributes); + // Read model bounds ctx.read32(reinterpret_cast(&model->get_bounds()), 6); @@ -262,14 +311,14 @@ std::unique_ptr resource_loader::load(::resource_m // Generate group ID by hashing material name group.id = hash::fnv1a32(material_name); - // Set group drawing mode - group.drawing_mode = gl::drawing_mode::triangles; + // Set group primitive topology + group.primitive_topology = gl::primitive_topology::triangle_list; - // Read offset to index of first vertex - ctx.read32(reinterpret_cast(&group.start_index), 1); + // Read index of first vertex + ctx.read32(reinterpret_cast(&group.first_vertex), 1); // Read vertex count - ctx.read32(reinterpret_cast(&group.index_count), 1); + ctx.read32(reinterpret_cast(&group.vertex_count), 1); // Slugify material filename std::string material_filename = material_name + ".mtl"; @@ -280,7 +329,7 @@ std::unique_ptr resource_loader::load(::resource_m } // Read skeleton - if (vertex_format_flags & vertex_attribute_bone) + if ((vertex_format_flags & vertex_attribute_bone_index) || (vertex_format_flags & vertex_attribute_bone_weight)) { ::skeleton& skeleton = model->get_skeleton(); diff --git a/src/engine/render/model.hpp b/src/engine/render/model.hpp index 7c52163..f67c183 100644 --- a/src/engine/render/model.hpp +++ b/src/engine/render/model.hpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include #include @@ -39,11 +39,11 @@ namespace render { */ struct model_group { - hash::fnv1a32_t id; - gl::drawing_mode drawing_mode; - std::uint32_t start_index; - std::uint32_t index_count; - std::shared_ptr material; + hash::fnv1a32_t id{}; + gl::primitive_topology primitive_topology{gl::primitive_topology::triangle_list}; + std::uint32_t first_vertex{}; + std::uint32_t vertex_count{}; + std::shared_ptr material; }; /** @@ -56,9 +56,24 @@ public: using aabb_type = geom::box; /** - * Constructs a model. + * Sets the byte offset to the first vertex in the vertex buffer. + * + * @param offset Byte offset into the vertex buffer. */ - model(); + inline void set_vertex_offset(std::size_t offset) noexcept + { + m_vertex_offset = offset; + } + + /** + * Sets the byte stride between consecutive elements within the vertex buffer. + * + * @param stride Byte stride between consecutive elements within the vertex buffer. + */ + inline void set_vertex_stride(std::size_t stride) noexcept + { + m_vertex_stride = stride; + } /** * Returns the vertex array associated with this model. @@ -66,11 +81,11 @@ public: /// @{ [[nodiscard]] inline const std::shared_ptr& get_vertex_array() const noexcept { - return vertex_array; + return m_vertex_array; } [[nodiscard]] inline std::shared_ptr& get_vertex_array() noexcept { - return vertex_array; + return m_vertex_array; } /// @} @@ -80,25 +95,37 @@ public: /// @{ [[nodiscard]] inline const std::shared_ptr& get_vertex_buffer() const noexcept { - return vertex_buffer; + return m_vertex_buffer; } [[nodiscard]] inline std::shared_ptr& get_vertex_buffer() noexcept { - return vertex_buffer; + return m_vertex_buffer; } /// @} + /// Returns the byte offset to the first vertex in the vertex buffer. + [[nodiscard]] inline constexpr std::size_t get_vertex_offset() const noexcept + { + return m_vertex_offset; + } + + /// Returns the byte stride between consecutive elements within the vertex buffer. + [[nodiscard]] inline constexpr std::size_t get_vertex_stride() const noexcept + { + return m_vertex_stride; + } + /** * Returns the bounds of the model. */ /// @{ [[nodiscard]] inline const aabb_type& get_bounds() const noexcept { - return bounds; + return m_bounds; } [[nodiscard]] inline aabb_type& get_bounds() noexcept { - return bounds; + return m_bounds; } /// @} @@ -108,11 +135,11 @@ public: /// @{ [[nodiscard]] inline const std::vector& get_groups() const noexcept { - return groups; + return m_groups; } [[nodiscard]] inline std::vector& get_groups() noexcept { - return groups; + return m_groups; } /// @} @@ -122,20 +149,22 @@ public: /// @{ [[nodiscard]] inline const ::skeleton& get_skeleton() const noexcept { - return skeleton; + return m_skeleton; } [[nodiscard]] inline ::skeleton& get_skeleton() noexcept { - return skeleton; + return m_skeleton; } /// @} private: - std::shared_ptr vertex_array; - std::shared_ptr vertex_buffer; - aabb_type bounds{{0, 0, 0}, {0, 0, 0}}; - std::vector groups; - ::skeleton skeleton; + std::shared_ptr m_vertex_array; + std::shared_ptr m_vertex_buffer; + std::size_t m_vertex_offset{}; + std::size_t m_vertex_stride{}; + aabb_type m_bounds{{0, 0, 0}, {0, 0, 0}}; + std::vector m_groups; + ::skeleton m_skeleton; }; } // namespace render diff --git a/src/engine/render/operation.hpp b/src/engine/render/operation.hpp index 81253b2..971cb7b 100644 --- a/src/engine/render/operation.hpp +++ b/src/engine/render/operation.hpp @@ -22,7 +22,8 @@ #include #include -#include +#include +#include #include #include #include @@ -35,16 +36,21 @@ namespace render { */ struct operation { + gl::primitive_topology primitive_topology{gl::primitive_topology::triangle_list}; const gl::vertex_array* vertex_array{nullptr}; - gl::drawing_mode drawing_mode{gl::drawing_mode::triangles}; - std::size_t start_index{}; - std::size_t index_count{}; + const gl::vertex_buffer* vertex_buffer{nullptr}; + std::size_t vertex_offset{0}; + std::size_t vertex_stride{0}; + std::uint32_t first_vertex{0}; + std::uint32_t vertex_count{0}; + std::uint32_t first_instance{0}; + std::uint32_t instance_count{1}; + std::shared_ptr material; math::fmat4 transform{math::fmat4::identity()}; float depth{}; - std::size_t instance_count{}; std::span matrix_palette{}; std::uint32_t layer_mask{}; diff --git a/src/engine/render/pass.cpp b/src/engine/render/pass.cpp index add2228..54c7edd 100644 --- a/src/engine/render/pass.cpp +++ b/src/engine/render/pass.cpp @@ -21,10 +21,10 @@ namespace render { -pass::pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer): - rasterizer(rasterizer), - framebuffer(framebuffer), - enabled(true) +pass::pass(gl::pipeline* pipeline, const gl::framebuffer* framebuffer): + m_pipeline(pipeline), + m_framebuffer(framebuffer), + m_enabled(true) {} pass::~pass() @@ -32,12 +32,17 @@ pass::~pass() void pass::set_enabled(bool enabled) { - this->enabled = enabled; + m_enabled = enabled; } void pass::set_framebuffer(const gl::framebuffer* framebuffer) { - this->framebuffer = framebuffer; + m_framebuffer = framebuffer; +} + +void pass::clear() +{ + m_pipeline->clear_attachments(m_clear_mask, m_clear_value); } } // namespace render diff --git a/src/engine/render/pass.hpp b/src/engine/render/pass.hpp index a5cb10a..77fbdd6 100644 --- a/src/engine/render/pass.hpp +++ b/src/engine/render/pass.hpp @@ -20,8 +20,9 @@ #ifndef ANTKEEPER_RENDER_PASS_HPP #define ANTKEEPER_RENDER_PASS_HPP -#include +#include #include +#include #include namespace render { @@ -32,30 +33,42 @@ namespace render { class pass { public: - pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer); + pass(gl::pipeline* pipeline, const gl::framebuffer* framebuffer); virtual ~pass(); virtual void render(render::context& ctx) = 0; void set_enabled(bool enabled); - bool is_enabled() const; + + [[nodiscard]] inline constexpr bool is_enabled() const noexcept + { + return m_enabled; + } void set_framebuffer(const gl::framebuffer* framebuffer); + + inline void set_clear_mask(std::uint8_t mask) noexcept + { + m_clear_mask = mask; + } + + inline void set_clear_value(const gl::clear_value& value) noexcept + { + m_clear_value = value; + } + + void clear(); protected: - gl::rasterizer* rasterizer; - const gl::framebuffer* framebuffer; - + gl::pipeline* m_pipeline; + const gl::framebuffer* m_framebuffer; + std::uint8_t m_clear_mask{}; + gl::clear_value m_clear_value; + private: - bool enabled; + bool m_enabled; }; -inline bool pass::is_enabled() const -{ - return enabled; -} - } // namespace render #endif // ANTKEEPER_RENDER_PASS_HPP - diff --git a/src/engine/render/passes/bloom-pass.cpp b/src/engine/render/passes/bloom-pass.cpp index f8937b8..fd7d081 100644 --- a/src/engine/render/passes/bloom-pass.cpp +++ b/src/engine/render/passes/bloom-pass.cpp @@ -19,37 +19,28 @@ #include #include -#include +#include #include #include #include #include #include -#include -#include -#include -#include -#include -#include +#include +#include #include #include #include -#include namespace render { -bloom_pass::bloom_pass(gl::rasterizer* rasterizer, resource_manager* resource_manager): - pass(rasterizer, nullptr), - source_texture(nullptr), - mip_chain_length(0), - filter_radius(0.005f), - corrected_filter_radius{filter_radius, filter_radius} +bloom_pass::bloom_pass(gl::pipeline* pipeline, resource_manager* resource_manager): + pass(pipeline, nullptr) { // Load downsample shader template auto downsample_shader_template = resource_manager->load("bloom-downsample.glsl"); // Build downsample shader program with Karis averaging - downsample_karis_shader = downsample_shader_template->build + m_downsample_karis_shader = downsample_shader_template->build ( { {"KARIS_AVERAGE", std::string()} @@ -57,213 +48,201 @@ bloom_pass::bloom_pass(gl::rasterizer* rasterizer, resource_manager* resource_ma ); // Build downsample shader program without Karis averaging - downsample_shader = downsample_shader_template->build(); + m_downsample_shader = downsample_shader_template->build(); // Load upsample shader template auto upsample_shader_template = resource_manager->load("bloom-upsample.glsl"); // Build upsample shader program - upsample_shader = upsample_shader_template->build(); + m_upsample_shader = upsample_shader_template->build(); + + // Construct framebuffer texture sampler + m_sampler = std::make_shared + ( + gl::sampler_filter::linear, + gl::sampler_filter::linear, + gl::sampler_mipmap_mode::linear, + gl::sampler_address_mode::clamp_to_edge, + gl::sampler_address_mode::clamp_to_edge + ); + + // Allocate empty vertex array + m_vertex_array = std::make_unique(); } void bloom_pass::render(render::context& ctx) { // Execute command buffer - for (const auto& command: command_buffer) + for (const auto& command: m_command_buffer) { command(); } } -void bloom_pass::resize() +void bloom_pass::set_source_texture(std::shared_ptr texture) { - unsigned int source_width = 1; - unsigned int source_height = 1; - if (source_texture) + if (m_source_texture != texture) { - // Get source texture dimensions - source_width = source_texture->get_width(); - source_height = source_texture->get_height(); + m_source_texture = texture; - // Correct filter radius according to source texture aspect ratio - const float aspect_ratio = static_cast(source_height) / static_cast(source_width); - corrected_filter_radius = {filter_radius * aspect_ratio, filter_radius}; - } - - // Resize mip chain - for (unsigned int i = 0; i < mip_chain_length; ++i) - { - // Calculate mip dimensions - unsigned int mip_width = std::max(1, source_width >> (i + 1)); - unsigned int mip_height = std::max(1, source_height >> (i + 1)); - - // Resize mip texture - textures[i]->resize(mip_width, mip_height, nullptr); - - // Resize mip framebuffer - framebuffers[i]->resize({(int)mip_width, (int)mip_height}); + rebuild_mip_chain(); + correct_filter_radius(); + rebuild_command_buffer(); } } -void bloom_pass::set_source_texture(const gl::texture_2d* texture) +void bloom_pass::set_mip_chain_length(unsigned int length) +{ + m_mip_chain_length = length; + rebuild_mip_chain(); + rebuild_command_buffer(); +} + +void bloom_pass::set_filter_radius(float radius) { - if (texture != source_texture) - { - if (texture) - { - if (source_texture) - { - if (texture->get_width() != source_texture->get_width() || texture->get_height() != source_texture->get_height()) - { - source_texture = texture; - resize(); - } - else - { - source_texture = texture; - } - } - else - { - source_texture = texture; - resize(); - rebuild_command_buffer(); - } - } - else - { - source_texture = nullptr; - rebuild_command_buffer(); - } - } + m_filter_radius = radius; + correct_filter_radius(); } -void bloom_pass::set_mip_chain_length(unsigned int length) +void bloom_pass::rebuild_mip_chain() { - unsigned int source_width = 1; - unsigned int source_height = 1; - if (source_texture) + if (m_source_texture && m_mip_chain_length) { - // Get source texture dimensions - source_width = source_texture->get_width(); - source_height = source_texture->get_height(); - } - - if (length > mip_chain_length) - { - // Generate additional framebuffers - for (unsigned int i = mip_chain_length; i < length; ++i) + // Rebuild target image + m_target_image = std::make_shared + ( + gl::format::r16g16b16_sfloat, + m_source_texture->get_image_view()->get_image()->get_dimensions()[0], + m_source_texture->get_image_view()->get_image()->get_dimensions()[1], + m_mip_chain_length + ); + + m_target_textures.resize(m_mip_chain_length); + m_target_framebuffers.resize(m_mip_chain_length); + for (unsigned int i = 0; i < m_mip_chain_length; ++i) { - // Calculate mip resolution - unsigned int mip_width = std::max(1, source_width >> (i + 1)); - unsigned int mip_height = std::max(1, source_height >> (i + 1)); - - // Generate mip texture - auto texture = std::make_unique(mip_width, mip_height, gl::pixel_type::float_16, gl::pixel_format::rgb); - texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); - texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - texture->set_max_anisotropy(0.0f); - - // Generate mip framebuffer - auto framebuffer = std::make_unique(mip_width, mip_height); - framebuffer->attach(gl::framebuffer_attachment_type::color, texture.get()); + // Rebuild mip texture + m_target_textures[i] = std::make_shared + ( + std::make_shared + ( + m_target_image, + m_target_image->get_format(), + i, + 1 + ), + m_sampler + ); - textures.push_back(std::move(texture)); - framebuffers.emplace_back(std::move(framebuffer)); + // Rebuild mip framebuffer + const gl::framebuffer_attachment attachments[1] = + {{ + gl::color_attachment_bit, + m_target_textures[i]->get_image_view(), + 0 + }}; + m_target_framebuffers[i] = std::make_shared + ( + attachments, + m_target_image->get_dimensions()[0] >> i, + m_target_image->get_dimensions()[1] >> i + ); } } - else if (length < mip_chain_length) + else { - framebuffers.resize(length); - textures.resize(length); + m_target_image = nullptr; + m_target_textures.clear(); + m_target_framebuffers.clear(); } - - // Update mip chain length - mip_chain_length = length; - - // Rebuild command buffer - rebuild_command_buffer(); } -void bloom_pass::set_filter_radius(float radius) +void bloom_pass::correct_filter_radius() { - filter_radius = radius; - - // Get aspect ratio of source texture + // Get aspect ratio of target image float aspect_ratio = 1.0f; - if (source_texture) + if (m_target_image) { - aspect_ratio = static_cast(source_texture->get_height()) / static_cast(source_texture->get_width()); + aspect_ratio = static_cast(m_target_image->get_dimensions()[1]) / + static_cast(m_target_image->get_dimensions()[0]); } - // Correct filter radius according to source texture aspect ratio - corrected_filter_radius = {filter_radius * aspect_ratio, filter_radius}; + // Correct filter radius according to target image aspect ratio + m_corrected_filter_radius = {m_filter_radius * aspect_ratio, m_filter_radius}; } void bloom_pass::rebuild_command_buffer() { - command_buffer.clear(); + m_command_buffer.clear(); - if (!source_texture || - !mip_chain_length || - !downsample_karis_shader || - !downsample_shader || - !upsample_shader) + if (!m_source_texture || + !m_mip_chain_length || + !m_downsample_karis_shader || + !m_downsample_shader || + !m_upsample_shader) { return; } // Setup downsample state - command_buffer.emplace_back + m_command_buffer.emplace_back ( - []() + [&]() { - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glDisable(GL_BLEND); + m_pipeline->set_primitive_topology(gl::primitive_topology::triangle_list); + m_pipeline->bind_vertex_array(m_vertex_array.get()); + m_pipeline->set_depth_test_enabled(false); + m_pipeline->set_cull_mode(gl::cull_mode::back); + m_pipeline->set_color_blend_enabled(false); } ); // Downsample first mip with Karis average - if (auto source_texture_var = downsample_karis_shader->variable("source_texture")) + if (auto source_texture_var = m_downsample_karis_shader->variable("source_texture")) { - command_buffer.emplace_back + m_command_buffer.emplace_back ( [&, source_texture_var]() { - rasterizer->use_program(*downsample_karis_shader); - rasterizer->use_framebuffer(*framebuffers[0]); - rasterizer->set_viewport(0, 0, textures[0]->get_width(), textures[0]->get_height()); + m_pipeline->bind_shader_program(m_downsample_karis_shader.get()); + m_pipeline->bind_framebuffer(m_target_framebuffers[0].get()); - source_texture_var->update(*source_texture); + const auto& target_dimensions = m_target_image->get_dimensions(); + const gl::viewport viewport[1] = {{0.0f, 0.0f, static_cast(target_dimensions[0]), static_cast(target_dimensions[1])}}; + m_pipeline->set_viewport(0, viewport); - rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); + source_texture_var->update(*m_source_texture); + + // Draw fullscreen triangle + m_pipeline->draw(3, 1, 0, 0); } ); } // Downsample remaining mips - if (mip_chain_length > 1) + if (m_mip_chain_length > 1) { - if (auto source_texture_var = downsample_shader->variable("source_texture")) + if (auto source_texture_var = m_downsample_shader->variable("source_texture")) { - command_buffer.emplace_back([&](){rasterizer->use_program(*downsample_shader);}); + m_command_buffer.emplace_back([&](){m_pipeline->bind_shader_program(m_downsample_shader.get());}); - for (int i = 1; i < static_cast(mip_chain_length); ++i) + for (int i = 1; i < static_cast(m_mip_chain_length); ++i) { - command_buffer.emplace_back + m_command_buffer.emplace_back ( [&, source_texture_var, i]() { - rasterizer->use_framebuffer(*framebuffers[i]); - rasterizer->set_viewport(0, 0, textures[i]->get_width(), textures[i]->get_height()); + m_pipeline->bind_framebuffer(m_target_framebuffers[i].get()); + + const auto& target_dimensions = m_target_image->get_dimensions(); + const gl::viewport viewport[1] = {{0.0f, 0.0f, static_cast(target_dimensions[0] >> i), static_cast(target_dimensions[1] >> i)}}; + m_pipeline->set_viewport(0, viewport); // Use previous downsample texture as downsample source - source_texture_var->update(*textures[i - 1]); + source_texture_var->update(*m_target_textures[i - 1]); - rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); + // Draw fullscreen triangle + m_pipeline->draw(3, 1, 0, 0); } ); } @@ -271,43 +250,54 @@ void bloom_pass::rebuild_command_buffer() } // Setup upsample state - command_buffer.emplace_back + m_command_buffer.emplace_back ( [&]() { // Enable additive blending - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE); - glBlendEquation(GL_FUNC_ADD); + m_pipeline->set_color_blend_enabled(true); + m_pipeline->set_color_blend_equation + ({ + gl::blend_factor::one, + gl::blend_factor::one, + gl::blend_op::add, + gl::blend_factor::one, + gl::blend_factor::one, + gl::blend_op::add + }); // Bind upsample shader - rasterizer->use_program(*upsample_shader); + m_pipeline->bind_shader_program(m_upsample_shader.get()); } ); // Update upsample filter radius - if (auto filter_radius_var = upsample_shader->variable("filter_radius")) + if (auto filter_radius_var = m_upsample_shader->variable("filter_radius")) { - command_buffer.emplace_back([&, filter_radius_var](){filter_radius_var->update(corrected_filter_radius);}); + m_command_buffer.emplace_back([&, filter_radius_var](){filter_radius_var->update(m_corrected_filter_radius);}); } // Upsample - if (auto source_texture_var = upsample_shader->variable("source_texture")) + if (auto source_texture_var = m_upsample_shader->variable("source_texture")) { - for (int i = static_cast(mip_chain_length) - 1; i > 0; --i) + for (int i = static_cast(m_mip_chain_length) - 1; i > 0; --i) { const int j = i - 1; - command_buffer.emplace_back + m_command_buffer.emplace_back ( [&, source_texture_var, i, j]() { - rasterizer->use_framebuffer(*framebuffers[j]); - rasterizer->set_viewport(0, 0, textures[j]->get_width(), textures[j]->get_height()); + m_pipeline->bind_framebuffer(m_target_framebuffers[j].get()); + + const auto& target_dimensions = m_target_image->get_dimensions(); + const gl::viewport viewport[1] = {{0.0f, 0.0f, static_cast(target_dimensions[0] >> j), static_cast(target_dimensions[1] >> j)}}; + m_pipeline->set_viewport(0, viewport); - source_texture_var->update(*textures[i]); + source_texture_var->update(*m_target_textures[i]); - rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); + // Draw fullscreen triangle + m_pipeline->draw(3, 1, 0, 0); } ); } diff --git a/src/engine/render/passes/bloom-pass.hpp b/src/engine/render/passes/bloom-pass.hpp index bf7b255..e7b831b 100644 --- a/src/engine/render/passes/bloom-pass.hpp +++ b/src/engine/render/passes/bloom-pass.hpp @@ -26,7 +26,8 @@ #include #include #include -#include +#include +#include #include #include @@ -46,10 +47,10 @@ public: /** * Constructs a bloom pass. * - * @param rasterizer Rasterizer. + * @param pipeline Graphics pipeline. * @param resource_manager Resource manager. */ - bloom_pass(gl::rasterizer* rasterizer, resource_manager* resource_manager); + bloom_pass(gl::pipeline* pipeline, resource_manager* resource_manager); /** * Renders a bloom texture. @@ -69,7 +70,7 @@ public: * * @param texture Bloom source texture. */ - void set_source_texture(const gl::texture_2d* texture); + void set_source_texture(std::shared_ptr texture); /** * Sets the mip chain length. A length of `1` indicates a single stage bloom. @@ -88,31 +89,34 @@ public: /** * Returns the texture containing the bloom result. */ - const gl::texture_2d* get_bloom_texture() const; + [[nodiscard]] inline std::shared_ptr get_bloom_texture() const + { + return m_target_textures.empty() ? nullptr : m_target_textures.front(); + } private: + void rebuild_mip_chain(); + void correct_filter_radius(); void rebuild_command_buffer(); - const gl::texture_2d* source_texture; + std::shared_ptr m_source_texture; + std::shared_ptr m_target_image; + std::vector> m_target_textures; + std::vector> m_target_framebuffers; - std::unique_ptr downsample_karis_shader; - std::unique_ptr downsample_shader; - std::unique_ptr upsample_shader; + std::unique_ptr m_downsample_karis_shader; + std::unique_ptr m_downsample_shader; + std::unique_ptr m_upsample_shader; - unsigned int mip_chain_length; - std::vector> framebuffers; - std::vector> textures; - float filter_radius; - math::fvec2 corrected_filter_radius; + std::shared_ptr m_sampler; + std::unique_ptr m_vertex_array; + unsigned int m_mip_chain_length{0}; + float m_filter_radius{0.005f}; + math::fvec2 m_corrected_filter_radius{0.005f, 0.005f}; - std::vector> command_buffer; + std::vector> m_command_buffer; }; -inline const gl::texture_2d* bloom_pass::get_bloom_texture() const -{ - return textures.empty() ? nullptr : textures.front().get(); -} - } // namespace render #endif // ANTKEEPER_RENDER_BLOOM_PASS_HPP diff --git a/src/engine/render/passes/clear-pass.cpp b/src/engine/render/passes/clear-pass.cpp deleted file mode 100644 index 62cf27b..0000000 --- a/src/engine/render/passes/clear-pass.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include -#include -#include -#include - -namespace render { - -clear_pass::clear_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer): - pass(rasterizer, framebuffer), - clear_color_buffer(false), - clear_depth_buffer(false), - clear_stencil_buffer(false), - clear_color({0.0f, 0.0f, 0.0f, 0.0f}), - clear_depth(1.0f), - clear_stencil(0) -{} - -void clear_pass::render(render::context& ctx) -{ - for (const auto& command: command_buffer) - { - command(); - } -} - -void clear_pass::set_cleared_buffers(bool color, bool depth, bool stencil) -{ - clear_color_buffer = color; - clear_depth_buffer = depth; - clear_stencil_buffer = stencil; - - rebuild_command_buffer(); -} - -void clear_pass::set_clear_color(const math::fvec4& color) -{ - clear_color = color; -} - -void clear_pass::set_clear_depth(float depth) -{ - clear_depth = depth; -} - -void clear_pass::set_clear_stencil(int stencil) -{ - clear_stencil = stencil; -} - -void clear_pass::rebuild_command_buffer() -{ - command_buffer.clear(); - - if (!clear_color_buffer && - !clear_depth_buffer && - !clear_stencil_buffer) - { - return; - } - - command_buffer.emplace_back - ( - [&]() - { - rasterizer->use_framebuffer(*framebuffer); - - auto viewport = framebuffer->get_dimensions(); - rasterizer->set_viewport(0, 0, std::get<0>(viewport), std::get<1>(viewport)); - } - ); - - if (clear_color_buffer) - { - // Update color buffer clear state - command_buffer.emplace_back - ( - [&]() - { - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - rasterizer->set_clear_color(clear_color[0], clear_color[1], clear_color[2], clear_color[3]); - } - ); - } - - if (clear_depth_buffer) - { - // Update depth buffer clear state - command_buffer.emplace_back - ( - [&]() - { - glDepthMask(GL_TRUE); - rasterizer->set_clear_depth(clear_depth); - } - ); - } - - if (clear_stencil_buffer) - { - // Update stencil buffer clear state - command_buffer.emplace_back - ( - [&]() - { - glStencilMask(0xFF); - rasterizer->set_clear_stencil(clear_stencil); - } - ); - } - - // Clear buffers - command_buffer.emplace_back - ( - [&]() - { - rasterizer->clear_framebuffer(clear_color_buffer, clear_depth_buffer, clear_stencil_buffer); - } - ); -} - -} // namespace render diff --git a/src/engine/render/passes/clear-pass.hpp b/src/engine/render/passes/clear-pass.hpp deleted file mode 100644 index fba0c04..0000000 --- a/src/engine/render/passes/clear-pass.hpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_RENDER_CLEAR_PASS_HPP -#define ANTKEEPER_RENDER_CLEAR_PASS_HPP - -#include -#include -#include - -namespace render { - -/** - * Clears the color, depth, or stencil buffer of a render target. - */ -class clear_pass: public pass -{ -public: - clear_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer); - - void render(render::context& ctx) override; - - /** - * Sets the buffers to be cleared. - * - * @param color Clear the color buffer. - * @param depth Clear the depth buffer. - * @param stencil Clear the stencil buffer. - */ - void set_cleared_buffers(bool color, bool depth, bool stencil); - - /** - * Sets color buffer clear color. - * - * @param color Clear color. - */ - void set_clear_color(const math::fvec4& color); - - /** - * Sets the depth buffer clear value. - * - * @param depth Clear value. - */ - void set_clear_depth(float depth); - - /** - * Sets the stencil buffer clear value. - * - * @param stencil Clear value. - */ - void set_clear_stencil(int stencil); - -private: - void rebuild_command_buffer(); - - bool clear_color_buffer; - bool clear_depth_buffer; - bool clear_stencil_buffer; - math::fvec4 clear_color; - float clear_depth; - int clear_stencil; - - std::vector> command_buffer; -}; - -} // namespace render - -#endif // ANTKEEPER_RENDER_CLEAR_PASS_HPP diff --git a/src/engine/render/passes/final-pass.cpp b/src/engine/render/passes/final-pass.cpp index 23c364d..c4e0945 100644 --- a/src/engine/render/passes/final-pass.cpp +++ b/src/engine/render/passes/final-pass.cpp @@ -19,160 +19,151 @@ #include #include -#include +#include #include #include #include #include #include -#include -#include -#include -#include -#include -#include +#include +#include #include #include #include -#include #include #include namespace render { -final_pass::final_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager): - pass(rasterizer, framebuffer), - color_texture(nullptr), - bloom_texture(nullptr), - bloom_weight(0.04f), - blue_noise_texture(nullptr), - blue_noise_scale(1.0f) +final_pass::final_pass(gl::pipeline* pipeline, const gl::framebuffer* framebuffer, resource_manager* resource_manager): + pass(pipeline, framebuffer) { + // Construct empty vertex array + m_vertex_array = std::make_unique(); + // Load shader template and build shader program auto shader_template = resource_manager->load("final.glsl"); - shader_program = shader_template->build(); - if (!shader_program->linked()) + m_shader_program = shader_template->build(); + if (!m_shader_program->linked()) { - debug::log::error("Failed to final pass shader program: {}", shader_program->info()); - debug::log::warning("{}", shader_template->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to final pass shader program: {}", m_shader_program->info()); + debug::log_warning("{}", shader_template->configure(gl::shader_stage::vertex)); } } void final_pass::render(render::context& ctx) { // Update resolution - const auto viewport_size = framebuffer->get_dimensions(); - resolution = {static_cast(std::get<0>(viewport_size)), static_cast(std::get<1>(viewport_size))}; + const auto& viewport_dimensions = (m_framebuffer) ? m_framebuffer->dimensions() : m_pipeline->get_default_framebuffer_dimensions(); + m_resolution = {static_cast(viewport_dimensions[0]), static_cast(viewport_dimensions[1])}; // Update time - time = ctx.t; + m_time = ctx.t; // Execute render commands - for (const auto& command: command_buffer) + for (const auto& command: m_command_buffer) { command(); } // Increment current frame - ++frame; + ++m_frame; } -void final_pass::set_color_texture(const gl::texture_2d* texture) +void final_pass::set_color_texture(std::shared_ptr texture) { - this->color_texture = texture; - + m_color_texture = texture; rebuild_command_buffer(); } -void final_pass::set_bloom_texture(const gl::texture_2d* texture) noexcept +void final_pass::set_bloom_texture(std::shared_ptr texture) noexcept { - this->bloom_texture = texture; - + m_bloom_texture = texture; rebuild_command_buffer(); } void final_pass::set_bloom_weight(float weight) noexcept { - this->bloom_weight = weight; + m_bloom_weight = weight; } void final_pass::set_blue_noise_texture(std::shared_ptr texture) { - this->blue_noise_texture = texture; - blue_noise_scale = 1.0f / static_cast(texture->get_dimensions()[0]); - + m_blue_noise_texture = texture; + m_blue_noise_scale = 1.0f / static_cast(texture->get_image_view()->get_image()->get_dimensions()[0]); rebuild_command_buffer(); } void final_pass::rebuild_command_buffer() { - command_buffer.clear(); + m_command_buffer.clear(); - command_buffer.emplace_back + m_command_buffer.emplace_back ( [&]() { - rasterizer->use_framebuffer(*framebuffer); - rasterizer->set_viewport(0, 0, static_cast(resolution.x()), static_cast(resolution.y())); - - glDisable(GL_BLEND); - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); + const gl::viewport viewport[1] = {{0.0f, 0.0f, m_resolution.x(), m_resolution.y()}}; - rasterizer->use_program(*shader_program); + m_pipeline->set_primitive_topology(gl::primitive_topology::triangle_list); + m_pipeline->set_viewport(0, viewport); + m_pipeline->set_depth_test_enabled(false); + m_pipeline->set_cull_mode(gl::cull_mode::back); + m_pipeline->set_color_blend_enabled(false); + m_pipeline->bind_framebuffer(m_framebuffer); + m_pipeline->bind_shader_program(m_shader_program.get()); + m_pipeline->bind_vertex_array(m_vertex_array.get()); } ); - if (color_texture) + if (m_color_texture) { - if (const auto var = shader_program->variable("color_texture")) + if (const auto var = m_shader_program->variable("color_texture")) { - command_buffer.emplace_back([&, var](){var->update(*color_texture);}); + m_command_buffer.emplace_back([&, var](){var->update(*m_color_texture);}); } } - if (bloom_texture) + if (m_bloom_texture) { - if (const auto var = shader_program->variable("bloom_texture")) + if (const auto var = m_shader_program->variable("bloom_texture")) { - command_buffer.emplace_back([&, var](){var->update(*bloom_texture);}); + m_command_buffer.emplace_back([&, var](){var->update(*m_bloom_texture);}); } } - if (blue_noise_texture) + if (m_blue_noise_texture) { - if (const auto var = shader_program->variable("blue_noise_texture")) + if (const auto var = m_shader_program->variable("blue_noise_texture")) { - command_buffer.emplace_back([&, var](){var->update(*blue_noise_texture);}); + m_command_buffer.emplace_back([&, var](){var->update(*m_blue_noise_texture);}); } } - if (const auto var = shader_program->variable("bloom_weight")) + if (const auto var = m_shader_program->variable("bloom_weight")) { - command_buffer.emplace_back([&, var](){var->update(bloom_weight);}); + m_command_buffer.emplace_back([&, var](){var->update(m_bloom_weight);}); } - if (const auto var = shader_program->variable("blue_noise_scale")) + if (const auto var = m_shader_program->variable("blue_noise_scale")) { - command_buffer.emplace_back([&, var](){var->update(blue_noise_scale);}); + m_command_buffer.emplace_back([&, var](){var->update(m_blue_noise_scale);}); } - if (const auto var = shader_program->variable("resolution")) + if (const auto var = m_shader_program->variable("resolution")) { - command_buffer.emplace_back([&, var](){var->update(resolution);}); + m_command_buffer.emplace_back([&, var](){var->update(m_resolution);}); } - if (const auto var = shader_program->variable("time")) + if (const auto var = m_shader_program->variable("time")) { - command_buffer.emplace_back([&, var](){var->update(time);}); + m_command_buffer.emplace_back([&, var](){var->update(m_time);}); } - if (const auto frame_var = shader_program->variable("frame")) + if (const auto frame_var = m_shader_program->variable("frame")) { - command_buffer.emplace_back([&, frame_var](){frame_var->update(frame);}); + m_command_buffer.emplace_back([&, frame_var](){frame_var->update(m_frame);}); } - command_buffer.emplace_back + m_command_buffer.emplace_back ( [&]() { - rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); + // Draw fullscreen triangle + m_pipeline->draw(3, 1, 0, 0); } ); } diff --git a/src/engine/render/passes/final-pass.hpp b/src/engine/render/passes/final-pass.hpp index b58c39e..3297cce 100644 --- a/src/engine/render/passes/final-pass.hpp +++ b/src/engine/render/passes/final-pass.hpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include @@ -40,29 +40,30 @@ namespace render { class final_pass: public pass { public: - final_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager); + final_pass(gl::pipeline* pipeline, const gl::framebuffer* framebuffer, resource_manager* resource_manager); void render(render::context& ctx) override; - void set_color_texture(const gl::texture_2d* texture); - void set_bloom_texture(const gl::texture_2d* texture) noexcept; + void set_color_texture(std::shared_ptr texture); + void set_bloom_texture(std::shared_ptr texture) noexcept; void set_bloom_weight(float weight) noexcept; void set_blue_noise_texture(std::shared_ptr texture); private: void rebuild_command_buffer(); - std::unique_ptr shader_program; + std::unique_ptr m_vertex_array; + std::unique_ptr m_shader_program; - const gl::texture_2d* color_texture; - const gl::texture_2d* bloom_texture; - float bloom_weight; - std::shared_ptr blue_noise_texture; - float blue_noise_scale; - math::fvec2 resolution; - float time; - int frame{}; + std::shared_ptr m_color_texture{}; + std::shared_ptr m_bloom_texture{}; + float m_bloom_weight{0.04f}; + std::shared_ptr m_blue_noise_texture; + float m_blue_noise_scale{1.0f}; + math::fvec2 m_resolution{}; + float m_time{}; + int m_frame{}; - std::vector> command_buffer; + std::vector> m_command_buffer; }; } // namespace render diff --git a/src/engine/render/passes/fxaa-pass.cpp b/src/engine/render/passes/fxaa-pass.cpp deleted file mode 100644 index d69b4c8..0000000 --- a/src/engine/render/passes/fxaa-pass.cpp +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace render { - -fxaa_pass::fxaa_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager): - pass(rasterizer, framebuffer) -{ - // Load FXAA shader template - auto shader_template = resource_manager->load("fxaa.glsl"); - - // Build FXAA shader program - shader = shader_template->build(); - if (!shader->linked()) - { - debug::log::error("Failed to build FXAA shader program: {}", shader->info()); - debug::log::warning("{}", shader_template->configure(gl::shader_stage::vertex)); - } -} - -void fxaa_pass::render(render::context& ctx) -{ - for (const auto& command: command_buffer) - { - command(); - } -} - -void fxaa_pass::set_source_texture(const gl::texture_2d* texture) -{ - source_texture = texture; - rebuild_command_buffer(); -} - -void fxaa_pass::rebuild_command_buffer() -{ - command_buffer.clear(); - - if (!source_texture || !shader) - { - return; - } - - // Setup FXAA state - command_buffer.emplace_back - ( - [&]() - { - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glDisable(GL_BLEND); - - // Render FXAA - rasterizer->use_framebuffer(*framebuffer); - rasterizer->set_viewport(0, 0, framebuffer->get_dimensions()[0], framebuffer->get_dimensions()[1]); - rasterizer->use_program(*shader); - } - ); - - // Update shader variables - if (auto source_texture_var = shader->variable("source_texture")) - { - command_buffer.emplace_back([&, source_texture_var](){source_texture_var->update(*source_texture);}); - } - if (auto texel_size_var = shader->variable("texel_size")) - { - command_buffer.emplace_back - ( - [&, texel_size_var]() - { - const math::fvec2 texel_size = 1.0f / math::fvec2{static_cast(source_texture->get_width()), static_cast(source_texture->get_height())}; - texel_size_var->update(texel_size); - } - ); - } - - // Draw quad - command_buffer.emplace_back - ( - [&]() - { - rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); - } - ); -} - -} // namespace render diff --git a/src/engine/render/passes/fxaa-pass.hpp b/src/engine/render/passes/fxaa-pass.hpp deleted file mode 100644 index ab14e5b..0000000 --- a/src/engine/render/passes/fxaa-pass.hpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_RENDER_FXAA_PASS_HPP -#define ANTKEEPER_RENDER_FXAA_PASS_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -class resource_manager; - -namespace render { - -/** - * FXAA render pass. - * - * @see Lottes, T. (2009). Fxaa. White paper, Nvidia, Febuary, 2. - */ -class fxaa_pass: public pass -{ -public: - /** - * Constructs an FXAA pass. - * - * @param rasterizer Rasterizer. - * @param framebuffer Target framebuffer. - * @param resource_manager Resource manager. - */ - fxaa_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager); - - /** - * Renders FXAA. - * - * @param ctx Render context. - * @param queue Render queue. - */ - void render(render::context& ctx) override; - - /** - * Sets the FXAA source texture. - * - * @param texture FXAA source texture. - */ - void set_source_texture(const gl::texture_2d* texture); - -private: - void rebuild_command_buffer(); - - std::unique_ptr shader; - - const gl::texture_2d* source_texture{nullptr}; - - std::vector> command_buffer; -}; - -} // namespace render - -#endif // ANTKEEPER_RENDER_FXAA_PASS_HPP diff --git a/src/engine/render/passes/material-pass.cpp b/src/engine/render/passes/material-pass.cpp index 4ad8468..2d1c2e6 100644 --- a/src/engine/render/passes/material-pass.cpp +++ b/src/engine/render/passes/material-pass.cpp @@ -20,19 +20,14 @@ #include #include #include -#include #include #include #include #include #include -#include -#include -#include -#include -#include +#include #include -#include +#include #include #include #include @@ -46,9 +41,8 @@ #include #include #include -#include +#include #include -#include #include namespace render { @@ -123,8 +117,8 @@ bool operation_compare(const render::operation* a, const render::operation* b) } // namespace -material_pass::material_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager): - pass(rasterizer, framebuffer) +material_pass::material_pass(gl::pipeline* pipeline, const gl::framebuffer* framebuffer, resource_manager* resource_manager): + pass(pipeline, framebuffer) { // Load LTC LUT textures ltc_lut_1 = resource_manager->load("ltc-lut-1.tex"); @@ -136,18 +130,23 @@ material_pass::material_pass(gl::rasterizer* rasterizer, const gl::framebuffer* void material_pass::render(render::context& ctx) { - rasterizer->use_framebuffer(*framebuffer); + m_pipeline->bind_framebuffer(m_framebuffer); - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); - glDepthMask(GL_TRUE); - glDepthFunc(GL_GEQUAL); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glDisable(GL_STENCIL_TEST); + m_pipeline->set_color_blend_enabled(false); + m_pipeline->set_depth_test_enabled(true); + m_pipeline->set_depth_compare_op(gl::compare_op::greater_or_equal); + m_pipeline->set_cull_mode(gl::cull_mode::back); + m_pipeline->set_stencil_test_enabled(false); - auto viewport = framebuffer->get_dimensions(); - rasterizer->set_viewport(0, 0, std::get<0>(viewport), std::get<1>(viewport)); + const auto& viewport_dimensions = (m_framebuffer) ? m_framebuffer->dimensions() : m_pipeline->get_default_framebuffer_dimensions(); + const gl::viewport viewport[1] = + {{ + 0, + 0, + static_cast(viewport_dimensions[0]), + static_cast(viewport_dimensions[1]) + }}; + m_pipeline->set_viewport(0, viewport); //const gl::shader_program* active_shader_program = nullptr; const render::material* active_material = nullptr; @@ -204,11 +203,11 @@ void material_pass::render(render::context& ctx) { if (material->is_two_sided()) { - glDisable(GL_CULL_FACE); + m_pipeline->set_cull_mode(gl::cull_mode::none); } else { - glEnable(GL_CULL_FACE); + m_pipeline->set_cull_mode(gl::cull_mode::back); } active_two_sided = material->is_two_sided(); @@ -219,12 +218,20 @@ void material_pass::render(render::context& ctx) { if (material->get_blend_mode() == material_blend_mode::translucent) { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + m_pipeline->set_color_blend_enabled(true); + m_pipeline->set_color_blend_equation + ({ + gl::blend_factor::src_alpha, + gl::blend_factor::one_minus_src_alpha, + gl::blend_op::add, + gl::blend_factor::src_alpha, + gl::blend_factor::one_minus_src_alpha, + gl::blend_op::add + }); } else { - glDisable(GL_BLEND); + m_pipeline->set_color_blend_enabled(false); } active_blend_mode = material->get_blend_mode(); @@ -234,7 +241,7 @@ void material_pass::render(render::context& ctx) } // Calculate shader cache key - std::size_t cache_key = hash::combine(lighting_state_hash, material->get_shader_template()->hash()); + std::size_t cache_key = hash_combine(lighting_state_hash, material->get_shader_template()->hash()); if (active_cache_key != cache_key) { // Lookup shader cache entry @@ -250,7 +257,7 @@ void material_pass::render(render::context& ctx) build_shader_command_buffer(active_cache_entry->shader_command_buffer, *active_cache_entry->shader_program); build_geometry_command_buffer(active_cache_entry->geometry_command_buffer, *active_cache_entry->shader_program); - debug::log::trace("Generated material cache entry {:x}", cache_key); + debug::log_trace("Generated material cache entry {:x}", cache_key); } // Bind shader and update shader-specific variables @@ -273,7 +280,7 @@ void material_pass::render(render::context& ctx) material_command_buffer = &active_cache_entry->material_command_buffers[material]; build_material_command_buffer(*material_command_buffer, *active_cache_entry->shader_program, *material); - debug::log::trace("Generated material command buffer"); + debug::log_trace("Generated material command buffer"); } // Update material-dependent shader variables @@ -303,15 +310,10 @@ void material_pass::render(render::context& ctx) command(); } - // Draw geometry - if (operation->instance_count) - { - rasterizer->draw_arrays_instanced(*operation->vertex_array, operation->drawing_mode, operation->start_index, operation->index_count, operation->instance_count); - } - else - { - rasterizer->draw_arrays(*operation->vertex_array, operation->drawing_mode, operation->start_index, operation->index_count); - } + m_pipeline->set_primitive_topology(operation->primitive_topology); + m_pipeline->bind_vertex_array(operation->vertex_array); + m_pipeline->bind_vertex_buffers(0, {&operation->vertex_buffer, 1}, {&operation->vertex_offset, 1}, {&operation->vertex_stride, 1}); + m_pipeline->draw(operation->vertex_count, operation->instance_count, operation->first_vertex, operation->first_instance); } ++frame; @@ -372,7 +374,7 @@ void material_pass::evaluate_lighting(const render::context& ctx, std::uint32_t const scene::light& light = static_cast(*object); switch (light.get_light_type()) - { + { // Add directional light case scene::light_type::directional: { @@ -404,7 +406,7 @@ void material_pass::evaluate_lighting(const render::context& ctx, std::uint32_t directional_shadow_matrices.resize(directional_shadow_count); } - directional_shadow_maps[index] = static_cast(directional_light.get_shadow_framebuffer()->get_depth_attachment()); + directional_shadow_maps[index] = directional_light.get_shadow_texture().get(); directional_shadow_splits[index] = directional_light.get_shadow_cascade_distances(); directional_shadow_fade_ranges[index] = directional_light.get_shadow_fade_range(); directional_shadow_matrices[index] = directional_light.get_shadow_cascade_matrices(); @@ -487,11 +489,11 @@ void material_pass::evaluate_lighting(const render::context& ctx, std::uint32_t // Generate lighting state hash lighting_state_hash = std::hash{}(light_probe_count); - lighting_state_hash = hash::combine(lighting_state_hash, std::hash{}(directional_light_count)); - lighting_state_hash = hash::combine(lighting_state_hash, std::hash{}(directional_shadow_count)); - lighting_state_hash = hash::combine(lighting_state_hash, std::hash{}(point_light_count)); - lighting_state_hash = hash::combine(lighting_state_hash, std::hash{}(spot_light_count)); - lighting_state_hash = hash::combine(lighting_state_hash, std::hash{}(rectangle_light_count)); + lighting_state_hash = hash_combine(lighting_state_hash, std::hash{}(directional_light_count)); + lighting_state_hash = hash_combine(lighting_state_hash, std::hash{}(directional_shadow_count)); + lighting_state_hash = hash_combine(lighting_state_hash, std::hash{}(point_light_count)); + lighting_state_hash = hash_combine(lighting_state_hash, std::hash{}(spot_light_count)); + lighting_state_hash = hash_combine(lighting_state_hash, std::hash{}(rectangle_light_count)); } void material_pass::evaluate_misc(const render::context& ctx) @@ -500,11 +502,11 @@ void material_pass::evaluate_misc(const render::context& ctx) timestep = ctx.dt; subframe = ctx.alpha; - const auto viewport_size = framebuffer->get_dimensions(); + const auto& viewport_dimensions = (m_framebuffer) ? m_framebuffer->dimensions() : m_pipeline->get_default_framebuffer_dimensions(); resolution = { - static_cast(std::get<0>(viewport_size)), - static_cast(std::get<1>(viewport_size)) + static_cast(viewport_dimensions[0]), + static_cast(viewport_dimensions[1]) }; ///mouse_position = ... @@ -514,15 +516,15 @@ std::unique_ptr material_pass::generate_shader_program(const { std::unordered_map definitions; - definitions["VERTEX_POSITION"] = std::to_string(vertex_attribute::position); - definitions["VERTEX_UV"] = std::to_string(vertex_attribute::uv); - definitions["VERTEX_NORMAL"] = std::to_string(vertex_attribute::normal); - definitions["VERTEX_TANGENT"] = std::to_string(vertex_attribute::tangent); - definitions["VERTEX_COLOR"] = std::to_string(vertex_attribute::color); - definitions["VERTEX_BONE_INDEX"] = std::to_string(vertex_attribute::bone_index); - definitions["VERTEX_BONE_WEIGHT"] = std::to_string(vertex_attribute::bone_weight); - definitions["VERTEX_BARYCENTRIC"] = std::to_string(vertex_attribute::barycentric); - definitions["VERTEX_TARGET"] = std::to_string(vertex_attribute::target); + definitions["VERTEX_POSITION"] = std::to_string(vertex_attribute_location::position); + definitions["VERTEX_UV"] = std::to_string(vertex_attribute_location::uv); + definitions["VERTEX_NORMAL"] = std::to_string(vertex_attribute_location::normal); + definitions["VERTEX_TANGENT"] = std::to_string(vertex_attribute_location::tangent); + definitions["VERTEX_COLOR"] = std::to_string(vertex_attribute_location::color); + definitions["VERTEX_BONE_INDEX"] = std::to_string(vertex_attribute_location::bone_index); + definitions["VERTEX_BONE_WEIGHT"] = std::to_string(vertex_attribute_location::bone_weight); + definitions["VERTEX_BARYCENTRIC"] = std::to_string(vertex_attribute_location::barycentric); + definitions["VERTEX_TARGET"] = std::to_string(vertex_attribute_location::target); definitions["FRAGMENT_OUTPUT_COLOR"] = "0"; @@ -542,8 +544,8 @@ std::unique_ptr material_pass::generate_shader_program(const if (!shader_program->linked()) { - debug::log::error("Failed to link material shader program: {}", shader_program->info()); - debug::log::warning("{}", shader_template.configure(gl::shader_stage::fragment, definitions)); + debug::log_error("Failed to link material shader program: {}", shader_program->info()); + debug::log_warning("{}", shader_template.configure(gl::shader_stage::fragment, definitions)); } return shader_program; @@ -552,7 +554,7 @@ std::unique_ptr material_pass::generate_shader_program(const void material_pass::build_shader_command_buffer(std::vector>& command_buffer, const gl::shader_program& shader_program) const { // Bind shader program - command_buffer.emplace_back([&](){rasterizer->use_program(shader_program);}); + command_buffer.emplace_back([&](){m_pipeline->bind_shader_program(&shader_program);}); // Update camera variables if (auto view_var = shader_program.variable("view")) @@ -612,7 +614,7 @@ void material_pass::build_shader_command_buffer(std::vectorupdate(std::max(static_cast(light_probe_luminance_texture->get_mip_count()) - 4.0f, 0.0f)); + light_probe_luminance_mip_scale_var->update(std::max(static_cast(light_probe_luminance_texture->get_image_view()->get_mip_level_count()) - 4.0f, 0.0f)); } ); } diff --git a/src/engine/render/passes/material-pass.hpp b/src/engine/render/passes/material-pass.hpp index 72bc958..adae160 100644 --- a/src/engine/render/passes/material-pass.hpp +++ b/src/engine/render/passes/material-pass.hpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include #include @@ -41,7 +41,7 @@ namespace render { class material_pass: public pass { public: - material_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager); + material_pass(gl::pipeline* pipeline, const gl::framebuffer* framebuffer, resource_manager* resource_manager); void render(render::context& ctx) override; diff --git a/src/engine/render/passes/outline-pass.cpp b/src/engine/render/passes/outline-pass.cpp deleted file mode 100644 index 9ae9ed3..0000000 --- a/src/engine/render/passes/outline-pass.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace render { - -outline_pass::outline_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager): - pass(rasterizer, framebuffer), - fill_shader(nullptr), - stroke_shader(nullptr) -{ - // Load fill shader template - auto fill_shader_template = resource_manager->load("outline-fill-unskinned.glsl"); - - // Build fill shader - fill_shader = fill_shader_template->build({}); - fill_model_view_projection_var = fill_shader->variable("model_view_projection"); - - // Load stroke shader template - auto stroke_shader_template = resource_manager->load("outline-stroke-unskinned.glsl"); - - // Build stroke shader - stroke_shader = stroke_shader_template->build({}); - stroke_model_view_projection_var = stroke_shader->variable("model_view_projection"); - stroke_width_var = stroke_shader->variable("width"); - stroke_color_var = stroke_shader->variable("color"); -} - -void outline_pass::render(render::context& ctx) -{ - /* - rasterizer->use_framebuffer(*framebuffer); - - // Determine viewport based on framebuffer resolution - auto viewport = framebuffer->get_dimensions(); - rasterizer->set_viewport(0, 0, std::get<0>(viewport), std::get<1>(viewport)); - - // Get camera matrices - math::fmat4 view = ctx.camera->get_view_tween().interpolate(ctx.alpha); - math::fmat4 view_projection = ctx.camera->get_view_projection_tween().interpolate(ctx.alpha); - - math::fmat4 model_view_projection; - - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glDisable(GL_DEPTH_TEST); - glEnable(GL_STENCIL_TEST); - - // Render fill - { - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - glStencilFunc(GL_ALWAYS, 2, 0xFF); - glStencilMask(0xFF); - glDisable(GL_BLEND); - - // Setup fill shader - rasterizer->use_program(*fill_shader); - - - // Render fills - for (const render::operation& operation: queue) - { - const render::material* material = operation.material; - if (!material || !(material->get_flags() & MATERIAL_FLAG_OUTLINE)) - { - continue; - } - - model_view_projection = view_projection * operation.transform; - fill_model_view_projection_var->update(model_view_projection); - - rasterizer->draw_arrays(*operation.vertex_array, operation.drawing_mode, operation.start_index, operation.index_count); - } - } - - // Render stroke - { - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - - if (outline_color[3] < 1.0f) - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } - else - { - glDisable(GL_BLEND); - } - - glStencilFunc(GL_NOTEQUAL, 2, 0xFF); - glStencilMask(0x00); - - // Setup stroke shader - rasterizer->use_program(*stroke_shader); - stroke_width_var->update(outline_width); - stroke_color_var->update(outline_color); - - // Render strokes - for (const render::operation& operation: queue) - { - const render::material* material = operation.material; - if (!material || !(material->get_flags() & MATERIAL_FLAG_OUTLINE)) - continue; - - model_view_projection = view_projection * operation.transform; - stroke_model_view_projection_var->update(model_view_projection); - - rasterizer->draw_arrays(*operation.vertex_array, operation.drawing_mode, operation.start_index, operation.index_count); - } - } - - glDisable(GL_STENCIL_TEST); - */ -} - -void outline_pass::set_outline_width(float width) -{ - outline_width = width; -} - -void outline_pass::set_outline_color(const math::fvec4& color) -{ - outline_color = color; -} - -} // namespace render diff --git a/src/engine/render/passes/outline-pass.hpp b/src/engine/render/passes/outline-pass.hpp deleted file mode 100644 index cfa0094..0000000 --- a/src/engine/render/passes/outline-pass.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_RENDER_OUTLINE_PASS_HPP -#define ANTKEEPER_RENDER_OUTLINE_PASS_HPP - -#include -#include -#include -#include -#include - -class resource_manager; - -namespace render { - -/** - * - */ -class outline_pass: public pass -{ -public: - outline_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager); - virtual ~outline_pass() = default; - void render(render::context& ctx) override; - - void set_outline_width(float width); - void set_outline_color(const math::fvec4& color); - -private: - std::unique_ptr fill_shader; - const gl::shader_variable* fill_model_view_projection_var; - - std::unique_ptr stroke_shader; - const gl::shader_variable* stroke_model_view_projection_var; - const gl::shader_variable* stroke_width_var; - const gl::shader_variable* stroke_color_var; - - float outline_width; - math::fvec4 outline_color; -}; - -} // namespace render - -#endif // ANTKEEPER_RENDER_OUTLINE_PASS_HPP diff --git a/src/engine/render/passes/resample-pass.cpp b/src/engine/render/passes/resample-pass.cpp index 5ba2927..8b52327 100644 --- a/src/engine/render/passes/resample-pass.cpp +++ b/src/engine/render/passes/resample-pass.cpp @@ -19,90 +19,96 @@ #include #include -#include +#include #include #include #include #include #include -#include -#include -#include -#include +#include +#include #include -#include namespace render { -resample_pass::resample_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager): - pass(rasterizer, framebuffer), - source_texture{nullptr} +resample_pass::resample_pass(gl::pipeline* pipeline, const gl::framebuffer* framebuffer, resource_manager* resource_manager): + pass(pipeline, framebuffer) { + // Construct empty vertex array + m_vertex_array = std::make_unique(); + // Load resample shader template auto shader_template = resource_manager->load("resample.glsl"); // Build resample shader program - shader = shader_template->build(); - if (!shader->linked()) + m_shader_program = shader_template->build(); + if (!m_shader_program->linked()) { - debug::log::error("Failed to build resample shader program: {}", shader->info()); - debug::log::warning("{}", shader_template->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build resample shader program: {}", m_shader_program->info()); + debug::log_warning("{}", shader_template->configure(gl::shader_stage::vertex)); } } void resample_pass::render(render::context& ctx) { - for (const auto& command: command_buffer) + for (const auto& command: m_command_buffer) { command(); } } -void resample_pass::set_source_texture(const gl::texture_2d* texture) +void resample_pass::set_source_texture(std::shared_ptr texture) { - source_texture = texture; - + m_source_texture = texture; rebuild_command_buffer(); } void resample_pass::rebuild_command_buffer() { - command_buffer.clear(); + m_command_buffer.clear(); - if (!source_texture || !shader) + if (!m_source_texture || !m_shader_program) { return; } // Setup resample state - command_buffer.emplace_back + m_command_buffer.emplace_back ( [&]() { - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glDisable(GL_BLEND); + const auto& viewport_dimensions = (m_framebuffer) ? m_framebuffer->dimensions() : m_pipeline->get_default_framebuffer_dimensions(); + const gl::viewport viewport[1] = + {{ + 0.0f, + 0.0f, + static_cast(viewport_dimensions[0]), + static_cast(viewport_dimensions[1]) + }}; + + m_pipeline->set_primitive_topology(gl::primitive_topology::triangle_list); + m_pipeline->set_viewport(0, viewport); + m_pipeline->set_depth_test_enabled(false); + m_pipeline->set_cull_mode(gl::cull_mode::back); - rasterizer->use_framebuffer(*framebuffer); - rasterizer->set_viewport(0, 0, framebuffer->get_dimensions()[0], framebuffer->get_dimensions()[1]); - rasterizer->use_program(*shader); + m_pipeline->bind_framebuffer(m_framebuffer); + m_pipeline->bind_shader_program(m_shader_program.get()); + m_pipeline->bind_vertex_array(m_vertex_array.get()); } ); // Update shader variables - if (auto source_texture_var = shader->variable("source_texture")) + if (auto source_texture_var = m_shader_program->variable("source_texture")) { - command_buffer.emplace_back([&, source_texture_var](){source_texture_var->update(*source_texture);}); + m_command_buffer.emplace_back([&, source_texture_var](){source_texture_var->update(*m_source_texture);}); } - // Draw quad - command_buffer.emplace_back + m_command_buffer.emplace_back ( [&]() { - rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); + // Draw fullscreen triangle + m_pipeline->draw(3, 1, 0, 0); } ); } diff --git a/src/engine/render/passes/resample-pass.hpp b/src/engine/render/passes/resample-pass.hpp index a3a35a2..8e4cfa1 100644 --- a/src/engine/render/passes/resample-pass.hpp +++ b/src/engine/render/passes/resample-pass.hpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include @@ -43,11 +43,11 @@ public: /** * Constructs a resample pass. * - * @param rasterizer Rasterizer. + * @param pipeline Graphics pipeline. * @param framebuffer Target framebuffer. * @param resource_manager Resource manager. */ - resample_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager); + resample_pass(gl::pipeline* pipeline, const gl::framebuffer* framebuffer, resource_manager* resource_manager); /** * Resamples a texture. @@ -62,16 +62,15 @@ public: * * @param texture Texture to resample. */ - void set_source_texture(const gl::texture_2d* texture); + void set_source_texture(std::shared_ptr texture); private: void rebuild_command_buffer(); - std::unique_ptr shader; - - const gl::texture_2d* source_texture; - - std::vector> command_buffer; + std::unique_ptr m_vertex_array; + std::unique_ptr m_shader_program; + std::shared_ptr m_source_texture; + std::vector> m_command_buffer; }; } // namespace render diff --git a/src/engine/render/passes/sky-pass.cpp b/src/engine/render/passes/sky-pass.cpp index 67b0a3e..8b55580 100644 --- a/src/engine/render/passes/sky-pass.cpp +++ b/src/engine/render/passes/sky-pass.cpp @@ -19,18 +19,13 @@ #include #include -#include #include #include #include #include #include -#include -#include -#include -#include -#include -#include +#include +#include #include #include #include @@ -43,12 +38,11 @@ #include #include #include -#include namespace render { -sky_pass::sky_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager): - pass(rasterizer, framebuffer), +sky_pass::sky_pass(gl::pipeline* pipeline, const gl::framebuffer* framebuffer, resource_manager* resource_manager): + pass(pipeline, framebuffer), mouse_position({0.0f, 0.0f}), sky_model(nullptr), sky_material(nullptr), @@ -77,18 +71,23 @@ sky_pass::sky_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffe moon_illuminance_tween(math::fvec3{0.0f, 0.0f, 0.0f}, math::lerp), magnification(1.0f) { + // Construct LUT sampler + m_lut_sampler = std::make_shared + ( + gl::sampler_filter::linear, + gl::sampler_filter::linear, + gl::sampler_mipmap_mode::linear, + gl::sampler_address_mode::clamp_to_edge, + gl::sampler_address_mode::clamp_to_edge + ); + + // Construct empty vertex array + m_vertex_array = std::make_unique(); // Transmittance LUT { - // Construct transmittance LUT texture - m_transmittance_lut_texture = std::make_unique(m_transmittance_lut_resolution.x(), m_transmittance_lut_resolution.y(), gl::pixel_type::float_32, gl::pixel_format::rgb); - m_transmittance_lut_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); - m_transmittance_lut_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - m_transmittance_lut_texture->set_max_anisotropy(0.0f); - - // Construct transmittance LUT framebuffer and attach texture - m_transmittance_lut_framebuffer = std::make_unique(m_transmittance_lut_resolution.x(), m_transmittance_lut_resolution.y()); - m_transmittance_lut_framebuffer->attach(gl::framebuffer_attachment_type::color, m_transmittance_lut_texture.get()); + // Construct transmittance LUT texture and framebuffer + rebuild_transmittance_lut_framebuffer(); // Load transmittance LUT shader template m_transmittance_lut_shader_template = resource_manager->load("sky-transmittance-lut.glsl"); @@ -102,15 +101,8 @@ sky_pass::sky_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffe // Multiscattering LUT { - // Construct multiscattering LUT texture - m_multiscattering_lut_texture = std::make_unique(m_multiscattering_lut_resolution.x(), m_multiscattering_lut_resolution.y(), gl::pixel_type::float_32, gl::pixel_format::rgb); - m_multiscattering_lut_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); - m_multiscattering_lut_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - m_multiscattering_lut_texture->set_max_anisotropy(0.0f); - - // Construct multiscattering LUT framebuffer and attach texture - m_multiscattering_lut_framebuffer = std::make_unique(m_multiscattering_lut_resolution.x(), m_multiscattering_lut_resolution.y()); - m_multiscattering_lut_framebuffer->attach(gl::framebuffer_attachment_type::color, m_multiscattering_lut_texture.get()); + // Construct multiscattering LUT texture and framebuffer + rebuild_multiscattering_lut_framebuffer(); // Load multiscattering LUT shader template m_multiscattering_lut_shader_template = resource_manager->load("sky-multiscattering-lut.glsl"); @@ -124,15 +116,8 @@ sky_pass::sky_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffe // Luminance LUT { - // Construct luminance LUT texture - m_luminance_lut_texture = std::make_unique(m_luminance_lut_resolution.x(), m_luminance_lut_resolution.y(), gl::pixel_type::float_32, gl::pixel_format::rgb); - m_luminance_lut_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); - m_luminance_lut_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - m_luminance_lut_texture->set_max_anisotropy(0.0f); - - // Construct luminance LUT framebuffer and attach texture - m_luminance_lut_framebuffer = std::make_unique(m_luminance_lut_resolution.x(), m_luminance_lut_resolution.y()); - m_luminance_lut_framebuffer->attach(gl::framebuffer_attachment_type::color, m_luminance_lut_texture.get()); + // Construct luminance LUT texture and framebuffer + rebuild_luminance_lut_framebuffer(); // Load luminance LUT shader template m_luminance_lut_shader_template = resource_manager->load("sky-luminance-lut.glsl"); @@ -151,8 +136,8 @@ sky_pass::sky_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffe m_sky_probe_shader_program = m_sky_probe_shader_template->build({}); if (!m_sky_probe_shader_program->linked()) { - debug::log::error("Failed to build sky probe shader program: {}", m_sky_probe_shader_program->info()); - debug::log::warning("{}", m_sky_probe_shader_template->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build sky probe shader program: {}", m_sky_probe_shader_program->info()); + debug::log_warning("{}", m_sky_probe_shader_template->configure(gl::shader_stage::vertex)); } // Load moon textures @@ -167,11 +152,9 @@ void sky_pass::render(render::context& ctx) return; } - glDisable(GL_BLEND); - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); + m_pipeline->set_color_blend_enabled(false); + m_pipeline->set_depth_test_enabled(false); + m_pipeline->set_cull_mode(gl::cull_mode::back); // Render transmittance LUT (if parameters have changed) if (m_render_transmittance_lut) @@ -256,19 +239,58 @@ void sky_pass::render(render::context& ctx) command(); } - rasterizer->use_framebuffer(*framebuffer); - auto viewport = framebuffer->get_dimensions(); - rasterizer->set_viewport(0, 0, std::get<0>(viewport), std::get<1>(viewport)); - math::fvec2 resolution = {static_cast(std::get<0>(viewport)), static_cast(std::get<1>(viewport))}; + m_pipeline->bind_framebuffer(m_framebuffer); + m_pipeline->clear_attachments(gl::color_clear_bit | gl::depth_clear_bit | gl::stencil_clear_bit, {}); + + // Check if any corner of the view frustum is looking at or above the horizon + bool sky_visible = + (camera.pick(math::fvec2{-1, 1}).direction.y() > 0.0f) || + (camera.pick(math::fvec2{-1, -1}).direction.y() > 0.0f) || + (camera.pick(math::fvec2{ 1, 1}).direction.y() > 0.0f) || + (camera.pick(math::fvec2{ 1, -1}).direction.y() > 0.0f); + if (!sky_visible) + { + // Sky not visible, abort + return; + } + + const auto& viewport_dimensions = (m_framebuffer) ? m_framebuffer->dimensions() : m_pipeline->get_default_framebuffer_dimensions(); + const gl::viewport viewport[1] = + {{ + 0.0f, + 0.0f, + static_cast(viewport_dimensions[0]), + static_cast(viewport_dimensions[1]) + }}; + m_pipeline->set_viewport(0, viewport); + math::fvec2 resolution = {viewport[0].width, viewport[1].height}; // Draw atmosphere if (sky_model && sky_shader_program) { - rasterizer->use_program(*sky_shader_program); - + m_pipeline->bind_shader_program(sky_shader_program.get()); + // Upload shader parameters if (model_view_projection_var) model_view_projection_var->update(model_view_projection); + if (view_var) + { + view_var->update(view); + } + if (view_projection_var) + { + view_projection_var->update(view_projection); + } + if (inv_view_projection_var) + { + const auto inv_view_projection = math::fmat4(math::fmat3(camera.get_inv_view())) * camera.get_inv_projection(); + inv_view_projection_var->update(inv_view_projection); + // inv_view_projection_var->update(camera.get_inv_view_projection()); + } + if (camera_position_var) + { + camera_position_var->update(camera.get_translation()); + } if (mouse_var) mouse_var->update(mouse_position); if (resolution_var) @@ -292,20 +314,37 @@ void sky_pass::render(render::context& ctx) if (sky_luminance_lut_resolution_var) sky_luminance_lut_resolution_var->update(math::fvec2(m_luminance_lut_resolution)); - //sky_material->update(ctx.alpha); - - rasterizer->draw_arrays(*sky_model_vao, sky_model_drawing_mode, sky_model_start_index, sky_model_index_count); + m_pipeline->set_primitive_topology(sky_model_primitive_topology); + m_pipeline->bind_vertex_array(sky_model_vao); + m_pipeline->bind_vertex_buffers(0, {&sky_model_vbo, 1}, {&sky_model_vertex_offset, 1}, {&sky_model_vertex_stride, 1}); + m_pipeline->draw(sky_model_vertex_count, 1, sky_model_first_vertex, 0); } - glEnable(GL_BLEND); - // glBlendFunc(GL_SRC_ALPHA, GL_ONE); - glBlendFunc(GL_ONE, GL_ONE); + // Enable additive blending + m_pipeline->set_color_blend_enabled(true); + m_pipeline->set_color_blend_equation + ({ + gl::blend_factor::one, + gl::blend_factor::one, + gl::blend_op::add, + gl::blend_factor::one, + gl::blend_factor::one, + gl::blend_op::add + }); // Flag moon pixels in stencil buffer - glEnable(GL_STENCIL_TEST); - glStencilMask(0xff); - glStencilFunc(GL_ALWAYS, 1, 0xff); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + m_pipeline->set_stencil_test_enabled(true); + m_pipeline->set_stencil_write_mask(gl::stencil_face_front_and_back, 0xff); + m_pipeline->set_stencil_reference(gl::stencil_face_front_and_back, 1); + m_pipeline->set_stencil_compare_mask(gl::stencil_face_front_and_back, 0xff); + m_pipeline->set_stencil_op + ( + gl::stencil_face_front_and_back, + gl::stencil_op::keep, + gl::stencil_op::replace, + gl::stencil_op::keep, + gl::compare_op::always + ); // Draw moon model //if (moon_position.y() >= -moon_angular_radius) @@ -322,7 +361,7 @@ void sky_pass::render(render::context& ctx) model = moon_transform.matrix(); math::fmat3 normal_model = math::transpose(math::inverse(math::fmat3(model))); - rasterizer->use_program(*moon_shader_program); + m_pipeline->bind_shader_program(moon_shader_program.get()); if (moon_model_var) moon_model_var->update(model); if (moon_view_projection_var) @@ -350,13 +389,22 @@ void sky_pass::render(render::context& ctx) if (moon_atmosphere_radii_var) moon_atmosphere_radii_var->update(atmosphere_radii); - //moon_material->update(ctx.alpha); - rasterizer->draw_arrays(*moon_model_vao, moon_model_drawing_mode, moon_model_start_index, moon_model_index_count); + m_pipeline->set_primitive_topology(moon_model_primitive_topology); + m_pipeline->bind_vertex_array(moon_model_vao); + m_pipeline->bind_vertex_buffers(0, {&moon_model_vbo, 1}, {&moon_model_vertex_offset, 1}, {&moon_model_vertex_stride, 1}); + m_pipeline->draw(moon_model_vertex_count, 1, moon_model_first_vertex, 0); } // Prevent stars from being drawn in front of the moon - glStencilMask(0x00); - glStencilFunc(GL_NOTEQUAL, 1, 0xff); + m_pipeline->set_stencil_compare_mask(gl::stencil_face_front_and_back, 0x00); + m_pipeline->set_stencil_op + ( + gl::stencil_face_front_and_back, + gl::stencil_op::keep, + gl::stencil_op::replace, + gl::stencil_op::keep, + gl::compare_op::not_equal + ); // Draw stars if (star_shader_program) @@ -367,7 +415,7 @@ void sky_pass::render(render::context& ctx) model_view_projection = view_projection * model; - rasterizer->use_program(*star_shader_program); + m_pipeline->bind_shader_program(star_shader_program.get()); if (star_model_view_projection_var) star_model_view_projection_var->update(model_view_projection); if (star_exposure_var) @@ -375,12 +423,13 @@ void sky_pass::render(render::context& ctx) if (star_inv_resolution_var) star_inv_resolution_var->update(1.0f / resolution); - //star_material->update(ctx.alpha); - - rasterizer->draw_arrays(*stars_model_vao, stars_model_drawing_mode, stars_model_start_index, stars_model_index_count); + m_pipeline->set_primitive_topology(stars_model_primitive_topology); + m_pipeline->bind_vertex_array(stars_model_vao); + m_pipeline->bind_vertex_buffers(0, {&stars_model_vbo, 1}, {&stars_model_vertex_offset, 1}, {&stars_model_vertex_stride, 1}); + m_pipeline->draw(stars_model_vertex_count, 1, stars_model_first_vertex, 0); } - glDisable(GL_STENCIL_TEST); + m_pipeline->set_stencil_test_enabled(false); } void sky_pass::set_transmittance_lut_sample_count(std::uint16_t count) @@ -403,8 +452,8 @@ void sky_pass::set_transmittance_lut_resolution(const math::vec2& if (m_transmittance_lut_resolution.x() != resolution.x() || m_transmittance_lut_resolution.y() != resolution.y()) { m_transmittance_lut_resolution = resolution; - m_transmittance_lut_texture->resize(resolution.x(), resolution.y(), nullptr); - m_transmittance_lut_framebuffer->resize({resolution.x(), resolution.y()}); + + rebuild_transmittance_lut_framebuffer(); // Trigger rendering of transmittance LUT m_render_transmittance_lut = true; @@ -446,8 +495,8 @@ void sky_pass::set_multiscattering_lut_resolution(const math::vec2resize(resolution.x(), resolution.y(), nullptr); - m_multiscattering_lut_framebuffer->resize({resolution.x(), resolution.y()}); + + rebuild_multiscattering_lut_framebuffer(); // Trigger rendering of multiscattering LUT m_render_multiscattering_lut = true; @@ -474,8 +523,8 @@ void sky_pass::set_luminance_lut_resolution(const math::vec2& res if (m_luminance_lut_resolution.x() != resolution.x() || m_luminance_lut_resolution.y() != resolution.y()) { m_luminance_lut_resolution = resolution; - m_luminance_lut_texture->resize(resolution.x(), resolution.y(), nullptr); - m_luminance_lut_framebuffer->resize({resolution.x(), resolution.y()}); + + rebuild_luminance_lut_framebuffer(); // Trigger rendering of luminance LUT m_render_luminance_lut = true; @@ -490,14 +539,17 @@ void sky_pass::set_sky_model(std::shared_ptr model) if (sky_model) { sky_model_vao = model->get_vertex_array().get(); + sky_model_vbo = model->get_vertex_buffer().get(); for (const auto& group: model->get_groups()) { + sky_model_primitive_topology = group.primitive_topology; + sky_model_first_vertex = group.first_vertex; + sky_model_vertex_count = group.vertex_count; sky_material = group.material.get(); - sky_model_drawing_mode = group.drawing_mode; - sky_model_start_index = group.start_index; - sky_model_index_count = group.index_count; } + sky_model_vertex_offset = sky_model->get_vertex_offset(); + sky_model_vertex_stride = sky_model->get_vertex_stride(); if (sky_material) { @@ -506,6 +558,10 @@ void sky_pass::set_sky_model(std::shared_ptr model) if (sky_shader_program->linked()) { model_view_projection_var = sky_shader_program->variable("model_view_projection"); + view_var = sky_shader_program->variable("view"); + view_projection_var = sky_shader_program->variable("view_projection"); + inv_view_projection_var = sky_shader_program->variable("inv_view_projection"); + camera_position_var = sky_shader_program->variable("camera_position"); mouse_var = sky_shader_program->variable("mouse"); resolution_var = sky_shader_program->variable("resolution"); light_direction_var = sky_shader_program->variable("light_direction"); @@ -520,8 +576,8 @@ void sky_pass::set_sky_model(std::shared_ptr model) } else { - debug::log::error("Failed to build sky shader program: {}", sky_shader_program->info()); - debug::log::warning("{}", sky_material->get_shader_template()->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build sky shader program: {}", sky_shader_program->info()); + debug::log_warning("{}", sky_material->get_shader_template()->configure(gl::shader_stage::vertex)); } } } @@ -539,14 +595,17 @@ void sky_pass::set_moon_model(std::shared_ptr model) if (moon_model) { moon_model_vao = model->get_vertex_array().get(); + moon_model_vbo = model->get_vertex_buffer().get(); for (const auto& group: model->get_groups()) { + moon_model_primitive_topology = group.primitive_topology; + moon_model_first_vertex = group.first_vertex; + moon_model_vertex_count = group.vertex_count; moon_material = group.material.get(); - moon_model_drawing_mode = group.drawing_mode; - moon_model_start_index = group.start_index; - moon_model_index_count = group.index_count; } + moon_model_vertex_offset = moon_model->get_vertex_offset(); + moon_model_vertex_stride = moon_model->get_vertex_stride(); if (moon_material) { @@ -570,8 +629,8 @@ void sky_pass::set_moon_model(std::shared_ptr model) } else { - debug::log::error("Failed to build moon shader program: {}", moon_shader_program->info()); - debug::log::warning("{}", moon_material->get_shader_template()->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build moon shader program: {}", moon_shader_program->info()); + debug::log_warning("{}", moon_material->get_shader_template()->configure(gl::shader_stage::vertex)); } } } @@ -589,14 +648,17 @@ void sky_pass::set_stars_model(std::shared_ptr model) if (stars_model) { stars_model_vao = model->get_vertex_array().get(); + stars_model_vbo = model->get_vertex_buffer().get(); for (const auto& group: model->get_groups()) { - stars_model_drawing_mode = group.drawing_mode; - stars_model_start_index = group.start_index; - stars_model_index_count = group.index_count; + stars_model_primitive_topology = group.primitive_topology; + stars_model_first_vertex = group.first_vertex; + stars_model_vertex_count = group.vertex_count; star_material = group.material.get(); } + stars_model_vertex_offset = stars_model->get_vertex_offset(); + stars_model_vertex_stride = stars_model->get_vertex_stride(); if (star_material) { @@ -610,8 +672,8 @@ void sky_pass::set_stars_model(std::shared_ptr model) } else { - debug::log::error("Failed to build star shader program: {}", star_shader_program->info()); - debug::log::warning("{}", star_material->get_shader_template()->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build star shader program: {}", star_shader_program->info()); + debug::log_warning("{}", star_material->get_shader_template()->configure(gl::shader_stage::vertex)); } } } @@ -808,17 +870,21 @@ void sky_pass::set_sky_probe(std::shared_ptr probe) if (m_sky_probe && m_sky_probe->get_luminance_texture()) { - auto& luminance_texture = *m_sky_probe->get_luminance_texture(); + const auto& luminance_texture = m_sky_probe->get_luminance_texture(); - std::uint16_t face_size = luminance_texture.get_face_size(); - const std::uint8_t mip_count = static_cast(std::bit_width(face_size)); + const auto face_size = luminance_texture->get_image_view()->get_image()->get_dimensions()[0]; + const auto mip_count = static_cast(std::bit_width(face_size)); m_sky_probe_framebuffers.resize(mip_count); - for (std::uint8_t i = 0; i < mip_count; ++i) + for (std::uint32_t i = 0; i < mip_count; ++i) { - m_sky_probe_framebuffers[i] = std::make_unique(face_size, face_size); - m_sky_probe_framebuffers[i]->attach(gl::framebuffer_attachment_type::color, &luminance_texture, i); - face_size >>= 1; + const gl::framebuffer_attachment attachments[1] = + {{ + gl::color_attachment_bit, + luminance_texture->get_image_view(), + i + }}; + m_sky_probe_framebuffers[i] = std::make_unique(attachments, face_size >> i, face_size >> i); } } else @@ -829,6 +895,33 @@ void sky_pass::set_sky_probe(std::shared_ptr probe) rebuild_sky_probe_command_buffer(); } +void sky_pass::rebuild_transmittance_lut_framebuffer() +{ + // Rebuild transmittance LUT texture + m_transmittance_lut_texture = std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::r32g32b32_sfloat, + m_transmittance_lut_resolution.x(), + m_transmittance_lut_resolution.y() + ) + ), + m_lut_sampler + ); + + // Rebuild transmittance LUT framebuffer + const gl::framebuffer_attachment attachments[1] = + {{ + gl::color_attachment_bit, + m_transmittance_lut_texture->get_image_view(), + 0 + }}; + m_transmittance_lut_framebuffer = std::make_shared(attachments, m_transmittance_lut_resolution.x(), m_transmittance_lut_resolution.y()); +} + void sky_pass::rebuild_transmittance_lut_shader_program() { m_transmittance_lut_shader_program = m_transmittance_lut_shader_template->build @@ -839,8 +932,8 @@ void sky_pass::rebuild_transmittance_lut_shader_program() ); if (!m_transmittance_lut_shader_program->linked()) { - debug::log::error("Failed to build sky transmittance LUT shader program: {}", m_transmittance_lut_shader_program->info()); - debug::log::warning("{}", m_transmittance_lut_shader_template->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build sky transmittance LUT shader program: {}", m_transmittance_lut_shader_program->info()); + debug::log_warning("{}", m_transmittance_lut_shader_template->configure(gl::shader_stage::vertex)); } } @@ -858,9 +951,17 @@ void sky_pass::rebuild_transmittance_lut_command_buffer() ( [&]() { - rasterizer->set_viewport(0, 0, m_transmittance_lut_resolution.x(), m_transmittance_lut_resolution.y()); - rasterizer->use_framebuffer(*m_transmittance_lut_framebuffer); - rasterizer->use_program(*m_transmittance_lut_shader_program); + const gl::viewport viewport[1] = + {{ + 0.0f, + 0.0f, + static_cast(m_transmittance_lut_resolution.x()), + static_cast(m_transmittance_lut_resolution.y()) + }}; + m_pipeline->set_viewport(0, viewport); + + m_pipeline->bind_framebuffer(m_transmittance_lut_framebuffer.get()); + m_pipeline->bind_shader_program(m_transmittance_lut_shader_program.get()); } ); @@ -890,16 +991,45 @@ void sky_pass::rebuild_transmittance_lut_command_buffer() m_transmittance_lut_command_buffer.emplace_back([&, resolution_var](){resolution_var->update(math::fvec2(m_transmittance_lut_resolution));}); } - // Draw quad m_transmittance_lut_command_buffer.emplace_back ( [&]() { - rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); + // Draw fullscreen triangle + m_pipeline->bind_vertex_array(m_vertex_array.get()); + m_pipeline->set_primitive_topology(gl::primitive_topology::triangle_list); + m_pipeline->draw(3, 1, 0, 0); } ); } +void sky_pass::rebuild_multiscattering_lut_framebuffer() +{ + // Rebuild multiscattering LUT texture + m_multiscattering_lut_texture = std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::r32g32b32_sfloat, + m_multiscattering_lut_resolution.x(), + m_multiscattering_lut_resolution.y() + ) + ), + m_lut_sampler + ); + + // Rebuild multiscattering LUT framebuffer and attach texture + const gl::framebuffer_attachment attachments[1] = + {{ + gl::color_attachment_bit, + m_multiscattering_lut_texture->get_image_view(), + 0 + }}; + m_multiscattering_lut_framebuffer = std::make_shared(attachments, m_multiscattering_lut_resolution.x(), m_multiscattering_lut_resolution.y()); +} + void sky_pass::rebuild_multiscattering_lut_shader_program() { m_multiscattering_lut_shader_program = m_multiscattering_lut_shader_template->build @@ -911,8 +1041,8 @@ void sky_pass::rebuild_multiscattering_lut_shader_program() ); if (!m_multiscattering_lut_shader_program->linked()) { - debug::log::error("Failed to build sky multiscattering LUT shader program: {}", m_multiscattering_lut_shader_program->info()); - debug::log::warning("{}", m_multiscattering_lut_shader_template->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build sky multiscattering LUT shader program: {}", m_multiscattering_lut_shader_program->info()); + debug::log_warning("{}", m_multiscattering_lut_shader_template->configure(gl::shader_stage::vertex)); } } @@ -930,9 +1060,17 @@ void sky_pass::rebuild_multiscattering_lut_command_buffer() ( [&]() { - rasterizer->set_viewport(0, 0, m_multiscattering_lut_resolution.x(), m_multiscattering_lut_resolution.y()); - rasterizer->use_framebuffer(*m_multiscattering_lut_framebuffer); - rasterizer->use_program(*m_multiscattering_lut_shader_program); + const gl::viewport viewport[1] = + {{ + 0.0f, + 0.0f, + static_cast(m_multiscattering_lut_resolution.x()), + static_cast(m_multiscattering_lut_resolution.y()) + }}; + m_pipeline->set_viewport(0, viewport); + + m_pipeline->bind_framebuffer(m_multiscattering_lut_framebuffer.get()); + m_pipeline->bind_shader_program(m_multiscattering_lut_shader_program.get()); } ); @@ -970,16 +1108,45 @@ void sky_pass::rebuild_multiscattering_lut_command_buffer() m_multiscattering_lut_command_buffer.emplace_back([&, transmittance_lut_var](){transmittance_lut_var->update(*m_transmittance_lut_texture);}); } - // Draw quad m_multiscattering_lut_command_buffer.emplace_back ( [&]() { - rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); + // Draw fullscreen triangle + m_pipeline->bind_vertex_array(m_vertex_array.get()); + m_pipeline->set_primitive_topology(gl::primitive_topology::triangle_list); + m_pipeline->draw(3, 1, 0, 0); } ); } +void sky_pass::rebuild_luminance_lut_framebuffer() +{ + // Rebuild luminance LUT texture + m_luminance_lut_texture = std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::r32g32b32_sfloat, + m_luminance_lut_resolution.x(), + m_luminance_lut_resolution.y() + ) + ), + m_lut_sampler + ); + + // Rebuild luminance LUT framebuffer + const gl::framebuffer_attachment attachments[1] = + {{ + gl::color_attachment_bit, + m_luminance_lut_texture->get_image_view(), + 0 + }}; + m_luminance_lut_framebuffer = std::make_shared(attachments, m_luminance_lut_resolution.x(), m_luminance_lut_resolution.y()); +} + void sky_pass::rebuild_luminance_lut_shader_program() { m_luminance_lut_shader_program = m_luminance_lut_shader_template->build @@ -990,8 +1157,8 @@ void sky_pass::rebuild_luminance_lut_shader_program() ); if (!m_luminance_lut_shader_program->linked()) { - debug::log::error("Failed to build sky luminance LUT shader program: {}", m_luminance_lut_shader_program->info()); - debug::log::warning("{}", m_luminance_lut_shader_template->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build sky luminance LUT shader program: {}", m_luminance_lut_shader_program->info()); + debug::log_warning("{}", m_luminance_lut_shader_template->configure(gl::shader_stage::vertex)); } } @@ -1009,9 +1176,17 @@ void sky_pass::rebuild_luminance_lut_command_buffer() ( [&]() { - rasterizer->set_viewport(0, 0, m_luminance_lut_resolution.x(), m_luminance_lut_resolution.y()); - rasterizer->use_framebuffer(*m_luminance_lut_framebuffer); - rasterizer->use_program(*m_luminance_lut_shader_program); + const gl::viewport viewport[1] = + {{ + 0.0f, + 0.0f, + static_cast(m_luminance_lut_resolution.x()), + static_cast(m_luminance_lut_resolution.y()) + }}; + m_pipeline->set_viewport(0, viewport); + + m_pipeline->bind_framebuffer(m_luminance_lut_framebuffer.get()); + m_pipeline->bind_shader_program(m_luminance_lut_shader_program.get()); } ); @@ -1065,12 +1240,14 @@ void sky_pass::rebuild_luminance_lut_command_buffer() m_luminance_lut_command_buffer.emplace_back([&, multiscattering_lut_var](){multiscattering_lut_var->update(*m_multiscattering_lut_texture);}); } - // Draw quad m_luminance_lut_command_buffer.emplace_back ( [&]() { - rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); + // Draw fullscreen triangle + m_pipeline->bind_vertex_array(m_vertex_array.get()); + m_pipeline->set_primitive_topology(gl::primitive_topology::triangle_list); + m_pipeline->draw(3, 1, 0, 0); } ); } @@ -1089,10 +1266,19 @@ void sky_pass::rebuild_sky_probe_command_buffer() ( [&]() { - const auto resolution = m_sky_probe->get_luminance_texture()->get_face_size(); - rasterizer->set_viewport(0, 0, resolution, resolution); - rasterizer->use_framebuffer(*m_sky_probe_framebuffers[0]); - rasterizer->use_program(*m_sky_probe_shader_program); + const auto resolution = m_sky_probe->get_luminance_texture()->get_image_view()->get_image()->get_dimensions()[0]; + const gl::viewport viewport[1] = + {{ + 0.0f, + 0.0f, + static_cast(resolution), + static_cast(resolution) + }}; + m_pipeline->set_viewport(0, viewport); + + m_pipeline->bind_framebuffer(m_sky_probe_framebuffers[0].get()); + m_pipeline->bind_shader_program(m_sky_probe_shader_program.get()); + m_pipeline->bind_vertex_array(m_vertex_array.get()); } ); @@ -1126,7 +1312,8 @@ void sky_pass::rebuild_sky_probe_command_buffer() ( [&]() { - rasterizer->draw_arrays(gl::drawing_mode::points, 0, 1); + m_pipeline->set_primitive_topology(gl::primitive_topology::point_list); + m_pipeline->draw(1, 1, 0, 0); m_sky_probe->set_luminance_outdated(true); m_sky_probe->set_illuminance_outdated(true); } diff --git a/src/engine/render/passes/sky-pass.hpp b/src/engine/render/passes/sky-pass.hpp index 74e8311..0f8355c 100644 --- a/src/engine/render/passes/sky-pass.hpp +++ b/src/engine/render/passes/sky-pass.hpp @@ -29,8 +29,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -50,7 +49,7 @@ class model; class sky_pass: public pass { public: - sky_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager); + sky_pass(gl::pipeline* pipeline, const gl::framebuffer* framebuffer, resource_manager* resource_manager); virtual ~sky_pass() = default; void render(render::context& ctx) override; @@ -223,21 +222,27 @@ public: } private: + void rebuild_transmittance_lut_framebuffer(); void rebuild_transmittance_lut_shader_program(); void rebuild_transmittance_lut_command_buffer(); + void rebuild_multiscattering_lut_framebuffer(); void rebuild_multiscattering_lut_shader_program(); void rebuild_multiscattering_lut_command_buffer(); + void rebuild_luminance_lut_framebuffer(); void rebuild_luminance_lut_shader_program(); void rebuild_luminance_lut_command_buffer(); void rebuild_sky_lut_command_buffer(); void rebuild_sky_probe_command_buffer(); + std::shared_ptr m_lut_sampler; + std::unique_ptr m_vertex_array; + // Transmittance std::uint16_t m_transmittance_lut_sample_count{40}; math::vec2 m_transmittance_lut_resolution{256, 64}; - std::unique_ptr m_transmittance_lut_texture; - std::unique_ptr m_transmittance_lut_framebuffer; + std::shared_ptr m_transmittance_lut_texture; + std::shared_ptr m_transmittance_lut_framebuffer; std::shared_ptr m_transmittance_lut_shader_template; std::unique_ptr m_transmittance_lut_shader_program; std::vector> m_transmittance_lut_command_buffer; @@ -247,8 +252,8 @@ private: std::uint16_t m_multiscattering_lut_direction_sample_count{64}; std::uint16_t m_multiscattering_lut_scatter_sample_count{20}; math::vec2 m_multiscattering_lut_resolution{32, 32}; - std::unique_ptr m_multiscattering_lut_texture; - std::unique_ptr m_multiscattering_lut_framebuffer; + std::shared_ptr m_multiscattering_lut_texture; + std::shared_ptr m_multiscattering_lut_framebuffer; std::shared_ptr m_multiscattering_lut_shader_template; std::unique_ptr m_multiscattering_lut_shader_program; std::vector> m_multiscattering_lut_command_buffer; @@ -257,8 +262,8 @@ private: // Luminance std::uint16_t m_luminance_lut_sample_count{30}; math::vec2 m_luminance_lut_resolution{200, 100}; - std::unique_ptr m_luminance_lut_texture; - std::unique_ptr m_luminance_lut_framebuffer; + std::shared_ptr m_luminance_lut_texture; + std::shared_ptr m_luminance_lut_framebuffer; std::shared_ptr m_luminance_lut_shader_template; std::unique_ptr m_luminance_lut_shader_program; std::vector> m_luminance_lut_command_buffer; @@ -278,6 +283,10 @@ private: std::shared_ptr sky_shader_program; const gl::shader_variable* model_view_projection_var; + const gl::shader_variable* view_var; + const gl::shader_variable* view_projection_var; + const gl::shader_variable* inv_view_projection_var; + const gl::shader_variable* camera_position_var; const gl::shader_variable* mouse_var; const gl::shader_variable* resolution_var; const gl::shader_variable* light_direction_var; @@ -308,26 +317,34 @@ private: std::shared_ptr sky_model; const material* sky_material; const gl::vertex_array* sky_model_vao; - gl::drawing_mode sky_model_drawing_mode; - std::size_t sky_model_start_index; - std::size_t sky_model_index_count; + const gl::vertex_buffer* sky_model_vbo; + gl::primitive_topology sky_model_primitive_topology; + std::size_t sky_model_vertex_offset{}; + std::size_t sky_model_vertex_stride{}; + std::uint32_t sky_model_first_vertex{}; + std::uint32_t sky_model_vertex_count{}; std::shared_ptr moon_model; const material* moon_material; const gl::vertex_array* moon_model_vao; - gl::drawing_mode moon_model_drawing_mode; - std::size_t moon_model_start_index; - std::size_t moon_model_index_count; + const gl::vertex_buffer* moon_model_vbo; + gl::primitive_topology moon_model_primitive_topology; + std::size_t moon_model_vertex_offset{}; + std::size_t moon_model_vertex_stride{}; + std::uint32_t moon_model_first_vertex{}; + std::uint32_t moon_model_vertex_count{}; std::shared_ptr m_moon_albedo_map; std::shared_ptr m_moon_normal_map; - std::shared_ptr stars_model; const material* star_material; const gl::vertex_array* stars_model_vao; - gl::drawing_mode stars_model_drawing_mode; - std::size_t stars_model_start_index; - std::size_t stars_model_index_count; + const gl::vertex_buffer* stars_model_vbo; + gl::primitive_topology stars_model_primitive_topology; + std::size_t stars_model_vertex_offset{}; + std::size_t stars_model_vertex_stride{}; + std::uint32_t stars_model_first_vertex{}; + std::uint32_t stars_model_vertex_count{}; std::unique_ptr star_shader_program; const gl::shader_variable* star_model_view_projection_var; const gl::shader_variable* star_exposure_var; diff --git a/src/engine/render/renderer.cpp b/src/engine/render/renderer.cpp index 920dc0a..d8604ab 100644 --- a/src/engine/render/renderer.cpp +++ b/src/engine/render/renderer.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -38,10 +37,10 @@ namespace render { -renderer::renderer(gl::rasterizer& rasterizer, ::resource_manager& resource_manager) +renderer::renderer(gl::pipeline& pipeline, ::resource_manager& resource_manager) { - m_light_probe_stage = std::make_unique(rasterizer, resource_manager); - m_cascaded_shadow_map_stage = std::make_unique(rasterizer, resource_manager); + m_light_probe_stage = std::make_unique(pipeline, resource_manager); + m_cascaded_shadow_map_stage = std::make_unique(pipeline, resource_manager); m_culling_stage = std::make_unique(); m_queue_stage = std::make_unique(); } diff --git a/src/engine/render/renderer.hpp b/src/engine/render/renderer.hpp index 68cc496..70018a6 100644 --- a/src/engine/render/renderer.hpp +++ b/src/engine/render/renderer.hpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include @@ -41,10 +41,10 @@ public: /** * Constructs a renderer. * - * @param rasterizer GL rasterizer. + * @param pipeline Graphics pipeline. * @param resource_maanger Resource manager for loading shader templates. */ - renderer(gl::rasterizer& rasterizer, ::resource_manager& resource_manager); + renderer(gl::pipeline& pipeline, ::resource_manager& resource_manager); /** * Renders a collection of scene objects. diff --git a/src/engine/render/stages/cascaded-shadow-map-stage.cpp b/src/engine/render/stages/cascaded-shadow-map-stage.cpp index ce898ce..b1dff96 100644 --- a/src/engine/render/stages/cascaded-shadow-map-stage.cpp +++ b/src/engine/render/stages/cascaded-shadow-map-stage.cpp @@ -19,13 +19,12 @@ #include #include -#include +#include #include #include -#include #include #include -#include +#include #include #include #include @@ -37,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -46,18 +44,18 @@ namespace render { static bool operation_compare(const render::operation* a, const render::operation* b); -cascaded_shadow_map_stage::cascaded_shadow_map_stage(gl::rasterizer& rasterizer, ::resource_manager& resource_manager): - m_rasterizer(&rasterizer) +cascaded_shadow_map_stage::cascaded_shadow_map_stage(gl::pipeline& pipeline, ::resource_manager& resource_manager): + m_pipeline(&pipeline) { // Init shader template definitions - m_shader_template_definitions["VERTEX_POSITION"] = std::to_string(vertex_attribute::position); - m_shader_template_definitions["VERTEX_UV"] = std::to_string(vertex_attribute::uv); - m_shader_template_definitions["VERTEX_NORMAL"] = std::to_string(vertex_attribute::normal); - m_shader_template_definitions["VERTEX_TANGENT"] = std::to_string(vertex_attribute::tangent); - m_shader_template_definitions["VERTEX_COLOR"] = std::to_string(vertex_attribute::color); - m_shader_template_definitions["VERTEX_BONE_INDEX"] = std::to_string(vertex_attribute::bone_index); - m_shader_template_definitions["VERTEX_BONE_WEIGHT"] = std::to_string(vertex_attribute::bone_weight); - m_shader_template_definitions["VERTEX_BONE_WEIGHT"] = std::to_string(vertex_attribute::bone_weight); + m_shader_template_definitions["VERTEX_POSITION"] = std::to_string(vertex_attribute_location::position); + m_shader_template_definitions["VERTEX_UV"] = std::to_string(vertex_attribute_location::uv); + m_shader_template_definitions["VERTEX_NORMAL"] = std::to_string(vertex_attribute_location::normal); + m_shader_template_definitions["VERTEX_TANGENT"] = std::to_string(vertex_attribute_location::tangent); + m_shader_template_definitions["VERTEX_COLOR"] = std::to_string(vertex_attribute_location::color); + m_shader_template_definitions["VERTEX_BONE_INDEX"] = std::to_string(vertex_attribute_location::bone_index); + m_shader_template_definitions["VERTEX_BONE_WEIGHT"] = std::to_string(vertex_attribute_location::bone_weight); + m_shader_template_definitions["VERTEX_BONE_WEIGHT"] = std::to_string(vertex_attribute_location::bone_weight); m_shader_template_definitions["MAX_BONE_COUNT"] = std::to_string(m_max_bone_count); // Static mesh shader @@ -197,24 +195,23 @@ void cascaded_shadow_map_stage::queue(render::context& ctx, scene::directional_l void cascaded_shadow_map_stage::render_shadow_atlas(render::context& ctx, scene::directional_light& light) { // Disable blending - glDisable(GL_BLEND); + m_pipeline->set_color_blend_enabled(false); // Enable depth testing - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_GREATER); - glDepthMask(GL_TRUE); + m_pipeline->set_depth_test_enabled(true); + m_pipeline->set_depth_write_enabled(true); + m_pipeline->set_depth_compare_op(gl::compare_op::greater); - // Disable depth clipping (enable "pancaking") - glEnable(GL_DEPTH_CLAMP); + // Enable depth clamping ("pancaking") + m_pipeline->set_depth_clamp_enabled(true); // Enable back-face culling - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); + m_pipeline->set_cull_mode(gl::cull_mode::back); bool two_sided = false; // Bind and clear shadow atlas framebuffer - m_rasterizer->use_framebuffer(*light.get_shadow_framebuffer()); - m_rasterizer->clear_framebuffer(false, true, false); + m_pipeline->bind_framebuffer(light.get_shadow_framebuffer().get()); + m_pipeline->clear_attachments(gl::depth_clear_bit, {}); // Get camera const scene::camera& camera = *ctx.camera; @@ -239,7 +236,7 @@ void cascaded_shadow_map_stage::render_shadow_atlas(render::context& ctx, scene: } // Determine resolution of shadow atlas and cascades - const auto atlas_resolution = static_cast(light.get_shadow_framebuffer()->get_depth_attachment()->get_width()); + const auto atlas_resolution = static_cast(light.get_shadow_framebuffer()->width()); const auto cascade_resolution = atlas_resolution >> 1; // Sort render operations @@ -323,9 +320,14 @@ void cascaded_shadow_map_stage::render_shadow_atlas(render::context& ctx, scene: } // Set viewport for this cascade - const auto viewport_x = static_cast(i % 2) * cascade_resolution; - const auto viewport_y = static_cast(i >> 1) * cascade_resolution; - m_rasterizer->set_viewport(viewport_x, viewport_y, cascade_resolution, cascade_resolution); + const gl::viewport viewport[1] = + {{ + static_cast(static_cast(i % 2) * cascade_resolution), + static_cast(static_cast(i >> 1) * cascade_resolution), + static_cast(cascade_resolution), + static_cast(cascade_resolution) + }}; + m_pipeline->set_viewport(0, viewport); // Render geometry for (const render::operation* operation: ctx.operations) @@ -343,11 +345,11 @@ void cascaded_shadow_map_stage::render_shadow_atlas(render::context& ctx, scene: { if (material->is_two_sided()) { - glDisable(GL_CULL_FACE); + m_pipeline->set_cull_mode(gl::cull_mode::none); } else { - glEnable(GL_CULL_FACE); + m_pipeline->set_cull_mode(gl::cull_mode::back); } two_sided = material->is_two_sided(); @@ -359,7 +361,7 @@ void cascaded_shadow_map_stage::render_shadow_atlas(render::context& ctx, scene: if (active_shader_program != shader_program) { active_shader_program = shader_program; - m_rasterizer->use_program(*active_shader_program); + m_pipeline->bind_shader_program(active_shader_program); } // Calculate model-view-projection matrix @@ -379,14 +381,17 @@ void cascaded_shadow_map_stage::render_shadow_atlas(render::context& ctx, scene: m_skeletal_mesh_model_view_projection_var->update(model_view_projection); m_skeletal_mesh_matrix_palette_var->update(operation->matrix_palette); } - + // Draw geometry - m_rasterizer->draw_arrays(*operation->vertex_array, operation->drawing_mode, operation->start_index, operation->index_count); + m_pipeline->set_primitive_topology(operation->primitive_topology); + m_pipeline->bind_vertex_array(operation->vertex_array); + m_pipeline->bind_vertex_buffers(0, {&operation->vertex_buffer, 1}, {&operation->vertex_offset, 1}, {&operation->vertex_stride, 1}); + m_pipeline->draw(operation->vertex_count, 1, 0, 0); } } - // Re-enable depth clipping (disable "pancaking") - glDisable(GL_DEPTH_CLAMP); + // Disable depth clamping ("pancaking") + m_pipeline->set_depth_clamp_enabled(false); } void cascaded_shadow_map_stage::rebuild_static_mesh_shader_program() @@ -394,8 +399,8 @@ void cascaded_shadow_map_stage::rebuild_static_mesh_shader_program() m_static_mesh_shader_program = m_static_mesh_shader_template->build(m_shader_template_definitions); if (!m_static_mesh_shader_program->linked()) { - debug::log::error("Failed to build cascaded shadow map shader program for static meshes: {}", m_static_mesh_shader_program->info()); - debug::log::warning("{}", m_static_mesh_shader_template->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build cascaded shadow map shader program for static meshes: {}", m_static_mesh_shader_program->info()); + debug::log_warning("{}", m_static_mesh_shader_template->configure(gl::shader_stage::vertex)); m_static_mesh_model_view_projection_var = nullptr; } @@ -410,8 +415,8 @@ void cascaded_shadow_map_stage::rebuild_skeletal_mesh_shader_program() m_skeletal_mesh_shader_program = m_skeletal_mesh_shader_template->build(m_shader_template_definitions); if (!m_skeletal_mesh_shader_program->linked()) { - debug::log::error("Failed to build cascaded shadow map shader program for skeletal meshes: {}", m_skeletal_mesh_shader_program->info()); - debug::log::warning("{}", m_skeletal_mesh_shader_template->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build cascaded shadow map shader program for skeletal meshes: {}", m_skeletal_mesh_shader_program->info()); + debug::log_warning("{}", m_skeletal_mesh_shader_template->configure(gl::shader_stage::vertex)); m_skeletal_mesh_model_view_projection_var = nullptr; m_skeletal_mesh_matrix_palette_var = nullptr; diff --git a/src/engine/render/stages/cascaded-shadow-map-stage.hpp b/src/engine/render/stages/cascaded-shadow-map-stage.hpp index 38deeca..b928f51 100644 --- a/src/engine/render/stages/cascaded-shadow-map-stage.hpp +++ b/src/engine/render/stages/cascaded-shadow-map-stage.hpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include #include @@ -42,10 +42,10 @@ public: /** * Constructs a cascaded shadow map stage. * - * @param rasterizer GL rasterizer. + * @param pipeline Graphics pipeline. * @param resource_manager Resource manager for loading shader templates. */ - cascaded_shadow_map_stage(gl::rasterizer& rasterizer, ::resource_manager& resource_manager); + cascaded_shadow_map_stage(gl::pipeline& pipeline, ::resource_manager& resource_manager); void execute(render::context& ctx) override; @@ -84,7 +84,7 @@ private: /// Rebuilds the shader program for skeletal meshes. void rebuild_skeletal_mesh_shader_program(); - gl::rasterizer* m_rasterizer; + gl::pipeline* m_pipeline; std::size_t m_max_bone_count{64}; diff --git a/src/engine/render/stages/light-probe-stage.cpp b/src/engine/render/stages/light-probe-stage.cpp index 3f47a1a..16e24af 100644 --- a/src/engine/render/stages/light-probe-stage.cpp +++ b/src/engine/render/stages/light-probe-stage.cpp @@ -18,19 +18,59 @@ */ #include -#include +#include #include #include #include #include #include -#include namespace render { -light_probe_stage::light_probe_stage(gl::rasterizer& rasterizer, ::resource_manager& resource_manager): - m_rasterizer(&rasterizer) +light_probe_stage::light_probe_stage(gl::pipeline& pipeline, ::resource_manager& resource_manager): + m_pipeline(&pipeline) { + // Generate restricted mip range samplers + m_downsample_samplers.resize(16); + m_filter_samplers.resize(16); + for (std::size_t i = 0; i < m_downsample_samplers.size(); ++i) + { + m_downsample_samplers[i] = std::make_shared + ( + gl::sampler_filter::linear, + gl::sampler_filter::linear, + gl::sampler_mipmap_mode::linear, + gl::sampler_address_mode::repeat, + gl::sampler_address_mode::repeat, + gl::sampler_address_mode::repeat, + 0.0f, + 0.0f, + false, + gl::compare_op::less, + static_cast(i), + static_cast(i) + ); + + m_filter_samplers[i] = std::make_shared + ( + gl::sampler_filter::linear, + gl::sampler_filter::linear, + gl::sampler_mipmap_mode::linear, + gl::sampler_address_mode::repeat, + gl::sampler_address_mode::repeat, + gl::sampler_address_mode::repeat, + 0.0f, + 0.0f, + false, + gl::compare_op::less, + static_cast(i), + 1000.0f + ); + } + + // Construct empty vertex array + m_vertex_array = std::make_unique(); + // Load cubemap to spherical harmonics shader template and build shader program m_cubemap_to_sh_shader_template = resource_manager.load("cubemap-to-sh.glsl"); rebuild_cubemap_to_sh_shader_program(); @@ -44,11 +84,35 @@ light_probe_stage::light_probe_stage(gl::rasterizer& rasterizer, ::resource_mana rebuild_cubemap_filter_lut_shader_program(); // Allocate cubemap filter LUT texture - m_cubemap_filter_lut_texture = std::make_unique(static_cast(m_cubemap_filter_sample_count), static_cast(m_cubemap_filter_mip_count - 1), gl::pixel_type::float_32, gl::pixel_format::rgba); + m_cubemap_filter_lut_texture = std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::r32g32b32a32_sfloat, + static_cast(m_cubemap_filter_sample_count), + static_cast(m_cubemap_filter_mip_count - 1) + ) + ), + std::make_shared + ( + gl::sampler_filter::nearest, + gl::sampler_filter::nearest, + gl::sampler_mipmap_mode::nearest, + gl::sampler_address_mode::clamp_to_edge, + gl::sampler_address_mode::clamp_to_edge + ) + ); // Allocate cubemap filter LUT framebuffer and attach LUT texture - m_cubemap_filter_lut_framebuffer = std::make_unique(); - m_cubemap_filter_lut_framebuffer->attach(gl::framebuffer_attachment_type::color, m_cubemap_filter_lut_texture.get()); + const gl::framebuffer_attachment attachments[1] = + {{ + gl::color_attachment_bit, + m_cubemap_filter_lut_texture->get_image_view(), + 0 + }}; + m_cubemap_filter_lut_framebuffer = std::make_unique(attachments, m_cubemap_filter_lut_texture->get_image_view()->get_image()->get_dimensions()[0], m_cubemap_filter_lut_texture->get_image_view()->get_image()->get_dimensions()[1]); // Build cubemap filter LUT texture rebuild_cubemap_filter_lut_texture(); @@ -88,45 +152,55 @@ void light_probe_stage::update_light_probes_luminance(const std::vectorget_sampler(); + // Bind state, if unbound if (!state_bound) { - glDisable(GL_BLEND); + m_pipeline->set_primitive_topology(gl::primitive_topology::point_list); + m_pipeline->bind_vertex_array(m_vertex_array.get()); + m_pipeline->set_color_blend_enabled(false); state_bound = true; } // Bind cubemap downsample shader program - m_rasterizer->use_program(*m_cubemap_downsample_shader_program); - - // Update cubemap shader variable with light probe luminance texture - m_cubemap_downsample_cubemap_var->update(*light_probe.get_luminance_texture()); + m_pipeline->bind_shader_program(m_cubemap_downsample_shader_program.get()); // Get resolution of cubemap face for base mip level - const std::uint16_t base_mip_face_size = light_probe.get_luminance_texture()->get_face_size(); + const auto base_mip_face_size = light_probe.get_luminance_texture()->get_image_view()->get_image()->get_dimensions()[0]; // Downsample mip chain for (std::size_t i = 1; i < light_probe.get_luminance_framebuffers().size(); ++i) { // Set viewport to resolution of cubemap face size for current mip level - const std::uint16_t current_mip_face_size = base_mip_face_size >> i; - m_rasterizer->set_viewport(0, 0, current_mip_face_size, current_mip_face_size); + const auto current_mip_face_size = base_mip_face_size >> i; + const gl::viewport viewport[1] = + {{ + 0, + 0, + static_cast(current_mip_face_size), + static_cast(current_mip_face_size) + }}; + m_pipeline->set_viewport(0, viewport); // Restrict cubemap mipmap range to parent mip level - const std::uint8_t parent_mip_level = static_cast(i - 1); - light_probe.get_luminance_texture()->set_mip_range(parent_mip_level, parent_mip_level); + light_probe.get_luminance_texture()->set_sampler(m_downsample_samplers[i - 1]); + + // Update cubemap shader variable with light probe luminance texture + m_cubemap_downsample_cubemap_var->update(*light_probe.get_luminance_texture()); // Bind framebuffer of current cubemap mip level - m_rasterizer->use_framebuffer(*light_probe.get_luminance_framebuffers()[i]); + m_pipeline->bind_framebuffer(light_probe.get_luminance_framebuffers()[i].get()); // Downsample - m_rasterizer->draw_arrays(gl::drawing_mode::points, 0, 1); + m_pipeline->draw(1, 1, 0, 0); } // Bind cubemap filter shader program - m_rasterizer->use_program(*m_cubemap_filter_shader_program); + m_pipeline->bind_shader_program(m_cubemap_filter_shader_program.get()); - // Pass cubemap and filter lut textures to cubemap filter shader program - m_cubemap_filter_cubemap_var->update(*light_probe.get_luminance_texture()); + // Pass filter lut texture to cubemap filter shader program m_cubemap_filter_filter_lut_var->update(*m_cubemap_filter_lut_texture); // Filter mip chain @@ -136,21 +210,31 @@ void light_probe_stage::update_light_probes_luminance(const std::vectorupdate(static_cast(i)); // Set viewport to resolution of cubemap face size for current mip level - const std::uint16_t current_mip_face_size = base_mip_face_size >> i; - m_rasterizer->set_viewport(0, 0, current_mip_face_size, current_mip_face_size); + const auto current_mip_face_size = base_mip_face_size >> i; + const gl::viewport viewport[1] = + {{ + 0, + 0, + static_cast(current_mip_face_size), + static_cast(current_mip_face_size) + }}; + m_pipeline->set_viewport(0, viewport); // Restrict cubemap mipmap range to descendent levels - light_probe.get_luminance_texture()->set_mip_range(static_cast(i + 1), 255); + light_probe.get_luminance_texture()->set_sampler(m_filter_samplers[i + 1]); + + // Update cubemap shader variable with light probe luminance texture + m_cubemap_filter_cubemap_var->update(*light_probe.get_luminance_texture()); // Bind framebuffer of current cubemap mip level - m_rasterizer->use_framebuffer(*light_probe.get_luminance_framebuffers()[i]); + m_pipeline->bind_framebuffer(light_probe.get_luminance_framebuffers()[i].get()); // Filter - m_rasterizer->draw_arrays(gl::drawing_mode::points, 0, 1); + m_pipeline->draw(1, 1, 0, 0); } - // Restore cubemap mipmap range - light_probe.get_luminance_texture()->set_mip_range(0, 255); + // Restore light probe luminance sampler + light_probe.get_luminance_texture()->set_sampler(light_probe_luminance_sampler); // Mark light probe luminance as current light_probe.set_luminance_outdated(false); @@ -179,20 +263,31 @@ void light_probe_stage::update_light_probes_illuminance(const std::vectorset_viewport(0, 0, 12, 1); - m_rasterizer->use_program(*m_cubemap_to_sh_shader_program); + m_pipeline->set_primitive_topology(gl::primitive_topology::triangle_list); + m_pipeline->bind_vertex_array(m_vertex_array.get()); + m_pipeline->set_color_blend_enabled(false); + + const gl::viewport viewport[1] = + {{ + 0, + 0, + 12, + 1 + }}; + m_pipeline->set_viewport(0, viewport); + + m_pipeline->bind_shader_program(m_cubemap_to_sh_shader_program.get()); state_bound = true; } // Bind light probe illuminance framebuffer - m_rasterizer->use_framebuffer(*light_probe.get_illuminance_framebuffer()); + m_pipeline->bind_framebuffer(light_probe.get_illuminance_framebuffer().get()); // Update cubemap to spherical harmonics cubemap variable with light probe luminance texture m_cubemap_to_sh_cubemap_var->update(*light_probe.get_luminance_texture()); - // Draw quad - m_rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); + // Draw fullscreen triangle + m_pipeline->draw(3, 1, 0, 0); // Mark light probe illuminance as current light_probe.set_illuminance_outdated(false); @@ -234,8 +329,8 @@ void light_probe_stage::rebuild_cubemap_to_sh_shader_program() m_cubemap_to_sh_shader_program = m_cubemap_to_sh_shader_template->build({{"SAMPLE_COUNT", std::to_string(m_sh_sample_count)}}); if (!m_cubemap_to_sh_shader_program->linked()) { - debug::log::error("Failed to build cubemap to spherical harmonics shader program: {}", m_cubemap_to_sh_shader_program->info()); - debug::log::warning("{}", m_cubemap_to_sh_shader_template->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build cubemap to spherical harmonics shader program: {}", m_cubemap_to_sh_shader_program->info()); + debug::log_warning("{}", m_cubemap_to_sh_shader_template->configure(gl::shader_stage::vertex)); m_cubemap_to_sh_cubemap_var = nullptr; throw std::runtime_error("Failed to build cubemap to spherical harmonics shader program."); @@ -255,8 +350,8 @@ void light_probe_stage::rebuild_cubemap_downsample_shader_program() m_cubemap_downsample_shader_program = m_cubemap_downsample_shader_template->build({}); if (!m_cubemap_downsample_shader_program->linked()) { - debug::log::error("Failed to build cubemap downsample shader program: {}", m_cubemap_downsample_shader_program->info()); - debug::log::warning("{}", m_cubemap_downsample_shader_template->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build cubemap downsample shader program: {}", m_cubemap_downsample_shader_program->info()); + debug::log_warning("{}", m_cubemap_downsample_shader_template->configure(gl::shader_stage::vertex)); m_cubemap_downsample_cubemap_var = nullptr; throw std::runtime_error("Failed to build cubemap downsample shader program."); @@ -276,8 +371,8 @@ void light_probe_stage::rebuild_cubemap_filter_lut_shader_program() m_cubemap_filter_lut_shader_program = m_cubemap_filter_lut_shader_template->build({}); if (!m_cubemap_filter_lut_shader_program->linked()) { - debug::log::error("Failed to build cubemap filter LUT shader program: {}", m_cubemap_filter_lut_shader_program->info()); - debug::log::warning("{}", m_cubemap_filter_lut_shader_template->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build cubemap filter LUT shader program: {}", m_cubemap_filter_lut_shader_program->info()); + debug::log_warning("{}", m_cubemap_filter_lut_shader_template->configure(gl::shader_stage::vertex)); m_cubemap_filter_lut_resolution_var = nullptr; m_cubemap_filter_lut_face_size_var = nullptr; m_cubemap_filter_lut_mip_bias_var = nullptr; @@ -298,14 +393,28 @@ void light_probe_stage::rebuild_cubemap_filter_lut_shader_program() void light_probe_stage::rebuild_cubemap_filter_lut_texture() { - glDisable(GL_BLEND); - m_rasterizer->use_framebuffer(*m_cubemap_filter_lut_framebuffer); - m_rasterizer->set_viewport(0, 0, m_cubemap_filter_lut_texture->get_width(), m_cubemap_filter_lut_texture->get_height()); - m_rasterizer->use_program(*m_cubemap_filter_lut_shader_program); - m_cubemap_filter_lut_resolution_var->update(math::fvec2{static_cast(m_cubemap_filter_lut_texture->get_width()), static_cast(m_cubemap_filter_lut_texture->get_height())}); + m_pipeline->set_color_blend_enabled(false); + m_pipeline->bind_framebuffer(m_cubemap_filter_lut_framebuffer.get()); + + const auto& cubemap_filter_lut_dimensions = m_cubemap_filter_lut_texture->get_image_view()->get_image()->get_dimensions(); + const gl::viewport viewport[1] = + {{ + 0, + 0, + static_cast(cubemap_filter_lut_dimensions[0]), + static_cast(cubemap_filter_lut_dimensions[1]) + }}; + m_pipeline->set_viewport(0, viewport); + + m_pipeline->bind_shader_program(m_cubemap_filter_lut_shader_program.get()); + m_cubemap_filter_lut_resolution_var->update(math::fvec2{static_cast(cubemap_filter_lut_dimensions[0]), static_cast(cubemap_filter_lut_dimensions[1])}); m_cubemap_filter_lut_face_size_var->update(128.0f); m_cubemap_filter_lut_mip_bias_var->update(m_cubemap_filter_mip_bias); - m_rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); + m_cubemap_filter_lut_framebuffer->resize(cubemap_filter_lut_dimensions[0], cubemap_filter_lut_dimensions[1]); + + m_pipeline->bind_vertex_array(m_vertex_array.get()); + m_pipeline->set_primitive_topology(gl::primitive_topology::triangle_list); + m_pipeline->draw(3, 1, 0, 0); } void light_probe_stage::rebuild_cubemap_filter_shader_program() @@ -313,8 +422,8 @@ void light_probe_stage::rebuild_cubemap_filter_shader_program() m_cubemap_filter_shader_program = m_cubemap_filter_shader_template->build({{"SAMPLE_COUNT", std::to_string(m_cubemap_filter_sample_count)}}); if (!m_cubemap_filter_shader_program->linked()) { - debug::log::error("Failed to build cubemap filter shader program: {}", m_cubemap_filter_shader_program->info()); - debug::log::warning("{}", m_cubemap_filter_shader_template->configure(gl::shader_stage::vertex)); + debug::log_error("Failed to build cubemap filter shader program: {}", m_cubemap_filter_shader_program->info()); + debug::log_warning("{}", m_cubemap_filter_shader_template->configure(gl::shader_stage::vertex)); m_cubemap_filter_cubemap_var = nullptr; m_cubemap_filter_filter_lut_var = nullptr; m_cubemap_filter_mip_level_var = nullptr; diff --git a/src/engine/render/stages/light-probe-stage.hpp b/src/engine/render/stages/light-probe-stage.hpp index 55971dc..75a8fce 100644 --- a/src/engine/render/stages/light-probe-stage.hpp +++ b/src/engine/render/stages/light-probe-stage.hpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include #include @@ -43,7 +43,7 @@ public: /** * Constructs a light probe stage. * - * @param rasterizer GL rasterizer. + * @param pipeline Graphics pipeline. * @param resource_manager Resource manager for loading shader templates. * * @exception std::runtime_error Failed to build cubemap to spherical harmonics shader program. @@ -53,7 +53,7 @@ public: * @exception std::runtime_error Failed to build cubemap filter LUT shader program. * @exception std::runtime_error Cubemap filter LUT shader program is missing one or more required shader variables. */ - light_probe_stage(gl::rasterizer& rasterizer, ::resource_manager& resource_manager); + light_probe_stage(gl::pipeline& pipeline, ::resource_manager& resource_manager); void execute(render::context& ctx) override; @@ -113,7 +113,12 @@ private: void update_light_probes_luminance(const std::vector& light_probes); void update_light_probes_illuminance(const std::vector& light_probes); - gl::rasterizer* m_rasterizer; + gl::pipeline* m_pipeline; + + std::vector> m_downsample_samplers; + std::vector> m_filter_samplers; + std::unique_ptr m_vertex_array; + std::shared_ptr m_cubemap_to_sh_shader_template; std::unique_ptr m_cubemap_to_sh_shader_program; const gl::shader_variable* m_cubemap_to_sh_cubemap_var{}; @@ -127,7 +132,7 @@ private: std::vector> m_cubemap_downsample_framebuffers; std::unique_ptr m_cubemap_downsample_texture; - std::unique_ptr m_cubemap_filter_lut_texture; + std::shared_ptr m_cubemap_filter_lut_texture; std::unique_ptr m_cubemap_filter_lut_framebuffer; std::shared_ptr m_cubemap_filter_lut_shader_template; std::unique_ptr m_cubemap_filter_lut_shader_program; diff --git a/src/engine/render/vertex-attribute.hpp b/src/engine/render/vertex-attribute-location.hpp similarity index 82% rename from src/engine/render/vertex-attribute.hpp rename to src/engine/render/vertex-attribute-location.hpp index f94cc30..fa9504f 100644 --- a/src/engine/render/vertex-attribute.hpp +++ b/src/engine/render/vertex-attribute-location.hpp @@ -17,17 +17,15 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_RENDER_VERTEX_ATTRIBUTE_HPP -#define ANTKEEPER_RENDER_VERTEX_ATTRIBUTE_HPP +#ifndef ANTKEEPER_RENDER_VERTEX_ATTRIBUTE_LOCATION_HPP +#define ANTKEEPER_RENDER_VERTEX_ATTRIBUTE_LOCATION_HPP #include namespace render { -/** - * Contains enumerated vertex attribute locations. - */ -namespace vertex_attribute +/// Vertex attribute locations. +namespace vertex_attribute_location { enum: std::uint8_t { @@ -56,13 +54,10 @@ namespace vertex_attribute barycentric, /// Vertex morph target (vec3) - target, - - /// General purpose index (uint) - index + target }; } } // namespace render -#endif // ANTKEEPER_RENDER_VERTEX_ATTRIBUTE_HPP +#endif // ANTKEEPER_RENDER_VERTEX_ATTRIBUTE_LOCATION_HPP diff --git a/src/engine/resources/resource-manager.cpp b/src/engine/resources/resource-manager.cpp index ac276df..3a4fb2d 100644 --- a/src/engine/resources/resource-manager.cpp +++ b/src/engine/resources/resource-manager.cpp @@ -31,7 +31,7 @@ resource_manager::resource_manager() // PHYSFS_Version physfs_linked_version; // PHYSFS_VERSION(&physfs_compiled_version); // PHYSFS_getLinkedVersion(&physfs_linked_version); - // debug::log::info + // debug::log_info // ( // "PhysicsFS compiled version: {}.{}.{}; linked version: {}.{}.{}", // physfs_compiled_version.major, @@ -43,29 +43,29 @@ resource_manager::resource_manager() // ); // Init PhysicsFS - debug::log::trace("Initializing PhysicsFS..."); + debug::log_trace("Initializing PhysicsFS..."); if (!PHYSFS_init(nullptr)) { - debug::log::error("Failed to initialize PhysicsFS: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + debug::log_error("Failed to initialize PhysicsFS: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); throw std::runtime_error("Failed to initialize PhysicsFS"); } else { - debug::log::trace("Initialized PhysicsFS"); + debug::log_trace("Initialized PhysicsFS"); } } resource_manager::~resource_manager() { // Deinit PhysicsFS - debug::log::trace("Deinitializing PhysicsFS..."); + debug::log_trace("Deinitializing PhysicsFS..."); if (!PHYSFS_deinit()) { - debug::log::error("Failed to deinitialize PhysicsFS: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + debug::log_error("Failed to deinitialize PhysicsFS: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); } else { - debug::log::trace("Deinitialized PhysicsFS"); + debug::log_trace("Deinitialized PhysicsFS"); } } @@ -73,15 +73,15 @@ bool resource_manager::mount(const std::filesystem::path& path) { const std::string path_string = path.string(); - debug::log::trace("Mounting path \"{}\"...", path_string); + debug::log_trace("Mounting path \"{}\"...", path_string); if (!PHYSFS_mount(path_string.c_str(), nullptr, 1)) { - debug::log::error("Failed to mount path \"{}\": {}", path_string, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + debug::log_error("Failed to mount path \"{}\": {}", path_string, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); return false; } - debug::log::trace("Mounted path \"{}\"", path_string); + debug::log_trace("Mounted path \"{}\"", path_string); return true; } @@ -90,15 +90,15 @@ bool resource_manager::unmount(const std::filesystem::path& path) { const std::string path_string = path.string(); - debug::log::trace("Unmounting path \"{}\"...", path_string); + debug::log_trace("Unmounting path \"{}\"...", path_string); if (!PHYSFS_unmount(path_string.c_str())) { - debug::log::error("Failed to unmount path \"{}\": {}", path_string, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + debug::log_error("Failed to unmount path \"{}\": {}", path_string, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); return false; } - debug::log::trace("Unmounted path \"{}\"", path_string); + debug::log_trace("Unmounted path \"{}\"", path_string); return true; } @@ -109,13 +109,13 @@ bool resource_manager::set_write_path(const std::filesystem::path& path) if (!PHYSFS_setWriteDir(path_string.c_str())) { - debug::log::error("Failed set write path to \"{}\": {}", path_string, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + debug::log_error("Failed set write path to \"{}\": {}", path_string, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); return false; } write_path = path; - debug::log::trace("Set write path to \"{}\"", path_string); + debug::log_trace("Set write path to \"{}\"", path_string); return true; } @@ -130,7 +130,7 @@ std::shared_ptr resource_manager::fetch(const std::filesystem::path& path) } else { - debug::log::trace("Fetched expired resource from cache \"{}\"", path.string()); + debug::log_trace("Fetched expired resource from cache \"{}\"", path.string()); } } @@ -142,7 +142,7 @@ std::unique_ptr resource_manager::open_read(const std::file auto ctx = std::make_unique(path); if (!ctx->is_open()) { - debug::log::error("Failed to open file \"{}\" for reading: {}", path.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + debug::log_error("Failed to open file \"{}\" for reading: {}", path.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); return nullptr; } @@ -154,7 +154,7 @@ std::unique_ptr resource_manager::open_write(const std::files auto ctx = std::make_unique(path); if (!ctx->is_open()) { - debug::log::error("Failed to open file \"{}\" for writing: {}", path.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + debug::log_error("Failed to open file \"{}\" for writing: {}", path.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); return nullptr; } diff --git a/src/engine/resources/resource-manager.hpp b/src/engine/resources/resource-manager.hpp index 68f88e3..86d7843 100644 --- a/src/engine/resources/resource-manager.hpp +++ b/src/engine/resources/resource-manager.hpp @@ -154,7 +154,7 @@ std::shared_ptr resource_manager::load(const std::filesystem::path& path) try { - debug::log::trace("Loading resource \"{}\"...", path_string); + debug::log_trace("Loading resource \"{}\"...", path_string); // Open file for reading auto deserialize_ctx = open_read(path); @@ -163,13 +163,13 @@ std::shared_ptr resource_manager::load(const std::filesystem::path& path) std::shared_ptr resource = resource_loader::load(*this, *deserialize_ctx); resource_cache[path] = resource; - debug::log::trace("Loaded resource \"{}\"", path_string); + debug::log_trace("Loaded resource \"{}\"", path_string); return resource; } catch (const std::exception& e) { - debug::log::error("Failed to load resource \"{}\": {}", path_string, e.what()); + debug::log_error("Failed to load resource \"{}\": {}", path_string, e.what()); } return nullptr; @@ -182,20 +182,20 @@ bool resource_manager::save(const T& resource, const std::filesystem::path& path try { - debug::log::trace("Saving resource to \"{}\"...", path_string); + debug::log_trace("Saving resource to \"{}\"...", path_string); // Open file for writing auto serialize_ctx = open_write(path); serializer().serialize(resource, *serialize_ctx); - debug::log::trace("Saved resource to \"{}\"", path_string); + debug::log_trace("Saved resource to \"{}\"", path_string); return true; } catch (const std::exception& e) { - debug::log::error("Failed to save resource to \"{}\": {}", path_string, e.what()); + debug::log_error("Failed to save resource to \"{}\": {}", path_string, e.what()); } return false; diff --git a/src/engine/scene/billboard.cpp b/src/engine/scene/billboard.cpp index 1887d80..8ee1ce4 100644 --- a/src/engine/scene/billboard.cpp +++ b/src/engine/scene/billboard.cpp @@ -19,71 +19,63 @@ #include #include -#include +#include #include #include namespace scene { -billboard::billboard() -{ - const float vertex_data[] = +namespace { + + constexpr gl::vertex_input_attribute billboard_vertex_attributes[2] = { - -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, - -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, - -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f + { + render::vertex_attribute_location::position, + 0, + gl::format::r32g32_sfloat, + 0 + }, + { + render::vertex_attribute_location::uv, + 0, + gl::format::r32g32_sfloat, + 2 * sizeof(float) + } }; - - const std::size_t vertex_size = 8; - const std::size_t vertex_stride = sizeof(float) * vertex_size; - const std::size_t vertex_count = 6; - - m_vbo = std::make_unique(gl::buffer_usage::static_draw, sizeof(float) * vertex_size * vertex_count, std::as_bytes(std::span{vertex_data})); - std::size_t attribute_offset = 0; - - // Define position vertex attribute - gl::vertex_attribute position_attribute; - position_attribute.buffer = m_vbo.get(); - position_attribute.offset = attribute_offset; - position_attribute.stride = vertex_stride; - position_attribute.type = gl::vertex_attribute_type::float_32; - position_attribute.components = 3; - attribute_offset += position_attribute.components * sizeof(float); - - // Define UV vertex attribute - gl::vertex_attribute uv_attribute; - uv_attribute.buffer = m_vbo.get(); - uv_attribute.offset = attribute_offset; - uv_attribute.stride = vertex_stride; - uv_attribute.type = gl::vertex_attribute_type::float_32; - uv_attribute.components = 2; - attribute_offset += uv_attribute.components * sizeof(float); + constexpr float billboard_vertex_data[] = + { + -1.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f + }; - // Define barycentric vertex attribute - gl::vertex_attribute barycentric_attribute; - barycentric_attribute.buffer = m_vbo.get(); - barycentric_attribute.offset = attribute_offset; - barycentric_attribute.stride = vertex_stride; - barycentric_attribute.type = gl::vertex_attribute_type::float_32; - barycentric_attribute.components = 3; - //attribute_offset += barycentric_attribute.components * sizeof(float); + constexpr std::size_t billboard_vertex_stride = 4 * sizeof(float); +} + +billboard::billboard() +{ + // Construct vertex array + m_vertex_array = std::make_unique(billboard_vertex_attributes); - // Bind vertex attributes to VAO - m_vao = std::make_unique(); - m_vao->bind(render::vertex_attribute::position, position_attribute); - m_vao->bind(render::vertex_attribute::uv, uv_attribute); - m_vao->bind(render::vertex_attribute::barycentric, barycentric_attribute); + // Construct vertex buffer + m_vertex_buffer = std::make_unique + ( + gl::buffer_usage::static_draw, + std::as_bytes(std::span{billboard_vertex_data}) + ); // Init render operation - m_render_op.vertex_array = m_vao.get(); - m_render_op.drawing_mode = gl::drawing_mode::triangles; - m_render_op.start_index = 0; - m_render_op.index_count = 6; - m_render_op.transform = math::fmat4::identity(); + m_render_op.primitive_topology = gl::primitive_topology::triangle_strip; + m_render_op.vertex_array = m_vertex_array.get(); + m_render_op.vertex_buffer = m_vertex_buffer.get(); + m_render_op.vertex_offset = 0; + m_render_op.vertex_stride = billboard_vertex_stride; + m_render_op.first_vertex = 0; + m_render_op.vertex_count = 4; + m_render_op.first_instance = 0; + m_render_op.instance_count = 1; } void billboard::render(render::context& ctx) const diff --git a/src/engine/scene/billboard.hpp b/src/engine/scene/billboard.hpp index 0b1251b..908a1af 100644 --- a/src/engine/scene/billboard.hpp +++ b/src/engine/scene/billboard.hpp @@ -89,8 +89,8 @@ public: private: void transformed() override; - std::unique_ptr m_vbo; - std::unique_ptr m_vao; + std::unique_ptr m_vertex_array; + std::unique_ptr m_vertex_buffer; mutable render::operation m_render_op; aabb_type m_bounds{{-1.0f, -1.0f, -1.0f}, {1.0f, 1.0f, 1.0f}}; billboard_type m_billboard_type{billboard_type::flat}; diff --git a/src/engine/scene/directional-light.cpp b/src/engine/scene/directional-light.cpp index 668225c..318e4a6 100644 --- a/src/engine/scene/directional-light.cpp +++ b/src/engine/scene/directional-light.cpp @@ -18,6 +18,8 @@ */ #include +#include +#include namespace scene { @@ -40,6 +42,37 @@ void directional_light::set_shadow_caster(bool caster) noexcept void directional_light::set_shadow_framebuffer(std::shared_ptr framebuffer) noexcept { m_shadow_framebuffer = std::move(framebuffer); + if (m_shadow_framebuffer) + { + if (!m_shadow_texture) + { + m_shadow_texture = std::make_shared + ( + std::static_pointer_cast(m_shadow_framebuffer->attachments().front().image_view), + std::make_shared + ( + gl::sampler_filter::linear, + gl::sampler_filter::linear, + gl::sampler_mipmap_mode::linear, + gl::sampler_address_mode::clamp_to_border, + gl::sampler_address_mode::clamp_to_border, + gl::sampler_address_mode::clamp_to_border, + 0.0f, + 0.0f, + true, + gl::compare_op::greater + ) + ); + } + else + { + m_shadow_texture->set_image_view(std::static_pointer_cast(m_shadow_framebuffer->attachments().front().image_view)); + } + } + else + { + m_shadow_texture = nullptr; + } } void directional_light::set_shadow_bias(float bias) noexcept diff --git a/src/engine/scene/directional-light.hpp b/src/engine/scene/directional-light.hpp index fa8c982..df99e7e 100644 --- a/src/engine/scene/directional-light.hpp +++ b/src/engine/scene/directional-light.hpp @@ -21,7 +21,7 @@ #define ANTKEEPER_SCENE_DIRECTIONAL_LIGHT_HPP #include -#include +#include #include #include #include @@ -167,6 +167,12 @@ public: return m_shadow_framebuffer; } + /// Returns the shadow map texture. + [[nodiscard]] inline constexpr const std::shared_ptr& get_shadow_texture() const noexcept + { + return m_shadow_texture; + } + /// Returns the shadow bias factor. [[nodiscard]] inline constexpr float get_shadow_bias() const noexcept { @@ -242,7 +248,8 @@ private: math::fvec3 m_colored_illuminance{}; bool m_shadow_caster{false}; - std::shared_ptr m_shadow_framebuffer{nullptr}; + std::shared_ptr m_shadow_framebuffer; + std::shared_ptr m_shadow_texture; float m_shadow_bias{0.005f}; unsigned int m_shadow_cascade_count{4}; float m_shadow_max_distance{100.0f}; diff --git a/src/engine/scene/light-probe.cpp b/src/engine/scene/light-probe.cpp index 2ab0f3d..0b42d7e 100644 --- a/src/engine/scene/light-probe.cpp +++ b/src/engine/scene/light-probe.cpp @@ -24,12 +24,35 @@ namespace scene { light_probe::light_probe() { // Allocate illuminance texture - m_illuminance_texture = std::make_shared(12, gl::pixel_type::float_32, gl::pixel_format::rgba); - m_illuminance_texture->set_filters(gl::texture_min_filter::nearest, gl::texture_mag_filter::nearest); + m_illuminance_texture = std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::r32g32b32a32_sfloat, + 12 + ) + ), + std::make_shared + ( + gl::sampler_filter::nearest, + gl::sampler_filter::nearest, + gl::sampler_mipmap_mode::nearest, + gl::sampler_address_mode::clamp_to_edge + ) + ); // Allocate and init illuminance framebuffer - m_illuminance_framebuffer = std::make_shared(12, 1); - m_illuminance_framebuffer->attach(gl::framebuffer_attachment_type::color, m_illuminance_texture.get()); + const gl::framebuffer_attachment attachments[1] = + { + { + gl::color_attachment_bit, + m_illuminance_texture->get_image_view(), + 0 + } + }; + m_illuminance_framebuffer = std::make_shared(attachments, 12, 1); // Init illuminance matrices m_illuminance_matrices[0] = {}; @@ -39,7 +62,18 @@ light_probe::light_probe() void light_probe::update_illuminance_matrices() { - m_illuminance_texture->read(std::as_writable_bytes(std::span{m_illuminance_matrices}), gl::pixel_type::float_32, gl::pixel_format::rgba, 0); + m_illuminance_texture->get_image_view()->get_image()->read + ( + 0, + 0, + 0, + 0, + 12, + 1, + 1, + gl::format::r32g32b32a32_sfloat, + std::as_writable_bytes(std::span{m_illuminance_matrices}) + ); } void light_probe::set_luminance_texture(std::shared_ptr texture) @@ -51,22 +85,28 @@ void light_probe::set_luminance_texture(std::shared_ptr textur // Update luminance framebuffers if (m_luminance_texture) { - const std::uint8_t mip_count = 1 + static_cast(std::log2(texture->get_face_size())); - for (std::uint8_t i = 0; i < mip_count; ++i) - { - if (i >= m_luminance_framebuffers.size()) - { - m_luminance_framebuffers.emplace_back(std::make_shared()); - } - - m_luminance_framebuffers[i]->attach(gl::framebuffer_attachment_type::color, m_luminance_texture.get(), i); - } + const auto face_size = texture->get_image_view()->get_image()->get_dimensions()[0]; + const auto mip_count = static_cast(std::bit_width(face_size)); + + m_luminance_framebuffers.resize(mip_count); - if (m_luminance_framebuffers.size() > mip_count) + for (std::uint32_t i = 0; i < mip_count; ++i) { - m_luminance_framebuffers.resize(mip_count); + const gl::framebuffer_attachment attachments[1] = + { + { + gl::color_attachment_bit, + m_luminance_texture->get_image_view(), + i + } + }; + m_luminance_framebuffers[i] = std::make_shared(attachments, face_size >> i, face_size >> i); } } + else + { + m_luminance_framebuffers.clear(); + } set_luminance_outdated(true); set_illuminance_outdated(true); diff --git a/src/engine/scene/light-probe.hpp b/src/engine/scene/light-probe.hpp index 8fb4edc..282d2e9 100644 --- a/src/engine/scene/light-probe.hpp +++ b/src/engine/scene/light-probe.hpp @@ -21,8 +21,7 @@ #define ANTKEEPER_SCENE_LIGHT_PROBE_HPP #include -#include -#include +#include #include #include #include diff --git a/src/engine/scene/skeletal-mesh.cpp b/src/engine/scene/skeletal-mesh.cpp index 4ea0784..0022be5 100644 --- a/src/engine/scene/skeletal-mesh.cpp +++ b/src/engine/scene/skeletal-mesh.cpp @@ -39,12 +39,17 @@ void skeletal_mesh::set_model(std::shared_ptr model) for (std::size_t i = 0; i < m_operations.size(); ++i) { const auto& group = m_model->get_groups()[i]; - auto& operation = m_operations[i]; + + operation.primitive_topology = group.primitive_topology; operation.vertex_array = m_model->get_vertex_array().get(); - operation.drawing_mode = group.drawing_mode; - operation.start_index = group.start_index; - operation.index_count = group.index_count; + operation.vertex_buffer = m_model->get_vertex_buffer().get(); + operation.vertex_offset = m_model->get_vertex_offset(); + operation.vertex_stride = m_model->get_vertex_stride(); + operation.first_vertex = group.first_vertex; + operation.vertex_count = group.vertex_count; + operation.first_instance = 0; + operation.instance_count = 1; operation.material = group.material; operation.matrix_palette = m_pose.get_matrix_palette(); } diff --git a/src/engine/scene/static-mesh.cpp b/src/engine/scene/static-mesh.cpp index 58ba1ef..dccbce1 100644 --- a/src/engine/scene/static-mesh.cpp +++ b/src/engine/scene/static-mesh.cpp @@ -39,12 +39,17 @@ void static_mesh::set_model(std::shared_ptr model) for (std::size_t i = 0; i < m_operations.size(); ++i) { const auto& group = m_model->get_groups()[i]; - auto& operation = m_operations[i]; + + operation.primitive_topology = group.primitive_topology; operation.vertex_array = m_model->get_vertex_array().get(); - operation.drawing_mode = group.drawing_mode; - operation.start_index = group.start_index; - operation.index_count = group.index_count; + operation.vertex_buffer = m_model->get_vertex_buffer().get(); + operation.vertex_offset = m_model->get_vertex_offset(); + operation.vertex_stride = m_model->get_vertex_stride(); + operation.first_vertex = group.first_vertex; + operation.vertex_count = group.vertex_count; + operation.first_instance = 0; + operation.instance_count = 1; operation.material = group.material; } } diff --git a/src/engine/scene/text.cpp b/src/engine/scene/text.cpp index dcffc51..2e776a4 100644 --- a/src/engine/scene/text.cpp +++ b/src/engine/scene/text.cpp @@ -18,65 +18,66 @@ */ #include -#include +#include #include #include +#include #include namespace scene { -text::text() -{ - // Allocate VBO and VAO - m_vbo = std::make_unique(); - m_vao = std::make_unique(); - - // Calculate vertex stride - m_vertex_stride = (3 + 2 + 4) * sizeof(float); - - // Init vertex attribute offset - std::size_t attribute_offset = 0; +namespace { - // Define vertex position attribute - gl::vertex_attribute position_attribute; - position_attribute.buffer = m_vbo.get(); - position_attribute.offset = attribute_offset; - position_attribute.stride = m_vertex_stride; - position_attribute.type = gl::vertex_attribute_type::float_32; - position_attribute.components = 3; - attribute_offset += position_attribute.components * sizeof(float); - - // Define vertex UV attribute - gl::vertex_attribute uv_attribute; - uv_attribute.buffer = m_vbo.get(); - uv_attribute.offset = attribute_offset; - uv_attribute.stride = m_vertex_stride; - uv_attribute.type = gl::vertex_attribute_type::float_32; - uv_attribute.components = 2; - attribute_offset += uv_attribute.components * sizeof(float); + /// Text vertex attributes. + constexpr gl::vertex_input_attribute text_vertex_attributes[3] = + { + { + render::vertex_attribute_location::position, + 0, + gl::format::r32g32_sfloat, + 0 + }, + { + render::vertex_attribute_location::uv, + 0, + gl::format::r32g32_sfloat, + 2 * sizeof(float) + }, + { + render::vertex_attribute_location::color, + 0, + gl::format::r32g32b32a32_sfloat, + 4 * sizeof(float) + } + }; - // Define vertex color attribute - gl::vertex_attribute color_attribute; - color_attribute.buffer = m_vbo.get(); - color_attribute.offset = attribute_offset; - color_attribute.stride = m_vertex_stride; - color_attribute.type = gl::vertex_attribute_type::float_32; - color_attribute.components = 4; - //attribute_offset += color_attribute.components * sizeof(float); + /// Text vertex stride. + constexpr std::size_t text_vertex_stride = (2 + 2 + 4) * sizeof(float); +} + +text::text() +{ + // Construct vertex array + m_vertex_array = std::make_unique(text_vertex_attributes); - // Bind vertex attributes to VAO - m_vao->bind(render::vertex_attribute::position, position_attribute); - m_vao->bind(render::vertex_attribute::uv, uv_attribute); - m_vao->bind(render::vertex_attribute::color, color_attribute); + // Construct empty vertex buffer + m_vertex_buffer = std::make_unique(); // Init render operation - m_render_op.vertex_array = m_vao.get(); - m_render_op.drawing_mode = gl::drawing_mode::triangles; + m_render_op.primitive_topology = gl::primitive_topology::triangle_list; + m_render_op.vertex_array = m_vertex_array.get(); + m_render_op.vertex_buffer = m_vertex_buffer.get(); + m_render_op.vertex_offset = 0; + m_render_op.vertex_stride = text_vertex_stride; + m_render_op.first_vertex = 0; + m_render_op.vertex_count = 0; + m_render_op.first_instance = 0; + m_render_op.instance_count = 1; } void text::render(render::context& ctx) const { - if (m_vertex_count) + if (m_render_op.vertex_count) { m_render_op.depth = ctx.camera->get_view_frustum().near().distance(get_translation()); m_render_op.layer_mask = get_layer_mask(); @@ -145,26 +146,25 @@ void text::update_content() // If no valid font or no text, clear vertex count if (!m_font || m_content_u32.empty()) { - m_vertex_count = 0; - m_render_op.index_count = 0; + m_render_op.vertex_count = 0; m_local_bounds = {{0, 0, 0}, {0, 0, 0}}; transformed(); return; } - // Calculate new vertex count and minimum vertex buffer size - std::size_t vertex_count = m_content_u32.length() * 6; - std::size_t min_vertex_buffer_size = vertex_count * m_vertex_stride; - - // Expand vertex data buffer to accommodate vertices - if (m_vertex_data.size() < min_vertex_buffer_size) + // Reserve vertex data + auto vbo_min_size = m_content_u32.length() * text_vertex_stride * 6; + if (m_vertex_data.size() < vbo_min_size) { - m_vertex_data.resize(min_vertex_buffer_size); + m_vertex_data.resize(vbo_min_size); } - // Get font metrics and bitmap + std::uint32_t visible_character_count = static_cast(m_content_u32.length()); + + // Get font metrics and texture const type::font_metrics& font_metrics = m_font->get_font_metrics(); - const image& font_bitmap = m_font->get_bitmap(); + const auto& font_texture = m_font->get_texture(); + const auto& font_texture_dimensions = font_texture->get_image_view()->get_image()->get_dimensions(); // Init pen position math::fvec2 pen_position = {0.0f, 0.0f}; @@ -184,25 +184,24 @@ void text::update_content() pen_position.x() += m_font->get_kerning(previous_code, code).x(); } - if (m_font->contains(code)) + // Get glyph + const type::bitmap_glyph* glyph = m_font->get_glyph(code); + if (glyph) { - // Get glyph - const type::bitmap_glyph& glyph = m_font->get_glyph(code); - // Calculate vertex positions math::fvec2 positions[6]; - positions[0] = pen_position + glyph.metrics.horizontal_bearing; - positions[1] = {positions[0].x(), positions[0].y() - glyph.metrics.height}; - positions[2] = {positions[0].x() + glyph.metrics.width, positions[1].y()}; + positions[0] = pen_position + glyph->metrics.horizontal_bearing; + positions[1] = {positions[0].x(), positions[0].y() - glyph->metrics.height}; + positions[2] = {positions[0].x() + glyph->metrics.width, positions[1].y()}; positions[3] = {positions[2].x(), positions[0].y()}; positions[4] = positions[0]; positions[5] = positions[2]; // Calculate vertex UVs math::fvec2 uvs[6]; - uvs[0] = {static_cast(glyph.position.x()), static_cast(glyph.position.y())}; - uvs[1] = {uvs[0].x(), uvs[0].y() + glyph.metrics.height}; - uvs[2] = {uvs[0].x() + glyph.metrics.width, uvs[1].y()}; + uvs[0] = {static_cast(glyph->position.x()), static_cast(glyph->position.y())}; + uvs[1] = {uvs[0].x(), uvs[0].y() + glyph->metrics.height}; + uvs[2] = {uvs[0].x() + glyph->metrics.width, uvs[1].y()}; uvs[3] = {uvs[2].x(), uvs[0].y()}; uvs[4] = uvs[0]; uvs[5] = uvs[2]; @@ -214,8 +213,8 @@ void text::update_content() positions[i].y() = std::round(positions[i].y()); // Normalize UVs - uvs[i].x() = uvs[i].x() / static_cast(font_bitmap.size().x()); - uvs[i].y() = uvs[i].y() / static_cast(font_bitmap.size().y()); + uvs[i].x() = uvs[i].x() / static_cast(font_texture_dimensions[0]); + uvs[i].y() = uvs[i].y() / static_cast(font_texture_dimensions[1]); } // Add vertex to vertex data buffer @@ -223,7 +222,6 @@ void text::update_content() { *(v++) = positions[i].x(); *(v++) = positions[i].y(); - *(v++) = 0.0f; *(v++) = uvs[i].x(); *(v++) = uvs[i].y(); *(v++) = m_color[0]; @@ -233,7 +231,7 @@ void text::update_content() } // Advance pen position - pen_position.x() += glyph.metrics.horizontal_advance; + pen_position.x() += glyph->metrics.horizontal_advance; // Update local-space bounds for (int i = 0; i < 4; ++i) @@ -248,15 +246,11 @@ void text::update_content() } else { - // Glyph not in font, zero vertex data - for (std::size_t i = 0; i < (6 * 9); ++i) - { - *(v++) = 0.0f; - } + --visible_character_count; } // Handle newlines - if (code == static_cast('\n')) + if (code == U'\n') { pen_position.x() = 0.0f; pen_position.y() -= font_metrics.linegap; @@ -266,19 +260,21 @@ void text::update_content() previous_code = code; } - // Resize VBO, if necessary, and upload vertex data - if (vertex_count > m_vertex_count) + // Adjust min VBO size + vbo_min_size = visible_character_count * text_vertex_stride * 6; + + // Upload vertex data to VBO, growing VBO size if necessary + if (vbo_min_size > m_vertex_buffer->size()) { - m_vbo->resize(min_vertex_buffer_size, m_vertex_data); + m_vertex_buffer->resize(vbo_min_size, {m_vertex_data.data(), vbo_min_size}); } else { - m_vbo->write({m_vertex_data.data(), min_vertex_buffer_size}); + m_vertex_buffer->write({m_vertex_data.data(), vbo_min_size}); } - // Update vertex count - m_vertex_count = vertex_count; - m_render_op.index_count = vertex_count; + // Update render op + m_render_op.vertex_count = visible_character_count * 6; // Update world-space bounds transformed(); @@ -286,21 +282,21 @@ void text::update_content() void text::update_color() { - float* v = reinterpret_cast(m_vertex_data.data()); - for (std::size_t i = 0; i < m_vertex_count; ++i) + std::byte* v = m_vertex_data.data(); + + // Skip position UV + v += (2 + 2) * sizeof(float); + + for (std::size_t i = 0; i < m_render_op.vertex_count; ++i) { - // Skip vertex position (vec3) and vertex UV (vec2) - v += (3 + 2); - // Update vertex color - *(v++) = m_color[0]; - *(v++) = m_color[1]; - *(v++) = m_color[2]; - *(v++) = m_color[3]; + std::memcpy(v, m_color.data(), sizeof(float) * 4); + + v += text_vertex_stride; } // Update VBO - m_vbo->write({m_vertex_data.data(), m_vertex_count * m_vertex_stride}); + m_vertex_buffer->write({m_vertex_data.data(), m_render_op.vertex_count * text_vertex_stride}); } } // namespace scene diff --git a/src/engine/scene/text.hpp b/src/engine/scene/text.hpp index 85da127..c88883c 100644 --- a/src/engine/scene/text.hpp +++ b/src/engine/scene/text.hpp @@ -84,7 +84,7 @@ public: void set_color(const math::fvec4& color); /// Returns the text material. - [[nodiscard]] inline std::shared_ptr get_material() const noexcept + [[nodiscard]] inline const std::shared_ptr& get_material() const noexcept { return m_render_op.material; } @@ -121,8 +121,7 @@ public: private: void update_content(); void update_color(); - - virtual void transformed(); + void transformed() override; mutable render::operation m_render_op; aabb_type m_local_bounds{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}}; @@ -132,11 +131,9 @@ private: std::string m_content_u8; std::u32string m_content_u32; math::fvec4 m_color{1.0f, 0.0f, 1.0f, 1.0f}; - std::size_t m_vertex_stride{0}; - std::size_t m_vertex_count{0}; std::vector m_vertex_data; - std::unique_ptr m_vao; - std::unique_ptr m_vbo; + std::unique_ptr m_vertex_array; + std::unique_ptr m_vertex_buffer; }; } // namespace scene diff --git a/src/engine/type/bitmap-font.cpp b/src/engine/type/bitmap-font.cpp index b3d9397..8cc264d 100644 --- a/src/engine/type/bitmap-font.cpp +++ b/src/engine/type/bitmap-font.cpp @@ -27,28 +27,31 @@ bitmap_font::bitmap_font(const font_metrics& metrics): font(metrics) {} -bitmap_font::bitmap_font() +bitmap_font::bitmap_font(): + bitmap_font(font_metrics{}) {} bool bitmap_font::contains(char32_t code) const { - return glyphs.count(code) != 0; + return m_glyphs.count(code) != 0; } -void bitmap_font::insert(char32_t code, const bitmap_glyph& glyph) +bitmap_glyph& bitmap_font::insert(char32_t code) { - glyphs[code] = glyph; + return std::get<0>(m_glyphs.try_emplace(code))->second; } void bitmap_font::remove(char32_t code) { - if (auto it = glyphs.find(code); it != glyphs.end()) - glyphs.erase(it); + if (auto it = m_glyphs.find(code); it != m_glyphs.end()) + { + m_glyphs.erase(it); + } } void bitmap_font::clear() { - glyphs.clear(); + m_glyphs.clear(); } bool bitmap_font::pack(bool resize) @@ -66,27 +69,27 @@ bool bitmap_font::pack(bool resize) }; // Calculate initial size of the font bitmap - std::uint32_t bitmap_w; - std::uint32_t bitmap_h; + std::uint32_t bitmap_w = 0; + std::uint32_t bitmap_h = 0; if (resize) { // Find the maximum glyph dimensions std::uint32_t max_glyph_w = 0; std::uint32_t max_glyph_h = 0; - for (auto it = glyphs.begin(); it != glyphs.end(); ++it) + for (auto it = m_glyphs.begin(); it != m_glyphs.end(); ++it) { - max_glyph_w = std::max(max_glyph_w, static_cast(it->second.bitmap.size().x())); - max_glyph_h = std::max(max_glyph_h, static_cast(it->second.bitmap.size().y())); + max_glyph_w = std::max(max_glyph_w, it->second.bitmap_width); + max_glyph_h = std::max(max_glyph_h, it->second.bitmap_height); } // Find minimum power of two dimensions that can accommodate maximum glyph dimensions bitmap_w = ceil2(max_glyph_w); bitmap_h = ceil2(max_glyph_h); } - else + else if (m_texture) { - bitmap_w = static_cast(bitmap.size().x()); - bitmap_h = static_cast(bitmap.size().y()); + bitmap_w = m_texture->get_image_view()->get_image()->get_dimensions()[0]; + bitmap_h = m_texture->get_image_view()->get_image()->get_dimensions()[1]; } bool packed = false; @@ -96,10 +99,10 @@ bool bitmap_font::pack(bool resize) while (!packed) { // For each glyph - for (auto it = glyphs.begin(); it != glyphs.end(); ++it) + for (auto it = m_glyphs.begin(); it != m_glyphs.end(); ++it) { // Attempt to pack glyph bitmap - const auto* node = glyph_pack.pack(static_cast(it->second.bitmap.size().x()), static_cast(it->second.bitmap.size().y())); + const auto* node = glyph_pack.pack(it->second.bitmap_width, it->second.bitmap_height); // Abort if packing failed if (!node) @@ -110,7 +113,7 @@ bool bitmap_font::pack(bool resize) } // Check if not all glyphs were packed - if (glyph_map.size() != glyphs.size()) + if (glyph_map.size() != m_glyphs.size()) { if (!resize) { @@ -141,82 +144,103 @@ bool bitmap_font::pack(bool resize) // Copy glyph bitmaps into font bitmap if (packed) { - // Resize font bitmap - bitmap.resize({bitmap_w, bitmap_h, 1}); + // Resize bitmap font image + if (!m_texture || + bitmap_w != m_texture->get_image_view()->get_image()->get_dimensions()[0] || + bitmap_h != m_texture->get_image_view()->get_image()->get_dimensions()[1]) + { + + // Construct font bitmap sampler + if (!m_sampler) + { + m_sampler = std::make_shared + ( + gl::sampler_filter::linear, + gl::sampler_filter::linear, + gl::sampler_mipmap_mode::linear, + gl::sampler_address_mode::clamp_to_edge, + gl::sampler_address_mode::clamp_to_edge + ); + } + + const std::uint32_t mip_count = static_cast(std::bit_width(std::max(bitmap_w, bitmap_h))); + m_texture = std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::r8_unorm, + bitmap_w, + bitmap_h, + mip_count + ), + gl::format::r8_unorm, + 0, + mip_count + ), + m_sampler + ); + } // For each glyph - for (auto it = glyphs.begin(); it != glyphs.end(); ++it) + for (auto it = m_glyphs.begin(); it != m_glyphs.end(); ++it) { // Find rect pack node corresponding to the glyph const auto* node = glyph_map[it->first]; - // Copy glyph bitmap data into font bitmap - image& glyph_bitmap = it->second.bitmap; - bitmap.copy(glyph_bitmap, {glyph_bitmap.size().x(), glyph_bitmap.size().y()}, {0, 0}, math::uvec2{node->bounds.min.x(), node->bounds.min.y()}); + // Write glyph bitmap data into bitmap font image + m_texture->get_image_view()->get_image()->write + ( + 0, + node->bounds.min.x(), + node->bounds.min.y(), + 0, + it->second.bitmap_width, + it->second.bitmap_height, + 1, + gl::format::r8_unorm, + it->second.bitmap + ); // Record coordinates of glyph bitmap within font bitmap it->second.position = {node->bounds.min.x(), node->bounds.min.y()}; - - // Clear glyph bitmap data - glyph_bitmap.resize({0u, 0u, 0u}); } - } - - return packed; -} - -void bitmap_font::unpack(bool resize) -{ - for (auto it = glyphs.begin(); it != glyphs.end(); ++it) - { - bitmap_glyph& glyph = it->second; - // Get glyph dimensions - std::uint32_t glyph_width = static_cast(glyph.metrics.width + 0.5f); - std::uint32_t glyph_height = static_cast(glyph.metrics.height + 0.5f); - - // Reformat glyph bitmap if necessary - if (!glyph.bitmap.compatible(bitmap)) - { - glyph.bitmap.format(bitmap.channels(), bitmap.bit_depth()); - } - - // Resize glyph bitmap if necessary - if (static_cast(glyph.bitmap.size().x()) != glyph_width || static_cast(glyph.bitmap.size().y()) != glyph_height) - { - glyph.bitmap.resize(math::uvec2{glyph_width, glyph_height}); - } - - // Copy pixel data from font bitmap to glyph bitmap - glyph.bitmap.copy(bitmap, math::uvec2{glyph_width, glyph_height}, math::uvec2{glyph.position.x(), glyph.position.y()}); + // Regenerate mipmaps + m_texture->get_image_view()->get_image()->generate_mipmaps(); } - // Free font bitmap pixel data - if (resize) - { - bitmap.resize({0, 0, 0}); - } + return packed; } const glyph_metrics& bitmap_font::get_glyph_metrics(char32_t code) const { - if (auto it = glyphs.find(code); it != glyphs.end()) + if (auto it = m_glyphs.find(code); it != m_glyphs.end()) + { return it->second.metrics; + } throw std::invalid_argument("Cannot fetch metrics of unknown bitmap glyph"); } -const bitmap_glyph& bitmap_font::get_glyph(char32_t code) const +const bitmap_glyph* bitmap_font::get_glyph(char32_t code) const { - if (auto it = glyphs.find(code); it != glyphs.end()) - return it->second; - throw std::invalid_argument("Cannot get unknown bitmap glyph"); + if (auto it = m_glyphs.find(code); it != m_glyphs.end()) + { + return &it->second; + } + + return nullptr; } -bitmap_glyph& bitmap_font::get_glyph(char32_t code) +bitmap_glyph* bitmap_font::get_glyph(char32_t code) { - if (auto it = glyphs.find(code); it != glyphs.end()) - return it->second; - throw std::invalid_argument("Cannot get unknown bitmap glyph"); + if (auto it = m_glyphs.find(code); it != m_glyphs.end()) + { + return &it->second; + } + + return nullptr; } } // namespace type diff --git a/src/engine/type/bitmap-font.hpp b/src/engine/type/bitmap-font.hpp index 551b5ef..07fff3d 100644 --- a/src/engine/type/bitmap-font.hpp +++ b/src/engine/type/bitmap-font.hpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include namespace type { @@ -58,9 +58,10 @@ public: * Inserts a glyph into the font. * * @param code UTF-32 character code of the glyph to insert. - * @param glyph Bitmap glyph data. + * + * @return Reference to the inserted glyph. */ - void insert(char32_t code, const bitmap_glyph& glyph); + bitmap_glyph& insert(char32_t code); /** * Removes a glyph from the font. @@ -92,12 +93,6 @@ public: */ void unpack(bool resize = true); - /// Returns a reference to the bitmap containing glyph pixel data. - const image& get_bitmap() const; - - /// @copydoc bitmap_font::get_bitmap() const - image& get_bitmap(); - /** * @copydoc font::get_glyph_metrics(char32_t) const * @@ -106,46 +101,31 @@ public: virtual const glyph_metrics& get_glyph_metrics(char32_t code) const; /** - * Returns a reference to the glyph corresponding to a UTF-32 character code. + * Returns a pointer to the glyph corresponding to a UTF-32 character code, or `nullptr` if no such glyph was found. * * @param code UTF-32 character code of a glyph. - * @return Reference to the corresponding glyph. * - * @except std::invalid_argument Cannot get unknown bitmap glyph + * @return Pointer to the corresponding glyph. */ - const bitmap_glyph& get_glyph(char32_t code) const; + /// @{ + [[nodiscard]] const bitmap_glyph* get_glyph(char32_t code) const; + [[nodiscard]] bitmap_glyph* get_glyph(char32_t code); + /// @} /// @copydoc bitmap_font::get_glyph(char32_t) const - bitmap_glyph& get_glyph(char32_t code); - /** - * Returns a reference to the glyph corresponding to a UTF-32 character code, performing an insertion if such glyph does not already exist. - * - * @param code UTF-32 character code of a glyph. - * @return Reference to the corresponding glyph. - */ - bitmap_glyph& operator[](char32_t code); + /// Returns the bitmap font's bitmap texture. + [[nodiscard]] inline const std::shared_ptr& get_texture() const noexcept + { + return m_texture; + } private: - std::unordered_map glyphs; - image bitmap; + std::unordered_map m_glyphs; + std::shared_ptr m_texture; + std::shared_ptr m_sampler; }; -inline const image& bitmap_font::get_bitmap() const -{ - return bitmap; -} - -inline image& bitmap_font::get_bitmap() -{ - return bitmap; -} - -inline bitmap_glyph& bitmap_font::operator[](char32_t code) -{ - return glyphs[code]; -} - } // namespace type #endif // ANTKEEPER_TYPE_BITMAP_FONT_HPP diff --git a/src/engine/type/bitmap-glyph.hpp b/src/engine/type/bitmap-glyph.hpp index 1876627..9a24153 100644 --- a/src/engine/type/bitmap-glyph.hpp +++ b/src/engine/type/bitmap-glyph.hpp @@ -20,9 +20,10 @@ #ifndef ANTKEEPER_TYPE_BITMAP_GLYPH_HPP #define ANTKEEPER_TYPE_BITMAP_GLYPH_HPP -#include #include #include +#include +#include namespace type { @@ -37,7 +38,13 @@ struct bitmap_glyph glyph_metrics metrics; /// Bitmap representing the glyph. - image bitmap; + std::vector bitmap; + + /// Width of the glyph bitmap, in pixels. + std::uint32_t bitmap_width{}; + + /// Height of the glyph bitmap, in pixels. + std::uint32_t bitmap_height{}; /// Position of the packed glyph bitmap within the font bitmap. math::uvec2 position; diff --git a/src/engine/type/freetype/ft-typeface.cpp b/src/engine/type/freetype/ft-typeface.cpp index 8e88269..b7f129d 100644 --- a/src/engine/type/freetype/ft-typeface.cpp +++ b/src/engine/type/freetype/ft-typeface.cpp @@ -96,7 +96,7 @@ bool ft_typeface::get_metrics(float height, char32_t code, glyph_metrics& metric return true; } -bool ft_typeface::get_bitmap(float height, char32_t code, image& bitmap) const +bool ft_typeface::get_bitmap(float height, char32_t code, std::vector& bitmap, std::uint32_t& bitmap_width, std::uint32_t& bitmap_height) const { // Set font size set_face_pixel_size(height); @@ -110,13 +110,12 @@ bool ft_typeface::get_bitmap(float height, char32_t code, image& bitmap) const throw std::runtime_error("FreeType failed to load glyph (error code " + std::to_string(error) + ")"); } - // Format and resize bitmap - bitmap.resize({0, 0, 0}); - bitmap.format(1, sizeof(FT_Byte) * 8); - bitmap.resize({face->glyph->bitmap.width, face->glyph->bitmap.rows, 1}); + // Copy glyph bitmap data into bitmap + bitmap.resize(face->glyph->bitmap.width * face->glyph->bitmap.rows); + std::memcpy(bitmap.data(), face->glyph->bitmap.buffer, bitmap.size()); - // Copy glyph bitmap data in bitmap - std::memcpy(bitmap.data(), face->glyph->bitmap.buffer, bitmap.size_bytes()); + bitmap_width = static_cast(face->glyph->bitmap.width); + bitmap_height = static_cast(face->glyph->bitmap.rows); return true; } diff --git a/src/engine/type/freetype/ft-typeface.hpp b/src/engine/type/freetype/ft-typeface.hpp index 1fc08f1..24fe421 100644 --- a/src/engine/type/freetype/ft-typeface.hpp +++ b/src/engine/type/freetype/ft-typeface.hpp @@ -51,7 +51,7 @@ public: virtual bool has_kerning() const; virtual bool get_metrics(float height, font_metrics& metrics) const; virtual bool get_metrics(float height, char32_t code, glyph_metrics& metrics) const; - virtual bool get_bitmap(float height, char32_t code, image& bitmap) const; + virtual bool get_bitmap(float height, char32_t code, std::vector& bitmap, std::uint32_t& bitmap_width, std::uint32_t& bitmap_height) const; virtual bool get_kerning(float height, char32_t first, char32_t second, math::fvec2& offset) const; private: diff --git a/src/engine/type/typeface.hpp b/src/engine/type/typeface.hpp index fa0352d..8aef862 100644 --- a/src/engine/type/typeface.hpp +++ b/src/engine/type/typeface.hpp @@ -22,8 +22,8 @@ #include #include -#include #include +#include #include namespace type { @@ -119,7 +119,7 @@ public: * @param[out] bitmap Glyph bitmap data. * @return `true` if glyph bitmap data was returned, `false` otherwise. */ - virtual bool get_bitmap(float height, char32_t code, image& bitmap) const = 0; + virtual bool get_bitmap(float height, char32_t code, std::vector& bitmap, std::uint32_t& bitmap_width, std::uint32_t& bitmap_height) const = 0; /** * Gets the kerning offset for a pair of glyphs. diff --git a/src/engine/utility/hash/combine.hpp b/src/engine/utility/hash/hash-combine.hpp similarity index 85% rename from src/engine/utility/hash/combine.hpp rename to src/engine/utility/hash/hash-combine.hpp index 2f314d5..d0f5b98 100644 --- a/src/engine/utility/hash/combine.hpp +++ b/src/engine/utility/hash/hash-combine.hpp @@ -22,8 +22,6 @@ #include -namespace hash { - /** * Combines two hash values. * @@ -33,19 +31,17 @@ namespace hash { * @return Combination of @p x and @p y. */ /// @{ -[[nodiscard]] inline std::uint32_t combine(std::uint32_t x, std::uint32_t y) noexcept +[[nodiscard]] inline constexpr std::uint32_t hash_combine(std::uint32_t x, std::uint32_t y) noexcept { // 0x9e3779b9 = 2^32 / Phi return x ^ (y + 0x9e3779b9 + (x << 6) + (x >> 2)); } -[[nodiscard]] inline std::uint64_t combine(std::uint64_t x, std::uint64_t y) noexcept +[[nodiscard]] inline constexpr std::uint64_t hash_combine(std::uint64_t x, std::uint64_t y) noexcept { // 0x9e3779b97f4a7c16 = 2^64 / Phi return x ^ (y + 0x9e3779b97f4a7c16 + (x << 6) + (x >> 2)); } /// @} -} // namespace hash - #endif // ANTKEEPER_UTILITY_HASH_COMBINE_HPP diff --git a/src/engine/utility/image.cpp b/src/engine/utility/image.cpp deleted file mode 100644 index 1e8f09a..0000000 --- a/src/engine/utility/image.cpp +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -bool image::compatible(const image& other) const noexcept -{ - return (other.m_channels == m_channels && other.m_bit_depth == m_bit_depth); -} - -void image::copy -( - const image& source, - const math::uvec2& dimensions, - const math::uvec2& from, - const math::uvec2& to -) -{ - if (!compatible(source)) - { - throw std::runtime_error("Cannot copy image with mismatched format"); - } - - for (auto i = 0u; i < dimensions.y(); ++i) - { - // Calculate vertical pixel offset - const auto from_i = from.y() + i; - const auto to_i = to.y() + i; - - // Bounds check - if (from_i >= source.m_size.y() || to_i >= m_size.y()) - { - break; - } - - for (auto j = 0u; j < dimensions.x(); ++j) - { - // Calculate horizontal pixel offsets - const auto from_j = from.x() + j; - const auto to_j = to.x() + j; - - // Bounds check - if (from_j >= source.m_size.x() || to_j >= m_size.x()) - { - continue; - } - - // Calculate pixel data offset (in bytes) - const auto from_offset = (static_cast(from_i) * source.m_size.x() + from_j) * m_pixel_stride; - const auto to_offset = (static_cast(to_i) * m_size.x() + to_j) * m_pixel_stride; - - // Copy single pixel - std::memcpy(data() + to_offset, source.data() + from_offset, m_pixel_stride); - } - } -} - -void image::format(unsigned int channels, unsigned int bit_depth) -{ - if (bit_depth % 8 != 0) - { - throw std::runtime_error("Image bit depth must be byte-aligned"); - } - - if (m_channels != channels || m_bit_depth != bit_depth) - { - m_channels = channels; - m_bit_depth = bit_depth; - m_pixel_stride = m_channels * (m_bit_depth >> 3); - m_sample_scale = static_cast(1.0 / (std::exp2(m_bit_depth) - 1.0)); - m_data.resize(static_cast(m_size.x()) * m_size.y() * m_size.z() * m_pixel_stride); - } -} - -void image::resize(const math::uvec3& size) -{ - if (m_size.x() != size.x() || m_size.y() != size.y() || m_size.z() != size.z()) - { - m_size = size; - m_data.resize(static_cast(m_size.x()) * m_size.y() * m_size.z() * m_pixel_stride); - } -} - -math::fvec4 image::sample(std::size_t index) const -{ - math::fvec4 color{0, 0, 0, 1}; - - const auto pixel_data = data() + index * m_pixel_stride; - for (auto i = 0u; i < std::min(4u, m_channels); ++i) - { - std::uint32_t value = 0u; - std::memcpy(&value, pixel_data + (m_bit_depth >> 3) * i, m_bit_depth >> 3); - color[i] = static_cast(value) * m_sample_scale; - } - - return color; -} - -static void deserialize_tinyexr(image& image, deserialize_context& ctx) -{ - const char* error = nullptr; - - // Read data into file buffer - std::vector file_buffer(ctx.size()); - ctx.read8(reinterpret_cast(file_buffer.data()), file_buffer.size()); - - // Read EXR version - EXRVersion exr_version; - if (int status = ParseEXRVersionFromMemory(&exr_version, file_buffer.data(), file_buffer.size()); status != TINYEXR_SUCCESS) - { - throw deserialize_error(std::format("TinyEXR version parse error {}", status)); - } - - // Check if image is multipart - if (exr_version.multipart) - { - throw deserialize_error("OpenEXR multipart images not supported"); - } - - // Init and read EXR header data - EXRHeader exr_header; - InitEXRHeader(&exr_header); - if (int status = ParseEXRHeaderFromMemory(&exr_header, &exr_version, file_buffer.data(), file_buffer.size(), &error); status != TINYEXR_SUCCESS) - { - const std::string error_message(error); - FreeEXRErrorMessage(error); - throw deserialize_error(error_message); - } - - // Check if image is tiled - if (exr_header.tiled) - { - FreeEXRHeader(&exr_header); - throw deserialize_error("OpenEXR tiled images not supported"); - } - - // Read half channels as float - for (int i = 0; i < exr_header.num_channels; ++i) - { - if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) - { - exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; - } - } - - // Init and read EXR image data - EXRImage exr_image; - InitEXRImage(&exr_image); - if (int status = LoadEXRImageFromMemory(&exr_image, &exr_header, file_buffer.data(), file_buffer.size(), &error); status != TINYEXR_SUCCESS) - { - const std::string error_message(error); - FreeEXRErrorMessage(error); - FreeEXRHeader(&exr_header); - throw deserialize_error(error_message); - } - - // Free file buffer - file_buffer.clear(); - - // Format and resize image - image.format(exr_image.num_channels, sizeof(float) * 8); - image.resize({static_cast(exr_image.width), static_cast(exr_image.height), 1u}); - - // Fill image pixels - std::byte* component = image.data(); - for (int y = exr_image.height - 1; y >= 0; --y) - { - int row_offset = y * exr_image.width; - - for (int x = 0; x < exr_image.width; ++x) - { - int pixel_index = row_offset + x; - - for (int c = exr_image.num_channels - 1; c >= 0; --c) - { - std::memcpy(component, exr_image.images[c] + pixel_index * sizeof(float), sizeof(float)); - component += sizeof(float); - } - } - } - - // Free EXR image and header data - FreeEXRImage(&exr_image); - FreeEXRHeader(&exr_header); -} - -static int stb_io_read(void* user, char* data, int size) -{ - deserialize_context& ctx = *static_cast(user); - return static_cast(ctx.read8(reinterpret_cast(data), static_cast(size))); -} - -static void stb_io_skip(void* user, int n) -{ - deserialize_context& ctx = *static_cast(user); - ctx.seek(ctx.tell() + n); -} - -static int stb_io_eof(void* user) -{ - deserialize_context& ctx = *static_cast(user); - return static_cast(ctx.eof()); -} - -static void deserialize_stb_image(image& image, deserialize_context& ctx) -{ - // Set vertical flip on load in order to upload pixels correctly to OpenGL - stbi_set_flip_vertically_on_load(true); - - // Setup IO callbacks - const stbi_io_callbacks io_callbacks - { - &stb_io_read, - &stb_io_skip, - &stb_io_eof - }; - - int width = 0; - int height = 0; - int channels = 0; - - if (stbi_is_16_bit_from_callbacks(&io_callbacks, &ctx)) - { - // Load 16-bit image - ctx.seek(0); - stbi_us* pixels = stbi_load_16_from_callbacks(&io_callbacks, &ctx, &width, &height, &channels, 0); - if (!pixels) - { - throw deserialize_error(stbi_failure_reason()); - } - - // Format image and resize image, then copy pixel data - image.format(static_cast(channels), 16u); - image.resize({static_cast(width), static_cast(height), 1u}); - std::memcpy(image.data(), pixels, image.size_bytes()); - - // Free loaded image data - stbi_image_free(pixels); - } - else - { - // Load 8-bit image - ctx.seek(0); - stbi_uc* pixels = stbi_load_from_callbacks(&io_callbacks, &ctx, &width, &height, &channels, 0); - if (!pixels) - { - throw deserialize_error(stbi_failure_reason()); - } - - // Format image and resize image, then copy pixel data - image.format(static_cast(channels), 8u); - image.resize({static_cast(width), static_cast(height), 1u}); - std::memcpy(image.data(), pixels, image.size_bytes()); - - // Free loaded image data - stbi_image_free(pixels); - } -} - -/** - * Deserializes an image. - * - * @param[out] image Image to deserialize. - * @param[in,out] ctx Deserialize context. - * - * @throw deserialize_error Read error. - */ -template <> -void deserializer::deserialize(image& image, deserialize_context& ctx) -{ - // Select loader according to file extension - if (ctx.path().extension() == ".exr") - { - // Deserialize EXR images with TinyEXR - deserialize_tinyexr(image, ctx); - } - else - { - // Deserialize other image formats with stb_image - deserialize_stb_image(image, ctx); - } -} - -template <> -std::unique_ptr resource_loader::load(::resource_manager& resource_manager, deserialize_context& ctx) -{ - auto resource = std::make_unique(); - - deserializer().deserialize(*resource, ctx); - - return resource; -} diff --git a/src/engine/utility/image.hpp b/src/engine/utility/image.hpp deleted file mode 100644 index e16f372..0000000 --- a/src/engine/utility/image.hpp +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_UTILITY_IMAGE_HPP -#define ANTKEEPER_UTILITY_IMAGE_HPP - -#include -#include -#include - -/** - * Pixel data buffer. - */ -class image -{ -public: - /** - * Checks whether another image has the same number of channels and pixel size as this image. - * - * @param other Image for with which to compare compatibility. - * @return `true` if the image formats are compatible, `false` otherwise. - */ - [[nodiscard]] bool compatible(const image& other) const noexcept; - - /** - * Copies pixel data from another image with a compatible format into this image. - * - * @param source Source image from which to copy pixel data. - * @param dimensions Dimensions of the subimage to copy. - * @param from Coordinates of the first pixel to copy from the source subimage. - * @param to Coordinates of the first pixel in the destination subimage. - * - * @except std::runtime_error Cannot copy image with mismatched format. - * - * @see image::compatible(const image&) const - */ - void copy - ( - const image& source, - const math::uvec2& dimensions, - const math::uvec2& from = {}, - const math::uvec2& to = {} - ); - - /** - * Changes the format of the image. - * - * @param channels Number of channels in the image. - * @param bit_depth Number of bits per channel. - * - * @warning Pre-existing pixel data will be invalidated. - * @warning Bit depth must be byte-aligned. - * - * @except std::runtime_error Image bit depth must be byte-aligned. - */ - void format(unsigned int channels, unsigned int bit_depth = 8u); - - /** - * Resizes the image. - * - * @param size New dimensions of the image, in pixels. - * - * @warning Pre-existing pixel data will be invalidated. - */ - /// @{ - inline void resize(unsigned int size) - { - resize(math::uvec3{size, 1u, 1u}); - } - - inline void resize(const math::uvec2& size) - { - resize(math::uvec3{size.x(), size.y(), 1u}); - } - - void resize(const math::uvec3& size); - /// @} - - /// @name Pixel access - /// @{ - - /// Returns a pointer to the pixel data. - /// @{ - [[nodiscard]] inline constexpr const std::byte* data() const noexcept - { - return m_data.data(); - } - [[nodiscard]] inline constexpr std::byte* data() noexcept - { - return m_data.data(); - } - /// @} - - /** - * Returns the value of a pixel. - * - * @tparam T Pixel data type. - * - * @param position Coordinates of a pixel. - * - * @return Pixel value. - */ - /// @{ - template - [[nodiscard]] T get(unsigned int position) const - { - T value; - std::memcpy(&value, data() + static_cast(position) * m_pixel_stride, sizeof(T)); - return value; - } - - template - [[nodiscard]] T get(const math::uvec2& position) const - { - const auto index = static_cast(position.y()) * m_size.x() + position.x(); - T value; - std::memcpy(&value, data() + index * m_pixel_stride, sizeof(T)); - return value; - } - - template - [[nodiscard]] T get(const math::uvec3& position) const - { - const auto index = (static_cast(position.z()) * m_size.y() + position.y()) * m_size.x() + position.x(); - T value; - std::memcpy(&value, data() + index * m_pixel_stride, sizeof(T)); - return value; - } - /// @} - - /** - * Sets the value of a pixel. - * - * @tparam T Pixel data type. - * - * @param position Coordinates of a pixel. - * @param value Pixel value. - */ - /// @{ - template - [[nodiscard]] void set(unsigned int position, const T& value) - { - std::memcpy(data() + static_cast(position) * m_pixel_stride, &value, sizeof(T)); - } - - template - [[nodiscard]] void set(const math::uvec2& position, const T& value) - { - const auto index = static_cast(position.y()) * m_size.x() + position.x(); - std::memcpy(data() + index * m_pixel_stride, &value, sizeof(T)); - } - - template - [[nodiscard]] void set(const math::uvec3& position, const T& value) - { - const auto index = (static_cast(position.z()) * m_size.y() + position.y()) * m_size.x() + position.x(); - std::memcpy(data() + index * m_pixel_stride, &value, sizeof(T)); - } - /// @} - - /** - * Samples a texel. - * - * @param position Coordinates of a pixel. - * - * @return RGBA texel, on `[0, 1]`. - */ - /// @{ - [[nodiscard]] inline math::fvec4 sample(unsigned int position) const - { - return sample(static_cast(position)); - } - - [[nodiscard]] inline math::fvec4 sample(const math::uvec2& position) const - { - return sample(static_cast(position.y()) * m_size.x() + position.x()); - } - - [[nodiscard]] inline math::fvec4 sample(const math::uvec3& position) const - { - return sample((static_cast(position.z()) * m_size.y() + position.y()) * m_size.x() + position.x()); - } - /// @} - - /// @} - - /// Returns the dimensions of the the image, in pixels. - [[nodiscard]] inline constexpr const math::uvec3& size() const noexcept - { - return m_size; - } - - /// Returns the number of channels in the image. - [[nodiscard]] inline constexpr unsigned int channels() const noexcept - { - return m_channels; - } - - /// Returns the number of bits per channel in the image. - [[nodiscard]] inline constexpr unsigned int bit_depth() const noexcept - { - return m_bit_depth; - } - - /// Returns the size of the image, in bytes. - [[nodiscard]] inline constexpr std::size_t size_bytes() const noexcept - { - return m_data.size(); - } - -private: - [[nodiscard]] math::fvec4 sample(std::size_t index) const; - - math::uvec3 m_size{}; - unsigned int m_channels{}; - unsigned int m_bit_depth{}; - unsigned int m_pixel_stride{}; - float m_sample_scale{}; - std::vector m_data; -}; - -#endif // ANTKEEPER_UTILITY_IMAGE_HPP diff --git a/src/game/ant/ant-morphogenesis.cpp b/src/game/ant/ant-morphogenesis.cpp index 881d6cd..ce21835 100644 --- a/src/game/ant/ant-morphogenesis.cpp +++ b/src/game/ant/ant-morphogenesis.cpp @@ -21,7 +21,7 @@ #include "game/ant/ant-bone-set.hpp" #include "game/ant/ant-skeleton.hpp" #include -#include +#include #include #include #include @@ -46,10 +46,11 @@ void reskin_vertices ( std::byte* vertex_data, std::size_t vertex_count, - const gl::vertex_attribute& position_attribute, - const gl::vertex_attribute& normal_attribute, - const gl::vertex_attribute& tangent_attribute, - const gl::vertex_attribute& bone_index_attribute, + const gl::vertex_input_attribute& position_attribute, + const gl::vertex_input_attribute& normal_attribute, + const gl::vertex_input_attribute& tangent_attribute, + const gl::vertex_input_attribute& bone_index_attribute, + std::size_t vertex_stride, const std::unordered_map*>>& reskin_map ) { @@ -61,7 +62,7 @@ void reskin_vertices for (std::size_t i = 0; i < vertex_count; ++i) { // Get bone index of current vertex - std::uint16_t& bone_index = reinterpret_cast(*(bone_index_data + bone_index_attribute.stride * i)); + std::uint16_t& bone_index = reinterpret_cast(*(bone_index_data + vertex_stride * i)); // Ignore vertices with unmapped bone indices auto entry = reskin_map.find(static_cast(bone_index)); @@ -76,13 +77,13 @@ void reskin_vertices bone_index = static_cast(new_bone_index); // Get vertex attributes - float* px = reinterpret_cast(position_data + position_attribute.stride * i); + float* px = reinterpret_cast(position_data + vertex_stride * i); float* py = px + 1; float* pz = py + 1; - float* nx = reinterpret_cast(normal_data + normal_attribute.stride * i); + float* nx = reinterpret_cast(normal_data + vertex_stride * i); float* ny = nx + 1; float* nz = ny + 1; - float* tx = reinterpret_cast(tangent_data + tangent_attribute.stride * i); + float* tx = reinterpret_cast(tangent_data + vertex_stride * i); float* ty = tx + 1; float* tz = ty + 1; @@ -114,7 +115,8 @@ void reskin_vertices void tag_vertices ( std::span vertex_data, - const gl::vertex_attribute& bone_index_attribute, + const gl::vertex_input_attribute& bone_index_attribute, + std::size_t vertex_stride, std::uint16_t vertex_tag ) { @@ -123,7 +125,7 @@ void tag_vertices for (std::size_t i = 0; i < vertex_data.size(); ++i) { // Get bone indices of current vertex - std::uint16_t* bone_indices = reinterpret_cast(bone_index_data + bone_index_attribute.stride * i); + std::uint16_t* bone_indices = reinterpret_cast(bone_index_data + vertex_stride * i); // Tag fourth bone index bone_indices[3] = vertex_tag; @@ -141,7 +143,8 @@ void tag_vertices float calculate_uv_area ( std::span vertex_data, - const gl::vertex_attribute& uv_attribute + const gl::vertex_input_attribute& uv_attribute, + std::size_t vertex_stride ) { std::byte* uv_data = vertex_data.data() + uv_attribute.offset; @@ -150,9 +153,9 @@ float calculate_uv_area for (std::size_t i = 0; i + 2 < vertex_data.size(); i += 3) { - const float* uv_data_a = reinterpret_cast(uv_data + uv_attribute.stride * i); - const float* uv_data_b = reinterpret_cast(uv_data + uv_attribute.stride * (i + 1)); - const float* uv_data_c = reinterpret_cast(uv_data + uv_attribute.stride * (i + 2)); + const float* uv_data_a = reinterpret_cast(uv_data + vertex_stride * i); + const float* uv_data_b = reinterpret_cast(uv_data + vertex_stride * (i + 1)); + const float* uv_data_c = reinterpret_cast(uv_data + vertex_stride * (i + 2)); const math::fvec3 uva = {uv_data_a[0], uv_data_a[1], 0.0f}; const math::fvec3 uvb = {uv_data_b[0], uv_data_b[1], 0.0f}; @@ -180,7 +183,8 @@ float calculate_uv_area ( const std::byte* vertex_data, std::size_t vertex_count, - const gl::vertex_attribute& position_attribute + const gl::vertex_input_attribute& position_attribute, + std::size_t vertex_stride ) { const std::byte* position_data = vertex_data + position_attribute.offset; @@ -188,7 +192,7 @@ float calculate_uv_area geom::box bounds = {math::fvec3::infinity(), -math::fvec3::infinity()}; for (std::size_t i = 0; i < vertex_count; ++i) { - const float* px = reinterpret_cast(position_data + position_attribute.stride * i); + const float* px = reinterpret_cast(position_data + vertex_stride * i); const float* py = px + 1; const float* pz = py + 1; @@ -394,42 +398,44 @@ std::unique_ptr ant_morphogenesis(const ant_phenome& phenome) // Allocate model std::unique_ptr model = std::make_unique(); - // Setup model VAO - gl::vertex_array& model_vao = *model->get_vertex_array(); - for (auto [location, attribute]: mesosoma_model->get_vertex_array()->attributes()) - { - attribute.buffer = model->get_vertex_buffer().get(); - model_vao.bind(location, attribute); - } + // Construct model VAO (clone mesosoma model VAO) + auto& model_vao = model->get_vertex_array(); + model_vao = std::make_unique(mesosoma_model->get_vertex_array()->attributes()); // Get vertex attributes - const gl::vertex_attribute* position_attribute = nullptr; - const gl::vertex_attribute* uv_attribute = nullptr; - const gl::vertex_attribute* normal_attribute = nullptr; - const gl::vertex_attribute* tangent_attribute = nullptr; - const gl::vertex_attribute* bone_index_attribute = nullptr; - const auto& vertex_attribute_map = model_vao.attributes(); - if (auto it = vertex_attribute_map.find(render::vertex_attribute::position); it != vertex_attribute_map.end()) - { - position_attribute = &it->second; - } - if (auto it = vertex_attribute_map.find(render::vertex_attribute::uv); it != vertex_attribute_map.end()) - { - uv_attribute = &it->second; - } - if (auto it = vertex_attribute_map.find(render::vertex_attribute::normal); it != vertex_attribute_map.end()) - { - normal_attribute = &it->second; - } - if (auto it = vertex_attribute_map.find(render::vertex_attribute::tangent); it != vertex_attribute_map.end()) - { - tangent_attribute = &it->second; - } - if (auto it = vertex_attribute_map.find(render::vertex_attribute::bone_index); it != vertex_attribute_map.end()) - { - bone_index_attribute = &it->second; + const gl::vertex_input_attribute* position_attribute = nullptr; + const gl::vertex_input_attribute* uv_attribute = nullptr; + const gl::vertex_input_attribute* normal_attribute = nullptr; + const gl::vertex_input_attribute* tangent_attribute = nullptr; + const gl::vertex_input_attribute* bone_index_attribute = nullptr; + for (const auto& attribute: model_vao->attributes()) + { + switch (attribute.location) + { + case render::vertex_attribute_location::position: + position_attribute = &attribute; + break; + case render::vertex_attribute_location::uv: + uv_attribute = &attribute; + break; + case render::vertex_attribute_location::normal: + normal_attribute = &attribute; + break; + case render::vertex_attribute_location::tangent: + tangent_attribute = &attribute; + break; + case render::vertex_attribute_location::bone_index: + bone_index_attribute = &attribute; + break; + default: + break; + } } + // Init model vertex binding + model->set_vertex_offset(0); + model->set_vertex_stride(mesosoma_model->get_vertex_stride()); + // Generate ant skeleton ::skeleton& skeleton = model->get_skeleton(); ant_bone_set bones; @@ -437,17 +443,17 @@ std::unique_ptr ant_morphogenesis(const ant_phenome& phenome) const auto& rest_pose = skeleton.get_rest_pose(); // Get number of vertices for each body part - const std::uint32_t mesosoma_vertex_count = (mesosoma_model->get_groups()).front().index_count; - const std::uint32_t legs_vertex_count = (legs_model->get_groups()).front().index_count; - const std::uint32_t head_vertex_count = (head_model->get_groups()).front().index_count; - const std::uint32_t mandibles_vertex_count = (mandibles_model->get_groups()).front().index_count; - const std::uint32_t antennae_vertex_count = (antennae_model->get_groups()).front().index_count; - const std::uint32_t waist_vertex_count = (waist_model->get_groups()).front().index_count; - const std::uint32_t gaster_vertex_count = (gaster_model->get_groups()).front().index_count; - const std::uint32_t sting_vertex_count = (phenome.sting->present) ? (sting_model->get_groups()).front().index_count : 0; - const std::uint32_t eyes_vertex_count = (phenome.eyes->present) ? (eyes_model->get_groups()).front().index_count : 0; - const std::uint32_t ocelli_vertex_count = (phenome.ocelli->lateral_ocelli_present || phenome.ocelli->median_ocellus_present) ? (ocelli_model->get_groups()).front().index_count : 0; - const std::uint32_t wings_vertex_count = (phenome.wings->present) ? wings_model->get_groups().front().index_count : 0; + const std::uint32_t mesosoma_vertex_count = (mesosoma_model->get_groups()).front().vertex_count; + const std::uint32_t legs_vertex_count = (legs_model->get_groups()).front().vertex_count; + const std::uint32_t head_vertex_count = (head_model->get_groups()).front().vertex_count; + const std::uint32_t mandibles_vertex_count = (mandibles_model->get_groups()).front().vertex_count; + const std::uint32_t antennae_vertex_count = (antennae_model->get_groups()).front().vertex_count; + const std::uint32_t waist_vertex_count = (waist_model->get_groups()).front().vertex_count; + const std::uint32_t gaster_vertex_count = (gaster_model->get_groups()).front().vertex_count; + const std::uint32_t sting_vertex_count = (phenome.sting->present) ? (sting_model->get_groups()).front().vertex_count : 0; + const std::uint32_t eyes_vertex_count = (phenome.eyes->present) ? (eyes_model->get_groups()).front().vertex_count : 0; + const std::uint32_t ocelli_vertex_count = (phenome.ocelli->lateral_ocelli_present || phenome.ocelli->median_ocellus_present) ? (ocelli_model->get_groups()).front().vertex_count : 0; + const std::uint32_t wings_vertex_count = (phenome.wings->present) ? wings_model->get_groups().front().vertex_count : 0; // Get body part skeletons const ::skeleton& mesosoma_skeleton = phenome.mesosoma->model->get_skeleton(); @@ -651,42 +657,42 @@ std::unique_ptr ant_morphogenesis(const ant_phenome& phenome) } // Reskin legs vertices - reskin_vertices(vertex_buffer_data.data() + legs_vbo_offset, legs_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, legs_reskin_map); + reskin_vertices(vertex_buffer_data.data() + legs_vbo_offset, legs_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, model->get_vertex_stride(), legs_reskin_map); // Reskin head vertices - reskin_vertices(vertex_buffer_data.data() + head_vbo_offset, head_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, head_reskin_map); + reskin_vertices(vertex_buffer_data.data() + head_vbo_offset, head_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, model->get_vertex_stride(), head_reskin_map); // Reskin mandibles vertices - reskin_vertices(vertex_buffer_data.data() + mandibles_vbo_offset, mandibles_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, mandibles_reskin_map); + reskin_vertices(vertex_buffer_data.data() + mandibles_vbo_offset, mandibles_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, model->get_vertex_stride(), mandibles_reskin_map); // Reskin antennae vertices - reskin_vertices(vertex_buffer_data.data() + antennae_vbo_offset, antennae_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, antennae_reskin_map); + reskin_vertices(vertex_buffer_data.data() + antennae_vbo_offset, antennae_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, model->get_vertex_stride(), antennae_reskin_map); // Reskin waist vertices if (phenome.waist->present) { - reskin_vertices(vertex_buffer_data.data() + waist_vbo_offset, waist_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, waist_reskin_map); + reskin_vertices(vertex_buffer_data.data() + waist_vbo_offset, waist_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, model->get_vertex_stride(), waist_reskin_map); } // Reskin gaster vertices - reskin_vertices(vertex_buffer_data.data() + gaster_vbo_offset, gaster_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, gaster_reskin_map); + reskin_vertices(vertex_buffer_data.data() + gaster_vbo_offset, gaster_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, model->get_vertex_stride(), gaster_reskin_map); // Reskin sting vertices if (phenome.sting->present) { - reskin_vertices(vertex_buffer_data.data() + sting_vbo_offset, sting_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, sting_reskin_map); + reskin_vertices(vertex_buffer_data.data() + sting_vbo_offset, sting_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, model->get_vertex_stride(), sting_reskin_map); } // Reskin eyes vertices if (phenome.eyes->present) { - reskin_vertices(vertex_buffer_data.data() + eyes_vbo_offset, eyes_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, eyes_reskin_map); + reskin_vertices(vertex_buffer_data.data() + eyes_vbo_offset, eyes_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, model->get_vertex_stride(), eyes_reskin_map); } // Reskin ocelli vertices if (phenome.ocelli->lateral_ocelli_present || phenome.ocelli->median_ocellus_present) { - reskin_vertices(vertex_buffer_data.data() + ocelli_vbo_offset, ocelli_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, ocelli_reskin_map); + reskin_vertices(vertex_buffer_data.data() + ocelli_vbo_offset, ocelli_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, model->get_vertex_stride(), ocelli_reskin_map); } // Reskin wings vertices @@ -703,17 +709,18 @@ std::unique_ptr ant_morphogenesis(const ant_phenome& phenome) wings_reskin_map.emplace(*wings_skeleton->get_bone_index("hindwing_l"), std::tuple(*bones.hindwing_l, &hindwing_l_to_body)); wings_reskin_map.emplace(*wings_skeleton->get_bone_index("hindwing_r"), std::tuple(*bones.hindwing_r, &hindwing_r_to_body)); - reskin_vertices(vertex_buffer_data.data() + wings_vbo_offset, wings_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, wings_reskin_map); + reskin_vertices(vertex_buffer_data.data() + wings_vbo_offset, wings_vertex_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, model->get_vertex_stride(), wings_reskin_map); } // Tag eye vertices if (phenome.eyes->present) { - tag_vertices({vertex_buffer_data.data() + eyes_vbo_offset, vertex_buffer_data.data() + eyes_vbo_offset + eyes_vertex_count}, *bone_index_attribute, 1); + tag_vertices({vertex_buffer_data.data() + eyes_vbo_offset, vertex_buffer_data.data() + eyes_vbo_offset + eyes_vertex_count}, *bone_index_attribute, model->get_vertex_stride(), 1); } - // Upload vertex buffer data to model VBO - model->get_vertex_buffer()->repurpose(gl::buffer_usage::static_draw, vertex_buffer_size, vertex_buffer_data); + // Construct model VBO + auto& model_vbo = model->get_vertex_buffer(); + model_vbo = std::make_unique(gl::buffer_usage::static_draw, vertex_buffer_data); // Allocate model groups model->get_groups().resize(phenome.wings->present ? 2 : 1); @@ -722,7 +729,7 @@ std::unique_ptr ant_morphogenesis(const ant_phenome& phenome) float eye_uv_area = 0.0f; if (phenome.eyes->present) { - eye_uv_area = calculate_uv_area({vertex_buffer_data.data() + eyes_vbo_offset, vertex_buffer_data.data() + eyes_vbo_offset + eyes_vertex_count / 2}, *uv_attribute); + eye_uv_area = calculate_uv_area({vertex_buffer_data.data() + eyes_vbo_offset, vertex_buffer_data.data() + eyes_vbo_offset + eyes_vertex_count / 2}, *uv_attribute, model->get_vertex_stride()); } // Generate exoskeleton material @@ -732,9 +739,9 @@ std::unique_ptr ant_morphogenesis(const ant_phenome& phenome) render::model_group& model_group = model->get_groups()[0]; model_group.id = "exoskeleton"; model_group.material = exoskeleton_material; - model_group.drawing_mode = gl::drawing_mode::triangles; - model_group.start_index = 0; - model_group.index_count = mesosoma_vertex_count + + model_group.primitive_topology = gl::primitive_topology::triangle_list; + model_group.first_vertex = 0; + model_group.vertex_count = mesosoma_vertex_count + legs_vertex_count + head_vertex_count + mandibles_vertex_count + @@ -751,13 +758,13 @@ std::unique_ptr ant_morphogenesis(const ant_phenome& phenome) render::model_group& wings_group = model->get_groups()[1]; wings_group.id = "wings"; wings_group.material = wings_model->get_groups().front().material; - wings_group.drawing_mode = gl::drawing_mode::triangles; - wings_group.start_index = model_group.index_count; - wings_group.index_count = wings_vertex_count; + wings_group.primitive_topology = gl::primitive_topology::triangle_list; + wings_group.first_vertex = model_group.vertex_count; + wings_group.vertex_count = wings_vertex_count; } // Calculate model bounding box - model->get_bounds() = calculate_bounds(vertex_buffer_data.data(), model_group.index_count, *position_attribute); + model->get_bounds() = calculate_bounds(vertex_buffer_data.data(), model_group.vertex_count, *position_attribute, model->get_vertex_stride()); return model; } diff --git a/src/game/ant/genes/ant-gene-loader.hpp b/src/game/ant/genes/ant-gene-loader.hpp index 2f66138..5c16757 100644 --- a/src/game/ant/genes/ant-gene-loader.hpp +++ b/src/game/ant/genes/ant-gene-loader.hpp @@ -42,7 +42,7 @@ void load_ant_gene(ant_gene& gene, resource_manager& resource_manager, deseri std::uint32_t format_identifier{0}; ctx.read32(reinterpret_cast(&format_identifier), 1); - // Validate file format identifier + // Validate file format identifier (U+1F9EC = DNA double helix) if (format_identifier != 0xaca79ff0) { throw deserialize_error("Invalid ant gene file"); diff --git a/src/game/ant/genes/ant-sculpturing-gene.cpp b/src/game/ant/genes/ant-sculpturing-gene.cpp index 36c5412..e92f294 100644 --- a/src/game/ant/genes/ant-sculpturing-gene.cpp +++ b/src/game/ant/genes/ant-sculpturing-gene.cpp @@ -21,7 +21,7 @@ #include "game/ant/genes/ant-gene-loader.hpp" #include #include -#include +#include namespace { diff --git a/src/game/ant/genes/ant-sculpturing-gene.hpp b/src/game/ant/genes/ant-sculpturing-gene.hpp index 9a71dd4..4439ab3 100644 --- a/src/game/ant/genes/ant-sculpturing-gene.hpp +++ b/src/game/ant/genes/ant-sculpturing-gene.hpp @@ -21,7 +21,7 @@ #define ANTKEEPER_GAME_ANT_SCULPTURING_GENE_HPP #include "game/ant/genes/ant-gene.hpp" -#include +#include #include /** diff --git a/src/game/fonts.cpp b/src/game/fonts.cpp index af98be8..7da541c 100644 --- a/src/game/fonts.cpp +++ b/src/game/fonts.cpp @@ -20,8 +20,6 @@ #include "game/fonts.hpp" #include #include -#include -#include #include #include #include @@ -36,10 +34,6 @@ static void build_bitmap_font(const type::typeface& typeface, float size, const font.set_font_metrics(metrics); } - // Format font bitmap - image& font_bitmap = font.get_bitmap(); - font_bitmap.format(1, sizeof(std::byte) * 8); - // For each UTF-32 character code in the character set for (char32_t code: charset) { @@ -50,9 +44,9 @@ static void build_bitmap_font(const type::typeface& typeface, float size, const } // Add glyph to font - type::bitmap_glyph& glyph = font[code]; + type::bitmap_glyph& glyph = font.insert(code); typeface.get_metrics(size, code, glyph.metrics); - typeface.get_bitmap(size, code, glyph.bitmap); + typeface.get_bitmap(size, code, glyph.bitmap, glyph.bitmap_width, glyph.bitmap_height); } // Pack glyph bitmaps into the font bitmap @@ -60,22 +54,10 @@ static void build_bitmap_font(const type::typeface& typeface, float size, const // Create font material material.set_blend_mode(render::material_blend_mode::translucent); - if (auto var = material.get_variable("font_bitmap")) - { - // Update font texture - auto texture = std::static_pointer_cast(var)->get(); - texture->resize(static_cast(font_bitmap.size().x()), static_cast(font_bitmap.size().y()), font_bitmap.data()); - } - else - { - // Create font texture from bitmap - std::shared_ptr font_texture = std::make_shared(static_cast(font_bitmap.size().x()), static_cast(font_bitmap.size().y()), gl::pixel_type::uint_8, gl::pixel_format::r, gl::transfer_function::linear, font_bitmap.data()); - font_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); - font_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - - // Create font bitmap variable - material.set_variable("font_bitmap", std::make_shared(1, font_texture)); - } + + // Create font bitmap variable + material.set_variable("font_bitmap", std::make_shared(1, font.get_texture())); + material.set_shader_template(shader_template); } diff --git a/src/game/game.cpp b/src/game/game.cpp index 8a07ab7..74a64f3 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -62,12 +62,8 @@ #include #include #include -#include -#include -#include -#include +#include #include -#include #include #include #include @@ -79,15 +75,12 @@ #include #include #include -#include #include -#include #include -#include #include #include #include -#include +#include #include #include #include @@ -110,7 +103,7 @@ using namespace hash::literals; game::game(int argc, const char* const* argv) { // Boot process - debug::log::trace("Booting up..."); + debug::log_trace("Booting up..."); // Profile boot duration #if !defined(NDEBUG) @@ -143,17 +136,17 @@ game::game(int argc, const char* const* argv) auto boot_t1 = std::chrono::high_resolution_clock::now(); #endif - debug::log::trace("Boot up complete"); + debug::log_trace("Boot up complete"); // Print boot duration #if !defined(NDEBUG) - debug::log::info("Boot duration: {}", std::chrono::duration_cast>(boot_t1 - boot_t0)); + debug::log_info("Boot duration: {}", std::chrono::duration_cast>(boot_t1 - boot_t0)); #endif } game::~game() { - debug::log::trace("Booting down..."); + debug::log_trace("Booting down..."); // Exit all active game states while (!state_machine.empty()) @@ -187,7 +180,7 @@ game::~game() // Shut down audio shutdown_audio(); - debug::log::trace("Boot down complete"); + debug::log_trace("Boot down complete"); } void game::parse_options(int argc, const char* const* argv) @@ -198,7 +191,7 @@ void game::parse_options(int argc, const char* const* argv) return; } - debug::log::trace("Parsing command-line options..."); + debug::log_trace("Parsing command-line options..."); // Parse command-line options with cxxopts try @@ -263,11 +256,11 @@ void game::parse_options(int argc, const char* const* argv) option_windowed = true; } - debug::log::info("Parsed {} command-line options", argc); + debug::log_info("Parsed {} command-line options", argc); } catch (const std::exception& e) { - debug::log::error("An error occurred while parsing command-line options: {}", e.what()); + debug::log_error("An error occurred while parsing command-line options: {}", e.what()); } } @@ -305,10 +298,10 @@ void game::setup_resources() controls_path = shared_config_path / "controls"; // Log paths - debug::log::info("Data package path: \"{}\"", data_package_path.string()); - debug::log::info("Local config path: \"{}\"", local_config_path.string()); - debug::log::info("Shared config path: \"{}\"", shared_config_path.string()); - debug::log::info("Mods path: \"{}\"", mods_path.string()); + debug::log_info("Data package path: \"{}\"", data_package_path.string()); + debug::log_info("Local config path: \"{}\"", local_config_path.string()); + debug::log_info("Shared config path: \"{}\"", shared_config_path.string()); + debug::log_info("Mods path: \"{}\"", mods_path.string()); // Create nonexistent config directories std::vector config_paths; @@ -323,12 +316,12 @@ void game::setup_resources() { if (std::filesystem::create_directories(path)) { - debug::log::info("Created directory \"{}\"", path.string()); + debug::log_info("Created directory \"{}\"", path.string()); } } catch (const std::filesystem::filesystem_error& e) { - debug::log::error("Failed to create directory \"{}\": {}", path.string(), e.what()); + debug::log_error("Failed to create directory \"{}\": {}", path.string(), e.what()); } } @@ -341,7 +334,7 @@ void game::setup_resources() if (entry.is_directory() || (entry.is_regular_file() && entry.path().extension() == ".zip")) { mod_paths.push_back(entry.path()); - debug::log::info("Found mod \"{}\"", entry.path().filename().string()); + debug::log_info("Found mod \"{}\"", entry.path().filename().string()); } } } @@ -371,14 +364,14 @@ void game::load_settings() settings = std::make_shared>(); resource_manager->set_write_path(shared_config_path); resource_manager->save(*settings, "settings.cfg"); - debug::log::info("Settings reset"); + debug::log_info("Settings reset"); } else { settings = resource_manager->load>("settings.cfg"); if (!settings) { - debug::log::info("Settings not found"); + debug::log_info("Settings not found"); settings = std::make_shared>(); } } @@ -471,7 +464,7 @@ void game::setup_window() void game::setup_audio() { - debug::log::trace("Setting up audio..."); + debug::log_trace("Setting up audio..."); // Default audio settings master_volume = 1.0f; @@ -490,11 +483,11 @@ void game::setup_audio() read_or_write_setting(*this, "captions_size", captions_size); // Open audio device - debug::log::trace("Opening audio device..."); + debug::log_trace("Opening audio device..."); alc_device = alcOpenDevice(nullptr); if (!alc_device) { - debug::log::error("Failed to open audio device: AL error code {}", alGetError()); + debug::log_error("Failed to open audio device: AL error code {}", alGetError()); return; } else @@ -511,28 +504,28 @@ void game::setup_audio() } // Log audio device name - debug::log::info("Opened audio device \"{}\"", alc_device_name); + debug::log_info("Opened audio device \"{}\"", alc_device_name); } // Create audio context - debug::log::trace("Creating audio context..."); + debug::log_trace("Creating audio context..."); alc_context = alcCreateContext(alc_device, nullptr); if (!alc_context) { - debug::log::error("Failed to create audio context: ALC error code {}", alcGetError(alc_device)); + debug::log_error("Failed to create audio context: ALC error code {}", alcGetError(alc_device)); alcCloseDevice(alc_device); return; } else { - debug::log::trace("Created audio context"); + debug::log_trace("Created audio context"); } // Make audio context current - debug::log::trace("Making audio context current..."); + debug::log_trace("Making audio context current..."); if (alcMakeContextCurrent(alc_context) == ALC_FALSE) { - debug::log::error("Failed to make audio context current: ALC error code {}", alcGetError(alc_device)); + debug::log_error("Failed to make audio context current: ALC error code {}", alcGetError(alc_device)); if (alc_context != nullptr) { alcDestroyContext(alc_context); @@ -542,10 +535,10 @@ void game::setup_audio() } else { - debug::log::trace("Made audio context current"); + debug::log_trace("Made audio context current"); } - debug::log::trace("Set up audio"); + debug::log_trace("Set up audio"); } void game::setup_input() @@ -631,7 +624,7 @@ void game::setup_input() void game::load_strings() { - debug::log::trace("Loading strings..."); + debug::log_trace("Loading strings..."); // Default strings settings language_tag = "en"; @@ -656,7 +649,7 @@ void game::load_strings() string_map = resource_manager->load(language_slug + ".str"); // Log language info - debug::log::info("Language tag: {}", language_tag); + debug::log_info("Language tag: {}", language_tag); // Change window title const std::string window_title = get_string(*this, "window_title"); @@ -665,12 +658,12 @@ void game::load_strings() // Update window title setting (*settings)["window_title"] = window_title; - debug::log::trace("Loaded strings"); + debug::log_trace("Loaded strings"); } void game::setup_rendering() { - debug::log::trace("Setting up rendering..."); + debug::log_trace("Setting up rendering..."); // Default rendering settings render_scale = 1.0f; @@ -691,104 +684,68 @@ void game::setup_rendering() // Setup common render passes { // Construct bloom pass - bloom_pass = std::make_unique(window->get_rasterizer(), resource_manager.get()); - bloom_pass->set_source_texture(hdr_color_texture.get()); + bloom_pass = std::make_unique(&window->get_graphics_pipeline(), resource_manager.get()); + bloom_pass->set_source_texture(hdr_color_texture); bloom_pass->set_mip_chain_length(5); - //bloom_pass->set_mip_chain_length(0); bloom_pass->set_filter_radius(0.005f); - common_final_pass = std::make_unique(window->get_rasterizer(), ldr_framebuffer_a.get(), resource_manager.get()); - common_final_pass->set_color_texture(hdr_color_texture.get()); + common_final_pass = std::make_unique(&window->get_graphics_pipeline(), nullptr, resource_manager.get()); + common_final_pass->set_color_texture(hdr_color_texture); common_final_pass->set_bloom_texture(bloom_pass->get_bloom_texture()); common_final_pass->set_bloom_weight(0.04f); //common_final_pass->set_bloom_weight(0.0f); common_final_pass->set_blue_noise_texture(resource_manager->load("blue-noise.tex")); - fxaa_pass = std::make_unique(window->get_rasterizer(), &window->get_rasterizer()->get_default_framebuffer(), resource_manager.get()); - fxaa_pass->set_source_texture(ldr_color_texture_a.get()); - - resample_pass = std::make_unique(window->get_rasterizer(), &window->get_rasterizer()->get_default_framebuffer(), resource_manager.get()); - resample_pass->set_source_texture(ldr_color_texture_b.get()); + resample_pass = std::make_unique(&window->get_graphics_pipeline(), nullptr, resource_manager.get()); + resample_pass->set_source_texture(ldr_color_texture_a); resample_pass->set_enabled(false); - - // Configure anti-aliasing according to settings - graphics::select_anti_aliasing_method(*this, anti_aliasing_method); - - // Configure render scaling according to settings - graphics::change_render_resolution(*this, render_scale); } // Setup UI compositor { - ui_clear_pass = std::make_unique(window->get_rasterizer(), &window->get_rasterizer()->get_default_framebuffer()); - ui_clear_pass->set_cleared_buffers(false, true, false); - ui_clear_pass->set_clear_depth(0.0f); - - ui_material_pass = std::make_unique(window->get_rasterizer(), &window->get_rasterizer()->get_default_framebuffer(), resource_manager.get()); + ui_material_pass = std::make_unique(&window->get_graphics_pipeline(), nullptr, resource_manager.get()); ui_material_pass->set_fallback_material(fallback_material); + ui_material_pass->set_clear_mask(gl::depth_clear_bit); + ui_material_pass->set_clear_value({{0.0f, 0.0f, 0.0f, 0.0f}, 0.0f, 0}); + ui_compositor = std::make_unique(); - ui_compositor->add_pass(ui_clear_pass.get()); ui_compositor->add_pass(ui_material_pass.get()); } // Setup surface compositor { - surface_clear_pass = std::make_unique(window->get_rasterizer(), hdr_framebuffer.get()); - surface_clear_pass->set_clear_color({0.0f, 0.0f, 0.0f, 1.0f}); - surface_clear_pass->set_clear_depth(0.0f); - surface_clear_pass->set_clear_stencil(0); - surface_clear_pass->set_cleared_buffers(true, true, true); - - sky_pass = std::make_unique(window->get_rasterizer(), hdr_framebuffer.get(), resource_manager.get()); + sky_pass = std::make_unique(&window->get_graphics_pipeline(), hdr_framebuffer.get(), resource_manager.get()); + sky_pass->set_clear_mask(gl::color_clear_bit | gl::depth_clear_bit | gl::stencil_clear_bit); + sky_pass->set_clear_value({{0.0f, 0.0f, 0.0f, 0.0f}, 0.0f, 0}); // sky_pass->set_magnification(3.0f); - surface_material_pass = std::make_unique(window->get_rasterizer(), hdr_framebuffer.get(), resource_manager.get()); + surface_material_pass = std::make_unique(&window->get_graphics_pipeline(), hdr_framebuffer.get(), resource_manager.get()); surface_material_pass->set_fallback_material(fallback_material); - surface_outline_pass = std::make_unique(window->get_rasterizer(), hdr_framebuffer.get(), resource_manager.get()); - surface_outline_pass->set_outline_width(0.25f); - surface_outline_pass->set_outline_color(math::fvec4{1.0f, 1.0f, 1.0f, 1.0f}); - surface_compositor = std::make_unique(); - surface_compositor->add_pass(surface_clear_pass.get()); surface_compositor->add_pass(sky_pass.get()); surface_compositor->add_pass(surface_material_pass.get()); - //surface_compositor->add_pass(surface_outline_pass.get()); surface_compositor->add_pass(bloom_pass.get()); surface_compositor->add_pass(common_final_pass.get()); - surface_compositor->add_pass(fxaa_pass.get()); surface_compositor->add_pass(resample_pass.get()); } - // Setup underground compositor - { - underground_clear_pass = std::make_unique(window->get_rasterizer(), hdr_framebuffer.get()); - underground_clear_pass->set_cleared_buffers(true, true, false); - underground_clear_pass->set_clear_color({0, 0, 0, 1}); - underground_clear_pass->set_clear_depth(0.0f); - - underground_material_pass = std::make_unique(window->get_rasterizer(), hdr_framebuffer.get(), resource_manager.get()); - underground_material_pass->set_fallback_material(fallback_material); - - underground_compositor = std::make_unique(); - underground_compositor->add_pass(underground_clear_pass.get()); - underground_compositor->add_pass(underground_material_pass.get()); - underground_compositor->add_pass(bloom_pass.get()); - underground_compositor->add_pass(common_final_pass.get()); - underground_compositor->add_pass(fxaa_pass.get()); - underground_compositor->add_pass(resample_pass.get()); - } + // Configure anti-aliasing according to settings + graphics::select_anti_aliasing_method(*this, anti_aliasing_method); + + // Configure render scaling according to settings + graphics::change_render_resolution(*this, render_scale); // Create renderer - renderer = std::make_unique(*window->get_rasterizer(), *resource_manager); + renderer = std::make_unique(window->get_graphics_pipeline(), *resource_manager); - debug::log::trace("Set up rendering"); + debug::log_trace("Set up rendering"); } void game::setup_scenes() { - debug::log::trace("Setting up scenes..."); + debug::log_trace("Setting up scenes..."); // Ratio of meters to scene units. constexpr float scene_scale = 1.0f / 100.0f; @@ -814,13 +771,11 @@ void game::setup_scenes() // Allocate and init underground camera underground_camera = std::make_shared(); underground_camera->set_perspective(math::radians(45.0f), viewport_aspect_ratio, 0.5f); - underground_camera->set_compositor(underground_compositor.get()); - underground_camera->set_composite_index(0); // Clear active scene active_scene = nullptr; - debug::log::trace("Set up scenes"); + debug::log_trace("Set up scenes"); } void game::setup_animation() @@ -855,15 +810,15 @@ void game::setup_ui() title_font_material = std::make_shared(); // Load fonts - debug::log::trace("Loading fonts..."); + debug::log_trace("Loading fonts..."); try { ::load_fonts(*this); - debug::log::trace("Loaded fonts"); + debug::log_trace("Loaded fonts"); } - catch (...) + catch (const std::exception& e) { - debug::log::error("Failed to load fonts"); + debug::log_error("Failed to load fonts: {}", e.what()); } // Get default framebuffer @@ -1096,19 +1051,19 @@ void game::setup_systems() void game::setup_controls() { - debug::log::trace("Setting up controls..."); + debug::log_trace("Setting up controls..."); // Load SDL game controller mappings database - // debug::log::trace("Loading SDL game controller mappings..."); + // debug::log_trace("Loading SDL game controller mappings..."); // file_buffer* game_controller_db = resource_manager->load("gamecontrollerdb.txt"); // if (!game_controller_db) // { - // debug::log::error("Failed to load SDL game controller mappings"); + // debug::log_error("Failed to load SDL game controller mappings"); // } // else // { // app->add_game_controller_mappings(game_controller_db->data(), game_controller_db->size()); - // debug::log::trace("Loaded SDL game controller mappings"); + // debug::log_trace("Loaded SDL game controller mappings"); // resource_manager->unload("gamecontrollerdb.txt"); // } @@ -1171,7 +1126,7 @@ void game::setup_controls() enable_debug_controls(*this); #endif - debug::log::trace("Set up controls"); + debug::log_trace("Set up controls"); } void game::setup_debugging() @@ -1219,7 +1174,7 @@ void game::setup_timing() void game::shutdown_audio() { - debug::log::trace("Shutting down audio..."); + debug::log_trace("Shutting down audio..."); if (alc_context) { @@ -1232,7 +1187,7 @@ void game::shutdown_audio() alcCloseDevice(alc_device); } - debug::log::trace("Shut down audio"); + debug::log_trace("Shut down audio"); } void game::fixed_update(::frame_scheduler::duration_type fixed_update_time, ::frame_scheduler::duration_type fixed_update_interval) @@ -1315,7 +1270,7 @@ void game::execute() // Change to initial state state_machine.emplace(std::make_unique(*this, true)); - debug::log::trace("Entered main loop"); + debug::log_trace("Entered main loop"); frame_scheduler.refresh(); @@ -1324,7 +1279,7 @@ void game::execute() frame_scheduler.tick(); } - debug::log::trace("Exited main loop"); + debug::log_trace("Exited main loop"); // Exit all active game states while (!state_machine.empty()) diff --git a/src/game/game.hpp b/src/game/game.hpp index b0339cc..e042b8a 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -31,8 +31,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -82,14 +81,11 @@ namespace debug namespace render { class bloom_pass; - class clear_pass; class compositor; class final_pass; - class fxaa_pass; class resample_pass; class material_pass; class renderer; - class outline_pass; class simple_render_pass; class sky_pass; } @@ -307,35 +303,29 @@ public: std::queue> function_queue; // Framebuffers - std::unique_ptr hdr_color_texture; - std::unique_ptr hdr_depth_texture; - std::unique_ptr hdr_framebuffer; - std::unique_ptr ldr_color_texture_a; - std::unique_ptr ldr_framebuffer_a; - std::unique_ptr ldr_color_texture_b; - std::unique_ptr ldr_framebuffer_b; + std::shared_ptr hdr_color_texture; + std::shared_ptr hdr_depth_texture; + std::shared_ptr hdr_framebuffer; + std::shared_ptr ldr_color_texture_a; + std::shared_ptr ldr_framebuffer_a; + std::shared_ptr ldr_color_texture_b; + std::shared_ptr ldr_framebuffer_b; std::shared_ptr shadow_map_depth_texture; std::shared_ptr shadow_map_framebuffer; // Rendering - //gl::rasterizer* rasterizer; math::ivec2 render_resolution; float render_scale; int shadow_map_resolution; - std::unique_ptr ui_clear_pass; std::unique_ptr ui_material_pass; std::unique_ptr ui_compositor; std::unique_ptr bloom_pass; std::unique_ptr common_final_pass; - std::unique_ptr fxaa_pass; std::unique_ptr resample_pass; - std::unique_ptr underground_clear_pass; std::unique_ptr underground_material_pass; std::unique_ptr underground_compositor; - std::unique_ptr surface_clear_pass; std::unique_ptr sky_pass; std::unique_ptr surface_material_pass; - std::unique_ptr surface_outline_pass; std::unique_ptr surface_compositor; std::unique_ptr renderer; diff --git a/src/game/graphics.cpp b/src/game/graphics.cpp index c842c40..afc76f5 100644 --- a/src/game/graphics.cpp +++ b/src/game/graphics.cpp @@ -21,17 +21,16 @@ #include #include #include -#include -#include -#include +#include #include #include -#include #include +#include +#include #include #include #include -#include +#include #include #include @@ -39,57 +38,194 @@ namespace graphics { static void reroute_framebuffers(::game& ctx); +static void rebuild_hdr_framebuffer(::game& ctx) +{ + // Construct HDR framebuffer sampler + auto hdr_sampler = std::make_shared + ( + gl::sampler_filter::linear, + gl::sampler_filter::linear, + gl::sampler_mipmap_mode::linear, + gl::sampler_address_mode::clamp_to_edge, + gl::sampler_address_mode::clamp_to_edge + ); + + // Construct HDR framebuffer color texture + ctx.hdr_color_texture = std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::r32g32b32_sfloat, + ctx.render_resolution.x(), + ctx.render_resolution.y() + ) + ), + hdr_sampler + ); + + // Construct HDR framebuffer depth texture + ctx.hdr_depth_texture = std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::d32_sfloat_s8_uint, + ctx.render_resolution.x(), + ctx.render_resolution.y() + ) + ), + hdr_sampler + ); + + // Construct HDR framebuffer + const gl::framebuffer_attachment hdr_attachments[2] = + { + { + gl::color_attachment_bit, + ctx.hdr_color_texture->get_image_view(), + 0 + }, + { + gl::depth_stencil_attachment_bits, + ctx.hdr_depth_texture->get_image_view(), + 0 + } + }; + ctx.hdr_framebuffer = std::make_shared(hdr_attachments, ctx.render_resolution.x(), ctx.render_resolution.y()); +} + +static void rebuild_ldr_framebuffers(::game& ctx) +{ + auto ldr_sampler = std::make_shared + ( + gl::sampler_filter::linear, + gl::sampler_filter::linear, + gl::sampler_mipmap_mode::linear, + gl::sampler_address_mode::clamp_to_edge, + gl::sampler_address_mode::clamp_to_edge + ); + + // Construct LDR framebuffer A color texture + ctx.ldr_color_texture_a = std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::r8g8b8_unorm, + ctx.render_resolution.x(), + ctx.render_resolution.y() + ) + ), + ldr_sampler + ); + + // Construct LDR framebuffer A + const gl::framebuffer_attachment ldr_attachments_a[1] = + { + { + gl::color_attachment_bit, + ctx.ldr_color_texture_a->get_image_view(), + 0 + } + }; + ctx.ldr_framebuffer_a = std::make_shared(ldr_attachments_a, ctx.render_resolution.x(), ctx.render_resolution.y()); + + // Construct LDR framebuffer B color texture + ctx.ldr_color_texture_b = std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::r8g8b8_unorm, + ctx.render_resolution.x(), + ctx.render_resolution.y() + ) + ), + ldr_sampler + ); + + // Construct LDR framebuffer B + const gl::framebuffer_attachment ldr_attachments_b[1] = + { + { + gl::color_attachment_bit, + ctx.ldr_color_texture_b->get_image_view(), + 0 + } + }; + ctx.ldr_framebuffer_b = std::make_shared(ldr_attachments_b, ctx.render_resolution.x(), ctx.render_resolution.y()); +} + +void rebuild_shadow_framebuffer(::game& ctx) +{ + // Construct shadow map sampler + auto shadow_sampler = std::make_shared + ( + gl::sampler_filter::linear, + gl::sampler_filter::linear, + gl::sampler_mipmap_mode::linear, + gl::sampler_address_mode::clamp_to_border, + gl::sampler_address_mode::clamp_to_border, + gl::sampler_address_mode::clamp_to_border, + 0.0f, + 0.0f, + true, + gl::compare_op::greater, + -1000.0f, + 1000.0f, + std::array{0.0f, 0.0f, 0.0f, 0.0f} + ); + + // Construct shadow map framebuffer depth texture + ctx.shadow_map_depth_texture = std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::d32_sfloat, + ctx.shadow_map_resolution, + ctx.shadow_map_resolution + ) + ), + shadow_sampler + ); + + // Construct shadow map framebuffer + const gl::framebuffer_attachment shadow_map_attachments[1] = + { + { + gl::depth_attachment_bit, + ctx.shadow_map_depth_texture->get_image_view(), + 0 + } + }; + ctx.shadow_map_framebuffer = std::make_shared(shadow_map_attachments, ctx.shadow_map_resolution, ctx.shadow_map_resolution); +} + void create_framebuffers(::game& ctx) { - debug::log::trace("Creating framebuffers..."); + debug::log_trace("Creating framebuffers..."); // Calculate render resolution const math::ivec2& viewport_size = ctx.window->get_viewport_size(); ctx.render_resolution = {static_cast(viewport_size.x() * ctx.render_scale + 0.5f), static_cast(viewport_size.y() * ctx.render_scale + 0.5f)}; - // Create HDR framebuffer (32F color, 32F depth) - ctx.hdr_color_texture = std::make_unique(ctx.render_resolution.x(), ctx.render_resolution.y(), gl::pixel_type::float_32, gl::pixel_format::rgb); - ctx.hdr_color_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); - ctx.hdr_color_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - ctx.hdr_color_texture->set_max_anisotropy(0.0f); - ctx.hdr_depth_texture = std::make_unique(ctx.render_resolution.x(), ctx.render_resolution.y(), gl::pixel_type::float_32, gl::pixel_format::ds); - ctx.hdr_depth_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); - ctx.hdr_depth_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - ctx.hdr_depth_texture->set_max_anisotropy(0.0f); - ctx.hdr_framebuffer = std::make_unique(ctx.render_resolution.x(), ctx.render_resolution.y()); - ctx.hdr_framebuffer->attach(gl::framebuffer_attachment_type::color, ctx.hdr_color_texture.get()); - ctx.hdr_framebuffer->attach(gl::framebuffer_attachment_type::depth, ctx.hdr_depth_texture.get()); - ctx.hdr_framebuffer->attach(gl::framebuffer_attachment_type::stencil, ctx.hdr_depth_texture.get()); - - // Create LDR framebuffers (8-bit color, no depth) - ctx.ldr_color_texture_a = std::make_unique(ctx.render_resolution.x(), ctx.render_resolution.y(), gl::pixel_type::uint_8, gl::pixel_format::rgb); - ctx.ldr_color_texture_a->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); - ctx.ldr_color_texture_a->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - ctx.ldr_color_texture_a->set_max_anisotropy(0.0f); - ctx.ldr_framebuffer_a = std::make_unique(ctx.render_resolution.x(), ctx.render_resolution.y()); - ctx.ldr_framebuffer_a->attach(gl::framebuffer_attachment_type::color, ctx.ldr_color_texture_a.get()); - - ctx.ldr_color_texture_b = std::make_unique(ctx.render_resolution.x(), ctx.render_resolution.y(), gl::pixel_type::uint_8, gl::pixel_format::rgb); - ctx.ldr_color_texture_b->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); - ctx.ldr_color_texture_b->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - ctx.ldr_color_texture_b->set_max_anisotropy(0.0f); - ctx.ldr_framebuffer_b = std::make_unique(ctx.render_resolution.x(), ctx.render_resolution.y()); - ctx.ldr_framebuffer_b->attach(gl::framebuffer_attachment_type::color, ctx.ldr_color_texture_b.get()); - - // Create shadow map framebuffer - ctx.shadow_map_depth_texture = std::make_shared(ctx.shadow_map_resolution, ctx.shadow_map_resolution, gl::pixel_type::float_32, gl::pixel_format::d); - ctx.shadow_map_depth_texture->set_wrapping(gl::texture_wrapping::clip, gl::texture_wrapping::clip); - ctx.shadow_map_depth_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - ctx.shadow_map_depth_texture->set_max_anisotropy(0.0f); - ctx.shadow_map_framebuffer = std::make_shared(ctx.shadow_map_resolution, ctx.shadow_map_resolution); - ctx.shadow_map_framebuffer->attach(gl::framebuffer_attachment_type::depth, ctx.shadow_map_depth_texture.get()); - - debug::log::trace("Created framebuffers"); + rebuild_hdr_framebuffer(ctx); + rebuild_ldr_framebuffers(ctx); + rebuild_shadow_framebuffer(ctx); + + debug::log_trace("Created framebuffers"); } void destroy_framebuffers(::game& ctx) { - debug::log::trace("Destroying framebuffers..."); + debug::log_trace("Destroying framebuffers..."); // Delete HDR framebuffer and its attachments ctx.hdr_framebuffer.reset(); @@ -107,48 +243,44 @@ void destroy_framebuffers(::game& ctx) ctx.shadow_map_framebuffer.reset(); ctx.shadow_map_depth_texture.reset(); - debug::log::trace("Destroyed framebuffers"); + debug::log_trace("Destroyed framebuffers"); } void change_render_resolution(::game& ctx, float scale) { - debug::log::trace("Changing render resolution to {}...", scale); - - // Update render resolution scale - ctx.render_scale = scale; - // Recalculate render resolution const math::ivec2& viewport_size = ctx.window->get_viewport_size(); - ctx.render_resolution = {static_cast(viewport_size.x() * ctx.render_scale + 0.5f), static_cast(viewport_size.y() * ctx.render_scale + 0.5f)}; + const auto render_resolution = math::ivec2{static_cast(viewport_size.x() * scale + 0.5f), static_cast(viewport_size.y() * scale + 0.5f)}; + + if (ctx.render_resolution == render_resolution) + { + return; + } - // Resize HDR framebuffer and attachments - ctx.hdr_framebuffer->resize({ctx.render_resolution.x(), ctx.render_resolution.y()}); - ctx.hdr_color_texture->resize(ctx.render_resolution.x(), ctx.render_resolution.y(), nullptr); - ctx.hdr_depth_texture->resize(ctx.render_resolution.x(), ctx.render_resolution.y(), nullptr); + debug::log_trace("Changing render resolution to {}...", scale); - // Resize LDR framebuffers and attachments - ctx.ldr_framebuffer_a->resize({ctx.render_resolution.x(), ctx.render_resolution.y()}); - ctx.ldr_color_texture_a->resize(ctx.render_resolution.x(), ctx.render_resolution.y(), nullptr); - ctx.ldr_framebuffer_b->resize({ctx.render_resolution.x(), ctx.render_resolution.y()}); - ctx.ldr_color_texture_b->resize(ctx.render_resolution.x(), ctx.render_resolution.y(), nullptr); + // Update render resolution scale + ctx.render_scale = scale; + ctx.render_resolution = render_resolution; - // Resize bloom render pass - ctx.bloom_pass->resize(); + rebuild_hdr_framebuffer(ctx); + rebuild_ldr_framebuffers(ctx); // Enable or disable resample pass if (viewport_size.x() != ctx.render_resolution.x() || viewport_size.y() != ctx.render_resolution.y()) { ctx.resample_pass->set_enabled(true); - debug::log::debug("Resample pass enabled"); + debug::log_debug("Resample pass enabled"); } else { ctx.resample_pass->set_enabled(false); - debug::log::debug("Resample pass disabled"); + debug::log_debug("Resample pass disabled"); } + reroute_framebuffers(ctx); - debug::log::trace("Changed render resolution to {}", scale); + debug::log_trace("Changed render resolution to {}", scale); } void save_screenshot(::game& ctx) @@ -160,29 +292,27 @@ void save_screenshot(::game& ctx) // Determine path to screenshot file std::filesystem::path screenshot_filepath = ctx.screenshots_path / screenshot_filename; std::string screenshot_filepath_string = screenshot_filepath.string(); - debug::log::debug("Saving screenshot to \"{}\"...", screenshot_filepath_string); + debug::log_debug("Saving screenshot to \"{}\"...", screenshot_filepath_string); // Get viewport dimensions - const math::ivec2& viewport_size = ctx.window->get_viewport_size(); + const auto& viewport_size = ctx.window->get_viewport_size(); - // Allocate screenshot image - std::shared_ptr frame = std::make_shared(); - frame->format(3, 8); - frame->resize({static_cast(viewport_size.x()), static_cast(viewport_size.y()), 1}); + // Allocate screenshot pixel data buffer + std::unique_ptr frame = std::make_unique(viewport_size.x() * viewport_size.y() * 3); - // Read pixel data from backbuffer into image + // Read pixel data from backbuffer into pixel data buffer glReadBuffer(GL_BACK); - glReadPixels(0, 0, viewport_size.x(), viewport_size.y(), GL_RGB, GL_UNSIGNED_BYTE, frame->data()); + glReadPixels(0, 0, viewport_size.x(), viewport_size.y(), GL_RGB, GL_UNSIGNED_BYTE, frame.get()); // Write screenshot file in separate thread std::thread ( - [frame = std::move(frame), path = std::move(screenshot_filepath_string)] + [frame = std::move(frame), w = viewport_size.x(), h = viewport_size.y(), path = std::move(screenshot_filepath_string)] { stbi_flip_vertically_on_write(1); - stbi_write_png(path.c_str(), static_cast(frame->size().x()), static_cast(frame->size().y()), static_cast(frame->channels()), frame->data(), static_cast(frame->size().x() * frame->channels())); + stbi_write_png(path.c_str(), w, h, 3, frame.get(), w * 3); - debug::log::debug("Saved screenshot to \"{}\"", path); + debug::log_debug("Saved screenshot to \"{}\"", path); } ).detach(); } @@ -194,15 +324,13 @@ void select_anti_aliasing_method(::game& ctx, render::anti_aliasing_method metho { // Off case render::anti_aliasing_method::none: - debug::log::debug("Anti-aliasing disabled"); - ctx.fxaa_pass->set_enabled(false); + debug::log_debug("Anti-aliasing disabled"); reroute_framebuffers(ctx); break; // FXAA case render::anti_aliasing_method::fxaa: - debug::log::debug("Anti-aliasing enabled (FXAA)"); - ctx.fxaa_pass->set_enabled(true); + debug::log_debug("Anti-aliasing enabled (FXAA)"); reroute_framebuffers(ctx); break; } @@ -213,30 +341,20 @@ void select_anti_aliasing_method(::game& ctx, render::anti_aliasing_method metho void reroute_framebuffers(::game& ctx) { - if (ctx.fxaa_pass->is_enabled()) + if (ctx.resample_pass->is_enabled()) { - if (ctx.resample_pass->is_enabled()) - { - ctx.common_final_pass->set_framebuffer(ctx.ldr_framebuffer_a.get()); - ctx.fxaa_pass->set_framebuffer(ctx.ldr_framebuffer_b.get()); - } - else - { - ctx.common_final_pass->set_framebuffer(ctx.ldr_framebuffer_a.get()); - ctx.fxaa_pass->set_framebuffer(&ctx.window->get_rasterizer()->get_default_framebuffer()); - } + ctx.common_final_pass->set_framebuffer(ctx.ldr_framebuffer_a.get()); } else { - if (ctx.resample_pass->is_enabled()) - { - ctx.common_final_pass->set_framebuffer(ctx.ldr_framebuffer_b.get()); - } - else - { - ctx.common_final_pass->set_framebuffer(&ctx.window->get_rasterizer()->get_default_framebuffer()); - } + ctx.common_final_pass->set_framebuffer(nullptr); } + + ctx.sky_pass->set_framebuffer(ctx.hdr_framebuffer.get()); + ctx.surface_material_pass->set_framebuffer(ctx.hdr_framebuffer.get()); + ctx.bloom_pass->set_source_texture(ctx.hdr_color_texture); + ctx.common_final_pass->set_color_texture(ctx.hdr_color_texture); + ctx.common_final_pass->set_bloom_texture(ctx.bloom_pass->get_bloom_texture()); } } // namespace graphics diff --git a/src/game/load.cpp b/src/game/load.cpp index fef80be..7827e07 100644 --- a/src/game/load.cpp +++ b/src/game/load.cpp @@ -28,16 +28,16 @@ void colony(::game& ctx, const std::filesystem::path& path) { // const std::string path_string = path.string(); - // debug::log::trace("Loading colony from \"{}\"...", path_string); + // debug::log_trace("Loading colony from \"{}\"...", path_string); // try // { // json* data = ctx.resource_manager->load(path); - // debug::log::trace("Loaded colony from \"{}\"", path_string); + // debug::log_trace("Loaded colony from \"{}\"", path_string); // } // catch (...) // { - // debug::log::error("Failed to load colony from \"{}\"", path_string); + // debug::log_error("Failed to load colony from \"{}\"", path_string); // } } diff --git a/src/game/main.cpp b/src/game/main.cpp index b8e50d9..eb5fc6c 100644 --- a/src/game/main.cpp +++ b/src/game/main.cpp @@ -41,7 +41,7 @@ int main(int argc, char* argv[]) debug::console::enable_vt100(); // Subscribe log to cout function to message logged events - auto log_to_cout_subscription = debug::log::default_logger().get_message_logged_channel().subscribe + auto log_to_cout_subscription = debug::default_logger().get_message_logged_channel().subscribe ( [&launch_time](const auto& event) { @@ -91,7 +91,7 @@ int main(int argc, char* argv[]) // Create log archive if it doesn't exist if (std::filesystem::create_directories(log_archive_path)) { - debug::log::debug("Created log archive \"{}\"", log_archive_path.string()); + debug::log_debug("Created log archive \"{}\"", log_archive_path.string()); } else { @@ -109,7 +109,7 @@ int main(int argc, char* argv[]) } } - debug::log::debug("Detected {} archived log{} at \"{}\"", log_archive.size(), log_archive.size() != 1 ? "s" : "", log_archive_path.string()); + debug::log_debug("Detected {} archived log{} at \"{}\"", log_archive.size(), log_archive.size() != 1 ? "s" : "", log_archive_path.string()); // Delete expired logs if (!log_archive.empty()) @@ -117,14 +117,14 @@ int main(int argc, char* argv[]) for (std::size_t i = log_archive.size() + 1; i > config::debug_log_archive_capacity; --i) { std::filesystem::remove(*log_archive.begin()); - debug::log::debug("Deleted expired log file \"{}\"", log_archive.begin()->string()); + debug::log_debug("Deleted expired log file \"{}\"", log_archive.begin()->string()); log_archive.erase(log_archive.begin()); } } } catch (const std::filesystem::filesystem_error& e) { - debug::log::error("An error occured while cleaning the log archive \"{}\": {}", log_archive_path.string(), e.what()); + debug::log_error("An error occured while cleaning the log archive \"{}\": {}", log_archive_path.string(), e.what()); } } @@ -132,7 +132,7 @@ int main(int argc, char* argv[]) } catch (const std::filesystem::filesystem_error& e) { - debug::log::error("Failed to create log archive at \"{}\": {}", log_archive_path.string(), e.what()); + debug::log_error("Failed to create log archive at \"{}\": {}", log_archive_path.string(), e.what()); } // Set up logging to file @@ -151,7 +151,7 @@ int main(int argc, char* argv[]) if (log_filestream->is_open()) { - debug::log::debug("Opened log file \"{}\"", log_filepath_string); + debug::log_debug("Opened log file \"{}\"", log_filepath_string); // Write log file header (*log_filestream) << "time\tfile\tline\tcolumn\tseverity\tmessage"; @@ -159,7 +159,7 @@ int main(int argc, char* argv[]) if (log_filestream->good()) { // Subscribe log to file function to message logged events - log_to_file_subscription = debug::log::default_logger().get_message_logged_channel().subscribe + log_to_file_subscription = debug::default_logger().get_message_logged_channel().subscribe ( [&launch_time, log_filestream](const auto& event) { @@ -183,38 +183,39 @@ int main(int argc, char* argv[]) } else { - debug::log::error("Failed to write to log file \"{}\"", log_filepath_string); + debug::log_error("Failed to write to log file \"{}\"", log_filepath_string); } } else { - debug::log::error("Failed to open log file \"{}\"", log_filepath_string); + debug::log_error("Failed to open log file \"{}\"", log_filepath_string); } } // Log application name and version string, followed by launch time - debug::log::info("{0} {1}; {2:%Y%m%d}T{2:%H%M%S}Z", config::application_name, config::application_version_string, std::chrono::floor(launch_time)); + debug::log_info("{0} {1}; {2:%Y%m%d}T{2:%H%M%S}Z", config::application_name, config::application_version_string, std::chrono::floor(launch_time)); // Start marker - debug::log::debug("Hi! 🐜"); + debug::log_debug("Hi! 🐜"); + // #if defined(NDEBUG) try { game(argc, argv).execute(); } catch (const std::exception& e) { - debug::log::fatal("Unhandled exception: {}", e.what()); - - #if defined(NDEBUG) - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", std::format("Unhandled exception: {}", e.what()).c_str(), nullptr); - #endif + debug::log_fatal("Unhandled exception: {}", e.what()); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", std::format("Unhandled exception: {}", e.what()).c_str(), nullptr); return EXIT_FAILURE; } + // #else + // game(argc, argv).execute(); + // #endif // Clean exit marker - debug::log::debug("Bye! 🐜"); + debug::log_debug("Bye! 🐜"); return EXIT_SUCCESS; } diff --git a/src/game/settings.hpp b/src/game/settings.hpp index 7c31eda..bd140fd 100644 --- a/src/game/settings.hpp +++ b/src/game/settings.hpp @@ -46,14 +46,14 @@ bool read_or_write_setting(::game& ctx, hash::fnv1a32_t key, T& value) } catch (const std::bad_any_cast&) { - debug::log::error("Setting type mismatch ({:x}={})", key.value, value); + debug::log_error("Setting type mismatch ({:x}={})", key.value, value); i->second = value; return false; } } else { - debug::log::trace("Setting key not found ({:x}={})", key.value, value); + debug::log_trace("Setting key not found ({:x}={})", key.value, value); (*ctx.settings)[key] = value; return false; } diff --git a/src/game/states/collection-menu-state.cpp b/src/game/states/collection-menu-state.cpp index bd3893c..d155a83 100644 --- a/src/game/states/collection-menu-state.cpp +++ b/src/game/states/collection-menu-state.cpp @@ -28,17 +28,13 @@ #include #include #include -#include using namespace hash::literals; collection_menu_state::collection_menu_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering collection menu state..."); - - // Enable color buffer clearing in UI pass - ctx.ui_clear_pass->set_cleared_buffers(true, true, false); + debug::log_trace("Entering collection menu state..."); // Construct box material box_material = std::make_shared(); @@ -106,7 +102,7 @@ collection_menu_state::collection_menu_state(::game& ctx): } ); - debug::log::debug("selected colony: ({}, {})", selected_column, selected_row); + debug::log_debug("selected colony: ({}, {})", selected_column, selected_row); } } } @@ -126,17 +122,17 @@ collection_menu_state::collection_menu_state(::game& ctx): // Fade in from black ctx.fade_transition->transition(config::title_fade_in_duration, true, ease::out_cubic); - debug::log::trace("Entered collection menu state"); + debug::log_trace("Entered collection menu state"); } collection_menu_state::~collection_menu_state() { - debug::log::trace("Exiting collection menu state..."); + debug::log_trace("Exiting collection menu state..."); // Destruct menu //::disable_menu_controls(ctx); - debug::log::trace("Exited collection menu state"); + debug::log_trace("Exited collection menu state"); } void collection_menu_state::resize_box() diff --git a/src/game/states/controls-menu-state.cpp b/src/game/states/controls-menu-state.cpp index 0523bf0..8314044 100644 --- a/src/game/states/controls-menu-state.cpp +++ b/src/game/states/controls-menu-state.cpp @@ -33,7 +33,7 @@ using namespace hash::literals; controls_menu_state::controls_menu_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering controls menu state..."); + debug::log_trace("Entering controls menu state..."); // Construct menu item texts keyboard_text = std::make_unique(); @@ -151,12 +151,12 @@ controls_menu_state::controls_menu_state(::game& ctx): // Fade in menu ::menu::fade_in(ctx, nullptr); - debug::log::trace("Entered controls menu state"); + debug::log_trace("Entered controls menu state"); } controls_menu_state::~controls_menu_state() { - debug::log::trace("Exiting options menu state..."); + debug::log_trace("Exiting options menu state..."); // Destruct menu ::disable_menu_controls(ctx); @@ -165,5 +165,5 @@ controls_menu_state::~controls_menu_state() ::menu::remove_text_from_ui(ctx); ::menu::delete_text(ctx); - debug::log::trace("Exited controls menu state"); + debug::log_trace("Exited controls menu state"); } diff --git a/src/game/states/credits-state.cpp b/src/game/states/credits-state.cpp index 323e47f..f9254ef 100644 --- a/src/game/states/credits-state.cpp +++ b/src/game/states/credits-state.cpp @@ -32,7 +32,7 @@ credits_state::credits_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering credits state..."); + debug::log_trace("Entering credits state..."); const math::fvec2 viewport_size = math::fvec2(ctx.window->get_viewport_size()); const math::fvec2 viewport_center = viewport_size * 0.5f; @@ -133,12 +133,12 @@ credits_state::credits_state(::game& ctx): ctx.ui_scene->add_object(credits_text); - debug::log::trace("Entered credits state"); + debug::log_trace("Entered credits state"); } credits_state::~credits_state() { - debug::log::trace("Exiting credits state..."); + debug::log_trace("Exiting credits state..."); // Disable credits skippers ctx.input_mapper.disconnect(); @@ -150,5 +150,5 @@ credits_state::~credits_state() // Destruct credits animations ctx.animator->remove_animation(&credits_fade_in_animation); - debug::log::trace("Exited credits state"); + debug::log_trace("Exited credits state"); } diff --git a/src/game/states/experiments/treadmill-experiment-state.cpp b/src/game/states/experiments/treadmill-experiment-state.cpp index d65fe0b..d1f8b64 100644 --- a/src/game/states/experiments/treadmill-experiment-state.cpp +++ b/src/game/states/experiments/treadmill-experiment-state.cpp @@ -77,7 +77,6 @@ #include #include #include -#include #include #include #include @@ -96,24 +95,24 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering nest view state..."); + debug::log_trace("Entering nest view state..."); ctx.active_scene = ctx.surface_scene.get(); ctx.active_ecoregion = ctx.resource_manager->load<::ecoregion>("seedy-scrub.eco"); ::world::enter_ecoregion(ctx, *ctx.active_ecoregion); - debug::log::trace("Generating genome..."); + debug::log_trace("Generating genome..."); std::shared_ptr genome = ant_cladogenesis(ctx.active_ecoregion->gene_pools[0], ctx.rng); - debug::log::trace("Generated genome"); + debug::log_trace("Generated genome"); - debug::log::trace("Building worker phenome..."); + debug::log_trace("Building worker phenome..."); worker_phenome = std::make_shared(*genome, ant_caste_type::worker); - debug::log::trace("Built worker phenome..."); + debug::log_trace("Built worker phenome..."); - debug::log::trace("Generating worker model..."); + debug::log_trace("Generating worker model..."); std::shared_ptr worker_model = ant_morphogenesis(*worker_phenome); - debug::log::trace("Generated worker model"); + debug::log_trace("Generated worker model"); // Create nest exterior { @@ -156,7 +155,7 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx): // Generate terrain { - auto heightmap = ctx.resource_manager->load("chiricahua-s.png"); + auto heightmap = ctx.resource_manager->load("chiricahua-s.png"); auto subdivisions = math::uvec2{0, 0}; // auto subdivisions = math::uvec2{3, 3}; auto transform = math::transform::identity(); @@ -310,9 +309,6 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx): color_checker_scene_component.layer_mask = 1; ctx.entity_registry->emplace(color_checker_eid, std::move(color_checker_scene_component)); - // Disable UI color clear - ctx.ui_clear_pass->set_cleared_buffers(false, true, false); - // Set world time ::world::set_time(ctx, 2022, 6, 21, 12, 0, 0.0); @@ -326,7 +322,35 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx): ctx.sky_pass->set_enabled(true); sky_probe = std::make_shared(); - sky_probe->set_luminance_texture(std::make_shared(512, 384, gl::pixel_type::float_16, gl::pixel_format::rgb)); + const std::uint32_t sky_probe_face_size = 128; + const auto sky_probe_mip_levels = static_cast(std::bit_width(sky_probe_face_size)); + sky_probe->set_luminance_texture + ( + std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::r16g16b16_sfloat, + sky_probe_face_size, + sky_probe_mip_levels + ), + gl::format::undefined, + 0, + sky_probe_mip_levels + ), + std::make_shared + ( + gl::sampler_filter::linear, + gl::sampler_filter::linear, + gl::sampler_mipmap_mode::linear, + gl::sampler_address_mode::clamp_to_edge, + gl::sampler_address_mode::clamp_to_edge + ) + ) + ); + ctx.sky_pass->set_sky_probe(sky_probe); ctx.surface_scene->add_object(*sky_probe); @@ -361,12 +385,12 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx): // Refresh frame scheduler ctx.frame_scheduler.refresh(); - debug::log::trace("Entered nest view state"); + debug::log_trace("Entered nest view state"); } treadmill_experiment_state::~treadmill_experiment_state() { - debug::log::trace("Exiting nest view state..."); + debug::log_trace("Exiting nest view state..."); // Disable game controls ::disable_game_controls(ctx); @@ -377,7 +401,7 @@ treadmill_experiment_state::~treadmill_experiment_state() destroy_third_person_camera_rig(); - debug::log::trace("Exited nest view state"); + debug::log_trace("Exited nest view state"); } void treadmill_experiment_state::create_third_person_camera_rig() @@ -546,7 +570,7 @@ void treadmill_experiment_state::setup_controls() if (auto trace = ctx.physics_system->trace(mouse_ray, entt::null, camera_object.get_layer_mask())) { - // debug::log::debug("HIT! EID: {}; distance: {}; face: {}", static_cast(std::get<0>(*trace)), std::get<1>(*trace), std::get<2>(*trace)); + // debug::log_debug("HIT! EID: {}; distance: {}; face: {}", static_cast(std::get<0>(*trace)), std::get<1>(*trace), std::get<2>(*trace)); const auto& hit_rigid_body = *ctx.entity_registry->get(std::get<0>(*trace)).body; const auto& hit_collider = *hit_rigid_body.get_collider(); diff --git a/src/game/states/extras-menu-state.cpp b/src/game/states/extras-menu-state.cpp index 3f1ffb2..9a3adf4 100644 --- a/src/game/states/extras-menu-state.cpp +++ b/src/game/states/extras-menu-state.cpp @@ -33,7 +33,7 @@ using namespace hash::literals; extras_menu_state::extras_menu_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering extras menu state..."); + debug::log_trace("Entering extras menu state..."); // Construct menu item texts credits_text = std::make_unique(); @@ -123,12 +123,12 @@ extras_menu_state::extras_menu_state(::game& ctx): // Queue enable menu controls ctx.function_queue.push(std::bind(::enable_menu_controls, std::ref(ctx))); - debug::log::trace("Entered extras menu state"); + debug::log_trace("Entered extras menu state"); } extras_menu_state::~extras_menu_state() { - debug::log::trace("Exiting extras menu state..."); + debug::log_trace("Exiting extras menu state..."); // Destruct menu ::disable_menu_controls(ctx); @@ -137,6 +137,6 @@ extras_menu_state::~extras_menu_state() ::menu::remove_text_from_ui(ctx); ::menu::delete_text(ctx); - debug::log::trace("Exited extras menu state"); + debug::log_trace("Exited extras menu state"); } diff --git a/src/game/states/gamepad-config-menu-state.cpp b/src/game/states/gamepad-config-menu-state.cpp index f9418a6..e1db553 100644 --- a/src/game/states/gamepad-config-menu-state.cpp +++ b/src/game/states/gamepad-config-menu-state.cpp @@ -36,7 +36,7 @@ gamepad_config_menu_state::gamepad_config_menu_state(::game& ctx): game_state(ctx), action_remapped(false) { - debug::log::trace("Entering gamepad config menu state..."); + debug::log_trace("Entering gamepad config menu state..."); // Add control menu items add_control_item(ctx.movement_action_map, ctx.move_forward_action, "control_move_forward"); @@ -107,12 +107,12 @@ gamepad_config_menu_state::gamepad_config_menu_state(::game& ctx): // Fade in menu ::menu::fade_in(ctx, nullptr); - debug::log::trace("Entered gamepad config menu state"); + debug::log_trace("Entered gamepad config menu state"); } gamepad_config_menu_state::~gamepad_config_menu_state() { - debug::log::trace("Exiting gamepad config menu state..."); + debug::log_trace("Exiting gamepad config menu state..."); // Destruct menu ::disable_menu_controls(ctx); @@ -131,7 +131,7 @@ gamepad_config_menu_state::~gamepad_config_menu_state() ctx.resource_manager->save(*ctx.control_profile, ctx.control_profile_filename); } - debug::log::trace("Exited gamepad config menu state"); + debug::log_trace("Exited gamepad config menu state"); } std::string gamepad_config_menu_state::get_mapping_string(const input::action_map& action_map, const input::action& control) diff --git a/src/game/states/graphics-menu-state.cpp b/src/game/states/graphics-menu-state.cpp index 52215a0..f4eb999 100644 --- a/src/game/states/graphics-menu-state.cpp +++ b/src/game/states/graphics-menu-state.cpp @@ -35,7 +35,7 @@ using namespace hash::literals; graphics_menu_state::graphics_menu_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering graphics menu state..."); + debug::log_trace("Entering graphics menu state..."); // Construct menu item texts fullscreen_name_text = std::make_unique(); @@ -224,9 +224,9 @@ graphics_menu_state::graphics_menu_state(::game& ctx): this->update_value_text_content(); // Reload fonts - debug::log::trace("Reloading fonts..."); + debug::log_trace("Reloading fonts..."); ::load_fonts(ctx); - debug::log::trace("Reloaded fonts"); + debug::log_trace("Reloaded fonts"); // Refresh and realign text ::menu::refresh_text(ctx); @@ -252,9 +252,9 @@ graphics_menu_state::graphics_menu_state(::game& ctx): this->update_value_text_content(); // Reload fonts - debug::log::trace("Reloading fonts..."); + debug::log_trace("Reloading fonts..."); ::load_fonts(ctx); - debug::log::trace("Reloaded fonts"); + debug::log_trace("Reloaded fonts"); // Refresh and realign text ::menu::refresh_text(ctx); @@ -272,9 +272,9 @@ graphics_menu_state::graphics_menu_state(::game& ctx): (*ctx.settings)["dyslexia_font"] = ctx.dyslexia_font; // Reload fonts - debug::log::trace("Reloading fonts..."); + debug::log_trace("Reloading fonts..."); ::load_fonts(ctx); - debug::log::trace("Reloaded fonts"); + debug::log_trace("Reloaded fonts"); // Refresh and realign text ::menu::refresh_text(ctx); @@ -339,12 +339,12 @@ graphics_menu_state::graphics_menu_state(::game& ctx): // Fade in menu ::menu::fade_in(ctx, nullptr); - debug::log::trace("Entered graphics menu state"); + debug::log_trace("Entered graphics menu state"); } graphics_menu_state::~graphics_menu_state() { - debug::log::trace("Exiting graphics menu state..."); + debug::log_trace("Exiting graphics menu state..."); // Destruct menu ::disable_menu_controls(ctx); @@ -353,7 +353,7 @@ graphics_menu_state::~graphics_menu_state() ::menu::remove_text_from_ui(ctx); ::menu::delete_text(ctx); - debug::log::trace("Exited graphics menu state"); + debug::log_trace("Exited graphics menu state"); } void graphics_menu_state::update_value_text_content() diff --git a/src/game/states/keyboard-config-menu-state.cpp b/src/game/states/keyboard-config-menu-state.cpp index 356293e..cb8e6dc 100644 --- a/src/game/states/keyboard-config-menu-state.cpp +++ b/src/game/states/keyboard-config-menu-state.cpp @@ -36,7 +36,7 @@ keyboard_config_menu_state::keyboard_config_menu_state(::game& ctx): game_state(ctx), action_remapped(false) { - debug::log::trace("Entering keyboard config menu state..."); + debug::log_trace("Entering keyboard config menu state..."); // Add control menu items add_control_item(ctx.movement_action_map, ctx.move_forward_action, "control_move_forward"); @@ -107,12 +107,12 @@ keyboard_config_menu_state::keyboard_config_menu_state(::game& ctx): // Fade in menu ::menu::fade_in(ctx, nullptr); - debug::log::trace("Entered keyboard config menu state"); + debug::log_trace("Entered keyboard config menu state"); } keyboard_config_menu_state::~keyboard_config_menu_state() { - debug::log::trace("Exiting keyboard config menu state..."); + debug::log_trace("Exiting keyboard config menu state..."); // Destruct menu ::disable_menu_controls(ctx); @@ -131,7 +131,7 @@ keyboard_config_menu_state::~keyboard_config_menu_state() ctx.resource_manager->save(*ctx.control_profile, ctx.control_profile_filename); } - debug::log::trace("Exited keyboard config menu state..."); + debug::log_trace("Exited keyboard config menu state..."); } std::string keyboard_config_menu_state::get_mapping_string(const input::action_map& action_map, const input::action& control) diff --git a/src/game/states/language-menu-state.cpp b/src/game/states/language-menu-state.cpp index ef6a068..8418cf4 100644 --- a/src/game/states/language-menu-state.cpp +++ b/src/game/states/language-menu-state.cpp @@ -35,7 +35,7 @@ using namespace hash::literals; language_menu_state::language_menu_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering language menu state..."); + debug::log_trace("Entering language menu state..."); // Load language manifest language_manifest = ctx.resource_manager->load("languages.txt"); @@ -101,12 +101,12 @@ language_menu_state::language_menu_state(::game& ctx): (*ctx.settings)["language_tag"] = ctx.language_tag; // Log language change - debug::log::info("Language tag: {}", ctx.language_tag); + debug::log_info("Language tag: {}", ctx.language_tag); // Reload fonts - debug::log::trace("Reloading fonts..."); + debug::log_trace("Reloading fonts..."); ::load_fonts(ctx); - debug::log::trace("Reloaded fonts"); + debug::log_trace("Reloaded fonts"); // Update menus ::menu::update_text_font(ctx); @@ -178,12 +178,12 @@ language_menu_state::language_menu_state(::game& ctx): // Fade in menu ::menu::fade_in(ctx, nullptr); - debug::log::trace("Entered language menu state"); + debug::log_trace("Entered language menu state"); } language_menu_state::~language_menu_state() { - debug::log::trace("Exiting language menu state..."); + debug::log_trace("Exiting language menu state..."); // Destruct menu ::disable_menu_controls(ctx); @@ -192,7 +192,7 @@ language_menu_state::~language_menu_state() ::menu::remove_text_from_ui(ctx); ::menu::delete_text(ctx); - debug::log::trace("Exited language menu state"); + debug::log_trace("Exited language menu state"); } void language_menu_state::update_text_content() diff --git a/src/game/states/main-menu-state.cpp b/src/game/states/main-menu-state.cpp index 8a1567b..e9c8723 100644 --- a/src/game/states/main-menu-state.cpp +++ b/src/game/states/main-menu-state.cpp @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include @@ -51,9 +50,7 @@ main_menu_state::main_menu_state(::game& ctx, bool fade_in): game_state(ctx) { - debug::log::trace("Entering main menu state..."); - - ctx.ui_clear_pass->set_cleared_buffers(true, true, false); + debug::log_trace("Entering main menu state..."); const math::fvec2 viewport_size = math::fvec2(ctx.window->get_viewport_size()); const math::fvec2 viewport_center = viewport_size * 0.5f; @@ -270,9 +267,6 @@ main_menu_state::main_menu_state(::game& ctx, bool fade_in): // Setup and enable sky and ground passes ctx.sky_pass->set_enabled(true); - // Enable UI color clear - ctx.ui_clear_pass->set_cleared_buffers(true, true, false); - // Setup window resized callback window_resized_subscription = ctx.window->get_resized_channel().subscribe ( @@ -294,12 +288,12 @@ main_menu_state::main_menu_state(::game& ctx, bool fade_in): // Enable menu controls ctx.function_queue.push(std::bind(::enable_menu_controls, std::ref(ctx))); - debug::log::trace("Entered main menu state"); + debug::log_trace("Entered main menu state"); } main_menu_state::~main_menu_state() { - debug::log::trace("Exiting main menu state..."); + debug::log_trace("Exiting main menu state..."); // Destruct menu ::disable_menu_controls(ctx); @@ -317,7 +311,7 @@ main_menu_state::~main_menu_state() // Destruct text ctx.ui_scene->remove_object(*title_text); - debug::log::trace("Exited main menu state"); + debug::log_trace("Exited main menu state"); } void main_menu_state::fade_in_title() diff --git a/src/game/states/nest-selection-state.cpp b/src/game/states/nest-selection-state.cpp index 90bd78c..e7d42e0 100644 --- a/src/game/states/nest-selection-state.cpp +++ b/src/game/states/nest-selection-state.cpp @@ -67,7 +67,6 @@ #include #include #include -#include #include #include #include @@ -78,7 +77,7 @@ nest_selection_state::nest_selection_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering nest selection state..."); + debug::log_trace("Entering nest selection state..."); // Create world if not yet created if (ctx.entities.find("earth") == ctx.entities.end()) @@ -93,17 +92,17 @@ nest_selection_state::nest_selection_state(::game& ctx): ctx.active_ecoregion = ctx.resource_manager->load<::ecoregion>("seedy-scrub.eco"); ::world::enter_ecoregion(ctx, *ctx.active_ecoregion); - debug::log::trace("Generating genome..."); + debug::log_trace("Generating genome..."); std::unique_ptr genome = ant_cladogenesis(ctx.active_ecoregion->gene_pools[0], ctx.rng); - debug::log::trace("Generated genome"); + debug::log_trace("Generated genome"); - debug::log::trace("Building worker phenome..."); + debug::log_trace("Building worker phenome..."); ant_phenome worker_phenome = ant_phenome(*genome, ant_caste_type::queen); - debug::log::trace("Built worker phenome..."); + debug::log_trace("Built worker phenome..."); - debug::log::trace("Generating worker model..."); + debug::log_trace("Generating worker model..."); worker_model = ant_morphogenesis(worker_phenome); - debug::log::trace("Generated worker model"); + debug::log_trace("Generated worker model"); // Create floor plane @@ -201,9 +200,6 @@ nest_selection_state::nest_selection_state(::game& ctx): ctx.entity_registry->emplace(larva_eid, std::move(larva_ik_rig)); - // Disable UI color clear - ctx.ui_clear_pass->set_cleared_buffers(false, true, false); - // Set world time ::world::set_time(ctx, 2022, 6, 21, 12, 0, 0.0); @@ -294,7 +290,34 @@ nest_selection_state::nest_selection_state(::game& ctx): */ sky_probe = std::make_shared(); - sky_probe->set_luminance_texture(std::make_shared(384, 512, gl::pixel_type::float_16, gl::pixel_format::rgb)); + const std::uint32_t sky_probe_face_size = 128; + const auto sky_probe_mip_levels = static_cast(std::bit_width(sky_probe_face_size)); + sky_probe->set_luminance_texture + ( + std::make_shared + ( + std::make_shared + ( + std::make_shared + ( + gl::format::r16g16b16_sfloat, + sky_probe_face_size, + sky_probe_mip_levels + ), + gl::format::undefined, + 0, + sky_probe_mip_levels + ), + std::make_shared + ( + gl::sampler_filter::linear, + gl::sampler_filter::linear, + gl::sampler_mipmap_mode::linear, + gl::sampler_address_mode::clamp_to_edge, + gl::sampler_address_mode::clamp_to_edge + ) + ) + ); ctx.sky_pass->set_sky_probe(sky_probe); ctx.surface_scene->add_object(*sky_probe); @@ -327,19 +350,19 @@ nest_selection_state::nest_selection_state(::game& ctx): // Refresh frame scheduler ctx.frame_scheduler.refresh(); - debug::log::trace("Entered nest selection state"); + debug::log_trace("Entered nest selection state"); } nest_selection_state::~nest_selection_state() { - debug::log::trace("Exiting nest selection state..."); + debug::log_trace("Exiting nest selection state..."); // Disable game controls ::disable_game_controls(ctx); destroy_first_person_camera_rig(); - debug::log::trace("Exited nest selection state"); + debug::log_trace("Exited nest selection state"); } void nest_selection_state::create_first_person_camera_rig() @@ -462,7 +485,7 @@ void nest_selection_state::setup_controls() for (std::size_t i = 0; i < 3; ++i) { const auto m = matrices[i]; - debug::log::warning("\nmat4({},{},{},{},\n{},{},{},{},\n{},{},{},{},\n{},{},{},{});", m[0][0], m[0][1], m[0][2], m[0][3], m[1][0], m[1][1], m[1][2], m[1][3], m[2][0], m[2][1], m[2][2], m[2][3], m[3][0], m[3][1], m[3][2], m[3][3]); + debug::log_warning("\nmat4({},{},{},{},\n{},{},{},{},\n{},{},{},{},\n{},{},{},{});", m[0][0], m[0][1], m[0][2], m[0][3], m[1][0], m[1][1], m[1][2], m[1][3], m[2][0], m[2][1], m[2][2], m[2][3], m[3][0], m[3][1], m[3][2], m[3][3]); } */ } diff --git a/src/game/states/nest-view-state.cpp b/src/game/states/nest-view-state.cpp index 26ce8a5..19f1b62 100644 --- a/src/game/states/nest-view-state.cpp +++ b/src/game/states/nest-view-state.cpp @@ -68,7 +68,6 @@ #include #include #include -#include #include #include #include @@ -86,7 +85,7 @@ nest_view_state::nest_view_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering nest view state..."); + debug::log_trace("Entering nest view state..."); // Create world if not yet created if (ctx.entities.find("earth") == ctx.entities.end()) @@ -101,17 +100,17 @@ nest_view_state::nest_view_state(::game& ctx): ctx.active_ecoregion = ctx.resource_manager->load<::ecoregion>("seedy-scrub.eco"); ::world::enter_ecoregion(ctx, *ctx.active_ecoregion); - debug::log::trace("Generating genome..."); + debug::log_trace("Generating genome..."); std::unique_ptr genome = ant_cladogenesis(ctx.active_ecoregion->gene_pools[0], ctx.rng); - debug::log::trace("Generated genome"); + debug::log_trace("Generated genome"); - debug::log::trace("Building worker phenome..."); + debug::log_trace("Building worker phenome..."); worker_phenome = ant_phenome(*genome, ant_caste_type::worker); - debug::log::trace("Built worker phenome..."); + debug::log_trace("Built worker phenome..."); - debug::log::trace("Generating worker model..."); + debug::log_trace("Generating worker model..."); std::shared_ptr worker_model = ant_morphogenesis(worker_phenome); - debug::log::trace("Generated worker model"); + debug::log_trace("Generated worker model"); // Create directional light // ctx.underground_directional_light = std::make_unique(); @@ -126,8 +125,6 @@ nest_view_state::nest_view_state(::game& ctx): // ctx.underground_directional_light->set_shadow_cascade_distribution(0.8f); // ctx.underground_scene->add_object(*ctx.underground_directional_light); - ctx.underground_clear_pass->set_clear_color({0.214f, 0.214f, 0.214f, 1.0f}); - // ctx.underground_clear_pass->set_clear_color({}); light_probe = std::make_shared(); light_probe->set_luminance_texture(ctx.resource_manager->load("grey-furnace.tex")); ctx.underground_scene->add_object(*light_probe); @@ -229,9 +226,6 @@ nest_view_state::nest_view_state(::game& ctx): // } // ); - // Disable UI color clear - ctx.ui_clear_pass->set_cleared_buffers(false, true, false); - // Set world time ::world::set_time(ctx, 2022, 6, 21, 12, 0, 0.0); @@ -277,23 +271,23 @@ nest_view_state::nest_view_state(::game& ctx): geom::generate_vertex_normals(*navmesh); // Build navmesh BVH - debug::log::info("building bvh"); + debug::log_info("building bvh"); navmesh_bvh = std::make_unique(*navmesh); - debug::log::info("building bvh done"); + debug::log_info("building bvh done"); - debug::log::trace("Entered nest view state"); + debug::log_trace("Entered nest view state"); } nest_view_state::~nest_view_state() { - debug::log::trace("Exiting nest view state..."); + debug::log_trace("Exiting nest view state..."); // Disable game controls ::disable_game_controls(ctx); destroy_third_person_camera_rig(); - debug::log::trace("Exited nest view state"); + debug::log_trace("Exited nest view state"); } void nest_view_state::create_third_person_camera_rig() @@ -500,7 +494,7 @@ void nest_view_state::setup_controls() const auto& mouse_position = (*ctx.input_manager->get_mice().begin())->get_position(); const auto mouse_ray = get_mouse_ray(mouse_position); - debug::log::info("pick:"); + debug::log_info("pick:"); float nearest_hit = std::numeric_limits::infinity(); bool hit = false; std::uint32_t hit_index; @@ -538,7 +532,7 @@ void nest_view_state::setup_controls() } ); - debug::log::info("box tests passed: {}", box_test_passed); + debug::log_info("box tests passed: {}", box_test_passed); if (hit) { @@ -566,11 +560,11 @@ void nest_view_state::setup_controls() } ); - debug::log::info("hit! test count: {}", test_count); + debug::log_info("hit! test count: {}", test_count); } else { - debug::log::info("no hit"); + debug::log_info("no hit"); } } ) diff --git a/src/game/states/nuptial-flight-state.cpp b/src/game/states/nuptial-flight-state.cpp index 372ad36..c62b3fc 100644 --- a/src/game/states/nuptial-flight-state.cpp +++ b/src/game/states/nuptial-flight-state.cpp @@ -54,7 +54,6 @@ #include #include "game/world.hpp" #include "game/strings.hpp" -#include #include #include #include @@ -69,15 +68,12 @@ using namespace hash::literals; nuptial_flight_state::nuptial_flight_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering nuptial flight state..."); + debug::log_trace("Entering nuptial flight state..."); // Init selected picking flag selected_picking_flag = std::uint32_t{1} << (sizeof(std::uint32_t) * 8 - 1); selected_eid = entt::null; - // Disable UI color clear - ctx.ui_clear_pass->set_cleared_buffers(false, true, false); - // Create world if not yet created if (ctx.entities.find("earth") == ctx.entities.end()) { @@ -184,12 +180,12 @@ nuptial_flight_state::nuptial_flight_state(::game& ctx): // Refresh frame scheduler ctx.frame_scheduler.refresh(); - debug::log::trace("Entered nuptial flight state"); + debug::log_trace("Entered nuptial flight state"); } nuptial_flight_state::~nuptial_flight_state() { - debug::log::trace("Exiting nuptial flight state..."); + debug::log_trace("Exiting nuptial flight state..."); // Disable game controls ::disable_game_controls(ctx); @@ -204,7 +200,7 @@ nuptial_flight_state::~nuptial_flight_state() destroy_camera_rig(); destroy_ant_swarm(ctx, swarm_eid); - debug::log::trace("Exited nuptial flight state"); + debug::log_trace("Exited nuptial flight state"); } void nuptial_flight_state::create_camera_rig() diff --git a/src/game/states/options-menu-state.cpp b/src/game/states/options-menu-state.cpp index d22a1c8..b26e278 100644 --- a/src/game/states/options-menu-state.cpp +++ b/src/game/states/options-menu-state.cpp @@ -39,7 +39,7 @@ using namespace hash::literals; options_menu_state::options_menu_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering options menu state..."); + debug::log_trace("Entering options menu state..."); // Construct menu item texts controls_text = std::make_unique(); @@ -215,12 +215,12 @@ options_menu_state::options_menu_state(::game& ctx): // Queue enable menu controls ctx.function_queue.push(std::bind(::enable_menu_controls, std::ref(ctx))); - debug::log::trace("Entered options menu state"); + debug::log_trace("Entered options menu state"); } options_menu_state::~options_menu_state() { - debug::log::trace("Exiting options menu state..."); + debug::log_trace("Exiting options menu state..."); // Destruct menu ::disable_menu_controls(ctx); @@ -229,5 +229,5 @@ options_menu_state::~options_menu_state() ::menu::remove_text_from_ui(ctx); ::menu::delete_text(ctx); - debug::log::trace("Exited options menu state"); + debug::log_trace("Exited options menu state"); } diff --git a/src/game/states/pause-menu-state.cpp b/src/game/states/pause-menu-state.cpp index 2001df6..5e28878 100644 --- a/src/game/states/pause-menu-state.cpp +++ b/src/game/states/pause-menu-state.cpp @@ -38,7 +38,7 @@ using namespace hash::literals; pause_menu_state::pause_menu_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering pause menu state..."); + debug::log_trace("Entering pause menu state..."); // Construct menu item texts resume_text = std::make_unique();; @@ -206,12 +206,12 @@ pause_menu_state::pause_menu_state(::game& ctx): // Save colony //::save::colony(ctx); - debug::log::trace("Entered pause menu state"); + debug::log_trace("Entered pause menu state"); } pause_menu_state::~pause_menu_state() { - debug::log::trace("Exiting pause menu state..."); + debug::log_trace("Exiting pause menu state..."); // Destruct menu ::disable_menu_controls(ctx); @@ -220,5 +220,5 @@ pause_menu_state::~pause_menu_state() ::menu::remove_text_from_ui(ctx); ::menu::delete_text(ctx); - debug::log::trace("Exited pause menu state"); + debug::log_trace("Exited pause menu state"); } diff --git a/src/game/states/sound-menu-state.cpp b/src/game/states/sound-menu-state.cpp index 3251fd0..d15ba45 100644 --- a/src/game/states/sound-menu-state.cpp +++ b/src/game/states/sound-menu-state.cpp @@ -32,7 +32,7 @@ using namespace hash::literals; sound_menu_state::sound_menu_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering sound menu state..."); + debug::log_trace("Entering sound menu state..."); // Construct menu item texts master_volume_name_text = std::make_unique(); @@ -215,12 +215,12 @@ sound_menu_state::sound_menu_state(::game& ctx): // Fade in menu ::menu::fade_in(ctx, nullptr); - debug::log::trace("Entered sound menu state"); + debug::log_trace("Entered sound menu state"); } sound_menu_state::~sound_menu_state() { - debug::log::trace("Exiting sound menu state..."); + debug::log_trace("Exiting sound menu state..."); // Destruct menu ::disable_menu_controls(ctx); @@ -229,7 +229,7 @@ sound_menu_state::~sound_menu_state() ::menu::remove_text_from_ui(ctx); ::menu::delete_text(ctx); - debug::log::trace("Exited sound menu state"); + debug::log_trace("Exited sound menu state"); } void sound_menu_state::update_value_text_content() diff --git a/src/game/states/splash-state.cpp b/src/game/states/splash-state.cpp index 14808a5..cba3735 100644 --- a/src/game/states/splash-state.cpp +++ b/src/game/states/splash-state.cpp @@ -27,25 +27,22 @@ #include #include #include -#include #include +#include splash_state::splash_state(::game& ctx): game_state(ctx) { - debug::log::trace("Entering splash state..."); + debug::log_trace("Entering splash state..."); const math::fvec2 viewport_size = math::fvec2(ctx.window->get_viewport_size()); const math::fvec2 viewport_center = viewport_size * 0.5f; - // Enable color buffer clearing in UI pass - ctx.ui_clear_pass->set_cleared_buffers(true, true, false); - // Load splash texture auto splash_texture = ctx.resource_manager->load("splash.tex"); // Get splash texture dimensions - auto splash_dimensions = splash_texture->get_dimensions(); + const auto& splash_dimensions = splash_texture->get_image_view()->get_image()->get_dimensions(); // Construct splash billboard material splash_billboard_material = std::make_shared(); @@ -58,7 +55,7 @@ splash_state::splash_state(::game& ctx): // Construct splash billboard splash_billboard.set_material(splash_billboard_material); - splash_billboard.set_scale({static_cast(std::get<0>(splash_dimensions)) * 0.5f, static_cast(std::get<1>(splash_dimensions)) * 0.5f, 1.0f}); + splash_billboard.set_scale({static_cast(splash_dimensions[0]) * 0.5f, static_cast(splash_dimensions[1]) * 0.5f, 1.0f}); splash_billboard.set_translation({std::round(viewport_center.x()), std::round(viewport_center.y()), 0.0f}); // Add splash billboard to UI scene @@ -142,8 +139,7 @@ splash_state::splash_state(::game& ctx): [&]() { // Black out screen - ctx.window->get_rasterizer()->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f); - ctx.window->get_rasterizer()->clear_framebuffer(true, false, false); + ctx.window->get_graphics_pipeline().clear_attachments(gl::color_clear_bit, {{0.0f, 0.0f, 0.0f, 0.0f}}); ctx.window->swap_buffers(); // Change to main menu state @@ -185,12 +181,12 @@ splash_state::splash_state(::game& ctx): } ); - debug::log::trace("Entered splash state"); + debug::log_trace("Entered splash state"); } splash_state::~splash_state() { - debug::log::trace("Exiting splash state..."); + debug::log_trace("Exiting splash state..."); // Disable splash skippers ctx.input_mapper.disconnect(); @@ -203,8 +199,5 @@ splash_state::~splash_state() // Remove splash billboard from UI scene ctx.ui_scene->remove_object(splash_billboard); - // Disable color buffer clearing in UI pass - ctx.ui_clear_pass->set_cleared_buffers(false, true, false); - - debug::log::trace("Exited splash state"); + debug::log_trace("Exited splash state"); } diff --git a/src/game/systems/terrain-system.cpp b/src/game/systems/terrain-system.cpp index aabebf9..50b0a92 100644 --- a/src/game/systems/terrain-system.cpp +++ b/src/game/systems/terrain-system.cpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include #include @@ -45,26 +45,27 @@ void terrain_system::update(float t, float dt) { } -entity::id terrain_system::generate(std::shared_ptr heightmap, const math::uvec2& subdivisions, const math::transform& transform, std::shared_ptr material) +entity::id terrain_system::generate(std::shared_ptr heightmap, const math::uvec2& subdivisions, const math::transform& transform, std::shared_ptr material) { if (!heightmap) { - debug::log::error("Failed to generate terrain from null heightmap"); + debug::log_error("Failed to generate terrain from null heightmap"); throw std::invalid_argument("Failed to generate terrain from null heightmap"); } - if (heightmap->size().x() < 2 || heightmap->size().y() < 2) + const auto& heightmap_dimensions = heightmap->get_dimensions(); + if (heightmap_dimensions[0] < 2 || heightmap_dimensions[1] < 2) { - debug::log::error("Heightmap size less than 2x2"); + debug::log_error("Heightmap size less than 2x2"); throw std::runtime_error("Heightmap size less than 2x2"); } - if (((heightmap->size().x() - 1) % (subdivisions.x() + 1)) != 0 || - ((heightmap->size().y() - 1) % (subdivisions.y() + 1)) != 0) + if (((heightmap_dimensions[0] - 1) % (subdivisions.x() + 1)) != 0 || + ((heightmap_dimensions[1] - 1) % (subdivisions.y() + 1)) != 0) { - debug::log::error("{}x{} heightmap cannot be subdivided {}x{} times", - heightmap->size().x(), - heightmap->size().y(), + debug::log_error("{}x{} heightmap cannot be subdivided {}x{} times", + heightmap_dimensions[0], + heightmap_dimensions[1], subdivisions.x(), subdivisions.y()); throw std::runtime_error("Heightmap subdivision failed"); @@ -86,7 +87,7 @@ entity::id terrain_system::generate(std::shared_ptr heightmap, const math } // Calculate cell dimensions - const auto cell_quad_dimensions = math::uvec2{static_cast(heightmap->size().x() - 1) / grid.dimensions.x(), static_cast(heightmap->size().y() - 1) / grid.dimensions.y()}; + const auto cell_quad_dimensions = math::uvec2{static_cast(heightmap_dimensions[0] - 1) / grid.dimensions.x(), static_cast(heightmap_dimensions[1] - 1) / grid.dimensions.y()}; const auto cell_vert_dimensions = cell_quad_dimensions + 1u; const auto max_scale = math::max(transform.scale); @@ -94,6 +95,27 @@ entity::id terrain_system::generate(std::shared_ptr heightmap, const math const auto vertex_scale = scale_ratio * math::fvec3{2.0f / static_cast(cell_quad_dimensions.x()), 2.0f, 2.0f / static_cast(cell_quad_dimensions.y())}; const auto vertex_translation = -scale_ratio; + // Allocate heightmap data buffer and select heightmap sampling function according to image format + std::vector heightmap_data(heightmap_dimensions[0] * heightmap_dimensions[1]); + std::function sample = [&](const math::uvec2& p) + { + return heightmap_data[p.y() * heightmap_dimensions[0] + p.x()]; + }; + + // Read heightmap pixel data into buffer + heightmap->read + ( + 0, + 0, + 0, + 0, + heightmap_dimensions[0], + heightmap_dimensions[1], + 1, + gl::format::r32_sfloat, + std::as_writable_bytes(std::span{heightmap_data}) + ); + // Generate terrain cell meshes std::for_each ( @@ -121,7 +143,7 @@ entity::id terrain_system::generate(std::shared_ptr heightmap, const math auto vertex = mesh->vertices().emplace_back(); // Get vertex height from heightmap - float height = heightmap->sample(pixel_position).x(); + float height = sample(pixel_position); // Set vertex position auto& position = vertex_positions[vertex->index()]; @@ -171,11 +193,6 @@ entity::id terrain_system::generate(std::shared_ptr heightmap, const math const auto height_s = vertex_positions[index_s].y(); const auto height_n = vertex_positions[index_n].y(); - // float height_w = heightmap->sample(pixel_w).x(); - // float height_e = heightmap->sample(pixel_e).x(); - // float height_s = heightmap->sample(pixel_s).x(); - // float height_n = heightmap->sample(pixel_n).x(); - auto& normal_c = vertex_normals[index_c]; normal_c = math::normalize(math::fvec3{(height_w - height_e) / vertex_scale.x(), 2.0f, (height_s - height_n) / vertex_scale.z()}); } @@ -212,31 +229,30 @@ std::unique_ptr terrain_system::generate_terrain_model(const geom auto& bounds = model->get_bounds(); bounds = {math::fvec3::infinity(), -math::fvec3::infinity()}; - // Get model VBO and VAO - auto& vbo = model->get_vertex_buffer(); + // Construct VAO + constexpr gl::vertex_input_attribute vertex_attributes[] = + { + { + render::vertex_attribute_location::position, + 0, + gl::format::r16g16b16_snorm, + 0 + }, + { + render::vertex_attribute_location::normal, + 0, + gl::format::r32g32b32_sfloat, + 3 * sizeof(std::int16_t) + } + }; auto& vao = model->get_vertex_array(); - - // Build vertex format - const std::size_t vertex_size = 3 * sizeof(std::int16_t) + 3 * sizeof(float); - gl::vertex_attribute position_attribute; - position_attribute.buffer = vbo.get(); - position_attribute.offset = 0; - position_attribute.stride = vertex_size; - position_attribute.type = gl::vertex_attribute_type::int_16; - position_attribute.components = 3; - position_attribute.normalized = true; - gl::vertex_attribute normal_attribute; - normal_attribute.buffer = vbo.get(); - normal_attribute.offset = 3 * sizeof(std::int16_t); - normal_attribute.stride = vertex_size; - normal_attribute.type = gl::vertex_attribute_type::float_32; - normal_attribute.components = 3; - - const auto vert_dimensions = quad_dimensions + 1u; + vao = std::make_unique(vertex_attributes); // Interleave vertex data + const auto vert_dimensions = quad_dimensions + 1u; const std::size_t vertex_count = 2 * (vert_dimensions.x() * quad_dimensions.y() + quad_dimensions.y() - 1); - std::vector vertex_data(vertex_count * vertex_size); + constexpr std::size_t vertex_stride = 3 * sizeof(std::int16_t) + 3 * sizeof(float); + std::vector vertex_data(vertex_count * vertex_stride); std::byte* v = vertex_data.data(); auto normalized_int16 = [](const math::fvec3& f) -> math::vec3 @@ -293,15 +309,11 @@ std::unique_ptr terrain_system::generate_terrain_model(const geom } } - // Resize model VBO and upload interleaved vertex data - vbo->resize(vertex_data.size(), vertex_data); - - // Free interleaved vertex data - vertex_data.clear(); - - // Bind vertex attributes to VAO - vao->bind(render::vertex_attribute::position, position_attribute); - vao->bind(render::vertex_attribute::normal, normal_attribute); + // Construct VBO + auto& vbo = model->get_vertex_buffer(); + vbo = std::make_unique(gl::buffer_usage::static_draw, vertex_data); + model->set_vertex_offset(0); + model->set_vertex_stride(vertex_stride); // Create material group model->get_groups().resize(1); @@ -309,9 +321,9 @@ std::unique_ptr terrain_system::generate_terrain_model(const geom model_group.id = {}; model_group.material = material; - model_group.drawing_mode = gl::drawing_mode::triangle_strip; - model_group.start_index = 0; - model_group.index_count = static_cast(vertex_count); + model_group.primitive_topology = gl::primitive_topology::triangle_strip; + model_group.first_vertex = 0; + model_group.vertex_count = static_cast(vertex_count); return model; } diff --git a/src/game/systems/terrain-system.hpp b/src/game/systems/terrain-system.hpp index 2ca36c1..5c34d34 100644 --- a/src/game/systems/terrain-system.hpp +++ b/src/game/systems/terrain-system.hpp @@ -23,7 +23,7 @@ #include "game/systems/updatable-system.hpp" #include "game/components/terrain-component.hpp" #include -#include +#include #include #include #include @@ -56,7 +56,7 @@ public: * @except std::runtime_error Heightmap size less than 2x2. * @except std::runtime_error Heightmap subdivision failed. */ - entity::id generate(std::shared_ptr heightmap, const math::uvec2& subdivisions, const math::transform& transform, std::shared_ptr material); + entity::id generate(std::shared_ptr heightmap, const math::uvec2& subdivisions, const math::transform& transform, std::shared_ptr material); private: [[nodiscard]] std::unique_ptr generate_terrain_model(const geom::brep_mesh& mesh, std::shared_ptr material, const math::uvec2& quad_dimensions) const; diff --git a/src/game/textures/cocoon-silk-sdf.cpp b/src/game/textures/cocoon-silk-sdf.cpp index 30a9d28..787d6d9 100644 --- a/src/game/textures/cocoon-silk-sdf.cpp +++ b/src/game/textures/cocoon-silk-sdf.cpp @@ -19,7 +19,6 @@ #include "game/textures/cocoon-silk-sdf.hpp" #include -#include #include #include #include @@ -29,7 +28,7 @@ void generate_cocoon_silk_sdf(std::filesystem::path path) { /* - debug::log::info("Generating cocoon silk SDF image..."); + debug::log_info("Generating cocoon silk SDF image..."); image img; img.format(4, 8); @@ -91,13 +90,13 @@ void generate_cocoon_silk_sdf(std::filesystem::path path) // }; } ); - debug::log::info("Generated cocoon silk SDF image"); + debug::log_info("Generated cocoon silk SDF image"); - debug::log::info("Saving cocoon silk SDF image to \"{}\"...", path.string()); + debug::log_info("Saving cocoon silk SDF image to \"{}\"...", path.string()); stbi_flip_vertically_on_write(1); stbi_write_png(path.string().c_str(), img.width(), img.height(), img.channel_count(), img.data(), img.width() * img.channel_count()); - debug::log::info("Saved cocoon silk SDF image to \"{}\"", path.string()); + debug::log_info("Saved cocoon silk SDF image to \"{}\"", path.string()); */ } diff --git a/src/game/textures/rgb-voronoi-noise.cpp b/src/game/textures/rgb-voronoi-noise.cpp index 3ffed8f..4778105 100644 --- a/src/game/textures/rgb-voronoi-noise.cpp +++ b/src/game/textures/rgb-voronoi-noise.cpp @@ -19,7 +19,6 @@ #include "game/textures/rgb-voronoi-noise.hpp" #include -#include #include #include #include diff --git a/src/game/world.cpp b/src/game/world.cpp index f1943e7..9202352 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -35,11 +35,7 @@ #include "game/systems/orbit-system.hpp" #include "game/systems/terrain-system.hpp" #include -#include -#include -#include #include -#include #include #include #include @@ -56,8 +52,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -65,7 +60,6 @@ #include #include #include -#include #include #include @@ -91,19 +85,19 @@ static void create_moon(::game& ctx); void cosmogenesis(::game& ctx) { - debug::log::trace("Generating cosmos..."); + debug::log_trace("Generating cosmos..."); load_ephemeris(ctx); create_stars(ctx); create_sun(ctx); create_earth_moon_system(ctx); - debug::log::trace("Generated cosmos"); + debug::log_trace("Generated cosmos"); } void create_observer(::game& ctx) { - debug::log::trace("Creating observer..."); + debug::log_trace("Creating observer..."); { // Create observer entity @@ -134,7 +128,7 @@ void create_observer(::game& ctx) ctx.astronomy_system->set_observer(observer_eid); } - debug::log::trace("Created observer"); + debug::log_trace("Created observer"); } void set_location(::game& ctx, double elevation, double latitude, double longitude) @@ -167,11 +161,11 @@ void set_time(::game& ctx, double t) ctx.astronomy_system->set_time(t); ctx.orbit_system->set_time(t); - // debug::log::info("Set time to UT1 {}", t); + // debug::log_info("Set time to UT1 {}", t); } catch (const std::exception& e) { - debug::log::error("Failed to set time to UT1 {}: {}", t, e.what()); + debug::log_error("Failed to set time to UT1 {}: {}", t, e.what()); } } @@ -216,7 +210,7 @@ void load_ephemeris(::game& ctx) void create_stars(::game& ctx) { - debug::log::trace("Generating fixed stars..."); + debug::log_trace("Generating fixed stars..."); // Load star catalog auto star_catalog = ctx.resource_manager->load("hipparcos-7.tsv"); @@ -225,9 +219,8 @@ void create_stars(::game& ctx) std::size_t star_count = 0; if (star_catalog->rows.size() > 0) star_count = star_catalog->rows.size() - 1; - std::size_t star_vertex_size = 7; - std::size_t star_vertex_stride = star_vertex_size * sizeof(float); - std::vector star_vertex_data(star_count * star_vertex_size); + constexpr std::size_t star_vertex_stride = 7 * sizeof(float); + std::vector star_vertex_data(star_count * star_vertex_stride / sizeof(float)); float* star_vertex = star_vertex_data.data(); // Init starlight illuminance @@ -252,7 +245,7 @@ void create_stars(::game& ctx) } catch (const std::exception&) { - debug::log::warning("Invalid star catalog item on row {}", i); + debug::log_warning("Invalid star catalog item on row {}", i); continue; } @@ -294,49 +287,39 @@ void create_stars(::game& ctx) // Allocate stars model std::shared_ptr stars_model = std::make_shared(); - // Get model VBO and VAO - auto& vbo = stars_model->get_vertex_buffer(); + // Construct stars VAO + constexpr gl::vertex_input_attribute star_vertex_attributes[] = + { + { + render::vertex_attribute_location::position, + 0, + gl::format::r32g32b32_sfloat, + 0 + }, + { + render::vertex_attribute_location::color, + 0, + gl::format::r32g32b32a32_sfloat, + 3 * sizeof(float) + } + }; auto& vao = stars_model->get_vertex_array(); + vao = std::make_unique(star_vertex_attributes); - // Resize model VBO and upload vertex data - vbo->resize(star_vertex_data.size() * sizeof(float), std::as_bytes(std::span{star_vertex_data})); - - std::size_t attribute_offset = 0; - - // Define position vertex attribute - gl::vertex_attribute position_attribute; - position_attribute.buffer = vbo.get(); - position_attribute.offset = attribute_offset; - position_attribute.stride = star_vertex_stride; - position_attribute.type = gl::vertex_attribute_type::float_32; - position_attribute.components = 3; - attribute_offset += position_attribute.components * sizeof(float); - - // Define color vertex attribute - gl::vertex_attribute color_attribute; - color_attribute.buffer = vbo.get(); - color_attribute.offset = attribute_offset; - color_attribute.stride = star_vertex_stride; - color_attribute.type = gl::vertex_attribute_type::float_32; - color_attribute.components = 4; - //attribute_offset += color_attribute.components * sizeof(float); - - // Bind vertex attributes to VAO - vao->bind(render::vertex_attribute::position, position_attribute); - vao->bind(render::vertex_attribute::color, color_attribute); - - // Load star material - std::shared_ptr star_material = ctx.resource_manager->load("fixed-star.mtl"); + // Construct stars VBO + auto& vbo = stars_model->get_vertex_buffer(); + vbo = std::make_unique(gl::buffer_usage::static_draw, std::as_bytes(std::span{star_vertex_data})); + stars_model->set_vertex_offset(0); + stars_model->set_vertex_stride(star_vertex_stride); - // Create model group + // Construct star model group stars_model->get_groups().resize(1); render::model_group& stars_model_group = stars_model->get_groups().front(); - stars_model_group.id = "stars"; - stars_model_group.material = star_material; - stars_model_group.drawing_mode = gl::drawing_mode::points; - stars_model_group.start_index = 0; - stars_model_group.index_count = static_cast(star_count); + stars_model_group.material = ctx.resource_manager->load("fixed-star.mtl"); + stars_model_group.primitive_topology = gl::primitive_topology::point_list; + stars_model_group.first_vertex = 0; + stars_model_group.vertex_count = static_cast(star_count); // Pass stars model to sky pass ctx.sky_pass->set_stars_model(stars_model); @@ -344,12 +327,12 @@ void create_stars(::game& ctx) // Pass starlight illuminance to astronomy system ctx.astronomy_system->set_starlight_illuminance(starlight_illuminance); - debug::log::trace("Generated fixed stars"); + debug::log_trace("Generated fixed stars"); } void create_sun(::game& ctx) { - debug::log::trace("Generating Sun..."); + debug::log_trace("Generating Sun..."); { // Create sun entity @@ -374,12 +357,12 @@ void create_sun(::game& ctx) ctx.astronomy_system->set_sun_light(ctx.sun_light.get()); } - debug::log::trace("Generated Sun"); + debug::log_trace("Generated Sun"); } void create_earth_moon_system(::game& ctx) { - debug::log::trace("Generating Earth-Moon system..."); + debug::log_trace("Generating Earth-Moon system..."); { // Create Earth-Moon barycenter entity @@ -394,12 +377,12 @@ void create_earth_moon_system(::game& ctx) create_moon(ctx); } - debug::log::trace("Generated Earth-Moon system"); + debug::log_trace("Generated Earth-Moon system"); } void create_earth(::game& ctx) { - debug::log::trace("Generating Earth..."); + debug::log_trace("Generating Earth..."); { // Create earth entity @@ -411,12 +394,12 @@ void create_earth(::game& ctx) ctx.entity_registry->get<::orbit_component>(earth_eid).parent = ctx.entities["em_bary"]; } - debug::log::trace("Generated Earth"); + debug::log_trace("Generated Earth"); } void create_moon(::game& ctx) { - debug::log::trace("Generating Moon..."); + debug::log_trace("Generating Moon..."); { // Create lunar entity @@ -440,12 +423,12 @@ void create_moon(::game& ctx) ctx.astronomy_system->set_moon_light(ctx.moon_light.get()); } - debug::log::trace("Generated Moon"); + debug::log_trace("Generated Moon"); } void enter_ecoregion(::game& ctx, const ecoregion& ecoregion) { - debug::log::trace("Entering ecoregion {}...", ecoregion.name); + debug::log_trace("Entering ecoregion {}...", ecoregion.name); { // Set active ecoregion //ctx.active_ecoregion = &ecoregion; @@ -484,7 +467,7 @@ void enter_ecoregion(::game& ctx, const ecoregion& ecoregion) // ); } - debug::log::trace("Entered ecoregion {}", ecoregion.name); + debug::log_trace("Entered ecoregion {}", ecoregion.name); } void switch_scene(::game& ctx)