diff --git a/engine/include/sophia/application.hpp b/engine/include/sophia/application.hpp new file mode 100644 index 0000000..c4fc790 --- /dev/null +++ b/engine/include/sophia/application.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +namespace sophia { + +class Application { + public: + struct Config { + int width; + int height; + std::string name; + bool headless = false; + }; + + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + + Application(const Config& config); + ~Application(); + + void run(); + + private: + class Impl; + std::unique_ptr impl; +}; + +} // namespace sophia diff --git a/engine/include/sophia/event.hpp b/engine/include/sophia/event.hpp new file mode 100644 index 0000000..0ac3e37 --- /dev/null +++ b/engine/include/sophia/event.hpp @@ -0,0 +1,137 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#define BIT(x) (1 << (x)) + +namespace sophia { +// https://github.com/TheCherno/Hazel/blob/1feb70572fa87fa1c4ba784a2cfeada5b4a500db/Hazel/src/Hazel/Events/Event.h + +enum EventCategoryFlags : u32 { + None = 0, + EventCategoryApplication = BIT(0), + EventCategoryInput = BIT(1), + EventCategoryKeyboard = BIT(2), + EventCategoryMouse = BIT(3), + EventCategoryMouseButton = BIT(4) +}; + +class Event { + public: + virtual ~Event() = default; + + virtual const char* getName() const = 0; + virtual std::string toString() const { return getName(); } + + virtual int getCategoryFlags() const { return EventCategoryFlags::None; }; + + inline bool isInCategory(EventCategoryFlags category) { + return getCategoryFlags() & category; + } +}; + +template +class EventDelegate : public Delegate { + public: + EventDelegate() { + static_assert(std::is_base_of::value, + "DerivedEvent must derive from Event"); + } +}; + +inline std::ostream& operator<<(std::ostream& os, const Event& e) { + return os << e.toString(); +} + +/** + * Singleton class for managing event subscription and dispatching. + * + * TODO heavy documentation is needed + * + * @todo impl removing functors from delegate + * @warning this class is in heavy development, use at your own risk + * @warning NOT THREAD SAFE + */ +class EventSystem { + public: + using EventType = decltype(std::declval().hash_code()); + +#define GET_EVENT_TYPE(E) typeid(E).hash_code() + + EventSystem(const EventSystem&) = delete; + EventSystem& operator=(const EventSystem&) = delete; + + static EventSystem& get() { + static EventSystem instance; + return instance; + } + + template + void addListener(std::function f) { + static_assert(std::is_base_of::value, "E must derive from Event"); + + EventType eventType = GET_EVENT_TYPE(E); + + // Ensures pointer to EventDelegate is not null in unordered map + if (!this->eventDelegates.count(eventType)) { + this->eventDelegates[eventType] = + std::make_unique(EventDelegate()); + + log::trace("EventSystem creating new event type: ", eventType); + } + + EventDelegate& delegate = + std::any_cast&>(*eventDelegates[eventType]); + delegate.add(f); + } + + template + void addMethodListener(void (O::*memberFn)(E&), std::shared_ptr obj) { + static_assert(std::is_base_of::value, "E must derive from Event"); + + EventType eventType = GET_EVENT_TYPE(E); + + // Ensures pointer to EventDelegate is not null in unordered map + if (!this->eventDelegates.count(eventType)) { + this->eventDelegates[eventType] = + std::make_unique(EventDelegate()); + + log::trace("EventSystem creating new event type: ", eventType); + } + + EventDelegate& delegate = + std::any_cast&>(*eventDelegates[eventType]); + delegate.addMethod(memberFn, obj); + } + + template + void dispatch(E& event) { + static_assert(std::is_base_of::value, "E must derive from Event"); + EventType eventType = GET_EVENT_TYPE(E); + + if (!this->eventDelegates.count(eventType)) { return; } + + try { + EventDelegate& delegate = + std::any_cast&>(*this->eventDelegates.at(eventType)); + delegate.invoke(event); + } catch (const std::out_of_range& error) { + log::error("event dispatch failed: ", event.toString()); + } + } + + private: + EventSystem() { log::info("EventSystem Initialized: ", this); } + ~EventSystem() { log::info("EventSystem Destroyed"); } + + std::unordered_map> eventDelegates; +}; + +} // namespace sophia diff --git a/engine/include/sophia/frame_info.hpp b/engine/include/sophia/frame_info.hpp new file mode 100644 index 0000000..1cbd013 --- /dev/null +++ b/engine/include/sophia/frame_info.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include + +namespace sophia { + +struct FrameInfo { + u32 frameIndex; + double elapsedTime; + double deltaTime; + glm::vec2 currentFramebufferExtent; +}; + +} // namespace sophia diff --git a/engine/include/sophia/key_codes.hpp b/engine/include/sophia/key_codes.hpp new file mode 100644 index 0000000..d429ac7 --- /dev/null +++ b/engine/include/sophia/key_codes.hpp @@ -0,0 +1,141 @@ +#pragma once + +#define GLFW_INCLUDE_VULKAN +#include + +#include + +namespace sophia { + +using KeyCode = u32; + +namespace Key { + +enum : KeyCode { + Space = GLFW_KEY_SPACE, + Apostrophe = GLFW_KEY_APOSTROPHE, /* ' */ + Comma = GLFW_KEY_COMMA, /* , */ + Minus = GLFW_KEY_MINUS, /* - */ + Period = GLFW_KEY_PERIOD, /* . */ + Slash = GLFW_KEY_SLASH, /* / */ + D0 = GLFW_KEY_0, + D1 = GLFW_KEY_1, + D2 = GLFW_KEY_2, + D3 = GLFW_KEY_3, + D4 = GLFW_KEY_4, + D5 = GLFW_KEY_5, + D6 = GLFW_KEY_6, + D7 = GLFW_KEY_7, + D8 = GLFW_KEY_8, + D9 = GLFW_KEY_9, + Semicolon = GLFW_KEY_SEMICOLON, /* ; */ + Equal = GLFW_KEY_EQUAL, /* = */ + A = GLFW_KEY_A, + B = GLFW_KEY_B, + C = GLFW_KEY_C, + D = GLFW_KEY_D, + E = GLFW_KEY_E, + F = GLFW_KEY_F, + G = GLFW_KEY_G, + H = GLFW_KEY_H, + I = GLFW_KEY_I, + J = GLFW_KEY_J, + K = GLFW_KEY_K, + L = GLFW_KEY_L, + M = GLFW_KEY_M, + N = GLFW_KEY_N, + O = GLFW_KEY_O, + P = GLFW_KEY_P, + Q = GLFW_KEY_Q, + R = GLFW_KEY_R, + S = GLFW_KEY_S, + T = GLFW_KEY_T, + U = GLFW_KEY_U, + V = GLFW_KEY_V, + W = GLFW_KEY_W, + X = GLFW_KEY_X, + Y = GLFW_KEY_Y, + Z = GLFW_KEY_Z, + LeftBracket = GLFW_KEY_LEFT_BRACKET, /* [ */ + Backslash = GLFW_KEY_BACKSLASH, /* \ */ + RightBracket = GLFW_KEY_RIGHT_BRACKET, /* ] */ + GraveAccent = GLFW_KEY_GRAVE_ACCENT, /* ` */ + World1 = GLFW_KEY_WORLD_1, /* non-US #1 */ + World2 = GLFW_KEY_WORLD_2, /* non-US #2 */ + + /* Function keys */ + Escape = GLFW_KEY_ESCAPE, + Enter = GLFW_KEY_ENTER, + Tab = GLFW_KEY_TAB, + Backspace = GLFW_KEY_BACKSPACE, + Insert = GLFW_KEY_INSERT, + Delete = GLFW_KEY_DELETE, + Right = GLFW_KEY_RIGHT, + Left = GLFW_KEY_LEFT, + Down = GLFW_KEY_DOWN, + Up = GLFW_KEY_UP, + PageUp = GLFW_KEY_PAGE_UP, + PageDown = GLFW_KEY_PAGE_DOWN, + Home = GLFW_KEY_HOME, + End = GLFW_KEY_END, + CapsLock = GLFW_KEY_CAPS_LOCK, + ScrollLock = GLFW_KEY_SCROLL_LOCK, + NumLock = GLFW_KEY_NUM_LOCK, + PrintScreen = GLFW_KEY_PRINT_SCREEN, + Pause = GLFW_KEY_PAUSE, + F1 = GLFW_KEY_F1, + F2 = GLFW_KEY_F2, + F3 = GLFW_KEY_F3, + F4 = GLFW_KEY_F4, + F5 = GLFW_KEY_F5, + F6 = GLFW_KEY_F6, + F7 = GLFW_KEY_F7, + F8 = GLFW_KEY_F8, + F9 = GLFW_KEY_F9, + F10 = GLFW_KEY_F10, + F11 = GLFW_KEY_F11, + F12 = GLFW_KEY_F12, + F13 = GLFW_KEY_F13, + F14 = GLFW_KEY_F14, + F15 = GLFW_KEY_F15, + F16 = GLFW_KEY_F16, + F17 = GLFW_KEY_F17, + F18 = GLFW_KEY_F18, + F19 = GLFW_KEY_F19, + F20 = GLFW_KEY_F20, + F21 = GLFW_KEY_F21, + F22 = GLFW_KEY_F22, + F23 = GLFW_KEY_F23, + F24 = GLFW_KEY_F24, + F25 = GLFW_KEY_F25, + KP0 = GLFW_KEY_KP_0, + KP1 = GLFW_KEY_KP_1, + KP2 = GLFW_KEY_KP_2, + KP3 = GLFW_KEY_KP_3, + KP4 = GLFW_KEY_KP_4, + KP5 = GLFW_KEY_KP_5, + KP6 = GLFW_KEY_KP_6, + KP7 = GLFW_KEY_KP_7, + KP8 = GLFW_KEY_KP_8, + KP9 = GLFW_KEY_KP_9, + KPDecimal = GLFW_KEY_KP_DECIMAL, + KPDivide = GLFW_KEY_KP_DIVIDE, + KPMultiply = GLFW_KEY_KP_MULTIPLY, + KPSubtract = GLFW_KEY_KP_SUBTRACT, + KPAdd = GLFW_KEY_KP_ADD, + KPEnter = GLFW_KEY_KP_ENTER, + KPEqual = GLFW_KEY_KP_EQUAL, + LeftShift = GLFW_KEY_LEFT_SHIFT, + LeftControl = GLFW_KEY_LEFT_CONTROL, + LeftAlt = GLFW_KEY_LEFT_ALT, + LeftSuper = GLFW_KEY_LEFT_SUPER, + RightShift = GLFW_KEY_RIGHT_SHIFT, + RightControl = GLFW_KEY_RIGHT_CONTROL, + RightAlt = GLFW_KEY_RIGHT_ALT, + RightSuper = GLFW_KEY_RIGHT_SUPER, + Menu = GLFW_KEY_MENU, +}; + +} // namespace Key + +} // namespace sophia diff --git a/engine/include/sophia/key_event.hpp b/engine/include/sophia/key_event.hpp new file mode 100644 index 0000000..824f8df --- /dev/null +++ b/engine/include/sophia/key_event.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include + +#include +#include + +namespace sophia { + +class KeyEvent : public Event { + public: + KeyCode getKeyCode() const { return this->keycode; } + + virtual int getCategoryFlags() const override { + return EventCategoryKeyboard | EventCategoryInput; + } + + protected: + KeyEvent(const KeyCode keycode) : keycode{keycode} {} + + KeyCode keycode; +}; + +class KeyPressedEvent : public KeyEvent { + public: + KeyPressedEvent(const KeyCode keycode, bool repeated = false) + : KeyEvent{keycode}, repeated{repeated} {} + + bool isRepeat() const { return this->repeated; } + + std::string toString() const override { + std::stringstream ss; + ss << "KeyPressedEvent: " << this->keycode + << " (repeat = " << this->repeated << ")"; + return ss.str(); + } + + virtual const char* getName() const override { return "KeyPressed"; } + + private: + bool repeated; +}; + +class KeyReleasedEvent : public KeyEvent { + public: + KeyReleasedEvent(const KeyCode keycode) : KeyEvent{keycode} {} + + std::string toString() const override { + std::stringstream ss; + ss << "KeyReleasedEvent: " << this->keycode; + return ss.str(); + } + + virtual const char* getName() const override { return "KeyReleased"; } +}; + +class KeyTypedEvent : public KeyEvent { + public: + KeyTypedEvent(const KeyCode keycode) : KeyEvent{keycode} {} + + std::string toString() const override { + std::stringstream ss; + ss << "KeyTypedEvent: " << this->keycode; + return ss.str(); + } + + virtual const char* getName() const override { return "KeyTyped"; } +}; + +} // namespace sophia diff --git a/engine/include/sophia/types.hpp b/engine/include/sophia/types.hpp new file mode 100644 index 0000000..83fc851 --- /dev/null +++ b/engine/include/sophia/types.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace sophia { + +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t; + +using s8 = int8_t; +using s16 = int16_t; +using s32 = int32_t; +using s64 = int64_t; + +using f32 = float; +using f64 = double; + +} // namespace sophia diff --git a/engine/include/util/delegate.hpp b/engine/include/sophia/util/delegate.hpp similarity index 97% rename from engine/include/util/delegate.hpp rename to engine/include/sophia/util/delegate.hpp index e59b15d..c526c06 100644 --- a/engine/include/util/delegate.hpp +++ b/engine/include/sophia/util/delegate.hpp @@ -7,11 +7,11 @@ #include #include -#include "util/logger.hpp" +#include #define DEFAULT_DELEGATE_FUNCTOR_ALLOC 5 -namespace hep { +namespace sophia { class expired_weak_object : public std::runtime_error { public: @@ -108,4 +108,4 @@ class Delegate { std::vector functors; }; -} // namespace hep +} // namespace sophia diff --git a/engine/include/sophia/util/logger.hpp b/engine/include/sophia/util/logger.hpp new file mode 100644 index 0000000..cc3c17f --- /dev/null +++ b/engine/include/sophia/util/logger.hpp @@ -0,0 +1,126 @@ +/** + * Lots of work still needs to be done in here. + * + * Usage: + * using namespace sophia; + * log::fatal("fatal message") + * log::info("Infomation message") + * + * TODO Impl some form of log error handling + */ + +#pragma once + +#include +#include + +#include + +namespace sophia::log { + +enum class LEVEL { + NONE, + FATAL, + ERROR, + WARNING, + INFO, + VERBOSE, + DEBUGGING, + 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 ") + +template +void log(LEVEL level = LEVEL::NONE, Args... args) { +#ifndef NDEBUG + switch (level) { + case LEVEL::FATAL: + LOG_PREFIX_FATAL; + break; + case LEVEL::ERROR: + LOG_PREFIX_ERROR; + break; + case LEVEL::WARNING: + LOG_PREFIX_WARNING; + break; + case LEVEL::INFO: + LOG_PREFIX_INFO; + break; + case LEVEL::VERBOSE: + LOG_PREFIX_VERBOSE; + break; + // case LEVEL::DEBUGGING: + // LOG_PREFIX_DEBUG; + // break; + case LEVEL::TRACE: + LOG_PREFIX_TRACE; + break; + default: + std::cout << "\033[1;90m[LOG]\033[0m "; + break; + } + + ((std::cout << ' ' << std::forward(args)), ...); + std::cout << std::endl; +#else + (void)level; + (void)std::initializer_list{((void)args, 0)...}; +#endif // !NDEBUG +} + +template +void fatal(Args... args) { + log(LEVEL::FATAL, args...); +} + +template +void error(Args... args) { + log(LEVEL::ERROR, args...); +} + +template +void warning(Args... args) { + log(LEVEL::WARNING, args...); +} + +template +void info(Args... args) { + log(LEVEL::INFO, args...); +} + +template +void verbose(Args... args) { + log(LEVEL::VERBOSE, 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 +} +// trick +#define debug(...) debug(__FILE__, __LINE__, __VA_ARGS__) + +template +void trace(Args... args) { + log(LEVEL::TRACE, args...); +} + +} // namespace sophia::log diff --git a/engine/include/types.hpp b/engine/include/types.hpp deleted file mode 100644 index 0f1ec60..0000000 --- a/engine/include/types.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -namespace hep -{ - - using u8 = uint8_t; - using u16 = uint16_t; - using u32 = uint32_t; - using u64 = uint64_t; - - using s8 = int8_t; - using s16 = int16_t; - using s32 = int32_t; - using s64 = int64_t; - - using f32 = float; - using f64 = double; - -} // namespace hep diff --git a/engine/include/util/logger.hpp b/engine/include/util/logger.hpp deleted file mode 100644 index 94ad350..0000000 --- a/engine/include/util/logger.hpp +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Lots of work still needs to be done in here. - * - * Usage: - * using namespace hep; - * log::fatal("fatal message") - * log::info("Infomation message") - * - * TODO Impl some form of log error handling - */ - -#pragma once - -#include -#include - -#include "types.hpp" - -namespace hep -{ - namespace log - { - - enum class LEVEL - { - NONE, - FATAL, - ERROR, - WARNING, - INFO, - VERBOSE, - DEBUGGING, - 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 ") - - template - void log(LEVEL level = LEVEL::NONE, Args... args) - { -#ifndef NDEBUG - switch (level) - { - case LEVEL::FATAL: - LOG_PREFIX_FATAL; - break; - case LEVEL::ERROR: - LOG_PREFIX_ERROR; - break; - case LEVEL::WARNING: - LOG_PREFIX_WARNING; - break; - case LEVEL::INFO: - LOG_PREFIX_INFO; - break; - case LEVEL::VERBOSE: - LOG_PREFIX_VERBOSE; - break; - // case LEVEL::DEBUGGING: - // LOG_PREFIX_DEBUG; - // break; - case LEVEL::TRACE: - LOG_PREFIX_TRACE; - break; - default: - std::cout << "\033[1;90m[LOG]\033[0m "; - break; - } - - ((std::cout << ' ' << std::forward(args)), ...); - std::cout << std::endl; -#else - (void)level; - (void)std::initializer_list{((void)args, 0)...}; -#endif // !NDEBUG - } - - template - void fatal(Args... args) - { - log(LEVEL::FATAL, args...); - } - - template - void error(Args... args) - { - log(LEVEL::ERROR, args...); - } - - template - void warning(Args... args) - { - log(LEVEL::WARNING, args...); - } - - template - void info(Args... args) - { - log(LEVEL::INFO, args...); - } - - template - void verbose(Args... args) - { - log(LEVEL::VERBOSE, 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 - } -// trick -#define debug(...) debug(__FILE__, __LINE__, __VA_ARGS__) - - template - void trace(Args... args) - { - log(LEVEL::TRACE, args...); - } - - } // namespace log -} // namespace hep diff --git a/engine/src/application.cpp b/engine/src/application.cpp new file mode 100644 index 0000000..675813a --- /dev/null +++ b/engine/src/application.cpp @@ -0,0 +1,25 @@ +#include + +#include "engine.hpp" + +namespace sophia { + +class Application::Impl { + public: + Impl(const Application::Config& config) + : engine{{config.width, config.height, config.name}} {} + + void run() { engine.run(); } + + private: + Engine engine; +}; + +Application::Application(const Config& config) + : impl{std::make_unique(config)} {} + +Application::~Application() = default; + +void Application::run() { impl->run(); } + +} // namespace sophia diff --git a/engine/src/buffer.cpp b/engine/src/buffer.cpp new file mode 100644 index 0000000..2b42268 --- /dev/null +++ b/engine/src/buffer.cpp @@ -0,0 +1,107 @@ +/** + * Based on + * https://github.com/blurrypiano/littleVulkanEngine/blob/main/src/lve_buffer.cpp + */ +#include "buffer.hpp" + +namespace sophia { + +VkDeviceSize Buffer::getAlignment(VkDeviceSize instanceSize, + VkDeviceSize minOffsetAlignment) { + if (minOffsetAlignment > 0) { + return (instanceSize + minOffsetAlignment - 1) & ~(minOffsetAlignment - 1); + } + return instanceSize; +} + +Buffer::Buffer(Device& device, vk::DeviceSize instanceSize, u32 instanceCount, + vk::BufferUsageFlags usageFlags, + vk::MemoryPropertyFlags memoryPropertyFlags, + vk::DeviceSize minOffsetAlignment) + : device{device}, + instanceSize{instanceSize}, + instanceCount{instanceCount}, + usageFlags{usageFlags}, + memoryPropertyFlags{memoryPropertyFlags} { + alignmentSize = getAlignment(instanceSize, minOffsetAlignment); + bufferSize = alignmentSize * instanceCount; + device.createBuffer(bufferSize, usageFlags, memoryPropertyFlags, buffer, + memory); +} + +Buffer::~Buffer() { + unmap(); + this->device.get()->destroyBuffer(this->buffer); + this->device.get()->freeMemory(this->memory); +} + +vk::Result Buffer::map(vk::DeviceSize size, vk::DeviceSize offset) { + assert(buffer && memory && "Called map on buffer before create"); + + vk::MemoryMapFlags emptyFlag{}; + return this->device.get()->mapMemory(this->memory, offset, size, emptyFlag, + &this->mapped); +} + +void Buffer::unmap() { + if (mapped) { + this->device.get()->unmapMemory(this->memory); + mapped = nullptr; + } +} + +void Buffer::writeToBuffer(void* data, vk::DeviceSize size, + vk::DeviceSize offset) { + assert(mapped && "Cannot copy to unmapped buffer"); + + if (size == VK_WHOLE_SIZE) { + memcpy(mapped, data, bufferSize); + } else { + char* memOffset = (char*)mapped; + memOffset += offset; + memcpy(memOffset, data, size); + } +} + +vk::Result Buffer::flush(vk::DeviceSize size, vk::DeviceSize offset) { + vk::MappedMemoryRange mappedRange = {}; + mappedRange.memory = memory; + mappedRange.offset = offset; + mappedRange.size = size; + return this->device.get()->flushMappedMemoryRanges(1, &mappedRange); +} + +vk::Result Buffer::invalidate(vk::DeviceSize size, vk::DeviceSize offset) { + vk::MappedMemoryRange mappedRange = {}; + mappedRange.memory = memory; + mappedRange.offset = offset; + mappedRange.size = size; + return this->device.get()->invalidateMappedMemoryRanges(1, &mappedRange); +} + +vk::DescriptorBufferInfo Buffer::descriptorInfo(vk::DeviceSize size, + vk::DeviceSize offset) { + return vk::DescriptorBufferInfo{ + this->buffer, + offset, + size, + }; +} + +void Buffer::writeToIndex(void* data, int index) { + writeToBuffer(data, instanceSize, index * alignmentSize); +} + +vk::Result Buffer::flushIndex(int index) { + return flush(alignmentSize, index * alignmentSize); +} + +vk::DescriptorBufferInfo Buffer::descriptorInfoForIndex(int index) { + return descriptorInfo(alignmentSize, index * alignmentSize); +} + +vk::Result Buffer::invalidateIndex(int index) { + return invalidate(alignmentSize, index * alignmentSize); +} + +} // namespace sophia diff --git a/engine/src/buffer.hpp b/engine/src/buffer.hpp new file mode 100644 index 0000000..7c313fa --- /dev/null +++ b/engine/src/buffer.hpp @@ -0,0 +1,68 @@ +/** + * Based on + * https://github.com/blurrypiano/littleVulkanEngine/blob/main/src/lve_buffer.hpp + */ +#pragma once + +#include "device.hpp" + +namespace sophia { + +class Buffer { + public: + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + + Buffer(Device& device, vk::DeviceSize instanceSize, u32 instanceCount, + vk::BufferUsageFlags usageFlags, + vk::MemoryPropertyFlags memoryPropertyFlags, + vk::DeviceSize minOffsetAlignment = 1); + ~Buffer(); + + vk::Result map(vk::DeviceSize size = VK_WHOLE_SIZE, + vk::DeviceSize offset = 0); + void unmap(); + + void writeToBuffer(void* data, vk::DeviceSize size = VK_WHOLE_SIZE, + vk::DeviceSize offset = 0); + vk::Result flush(vk::DeviceSize size = VK_WHOLE_SIZE, + vk::DeviceSize offset = 0); + vk::DescriptorBufferInfo descriptorInfo(vk::DeviceSize size = VK_WHOLE_SIZE, + vk::DeviceSize offset = 0); + vk::Result invalidate(vk::DeviceSize size = VK_WHOLE_SIZE, + vk::DeviceSize offset = 0); + + void writeToIndex(void* data, int index); + vk::Result flushIndex(int index); + vk::DescriptorBufferInfo descriptorInfoForIndex(int index); + vk::Result invalidateIndex(int index); + + vk::Buffer getBuffer() const { return 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::BufferUsageFlags getUsageFlags() const { return usageFlags; } + vk::MemoryPropertyFlags getMemoryPropertyFlags() const { + return memoryPropertyFlags; + } + vk::DeviceSize getBufferSize() const { return bufferSize; } + + private: + static vk::DeviceSize getAlignment(vk::DeviceSize instanceSize, + vk::DeviceSize minOffsetAlignment); + + Device& device; + void* mapped = nullptr; + vk::Buffer buffer = VK_NULL_HANDLE; + vk::DeviceMemory memory = VK_NULL_HANDLE; + + vk::DeviceSize bufferSize; + uint32_t instanceCount; + vk::DeviceSize instanceSize; + vk::DeviceSize alignmentSize; + vk::BufferUsageFlags usageFlags; + vk::MemoryPropertyFlags memoryPropertyFlags; +}; + +} // namespace sophia diff --git a/engine/src/compute_pipeline.cpp b/engine/src/compute_pipeline.cpp new file mode 100644 index 0000000..0b2143c --- /dev/null +++ b/engine/src/compute_pipeline.cpp @@ -0,0 +1,72 @@ +#include "compute_pipeline.hpp" + +#include +#include + +namespace sophia { + +ComputePipeline::ComputePipeline(Device& device) : device{device} {} + +ComputePipeline::~ComputePipeline() { + log::trace("destoryed vk::Pipeline (compute)"); +} + +void ComputePipeline::create(Shader& shader, + vk::PipelineLayout pipelineLayout) { + if (shader.getStage() != ShaderStage::COMPUTE) { + log::fatal( + "failed to create compute pipeline: shader is not a compute shader"); + throw std::runtime_error( + "failed to create compute pipeline: shader is not a compute shader"); + } + + if (pipelineLayout == VK_NULL_HANDLE) { + log::fatal( + "failed to create compute pipeline: no vk::PipelineLayout provided"); + throw std::runtime_error( + "failed to create compute pipeline: no vk::PipelineLayout provided"); + } + + vk::UniqueShaderModule computeShaderModule = + createShaderModule(shader.getSpirv()); + log::trace("created compute shader module"); + + vk::PipelineShaderStageCreateInfo shaderStage{ + vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eCompute, + computeShaderModule.get(), "main"}; + + vk::ComputePipelineCreateInfo pipelineInfo{}; + pipelineInfo.stage = shaderStage; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.basePipelineHandle = nullptr; + + try { + this->computePipeline = + this->device.get() + ->createComputePipelineUnique(nullptr, pipelineInfo) + .value; + log::trace("created vk::Pipeline (compute)"); + } catch (const vk::SystemError& err) { + log::fatal("failed to create vk::Pipeline (compute)"); + throw std::runtime_error("failed to create vk::Pipeline (compute)"); + } +} + +void ComputePipeline::bind(vk::CommandBuffer commandBuffer) { + commandBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, + this->computePipeline.get()); +} + +vk::UniqueShaderModule ComputePipeline::createShaderModule( + const std::vector& spirv) { + try { + return device.get()->createShaderModuleUnique( + {vk::ShaderModuleCreateFlags(), spirv.size() * sizeof(u32), + spirv.data()}); + } catch (const vk::SystemError& err) { + log::fatal("failed to create shader module"); + throw std::runtime_error("failed to create shader module"); + } +} + +} // namespace sophia diff --git a/engine/src/compute_pipeline.hpp b/engine/src/compute_pipeline.hpp new file mode 100644 index 0000000..3a9f2bb --- /dev/null +++ b/engine/src/compute_pipeline.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "device.hpp" +#include "shader.hpp" + +namespace sophia { + +class ComputePipeline { + public: + ComputePipeline(const ComputePipeline&) = delete; + ComputePipeline& operator=(const ComputePipeline&) = delete; + + ComputePipeline(Device& device); + ~ComputePipeline(); + + void create(Shader& shader, vk::PipelineLayout pipelineLayout); + + void bind(vk::CommandBuffer commandBuffer); + + private: + vk::UniqueShaderModule createShaderModule(const std::vector& spirv); + + Device& device; + vk::UniquePipeline computePipeline; +}; + +} // namespace sophia diff --git a/engine/src/device.cpp b/engine/src/device.cpp index 1ddc6e5..55ec201 100644 --- a/engine/src/device.cpp +++ b/engine/src/device.cpp @@ -3,24 +3,23 @@ #include #include +#include -#include "util/logger.hpp" +namespace sophia { -namespace hep { - -static VKAPI_ATTR VkBool32 VKAPI_CALL -debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT m_severity, - VkDebugUtilsMessageTypeFlagsEXT m_type, - const VkDebugUtilsMessengerCallbackDataEXT* pCallback_data, +static VKAPI_ATTR vk::Bool32 VKAPI_CALL +debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT m_severity, + vk::DebugUtilsMessageTypeFlagsEXT m_type, + const vk::DebugUtilsMessengerCallbackDataEXT* pCallback_data, void* pUser_data) { (void)m_type; (void)pCallback_data; (void)pUser_data; - if (m_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) { log::error(pCallback_data->pMessage); return VK_SUCCESS; - } else if (m_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + } else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { log::warning(pCallback_data->pMessage); } else { log::info(pCallback_data->pMessage); @@ -30,8 +29,7 @@ debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT m_severity, } VkResult createDebugUtilsMessengerEXT( - VkInstance instance, - const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pCallback) { auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( @@ -48,7 +46,9 @@ void destroyDebugUtilsMessengerEXT(VkInstance instance, const VkAllocationCallbacks* pAllocator) { auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( instance, "vkDestroyDebugUtilsMessengerEXT"); - if (func != nullptr) { func(instance, callback, pAllocator); } + if (func != nullptr) { + func(instance, callback, pAllocator); + } } Device::Device(Window& window) : window{window} { @@ -91,8 +91,7 @@ u32 Device::findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) { } vk::Format Device::findSupportedFormat( - const std::vector& candidates, - vk::ImageTiling tiling, + const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) { for (vk::Format format : candidates) { vk::FormatProperties properties = @@ -110,11 +109,9 @@ vk::Format Device::findSupportedFormat( throw std::runtime_error("failed to find supported format"); } -void Device::createBuffer(vk::DeviceSize size, - vk::BufferUsageFlags usage, +void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, - vk::Buffer& buffer, - vk::DeviceMemory& bufferMemory) { + vk::Buffer& buffer, vk::DeviceMemory& bufferMemory) { vk::BufferCreateInfo bufferInfo = {}; bufferInfo.size = size; bufferInfo.usage = usage; @@ -156,7 +153,9 @@ vk::CommandBuffer Device::beginSingleTimeCommands() { try { vk::Result result = this->device->allocateCommandBuffers(&allocInfo, &commandBuffer); - if (result != vk::Result::eSuccess) { throw vk::SystemError(result); } + if (result != vk::Result::eSuccess) { + throw vk::SystemError(result); + } } catch (const vk::SystemError& error) { log::error("failed to allocate single time commandBuffer. Error: ", error.what()); @@ -168,7 +167,9 @@ vk::CommandBuffer Device::beginSingleTimeCommands() { try { vk::Result result = commandBuffer.begin(&beginInfo); - if (result != vk::Result::eSuccess) { throw vk::SystemError(result); } + if (result != vk::Result::eSuccess) { + throw vk::SystemError(result); + } } catch (const vk::SystemError& error) { log::error("failed to begin single time commandBuffer. Error: ", error.what()); @@ -191,8 +192,7 @@ void Device::endSingleTimeCommands(vk::CommandBuffer commandBuffer) { this->device->freeCommandBuffers(commandPool, commandBuffer); } -void Device::copyBuffer(vk::Buffer sourceBuffer, - vk::Buffer destinationBuffer, +void Device::copyBuffer(vk::Buffer sourceBuffer, vk::Buffer destinationBuffer, vk::DeviceSize size) { vk::CommandBuffer commandBuffer = beginSingleTimeCommands(); @@ -210,7 +210,9 @@ void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo, vk::DeviceMemory& imageMemory) { try { vk::Result result = this->device->createImage(&imageInfo, nullptr, &image); - if (result != vk::Result::eSuccess) { throw vk::SystemError(result); } + if (result != vk::Result::eSuccess) { + throw vk::SystemError(result); + } } catch (const vk::SystemError& error) { log::fatal("failed to create image. Error: ", error.what()); throw std::runtime_error("failed to create image"); @@ -227,7 +229,9 @@ void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo, try { vk::Result result = this->device->allocateMemory(&allocateInfo, nullptr, &imageMemory); - if (result != vk::Result::eSuccess) { throw vk::SystemError(result); } + if (result != vk::Result::eSuccess) { + throw vk::SystemError(result); + } } catch (const vk::SystemError& error) { log::fatal("failed to allocate image memory. Error: ", error.what()); throw std::runtime_error("failed to allocate image memory"); @@ -243,7 +247,7 @@ void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo, void Device::populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo) { initInfo.Instance = this->instance.get(); - initInfo.ApiVersion = HEP_VULKAN_API_VERSION; + initInfo.ApiVersion = SOPHIA_VULKAN_API_VERSION; initInfo.PhysicalDevice = this->physicalDevice; initInfo.Device = this->device.get(); @@ -286,7 +290,9 @@ bool Device::checkValidationLayerSupport() { } } - if (!layerFound) { return false; } + if (!layerFound) { + return false; + } } return true; @@ -309,7 +315,9 @@ std::vector Device::getRequiredExtensions() { vk::Result result = vk::enumerateInstanceExtensionProperties( nullptr, &available_extension_count, nullptr); - if (result != vk::Result::eSuccess) { throw std::exception(); } + if (result != vk::Result::eSuccess) { + throw std::exception(); + } std::vector available_extensions( available_extension_count); @@ -317,7 +325,9 @@ std::vector Device::getRequiredExtensions() { result = vk::enumerateInstanceExtensionProperties( nullptr, &available_extension_count, available_extensions.data()); - if (result != vk::Result::eSuccess) { throw std::exception(); } + if (result != vk::Result::eSuccess) { + throw std::exception(); + } log::verbose("Number of available extensions: ", available_extension_count); @@ -326,7 +336,9 @@ std::vector Device::getRequiredExtensions() { std::cout << '\t' << e.extensionName << '\n'; } std::cout << "Required extensions:\n"; - for (const auto& e : extensions) { std::cout << "\t" << e << '\n'; } + for (const auto& e : extensions) { + std::cout << "\t" << e << '\n'; + } #endif return extensions; @@ -373,7 +385,9 @@ QueueFamilyIndices Device::findQueueFamilies(vk::PhysicalDevice device) { indices.presentFamily = i; } - if (indices.isComplete()) { break; } + if (indices.isComplete()) { + break; + } i++; } @@ -457,12 +471,6 @@ void Device::createLogicalDevice() { static_cast(this->enabledExtensions.size()); createInfo.ppEnabledExtensionNames = this->enabledExtensions.data(); - if (this->enableValidationLayers) { - createInfo.enabledLayerCount = - static_cast(this->enabledLayers.size()); - createInfo.ppEnabledLayerNames = this->enabledLayers.data(); - } - try { this->device = this->physicalDevice.createDeviceUnique(createInfo); log::trace("created vk::Device."); @@ -502,4 +510,4 @@ void Device::createCommandPool() { } } -} // namespace hep +} // namespace sophia diff --git a/engine/include/device.hpp b/engine/src/device.hpp similarity index 86% rename from engine/include/device.hpp rename to engine/src/device.hpp index ed49da5..dd6ad4e 100644 --- a/engine/include/device.hpp +++ b/engine/src/device.hpp @@ -4,15 +4,15 @@ #include #include +#include #include #include -#include "types.hpp" #include "window.hpp" -#define HEP_VULKAN_API_VERSION VK_API_VERSION_1_3 +#define SOPHIA_VULKAN_API_VERSION VK_API_VERSION_1_3 -namespace hep { +namespace sophia { struct QueueFamilyIndices { std::optional graphicsFamily; @@ -65,23 +65,19 @@ class Device { vk::ImageTiling tiling, vk::FormatFeatureFlags features); - void createBuffer(vk::DeviceSize size, - vk::BufferUsageFlags usage, - vk::MemoryPropertyFlags properties, - vk::Buffer& buffer, + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, + vk::MemoryPropertyFlags properties, vk::Buffer& buffer, vk::DeviceMemory& bufferMemory); vk::CommandBuffer beginSingleTimeCommands(); void endSingleTimeCommands(vk::CommandBuffer commandBuffer); - void copyBuffer(vk::Buffer sourceBuffer, - vk::Buffer destinationBuffer, + void copyBuffer(vk::Buffer sourceBuffer, vk::Buffer destinationBuffer, vk::DeviceSize size); void createImageWithInfo(const vk::ImageCreateInfo& imageInfo, - vk::MemoryPropertyFlags properties, - vk::Image& image, + vk::MemoryPropertyFlags properties, vk::Image& image, vk::DeviceMemory& imageMemory); void populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo); @@ -131,4 +127,4 @@ class Device { VK_KHR_SWAPCHAIN_EXTENSION_NAME}; }; -} // namespace hep +} // namespace sophia diff --git a/engine/src/engine.cpp b/engine/src/engine.cpp new file mode 100644 index 0000000..846582c --- /dev/null +++ b/engine/src/engine.cpp @@ -0,0 +1,76 @@ +#include "engine.hpp" + +#include +#include +#include +#include + +namespace sophia { + +Engine::Engine(const Config& config) + : config{config}, + window{config.width, config.height, config.name}, + device{this->window} // renderer{this->window, this->device} +{ + // this->imguiDescriptorPool = + // DescriptorPool::Builder(this->device) + // .addPoolSize(vk::DescriptorType::eCombinedImageSampler, + // IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE + // + 5) + // .setPoolFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet) + // .setMaxSets(IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE + // + 5) + // .build(); + + // this->uiManager = + // UIManager::Builder(this->window, this->device, this->renderer, + // imguiDescriptorPool->get()) + // .darkTheme() + // .setDocking(true) + // .setKeyboard(true) + // .build(); +}; + +Engine::~Engine() { this->device.waitIdle(); }; + +void Engine::run() { + auto startTime = std::chrono::high_resolution_clock::now(); + auto currentTime = startTime; + + // EventSystem::get().addListener( + // std::bind(&Application::onEvent, this, std::placeholders::_1)); + + while (this->isRunning) { + glfwPollEvents(); + + auto newTime = std::chrono::high_resolution_clock::now(); + double deltaTime = + std::chrono::duration(newTime - currentTime).count(); + currentTime = newTime; + + // // Attempt to start a new frame + // vk::CommandBuffer commandBuffer = this->renderer.beginFrame(); + // if (commandBuffer != nullptr) { + // double elapsedTime = + // std::chrono::duration(currentTime - startTime).count(); + + // vk::Extent2D extent = this->renderer.getCurrentFramebufferExtent(); + // glm::vec2 extentVec2(static_cast(extent.width), + // static_cast(extent.height)); + + // FrameInfo frameInfo{this->renderer.getFrameIndex(), elapsedTime, + // deltaTime, extentVec2}; + // } + + this->isRunning = false; + } + + this->device.waitIdle(); + + auto endTime = std::chrono::high_resolution_clock::now(); + double totalRuntime = + std::chrono::duration(endTime - startTime).count(); + log::info("Application ran for", totalRuntime, "s"); +} + +} // namespace sophia diff --git a/engine/src/engine.hpp b/engine/src/engine.hpp new file mode 100644 index 0000000..64c6d0b --- /dev/null +++ b/engine/src/engine.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "device.hpp" +#include "window.hpp" + +namespace sophia { + +class Engine { + public: + struct Config { + int width; + int height; + std::string name; + }; + + Engine(const Engine&) = delete; + Engine& operator=(const Engine&) = delete; + + Engine(const Config& config); + ~Engine(); + + void run(); + + private: + Config config; + + Window window; + Device device; + + bool isRunning = true; +}; + +} // namespace sophia diff --git a/engine/src/image.cpp b/engine/src/image.cpp new file mode 100644 index 0000000..baad2f9 --- /dev/null +++ b/engine/src/image.cpp @@ -0,0 +1,324 @@ +#include "image.hpp" + +#include +#include +#include + +#include "device.hpp" + +namespace sophia { + +void Image::transitionLayout(vk::CommandBuffer commandBuffer, + vk::ImageLayout newLayout) { + vk::ImageMemoryBarrier barrier{}; + barrier.oldLayout = this->layout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = this->get(); + barrier.subresourceRange.aspectMask = this->aspectMask; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = this->mipLevels; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = this->arrayLayers; + + vk::PipelineStageFlags srcStage; + vk::PipelineStageFlags dstStage; + + if (this->layout == vk::ImageLayout::eUndefined && + newLayout == vk::ImageLayout::eTransferDstOptimal) { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + srcStage = vk::PipelineStageFlagBits::eTopOfPipe; + dstStage = vk::PipelineStageFlagBits::eTransfer; + } else if (this->layout == vk::ImageLayout::eTransferDstOptimal && + newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + srcStage = vk::PipelineStageFlagBits::eTransfer; + dstStage = vk::PipelineStageFlagBits::eFragmentShader; + } else if (this->layout == vk::ImageLayout::eUndefined && + newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | + vk::AccessFlagBits::eDepthStencilAttachmentWrite; + srcStage = vk::PipelineStageFlagBits::eTopOfPipe; + dstStage = vk::PipelineStageFlagBits::eEarlyFragmentTests; + } else if (this->layout == vk::ImageLayout::eUndefined && + newLayout == vk::ImageLayout::eColorAttachmentOptimal) { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + srcStage = vk::PipelineStageFlagBits::eTopOfPipe; + dstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput; + } else if (this->layout == vk::ImageLayout::eUndefined && + newLayout == vk::ImageLayout::eGeneral) { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = + vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite; + srcStage = vk::PipelineStageFlagBits::eTopOfPipe; + dstStage = vk::PipelineStageFlagBits::eComputeShader; + } else if (this->layout == vk::ImageLayout::eColorAttachmentOptimal && + newLayout == vk::ImageLayout::ePresentSrcKHR) { + barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + barrier.dstAccessMask = {}; + srcStage = vk::PipelineStageFlagBits::eColorAttachmentOutput; + dstStage = vk::PipelineStageFlagBits::eBottomOfPipe; + } else { + log::fatal("Unsupported image layout transition."); + throw std::runtime_error("Unsupported image layout transition."); + } + + commandBuffer.pipelineBarrier(srcStage, dstStage, {}, nullptr, nullptr, + barrier); + + this->layout = newLayout; +} + +void Image::transitionLayoutImmediate(vk::ImageLayout newLayout) { + vk::CommandBuffer commandBuffer = this->device.beginSingleTimeCommands(); + this->transitionLayout(commandBuffer, newLayout); + this->device.endSingleTimeCommands(commandBuffer); +} + +Image::Builder& Image::Builder::setExtent(u32 width, u32 height, u32 depth) { + this->extent = vk::Extent3D{width, height, depth}; + return *this; +} + +Image::Builder& Image::Builder::setFormat(vk::Format format) { + this->format = format; + return *this; +} + +Image::Builder& Image::Builder::setUsage(vk::ImageUsageFlags usage) { + this->usage = usage; + return *this; +} + +Image::Builder& Image::Builder::setTiling(vk::ImageTiling tiling) { + this->tiling = tiling; + return *this; +} + +Image::Builder& Image::Builder::setMemoryProperties( + vk::MemoryPropertyFlags properties) { + this->memoryProperties = properties; + return *this; +} + +Image::Builder& Image::Builder::setSamples(vk::SampleCountFlagBits samples) { + this->samples = samples; + return *this; +} + +Image::Builder& Image::Builder::setImageType(vk::ImageType imageType) { + this->imageType = imageType; + return *this; +} + +Image::Builder& Image::Builder::setMipLevels(u32 mipLevels) { + this->mipLevels = mipLevels; + return *this; +} + +Image::Builder& Image::Builder::setArrayLayers(u32 arrayLayers) { + this->arrayLayers = arrayLayers; + return *this; +} + +Image::Builder& Image::Builder::setAspectMask(vk::ImageAspectFlags aspectMask) { + this->aspectMask = aspectMask; + return *this; +} + +Image::Builder& Image::Builder::asComputeStorage() { + // Storage images commonly can't use sRGB formats without an extension, so + // default to a plain UNORM format; override with setFormat() if needed. + this->format = vk::Format::eR8G8B8A8Unorm; + this->usage = vk::ImageUsageFlagBits::eStorage; + this->aspectMask = vk::ImageAspectFlagBits::eColor; + this->tiling = vk::ImageTiling::eOptimal; + this->memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal; + return *this; +} + +Image::Builder& Image::Builder::asColorAttachment() { + this->usage = vk::ImageUsageFlagBits::eColorAttachment | + vk::ImageUsageFlagBits::eSampled; + this->aspectMask = vk::ImageAspectFlagBits::eColor; + this->tiling = vk::ImageTiling::eOptimal; + this->memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal; + return *this; +} + +Image::Builder& Image::Builder::asDepthAttachment() { + this->format = vk::Format::eD32Sfloat; + this->usage = vk::ImageUsageFlagBits::eDepthStencilAttachment; + this->aspectMask = vk::ImageAspectFlagBits::eDepth; + this->tiling = vk::ImageTiling::eOptimal; + this->memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal; + return *this; +} + +Image::Builder& Image::Builder::wrapExisting(vk::Image image) { + this->isExternalImage = true; + this->externalImage = image; + return *this; +} + +Image::Builder& Image::Builder::setInitialData(const void* data, + vk::DeviceSize size) { + this->initialData = data; + this->initialDataSize = size; + return *this; +} + +Image::Builder& Image::Builder::withView(bool enabled) { + this->createView = enabled; + return *this; +} + +Image::Builder& Image::Builder::withSampler( + vk::Filter filter, vk::SamplerAddressMode addressMode) { + this->createSampler = true; + this->samplerFilter = filter; + this->samplerAddressMode = addressMode; + return *this; +} + +std::unique_ptr Image::Builder::build() { + vk::ImageUsageFlags usage = this->usage; + + auto result = std::unique_ptr(new Image(this->device)); + result->format = this->format; + result->extent = this->extent; + result->aspectMask = this->aspectMask; + result->mipLevels = this->mipLevels; + result->arrayLayers = this->arrayLayers; + + if (this->isExternalImage) { + if (this->initialData != nullptr) { + log::warning( + "Image::Builder::setInitialData is ignored when wrapExisting is " + "used."); + } + result->externalImage = this->externalImage; + // Wrapped images (e.g. swapchain images) are handed to us already in a + // known layout controlled by their owner; we can't observe it here, so + // leave `layout` at eUndefined and let the caller call + // transitionLayout() to bring it in sync before relying on it. + } else { + if (this->initialData != nullptr) { + usage |= vk::ImageUsageFlagBits::eTransferDst; + } + if (this->createView || this->createSampler) { + usage |= vk::ImageUsageFlagBits::eSampled; + } + + vk::ImageCreateInfo imageInfo{}; + imageInfo.imageType = this->imageType; + imageInfo.extent = this->extent; + imageInfo.mipLevels = this->mipLevels; + imageInfo.arrayLayers = this->arrayLayers; + imageInfo.format = this->format; + imageInfo.tiling = this->tiling; + imageInfo.initialLayout = vk::ImageLayout::eUndefined; + imageInfo.usage = usage; + imageInfo.sharingMode = vk::SharingMode::eExclusive; + imageInfo.samples = this->samples; + + result->image = this->device.get()->createImageUnique(imageInfo); + + vk::MemoryRequirements memoryRequirements = + this->device.get()->getImageMemoryRequirements(result->image.get()); + + vk::MemoryAllocateInfo allocInfo{}; + allocInfo.allocationSize = memoryRequirements.size; + allocInfo.memoryTypeIndex = this->device.findMemoryType( + memoryRequirements.memoryTypeBits, this->memoryProperties); + + result->memory = this->device.get()->allocateMemoryUnique(allocInfo); + this->device.get()->bindImageMemory(result->image.get(), + result->memory.get(), 0); + } + + if (!this->isExternalImage && this->initialData != nullptr) { + vk::Buffer stagingBuffer; + vk::DeviceMemory stagingMemory; + this->device.createBuffer(this->initialDataSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent, + stagingBuffer, stagingMemory); + + void* mapped = + this->device.get()->mapMemory(stagingMemory, 0, this->initialDataSize); + memcpy(mapped, this->initialData, + static_cast(this->initialDataSize)); + this->device.get()->unmapMemory(stagingMemory); + + vk::BufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = this->aspectMask; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = this->arrayLayers; + region.imageOffset = vk::Offset3D{0, 0, 0}; + region.imageExtent = this->extent; + + vk::CommandBuffer commandBuffer = this->device.beginSingleTimeCommands(); + result->transitionLayout(commandBuffer, vk::ImageLayout::eTransferDstOptimal); + commandBuffer.copyBufferToImage(stagingBuffer, result->get(), + vk::ImageLayout::eTransferDstOptimal, + region); + if (usage & vk::ImageUsageFlagBits::eSampled) { + result->transitionLayout(commandBuffer, + vk::ImageLayout::eShaderReadOnlyOptimal); + } + this->device.endSingleTimeCommands(commandBuffer); + + this->device.get()->destroyBuffer(stagingBuffer); + this->device.get()->freeMemory(stagingMemory); + } + + if (this->createView) { + vk::ImageViewCreateInfo viewInfo{}; + viewInfo.image = result->get(); + viewInfo.viewType = vk::ImageViewType::e2D; + viewInfo.format = this->format; + viewInfo.subresourceRange.aspectMask = this->aspectMask; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = this->mipLevels; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = this->arrayLayers; + + result->view = this->device.get()->createImageViewUnique(viewInfo); + } + + if (this->createSampler) { + vk::SamplerCreateInfo samplerInfo{}; + samplerInfo.magFilter = this->samplerFilter; + samplerInfo.minFilter = this->samplerFilter; + samplerInfo.addressModeU = this->samplerAddressMode; + samplerInfo.addressModeV = this->samplerAddressMode; + samplerInfo.addressModeW = this->samplerAddressMode; + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.maxAnisotropy = 1.0f; + samplerInfo.borderColor = vk::BorderColor::eIntOpaqueBlack; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = vk::CompareOp::eAlways; + samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = static_cast(this->mipLevels); + + result->sampler = this->device.get()->createSamplerUnique(samplerInfo); + } + + log::trace("created vk::Image."); + return result; +} + +} // namespace sophia diff --git a/engine/src/image.hpp b/engine/src/image.hpp new file mode 100644 index 0000000..b93095f --- /dev/null +++ b/engine/src/image.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include + +namespace sophia { + +class Device; + +class Image { + public: + class Builder; + + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + Image(Image&&) = default; + Image& operator=(Image&&) = delete; + + vk::Image get() const { + return this->image ? this->image.get() : this->externalImage; + } + vk::ImageView getView() const { return this->view.get(); } + vk::Sampler getSampler() const { return this->sampler.get(); } + + bool isExternal() const { return !this->image; } + + vk::Format getFormat() const { return this->format; } + vk::Extent3D getExtent() const { return this->extent; } + vk::ImageLayout getLayout() const { return this->layout; } + + void transitionLayout(vk::CommandBuffer commandBuffer, + vk::ImageLayout newLayout); + + void transitionLayoutImmediate(vk::ImageLayout newLayout); + + private: + Image(Device& device) : device{device} {} + + Device& device; + + vk::Image externalImage; + vk::UniqueImage image; + vk::UniqueDeviceMemory memory; + vk::UniqueImageView view; + vk::UniqueSampler sampler; + + vk::Format format = vk::Format::eUndefined; + vk::Extent3D extent{}; + vk::ImageAspectFlags aspectMask = vk::ImageAspectFlagBits::eColor; + u32 mipLevels = 1; + u32 arrayLayers = 1; + vk::ImageLayout layout = vk::ImageLayout::eUndefined; +}; + +class Image::Builder { + public: + Builder(Device& device) : device{device} {} + + Builder& setExtent(u32 width, u32 height, u32 depth = 1); + Builder& setFormat(vk::Format format); + Builder& setUsage(vk::ImageUsageFlags usage); + Builder& setTiling(vk::ImageTiling tiling); + Builder& setMemoryProperties(vk::MemoryPropertyFlags properties); + Builder& setSamples(vk::SampleCountFlagBits samples); + Builder& setImageType(vk::ImageType imageType); + Builder& setMipLevels(u32 mipLevels); + Builder& setArrayLayers(u32 arrayLayers); + Builder& setAspectMask(vk::ImageAspectFlags aspectMask); + + // High-level usage presets for GPGPU + Graphics. Each sets usage/aspect/ + // tiling/memory-property defaults for the common case; call the + // individual setters afterward to override anything preset-specific. + Builder& asComputeStorage(); + Builder& asColorAttachment(); + Builder& asDepthAttachment(); + + // Wraps an image this Builder does not own instead of creating one — + // e.g. a swapchain image. build() will skip image/memory creation and + // teardown entirely; setUsage/setTiling/setMemoryProperties/setSamples/ + // setInitialData are ignored, but setFormat/setExtent/setAspectMask still + // matter, since view/sampler creation relies on them. + Builder& wrapExisting(vk::Image image); + + // Uploads `size` bytes to the image via a staging buffer once it is + // created, transitioning it to eTransferDstOptimal (and on to + // eShaderReadOnlyOptimal if the image is sampled) in the process. + Builder& setInitialData(const void* data, vk::DeviceSize size); + + Builder& withView(bool enabled = true); + Builder& withSampler( + vk::Filter filter = vk::Filter::eLinear, + vk::SamplerAddressMode addressMode = vk::SamplerAddressMode::eRepeat); + + std::unique_ptr build(); + + private: + Device& device; + + vk::Extent3D extent{1, 1, 1}; + vk::Format format = vk::Format::eR8G8B8A8Srgb; + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eSampled; + vk::ImageTiling tiling = vk::ImageTiling::eOptimal; + vk::MemoryPropertyFlags memoryProperties = + vk::MemoryPropertyFlagBits::eDeviceLocal; + vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1; + vk::ImageType imageType = vk::ImageType::e2D; + vk::ImageAspectFlags aspectMask = vk::ImageAspectFlagBits::eColor; + u32 mipLevels = 1; + u32 arrayLayers = 1; + + const void* initialData = nullptr; + vk::DeviceSize initialDataSize = 0; + + bool isExternalImage = false; + vk::Image externalImage; + + bool createView = false; + bool createSampler = false; + vk::Filter samplerFilter = vk::Filter::eLinear; + vk::SamplerAddressMode samplerAddressMode = vk::SamplerAddressMode::eRepeat; +}; + +} // namespace sophia diff --git a/engine/src/shader.cpp b/engine/src/shader.cpp new file mode 100644 index 0000000..178bf8b --- /dev/null +++ b/engine/src/shader.cpp @@ -0,0 +1,125 @@ +#include "shader.hpp" + +#include +#include +#include +#include + +namespace sophia { + +namespace { + +shaderc_shader_kind toShadercKind(ShaderStage shaderStage) { + switch (shaderStage) { + case ShaderStage::VERTEX: + return shaderc_vertex_shader; + case ShaderStage::FRAGMENT: + return shaderc_fragment_shader; + case ShaderStage::COMPUTE: + return shaderc_compute_shader; + } + throw std::invalid_argument("unsupported shader stage"); +} + +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); + throw std::runtime_error("failed to open: " + path); + } + + size_t fileSize = static_cast(file.tellg()); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); + + return buffer; +} + +} // namespace + +void Shader::compile(const std::string& path, ShaderStage shaderStage) { + this->stage = shaderStage; + + std::vector source = readFile(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()); + 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)"); +} + +void Shader::load(const std::string& path, ShaderStage shaderStage) { + this->stage = 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); + 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)"); +} + +Shader::Builder& Shader::Builder::fromGLSL(const std::string& path) { + this->path = path; + this->precompiled = false; + return *this; +} + +Shader::Builder& Shader::Builder::fromSPIRV(const std::string& path) { + this->path = path; + this->precompiled = true; + return *this; +} + +Shader::Builder& Shader::Builder::setStage(ShaderStage shaderStage) { + this->stage = shaderStage; + this->stageSet = true; + return *this; +} + +std::unique_ptr Shader::Builder::build() { + if (this->path.empty()) { + log::fatal("failed to build shader: no source path provided"); + throw std::runtime_error("failed to build shader: no source path provided"); + } + + if (!this->stageSet) { + log::fatal("failed to build shader: no ShaderStage provided"); + throw std::runtime_error("failed to build shader: no ShaderStage provided"); + } + + auto shader = std::unique_ptr(new Shader()); + + if (this->precompiled) { + shader->load(this->path, this->stage); + } else { + shader->compile(this->path, this->stage); + } + + return shader; +} + +} // namespace sophia diff --git a/engine/src/shader.hpp b/engine/src/shader.hpp new file mode 100644 index 0000000..3f26717 --- /dev/null +++ b/engine/src/shader.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +namespace sophia { + +enum class ShaderStage { VERTEX, FRAGMENT, COMPUTE }; + +class Shader { + public: + class Builder; + + Shader(const Shader&) = delete; + Shader& operator=(const Shader&) = delete; + + ~Shader() = default; + + const std::vector& getSpirv() const { return this->spirv; } + ShaderStage getStage() const { return this->stage; } + + private: + Shader() = default; + + void compile(const std::string& path, ShaderStage shaderStage); + void load(const std::string& path, ShaderStage shaderStage); + + ShaderStage stage; + std::vector spirv; +}; + +class Shader::Builder { + public: + Builder() = default; + + Builder& fromGLSL(const std::string& path); + Builder& fromSPIRV(const std::string& path); + Builder& setStage(ShaderStage shaderStage); + + std::unique_ptr build(); + + private: + std::string path; + ShaderStage stage; + bool stageSet = false; + bool precompiled = false; +}; + +} // namespace sophia diff --git a/engine/src/window.cpp b/engine/src/window.cpp index 3b23f81..74bf3fe 100644 --- a/engine/src/window.cpp +++ b/engine/src/window.cpp @@ -1,9 +1,9 @@ #include "window.hpp" -#include "key_event.hpp" -#include "util/logger.hpp" +#include +#include -namespace hep { +namespace sophia { Window::Window(int width, int height, const std::string& name) : width{width}, height{height}, name{name} { @@ -28,11 +28,8 @@ void Window::createSurface(const vk::Instance& instance, log::trace("Created vk::SurfaceKHR."); } -void Window::keyEventCallback(GLFWwindow* window, - int key, - int scancode, - int action, - int mods) { +void Window::keyEventCallback(GLFWwindow* window, int key, int scancode, + int action, int mods) { if (action == GLFW_PRESS) { KeyPressedEvent event{static_cast(key)}; EventSystem::get().dispatch(event); @@ -61,4 +58,4 @@ void Window::initialize() { glfwSetKeyCallback(this->window, keyEventCallback); } -} // namespace hep +} // namespace sophia diff --git a/engine/include/window.hpp b/engine/src/window.hpp similarity index 86% rename from engine/include/window.hpp rename to engine/src/window.hpp index 5833121..ae0b950 100644 --- a/engine/include/window.hpp +++ b/engine/src/window.hpp @@ -6,17 +6,17 @@ #include #include -#include "event.hpp" -#include "types.hpp" +#include +#include -namespace hep { +namespace sophia { class Window { public: Window(const Window&) = delete; Window& operator=(const Window&) = delete; - Window(int width, int height, const std::string& name = "hephaestus"); + Window(int width, int height, const std::string& name = "sophiahaestus"); ~Window(); bool shouldClose() { return glfwWindowShouldClose(window); } @@ -48,4 +48,4 @@ class Window { GLFWwindow* window; }; -} // namespace hep +} // namespace sophia