From 6cc8bf42629ff163acc1935a194852fd61c7df0c Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sun, 4 Oct 2026 01:03:47 -0700 Subject: [PATCH 01/10] adding in sandbox --- CMakeLists.txt | 1 + bin/dev | 14 ++++++++++++++ engine/src/window.hpp | 2 +- sandbox/CMakeLists.txt | 22 ++++++++++++++++++++++ sandbox/main.cpp | 28 ++++++++++++++++++++++++++++ sandbox/shaders/.gitkeep | 0 6 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 sandbox/CMakeLists.txt create mode 100644 sandbox/main.cpp create mode 100644 sandbox/shaders/.gitkeep diff --git a/CMakeLists.txt b/CMakeLists.txt index 89400a2..2170f36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,4 +39,5 @@ add_subdirectory(third_party) add_subdirectory(engine) add_subdirectory(editor) add_subdirectory(testbed) +add_subdirectory(sandbox) add_subdirectory(examples) diff --git a/bin/dev b/bin/dev index ccd8c2c..b1d1455 100755 --- a/bin/dev +++ b/bin/dev @@ -80,6 +80,20 @@ class DevCommand(GiteaCommand, RenderDocCommand, ShadersCommand, TracyCommand): os.chdir(preset_dir) os.execv(binary, [binary, *args]) + ## + # Builds the debug preset (incrementally), then runs the raw Vulkan + # sandbox from build/debug. Any arguments are passed through. + # dev sandbox [args...] + def sandbox(self, *args, **kwargs): + preset = self.DEFAULT_PRESET + self._build_preset(preset, fresh=False) + + preset_dir = os.path.join(self._project_root(), "build", preset) + binary = os.path.join(preset_dir, "bin", "sandbox") + print(f"Running: {shlex.join([binary, *args])}") + os.chdir(preset_dir) + os.execv(binary, [binary, *args]) + # Custom helpers def ownit(self, *args, **kwargs): diff --git a/engine/src/window.hpp b/engine/src/window.hpp index 672215d..29da336 100644 --- a/engine/src/window.hpp +++ b/engine/src/window.hpp @@ -15,7 +15,7 @@ class Window { Window(const Window&) = delete; Window& operator=(const Window&) = delete; - Window(int width, int height, const std::string& name = "sophiahaestus"); + Window(int width, int height, const std::string& name = "sophia"); ~Window(); bool shouldClose() { return glfwWindowShouldClose(window); } diff --git a/sandbox/CMakeLists.txt b/sandbox/CMakeLists.txt new file mode 100644 index 0000000..0dd2b8e --- /dev/null +++ b/sandbox/CMakeLists.txt @@ -0,0 +1,22 @@ +# Vulkan playground. Links the engine (and its third-party libraries), and +# can also include the engine's internal headers from engine/src. +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS *.cpp) + +add_executable(sandbox ${SOURCES}) + +target_compile_definitions(sandbox PRIVATE + SANDBOX_SHADER_DIR="${CMAKE_CURRENT_SOURCE_DIR}/shaders" +) + +# engine/include comes in through the engine's public include path. +target_include_directories(sandbox PRIVATE + "${CMAKE_SOURCE_DIR}/engine/src" +) + +# The engine publicly provides vulkan, vma, shaderc, glfw, glm, imgui, entt +# and Tracy, plus the GLM defines. The rest are engine-private. +target_link_libraries(sandbox PRIVATE + ${PROJECT_NAME} + imguizmo + stb_image +) diff --git a/sandbox/main.cpp b/sandbox/main.cpp new file mode 100644 index 0000000..b8415e1 --- /dev/null +++ b/sandbox/main.cpp @@ -0,0 +1,28 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VK_CHECK(expr) \ + do { \ + vk::Result result_ = (expr); \ + if (result_ != vk::Result::eSuccess) { \ + std::fprintf(stderr, "%s:%d: %s -> %d\n", __FILE__, __LINE__, #expr, \ + result_); \ + std::abort(); \ + } \ + } while (0) + +constexpr uint32_t WIDTH = 1280; +constexpr uint32_t HEIGHT = 720; +constexpr const char* NAME = ""; + +int main() { return EXIT_SUCCESS; } diff --git a/sandbox/shaders/.gitkeep b/sandbox/shaders/.gitkeep new file mode 100644 index 0000000..e69de29 From 31089f353922602ee5c0e31c0cc2d00056051764 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sun, 4 Oct 2026 22:41:42 -0700 Subject: [PATCH 02/10] adding fmt third_party library + adding cloc support --- .clocignore | 8 ++++++++ .gitmodules | 3 +++ bin/dev | 7 +++++++ third_party/fmt | 1 + 4 files changed, 19 insertions(+) create mode 100644 .clocignore create mode 160000 third_party/fmt diff --git a/.clocignore b/.clocignore new file mode 100644 index 0000000..54aa02d --- /dev/null +++ b/.clocignore @@ -0,0 +1,8 @@ +third_party +build +__dump +traces +.vscode +.claude +.git +imgui.ini diff --git a/.gitmodules b/.gitmodules index c4d9b71..8b6e843 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ [submodule "third_party/ImGuizmo"] path = third_party/ImGuizmo url = https://github.com/CedricGuillemet/ImGuizmo.git +[submodule "third_party/fmt"] + path = third_party/fmt + url = https://github.com/fmtlib/fmt diff --git a/bin/dev b/bin/dev index b1d1455..74bde17 100755 --- a/bin/dev +++ b/bin/dev @@ -96,6 +96,13 @@ class DevCommand(GiteaCommand, RenderDocCommand, ShadersCommand, TracyCommand): # Custom helpers + ## + # Counts lines of code from the project root, skipping paths listed in + # .clocignore. Any arguments are passed through to cloc. + # dev cloc [args...] + def cloc(self, *args, **kwargs): + self._run_in_root(["cloc", "--exclude-list-file=.clocignore", *args, "."]) + def ownit(self, *args, **kwargs): file_or_directory = args[0] if args else None if file_or_directory is None: diff --git a/third_party/fmt b/third_party/fmt new file mode 160000 index 0000000..4afd0e4 --- /dev/null +++ b/third_party/fmt @@ -0,0 +1 @@ +Subproject commit 4afd0e498e9e1dd68775cc52087df774a19c761f From 9538add3acb53b9ef428b75050045918ffd3cb14 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sun, 4 Oct 2026 22:43:09 -0700 Subject: [PATCH 03/10] Using version 1.12.0 of fmt in project --- third_party/fmt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/fmt b/third_party/fmt index 4afd0e4..1be298e 160000 --- a/third_party/fmt +++ b/third_party/fmt @@ -1 +1 @@ -Subproject commit 4afd0e498e9e1dd68775cc52087df774a19c761f +Subproject commit 1be298e1bd68957e4cd352e1f676f00e07dcfb57 From 3c3fcfdd6bbde16d1baf0cee0504d706c6d720c9 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sun, 4 Oct 2026 22:52:07 -0700 Subject: [PATCH 04/10] removing fmt --- .gitmodules | 3 --- third_party/fmt | 1 - 2 files changed, 4 deletions(-) delete mode 160000 third_party/fmt diff --git a/.gitmodules b/.gitmodules index 8b6e843..c4d9b71 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,6 +14,3 @@ [submodule "third_party/ImGuizmo"] path = third_party/ImGuizmo url = https://github.com/CedricGuillemet/ImGuizmo.git -[submodule "third_party/fmt"] - path = third_party/fmt - url = https://github.com/fmtlib/fmt diff --git a/third_party/fmt b/third_party/fmt deleted file mode 160000 index 1be298e..0000000 --- a/third_party/fmt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1be298e1bd68957e4cd352e1f676f00e07dcfb57 From b31c12069166a213736131eca7addb2c9b1bffee Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sun, 4 Oct 2026 22:54:07 -0700 Subject: [PATCH 05/10] bumping project cxx version to 23 --- CMakeLists.txt | 2 +- editor/CMakeLists.txt | 2 +- testbed/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2170f36..9a6cf71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ set(NAME sophia) project(${NAME} VERSION 0.0.0) enable_language(CXX) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index cf177ec..5d2c12b 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -10,7 +10,7 @@ target_link_libraries(${NAME}_deps INTERFACE imguizmo ) -target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23) set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/build") add_executable(editor ${SOURCES}) diff --git a/testbed/CMakeLists.txt b/testbed/CMakeLists.txt index abf111d..ad9b25c 100644 --- a/testbed/CMakeLists.txt +++ b/testbed/CMakeLists.txt @@ -9,7 +9,7 @@ target_link_libraries(${NAME}_deps INTERFACE ${ENGINE_NAME} ) -target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23) set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/build") add_executable(testbed main.cpp) From b6098d4dc658d56ae420f6783c0160e154f1b1e4 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sun, 4 Oct 2026 23:14:17 -0700 Subject: [PATCH 06/10] New logger --- editor/utilities/logger.hpp | 9 +- engine/include/sophia/event.hpp | 10 +- engine/include/sophia/ui/panel_registry.hpp | 2 +- engine/include/sophia/util/assert.hpp | 20 ++- engine/include/sophia/util/delegate.hpp | 13 +- engine/include/sophia/util/logger.hpp | 168 +++++++++--------- engine/include/sophia/util/pool.hpp | 11 +- engine/src/application.cpp | 13 +- engine/src/renderer/mesh_render_system.cpp | 5 +- engine/src/renderer/render_context.cpp | 4 +- engine/src/renderer/render_graph.cpp | 16 +- engine/src/renderer/sprite_render_system.cpp | 5 +- .../src/resources/resource_registry_ref.cpp | 2 +- engine/src/vulkan/access.cpp | 2 +- engine/src/vulkan/device.cpp | 33 ++-- engine/src/vulkan/shader.cpp | 16 +- engine/src/vulkan/swapchain.cpp | 2 +- engine/src/vulkan/vk_check.hpp | 20 +++ sandbox/main.cpp | 10 -- 19 files changed, 191 insertions(+), 170 deletions(-) create mode 100644 engine/src/vulkan/vk_check.hpp diff --git a/editor/utilities/logger.hpp b/editor/utilities/logger.hpp index 846896f..f42ac22 100644 --- a/editor/utilities/logger.hpp +++ b/editor/utilities/logger.hpp @@ -1,8 +1,13 @@ #pragma once +#include #include +#include template -void log(Args... args) { - sophia::log::print("\033[1;38;2;170;90;255m[editor]\033[0m", args...); +void log(std::format_string fmt, Args&&... args) { + if constexpr (sophia::log::enabled) { + sophia::log::print("\033[1;38;2;170;90;255m[editor]\033[0m {}", + std::format(fmt, std::forward(args)...)); + } } diff --git a/engine/include/sophia/event.hpp b/engine/include/sophia/event.hpp index 213fa54..2302e4f 100644 --- a/engine/include/sophia/event.hpp +++ b/engine/include/sophia/event.hpp @@ -83,7 +83,7 @@ class EventSystem { this->eventDelegates[eventType] = std::make_unique(EventDelegate()); - log::trace("EventSystem creating new event type: ", eventType); + log::trace("EventSystem creating new event type: {}", eventType); } EventDelegate& delegate = @@ -102,7 +102,7 @@ class EventSystem { this->eventDelegates[eventType] = std::make_unique(EventDelegate()); - log::trace("EventSystem creating new event type: ", eventType); + log::trace("EventSystem creating new event type: {}", eventType); } EventDelegate& delegate = @@ -124,12 +124,14 @@ class EventSystem { std::any_cast&>(*this->eventDelegates.at(eventType)); delegate.invoke(event); } catch (const std::out_of_range& error) { - log::error("event dispatch failed: ", event.toString()); + log::error("event dispatch failed: {}", event.toString()); } } private: - EventSystem() { log::info("EventSystem Initialized: ", this); } + EventSystem() { + log::info("EventSystem Initialized: {}", static_cast(this)); + } ~EventSystem() { log::info("EventSystem Destroyed"); } std::unordered_map> eventDelegates; diff --git a/engine/include/sophia/ui/panel_registry.hpp b/engine/include/sophia/ui/panel_registry.hpp index 5836fee..408a248 100644 --- a/engine/include/sophia/ui/panel_registry.hpp +++ b/engine/include/sophia/ui/panel_registry.hpp @@ -38,7 +38,7 @@ class PanelRegistry { const T& get(PanelHandle handle) const { const Panel& panel = get(handle); SOPHIA_ASSERT(dynamic_cast(&panel) != nullptr, - "PanelHandle does not refer to a", typeid(T).name()); + "PanelHandle does not refer to a {}", typeid(T).name()); return static_cast(panel); } diff --git a/engine/include/sophia/util/assert.hpp b/engine/include/sophia/util/assert.hpp index 5bc0363..3b66620 100644 --- a/engine/include/sophia/util/assert.hpp +++ b/engine/include/sophia/util/assert.hpp @@ -1,7 +1,9 @@ /** * Usage: * SOPHIA_ASSERT(pipelineLayout != nullptr); - * SOPHIA_ASSERT(index < count, "index", index, "out of bounds:", count); + * SOPHIA_ASSERT(index < count, "index {} out of bounds: {}", index, count); + * + * The optional message is a std::format string, checked at compile time. * * Compiled out entirely in release (NDEBUG) builds, matching 's * assert() semantics: don't rely on side effects inside the condition. @@ -10,16 +12,18 @@ #pragma once #include +#include #include #ifndef NDEBUG -#define SOPHIA_ASSERT(condition, ...) \ - do { \ - if (!(condition)) { \ - ::sophia::log::fatal("Assertion failed:", #condition, "at", __FILE__, \ - ":", __LINE__ __VA_OPT__(, ) __VA_ARGS__); \ - std::abort(); \ - } \ +#define SOPHIA_ASSERT(condition, ...) \ + do { \ + if (!(condition)) { \ + ::sophia::log::fatal( \ + "Assertion failed: {} at {}:{}" __VA_OPT__(" - {}"), #condition, \ + __FILE__, __LINE__ __VA_OPT__(, ::std::format(__VA_ARGS__))); \ + std::abort(); \ + } \ } while (false) #else #define SOPHIA_ASSERT(condition, ...) ((void)0) diff --git a/engine/include/sophia/util/delegate.hpp b/engine/include/sophia/util/delegate.hpp index c526c06..335498b 100644 --- a/engine/include/sophia/util/delegate.hpp +++ b/engine/include/sophia/util/delegate.hpp @@ -2,13 +2,12 @@ #include #include +#include #include #include #include #include -#include - #define DEFAULT_DELEGATE_FUNCTOR_ALLOC 5 namespace sophia { @@ -39,7 +38,9 @@ class expired_weak_object : public std::runtime_error { template std::function createWeakFunction(void (O::*method)(A...), std::shared_ptr obj) { - if (!obj) { throw std::invalid_argument("method and obj must not be null"); } + if (!obj) { + throw std::invalid_argument("method and obj must not be null"); + } std::weak_ptr weakObj = obj; @@ -82,7 +83,7 @@ class Delegate { try { this->functors[i](std::forward(args)...); } catch (const expired_weak_object& e) { - log::error("delegate invoke error: ", e.what()); + log::error("delegate invoke error: {}", e.what()); deadFunctorIndexs.push_back(i); } } @@ -97,7 +98,9 @@ class Delegate { void operator=(Functor c) { clear(); - if (c != nullptr) { add(c); } + if (c != nullptr) { + add(c); + } } void operator+=(Functor c) { add(c); } diff --git a/engine/include/sophia/util/logger.hpp b/engine/include/sophia/util/logger.hpp index cc3110d..f492daa 100644 --- a/engine/include/sophia/util/logger.hpp +++ b/engine/include/sophia/util/logger.hpp @@ -1,23 +1,36 @@ /** - * Lots of work still needs to be done in here. + * std::format-style logging. Format strings are checked at compile time. * * Usage: * using namespace sophia; - * log::fatal("fatal message") - * log::info("Infomation message") + * log::fatal("failed to load {}", path) + * log::info("loaded {} meshes in {:.2f}ms", count, ms) + * log::debug("value: {}", x) // prefixed with file:line * log::print("no prefix, just the message") * + * Everything is compiled out in release (NDEBUG) builds. + * * TODO Impl some form of log error handling */ #pragma once -#include -#include +#include +#include +#include +#include +#include +#include #include namespace sophia::log { +#ifndef NDEBUG +inline constexpr bool enabled = true; +#else +inline constexpr bool enabled = false; +#endif // !NDEBUG + enum class LEVEL { NONE, FATAL, @@ -29,118 +42,113 @@ enum class LEVEL { TRACE, }; -// Helper print functions -#define LOG_PRINT_HELPER(MSG) std::cout << MSG -#define LOG_PREFIX_FATAL LOG_PRINT_HELPER("\033[1;91m[FATAL]\033[0m ") -#define LOG_PREFIX_ERROR LOG_PRINT_HELPER("\033[1;31m[ERROR]\033[0m ") -#define LOG_PREFIX_WARNING LOG_PRINT_HELPER("\033[1;33m[WARNING]\033[0m ") -#define LOG_PREFIX_INFO LOG_PRINT_HELPER("\033[32m[INFO]\033[0m ") -#define LOG_PREFIX_VERBOSE LOG_PRINT_HELPER("\033[36m[VERBOSE]\033[0m ") -#define LOG_PREFIX_DEBUG LOG_PRINT_HELPER("\033[1;34m[DEBUG]\033[0m ") -#define LOG_PREFIX_TRACE LOG_PRINT_HELPER("\033[1;90m[TRACE]\033[0m ") +namespace detail { -template -void log(LEVEL level = LEVEL::NONE, Args... args) { -#ifndef NDEBUG +constexpr std::string_view prefix(LEVEL level) { switch (level) { case LEVEL::FATAL: - LOG_PREFIX_FATAL; - break; + return "\033[1;91m[FATAL]\033[0m "; case LEVEL::ERROR: - LOG_PREFIX_ERROR; - break; + return "\033[1;31m[ERROR]\033[0m "; case LEVEL::WARNING: - LOG_PREFIX_WARNING; - break; + return "\033[1;33m[WARNING]\033[0m "; case LEVEL::INFO: - LOG_PREFIX_INFO; - break; + return "\033[32m[INFO]\033[0m "; case LEVEL::VERBOSE: - LOG_PREFIX_VERBOSE; - break; - // case LEVEL::DEBUGGING: - // LOG_PREFIX_DEBUG; - // break; + return "\033[36m[VERBOSE]\033[0m "; + case LEVEL::DEBUGGING: + return "\033[1;34m[DEBUG]\033[0m "; case LEVEL::TRACE: - LOG_PREFIX_TRACE; - break; + return "\033[1;90m[TRACE]\033[0m "; default: - std::cout << "\033[1;90m[LOG]\033[0m "; - break; + return "\033[1;90m[LOG]\033[0m "; } +} - ((std::cout << ' ' << std::forward(args)), ...); - std::cout << std::endl; -#else - (void)level; - (void)std::initializer_list{((void)args, 0)...}; -#endif // !NDEBUG +inline void write(std::string_view prefix, std::string_view message) { + std::println(stdout, "{}{}", prefix, message); + std::fflush(stdout); +} + +// Captures the caller's location alongside the format string, since a +// defaulted std::source_location parameter can't follow a parameter pack. +template +struct FormatWithLocation { + std::format_string fmt; + std::source_location location; + + template + requires std::is_convertible_v + consteval FormatWithLocation( + const S& fmt, + std::source_location location = std::source_location::current()) + : fmt(fmt), location(location) {} +}; + +} // namespace detail + +template +void log(LEVEL level, std::format_string fmt, Args&&... args) { + if constexpr (enabled) { + detail::write(detail::prefix(level), + std::format(fmt, std::forward(args)...)); + } } template -void fatal(Args... args) { - log(LEVEL::FATAL, args...); +void fatal(std::format_string fmt, Args&&... args) { + log(LEVEL::FATAL, fmt, std::forward(args)...); } template -void error(Args... args) { - log(LEVEL::ERROR, args...); +void error(std::format_string fmt, Args&&... args) { + log(LEVEL::ERROR, fmt, std::forward(args)...); } template -void warning(Args... args) { - log(LEVEL::WARNING, args...); +void warning(std::format_string fmt, Args&&... args) { + log(LEVEL::WARNING, fmt, std::forward(args)...); } template -void info(Args... args) { - log(LEVEL::INFO, args...); +void info(std::format_string fmt, Args&&... args) { + log(LEVEL::INFO, fmt, std::forward(args)...); } template -void verbose(Args... args) { - log(LEVEL::VERBOSE, args...); +void verbose(std::format_string fmt, Args&&... args) { + log(LEVEL::VERBOSE, fmt, std::forward(args)...); } template -void debug(const char* file, u16 line, Args... args) { -#ifndef NDEBUG - LOG_PREFIX_DEBUG; - std::cout << file << "::" << line; - ((std::cout << ' ' << std::forward(args)), ...); - std::cout << std::endl; -#else - (void)file; - (void)line; - (void)std::initializer_list{((void)args, 0)...}; -#endif // !NDEBUG +void debug(detail::FormatWithLocation...> fmt, + Args&&... args) { + if constexpr (enabled) { + detail::write( + detail::prefix(LEVEL::DEBUGGING), + std::format("{}:{} {}", fmt.location.file_name(), fmt.location.line(), + std::format(fmt.fmt, std::forward(args)...))); + } } -// trick -#define debug(...) debug(__FILE__, __LINE__, __VA_ARGS__) template -void trace(Args... args) { - log(LEVEL::TRACE, args...); +void trace(std::format_string fmt, Args&&... args) { + log(LEVEL::TRACE, fmt, std::forward(args)...); } -// Like the functions above, but with no "[LEVEL]" prefix: just the arguments, -// separated by spaces, then a newline. With none, an empty line. +// Like the functions above, but with no "[LEVEL]" prefix. With no arguments, +// an empty line. inline void print() { -#ifndef NDEBUG - std::cout << std::endl; -#endif // !NDEBUG + if constexpr (enabled) { + detail::write("", ""); + } } -template -void print(First first, Rest... rest) { -#ifndef NDEBUG - std::cout << first; - ((std::cout << ' ' << rest), ...); - std::cout << std::endl; -#else - (void)first; - (void)std::initializer_list{((void)rest, 0)...}; -#endif // !NDEBUG +template +void print(std::format_string fmt, Args&&... args) { + if constexpr (enabled) { + detail::write("", std::format(fmt, std::forward(args)...)); + } } } // namespace sophia::log diff --git a/engine/include/sophia/util/pool.hpp b/engine/include/sophia/util/pool.hpp index 6ab2416..5cddcac 100644 --- a/engine/include/sophia/util/pool.hpp +++ b/engine/include/sophia/util/pool.hpp @@ -111,15 +111,12 @@ class Pool { }; const Slot& validate(Handle handle) const { - SOPHIA_ASSERT(handle.isValid(), "invalid handle:", name); - SOPHIA_ASSERT(handle.index < slots.size(), name, - "handle index out of range:", handle.index); + SOPHIA_ASSERT(handle.isValid(), "invalid handle: {}", name); + SOPHIA_ASSERT(handle.index < slots.size(), "{} handle index out of range: {}", name, static_cast(handle.index)); const Slot& slot = slots[handle.index]; - SOPHIA_ASSERT(slot.generation == handle.generation, "stale", name, - "handle (generation mismatch):", handle.index); - SOPHIA_ASSERT(slot.item != nullptr, name, - "handle refers to a released item:", handle.index); + SOPHIA_ASSERT(slot.generation == handle.generation, "stale {} handle (generation mismatch): {}", name, static_cast(handle.index)); + SOPHIA_ASSERT(slot.item != nullptr, "{} handle refers to a released item: {}", name, static_cast(handle.index)); return slot; } diff --git a/engine/src/application.cpp b/engine/src/application.cpp index 51d3b2d..f85118f 100644 --- a/engine/src/application.cpp +++ b/engine/src/application.cpp @@ -35,17 +35,6 @@ namespace sophia { namespace { -struct Vertex { - glm::vec2 position; - glm::vec3 color; -}; - -struct PushConstants { - glm::mat4 transform; - glm::vec4 color; - glm::vec4 data; -}; - std::vector commandLineArgs; constexpr ClearColor CLEAR_COLOR = {0.01f, 0.01f, 0.02f, 1.0f}; @@ -287,7 +276,7 @@ class Application::Impl { auto endTime = std::chrono::high_resolution_clock::now(); double totalRuntime = std::chrono::duration(endTime - startTime).count(); - log::info("Application ran for", totalRuntime, "s"); + log::info("Application ran for {:.2f}s", totalRuntime); } void pushMesh(std::vector vertices, std::vector indices) { diff --git a/engine/src/renderer/mesh_render_system.cpp b/engine/src/renderer/mesh_render_system.cpp index 71f19ed..7df390c 100644 --- a/engine/src/renderer/mesh_render_system.cpp +++ b/engine/src/renderer/mesh_render_system.cpp @@ -43,8 +43,9 @@ void MeshRenderSystem::render(vk::CommandBuffer commandBuffer, u32 available = MAX_INSTANCES_PER_FRAME - this->instanceCursor; if (instances.size() > available) { - log::warning("MeshRenderSystem: frame instance buffer full, dropping", - instances.size() - available, "meshes"); + log::warning( + "MeshRenderSystem: frame instance buffer full, dropping {} meshes", + instances.size() - available); } Buffer& buffer = *this->instanceBuffers[this->frameIndex]; diff --git a/engine/src/renderer/render_context.cpp b/engine/src/renderer/render_context.cpp index e5c39b0..5ea487a 100644 --- a/engine/src/renderer/render_context.cpp +++ b/engine/src/renderer/render_context.cpp @@ -38,7 +38,7 @@ vk::CommandBuffer RenderContext::beginFrame() { } if (fenceResult != vk::Result::eSuccess) { - log::error("failed to wait for fence: " + vk::to_string(fenceResult)); + log::error("failed to wait for fence: {}", vk::to_string(fenceResult)); } vk::Semaphore imageAvailable = frame.imageAvailable.get(); @@ -62,7 +62,7 @@ vk::CommandBuffer RenderContext::beginFrame() { imageInFlight, vk::True, std::numeric_limits::max()); if (fenceResult != vk::Result::eSuccess) { - log::error("failed to wait for fence: " + vk::to_string(fenceResult)); + log::error("failed to wait for fence: {}", vk::to_string(fenceResult)); } } diff --git a/engine/src/renderer/render_graph.cpp b/engine/src/renderer/render_graph.cpp index 170bba9..ca970d9 100644 --- a/engine/src/renderer/render_graph.cpp +++ b/engine/src/renderer/render_graph.cpp @@ -106,8 +106,8 @@ void RenderGraph::recordRendering(vk::CommandBuffer commandBuffer, Pass& pass) { const Image& image = *this->images[color.image.index].image; SOPHIA_ASSERT(image.getExtent().width == extent.width && image.getExtent().height == extent.height, - "Pass", pass.name, - "has color attachments of different sizes"); + "Pass {} has color attachments of different sizes", + pass.name); vk::RenderingAttachmentInfo attachment{}; attachment.imageView = image.getView(); @@ -129,8 +129,8 @@ void RenderGraph::recordRendering(vk::CommandBuffer commandBuffer, Pass& pass) { const Image& image = *this->images[pass.depthAttachment->image.index].image; SOPHIA_ASSERT(image.getExtent().width == extent.width && image.getExtent().height == extent.height, - "Pass", pass.name, - "has a depth attachment of a different size"); + "Pass {} has a depth attachment of a different size", + pass.name); depthAttachment.imageView = image.getView(); depthAttachment.imageLayout = @@ -168,8 +168,8 @@ RenderGraph::PassBuilder& RenderGraph::PassBuilder::writeColor( RenderGraph::PassBuilder& RenderGraph::PassBuilder::writeDepth( ImageRef image, vk::AttachmentLoadOp loadOp, float clearDepth) { - SOPHIA_ASSERT(!this->pass.depthAttachment, "Pass", this->pass.name, - "already has a depth attachment"); + SOPHIA_ASSERT(!this->pass.depthAttachment, + "Pass {} already has a depth attachment", this->pass.name); this->use(image, Access::DepthWrite); this->pass.depthAttachment = DepthAttachment{image, loadOp, clearDepth}; return *this; @@ -184,8 +184,8 @@ RenderGraph::PassBuilder& RenderGraph::PassBuilder::use(ImageRef image, SOPHIA_ASSERT(image.isValid() && image.index < this->graph.images.size(), "ImageRef doesn't belong to this graph"); for (const ImageUse& existing : this->pass.uses) { - SOPHIA_ASSERT(existing.image.index != image.index, "Pass", this->pass.name, - "uses the same image twice"); + SOPHIA_ASSERT(existing.image.index != image.index, + "Pass {} uses the same image twice", this->pass.name); } this->pass.uses.push_back({image, access}); diff --git a/engine/src/renderer/sprite_render_system.cpp b/engine/src/renderer/sprite_render_system.cpp index 932b0ec..da1e896 100644 --- a/engine/src/renderer/sprite_render_system.cpp +++ b/engine/src/renderer/sprite_render_system.cpp @@ -45,8 +45,9 @@ void SpriteRenderSystem::render(vk::CommandBuffer commandBuffer, u32 available = MAX_INSTANCES_PER_FRAME - this->instanceCursor; if (instances.size() > available) { - log::warning("SpriteRenderSystem: frame instance buffer full, dropping", - instances.size() - available, "sprites"); + log::warning( + "SpriteRenderSystem: frame instance buffer full, dropping {} sprites", + instances.size() - available); } Buffer& buffer = *this->instanceBuffers[this->frameIndex]; diff --git a/engine/src/resources/resource_registry_ref.cpp b/engine/src/resources/resource_registry_ref.cpp index a329abf..7b04592 100644 --- a/engine/src/resources/resource_registry_ref.cpp +++ b/engine/src/resources/resource_registry_ref.cpp @@ -26,7 +26,7 @@ TextureHandle ResourceRegistryRef::load(const std::string& path) { // Always decode to RGBA8, whatever the file has. stbi_uc* pixels = stbi_load(path.c_str(), &width, &height, &channels, 4); if (pixels == nullptr) { - log::fatal("failed to load texture", path, "-", stbi_failure_reason()); + log::fatal("failed to load texture {} - {}", path, stbi_failure_reason()); throw std::runtime_error("failed to load texture: " + path); } diff --git a/engine/src/vulkan/access.cpp b/engine/src/vulkan/access.cpp index 027d9dc..8a1f84e 100644 --- a/engine/src/vulkan/access.cpp +++ b/engine/src/vulkan/access.cpp @@ -75,7 +75,7 @@ AccessState getStateForLayout(vk::ImageLayout layout) { } } - log::fatal("Unsupported image layout:", vk::to_string(layout)); + log::fatal("Unsupported image layout: {}", vk::to_string(layout)); throw std::runtime_error("Unsupported image layout."); } diff --git a/engine/src/vulkan/device.cpp b/engine/src/vulkan/device.cpp index 6b25081..7509a02 100644 --- a/engine/src/vulkan/device.cpp +++ b/engine/src/vulkan/device.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -19,16 +20,16 @@ debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT m_severity, (void)pUser_data; if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) { - log::error(pCallback_data->pMessage); + log::error("{}", pCallback_data->pMessage); return VK_SUCCESS; } else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - log::warning(pCallback_data->pMessage); + log::warning("{}", pCallback_data->pMessage); } else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo) { - log::info(pCallback_data->pMessage); + log::info("{}", pCallback_data->pMessage); } else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose) { - log::verbose(pCallback_data->pMessage); + log::verbose("{}", pCallback_data->pMessage); } else { - log::info(pCallback_data->pMessage); + log::info("{}", pCallback_data->pMessage); } return VK_FALSE; @@ -107,8 +108,8 @@ static void forEachRequiredFeature(DeviceFeatureChain& chain, Fn&& fn) { static bool checkDeviceFeatureSupport(const vk::PhysicalDevice& device) { vk::PhysicalDeviceProperties properties = device.getProperties(); if (properties.apiVersion < SOPHIA_VULKAN_API_VERSION) { - log::verbose("Skipping ", properties.deviceName, - ": Vulkan 1.3 not supported"); + log::verbose("Skipping {}: Vulkan 1.3 not supported", + properties.deviceName.data()); return false; } @@ -119,8 +120,8 @@ static bool checkDeviceFeatureSupport(const vk::PhysicalDevice& device) { bool allSupported = true; forEachRequiredFeature(supported, [&](vk::Bool32 has, const char* name) { if (!has) { - log::verbose("Skipping ", properties.deviceName, - ": missing device feature ", name); + log::verbose("Skipping {}: missing device feature {}", + properties.deviceName.data(), name); allSupported = false; } }); @@ -197,7 +198,7 @@ void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, this->allocator, &static_cast(bufferInfo), &allocInfo, &rawBuffer, &allocation, nullptr); if (result != VK_SUCCESS) { - log::fatal("failed to create buffer. Error: ", + log::fatal("failed to create buffer. Error: {}", vk::to_string(static_cast(result))); throw std::runtime_error("failed to create buffer"); } @@ -220,7 +221,7 @@ vk::CommandBuffer Device::beginSingleTimeCommands() { throw vk::SystemError(result); } } catch (const vk::SystemError& error) { - log::error("failed to allocate single time commandBuffer. Error: ", + log::error("failed to allocate single time commandBuffer. Error: {}", error.what()); throw std::runtime_error("failed to allocate single time commandBuffer"); } @@ -234,7 +235,7 @@ vk::CommandBuffer Device::beginSingleTimeCommands() { throw vk::SystemError(result); } } catch (const vk::SystemError& error) { - log::error("failed to begin single time commandBuffer. Error: ", + log::error("failed to begin single time commandBuffer. Error: {}", error.what()); throw std::runtime_error("failed to begin single time commandBuffer"); } @@ -278,7 +279,7 @@ void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo, this->allocator, &static_cast(imageInfo), &allocInfo, &rawImage, &allocation, nullptr); if (result != VK_SUCCESS) { - log::fatal("failed to create image. Error: ", + log::fatal("failed to create image. Error: {}", vk::to_string(static_cast(result))); throw std::runtime_error("failed to create image"); } @@ -374,7 +375,7 @@ std::vector Device::getRequiredExtensions() { throw std::exception(); } - log::verbose("Number of available extensions: ", available_extension_count); + log::verbose("Number of available extensions: {}", available_extension_count); log::verbose("Available extensions:"); for (const auto& e : available_extensions) { @@ -482,7 +483,7 @@ void Device::pickPhysicalDevice() { log::fatal("Failed to find any physical devices."); throw std::exception(); } - log::verbose("Physical Devices count: ", physicalDevices.size()); + log::verbose("Physical Devices count: {}", physicalDevices.size()); // Select first physical device that is suitable for (const auto& device : physicalDevices) { @@ -498,7 +499,7 @@ void Device::pickPhysicalDevice() { } this->properties = this->physicalDevice.getProperties(); - log::verbose("Physical Device: ", this->properties.deviceName); + log::verbose("Physical Device: {}", this->properties.deviceName.data()); } void Device::createLogicalDevice() { diff --git a/engine/src/vulkan/shader.cpp b/engine/src/vulkan/shader.cpp index e0de91b..027aaad 100644 --- a/engine/src/vulkan/shader.cpp +++ b/engine/src/vulkan/shader.cpp @@ -25,7 +25,7 @@ std::vector readFile(const std::string& path) { std::ifstream file(path, std::ios::ate | std::ios::binary); if (!file.is_open()) { - log::error("failed to open shader: " + path); + log::error("failed to open shader: {}", path); throw std::runtime_error("failed to open: " + path); } @@ -46,22 +46,23 @@ void Shader::compile(const std::string& path, ShaderStage shaderStage) { std::vector source = readFile(path); - log::info("Compiling shader: " + path); + log::info("Compiling shader: {}", path); shaderc::Compiler compiler; shaderc::SpvCompilationResult result = compiler.CompileGlslToSpv( source.data(), source.size(), toShadercKind(shaderStage), path.c_str()); if (result.GetCompilationStatus() != shaderc_compilation_status_success) { - log::fatal("failed to compile shader:", path, result.GetErrorMessage()); + log::fatal("failed to compile shader: {}: {}", path, + result.GetErrorMessage()); throw std::runtime_error("failed to compile shader: " + path + ": " + result.GetErrorMessage()); } this->spirv.assign(result.cbegin(), result.cend()); - log::info("Successfully compiled shader: " + path + " (" + - std::to_string(this->spirv.size() * 4) + " bytes)"); + log::info("Successfully compiled shader: {} ({} bytes)", path, + this->spirv.size() * 4); } void Shader::load(const std::string& path, ShaderStage shaderStage) { @@ -70,15 +71,14 @@ void Shader::load(const std::string& path, ShaderStage shaderStage) { std::vector bytes = readFile(path); if (bytes.size() % sizeof(u32) != 0) { - log::fatal("invalid SPIR-V binary (size not a multiple of 4):", path); + log::fatal("invalid SPIR-V binary (size not a multiple of 4): {}", path); throw std::runtime_error("invalid SPIR-V binary: " + path); } this->spirv.assign(reinterpret_cast(bytes.data()), reinterpret_cast(bytes.data() + bytes.size())); - log::info("Loaded precompiled shader: " + path + " (" + - std::to_string(bytes.size()) + " bytes)"); + log::info("Loaded precompiled shader: {} ({} bytes)", path, bytes.size()); } Shader::Builder& Shader::Builder::fromGLSL(const std::string& path) { diff --git a/engine/src/vulkan/swapchain.cpp b/engine/src/vulkan/swapchain.cpp index 66fe4b0..6aaf02e 100644 --- a/engine/src/vulkan/swapchain.cpp +++ b/engine/src/vulkan/swapchain.cpp @@ -114,7 +114,7 @@ void Swapchain::createSwapchain() { this->device.get()->createSwapchainKHR(this->swapchainCreateInfo); // log::trace("created vk::SwapchainKHR"); } catch (const vk::SystemError& err) { - log::fatal("failed to create swapchain. Error: ", err.what()); + log::fatal("failed to create swapchain. Error: {}", err.what()); throw std::runtime_error("failed to create swapchain"); } } diff --git a/engine/src/vulkan/vk_check.hpp b/engine/src/vulkan/vk_check.hpp new file mode 100644 index 0000000..ab77d9c --- /dev/null +++ b/engine/src/vulkan/vk_check.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include +#include + +inline void vk_check(vk::Result result, std::source_location loc = + std::source_location::current()) { + if (result != vk::Result::eSuccess) { + std::fprintf(stderr, "%s:%u: %s: %s\n", loc.file_name(), loc.line(), + loc.function_name(), vk::to_string(result).c_str()); + std::abort(); + } +} + +inline void vk_check(VkResult result, std::source_location loc = + std::source_location::current()) { + vk_check(static_cast(result), loc); +} diff --git a/sandbox/main.cpp b/sandbox/main.cpp index b8415e1..af1b5ce 100644 --- a/sandbox/main.cpp +++ b/sandbox/main.cpp @@ -11,16 +11,6 @@ #include #include -#define VK_CHECK(expr) \ - do { \ - vk::Result result_ = (expr); \ - if (result_ != vk::Result::eSuccess) { \ - std::fprintf(stderr, "%s:%d: %s -> %d\n", __FILE__, __LINE__, #expr, \ - result_); \ - std::abort(); \ - } \ - } while (0) - constexpr uint32_t WIDTH = 1280; constexpr uint32_t HEIGHT = 720; constexpr const char* NAME = ""; From f6f4f8b7c96dc1f7bc79c3a9f7a97f02729248cb Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Mon, 5 Oct 2026 02:16:37 -0700 Subject: [PATCH 07/10] fixes to buffer class --- engine/src/vulkan/buffer.cpp | 7 ++----- engine/src/vulkan/buffer.hpp | 5 ++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/engine/src/vulkan/buffer.cpp b/engine/src/vulkan/buffer.cpp index 8054ee1..774eb15 100644 --- a/engine/src/vulkan/buffer.cpp +++ b/engine/src/vulkan/buffer.cpp @@ -36,17 +36,14 @@ Buffer::~Buffer() { vmaDestroyBuffer(this->device.getAllocator(), this->buffer, this->allocation); } -// VMA always maps the whole allocation, so `size` only exists to keep the -// interface; `offset` is applied to the returned pointer. -vk::Result Buffer::map(vk::DeviceSize size, vk::DeviceSize offset) { +vk::Result Buffer::map() { SOPHIA_ASSERT(buffer && allocation, "Called map on buffer before create"); - (void)size; void* base = nullptr; vk::Result result = static_cast( vmaMapMemory(this->device.getAllocator(), this->allocation, &base)); if (result == vk::Result::eSuccess) { - this->mapped = static_cast(base) + offset; + this->mapped = base; } return result; } diff --git a/engine/src/vulkan/buffer.hpp b/engine/src/vulkan/buffer.hpp index f4cfbb1..605a402 100644 --- a/engine/src/vulkan/buffer.hpp +++ b/engine/src/vulkan/buffer.hpp @@ -19,8 +19,7 @@ class Buffer { vk::DeviceSize minOffsetAlignment = 1); ~Buffer(); - vk::Result map(vk::DeviceSize size = VK_WHOLE_SIZE, - vk::DeviceSize offset = 0); + vk::Result map(); void unmap(); void writeToBuffer(void* data, vk::DeviceSize size = VK_WHOLE_SIZE, @@ -41,7 +40,7 @@ class Buffer { void* getMappedMemory() const { return mapped; } uint32_t getInstanceCount() const { return instanceCount; } vk::DeviceSize getInstanceSize() const { return instanceSize; } - vk::DeviceSize getAlignmentSize() const { return instanceSize; } + vk::DeviceSize getAlignmentSize() const { return alignmentSize; } vk::BufferUsageFlags getUsageFlags() const { return usageFlags; } vk::MemoryPropertyFlags getMemoryPropertyFlags() const { return memoryPropertyFlags; From 5b2d50a925c08404c3b38273ab36cdbc955ea743 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Mon, 5 Oct 2026 02:16:55 -0700 Subject: [PATCH 08/10] stubbing out conway game of life --- examples/conway_game_of_life/CMakeLists.txt | 3 +++ .../conway_game_of_life/comway_game_of_life.frag | 3 +++ examples/conway_game_of_life/main.cpp | 13 +++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 examples/conway_game_of_life/CMakeLists.txt create mode 100644 examples/conway_game_of_life/comway_game_of_life.frag create mode 100644 examples/conway_game_of_life/main.cpp diff --git a/examples/conway_game_of_life/CMakeLists.txt b/examples/conway_game_of_life/CMakeLists.txt new file mode 100644 index 0000000..319f47b --- /dev/null +++ b/examples/conway_game_of_life/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(conway_game_of_life main.cpp) + +target_link_libraries(conway_game_of_life PRIVATE ${PROJECT_NAME}) diff --git a/examples/conway_game_of_life/comway_game_of_life.frag b/examples/conway_game_of_life/comway_game_of_life.frag new file mode 100644 index 0000000..839f2d0 --- /dev/null +++ b/examples/conway_game_of_life/comway_game_of_life.frag @@ -0,0 +1,3 @@ +#version 450 + +void main(){} diff --git a/examples/conway_game_of_life/main.cpp b/examples/conway_game_of_life/main.cpp new file mode 100644 index 0000000..1168c0d --- /dev/null +++ b/examples/conway_game_of_life/main.cpp @@ -0,0 +1,13 @@ +#include +#include + +class ConwayGameOfLife : public sophia::Application { + public: + ConwayGameOfLife() + : Application{{.width = 800, .height = 600, .name = "ConwayGameOfLife"}} { + } + + void onCreate() override {} +}; + +SOPHIA_MAIN(ConwayGameOfLife) From e32e5f911facbf1c437b0356e665d8d64147aea4 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Mon, 5 Oct 2026 02:17:32 -0700 Subject: [PATCH 09/10] sandbox copying current application --- sandbox/main.cpp | 281 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 279 insertions(+), 2 deletions(-) diff --git a/sandbox/main.cpp b/sandbox/main.cpp index af1b5ce..8ab5895 100644 --- a/sandbox/main.cpp +++ b/sandbox/main.cpp @@ -1,18 +1,295 @@ #define GLFW_INCLUDE_VULKAN #include +#include #include #include +#include #include +#include #include #include #include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include + +#include "renderer/imgui_renderer.hpp" +#include "renderer/model_render_system.hpp" +#include "renderer/render_context.hpp" +#include "renderer/render_graph.hpp" +#include "renderer/scene_renderer.hpp" +#include "resources/mesh.hpp" +#include "resources/render_target.hpp" +#include "resources/resource_registry.hpp" +#include "vulkan/buffer.hpp" +#include "vulkan/device.hpp" +#include "vulkan/pipelines/graphics_pipeline.hpp" +#include "vulkan/shader.hpp" +#include "window.hpp" constexpr uint32_t WIDTH = 1280; constexpr uint32_t HEIGHT = 720; -constexpr const char* NAME = ""; +constexpr const char* NAME = "sandbox"; -int main() { return EXIT_SUCCESS; } +constexpr sophia::ClearColor CLEAR_COLOR = {0.01f, 0.01f, 0.02f, 1.0f}; + +bool hasFlag(sophia::ClearFlags flags, sophia::ClearFlags flag) { + return (static_cast(flags) & static_cast(flag)) != 0; +} + +int main(int argc, const char** argv) { + std::vector args; + args.assign(argv, argv + argc); + + std::cout << __FILE__ << "::" << __LINE__ << '\n'; + + sophia::Window window{WIDTH, HEIGHT, NAME}; + sophia::Device device{&window}; + sophia::RenderContext renderContext{window, device}; + sophia::ResourceRegistry resourceRegistry{ + device, renderContext.getImageFormat()}; // remove? + + // remove? + std::vector> meshes; + + // remove? + std::vector drawCalls; + + // todo: figure out which to remove + sophia::PanelRegistry panelRegistry; + sophia::SceneRegistry sceneRegistry; + sophia::SceneViewRegistry sceneViewRegistry; + sophia::ConsoleCommandRegistry consoleCommandRegistry; + + sophia::DebugUI debugUI{panelRegistry, consoleCommandRegistry}; + debugUI.showFrameStats(true); + + sophia::ModelRenderSystem modelRenderSystem{device, renderContext}; + sophia::SceneRenderer sceneRenderer{sceneRegistry, resourceRegistry, device, + renderContext.getImageFormat()}; + sophia::ImGuiRenderer imguiRenderer{ + window, device, renderContext.getImageFormat(), + renderContext.getImageCount(), resourceRegistry}; + + consoleCommandRegistry.registerCommand( + "reset_layout", "Restore the default panel layout", + [&imguiRenderer](const std::vector&) { + imguiRenderer.resetLayout(); + return std::string{"Layout reset"}; + }); + + auto& meshPool = resourceRegistry.meshPool; + auto& renderTargetPool = resourceRegistry.renderTargetPool; + + imguiRenderer.setDefaultLayout(panelRegistry.getDefaultLayout()); + + // Views already warned about, so each warns once. + std::set viewsWithoutCamera; + std::set viewsOverwritingOthers; + + auto startTime = std::chrono::high_resolution_clock::now(); + auto currentTime = startTime; + sophia::u32 frameIndex = 0; + + while (!window.shouldClose()) { + glfwPollEvents(); + + auto newTime = std::chrono::high_resolution_clock::now(); + double deltaTime = + std::chrono::duration(newTime - currentTime).count(); + currentTime = newTime; + + double elapsedTime = + std::chrono::duration(currentTime - startTime).count(); + + // Resizing waits for the GPU, so it has to happen before any of this + // frame's work (or ImGui's references to the targets) is recorded. + renderTargetPool.applyPendingResizes(); + + vk::Extent2D extent = renderContext.getExtent(); + sophia::FrameInfo frameInfo{frameIndex, elapsedTime, deltaTime, + glm::vec2(static_cast(extent.width), + static_cast(extent.height))}; + + // Canvas draws in window pixels, origin at the center. + // TODO: replace with Debug/Gizmos drawn into scene views through their + // cameras. + glm::mat4 canvasProjection = + glm::ortho(-frameInfo.currentFramebufferExtent.x / 2.f, + frameInfo.currentFramebufferExtent.x / 2.f, + -frameInfo.currentFramebufferExtent.y / 2.f, + frameInfo.currentFramebufferExtent.y / 2.f); + + vk::CommandBuffer commandBuffer = renderContext.beginFrame(); + if (!commandBuffer) { + continue; + } + + imguiRenderer.beginFrame(); + sceneRenderer.beginFrame(renderContext.getFrameIndex()); + + panelRegistry.update(frameInfo); + + std::vector instances; + instances.reserve(meshes.size() + drawCalls.size()); + for (const std::unique_ptr& mesh : meshes) { + instances.push_back({mesh.get(), glm::mat4{1.f}}); + } + instances.insert(instances.end(), drawCalls.begin(), drawCalls.end()); + drawCalls.clear(); + + sophia::RenderGraph graph{renderContext.getTracyContext()}; + sophia::ImageRef backbuffer = graph.importImage( + renderContext.getCurrentImage(), sophia::Access::Present); + + struct ViewTarget { + sophia::ImageRef color; + sophia::ImageRef depth; + }; + std::map viewTargets; + // Views without a target render straight into the backbuffer. + std::optional swapchainTarget; + for (sophia::SceneViewHandle handle : sceneViewRegistry.GetRenderQueue()) { + const sophia::SceneView& view = sceneViewRegistry.peek(handle); + bool toSwapchain = !view.target.isValid(); + if (!sceneRegistry.isAlive(view.scene) || + !sceneRegistry.get(view.scene).isVisible() || + (!toSwapchain && !renderTargetPool.isAlive(view.target))) { + continue; + } + + ViewTarget target; + vk::Extent2D targetExtent; + // Whether this is the first view this frame to render to its target. + bool firstOnTarget = false; + if (toSwapchain) { + firstOnTarget = !swapchainTarget; + if (!swapchainTarget) { + swapchainTarget = { + backbuffer, + graph.importImage(renderContext.getCurrentDepthImage(), + sophia::Access::DepthWrite)}; + } + target = *swapchainTarget; + targetExtent = renderContext.getExtent(); + } else { + sophia::RenderTarget& renderTarget = renderTargetPool.get(view.target); + auto [it, inserted] = viewTargets.try_emplace(view.target); + firstOnTarget = inserted; + if (inserted) { + it->second = {graph.importImage(renderTarget.getImage(), + sophia::Access::SampledRead), + graph.importImage(renderTarget.getDepthImage(), + sophia::Access::DepthWrite)}; + } + target = it->second; + targetExtent = renderTarget.getExtent(); + } + + if (!firstOnTarget && + hasFlag(view.clearFlags, sophia::ClearFlags::Color) && + viewsOverwritingOthers.insert(handle).second) { + sophia::log::warning( + "scene view clears its target after another view already " + "rendered to it this frame, hiding that view (give it clearFlags " + "without Color, or render it first with a lower priority)"); + } + + const sophia::vec4& color = view.clearColor; + graph.addPass( + "scene view " + std::to_string(handle.index), + [&](sophia::RenderGraph::PassBuilder& pass) { + pass.writeColor(target.color, + hasFlag(view.clearFlags, sophia::ClearFlags::Color) + ? vk::AttachmentLoadOp::eClear + : vk::AttachmentLoadOp::eLoad, + {color.r, color.g, color.b, color.a}); + pass.writeDepth(target.depth, + hasFlag(view.clearFlags, sophia::ClearFlags::Depth) + ? vk::AttachmentLoadOp::eClear + : vk::AttachmentLoadOp::eLoad); + }, + [&sceneRenderer, &sceneRegistry, &sceneViewRegistry, + &viewsWithoutCamera, handle, + targetExtent](vk::CommandBuffer commandBuffer) { + ZoneScopedN("scene view pass"); + const sophia::SceneView& view = sceneViewRegistry.peek(handle); + sophia::Entity camera = + view.camera ? view.camera + : sceneRegistry.get(view.scene).getActiveCamera(); + // No camera to look through: the target is just cleared. + if (!camera) { + if (viewsWithoutCamera.insert(handle).second) { + sophia::log::warning( + "scene view has no camera and its scene has no active " + "camera; it will only be cleared (see " + "Scene::createCamera)"); + } + return; + } + sceneRenderer.render(commandBuffer, view, camera, targetExtent); + }); + } + + graph.addPass( + "main", + [&](sophia::RenderGraph::PassBuilder& pass) { + // Keep what scene views drew into the backbuffer. + pass.writeColor(backbuffer, + swapchainTarget ? vk::AttachmentLoadOp::eLoad + : vk::AttachmentLoadOp::eClear, + CLEAR_COLOR); + for (const auto& [handle, target] : viewTargets) { + pass.sample(target.color); + } + }, + [&](vk::CommandBuffer commandBuffer) { + ZoneScopedN("main pass"); + modelRenderSystem.render(commandBuffer, instances, canvasProjection); + + imguiRenderer.endFrame(commandBuffer); + }); + + graph.execute(commandBuffer); + + renderContext.endFrame(); + FrameMark; + + frameIndex++; + } + + device.waitIdle(); + + auto endTime = std::chrono::high_resolution_clock::now(); + double totalRuntime = + std::chrono::duration(endTime - startTime).count(); + sophia::log::info("Application ran for {:.2f}s", totalRuntime); + + return EXIT_SUCCESS; +} From 777e407d6724c1c75aa27fecf3e76a8f3d817baf Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Tue, 6 Oct 2026 01:16:28 -0700 Subject: [PATCH 10/10] sandbox simplfy --- sandbox/main.cpp | 256 ++++------------------------------------------- 1 file changed, 19 insertions(+), 237 deletions(-) diff --git a/sandbox/main.cpp b/sandbox/main.cpp index 8ab5895..bd62059 100644 --- a/sandbox/main.cpp +++ b/sandbox/main.cpp @@ -1,295 +1,77 @@ #define GLFW_INCLUDE_VULKAN #include -#include -#include -#include -#include -#include -#include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include -#include "renderer/imgui_renderer.hpp" -#include "renderer/model_render_system.hpp" #include "renderer/render_context.hpp" #include "renderer/render_graph.hpp" #include "renderer/scene_renderer.hpp" -#include "resources/mesh.hpp" -#include "resources/render_target.hpp" #include "resources/resource_registry.hpp" -#include "vulkan/buffer.hpp" #include "vulkan/device.hpp" -#include "vulkan/pipelines/graphics_pipeline.hpp" -#include "vulkan/shader.hpp" #include "window.hpp" constexpr uint32_t WIDTH = 1280; constexpr uint32_t HEIGHT = 720; constexpr const char* NAME = "sandbox"; -constexpr sophia::ClearColor CLEAR_COLOR = {0.01f, 0.01f, 0.02f, 1.0f}; - -bool hasFlag(sophia::ClearFlags flags, sophia::ClearFlags flag) { - return (static_cast(flags) & static_cast(flag)) != 0; -} - -int main(int argc, const char** argv) { - std::vector args; - args.assign(argv, argv + argc); - - std::cout << __FILE__ << "::" << __LINE__ << '\n'; - +int main() { sophia::Window window{WIDTH, HEIGHT, NAME}; sophia::Device device{&window}; sophia::RenderContext renderContext{window, device}; - sophia::ResourceRegistry resourceRegistry{ - device, renderContext.getImageFormat()}; // remove? + sophia::ResourceRegistry resourceRegistry{device, + renderContext.getImageFormat()}; - // remove? - std::vector> meshes; - - // remove? - std::vector drawCalls; - - // todo: figure out which to remove - sophia::PanelRegistry panelRegistry; sophia::SceneRegistry sceneRegistry; - sophia::SceneViewRegistry sceneViewRegistry; - sophia::ConsoleCommandRegistry consoleCommandRegistry; - - sophia::DebugUI debugUI{panelRegistry, consoleCommandRegistry}; - debugUI.showFrameStats(true); - - sophia::ModelRenderSystem modelRenderSystem{device, renderContext}; sophia::SceneRenderer sceneRenderer{sceneRegistry, resourceRegistry, device, renderContext.getImageFormat()}; - sophia::ImGuiRenderer imguiRenderer{ - window, device, renderContext.getImageFormat(), - renderContext.getImageCount(), resourceRegistry}; - consoleCommandRegistry.registerCommand( - "reset_layout", "Restore the default panel layout", - [&imguiRenderer](const std::vector&) { - imguiRenderer.resetLayout(); - return std::string{"Layout reset"}; - }); - - auto& meshPool = resourceRegistry.meshPool; - auto& renderTargetPool = resourceRegistry.renderTargetPool; - - imguiRenderer.setDefaultLayout(panelRegistry.getDefaultLayout()); - - // Views already warned about, so each warns once. - std::set viewsWithoutCamera; - std::set viewsOverwritingOthers; - - auto startTime = std::chrono::high_resolution_clock::now(); - auto currentTime = startTime; - sophia::u32 frameIndex = 0; + sophia::SceneView view; + view.scene = sceneRegistry.create(); + view.clearColor = {0.01f, 0.01f, 0.02f, 1.0f}; + view.camera = sceneRegistry.get(view.scene).createCamera(); while (!window.shouldClose()) { glfwPollEvents(); - auto newTime = std::chrono::high_resolution_clock::now(); - double deltaTime = - std::chrono::duration(newTime - currentTime).count(); - currentTime = newTime; - - double elapsedTime = - std::chrono::duration(currentTime - startTime).count(); - - // Resizing waits for the GPU, so it has to happen before any of this - // frame's work (or ImGui's references to the targets) is recorded. - renderTargetPool.applyPendingResizes(); - - vk::Extent2D extent = renderContext.getExtent(); - sophia::FrameInfo frameInfo{frameIndex, elapsedTime, deltaTime, - glm::vec2(static_cast(extent.width), - static_cast(extent.height))}; - - // Canvas draws in window pixels, origin at the center. - // TODO: replace with Debug/Gizmos drawn into scene views through their - // cameras. - glm::mat4 canvasProjection = - glm::ortho(-frameInfo.currentFramebufferExtent.x / 2.f, - frameInfo.currentFramebufferExtent.x / 2.f, - -frameInfo.currentFramebufferExtent.y / 2.f, - frameInfo.currentFramebufferExtent.y / 2.f); - vk::CommandBuffer commandBuffer = renderContext.beginFrame(); if (!commandBuffer) { continue; } - imguiRenderer.beginFrame(); sceneRenderer.beginFrame(renderContext.getFrameIndex()); - panelRegistry.update(frameInfo); - - std::vector instances; - instances.reserve(meshes.size() + drawCalls.size()); - for (const std::unique_ptr& mesh : meshes) { - instances.push_back({mesh.get(), glm::mat4{1.f}}); - } - instances.insert(instances.end(), drawCalls.begin(), drawCalls.end()); - drawCalls.clear(); - sophia::RenderGraph graph{renderContext.getTracyContext()}; - sophia::ImageRef backbuffer = graph.importImage( - renderContext.getCurrentImage(), sophia::Access::Present); - - struct ViewTarget { - sophia::ImageRef color; - sophia::ImageRef depth; - }; - std::map viewTargets; - // Views without a target render straight into the backbuffer. - std::optional swapchainTarget; - for (sophia::SceneViewHandle handle : sceneViewRegistry.GetRenderQueue()) { - const sophia::SceneView& view = sceneViewRegistry.peek(handle); - bool toSwapchain = !view.target.isValid(); - if (!sceneRegistry.isAlive(view.scene) || - !sceneRegistry.get(view.scene).isVisible() || - (!toSwapchain && !renderTargetPool.isAlive(view.target))) { - continue; - } - - ViewTarget target; - vk::Extent2D targetExtent; - // Whether this is the first view this frame to render to its target. - bool firstOnTarget = false; - if (toSwapchain) { - firstOnTarget = !swapchainTarget; - if (!swapchainTarget) { - swapchainTarget = { - backbuffer, - graph.importImage(renderContext.getCurrentDepthImage(), - sophia::Access::DepthWrite)}; - } - target = *swapchainTarget; - targetExtent = renderContext.getExtent(); - } else { - sophia::RenderTarget& renderTarget = renderTargetPool.get(view.target); - auto [it, inserted] = viewTargets.try_emplace(view.target); - firstOnTarget = inserted; - if (inserted) { - it->second = {graph.importImage(renderTarget.getImage(), - sophia::Access::SampledRead), - graph.importImage(renderTarget.getDepthImage(), - sophia::Access::DepthWrite)}; - } - target = it->second; - targetExtent = renderTarget.getExtent(); - } - - if (!firstOnTarget && - hasFlag(view.clearFlags, sophia::ClearFlags::Color) && - viewsOverwritingOthers.insert(handle).second) { - sophia::log::warning( - "scene view clears its target after another view already " - "rendered to it this frame, hiding that view (give it clearFlags " - "without Color, or render it first with a lower priority)"); - } - - const sophia::vec4& color = view.clearColor; - graph.addPass( - "scene view " + std::to_string(handle.index), - [&](sophia::RenderGraph::PassBuilder& pass) { - pass.writeColor(target.color, - hasFlag(view.clearFlags, sophia::ClearFlags::Color) - ? vk::AttachmentLoadOp::eClear - : vk::AttachmentLoadOp::eLoad, - {color.r, color.g, color.b, color.a}); - pass.writeDepth(target.depth, - hasFlag(view.clearFlags, sophia::ClearFlags::Depth) - ? vk::AttachmentLoadOp::eClear - : vk::AttachmentLoadOp::eLoad); - }, - [&sceneRenderer, &sceneRegistry, &sceneViewRegistry, - &viewsWithoutCamera, handle, - targetExtent](vk::CommandBuffer commandBuffer) { - ZoneScopedN("scene view pass"); - const sophia::SceneView& view = sceneViewRegistry.peek(handle); - sophia::Entity camera = - view.camera ? view.camera - : sceneRegistry.get(view.scene).getActiveCamera(); - // No camera to look through: the target is just cleared. - if (!camera) { - if (viewsWithoutCamera.insert(handle).second) { - sophia::log::warning( - "scene view has no camera and its scene has no active " - "camera; it will only be cleared (see " - "Scene::createCamera)"); - } - return; - } - sceneRenderer.render(commandBuffer, view, camera, targetExtent); - }); - } + sophia::ImageRef color = graph.importImage(renderContext.getCurrentImage(), + sophia::Access::Present); + sophia::ImageRef depth = graph.importImage( + renderContext.getCurrentDepthImage(), sophia::Access::DepthWrite); graph.addPass( - "main", + "scene", [&](sophia::RenderGraph::PassBuilder& pass) { - // Keep what scene views drew into the backbuffer. - pass.writeColor(backbuffer, - swapchainTarget ? vk::AttachmentLoadOp::eLoad - : vk::AttachmentLoadOp::eClear, - CLEAR_COLOR); - for (const auto& [handle, target] : viewTargets) { - pass.sample(target.color); - } + const sophia::vec4& c = view.clearColor; + pass.writeColor(color, vk::AttachmentLoadOp::eClear, + {c.r, c.g, c.b, c.a}); + pass.writeDepth(depth, vk::AttachmentLoadOp::eClear); }, [&](vk::CommandBuffer commandBuffer) { - ZoneScopedN("main pass"); - modelRenderSystem.render(commandBuffer, instances, canvasProjection); - - imguiRenderer.endFrame(commandBuffer); + ZoneScopedN("scene pass"); + sceneRenderer.render(commandBuffer, view, view.camera, + renderContext.getExtent()); }); graph.execute(commandBuffer); renderContext.endFrame(); FrameMark; - - frameIndex++; } device.waitIdle(); - auto endTime = std::chrono::high_resolution_clock::now(); - double totalRuntime = - std::chrono::duration(endTime - startTime).count(); - sophia::log::info("Application ran for {:.2f}s", totalRuntime); - return EXIT_SUCCESS; }