Compare commits

..

11 Commits

Author SHA1 Message Date
burkkyy 0f0b0d1434 Merge pull request 'Adding in sandbox + Logger upgrade' (#80) from sandbox into main
Reviewed-on: #80
2026-10-06 01:20:21 -07:00
burkkyy 777e407d67 sandbox simplfy 2026-10-06 01:16:28 -07:00
burkkyy e32e5f911f sandbox copying current application 2026-10-05 02:17:32 -07:00
burkkyy 5b2d50a925 stubbing out conway game of life 2026-10-05 02:16:55 -07:00
burkkyy f6f4f8b7c9 fixes to buffer class 2026-10-05 02:16:37 -07:00
burkkyy b6098d4dc6 New logger 2026-10-04 23:14:17 -07:00
burkkyy b31c120691 bumping project cxx version to 23 2026-10-04 22:54:07 -07:00
burkkyy 3c3fcfdd6b removing fmt 2026-10-04 22:52:07 -07:00
burkkyy 9538add3ac Using version 1.12.0 of fmt in project 2026-10-04 22:43:09 -07:00
burkkyy 31089f3539 adding fmt third_party library + adding cloc support 2026-10-04 22:41:42 -07:00
burkkyy 6cc8bf4262 adding in sandbox 2026-10-04 01:03:47 -07:00
32 changed files with 347 additions and 172 deletions
+8
View File
@@ -0,0 +1,8 @@
third_party
build
__dump
traces
.vscode
.claude
.git
imgui.ini
+2 -1
View File
@@ -5,7 +5,7 @@ set(NAME sophia)
project(${NAME} VERSION 0.0.0) project(${NAME} VERSION 0.0.0)
enable_language(CXX) enable_language(CXX)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@@ -39,4 +39,5 @@ add_subdirectory(third_party)
add_subdirectory(engine) add_subdirectory(engine)
add_subdirectory(editor) add_subdirectory(editor)
add_subdirectory(testbed) add_subdirectory(testbed)
add_subdirectory(sandbox)
add_subdirectory(examples) add_subdirectory(examples)
+21
View File
@@ -80,8 +80,29 @@ class DevCommand(GiteaCommand, RenderDocCommand, ShadersCommand, TracyCommand):
os.chdir(preset_dir) os.chdir(preset_dir)
os.execv(binary, [binary, *args]) 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 # 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): def ownit(self, *args, **kwargs):
file_or_directory = args[0] if args else None file_or_directory = args[0] if args else None
if file_or_directory is None: if file_or_directory is None:
+1 -1
View File
@@ -10,7 +10,7 @@ target_link_libraries(${NAME}_deps INTERFACE
imguizmo 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") set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/build")
add_executable(editor ${SOURCES}) add_executable(editor ${SOURCES})
+7 -2
View File
@@ -1,8 +1,13 @@
#pragma once #pragma once
#include <format>
#include <sophia/util/logger.hpp> #include <sophia/util/logger.hpp>
#include <utility>
template <typename... Args> template <typename... Args>
void log(Args... args) { void log(std::format_string<Args...> fmt, Args&&... args) {
sophia::log::print("\033[1;38;2;170;90;255m[editor]\033[0m", 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>(args)...));
}
} }
+6 -4
View File
@@ -83,7 +83,7 @@ class EventSystem {
this->eventDelegates[eventType] = this->eventDelegates[eventType] =
std::make_unique<std::any>(EventDelegate<E>()); std::make_unique<std::any>(EventDelegate<E>());
log::trace("EventSystem creating new event type: ", eventType); log::trace("EventSystem creating new event type: {}", eventType);
} }
EventDelegate<E>& delegate = EventDelegate<E>& delegate =
@@ -102,7 +102,7 @@ class EventSystem {
this->eventDelegates[eventType] = this->eventDelegates[eventType] =
std::make_unique<std::any>(EventDelegate<E>()); std::make_unique<std::any>(EventDelegate<E>());
log::trace("EventSystem creating new event type: ", eventType); log::trace("EventSystem creating new event type: {}", eventType);
} }
EventDelegate<E>& delegate = EventDelegate<E>& delegate =
@@ -124,12 +124,14 @@ class EventSystem {
std::any_cast<EventDelegate<E>&>(*this->eventDelegates.at(eventType)); std::any_cast<EventDelegate<E>&>(*this->eventDelegates.at(eventType));
delegate.invoke(event); delegate.invoke(event);
} catch (const std::out_of_range& error) { } catch (const std::out_of_range& error) {
log::error("event dispatch failed: ", event.toString()); log::error("event dispatch failed: {}", event.toString());
} }
} }
private: private:
EventSystem() { log::info("EventSystem Initialized: ", this); } EventSystem() {
log::info("EventSystem Initialized: {}", static_cast<const void*>(this));
}
~EventSystem() { log::info("EventSystem Destroyed"); } ~EventSystem() { log::info("EventSystem Destroyed"); }
std::unordered_map<EventType, std::unique_ptr<std::any>> eventDelegates; std::unordered_map<EventType, std::unique_ptr<std::any>> eventDelegates;
+1 -1
View File
@@ -38,7 +38,7 @@ class PanelRegistry {
const T& get(PanelHandle handle) const { const T& get(PanelHandle handle) const {
const Panel& panel = get(handle); const Panel& panel = get(handle);
SOPHIA_ASSERT(dynamic_cast<const T*>(&panel) != nullptr, SOPHIA_ASSERT(dynamic_cast<const T*>(&panel) != nullptr,
"PanelHandle does not refer to a", typeid(T).name()); "PanelHandle does not refer to a {}", typeid(T).name());
return static_cast<const T&>(panel); return static_cast<const T&>(panel);
} }
+7 -3
View File
@@ -1,7 +1,9 @@
/** /**
* Usage: * Usage:
* SOPHIA_ASSERT(pipelineLayout != nullptr); * 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 <cassert>'s * Compiled out entirely in release (NDEBUG) builds, matching <cassert>'s
* assert() semantics: don't rely on side effects inside the condition. * assert() semantics: don't rely on side effects inside the condition.
@@ -10,14 +12,16 @@
#pragma once #pragma once
#include <cstdlib> #include <cstdlib>
#include <format>
#include <sophia/util/logger.hpp> #include <sophia/util/logger.hpp>
#ifndef NDEBUG #ifndef NDEBUG
#define SOPHIA_ASSERT(condition, ...) \ #define SOPHIA_ASSERT(condition, ...) \
do { \ do { \
if (!(condition)) { \ if (!(condition)) { \
::sophia::log::fatal("Assertion failed:", #condition, "at", __FILE__, \ ::sophia::log::fatal( \
":", __LINE__ __VA_OPT__(, ) __VA_ARGS__); \ "Assertion failed: {} at {}:{}" __VA_OPT__(" - {}"), #condition, \
__FILE__, __LINE__ __VA_OPT__(, ::std::format(__VA_ARGS__))); \
std::abort(); \ std::abort(); \
} \ } \
} while (false) } while (false)
+8 -5
View File
@@ -2,13 +2,12 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <sophia/util/logger.hpp>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <typeinfo> #include <typeinfo>
#include <vector> #include <vector>
#include <sophia/util/logger.hpp>
#define DEFAULT_DELEGATE_FUNCTOR_ALLOC 5 #define DEFAULT_DELEGATE_FUNCTOR_ALLOC 5
namespace sophia { namespace sophia {
@@ -39,7 +38,9 @@ class expired_weak_object : public std::runtime_error {
template <class O, typename... A> template <class O, typename... A>
std::function<void(A...)> createWeakFunction(void (O::*method)(A...), std::function<void(A...)> createWeakFunction(void (O::*method)(A...),
std::shared_ptr<O> obj) { std::shared_ptr<O> 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<O> weakObj = obj; std::weak_ptr<O> weakObj = obj;
@@ -82,7 +83,7 @@ class Delegate {
try { try {
this->functors[i](std::forward<A>(args)...); this->functors[i](std::forward<A>(args)...);
} catch (const expired_weak_object& e) { } catch (const expired_weak_object& e) {
log::error("delegate invoke error: ", e.what()); log::error("delegate invoke error: {}", e.what());
deadFunctorIndexs.push_back(i); deadFunctorIndexs.push_back(i);
} }
} }
@@ -97,7 +98,9 @@ class Delegate {
void operator=(Functor c) { void operator=(Functor c) {
clear(); clear();
if (c != nullptr) { add(c); } if (c != nullptr) {
add(c);
}
} }
void operator+=(Functor c) { add(c); } void operator+=(Functor c) { add(c); }
+88 -80
View File
@@ -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: * Usage:
* using namespace sophia; * using namespace sophia;
* log::fatal("fatal message") * log::fatal("failed to load {}", path)
* log::info("Infomation message") * log::info("loaded {} meshes in {:.2f}ms", count, ms)
* log::debug("value: {}", x) // prefixed with file:line
* log::print("no prefix, just the message") * log::print("no prefix, just the message")
* *
* Everything is compiled out in release (NDEBUG) builds.
*
* TODO Impl some form of log error handling * TODO Impl some form of log error handling
*/ */
#pragma once #pragma once
#include <iostream> #include <cstdio>
#include <sophia/types.hpp> #include <format>
#include <print>
#include <source_location>
#include <string_view>
#include <type_traits>
#include <utility> #include <utility>
namespace sophia::log { namespace sophia::log {
#ifndef NDEBUG
inline constexpr bool enabled = true;
#else
inline constexpr bool enabled = false;
#endif // !NDEBUG
enum class LEVEL { enum class LEVEL {
NONE, NONE,
FATAL, FATAL,
@@ -29,118 +42,113 @@ enum class LEVEL {
TRACE, TRACE,
}; };
// Helper print functions namespace detail {
#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 ")
template <typename... Args> constexpr std::string_view prefix(LEVEL level) {
void log(LEVEL level = LEVEL::NONE, Args... args) {
#ifndef NDEBUG
switch (level) { switch (level) {
case LEVEL::FATAL: case LEVEL::FATAL:
LOG_PREFIX_FATAL; return "\033[1;91m[FATAL]\033[0m ";
break;
case LEVEL::ERROR: case LEVEL::ERROR:
LOG_PREFIX_ERROR; return "\033[1;31m[ERROR]\033[0m ";
break;
case LEVEL::WARNING: case LEVEL::WARNING:
LOG_PREFIX_WARNING; return "\033[1;33m[WARNING]\033[0m ";
break;
case LEVEL::INFO: case LEVEL::INFO:
LOG_PREFIX_INFO; return "\033[32m[INFO]\033[0m ";
break;
case LEVEL::VERBOSE: case LEVEL::VERBOSE:
LOG_PREFIX_VERBOSE; return "\033[36m[VERBOSE]\033[0m ";
break; case LEVEL::DEBUGGING:
// case LEVEL::DEBUGGING: return "\033[1;34m[DEBUG]\033[0m ";
// LOG_PREFIX_DEBUG;
// break;
case LEVEL::TRACE: case LEVEL::TRACE:
LOG_PREFIX_TRACE; return "\033[1;90m[TRACE]\033[0m ";
break;
default: default:
std::cout << "\033[1;90m[LOG]\033[0m "; return "\033[1;90m[LOG]\033[0m ";
break;
} }
}
((std::cout << ' ' << std::forward<Args>(args)), ...); inline void write(std::string_view prefix, std::string_view message) {
std::cout << std::endl; std::println(stdout, "{}{}", prefix, message);
#else std::fflush(stdout);
(void)level; }
(void)std::initializer_list<int>{((void)args, 0)...};
#endif // !NDEBUG // Captures the caller's location alongside the format string, since a
// defaulted std::source_location parameter can't follow a parameter pack.
template <typename... Args>
struct FormatWithLocation {
std::format_string<Args...> fmt;
std::source_location location;
template <typename S>
requires std::is_convertible_v<const S&, std::string_view>
consteval FormatWithLocation(
const S& fmt,
std::source_location location = std::source_location::current())
: fmt(fmt), location(location) {}
};
} // namespace detail
template <typename... Args>
void log(LEVEL level, std::format_string<Args...> fmt, Args&&... args) {
if constexpr (enabled) {
detail::write(detail::prefix(level),
std::format(fmt, std::forward<Args>(args)...));
}
} }
template <typename... Args> template <typename... Args>
void fatal(Args... args) { void fatal(std::format_string<Args...> fmt, Args&&... args) {
log(LEVEL::FATAL, args...); log(LEVEL::FATAL, fmt, std::forward<Args>(args)...);
} }
template <typename... Args> template <typename... Args>
void error(Args... args) { void error(std::format_string<Args...> fmt, Args&&... args) {
log(LEVEL::ERROR, args...); log(LEVEL::ERROR, fmt, std::forward<Args>(args)...);
} }
template <typename... Args> template <typename... Args>
void warning(Args... args) { void warning(std::format_string<Args...> fmt, Args&&... args) {
log(LEVEL::WARNING, args...); log(LEVEL::WARNING, fmt, std::forward<Args>(args)...);
} }
template <typename... Args> template <typename... Args>
void info(Args... args) { void info(std::format_string<Args...> fmt, Args&&... args) {
log(LEVEL::INFO, args...); log(LEVEL::INFO, fmt, std::forward<Args>(args)...);
} }
template <typename... Args> template <typename... Args>
void verbose(Args... args) { void verbose(std::format_string<Args...> fmt, Args&&... args) {
log(LEVEL::VERBOSE, args...); log(LEVEL::VERBOSE, fmt, std::forward<Args>(args)...);
} }
template <typename... Args> template <typename... Args>
void debug(const char* file, u16 line, Args... args) { void debug(detail::FormatWithLocation<std::type_identity_t<Args>...> fmt,
#ifndef NDEBUG Args&&... args) {
LOG_PREFIX_DEBUG; if constexpr (enabled) {
std::cout << file << "::" << line; detail::write(
((std::cout << ' ' << std::forward<Args>(args)), ...); detail::prefix(LEVEL::DEBUGGING),
std::cout << std::endl; std::format("{}:{} {}", fmt.location.file_name(), fmt.location.line(),
#else std::format(fmt.fmt, std::forward<Args>(args)...)));
(void)file; }
(void)line;
(void)std::initializer_list<int>{((void)args, 0)...};
#endif // !NDEBUG
} }
// trick
#define debug(...) debug(__FILE__, __LINE__, __VA_ARGS__)
template <typename... Args> template <typename... Args>
void trace(Args... args) { void trace(std::format_string<Args...> fmt, Args&&... args) {
log(LEVEL::TRACE, args...); log(LEVEL::TRACE, fmt, std::forward<Args>(args)...);
} }
// Like the functions above, but with no "[LEVEL]" prefix: just the arguments, // Like the functions above, but with no "[LEVEL]" prefix. With no arguments,
// separated by spaces, then a newline. With none, an empty line. // an empty line.
inline void print() { inline void print() {
#ifndef NDEBUG if constexpr (enabled) {
std::cout << std::endl; detail::write("", "");
#endif // !NDEBUG }
} }
template <typename First, typename... Rest> template <typename... Args>
void print(First first, Rest... rest) { void print(std::format_string<Args...> fmt, Args&&... args) {
#ifndef NDEBUG if constexpr (enabled) {
std::cout << first; detail::write("", std::format(fmt, std::forward<Args>(args)...));
((std::cout << ' ' << rest), ...); }
std::cout << std::endl;
#else
(void)first;
(void)std::initializer_list<int>{((void)rest, 0)...};
#endif // !NDEBUG
} }
} // namespace sophia::log } // namespace sophia::log
+4 -7
View File
@@ -111,15 +111,12 @@ class Pool {
}; };
const Slot& validate(Handle<T> handle) const { const Slot& validate(Handle<T> handle) const {
SOPHIA_ASSERT(handle.isValid(), "invalid handle:", name); SOPHIA_ASSERT(handle.isValid(), "invalid handle: {}", name);
SOPHIA_ASSERT(handle.index < slots.size(), name, SOPHIA_ASSERT(handle.index < slots.size(), "{} handle index out of range: {}", name, static_cast<u32>(handle.index));
"handle index out of range:", handle.index);
const Slot& slot = slots[handle.index]; const Slot& slot = slots[handle.index];
SOPHIA_ASSERT(slot.generation == handle.generation, "stale", name, SOPHIA_ASSERT(slot.generation == handle.generation, "stale {} handle (generation mismatch): {}", name, static_cast<u32>(handle.index));
"handle (generation mismatch):", handle.index); SOPHIA_ASSERT(slot.item != nullptr, "{} handle refers to a released item: {}", name, static_cast<u32>(handle.index));
SOPHIA_ASSERT(slot.item != nullptr, name,
"handle refers to a released item:", handle.index);
return slot; return slot;
} }
+1 -12
View File
@@ -35,17 +35,6 @@ namespace sophia {
namespace { namespace {
struct Vertex {
glm::vec2 position;
glm::vec3 color;
};
struct PushConstants {
glm::mat4 transform;
glm::vec4 color;
glm::vec4 data;
};
std::vector<std::string> commandLineArgs; std::vector<std::string> commandLineArgs;
constexpr ClearColor CLEAR_COLOR = {0.01f, 0.01f, 0.02f, 1.0f}; 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(); auto endTime = std::chrono::high_resolution_clock::now();
double totalRuntime = double totalRuntime =
std::chrono::duration<double>(endTime - startTime).count(); std::chrono::duration<double>(endTime - startTime).count();
log::info("Application ran for", totalRuntime, "s"); log::info("Application ran for {:.2f}s", totalRuntime);
} }
void pushMesh(std::vector<CanvasVertex> vertices, std::vector<u32> indices) { void pushMesh(std::vector<CanvasVertex> vertices, std::vector<u32> indices) {
+3 -2
View File
@@ -43,8 +43,9 @@ void MeshRenderSystem::render(vk::CommandBuffer commandBuffer,
u32 available = MAX_INSTANCES_PER_FRAME - this->instanceCursor; u32 available = MAX_INSTANCES_PER_FRAME - this->instanceCursor;
if (instances.size() > available) { if (instances.size() > available) {
log::warning("MeshRenderSystem: frame instance buffer full, dropping", log::warning(
instances.size() - available, "meshes"); "MeshRenderSystem: frame instance buffer full, dropping {} meshes",
instances.size() - available);
} }
Buffer& buffer = *this->instanceBuffers[this->frameIndex]; Buffer& buffer = *this->instanceBuffers[this->frameIndex];
+2 -2
View File
@@ -38,7 +38,7 @@ vk::CommandBuffer RenderContext::beginFrame() {
} }
if (fenceResult != vk::Result::eSuccess) { 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(); vk::Semaphore imageAvailable = frame.imageAvailable.get();
@@ -62,7 +62,7 @@ vk::CommandBuffer RenderContext::beginFrame() {
imageInFlight, vk::True, std::numeric_limits<u64>::max()); imageInFlight, vk::True, std::numeric_limits<u64>::max());
if (fenceResult != vk::Result::eSuccess) { 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));
} }
} }
+8 -8
View File
@@ -106,8 +106,8 @@ void RenderGraph::recordRendering(vk::CommandBuffer commandBuffer, Pass& pass) {
const Image& image = *this->images[color.image.index].image; const Image& image = *this->images[color.image.index].image;
SOPHIA_ASSERT(image.getExtent().width == extent.width && SOPHIA_ASSERT(image.getExtent().width == extent.width &&
image.getExtent().height == extent.height, image.getExtent().height == extent.height,
"Pass", pass.name, "Pass {} has color attachments of different sizes",
"has color attachments of different sizes"); pass.name);
vk::RenderingAttachmentInfo attachment{}; vk::RenderingAttachmentInfo attachment{};
attachment.imageView = image.getView(); 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; const Image& image = *this->images[pass.depthAttachment->image.index].image;
SOPHIA_ASSERT(image.getExtent().width == extent.width && SOPHIA_ASSERT(image.getExtent().width == extent.width &&
image.getExtent().height == extent.height, image.getExtent().height == extent.height,
"Pass", pass.name, "Pass {} has a depth attachment of a different size",
"has a depth attachment of a different size"); pass.name);
depthAttachment.imageView = image.getView(); depthAttachment.imageView = image.getView();
depthAttachment.imageLayout = depthAttachment.imageLayout =
@@ -168,8 +168,8 @@ RenderGraph::PassBuilder& RenderGraph::PassBuilder::writeColor(
RenderGraph::PassBuilder& RenderGraph::PassBuilder::writeDepth( RenderGraph::PassBuilder& RenderGraph::PassBuilder::writeDepth(
ImageRef image, vk::AttachmentLoadOp loadOp, float clearDepth) { ImageRef image, vk::AttachmentLoadOp loadOp, float clearDepth) {
SOPHIA_ASSERT(!this->pass.depthAttachment, "Pass", this->pass.name, SOPHIA_ASSERT(!this->pass.depthAttachment,
"already has a depth attachment"); "Pass {} already has a depth attachment", this->pass.name);
this->use(image, Access::DepthWrite); this->use(image, Access::DepthWrite);
this->pass.depthAttachment = DepthAttachment{image, loadOp, clearDepth}; this->pass.depthAttachment = DepthAttachment{image, loadOp, clearDepth};
return *this; return *this;
@@ -184,8 +184,8 @@ RenderGraph::PassBuilder& RenderGraph::PassBuilder::use(ImageRef image,
SOPHIA_ASSERT(image.isValid() && image.index < this->graph.images.size(), SOPHIA_ASSERT(image.isValid() && image.index < this->graph.images.size(),
"ImageRef doesn't belong to this graph"); "ImageRef doesn't belong to this graph");
for (const ImageUse& existing : this->pass.uses) { for (const ImageUse& existing : this->pass.uses) {
SOPHIA_ASSERT(existing.image.index != image.index, "Pass", this->pass.name, SOPHIA_ASSERT(existing.image.index != image.index,
"uses the same image twice"); "Pass {} uses the same image twice", this->pass.name);
} }
this->pass.uses.push_back({image, access}); this->pass.uses.push_back({image, access});
+3 -2
View File
@@ -45,8 +45,9 @@ void SpriteRenderSystem::render(vk::CommandBuffer commandBuffer,
u32 available = MAX_INSTANCES_PER_FRAME - this->instanceCursor; u32 available = MAX_INSTANCES_PER_FRAME - this->instanceCursor;
if (instances.size() > available) { if (instances.size() > available) {
log::warning("SpriteRenderSystem: frame instance buffer full, dropping", log::warning(
instances.size() - available, "sprites"); "SpriteRenderSystem: frame instance buffer full, dropping {} sprites",
instances.size() - available);
} }
Buffer& buffer = *this->instanceBuffers[this->frameIndex]; Buffer& buffer = *this->instanceBuffers[this->frameIndex];
@@ -26,7 +26,7 @@ TextureHandle ResourceRegistryRef::load<Texture>(const std::string& path) {
// Always decode to RGBA8, whatever the file has. // Always decode to RGBA8, whatever the file has.
stbi_uc* pixels = stbi_load(path.c_str(), &width, &height, &channels, 4); stbi_uc* pixels = stbi_load(path.c_str(), &width, &height, &channels, 4);
if (pixels == nullptr) { 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); throw std::runtime_error("failed to load texture: " + path);
} }
+1 -1
View File
@@ -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."); throw std::runtime_error("Unsupported image layout.");
} }
+2 -5
View File
@@ -36,17 +36,14 @@ Buffer::~Buffer() {
vmaDestroyBuffer(this->device.getAllocator(), this->buffer, this->allocation); vmaDestroyBuffer(this->device.getAllocator(), this->buffer, this->allocation);
} }
// VMA always maps the whole allocation, so `size` only exists to keep the vk::Result Buffer::map() {
// interface; `offset` is applied to the returned pointer.
vk::Result Buffer::map(vk::DeviceSize size, vk::DeviceSize offset) {
SOPHIA_ASSERT(buffer && allocation, "Called map on buffer before create"); SOPHIA_ASSERT(buffer && allocation, "Called map on buffer before create");
(void)size;
void* base = nullptr; void* base = nullptr;
vk::Result result = static_cast<vk::Result>( vk::Result result = static_cast<vk::Result>(
vmaMapMemory(this->device.getAllocator(), this->allocation, &base)); vmaMapMemory(this->device.getAllocator(), this->allocation, &base));
if (result == vk::Result::eSuccess) { if (result == vk::Result::eSuccess) {
this->mapped = static_cast<char*>(base) + offset; this->mapped = base;
} }
return result; return result;
} }
+2 -3
View File
@@ -19,8 +19,7 @@ class Buffer {
vk::DeviceSize minOffsetAlignment = 1); vk::DeviceSize minOffsetAlignment = 1);
~Buffer(); ~Buffer();
vk::Result map(vk::DeviceSize size = VK_WHOLE_SIZE, vk::Result map();
vk::DeviceSize offset = 0);
void unmap(); void unmap();
void writeToBuffer(void* data, vk::DeviceSize size = VK_WHOLE_SIZE, void writeToBuffer(void* data, vk::DeviceSize size = VK_WHOLE_SIZE,
@@ -41,7 +40,7 @@ class Buffer {
void* getMappedMemory() const { return mapped; } void* getMappedMemory() const { return mapped; }
uint32_t getInstanceCount() const { return instanceCount; } uint32_t getInstanceCount() const { return instanceCount; }
vk::DeviceSize getInstanceSize() const { return instanceSize; } 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::BufferUsageFlags getUsageFlags() const { return usageFlags; }
vk::MemoryPropertyFlags getMemoryPropertyFlags() const { vk::MemoryPropertyFlags getMemoryPropertyFlags() const {
return memoryPropertyFlags; return memoryPropertyFlags;
+17 -16
View File
@@ -2,6 +2,7 @@
#include <string.h> #include <string.h>
#include <iostream>
#include <set> #include <set>
#include <sophia/util/logger.hpp> #include <sophia/util/logger.hpp>
@@ -19,16 +20,16 @@ debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT m_severity,
(void)pUser_data; (void)pUser_data;
if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) { if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) {
log::error(pCallback_data->pMessage); log::error("{}", pCallback_data->pMessage);
return VK_SUCCESS; return VK_SUCCESS;
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { } else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
log::warning(pCallback_data->pMessage); log::warning("{}", pCallback_data->pMessage);
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo) { } else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo) {
log::info(pCallback_data->pMessage); log::info("{}", pCallback_data->pMessage);
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose) { } else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose) {
log::verbose(pCallback_data->pMessage); log::verbose("{}", pCallback_data->pMessage);
} else { } else {
log::info(pCallback_data->pMessage); log::info("{}", pCallback_data->pMessage);
} }
return VK_FALSE; return VK_FALSE;
@@ -107,8 +108,8 @@ static void forEachRequiredFeature(DeviceFeatureChain& chain, Fn&& fn) {
static bool checkDeviceFeatureSupport(const vk::PhysicalDevice& device) { static bool checkDeviceFeatureSupport(const vk::PhysicalDevice& device) {
vk::PhysicalDeviceProperties properties = device.getProperties(); vk::PhysicalDeviceProperties properties = device.getProperties();
if (properties.apiVersion < SOPHIA_VULKAN_API_VERSION) { if (properties.apiVersion < SOPHIA_VULKAN_API_VERSION) {
log::verbose("Skipping ", properties.deviceName, log::verbose("Skipping {}: Vulkan 1.3 not supported",
": Vulkan 1.3 not supported"); properties.deviceName.data());
return false; return false;
} }
@@ -119,8 +120,8 @@ static bool checkDeviceFeatureSupport(const vk::PhysicalDevice& device) {
bool allSupported = true; bool allSupported = true;
forEachRequiredFeature(supported, [&](vk::Bool32 has, const char* name) { forEachRequiredFeature(supported, [&](vk::Bool32 has, const char* name) {
if (!has) { if (!has) {
log::verbose("Skipping ", properties.deviceName, log::verbose("Skipping {}: missing device feature {}",
": missing device feature ", name); properties.deviceName.data(), name);
allSupported = false; allSupported = false;
} }
}); });
@@ -197,7 +198,7 @@ void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
this->allocator, &static_cast<const VkBufferCreateInfo&>(bufferInfo), this->allocator, &static_cast<const VkBufferCreateInfo&>(bufferInfo),
&allocInfo, &rawBuffer, &allocation, nullptr); &allocInfo, &rawBuffer, &allocation, nullptr);
if (result != VK_SUCCESS) { if (result != VK_SUCCESS) {
log::fatal("failed to create buffer. Error: ", log::fatal("failed to create buffer. Error: {}",
vk::to_string(static_cast<vk::Result>(result))); vk::to_string(static_cast<vk::Result>(result)));
throw std::runtime_error("failed to create buffer"); throw std::runtime_error("failed to create buffer");
} }
@@ -220,7 +221,7 @@ vk::CommandBuffer Device::beginSingleTimeCommands() {
throw vk::SystemError(result); throw vk::SystemError(result);
} }
} catch (const vk::SystemError& error) { } 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()); error.what());
throw std::runtime_error("failed to allocate single time commandBuffer"); throw std::runtime_error("failed to allocate single time commandBuffer");
} }
@@ -234,7 +235,7 @@ vk::CommandBuffer Device::beginSingleTimeCommands() {
throw vk::SystemError(result); throw vk::SystemError(result);
} }
} catch (const vk::SystemError& error) { } 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()); error.what());
throw std::runtime_error("failed to begin single time commandBuffer"); 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<const VkImageCreateInfo&>(imageInfo), this->allocator, &static_cast<const VkImageCreateInfo&>(imageInfo),
&allocInfo, &rawImage, &allocation, nullptr); &allocInfo, &rawImage, &allocation, nullptr);
if (result != VK_SUCCESS) { if (result != VK_SUCCESS) {
log::fatal("failed to create image. Error: ", log::fatal("failed to create image. Error: {}",
vk::to_string(static_cast<vk::Result>(result))); vk::to_string(static_cast<vk::Result>(result)));
throw std::runtime_error("failed to create image"); throw std::runtime_error("failed to create image");
} }
@@ -374,7 +375,7 @@ std::vector<const char*> Device::getRequiredExtensions() {
throw std::exception(); 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:"); log::verbose("Available extensions:");
for (const auto& e : available_extensions) { for (const auto& e : available_extensions) {
@@ -482,7 +483,7 @@ void Device::pickPhysicalDevice() {
log::fatal("Failed to find any physical devices."); log::fatal("Failed to find any physical devices.");
throw std::exception(); throw std::exception();
} }
log::verbose("Physical Devices count: ", physicalDevices.size()); log::verbose("Physical Devices count: {}", physicalDevices.size());
// Select first physical device that is suitable // Select first physical device that is suitable
for (const auto& device : physicalDevices) { for (const auto& device : physicalDevices) {
@@ -498,7 +499,7 @@ void Device::pickPhysicalDevice() {
} }
this->properties = this->physicalDevice.getProperties(); this->properties = this->physicalDevice.getProperties();
log::verbose("Physical Device: ", this->properties.deviceName); log::verbose("Physical Device: {}", this->properties.deviceName.data());
} }
void Device::createLogicalDevice() { void Device::createLogicalDevice() {
+8 -8
View File
@@ -25,7 +25,7 @@ std::vector<char> readFile(const std::string& path) {
std::ifstream file(path, std::ios::ate | std::ios::binary); std::ifstream file(path, std::ios::ate | std::ios::binary);
if (!file.is_open()) { 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); throw std::runtime_error("failed to open: " + path);
} }
@@ -46,22 +46,23 @@ void Shader::compile(const std::string& path, ShaderStage shaderStage) {
std::vector<char> source = readFile(path); std::vector<char> source = readFile(path);
log::info("Compiling shader: " + path); log::info("Compiling shader: {}", path);
shaderc::Compiler compiler; shaderc::Compiler compiler;
shaderc::SpvCompilationResult result = compiler.CompileGlslToSpv( shaderc::SpvCompilationResult result = compiler.CompileGlslToSpv(
source.data(), source.size(), toShadercKind(shaderStage), path.c_str()); source.data(), source.size(), toShadercKind(shaderStage), path.c_str());
if (result.GetCompilationStatus() != shaderc_compilation_status_success) { 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 + ": " + throw std::runtime_error("failed to compile shader: " + path + ": " +
result.GetErrorMessage()); result.GetErrorMessage());
} }
this->spirv.assign(result.cbegin(), result.cend()); this->spirv.assign(result.cbegin(), result.cend());
log::info("Successfully compiled shader: " + path + " (" + log::info("Successfully compiled shader: {} ({} bytes)", path,
std::to_string(this->spirv.size() * 4) + " bytes)"); this->spirv.size() * 4);
} }
void Shader::load(const std::string& path, ShaderStage shaderStage) { void Shader::load(const std::string& path, ShaderStage shaderStage) {
@@ -70,15 +71,14 @@ void Shader::load(const std::string& path, ShaderStage shaderStage) {
std::vector<char> bytes = readFile(path); std::vector<char> bytes = readFile(path);
if (bytes.size() % sizeof(u32) != 0) { 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); throw std::runtime_error("invalid SPIR-V binary: " + path);
} }
this->spirv.assign(reinterpret_cast<const u32*>(bytes.data()), this->spirv.assign(reinterpret_cast<const u32*>(bytes.data()),
reinterpret_cast<const u32*>(bytes.data() + bytes.size())); reinterpret_cast<const u32*>(bytes.data() + bytes.size()));
log::info("Loaded precompiled shader: " + path + " (" + log::info("Loaded precompiled shader: {} ({} bytes)", path, bytes.size());
std::to_string(bytes.size()) + " bytes)");
} }
Shader::Builder& Shader::Builder::fromGLSL(const std::string& path) { Shader::Builder& Shader::Builder::fromGLSL(const std::string& path) {
+1 -1
View File
@@ -114,7 +114,7 @@ void Swapchain::createSwapchain() {
this->device.get()->createSwapchainKHR(this->swapchainCreateInfo); this->device.get()->createSwapchainKHR(this->swapchainCreateInfo);
// log::trace("created vk::SwapchainKHR"); // log::trace("created vk::SwapchainKHR");
} catch (const vk::SystemError& err) { } 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"); throw std::runtime_error("failed to create swapchain");
} }
} }
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <cstdio>
#include <cstdlib>
#include <source_location>
#include <vulkan/vulkan.hpp>
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<vk::Result>(result), loc);
}
+1 -1
View File
@@ -15,7 +15,7 @@ class Window {
Window(const Window&) = delete; Window(const Window&) = delete;
Window& operator=(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(); ~Window();
bool shouldClose() { return glfwWindowShouldClose(window); } bool shouldClose() { return glfwWindowShouldClose(window); }
@@ -0,0 +1,3 @@
add_executable(conway_game_of_life main.cpp)
target_link_libraries(conway_game_of_life PRIVATE ${PROJECT_NAME})
@@ -0,0 +1,3 @@
#version 450
void main(){}
+13
View File
@@ -0,0 +1,13 @@
#include <iostream>
#include <sophia/application.hpp>
class ConwayGameOfLife : public sophia::Application {
public:
ConwayGameOfLife()
: Application{{.width = 800, .height = 600, .name = "ConwayGameOfLife"}} {
}
void onCreate() override {}
};
SOPHIA_MAIN(ConwayGameOfLife)
+22
View File
@@ -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
)
+77
View File
@@ -0,0 +1,77 @@
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <cstdint>
#include <cstdlib>
#include <sophia/scene_registry.hpp>
#include <sophia/scene_view.hpp>
#include <tracy/Tracy.hpp>
#include <vulkan/vulkan.hpp>
#include "renderer/render_context.hpp"
#include "renderer/render_graph.hpp"
#include "renderer/scene_renderer.hpp"
#include "resources/resource_registry.hpp"
#include "vulkan/device.hpp"
#include "window.hpp"
constexpr uint32_t WIDTH = 1280;
constexpr uint32_t HEIGHT = 720;
constexpr const char* NAME = "sandbox";
int main() {
sophia::Window window{WIDTH, HEIGHT, NAME};
sophia::Device device{&window};
sophia::RenderContext renderContext{window, device};
sophia::ResourceRegistry resourceRegistry{device,
renderContext.getImageFormat()};
sophia::SceneRegistry sceneRegistry;
sophia::SceneRenderer sceneRenderer{sceneRegistry, resourceRegistry, device,
renderContext.getImageFormat()};
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();
vk::CommandBuffer commandBuffer = renderContext.beginFrame();
if (!commandBuffer) {
continue;
}
sceneRenderer.beginFrame(renderContext.getFrameIndex());
sophia::RenderGraph graph{renderContext.getTracyContext()};
sophia::ImageRef color = graph.importImage(renderContext.getCurrentImage(),
sophia::Access::Present);
sophia::ImageRef depth = graph.importImage(
renderContext.getCurrentDepthImage(), sophia::Access::DepthWrite);
graph.addPass(
"scene",
[&](sophia::RenderGraph::PassBuilder& pass) {
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("scene pass");
sceneRenderer.render(commandBuffer, view, view.camera,
renderContext.getExtent());
});
graph.execute(commandBuffer);
renderContext.endFrame();
FrameMark;
}
device.waitIdle();
return EXIT_SUCCESS;
}
View File
+1 -1
View File
@@ -9,7 +9,7 @@ target_link_libraries(${NAME}_deps INTERFACE
${ENGINE_NAME} ${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") set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/build")
add_executable(testbed main.cpp) add_executable(testbed main.cpp)