some decouplings (#2)

* logger business

* code review

* rm c-style array

* extract image rendering to view

* missing view files

* init view abstractions

* leverage concepts and provide a templateized Enumerate function

* RVO

* get rid of some warnings

* logger business

* code review

* rm c-style array

* extract image rendering to view

* missing view files

* init view abstractions

* leverage concepts and provide a templateized Enumerate function

* RVO

* get rid of some warnings

* logger business

* code review

* rm c-style array

* init view abstractions

* leverage concepts and provide a templateized Enumerate function

* RVO

* get rid of some warnings

* logger business

* code review

* rm c-style array

* init view abstractions

* leverage concepts and provide a templateized Enumerate function

* RVO

* get rid of some warnings

* Factor out AppGLFW base class

The intention here is to get rid of scaffolding in the consumer
application class and allow to focus on the "important bits".

* Some cleanup

* dump unnecessary dependency

* link stuff where needed & use only what's needed

* remove deprecation warnings

* add gallery toggle

* make some includes private

* add presets

* remove dir locals, use presets instead

`projectile-configure-project' in conjunction with CMakePresets.json
will allow to configure the project.
`projectile-compile-project' does sth similar for the compile command.

This is equivalent to something like
~cmake --preset clang-debug && cmake --build build --preset clang-debug~

* use presets in pipeline

---------

Co-authored-by: m-aXimilian <keuffnermax@gmail.com>
This commit is contained in:
m-aXimilian
2025-08-18 22:39:43 +00:00
committed by GitHub
parent 566dd112ff
commit d71f4168fb
30 changed files with 548 additions and 259 deletions
+1
View File
@@ -2,3 +2,4 @@ add_subdirectory(utilities)
add_subdirectory(imaging)
add_subdirectory(rendering)
add_subdirectory(resources)
add_subdirectory(app)
+198
View File
@@ -0,0 +1,198 @@
#include "AppGLFW.hpp"
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include "app_resources_default.h"
static void glfw_error_callback(int error, const char* description)
{
fprintf(stderr, "GLFW Error %d: %s\n", error, description);
}
void pixelarium::application::AppGLFW::InitMainWindow()
{
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit())
{
return;
}
// Decide GL+GLSL versions
#if defined(IMGUI_IMPL_OPENGL_ES2)
// GL ES 2.0 + GLSL 100
const char* glsl_version = "#version 100";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
#elif defined(__APPLE__)
// GL 3.2 + GLSL 150
const char* glsl_version = "#version 150";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
#else
// GL 3.0 + GLSL 130
#ifdef __linux__
const char* glsl_version = "#version 130";
#else
const char* glsl_version = reinterpret_cast<const char*>("#version 130");
#endif
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
// glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+
// only glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only
#endif
// int count;
// GLFWmonitor** monitors = glfwGetMonitors(&count); // at [0] is always the
// main monitor GLFWmonitor* monitor = monitors[1];
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
int xpos, ypos, width, height;
glfwGetMonitorWorkarea(monitor, &xpos, &ypos, &width, &height);
// lg::Logger::Info("screen width " + std::to_string(width) +
// " screen heigth " + std::to_string(height));
// Create window with graphics context
window = glfwCreateWindow(1200, 800, PIXELARIUM_TITLE, nullptr, nullptr);
if (window == nullptr)
{
// lg::Logger::Error("no window");
return;
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1); // Enable vsync
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
(void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform
// Windows
// io.ConfigViewportsNoAutoMerge = true;
// io.ConfigViewportsNoTaskBarIcon = true;
// Setup Dear ImGui style
ImGui::StyleColorsDark();
ImGuiStyle& style = ImGui::GetStyle();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
style.WindowRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
}
// Setup Platform/Renderer backends
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
}
int pixelarium::application::AppGLFW::RunLoop()
{
ImGuiIO& io = ImGui::GetIO();
(void)io;
while (!glfwWindowShouldClose(this->window))
{
glfwPollEvents();
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGui::DockSpaceOverViewport(ImGui::GetID("Backspace"));
this->MenuBar();
this->Run();
// Rendering
ImGui::Render();
int display_w, display_h;
glfwGetFramebufferSize(this->window, &display_w, &display_h);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
GLFWwindow* backup_current_context = glfwGetCurrentContext();
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
glfwMakeContextCurrent(backup_current_context);
}
glfwSwapBuffers(this->window);
}
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(this->window);
glfwTerminate();
return 0;
}
void pixelarium::application::AppGLFW::MenuBar()
{
if (ImGui::BeginMainMenuBar())
{
// main menu
if (ImGui::BeginMenu(MAINMENUNAME))
{
if (ImGui::BeginCombo(LOGLEVELSELECT, LOGLEVELS[log_level_].data()))
{
for (int n = 0; n < static_cast<int>(LOGLEVELS.size()); n++)
{
bool is_selected = (LOGLEVELS[log_level_] == LOGLEVELS[n]);
if (ImGui::Selectable(LOGLEVELS[n].data(), is_selected))
{
log_level_ = n;
this->logger_.ChangeLevel(static_cast<utils::log::LogLevel>(1 << log_level_));
}
if (is_selected) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
// consumer main menu bar entries
this->MenuBarOptionsColumn1();
ImGui::EndMenu();
}
// consumer menu bar columns
this->MenuBarOptionsColumn2();
this->MenuBarOptionsColumn3();
this->MenuBarOptionsColumn4();
this->MenuBarOptionsColumn5();
ImGui::EndMainMenuBar();
}
}
void pixelarium::application::AppGLFW::LogLevelSelect()
{
if (ImGui::BeginCombo(LOGLEVELSELECT, LOGLEVELS[log_level_].data()))
{
for (int n = 0; n < static_cast<int>(LOGLEVELS.size()); n++)
{
bool is_selected = (LOGLEVELS[log_level_] == LOGLEVELS[n]);
if (ImGui::Selectable(LOGLEVELS[n].data(), is_selected))
{
log_level_ = n;
this->logger_.ChangeLevel(static_cast<utils::log::LogLevel>(1 << log_level_));
}
if (is_selected) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <GLFW/glfw3.h>
#include <memory>
#include "imgui.h"
#include "utilities/ILog.hpp"
namespace pixelarium::application
{
class AppGLFW
{
public:
explicit AppGLFW(const utils::log::ILog& log) : logger_(log) { this->InitMainWindow(); }
void Start() { this->RunLoop(); }
protected:
virtual void MenuBarOptionsColumn1() {}
virtual void MenuBarOptionsColumn2() {}
virtual void MenuBarOptionsColumn3() {}
virtual void MenuBarOptionsColumn4() {}
virtual void MenuBarOptionsColumn5() {}
virtual void Run() {}
const utils::log::ILog& logger_;
private:
int RunLoop();
void InitMainWindow();
void MenuBar();
void LogLevelSelect();
int log_level_{0};
GLFWwindow* window = nullptr;
};
} // namespace pixelarium::application
+44
View File
@@ -0,0 +1,44 @@
set(PIXELARIUM_TITLE ${CMAKE_PROJECT_NAME})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/app_resources_default.h.in
${CMAKE_BINARY_DIR}/app_resources_default.h @ONLY)
set(APPLIBSRC
AppGLFW.cpp
${imgui_DIR}/imgui.cpp
${imgui_DIR}/imgui_demo.cpp
${imgui_DIR}/imgui_draw.cpp
${imgui_DIR}/imgui_tables.cpp
${imgui_DIR}/imgui_widgets.cpp
${imgui_DIR}/backends/imgui_impl_opengl3.cpp
${imgui_DIR}/backends/imgui_impl_glfw.cpp)
set(APPLIBNAME pixelariumapplicationlib)
add_library(${APPLIBNAME}
STATIC ${APPLIBSRC})
target_link_libraries(${APPLIBNAME}
PRIVATE pixelariumutilslib)
# This needs to be public to let the consumer know about it.
if(WIN32)
target_link_libraries(${APPLIBNAME}
PUBLIC opengl32.lib
PUBLIC glfw
PUBLIC OpenGL::GL)
endif()
if(LINUX)
target_link_libraries(${APPLIBNAME}
PUBLIC glfw GL)
endif()
if(APPLE)
target_link_libraries(${APPLIBNAME}
PUBLIC glfw
PUBLIC "-framework OpenGL")
endif()
target_include_directories(${APPLIBNAME}
PRIVATE ${CMAKE_BINARY_DIR}
PRIVATE ${PROJECT_SOURCE_DIR}/lib
PUBLIC ${imgui_DIR}
PUBLIC ${imgui_DIR}/backends)
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <array>
#include <string_view>
#cmakedefine PIXELARIUM_TITLE "@PIXELARIUM_TITLE@"
#define MAINMENUNAME "Menu"
#define FILEMENUNAME "File"
#define LOGLEVELSELECT "Log Level"
inline constexpr std::array<std::string_view, 5> LOGLEVELS = {"Trace", "Debug", "Info", "Warning", "Error"};
+6 -2
View File
@@ -14,8 +14,12 @@ class PixelariumImage
// get back the defaults
PixelariumImage() = default;
// we cannot copy an Image since this conflicts with the _img field
PixelariumImage(const PixelariumImage& other) = delete;
PixelariumImage(const PixelariumImage& other)
{
// be ware!!
// we make a copy of the image data here!
img_ = std::make_unique<cv::Mat>(*other.img_);
};
PixelariumImage(PixelariumImage&& other) noexcept
: img_(std::move(other.img_)) {}
// requires a copy ctor which we don't have
+2 -2
View File
@@ -61,8 +61,8 @@ GLuint pixelarium::render::CvMatRender::uploadTexture()
glBindTexture(GL_TEXTURE_2D, this->texture_);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+1 -1
View File
@@ -31,7 +31,7 @@ class CvMatRender
CvMatRender(const CvMatRender&) = delete;
CvMatRender(CvMatRender&&) = delete;
CvMatRender& operator=(CvMatRender&) = default;
CvMatRender& operator=(CvMatRender&&) = default;
CvMatRender& operator=(CvMatRender&& other) = default;
~CvMatRender();
explicit CvMatRender(const std::shared_ptr<pixelarium::imaging::PixelariumImage>& img);
+1
View File
@@ -1,6 +1,7 @@
#include "resource.hpp"
#include <atomic>
#include <functional>
#include <optional>
using pixelarium::imaging::PixelariumImage;
+19 -6
View File
@@ -1,5 +1,6 @@
#pragma once
#include <concepts>
#include <functional>
#include <memory>
#include <optional>
@@ -17,17 +18,17 @@ struct IResource
template <typename R>
concept ResT = requires(R& r) { static_cast<IResource&>(r); };
// template <ResT R>
template <typename R>
template <typename ResT>
class IResourcePool
{
public:
virtual ~IResourcePool() = default;
virtual std::optional<const R*> GetResource(size_t id) const = 0;
virtual size_t SetResource(std::unique_ptr<R> res) = 0;
virtual bool ModifyResource(size_t id, std::unique_ptr<R> res) = 0;
virtual std::optional<const ResT*> GetResource(size_t id) const = 0;
virtual size_t SetResource(std::unique_ptr<ResT> res) = 0;
virtual bool ModifyResource(size_t id, std::unique_ptr<ResT> res) = 0;
virtual bool DeleteResource(size_t id) = 0;
virtual void EnumerateResources(const std::function<void(size_t, const R&)>& func) = 0;
virtual void EnumerateResources(const std::function<void(size_t, const ResT&)>& func) = 0;
virtual size_t GetTotalSize() const = 0;
};
// Now with the =GetResource= method, I do not want to transfer ownership to the caller of that method. The ownership
@@ -52,6 +53,18 @@ class ImageResourcePool : public IResourcePool<imaging::PixelariumImage>
void EnumerateResources(const std::function<void(size_t, const imaging::PixelariumImage&)>& func) override;
template <typename Callable>
requires std::invocable<Callable, size_t, const imaging::PixelariumImage&>
void Enumerate(Callable&& func) const
{
for (const auto& e : this->resources_)
{
func(e.first, *e.second);
}
}
size_t GetTotalSize() const override { return resources_.size(); }
private:
std::unordered_map<size_t, std::unique_ptr<imaging::PixelariumImage>> resources_;
};
+5 -5
View File
@@ -14,11 +14,11 @@ enum class LogLevel
class ILog
{
public:
virtual void Info(const std::string& msg) = 0;
virtual void Debug(const std::string& msg) = 0;
virtual void Warn(const std::string& msg) = 0;
virtual void Error(const std::string& msg) = 0;
virtual void ChangeLevel(LogLevel lvl) = 0;
virtual void Info(const std::string& msg) const = 0;
virtual void Debug(const std::string& msg) const = 0;
virtual void Warn(const std::string& msg) const = 0;
virtual void Error(const std::string& msg) const = 0;
virtual void ChangeLevel(LogLevel lvl) const = 0;
virtual ~ILog() {}
};
+30 -17
View File
@@ -1,12 +1,13 @@
#include "SpdLogger.hpp"
#include <spdlog/common.h>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/spdlog.h>
#include <memory>
#include <string>
#include "ILog.hpp"
#include "ILog.hpp"
using namespace pixelarium::utils::log;
@@ -18,22 +19,32 @@ SpdLogger::SpdLogger(const std::string& file_sink, const std::string& name)
logger_->info("Logger initiated");
}
void SpdLogger::Info(const std::string& msg)
{
this->logger_->info(msg);
}
void SpdLogger::Debug(const std::string& msg)
{
this->logger_->debug(msg);
}
void SpdLogger::Warn(const std::string& msg)
{
this->logger_->warn(msg);
}
void SpdLogger::Error(const std::string& msg) { this->logger_->error(msg); }
void SpdLogger::Info(const std::string& msg) const { this->logger_->info(msg); }
void SpdLogger::Debug(const std::string& msg) const { this->logger_->debug(msg); }
void SpdLogger::Warn(const std::string& msg) const { this->logger_->warn(msg); }
void SpdLogger::Error(const std::string& msg) const { this->logger_->error(msg); }
void SpdLogger::ChangeLevel(LogLevel lvl)
void SpdLogger::ChangeLevel(LogLevel lvl) const
{
constexpr auto LogLevelToString = [](LogLevel l) -> const char*
{
switch (l)
{
case LogLevel::Trace:
return "Trace";
case LogLevel::Debug:
return "Debug";
case LogLevel::Info:
return "Info";
case LogLevel::Warn:
return "Warn";
case LogLevel::Error:
return "Error";
default:
return "Not Found";
}
};
switch (lvl)
{
case LogLevel::Trace:
@@ -58,5 +69,7 @@ void SpdLogger::ChangeLevel(LogLevel lvl)
spdlog::flush_on(spdlog::level::debug);
}
this->logger_->debug("Changed log level;");
// you will only get this message for log levels <= info! I.e., not for error or warning.
this->logger_->info(
std::format("{}: Changed log level to {}({})", __FUNCTION__, LogLevelToString(lvl), static_cast<int>(lvl)));
}
+5 -5
View File
@@ -13,11 +13,11 @@ class SpdLogger : public ILog
public:
explicit SpdLogger(const std::string& file_sink, const std::string& name);
void Info(const std::string& msg) override;
void Debug(const std::string& msg) override;
void Warn(const std::string& msg) override;
void Error(const std::string& msg) override;
void ChangeLevel(LogLevel lvl) override;
void Info(const std::string& msg) const override;
void Debug(const std::string& msg) const override;
void Warn(const std::string& msg) const override;
void Error(const std::string& msg) const override;
void ChangeLevel(LogLevel lvl) const override;
private:
std::shared_ptr<spdlog::logger> logger_;