diff --git a/.gitmodules b/.gitmodules index d92b0d2..d064ea5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,9 @@ [submodule "dependencies/freetype"] path = dependencies/freetype url = https://gitlab.freedesktop.org/freetype/freetype.git +[submodule "dependencies/volk"] + path = dependencies/volk + url = https://github.com/zeux/volk +[submodule "dependencies/VulkanMemoryAllocator"] + path = dependencies/VulkanMemoryAllocator + url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator diff --git a/CMakeLists.txt b/CMakeLists.txt index 1180d53..f066720 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.8) # options @@ -6,8 +6,7 @@ project(engine LANGUAGES CXX VERSION "0.1.0" ) -add_library(${PROJECT_NAME} SHARED - +set(SRC_FILES "src/engine.cpp" "src/window.cpp" "src/input.cpp" #TODO make input_manager @@ -31,13 +30,18 @@ add_library(${PROJECT_NAME} SHARED "src/resource_manager.cpp" "src/gfx_device_vulkan.cpp" + "src/gfx_device_null.cpp" + "src/gfx_device_opengl45.cpp" +) - # PUBLIC API +set(INCLUDE_FILES "include/engine_api.h" "include/engine.hpp" + "include/util.hpp" + "include/log.hpp" "include/window.hpp" @@ -67,19 +71,32 @@ add_library(${PROJECT_NAME} SHARED "include/gfx.hpp" "include/gfx_device.hpp" +) + +add_library(${PROJECT_NAME} STATIC + + ${SRC_FILES} + ${INCLUDE_FILES} ) +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/src" PREFIX "Source" FILES ${SRC_FILES}) +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/include" PREFIX "Include" FILES ${INCLUDE_FILES}) + # compiling options: target_compile_definitions(${PROJECT_NAME} PRIVATE DEFINITIONS "ENGINE_EXPORTS") +set_property(TARGET ${PROJECT_NAME} PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS ON) + set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20) set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) if (MSVC) target_compile_options(${PROJECT_NAME} PRIVATE /W3) target_compile_options(${PROJECT_NAME} PRIVATE /MP) + + target_compile_definitions(${PROJECT_NAME} PRIVATE _CRT_SECURE_NO_WARNINGS) endif() target_include_directories(${PROJECT_NAME} PUBLIC include) @@ -98,10 +115,31 @@ if (MINGW) target_link_libraries(${PROJECT_NAME} PUBLIC mingw32) endif() -# Vulkan -find_package(Vulkan REQUIRED) -target_include_directories(${PROJECT_NAME} PRIVATE ${Vulkan_INCLUDE_DIRS}) -target_link_libraries(${PROJECT_NAME} PRIVATE Vulkan::Vulkan) +# GLAD: +set(GLAD_PROFILE "core" CACHE INTERNAL "" FORCE) +set(GLAD_API "gl=3.3" CACHE INTERNAL "" FORCE) +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(GLAD_GENERATOR "c-debug" CACHE INTERNAL "" FORCE) +else() + set(GLAD_GENERATOR "c" CACHE INTERNAL "" FORCE) +endif() +set(GLAD_SPEC "gl" CACHE INTERNAL "" FORCE) +set(BUILD_SHARED_LIBS OFF) +add_subdirectory(dependencies/glad) +set_property(TARGET glad PROPERTY POSITION_INDEPENDENT_CODE ON) +target_link_libraries(${PROJECT_NAME} PUBLIC glad) +target_include_directories(${PROJECT_NAME} PUBLIC dependencies/glad/include) + +# Volk +set(VOLK_STATIC_DEFINES "") +set(VOLK_PULL_IN_VULKAN ON) +set(VOLK_INSTALL OFF) +set(VOLK_HEADERS_ONLY ON) +add_subdirectory(dependencies/volk) +target_link_libraries(${PROJECT_NAME} PRIVATE volk_headers) + +# Vulkan Memory Allocator +target_include_directories(${PROJECT_NAME} PRIVATE dependencies/VulkanMemoryAllocator/include) # SDL2: find_package(SDL2) @@ -124,24 +162,9 @@ set(BUILD_SHARED_LIBS OFF) add_subdirectory(dependencies/glm) target_include_directories(${PROJECT_NAME} PUBLIC dependencies/glm) -# GLAD: -set(GLAD_PROFILE "core" CACHE INTERNAL "" FORCE) -set(GLAD_API "gl=3.3" CACHE INTERNAL "" FORCE) -if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(GLAD_GENERATOR "c-debug" CACHE INTERNAL "" FORCE) -else() - set(GLAD_GENERATOR "c" CACHE INTERNAL "" FORCE) -endif() -set(GLAD_SPEC "gl" CACHE INTERNAL "" FORCE) -set(BUILD_SHARED_LIBS OFF) -add_subdirectory(dependencies/glad) -set_property(TARGET glad PROPERTY POSITION_INDEPENDENT_CODE ON) -target_link_libraries(${PROJECT_NAME} PUBLIC glad) -target_include_directories(${PROJECT_NAME} PUBLIC dependencies/glad/include) - # spdlog -set(SPDLOG_BUILD_SHARED ON CACHE INTERNAL "" FORCE) -set(BUILD_SHARED_LIBS ON) +set(SPDLOG_BUILD_SHARED OFF CACHE INTERNAL "" FORCE) +set(BUILD_SHARED_LIBS OFF) add_subdirectory(dependencies/spdlog) target_link_libraries(${PROJECT_NAME} PUBLIC spdlog) target_include_directories(${PROJECT_NAME} PUBLIC dependencies/spdlog/include) diff --git a/TODO b/TODO index bd9deb9..b9a529a 100644 --- a/TODO +++ b/TODO @@ -15,3 +15,6 @@ Add support for shadows and other complex lighting. Also add post-processing. For font rendering, put all ASCII characters in one large texture and use 'instancing' (and uniform buffer objects?) to reduce draw calls. + +# VULKAN # +The entire vulkan backend needs redesigning without so many classes diff --git a/dependencies/VulkanMemoryAllocator b/dependencies/VulkanMemoryAllocator new file mode 160000 index 0000000..c351692 --- /dev/null +++ b/dependencies/VulkanMemoryAllocator @@ -0,0 +1 @@ +Subproject commit c351692490513cdb0e5a2c925aaf7ea4a9b672f4 diff --git a/dependencies/volk b/dependencies/volk new file mode 160000 index 0000000..121a458 --- /dev/null +++ b/dependencies/volk @@ -0,0 +1 @@ +Subproject commit 121a4584f69056d2c6db2eb4104650ce749d4c72 diff --git a/include/components/camera.hpp b/include/components/camera.hpp index dc6e91f..e5f4e6a 100644 --- a/include/components/camera.hpp +++ b/include/components/camera.hpp @@ -8,10 +8,11 @@ #include -#include -#include +#include +#include +#include -namespace components { +namespace engine::components { class ENGINE_API Camera : public Component { diff --git a/include/components/component.hpp b/include/components/component.hpp index 5d3f950..5781db1 100644 --- a/include/components/component.hpp +++ b/include/components/component.hpp @@ -2,42 +2,46 @@ #include "engine_api.h" -class Object; -class Window; -class Input; -class ResourceManager; +namespace engine { + class Window; + class Input; -class ENGINE_API Component { + class Object; + class ResourceManager; -public: + class ENGINE_API Component { + + public: + + enum class TypeEnum { + TRANSFORM, + CAMERA, + RENDERER, + UI, + CUSTOM, + }; + + Component(Object* parent, TypeEnum type); + Component(const Component&) = delete; + Component& operator=(const Component&) = delete; + virtual ~Component() = 0; + + int getID(); + TypeEnum getType(); + + Object& parent; + + protected: + engine::Window& win; + engine::Input& inp; + ResourceManager& res; + + private: + static int s_next_component_id; + + int m_id = s_next_component_id; + TypeEnum m_type; - enum class TypeEnum { - TRANSFORM, - CAMERA, - RENDERER, - UI, - CUSTOM, }; - Component(Object* parent, TypeEnum type); - Component(const Component&) = delete; - Component& operator=(const Component&) = delete; - virtual ~Component() = 0; - - int getID(); - TypeEnum getType(); - - Object& parent; - -protected: - Window& win; - Input& inp; - ResourceManager& res; - -private: - static int s_next_component_id; - - int m_id = s_next_component_id; - TypeEnum m_type; - -}; +} \ No newline at end of file diff --git a/include/components/custom.hpp b/include/components/custom.hpp index 96b8434..b258b6d 100644 --- a/include/components/custom.hpp +++ b/include/components/custom.hpp @@ -6,7 +6,7 @@ #include -namespace components { +namespace engine::components { class ENGINE_API CustomComponent : public Component { diff --git a/include/components/mesh_renderer.hpp b/include/components/mesh_renderer.hpp index bd68556..588e7ce 100644 --- a/include/components/mesh_renderer.hpp +++ b/include/components/mesh_renderer.hpp @@ -12,7 +12,7 @@ #include #include -namespace components { +namespace engine::components { class ENGINE_API Renderer : public Component { @@ -21,7 +21,7 @@ public: ~Renderer() override; // called every frame, do not call manually - void render(glm::mat4 transform); + void render(glm::mat4 model); void setMesh(const std::string& name); void setTexture(const std::string& name); diff --git a/include/components/text_ui_renderer.hpp b/include/components/text_ui_renderer.hpp index 7a82b0e..420f9d2 100644 --- a/include/components/text_ui_renderer.hpp +++ b/include/components/text_ui_renderer.hpp @@ -6,10 +6,11 @@ #include "resources/font.hpp" #include "resources/mesh.hpp" +#include "resources/shader.hpp" #include -namespace components { +namespace engine::components { class ENGINE_API UI : public Component { diff --git a/include/engine.hpp b/include/engine.hpp index 9d2eaf8..809ecea 100644 --- a/include/engine.hpp +++ b/include/engine.hpp @@ -1,19 +1,53 @@ #pragma once -#include - -#include "log.hpp" -#include -#include +#include namespace engine { - struct AppInfo { - const char* name; - const char* version; + class Window; + class Input; + class ResourceManager; + class SceneRoot; + + class Application { + + public: + Application(const char* appName, const char* appVersion); + + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + + ~Application(); + + void gameLoop(); + + Window* window() + { + return m_win; + } + + Input* input() + { + return m_input; + } + + ResourceManager* resources() + { + return m_res; + } + + SceneRoot* scene() + { + return m_scene; + } + + + + private: + Window* m_win; + Input* m_input; + ResourceManager* m_res; + SceneRoot* m_scene; }; - - bool versionFromCharArray(const char* version, int* major, int* minor, int* patch); - } diff --git a/include/engine_api.h b/include/engine_api.h index 366c7c1..3c0015b 100644 --- a/include/engine_api.h +++ b/include/engine_api.h @@ -1,5 +1,6 @@ #pragma once +/* #ifndef ENGINE_API # ifdef _MSC_VER # ifdef ENGINE_EXPORTS @@ -11,3 +12,6 @@ # define ENGINE_API # endif #endif +*/ + +#define ENGINE_API \ No newline at end of file diff --git a/include/gfx.hpp b/include/gfx.hpp index 6210e26..028db74 100644 --- a/include/gfx.hpp +++ b/include/gfx.hpp @@ -2,29 +2,19 @@ #include #include +#include -namespace gfx { +namespace engine::gfx { enum class ShaderType { VERTEX, FRAGMENT, }; - struct Shader { - ShaderType type; - uint32_t handle; - }; - - typedef uint32_t Program; - enum class BufferType { VERTEX, INDEX, - }; - - struct Buffer { - BufferType type; - uint32_t handle; + UNIFORM, }; enum class Primitive { @@ -35,10 +25,28 @@ namespace gfx { TRIANGLE_STRIP, }; - enum class IndexBufferFormat { - UNSIGNED_8_BITS, - UNSIGNED_16_BITS, - UNSIGNED_32_BITS, + enum class VertexAttribFormat { + VEC2, + VEC3, }; + struct VertexBufferDesc { + uint64_t size; + }; + + struct VertexAttribDescription { + uint32_t location; + VertexAttribFormat format; + uint32_t offset; + }; + + struct VertexFormat { + uint32_t stride; + std::vector attributeDescriptions; + }; + + // handles (incomplete types) + struct Pipeline; + struct Buffer; + } diff --git a/include/gfx_device.hpp b/include/gfx_device.hpp index 3a454e5..5d8ca33 100644 --- a/include/gfx_device.hpp +++ b/include/gfx_device.hpp @@ -2,21 +2,50 @@ #include "engine_api.h" -#include "engine.hpp" +#include "gfx.hpp" -class Window; +#include -namespace engine::gfx { +struct SDL_Window; + +namespace engine { - struct ENGINE_API Device { + class ENGINE_API GFXDevice { - Device(AppInfo appInfo, const Window& window); - ~Device(); + public: + GFXDevice(const char* appName, const char* appVersion, SDL_Window* window); + + GFXDevice(const GFXDevice&) = delete; + GFXDevice& operator=(const GFXDevice&) = delete; + ~GFXDevice(); + + void getViewportSize(uint32_t *w, uint32_t *h); + + // adds a draw call to the queue + // vertexBuffer is required, indexBuffer can be NULL, uniformData is required + void draw(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t count, const void* pushConstantData); + + // Call once per frame. Executes all queued draw calls and renders to the screen. + void renderFrame(); + + // creates the equivalent of an OpenGL shader program & vertex attrib configuration + gfx::Pipeline* createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat, uint64_t uniformBufferSize); + void destroyPipeline(const gfx::Pipeline* pipeline); + + void updateUniformBuffer(const gfx::Pipeline* pipeline, void* data); + + gfx::Buffer* createBuffer(gfx::BufferType type, uint64_t size, const void* data); + void destroyBuffer(const gfx::Buffer* buffer); + + // wait until all the active GPU queues have finished working + void waitIdle(); private: - class Impl; - std::unique_ptr pimpl{}; + struct Impl; + std::unique_ptr pimpl; }; + extern GFXDevice* gfxdev; + } diff --git a/include/input.hpp b/include/input.hpp index bc4cb08..86ca3d2 100644 --- a/include/input.hpp +++ b/include/input.hpp @@ -9,81 +9,85 @@ #include #include -class Window; +namespace engine { -enum class InputDevice : int { - MOUSE, - KEYBOARD, - CONTROLLER, - SIZE -}; + class Window; -// This class should be used to get platform/input-device independent input -class ENGINE_API Input { - -public: - - // requires a window reference to get input from - Input(const Window& win); - Input(const Input&) = delete; - Input& operator=(const Input&) = delete; - ~Input(); - - // Add a mouse input - void addInputButton(const std::string& name, inputs::MouseButton button); - void addInputAxis(const std::string& name, inputs::MouseAxis axis); - void addInputButtonAsAxis(const std::string& name, inputs::MouseButton high, inputs::MouseButton low); - // Add a keyboard input - void addInputButton(const std::string& name, inputs::Key button); - void addInputButtonAsAxis(const std::string& name, inputs::Key high, inputs::Key low); - - void delInputButton(int index); - void delInputAxis(int index); - - void setDeviceActive(enum InputDevice device, bool active); - bool getDeviceActive(enum InputDevice device) const; - - float getAxis(const std::string& axisName) const; - bool getButton(const std::string& buttonName) const; - bool getButtonPress(const std::string& buttonName) const; - bool getButtonRelease(const std::string& buttonName) const; - - -private: - - struct ButtonEntry { - std::string name; - enum InputDevice device; - int button; // enumeration of device buttons or axes + enum class InputDevice : int { + MOUSE, + KEYBOARD, + CONTROLLER, + SIZE }; - struct AxisEntry { - std::string name; - enum InputDevice device; - int axis; - bool isButtonAxis; - int high; - int low; + // This class should be used to get platform/input-device independent input + class ENGINE_API Input { + + public: + + // requires a window reference to get input from + Input(const Window& win); + Input(const Input&) = delete; + Input& operator=(const Input&) = delete; + ~Input(); + + // Add a mouse input + void addInputButton(const std::string& name, inputs::MouseButton button); + void addInputAxis(const std::string& name, inputs::MouseAxis axis); + void addInputButtonAsAxis(const std::string& name, inputs::MouseButton high, inputs::MouseButton low); + // Add a keyboard input + void addInputButton(const std::string& name, inputs::Key button); + void addInputButtonAsAxis(const std::string& name, inputs::Key high, inputs::Key low); + + void delInputButton(int index); + void delInputAxis(int index); + + void setDeviceActive(enum InputDevice device, bool active); + bool getDeviceActive(enum InputDevice device) const; + + float getAxis(const std::string& axisName) const; + bool getButton(const std::string& buttonName) const; + bool getButtonPress(const std::string& buttonName) const; + bool getButtonRelease(const std::string& buttonName) const; + + + private: + + struct ButtonEntry { + std::string name; + enum InputDevice device; + int button; // enumeration of device buttons or axes + }; + + struct AxisEntry { + std::string name; + enum InputDevice device; + int axis; + bool isButtonAxis; + int high; + int low; + }; + + const Window& m_win; + + std::vector m_buttonEntries; + std::vector m_axisEntries; + + std::array(InputDevice::SIZE)> m_enabledDevices; + + // private methods + + float getDeviceAxis(enum InputDevice device, int axis) const; + bool getDeviceButton(enum InputDevice device, int button) const; + bool getDeviceButtonDown(enum InputDevice device, int button) const; + bool getDeviceButtonUp(enum InputDevice device, int button) const; + + float getButtonAxis(enum InputDevice device, int high, int low) const; + + void addInputButton(const std::string& name, InputDevice device, int button); + void addInputAxis(const std::string& name, InputDevice device, int axis); + void addInputButtonAsAxis(const std::string& name, InputDevice device, int high, int low); + }; - const Window& m_win; - - std::vector m_buttonEntries; - std::vector m_axisEntries; - - std::array(InputDevice::SIZE)> m_enabledDevices; - - // private methods - - float getDeviceAxis(enum InputDevice device, int axis) const; - bool getDeviceButton(enum InputDevice device, int button) const; - bool getDeviceButtonDown(enum InputDevice device, int button) const; - bool getDeviceButtonUp(enum InputDevice device, int button) const; - - float getButtonAxis(enum InputDevice device, int high, int low) const; - - void addInputButton(const std::string& name, InputDevice device, int button); - void addInputAxis(const std::string& name, InputDevice device, int axis); - void addInputButtonAsAxis(const std::string& name, InputDevice device, int high, int low); - -}; +} \ No newline at end of file diff --git a/include/inputs/keyboard.hpp b/include/inputs/keyboard.hpp index a8a6e66..491bf18 100644 --- a/include/inputs/keyboard.hpp +++ b/include/inputs/keyboard.hpp @@ -2,7 +2,7 @@ // Keyboard scancodes, taken from SDL_scancode.h -namespace inputs { +namespace engine::inputs { enum class Key : int { UNKNOWN = 0, diff --git a/include/inputs/mouse.hpp b/include/inputs/mouse.hpp index 0715f25..712455c 100644 --- a/include/inputs/mouse.hpp +++ b/include/inputs/mouse.hpp @@ -1,6 +1,6 @@ #pragma once -namespace inputs { +namespace engine::inputs { enum class MouseButton : int { M_LEFT, diff --git a/include/object.hpp b/include/object.hpp index cfc28f4..b6c4f71 100644 --- a/include/object.hpp +++ b/include/object.hpp @@ -6,135 +6,140 @@ #include "transform.hpp" +#include "components/component.hpp" + #include #include #include #include #include -class Window; -class Input; -class ResourceManager; +namespace engine { + class Window; + class Input; + class ResourceManager; -class SceneRoot; -class Component; + class SceneRoot; + class Component; -namespace components { - class Transform; - class Camera; - class Renderer; - class UI; - class CustomComponent; -} + namespace components { + class Camera; + class Renderer; + class UI; + class CustomComponent; + } -struct GameIO { - Window * const win; - Input * const input; - ResourceManager * const resMan; -}; - -// This object lives until it is deleted by its parent(s) or finally when the "Scene" is destroyed. -// Therefore it is safe to return raw pointers -class ENGINE_API Object { - -public: - Object(std::string name, Object* parent, SceneRoot& root, struct GameIO things); - Object(const Object&) = delete; - Object& operator=(const Object&) = delete; - ~Object(); - - Window& win; - Input& inp; - ResourceManager& res; - - SceneRoot& root; - - std::string getName(); - - Object* getParent(); - - Object* getChild(std::string name); - std::vector getChildren(); - - Object* createChild(std::string name); - void deleteChild(std::string name); - - void printTree(int level = 0); - - // Returns the component of type T - // Returns nullptr if the component is not found. - template T* getComponent(); - - // returns the component added - template T* createComponent(); - - template void deleteComponent(); - - struct CompList { - std::vector> cameras; - std::vector> renderers; - std::vector> uis; - std::vector> customs; + struct GameIO { + Window* win; + Input* input; + ResourceManager* resMan; }; - - // Adds to the provided vector all components of this object and of its children recursively. - // Ignores 'Transform' - void getAllSubComponents(struct CompList& compList, glm::mat4 t); - Transform transform; + // This object lives until it is deleted by its parent(s) or finally when the "Scene" is destroyed. + // Therefore it is safe to return raw pointers + class ENGINE_API Object { -private: - static int s_object_count; - int m_id = s_object_count; - std::string m_name; + public: + Object(std::string name, Object* parent, SceneRoot& root, struct GameIO things); + Object(const Object&) = delete; + Object& operator=(const Object&) = delete; + ~Object(); - std::list> m_children{}; - std::list> m_components{}; + Window& win; + Input& inp; + ResourceManager& res; - // If nullptr, this is the root object - Object* const m_parent; - struct GameIO m_gameIO; -}; + SceneRoot& root; -// implementation of template functions + std::string getName(); -template T* Object::getComponent() -{ - if (std::is_base_of::value == false) { - throw std::runtime_error("getComponent() error: specified type is not a subclass of 'Component'"); - } - for (const auto& component : m_components) { - T* derived = dynamic_cast(component.get()); - if (derived != nullptr) { - return derived; + Object* getParent(); + + Object* getChild(std::string name); + std::vector getChildren(); + + Object* createChild(std::string name); + void deleteChild(std::string name); + + void printTree(int level = 0); + + // Returns the component of type T + // Returns nullptr if the component is not found. + template T* getComponent(); + + // returns the component added + template T* createComponent(); + + template void deleteComponent(); + + struct CompList { + std::vector> cameras; + std::vector> renderers; + std::vector> uis; + std::vector> customs; + }; + + // Adds to the provided vector all components of this object and of its children recursively. + // Ignores 'Transform' + void getAllSubComponents(struct CompList& compList, glm::mat4 t); + + Transform transform; + + private: + static int s_object_count; + int m_id = s_object_count; + std::string m_name; + + std::list> m_children{}; + std::list> m_components{}; + + // If nullptr, this is the root object + Object* const m_parent; + struct GameIO m_gameIO; + + }; + + // implementation of template functions + + template T* Object::getComponent() + { + if (std::is_base_of::value == false) { + throw std::runtime_error("getComponent() error: specified type is not a subclass of 'Component'"); } - } - return nullptr; -} - -template T* Object::createComponent() -{ - if (std::is_base_of::value == false) { - throw std::runtime_error("addComponent() error: specified type is not a subclass of 'Component'"); - } - if (getComponent() != nullptr) { - throw std::runtime_error("addComponent() error: attempt to add component of a type already present"); - } - m_components.emplace_back(std::make_unique(this)); - return dynamic_cast(m_components.back().get()); -} - -template void Object::deleteComponent() -{ - if (std::is_base_of::value == false) { - throw std::runtime_error("deleteComponent() error: specified type is not a subclass of 'Component'"); - } - for (auto itr = m_components.begin(); itr != m_components.end(); ++itr) { - if (dynamic_cast((*itr).get()) != nullptr) { - m_components.erase(itr); - return; + for (const auto& component : m_components) { + T* derived = dynamic_cast(component.get()); + if (derived != nullptr) { + return derived; + } } + return nullptr; } - throw std::runtime_error("deleteComponent() error: attempt to delete component that is not present."); + + template T* Object::createComponent() + { + if (std::is_base_of::value == false) { + throw std::runtime_error("addComponent() error: specified type is not a subclass of 'Component'"); + } + if (getComponent() != nullptr) { + throw std::runtime_error("addComponent() error: attempt to add component of a type already present"); + } + m_components.emplace_back(std::make_unique(this)); + return dynamic_cast(m_components.back().get()); + } + + template void Object::deleteComponent() + { + if (std::is_base_of::value == false) { + throw std::runtime_error("deleteComponent() error: specified type is not a subclass of 'Component'"); + } + for (auto itr = m_components.begin(); itr != m_components.end(); ++itr) { + if (dynamic_cast((*itr).get()) != nullptr) { + m_components.erase(itr); + return; + } + } + throw std::runtime_error("deleteComponent() error: attempt to delete component that is not present."); + } + } diff --git a/include/resource_manager.hpp b/include/resource_manager.hpp index 5077098..923e7b1 100644 --- a/include/resource_manager.hpp +++ b/include/resource_manager.hpp @@ -9,73 +9,77 @@ // Doesn't own resources, only holds weak_ptrs -class ENGINE_API ResourceManager { +namespace engine { -public: - ResourceManager(); - ResourceManager(const ResourceManager&) = delete; - ResourceManager& operator=(const ResourceManager&) = delete; - ~ResourceManager() = default; + class ENGINE_API ResourceManager { + + public: + ResourceManager(); + ResourceManager(const ResourceManager&) = delete; + ResourceManager& operator=(const ResourceManager&) = delete; + ~ResourceManager() = default; + + template + std::shared_ptr create(const std::string& name); + + // creates the resource if it is not in the map or the weak_ptr has expired + template + std::shared_ptr get(const std::string& name); + + std::unique_ptr getResourcesListString(); + + std::vector> getAllResourcesOfType(const std::string& type); + + std::filesystem::path getFilePath(const std::string& name); + + private: + std::filesystem::path m_resourcesPath; + std::unordered_map> m_resources; + + }; template - std::shared_ptr create(const std::string& name); + std::shared_ptr ResourceManager::create(const std::string& name) + { + if (std::is_base_of::value == false) { + throw std::runtime_error("ResourceManager::create() error: specified type is not a subclass of 'Resource'"); + } + auto resource = std::make_shared(getFilePath(name)); + m_resources.emplace(name, std::dynamic_pointer_cast(resource)); + return resource; + } - // creates the resource if it is not in the map or the weak_ptr has expired template - std::shared_ptr get(const std::string& name); + std::shared_ptr ResourceManager::get(const std::string& name) + { - std::unique_ptr getResourcesListString(); + if (std::is_base_of::value == false) { + throw std::runtime_error("ResourceManager::get() error: specified type is not a subclass of 'Resource'"); + } - std::vector> getAllResourcesOfType(const std::string& type); + if (m_resources.contains(name)) { - std::filesystem::path getFilePath(const std::string& name); + std::weak_ptr res = m_resources.at(name); -private: - std::filesystem::path m_resourcesPath; - std::unordered_map> m_resources; - -}; - -template -std::shared_ptr ResourceManager::create(const std::string& name) -{ - if (std::is_base_of::value == false) { - throw std::runtime_error("ResourceManager::create() error: specified type is not a subclass of 'Resource'"); - } - auto resource = std::make_shared(getFilePath(name)); - m_resources.emplace(name, std::dynamic_pointer_cast(resource)); - return resource; -} - -template -std::shared_ptr ResourceManager::get(const std::string& name) -{ - - if (std::is_base_of::value == false) { - throw std::runtime_error("ResourceManager::get() error: specified type is not a subclass of 'Resource'"); - } - - if (m_resources.contains(name)) { - - std::weak_ptr res = m_resources.at(name); - - if (res.expired() == false) { - // resource definitely exists - auto castedSharedPtr = std::dynamic_pointer_cast(res.lock()); - if (castedSharedPtr == nullptr) { - throw std::runtime_error("error: attempt to get Resource which already exists as another type"); + if (res.expired() == false) { + // resource definitely exists + auto castedSharedPtr = std::dynamic_pointer_cast(res.lock()); + if (castedSharedPtr == nullptr) { + throw std::runtime_error("error: attempt to get Resource which already exists as another type"); + } + else { + return castedSharedPtr; + } } else { - return castedSharedPtr; + // Resource in map but no longer exists. Delete it. + m_resources.erase(name); } + } - else { - // Resource in map but no longer exists. Delete it. - m_resources.erase(name); - } + + return create(name); } - - return create(name); -} +} \ No newline at end of file diff --git a/include/resources/font.hpp b/include/resources/font.hpp index b284bfd..1e868c3 100644 --- a/include/resources/font.hpp +++ b/include/resources/font.hpp @@ -10,7 +10,7 @@ #include -namespace resources { +namespace engine::resources { class ENGINE_API Font : public Resource { diff --git a/include/resources/mesh.hpp b/include/resources/mesh.hpp index 4eb5349..6c9a384 100644 --- a/include/resources/mesh.hpp +++ b/include/resources/mesh.hpp @@ -4,9 +4,7 @@ #include "resource.hpp" -#include "resources/shader.hpp" - -#include +#include "gfx.hpp" #include #include @@ -20,34 +18,23 @@ struct Vertex { glm::vec2 uv; }; -namespace resources { +namespace engine::resources { class ENGINE_API Mesh : public Resource { public: Mesh(const std::vector& vertices); - Mesh(const std::vector& vertices, const std::vector& indices); + Mesh(const std::vector& vertices, const std::vector& indices); Mesh(const std::filesystem::path& resPath); ~Mesh() override; - void drawMesh(const Shader& shader); - - static void invalidate() - { - s_active_vao = -1; - } - std::vector m_vertices; - std::vector m_indices; + std::vector m_indices; + + const gfx::Buffer* vb; + const gfx::Buffer* ib; private: - static int s_active_vao; - - GLuint m_vao; - GLuint m_vbo; - GLuint m_ebo; - - void bindVAO() const; void initMesh(); diff --git a/include/resources/resource.hpp b/include/resources/resource.hpp index f18ae00..50bc78d 100644 --- a/include/resources/resource.hpp +++ b/include/resources/resource.hpp @@ -5,20 +5,24 @@ #include #include -class ENGINE_API Resource { +namespace engine { -public: - Resource(const std::filesystem::path& resPath, const std::string& type); - Resource(const Resource&) = delete; - Resource& operator=(const Resource&) = delete; - virtual ~Resource() = 0; + class ENGINE_API Resource { - std::string getType(); + public: + Resource(const std::filesystem::path& resPath, const std::string& type); + Resource(const Resource&) = delete; + Resource& operator=(const Resource&) = delete; + virtual ~Resource() = 0; -protected: - std::filesystem::path m_resourcePath; + std::string getType(); -private: - const std::string m_type; + protected: + std::filesystem::path m_resourcePath; -}; + private: + const std::string m_type; + + }; + +} \ No newline at end of file diff --git a/include/resources/shader.hpp b/include/resources/shader.hpp index 60ed5bf..e547c82 100644 --- a/include/resources/shader.hpp +++ b/include/resources/shader.hpp @@ -4,14 +4,16 @@ #include "resource.hpp" -#include - #include #include #include -namespace resources { +namespace engine::gfx { + struct Pipeline; +} + +namespace engine::resources { class ENGINE_API Shader : public Resource { @@ -19,58 +21,18 @@ public: Shader(const std::filesystem::path& resPath); ~Shader() override; - enum class UniformType { - FLOAT_MAT4 = GL_FLOAT_MAT4, - FLOAT_VEC2 = GL_FLOAT_VEC2, - FLOAT_VEC3 = GL_FLOAT_VEC3, - SAMPLER_2D = GL_SAMPLER_2D, - NOTFOUND + struct UniformBuffer { + glm::mat4 v; + glm::mat4 p; }; - - void makeActive() const; - bool setUniform_m4(const std::string& name, const glm::mat4&) const; - bool setUniform_v2(const std::string& name, const glm::vec2&) const; - bool setUniform_v3(const std::string& name, const glm::vec3&) const; - bool setUniform_i(const std::string& name, int) const; - bool setUniform_f(const std::string& name, float) const; - - UniformType getUniformType(const std::string& name) const; - int getAttribLocation(const std::string& name) const; - - static void invalidate() + gfx::Pipeline* getPipeline() { - s_activeProgram = -1; + return m_pipeline; } private: - - struct Uniform { - GLint size; - UniformType type; - GLuint location; - }; - - struct Attribute { - GLint size; - UniformType type; - GLuint location; - }; - - // fields - - GLuint m_program; - - std::map m_uniforms{}; - std::map m_attributes{}; - - // static members - - // Only valid if glUseProgram is never called outside this class's method - static GLuint s_activeProgram; - - // -1 if not found - int getUniformLocation(const std::string& name) const; + gfx::Pipeline* m_pipeline = nullptr; }; diff --git a/include/resources/texture.hpp b/include/resources/texture.hpp index 2c6e334..cdfc9a5 100644 --- a/include/resources/texture.hpp +++ b/include/resources/texture.hpp @@ -6,7 +6,7 @@ #include -namespace resources { +namespace engine::resources { class ENGINE_API Texture : public Resource { diff --git a/include/sceneroot.hpp b/include/sceneroot.hpp index 5096b50..9850523 100644 --- a/include/sceneroot.hpp +++ b/include/sceneroot.hpp @@ -6,24 +6,27 @@ #include -// Holds everything you would expect to find in a game scene -class ENGINE_API SceneRoot : public Object { +namespace engine { -public: - // create a new empty scene - SceneRoot(struct GameIO things); - SceneRoot(const std::filesystem::path& file, struct GameIO things); + // Holds everything you would expect to find in a game scene + class ENGINE_API SceneRoot : public Object { - SceneRoot(const SceneRoot&) = delete; - SceneRoot& operator=(const SceneRoot&) = delete; - ~SceneRoot(); + public: + // create a new empty scene + SceneRoot(struct GameIO things); - void updateStuff(); + SceneRoot(const SceneRoot&) = delete; + SceneRoot& operator=(const SceneRoot&) = delete; + ~SceneRoot(); - void activateCam(int id); - void deactivateCam(int id); + void updateStuff(); -private: - std::vector m_activeCameras{}; + void activateCam(int id); + void deactivateCam(int id); -}; + private: + std::vector m_activeCameras{}; + + }; + +} \ No newline at end of file diff --git a/include/transform.hpp b/include/transform.hpp index dbb3122..938a6ff 100644 --- a/include/transform.hpp +++ b/include/transform.hpp @@ -6,12 +6,16 @@ #include #include -struct ENGINE_API Transform { +namespace engine { -// Scale, rotate (XYZ), translate + struct ENGINE_API Transform { - glm::vec3 position{0.0f}; - glm::quat rotation{}; - glm::vec3 scale{1.0f}; + // Scale, rotate (XYZ), translate -}; \ No newline at end of file + glm::vec3 position{ 0.0f }; + glm::quat rotation{}; + glm::vec3 scale{ 1.0f }; + + }; + +} \ No newline at end of file diff --git a/include/util.hpp b/include/util.hpp new file mode 100644 index 0000000..6de177d --- /dev/null +++ b/include/util.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace engine { + + inline bool versionFromCharArray(const char* version, int* major, int* minor, int* patch) + { + if (sscanf(version, "%d.%d.%d", major, minor, patch) != 3) { + *major = 0; + *minor = 0; + *patch = 0; + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/include/window.hpp b/include/window.hpp index 1079d83..544a458 100644 --- a/include/window.hpp +++ b/include/window.hpp @@ -17,149 +17,136 @@ ENGINE_API extern const uint64_t BILLION; -class ENGINE_API Window { +namespace engine { -public: - Window(const std::string& title); - Window(const Window&) = delete; - Window& operator=(const Window&) = delete; - ~Window(); + class ENGINE_API Window { - // Return the title name - std::string getTitle() const; + public: + Window(const std::string& title, bool resizable = true); + Window(const Window&) = delete; + Window& operator=(const Window&) = delete; + ~Window(); - // Make this window the current OpenGL context. - // This is already done in window initialisation. - void makeContextCurrent(); + SDL_Window* getHandle() const; - // Tell the GPU to render the back buffer to the screen. - // Run this on every frame. - void swapBuffers(); - // Update the window state to capture any events that have occurred. - // Run this on every frame. - void getInputAndEvents(); + // Return the title name + std::string getTitle() const; - // if 'true', swapBuffers() will wait in order to synchronise with the - // monitor's refresh rate. - void setVSync(bool enable); - // Returns true if VSync is enabled. - bool getVSync() const; + // Update the window state to capture any events that have occurred. + // Run this on every frame. + void getInputAndEvents(); - glm::ivec2 getViewportSize(); + void setTitle(std::string title); - void setTitle(std::string title); + // Hides the window (it will appear closed to the user). + void hide(); + // Shows the window again. + void show(); + // Raises the window above other windows and sets the input focus + void focus(); + // Returns true if the window has focus + bool hasFocus() const; - // Hides the window (it will appear closed to the user). - void hide(); - // Shows the window again. - void show(); - // Raises the window above other windows and sets the input focus - void focus(); - // Returns true if the window has focus - bool hasFocus() const; + // Sets the close flag, check this with shouldClose() + void setCloseFlag(); + // Returns true if the window should remain open + bool isRunning() const; - // Sets the close flag, check this with shouldClose() - void setCloseFlag(); - // Returns true if the window should remain open - bool isRunning() const; + void setFullscreen(bool fullscreen, bool exclusive = false); + void toggleFullscreen(); - void setFullscreen(bool fullscreen, bool exclusive=true); - void toggleFullscreen(); + bool isFullscreen() const; - bool isFullscreen() const; + // Relative mouse mode captures the cursor for FPS style use. Returns false if unsupported. + bool setRelativeMouseMode(bool enabled); - // Relative mouse mode captures the cursor for FPS style use. Returns false if unsupported. - bool setRelativeMouseMode(bool enabled); + // returns true if relative mouse mode is enabled + bool mouseCaptured(); - // returns true if relative mouse mode is enabled - bool mouseCaptured(); + // window events - // window events + // Returns true if the window was just resized during the previous frame + bool getWindowResized() const; + // Set the window resized flag (to recalculate aspect ratios and such) + inline void setResizedFlag() + { + m_justResized = true; + } - // Returns true if the window was just resized during the previous frame - bool getWindowResized() const; - // Set the window resized flag (to recalculate aspect ratios and such) - inline void setResizedFlag() - { - m_justResized = true; - } + // keyboard events - // keyboard events + // returns true if key is down + bool getKey(inputs::Key key) const; + // returns true if key was just pressed + bool getKeyPress(inputs::Key key) const; + // returns true if key was just released + bool getKeyRelease(inputs::Key key) const; - // returns true if key is down - bool getKey(inputs::Key key) const; - // returns true if key was just pressed - bool getKeyPress(inputs::Key key) const; - // returns true if key was just released - bool getKeyRelease(inputs::Key key) const; + // mouse events - // mouse events + // returns true if button is down + bool getButton(inputs::MouseButton button) const; + // returns true if button was just pressed + bool getButtonPress(inputs::MouseButton button) const; + // returns true if button was just released + bool getButtonRelease(inputs::MouseButton button) const; - // returns true if button is down - bool getButton(inputs::MouseButton button) const; - // returns true if button was just pressed - bool getButtonPress(inputs::MouseButton button) const; - // returns true if button was just released - bool getButtonRelease(inputs::MouseButton button) const; + // retrieves x coordinate of the mouse + int getMouseX() const; + // retrieves y coordinate of the mouse + int getMouseY() const; + // retrieves mouse x coordinate normalised for OpenGL + float getMouseNormX() const; + // retrieves mouse y coordinate normalised for OpenGL + float getMouseNormY() const; + // retrieves dx of the mouse since the last frame + int getMouseDX() const; + // retrieves dy of the mouse since the last frame + int getMouseDY() const; + // retrieves amount scrolled vertically + float getMouseScrollX() const; + // retrieves amount scrolled horizontally + float getMouseScrollY() const; - // retrieves x coordinate of the mouse - int getMouseX() const; - // retrieves y coordinate of the mouse - int getMouseY() const; - // retrieves mouse x coordinate normalised for OpenGL - float getMouseNormX() const; - // retrieves mouse y coordinate normalised for OpenGL - float getMouseNormY() const; - // retrieves dx of the mouse since the last frame - int getMouseDX() const; - // retrieves dy of the mouse since the last frame - int getMouseDY() const; - // retrieves amount scrolled vertically - float getMouseScrollX() const; - // retrieves amount scrolled horizontally - float getMouseScrollY() const; - - // joystick/gamepad events (maybe), other misc events + // joystick/gamepad events (maybe), other misc events - // returns the performance counter value in nanoseconds; - uint64_t getNanos() const; - // get the time recorded at the end of the last frame - uint64_t getLastFrameStamp() const; + // returns the performance counter value in nanoseconds; + uint64_t getNanos() const; + // get the time recorded at the end of the last frame + uint64_t getLastFrameStamp() const; - // returns the number of frames elapsed since window creation - uint64_t getFrameCount() const; - uint64_t getStartTime() const;; - float dt() const; // returns delta time in seconds - uint64_t getFPS() const; - uint64_t getAvgFPS() const; + // returns the number of frames elapsed since window creation + uint64_t getFrameCount() const; + uint64_t getStartTime() const;; + float dt() const; // returns delta time in seconds + uint64_t getFPS() const; + uint64_t getAvgFPS() const; - void resetAvgFPS(); + void resetAvgFPS(); - bool infoBox(const std::string& title, const std::string& msg); + bool infoBox(const std::string& title, const std::string& msg); - std::vector getRequiredVulkanExtensions() const; - - /* STATIC METHODS */ - static void errorBox(const std::string& message); + /* STATIC METHODS */ + static void errorBox(const std::string& message); private: + SDL_Window* m_handle; - SDL_GLContext m_glContext; bool m_shouldClose = false; std::string m_title; + bool m_resizable; + bool m_fullscreen = false; bool m_justResized = false; bool m_keyboardFocus = true; // size in screen coordinates glm::ivec2 m_winSize = glm::vec2(640, 480); - // actual framebuffer size - glm::ivec2 m_fbSize; // performance counter frequency uint64_t m_counterFreq; @@ -214,5 +201,6 @@ public: void onMouseButtonEvent(SDL_MouseButtonEvent& e); void onMouseMotionEvent(SDL_MouseMotionEvent& e); void onMouseWheelEvent(SDL_MouseWheelEvent& e); + }; -}; +} \ No newline at end of file diff --git a/src/components/camera.cpp b/src/components/camera.cpp index edc035b..d5b3ff3 100644 --- a/src/components/camera.cpp +++ b/src/components/camera.cpp @@ -7,24 +7,17 @@ #include "window.hpp" +#include "gfx_device.hpp" + #include "log.hpp" -static const std::string VIEW_MAT_UNIFORM = "viewMat"; -static const std::string PROJ_MAT_UNIFORM = "projMat"; -static const std::string WINDOW_SIZE_UNIFORM = "windowSize"; - -namespace components { +namespace engine::components { glm::vec4 Camera::s_clearColor{-999.0f, -999.0f, -999.0f, -999.0f}; Camera::Camera(Object* parent) : Component(parent, TypeEnum::CAMERA) { parent->root.activateCam(getID()); - glEnable(GL_DEPTH_TEST); - - glDisable(GL_STENCIL_TEST); - - glEnable(GL_CULL_FACE); } Camera::~Camera() @@ -45,6 +38,14 @@ void Camera::updateCam(glm::mat4 transform) glm::mat4 viewMatrix = glm::inverse(transform); + struct { + glm::mat4 view; + glm::mat4 proj; + } uniformData{}; + + uniformData.view = viewMatrix; + uniformData.proj = m_projMatrix; + using namespace resources; auto resPtrs = parent.res.getAllResourcesOfType("shader"); @@ -52,26 +53,18 @@ void Camera::updateCam(glm::mat4 transform) // shader ref count increased by 1, but only in this scope auto lockedPtr = resPtr.lock(); auto shader = dynamic_cast(lockedPtr.get()); - shader->setUniform_m4(VIEW_MAT_UNIFORM, viewMatrix); - shader->setUniform_m4(PROJ_MAT_UNIFORM, m_projMatrix); - shader->setUniform_v2(WINDOW_SIZE_UNIFORM, win.getViewportSize()); - shader->setUniform_v3("lightPos", m_lightPos); + // SET VIEW TRANSFORM HERE + gfxdev->updateUniformBuffer(shader->getPipeline(), &uniformData); } - if (s_clearColor != clearColor) { - s_clearColor = clearColor; - glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a); - } - - glClear((m_noClear ? 0 : GL_COLOR_BUFFER_BIT) | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - } static glm::vec2 getViewportSize() { - GLint64 viewportParams[4]; - glGetInteger64v(GL_VIEWPORT, viewportParams); - return { viewportParams[2], viewportParams[3] }; + uint32_t width; + uint32_t height; + gfxdev->getViewportSize(&width, &height); + return {width, height}; } void Camera::usePerspective(float fovDeg) @@ -85,7 +78,8 @@ void Camera::usePerspective(float fovDeg) glm::vec2 viewportDim = getViewportSize(); float fovRad = glm::radians(fovDeg); - m_projMatrix = glm::perspectiveFov(fovRad, viewportDim.x, viewportDim.y, NEAR, FAR); + m_projMatrix = glm::perspectiveFovRH_ZO(fovRad, viewportDim.x, viewportDim.y, NEAR, FAR); + m_projMatrix[1][1] *= -1; } void Camera::useOrtho() @@ -95,7 +89,8 @@ void Camera::useOrtho() glm::vec2 viewportDim = getViewportSize(); float aspect = viewportDim.x / viewportDim.y; - m_projMatrix = glm::ortho(-10.0f * aspect, 10.0f * aspect, -10.0f, 10.0f, -100.0f, 100.0f); + m_projMatrix = glm::orthoRH_ZO(-10.0f * aspect, 10.0f * aspect, -10.0f, 10.0f, -100.0f, 100.0f); + m_projMatrix[1][1] *= -1; } } diff --git a/src/components/component.cpp b/src/components/component.cpp index 9bc9f45..ca91f33 100644 --- a/src/components/component.cpp +++ b/src/components/component.cpp @@ -4,27 +4,31 @@ #include -int Component::s_next_component_id = 0; +namespace engine { -Component::Component(Object* parent, TypeEnum type) : - m_type(type), parent(*parent), - win(parent->win), - inp(parent->inp), - res(parent->res) -{ - s_next_component_id++; -} + int Component::s_next_component_id = 0; -Component::~Component() -{ -} + Component::Component(Object* parent, TypeEnum type) : + m_type(type), parent(*parent), + win(parent->win), + inp(parent->inp), + res(parent->res) + { + s_next_component_id++; + } -int Component::getID() -{ - return m_id; -} + Component::~Component() + { + } -Component::TypeEnum Component::getType() -{ - return m_type; -} + int Component::getID() + { + return m_id; + } + + Component::TypeEnum Component::getType() + { + return m_type; + } + +} \ No newline at end of file diff --git a/src/components/custom.cpp b/src/components/custom.cpp index a1feee8..931b59b 100644 --- a/src/components/custom.cpp +++ b/src/components/custom.cpp @@ -1,6 +1,6 @@ #include "components/custom.hpp" -namespace components { +namespace engine::components { CustomComponent::CustomComponent(Object* parent) : Component(parent, TypeEnum::CUSTOM) { diff --git a/src/components/mesh_renderer.cpp b/src/components/mesh_renderer.cpp index 189fdc2..fa4deb4 100644 --- a/src/components/mesh_renderer.cpp +++ b/src/components/mesh_renderer.cpp @@ -4,14 +4,18 @@ #include "resource_manager.hpp" +#include "gfx_device.hpp" + +#include "log.hpp" + #include -namespace components { +namespace engine::components { Renderer::Renderer(Object* parent) : Component(parent, TypeEnum::RENDERER) { - m_shader = this->parent.res.get("shaders/basic.glsl"); - m_texture = this->parent.res.get("textures/missing.png"); + m_shader = this->parent.res.get("shader.glsl"); +// m_texture = this->parent.res.get("textures/missing.png"); } Renderer::~Renderer() @@ -21,20 +25,7 @@ Renderer::~Renderer() void Renderer::render(glm::mat4 transform) { - - m_shader->setUniform_f("ambientStrength", 0.4f); - m_shader->setUniform_v3("ambientColor", { 1.0f, 1.0f, 1.0f }); - - m_shader->setUniform_v3("lightColor", { 1.0f, 1.0f, 1.0f }); - - m_shader->setUniform_m4("modelMat", transform ); - - m_texture->bindTexture(); - m_shader->setUniform_v3("baseColor", m_color ); - m_shader->setUniform_v3("emission", m_emission ); - - if (m_mesh) - m_mesh->drawMesh(*m_shader); + gfxdev->draw(m_shader->getPipeline(), m_mesh->vb, m_mesh->ib, m_mesh->m_vertices.size(), &transform); } void Renderer::setMesh(const std::string& name) @@ -44,7 +35,7 @@ void Renderer::setMesh(const std::string& name) void Renderer::setTexture(const std::string& name) { - m_texture = parent.res.get(name); +// m_texture = parent.res.get(name); } } diff --git a/src/components/text_ui_renderer.cpp b/src/components/text_ui_renderer.cpp index 5a26fff..2491e24 100644 --- a/src/components/text_ui_renderer.cpp +++ b/src/components/text_ui_renderer.cpp @@ -4,7 +4,7 @@ #include "resource_manager.hpp" #include "resources/texture.hpp" -namespace components { +namespace engine::components { UI::UI(Object* parent) : Component(parent, TypeEnum::UI) { @@ -23,12 +23,15 @@ UI::~UI() void UI::render(glm::mat4 transform) { + /* glActiveTexture(GL_TEXTURE0); m_shader->setUniform_m4("modelMat", transform); m_shader->setUniform_v3("textColor", m_color); m_shader->setUniform_i("textScaling", (int)m_scaled); + */ + std::vector glyphs; for (char c : m_text) { glyphs.push_back(m_font->getChar(c)); @@ -57,6 +60,7 @@ void UI::render(glm::mat4 transform) float w = glyph.size.x * scale; float h = glyph.size.y * scale; + /* resources::Mesh mesh({ {{xpos, ypos + h, 0.0f}, {}, {0.0f, 0.0f}}, {{xpos, ypos , 0.0f}, {}, {0.0f, 1.0f}}, @@ -64,11 +68,11 @@ void UI::render(glm::mat4 transform) {{xpos, ypos + h, 0.0f}, {}, {0.0f, 0.0f}}, {{xpos + w, ypos, 0.0f}, {}, {1.0f, 1.0f}}, {{xpos + w, ypos + h, 0.0f}, {}, {1.0f, 0.0f}}, - }); + });*/ glBindTexture(GL_TEXTURE_2D, glyph.textureID); - mesh.drawMesh(*m_shader); +// mesh.drawMesh(*m_shader); x += (glyph.advance >> 6) * scale; diff --git a/src/engine.cpp b/src/engine.cpp index 0f50ff9..1ecc2c9 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -1,16 +1,87 @@ #include "engine.hpp" +#include "log.hpp" + +#include "window.hpp" +#include "input.hpp" +#include "resource_manager.hpp" +#include "sceneroot.hpp" + +#include "gfx_device.hpp" + +#include "resources/mesh.hpp" + +#include "components/mesh_renderer.hpp" +#include "components/camera.hpp" + namespace engine { -bool versionFromCharArray(const char* version, int* major, int* minor, int* patch) -{ - if (sscanf(version, "%d.%d.%d", major, minor, patch) != 3) { - *major = 0; - *minor = 0; - *patch = 0; - return false; + Application::Application(const char* appName, const char* appVersion) + { + m_win = new Window(appName, true); + + gfxdev = new GFXDevice(appName, appVersion, m_win->getHandle()); + + m_input = new Input(*m_win); + m_res = new ResourceManager(); + + GameIO things{ + m_win, + m_input, + m_res + }; + m_scene = new SceneRoot(things); + } + + Application::~Application() + { + delete m_scene; + delete m_res; + delete m_input; + + delete gfxdev; + + delete m_win; + } + + void Application::gameLoop() + { + TRACE("Begin game loop..."); + + uint64_t lastTick = m_win->getNanos(); + constexpr int TICKFREQ = 1; // in hz + + // single-threaded game loop + while (m_win->isRunning()) { + + /* logic */ + + if (m_win->getLastFrameStamp() >= lastTick + (BILLION / TICKFREQ)) { + lastTick = m_win->getLastFrameStamp(); + + // do tick stuff here + m_win->setTitle("frame time: " + std::to_string(m_win->dt() * 1000.0f) + " ms, " + std::to_string(m_win->getAvgFPS()) + " fps"); + m_win->resetAvgFPS(); + } + + if (m_win->getKeyPress(inputs::Key::F11)) { + m_win->toggleFullscreen(); + } + if (m_win->getKeyPress(inputs::Key::ESCAPE)) { + m_win->setCloseFlag(); + } + + m_scene->updateStuff(); + + /* draw */ + gfxdev->renderFrame(); + + /* poll events */ + m_win->getInputAndEvents(); + + } + + gfxdev->waitIdle(); } - return true; -} } diff --git a/src/gfx_device_null.cpp b/src/gfx_device_null.cpp new file mode 100644 index 0000000..6874ecd --- /dev/null +++ b/src/gfx_device_null.cpp @@ -0,0 +1,79 @@ +// This implementation of the graphics layer does nothing + +//#define ENGINE_BUILD_NULLGFX +#ifdef ENGINE_BUILD_NULLGFX + +#include "gfx_device.hpp" +#include "util.hpp" +#include "config.h" +#include "log.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +namespace engine { + + // structures and enums + + // class definitions + + struct GFXDevice::Impl { + + }; + + GFXDevice::GFXDevice(const char* appName, const char* appVersion, SDL_Window* window) + { + pimpl = std::make_unique(); + } + + GFXDevice::~GFXDevice() + { + TRACE("Destroying GFXDevice..."); + } + + void GFXDevice::drawBuffer(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, uint32_t count) + { + } + + void GFXDevice::drawIndexed(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t indexCount) + { + } + + void GFXDevice::renderFrame() + { + + } + + gfx::Pipeline* GFXDevice::createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat) + { + return nullptr; + } + + void GFXDevice::destroyPipeline(const gfx::Pipeline* pipeline) + { + + } + + gfx::Buffer* GFXDevice::createBuffer(gfx::BufferType type, uint64_t size, const void* data) + { + return nullptr; + } + + void GFXDevice::destroyBuffer(const gfx::Buffer* buffer) + { + + } + + void GFXDevice::waitIdle() + { + } + +} + +#endif diff --git a/src/gfx_device_opengl45.cpp b/src/gfx_device_opengl45.cpp new file mode 100644 index 0000000..dfd89c2 --- /dev/null +++ b/src/gfx_device_opengl45.cpp @@ -0,0 +1,105 @@ +// The implementation of the graphics layer using OpenGL 4.5 +// This uses SDL specific code + +//#undef ENGINE_BUILD_OPENGL +#ifdef ENGINE_BUILD_OPENGL + +#include "gfx_device.hpp" +#include "util.hpp" +#include "config.h" +#include "log.hpp" + +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace engine { + + // structures and enums + + static std::vector readFile(const std::string& filename) + { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + if (file.is_open() == false) { + throw std::runtime_error("Unable to open file " + filename); + } + std::vector buffer(file.tellg()); + file.seekg(0); + file.read(buffer.data(), buffer.size()); + file.close(); + return buffer; + } + + + + // class definitions + + struct GFXDevice::Impl { + + SDL_GLContext context = nullptr; + + }; + + GFXDevice::GFXDevice(const char* appName, const char* appVersion, SDL_Window* window) + { + pimpl = std::make_unique(); + + pimpl->context = SDL_GL_CreateContext(window); + + } + + GFXDevice::~GFXDevice() + { + TRACE("Destroying GFXDevice..."); + + SDL_GL_DeleteContext(pimpl->context); + } + + void GFXDevice::drawBuffer(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, uint32_t count) + { + } + + void GFXDevice::drawIndexed(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t indexCount) + { + } + + void GFXDevice::renderFrame() + { + + } + + gfx::Pipeline* GFXDevice::createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat) + { + return nullptr; + } + + void GFXDevice::destroyPipeline(const gfx::Pipeline* pipeline) + { + + } + + gfx::Buffer* GFXDevice::createBuffer(gfx::BufferType type, uint64_t size, const void* data) + { + return nullptr; + } + + void GFXDevice::destroyBuffer(const gfx::Buffer* buffer) + { + + } + + void GFXDevice::waitIdle() + { + glFinish(); + } + +} + +#endif diff --git a/src/gfx_device_vulkan.cpp b/src/gfx_device_vulkan.cpp index 67448e3..133abb2 100644 --- a/src/gfx_device_vulkan.cpp +++ b/src/gfx_device_vulkan.cpp @@ -1,64 +1,1686 @@ +// The implementation of the graphics layer using Vulkan 1.3. +// This uses SDL specific code + +//#undef ENGINE_BUILD_VULKAN #ifdef ENGINE_BUILD_VULKAN #include "gfx_device.hpp" - +#include "util.hpp" #include "config.h" +#include "log.hpp" -#include "window.hpp" +#define VOLK_IMPLEMENTATION +#include -#include +#define VMA_STATIC_VULKAN_FUNCTIONS 0 +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0 +#define VMA_VULKAN_VERSION 1003000 +#define VMA_IMPLEMENTATION +#include + +#include #include -#include +#include +#include +#include +#include +#include +#include +#include -namespace engine::gfx { +namespace engine { - class Device::Impl { - friend Device; + // EXTERNED GLOBAL VARIABLE + GFXDevice* gfxdev = nullptr; - VkInstance m_instance; + static constexpr uint32_t FRAMES_IN_FLIGHT = 2; // This improved FPS by 5x! (on Intel IGPU) + static constexpr size_t PUSH_CONSTANT_MAX_SIZE = 128; // bytes + + // structures and enums + + struct LayerInfo { + std::vector layersAvailable{}; + std::optional::iterator> validationLayer; }; - Device::Device(AppInfo appInfo, const Window& window) + struct Queue { + uint32_t familyIndex; + uint32_t queueIndex; + bool supportsGraphics; + bool supportsTransfer; + bool supportsCompute; + + VkQueue handle; + }; + + + struct DepthBuffer { + VkImage image; + VmaAllocation allocation; + VkImageView view; + }; + + struct Swapchain { + VkSwapchainKHR swapchain = VK_NULL_HANDLE; + + VkExtent2D extent; + VkSurfaceFormatKHR surfaceFormat; + VkPresentModeKHR presentMode; + + std::vector images{}; + std::vector imageViews{}; + std::vector framebuffers{}; + + DepthBuffer depthBuffer{}; + + VkQueue activeQueue{}; + + VkRenderPass renderpass; + + std::array acquireSemaphores{}; // waits until the image is available + std::array releaseSemaphores{}; // waits until rendering finishes + }; + + struct DrawCall { + const gfx::Buffer* vertexBuffer = nullptr; + const gfx::Buffer* indexBuffer = nullptr; // if this is nullptr, don't use indexed + uint32_t count = 0; + uint8_t pushConstantData[PUSH_CONSTANT_MAX_SIZE]; + }; + + enum class QueueFlags : uint32_t { + GRAPHICS = (1 << 0), + TRANSFER = (1 << 1), + COMPUTE = (1 << 2), + }; + + // handles + + struct gfx::Buffer { + gfx::BufferType type; + VkBuffer buffer = VK_NULL_HANDLE; + VmaAllocation allocation = nullptr; + VkDeviceSize size = 0; + }; + + struct gfx::Pipeline { + VkPipelineLayout layout = VK_NULL_HANDLE; + VkPipeline handle = VK_NULL_HANDLE; + std::vector uniformBuffers{}; + VkDescriptorPool descriptorPool = VK_NULL_HANDLE; + std::array descriptorSets{}; + }; + + + + // enum converters + + namespace vkinternal { + + static VkFormat getVertexAttribFormat(gfx::VertexAttribFormat fmt) + { + switch (fmt) { + case gfx::VertexAttribFormat::VEC2: + return VK_FORMAT_R32G32_SFLOAT; + case gfx::VertexAttribFormat::VEC3: + return VK_FORMAT_R32G32B32_SFLOAT; + } + throw std::runtime_error("Unknown vertex attribute format"); + } + + static VkBufferUsageFlagBits getBufferUsageFlag(gfx::BufferType type) + { + switch (type) { + case gfx::BufferType::VERTEX: + return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + case gfx::BufferType::INDEX: + return VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + } + throw std::runtime_error("Unknown buffer type"); + } + + } + + // functions + + static std::vector readFile(const std::string& filename) + { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + if (file.is_open() == false) { + throw std::runtime_error("Unable to open file " + filename); + } + std::vector buffer(file.tellg()); + file.seekg(0); + file.read(buffer.data(), buffer.size()); + file.close(); + return buffer; + } + + static std::vector getRequiredVulkanExtensions(SDL_Window* window) + { + SDL_bool res; + + unsigned int sdlExtensionCount = 0; + res = SDL_Vulkan_GetInstanceExtensions(window, &sdlExtensionCount, nullptr); + assert(res == SDL_TRUE); + std::vector requiredExtensions(sdlExtensionCount); + res = SDL_Vulkan_GetInstanceExtensions(window, &sdlExtensionCount, requiredExtensions.data()); + assert(res == SDL_TRUE); + + return requiredExtensions; + } + + static LayerInfo getAvailableLayers(bool useValidation) + { + constexpr const char* VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation"; + + LayerInfo info; + VkResult res; + + uint32_t layerCount; + res = vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + assert(res == VK_SUCCESS); + info.layersAvailable.resize(layerCount); + res = vkEnumerateInstanceLayerProperties(&layerCount, info.layersAvailable.data()); + assert(res == VK_SUCCESS); + + if (useValidation == true) { + // find validation layer and print all layers to log + for (auto it = info.layersAvailable.begin(); it != info.layersAvailable.end(); it++) { + if (strncmp(it->layerName, VALIDATION_LAYER_NAME, 256) == 0) { + info.validationLayer = it; + } + } + if (info.validationLayer.has_value() == false) { + throw std::runtime_error("The validation layer was not found. Quitting."); + } + } + + return info; + } + + static VkBool32 messengerCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageTypes, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) + { + + std::string msgType{}; + + if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) + msgType += " (GENERAL)"; + if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) + msgType += " (PERF.)"; + if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) + msgType += " (VALID.)"; + + switch (messageSeverity) { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + TRACE("VULKAN MESSAGE{}: ID: {} MSG: {}", msgType, pCallbackData->pMessageIdName, pCallbackData->pMessage); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + INFO("VULKAN MESSAGE{}: ID: {} MSG: {}", msgType, pCallbackData->pMessageIdName, pCallbackData->pMessage); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + WARN("VULKAN MESSAGE{}: ID: {} MSG: {}", msgType, pCallbackData->pMessageIdName, pCallbackData->pMessage); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + ERROR("VULKAN MESSAGE{}: ID: {} MSG: {}", msgType, pCallbackData->pMessageIdName, pCallbackData->pMessage); + break; + default: + break; + } + return VK_FALSE; + } + + static VkDebugUtilsMessengerCreateInfoEXT getDebugMessengerCreateInfo() + { + VkDebugUtilsMessengerCreateInfoEXT debugMessengerInfo{ + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .pNext = nullptr, + .flags = 0, + .messageSeverity = 0, + .messageType = + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + .pfnUserCallback = messengerCallback, + .pUserData = nullptr, + }; + + enum class MessageSeverity { + VERBOSE, + INFO, + WARNING, + ERROR + }; + + constexpr MessageSeverity MESSAGE_LEVEL = MessageSeverity::WARNING; + switch (MESSAGE_LEVEL) { + case MessageSeverity::VERBOSE: + debugMessengerInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; + // fall-through + case MessageSeverity::INFO: + debugMessengerInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; + // fall-through + case MessageSeverity::WARNING: + debugMessengerInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; + // fall-through + case MessageSeverity::ERROR: + debugMessengerInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + // fall-through + default: + break; + } + + return debugMessengerInfo; + } + + static VkSurfaceKHR createSurface(SDL_Window* window, VkInstance instance) + { + VkSurfaceKHR surface; + + if (SDL_Vulkan_CreateSurface(window, instance, &surface) == false) { + throw std::runtime_error("Unable to create window surface"); + } + + return surface; + } + + // returns the index of the queue supporting the requested flags + static Queue getQueueSupporting(const std::vector queues, QueueFlags flags) + { + uint32_t bitmask = static_cast(flags); + + for (int i = 0; i < queues.size(); i++) { + + if (bitmask & static_cast(QueueFlags::GRAPHICS)) { + if (queues[i].supportsGraphics == false) continue; + } + + if (bitmask & static_cast(QueueFlags::TRANSFER)) { + if (queues[i].supportsTransfer == false) continue; + } + + if (bitmask & static_cast(QueueFlags::COMPUTE)) { + if (queues[i].supportsCompute == false) continue; + } + + return queues[i]; + + } + + throw std::runtime_error("Unable to find the requested queue"); + } + + static DepthBuffer createDepthBuffer(VkDevice device, VmaAllocator allocator, VkExtent2D extent) + { + DepthBuffer db{}; + + VkResult res; + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = extent.width; + imageInfo.extent.height = extent.height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = VK_FORMAT_D32_SFLOAT; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.flags = 0; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + allocInfo.priority = 1.0f; + + res = vmaCreateImage(allocator, &imageInfo, &allocInfo, &db.image, &db.allocation, nullptr); + assert(res == VK_SUCCESS); + + VkImageViewCreateInfo imageViewInfo{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + imageViewInfo.image = db.image; + imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewInfo.format = VK_FORMAT_D32_SFLOAT; + imageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + imageViewInfo.subresourceRange.baseMipLevel = 0; + imageViewInfo.subresourceRange.levelCount = 1; + imageViewInfo.subresourceRange.baseArrayLayer = 0; + imageViewInfo.subresourceRange.layerCount = 1; + res = vkCreateImageView(device, &imageViewInfo, nullptr, &db.view); + assert(res == VK_SUCCESS); + + return db; + } + + static void destroyDepthBuffer(DepthBuffer db, VkDevice device, VmaAllocator allocator) + { + vkDestroyImageView(device, db.view, nullptr); + vmaDestroyImage(allocator, db.image, db.allocation); + } + + // This is called not just on initialisation, but also when the window is resized. + static void createSwapchain(VkDevice device, VkPhysicalDevice physicalDevice, VmaAllocator allocator, std::vector queues, SDL_Window* window, VkSurfaceKHR surface, Swapchain* swapchain) { VkResult res; - int appVersionMajor, appVersionMinor, appVersionPatch; - assert(versionFromCharArray(appInfo.version, &appVersionMajor, &appVersionMinor, &appVersionPatch)); - int engineVersionMajor, engineVersionMinor, engineVersionPatch; + // get surface capabilities + VkSurfaceCapabilitiesKHR caps; + res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &caps); + assert(res == VK_SUCCESS); + + // check there is at least one supported surface format + uint32_t surfaceFormatCount = 0; + std::vector formats{}; + res = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &surfaceFormatCount, nullptr); + assert(res == VK_SUCCESS); + formats.resize(surfaceFormatCount); + res = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &surfaceFormatCount, formats.data()); + assert(res == VK_SUCCESS); + + // check there is at least one supported present mode + uint32_t surfacePresentModeCount = 0; + std::vector presentModes{}; + res = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &surfacePresentModeCount, nullptr); + assert(res == VK_SUCCESS); + presentModes.resize(surfacePresentModeCount); + res = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &surfacePresentModeCount, presentModes.data()); + assert(res == VK_SUCCESS); + + + + // delete old framebuffers + for (VkFramebuffer fb : swapchain->framebuffers) { + vkDestroyFramebuffer(device, fb, nullptr); + } + + // delete old image views + for (VkImageView view : swapchain->imageViews) { + vkDestroyImageView(device, view, nullptr); + } + + swapchain->surfaceFormat = formats[0]; + for (const auto& format : formats) { + if (format.format == VK_FORMAT_B8G8R8A8_SRGB && + format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + swapchain->surfaceFormat = format; // prefer using srgb non linear colors + } + } + + swapchain->presentMode = VK_PRESENT_MODE_FIFO_KHR; // This mode is always available + + for (const auto& presMode : presentModes) { + if (presMode == VK_PRESENT_MODE_MAILBOX_KHR) { + swapchain->presentMode = presMode; // this mode allows uncapped FPS while also avoiding screen tearing + } + } + + VkExtent2D oldExtent = swapchain->extent; + + if (caps.currentExtent.width != std::numeric_limits::max()) { + swapchain->extent = caps.currentExtent; + } + else { + // if fb size isn't already found, get it from SDL + int width, height; + SDL_Vulkan_GetDrawableSize(window, &width, &height); + + swapchain->extent.width = static_cast(width); + swapchain->extent.height = static_cast(height); + + swapchain->extent.width = std::clamp( + swapchain->extent.width, + caps.minImageExtent.width, caps.maxImageExtent.width); + swapchain->extent.height = std::clamp( + swapchain->extent.height, + caps.minImageExtent.height, caps.maxImageExtent.height); + } + + uint32_t imageCount = caps.minImageCount + 1; + if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount) { + imageCount = caps.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{ + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .pNext = nullptr, + .flags = 0, + .surface = surface, + .minImageCount = imageCount, + .imageFormat = swapchain->surfaceFormat.format, + .imageColorSpace = swapchain->surfaceFormat.colorSpace, + .imageExtent = swapchain->extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .preTransform = caps.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = swapchain->presentMode, + .clipped = VK_TRUE, + .oldSwapchain = swapchain->swapchain, + + }; + + std::array queueFamilyIndices{ + getQueueSupporting(queues, QueueFlags::GRAPHICS).familyIndex, + getQueueSupporting(queues, QueueFlags::TRANSFER).familyIndex + }; + + // if graphics and transfer queues aren't in the same family + if (queueFamilyIndices[0] != queueFamilyIndices[1]) { + throw std::runtime_error("Graphics and transfer queues must be in the same family"); + } + + res = vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapchain->swapchain); + assert(res == VK_SUCCESS); + + if (createInfo.oldSwapchain != VK_NULL_HANDLE) { + // if recreating swapchain, destroy old one + vkDestroySwapchainKHR(device, createInfo.oldSwapchain, nullptr); + } + + // get all the image handles + uint32_t swapchainImageCount = 0; + res = vkGetSwapchainImagesKHR(device, swapchain->swapchain, &swapchainImageCount, nullptr); + assert(res == VK_SUCCESS); + swapchain->images.resize(swapchainImageCount); + res = vkGetSwapchainImagesKHR(device, swapchain->swapchain, &swapchainImageCount, swapchain->images.data()); + assert(res == VK_SUCCESS); + + // create depth buffer if old depth buffer is wrong size + if (swapchain->swapchain == VK_NULL_HANDLE) { + swapchain->depthBuffer = createDepthBuffer(device, allocator, swapchain->extent); + } + else if (swapchain->extent.width != oldExtent.width || swapchain->extent.height != oldExtent.height) { + destroyDepthBuffer(swapchain->depthBuffer, device, allocator); + swapchain->depthBuffer = createDepthBuffer(device, allocator, swapchain->extent); + } + + + // create the render pass + if (swapchain->renderpass == VK_NULL_HANDLE) { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapchain->surfaceFormat.format; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentDescription depthAttachment{}; + depthAttachment.format = VK_FORMAT_D32_SFLOAT; + depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthAttachmentRef{}; + depthAttachmentRef.attachment = 1; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + subpass.pDepthStencilAttachment = &depthAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + std::array attachments = { colorAttachment, depthAttachment }; + + VkRenderPassCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + createInfo.attachmentCount = (uint32_t)attachments.size(); + createInfo.pAttachments = attachments.data(); + createInfo.subpassCount = 1; + createInfo.pSubpasses = &subpass; + createInfo.dependencyCount = 1; + createInfo.pDependencies = &dependency; + + //if (swapchain->renderpass != VK_NULL_HANDLE) { + // vkDestroyRenderPass(device, swapchain->renderpass, nullptr); + // swapchain->renderpass = VK_NULL_HANDLE; + //} + res = vkCreateRenderPass(device, &createInfo, nullptr, &swapchain->renderpass); + } + + // create image views and framebuffers + + swapchain->imageViews.resize(swapchain->images.size()); + swapchain->framebuffers.resize(swapchain->images.size()); + for (int i = 0; i < swapchain->images.size(); i++) { + + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.pNext = nullptr; + createInfo.image = swapchain->images[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapchain->surfaceFormat.format; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + res = vkCreateImageView(device, &createInfo, nullptr, &swapchain->imageViews[i]); + assert(res == VK_SUCCESS); + + std::array attachments = { + swapchain->imageViews[i], + swapchain->depthBuffer.view + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = swapchain->renderpass; + framebufferInfo.attachmentCount = (uint32_t)attachments.size(); + framebufferInfo.pAttachments = attachments.data(); + framebufferInfo.width = swapchain->extent.width; + framebufferInfo.height = swapchain->extent.height; + framebufferInfo.layers = 1; + + res = vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapchain->framebuffers[i]); + assert(res == VK_SUCCESS); + + } + + // create the swapchain semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + for (auto& acquireSemaphore : swapchain->acquireSemaphores) { + if (acquireSemaphore == VK_NULL_HANDLE) { + res = vkCreateSemaphore(device, &semaphoreInfo, nullptr, &acquireSemaphore); + assert(res == VK_SUCCESS); + } + } + for (auto& releaseSemaphore : swapchain->releaseSemaphores) { + if (releaseSemaphore == VK_NULL_HANDLE) { + res = vkCreateSemaphore(device, &semaphoreInfo, nullptr, &releaseSemaphore); + assert(res == VK_SUCCESS); + } + } + + } + + static VkShaderModule createShaderModule(VkDevice device, const std::vector& code) + { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + static void copyBuffer(VkDevice device, VkCommandPool commandPool, VkQueue queue, VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) + { + VkResult res; + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + res = vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + assert(res == VK_SUCCESS); + + { // record the command buffer + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + res = vkBeginCommandBuffer(commandBuffer, &beginInfo); + assert(res == VK_SUCCESS); + + VkBufferCopy copyRegion{}; + copyRegion.srcOffset = 0; + copyRegion.dstOffset = 0; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + res = vkEndCommandBuffer(commandBuffer); + assert(res == VK_SUCCESS); + } + + // submit + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + res = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + assert(res == VK_SUCCESS); + + res = vkQueueWaitIdle(queue); + assert(res == VK_SUCCESS); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + + } + + // class definitions + + struct GFXDevice::Impl { + + VkInstance instance = VK_NULL_HANDLE; + VkDebugUtilsMessengerEXT debugMessenger = VK_NULL_HANDLE; + + SDL_Window* window = nullptr; + + VkSurfaceKHR surface = VK_NULL_HANDLE; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device = VK_NULL_HANDLE; + + std::vector queues{}; + Queue gfxQueue{}; + Queue presentQueue{}; + VkCommandPool commandPool = VK_NULL_HANDLE; + + VmaAllocator allocator = nullptr; + + Swapchain swapchain{}; + + uint64_t FRAMECOUNT = 0; + + std::array commandBuffers{}; + std::array inFlightFences{}; + + std::map> drawQueues{}; + + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + VkDescriptorSetLayout uboLayout{}; + + }; + + GFXDevice::GFXDevice(const char* appName, const char* appVersion, SDL_Window* window) + { + if (gfxdev != nullptr) { + throw std::runtime_error("There can only be one graphics device"); + } + gfxdev = this; + + pimpl = std::make_unique(); + + VkResult res; + + pimpl->window = window; + + // initialise vulkan + + res = volkInitialize(); + if (res == VK_ERROR_INITIALIZATION_FAILED) { + throw std::runtime_error("Unable to load vulkan, is it installed?"); + } + + assert(res == VK_SUCCESS); + + uint32_t vulkanVersion = volkGetInstanceVersion(); + if (vulkanVersion < VK_MAKE_VERSION(1, 3, 0)) { + throw std::runtime_error("The loaded Vulkan version must be at least 1.3"); + } + + bool useValidation; +#ifdef NDEBUG + useValidation = false; // release mode +#else + useValidation = true; // debug mode +#endif + + + + // get the both the engine and application versions + int appVersionMajor = 0, appVersionMinor = 0, appVersionPatch = 0; + assert(versionFromCharArray(appVersion, &appVersionMajor, &appVersionMinor, &appVersionPatch)); + int engineVersionMajor = 0, engineVersionMinor = 0, engineVersionPatch = 0; assert(versionFromCharArray(ENGINE_VERSION, &engineVersionMajor, &engineVersionMinor, &engineVersionPatch)); VkApplicationInfo applicationInfo{ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = nullptr, - .pApplicationName = appInfo.name, + .pApplicationName = appName, .applicationVersion = VK_MAKE_VERSION(appVersionMajor, appVersionMinor, appVersionPatch), .pEngineName = "engine", .engineVersion = VK_MAKE_VERSION(engineVersionMajor, engineVersionMinor, engineVersionPatch), - .apiVersion = VK_VERSION_1_0, + .apiVersion = VK_API_VERSION_1_3, }; + + + // make a list of all extensions to use + std::vector extensions{}; + + const std::vector windowExtensions = getRequiredVulkanExtensions(window); + extensions.insert(extensions.end(), windowExtensions.begin(), windowExtensions.end()); + + // also use debug utils extension + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + + + // make a list of layers to use + std::vector layers{}; + + const LayerInfo layerInfo = getAvailableLayers(useValidation); + + if (layerInfo.validationLayer.has_value()) { + layers.push_back(layerInfo.validationLayer.value()->layerName); + } + VkInstanceCreateInfo instanceInfo{ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = nullptr, .flags = 0, - .pApplicationInfo = nullptr, - .enabledLayerCount = 0, - .ppEnabledLayerNames = nullptr, - .enabledExtensionCount = 0, - .ppEnabledExtensionNames = nullptr, + .pApplicationInfo = &applicationInfo, + .enabledLayerCount = (uint32_t)layers.size(), + .ppEnabledLayerNames = layers.data(), + .enabledExtensionCount = (uint32_t)extensions.size(), + .ppEnabledExtensionNames = extensions.data(), }; - res = vkCreateInstance(&instanceInfo, nullptr, &pimpl->m_instance); - std::cout << "ERROR CODE: " << res << std::endl; + // add the debug messenger + VkDebugUtilsMessengerCreateInfoEXT debugMessengerInfo; + if (layerInfo.validationLayer.has_value()) { + debugMessengerInfo = getDebugMessengerCreateInfo(); + instanceInfo.pNext = &debugMessengerInfo; + } + else { + instanceInfo.pNext = nullptr; + } + + + +#ifndef NDEBUG + for (const char* ext : extensions) { + TRACE("Using Vulkan instance extension: {}", ext); + } +#endif + + res = vkCreateInstance(&instanceInfo, nullptr, &pimpl->instance); + if (res == VK_ERROR_INCOMPATIBLE_DRIVER) { + throw std::runtime_error("The graphics driver is incompatible with vulkan"); + } assert(res == VK_SUCCESS); + + + // load the instance functions + volkLoadInstanceOnly(pimpl->instance); + + + + // create the debug messenger + { + VkDebugUtilsMessengerCreateInfoEXT createInfo = getDebugMessengerCreateInfo(); + + VkResult res = vkCreateDebugUtilsMessengerEXT(pimpl->instance, &createInfo, nullptr, &pimpl->debugMessenger); + assert(res == VK_SUCCESS); + } + + + + // get the surface + pimpl->surface = createSurface(window, pimpl->instance); + + + + // Select a physical device and get capabilities, features, and display modes. + // Create a logical device and create the queues and their corresponding command buffers. + { + // enumerate physical devices + uint32_t physDeviceCount = 0; + VkResult res; + res = vkEnumeratePhysicalDevices(pimpl->instance, &physDeviceCount, nullptr); + assert(res == VK_SUCCESS); + if (physDeviceCount == 0) { + throw std::runtime_error("No GPU found with vulkan support!"); + } + std::vector physicalDevices(physDeviceCount); + res = vkEnumeratePhysicalDevices(pimpl->instance, &physDeviceCount, physicalDevices.data()); + assert(res == VK_SUCCESS); + + // find suitable device: + + const std::vector requiredDeviceExtensions{ + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + }; + + for (const auto& dev : physicalDevices) { + + // first, check extension support + uint32_t extensionCount; + res = vkEnumerateDeviceExtensionProperties(dev, nullptr, &extensionCount, nullptr); + assert(res == VK_SUCCESS); + std::vector availableExtensions(extensionCount); + res = vkEnumerateDeviceExtensionProperties(dev, nullptr, &extensionCount, availableExtensions.data()); + assert(res == VK_SUCCESS); + + for (const auto& extToFind : requiredDeviceExtensions) { + bool extFound = false; + for (const auto& ext : availableExtensions) { + if (strcmp(extToFind, ext.extensionName) == 0) { + extFound = true; + } + } + if (!extFound) { + continue; + } + } + + + + // check physical device properties + VkPhysicalDeviceProperties devProps; + vkGetPhysicalDeviceProperties(dev, &devProps); + + // check that the device supports vulkan 1.3 + if (devProps.apiVersion < VK_API_VERSION_1_3) { + continue; + } + + pimpl->physicalDevice = dev; + break; + + } // end for() + + if (pimpl->physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("No suitable Vulkan physical device found"); + } + + VkPhysicalDeviceProperties devProps; + vkGetPhysicalDeviceProperties(pimpl->physicalDevice, &devProps); + INFO("Selected physical device: {}", devProps.deviceName); + + + + // Get the queue families and find ones that support graphics, transfer, and compute + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(pimpl->physicalDevice, &queueFamilyCount, nullptr); + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(pimpl->physicalDevice, &queueFamilyCount, queueFamilies.data()); + + std::optional graphicsFamilyIndex; + std::optional transferFamilyIndex; + std::optional computeFamilyIndex; + + for (uint32_t i = 0; i < queueFamilyCount; i++) { + VkQueueFamilyProperties family = queueFamilies[i]; + if (family.queueCount > 0) { + if (graphicsFamilyIndex.has_value() == false && family.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + TRACE("GRAPHICS:"); + graphicsFamilyIndex = i; + } + if (transferFamilyIndex.has_value() == false && family.queueFlags & VK_QUEUE_TRANSFER_BIT) { + TRACE("TRANSFER:"); + transferFamilyIndex = i; + } + if (computeFamilyIndex.has_value() == false && family.queueFlags & VK_QUEUE_COMPUTE_BIT) { + TRACE("COMPUTE:"); + computeFamilyIndex = i; + } + TRACE("\t\ti = {}\t\tcount = {}", i, family.queueCount); + } + } + if (graphicsFamilyIndex.has_value() == false || + transferFamilyIndex.has_value() == false) { + throw std::runtime_error("Unable to find queues with the GRAPHICS or TRANSFER family flags"); + } + + // there is no guaranteed support for compute queues + + std::vector queueCreateInfos{}; + + // use a set to filter out duplicate indices + std::unordered_set uniqueQueueFamilies{}; + if (graphicsFamilyIndex.has_value()) uniqueQueueFamilies.insert(graphicsFamilyIndex.value()); + if (transferFamilyIndex.has_value()) uniqueQueueFamilies.insert(transferFamilyIndex.value()); + if (computeFamilyIndex.has_value()) uniqueQueueFamilies.insert(computeFamilyIndex.value()); + + float queuePriority = 1.0f; + + for (uint32_t family : uniqueQueueFamilies) { + // create a queue for each unique type to ensure that there are queues available for graphics, transfer, and compute + + Queue newQueue{}; + newQueue.familyIndex = family; + newQueue.queueIndex = 0; + newQueue.supportsGraphics = false; + newQueue.supportsTransfer = false; + newQueue.supportsCompute = false; + if (graphicsFamilyIndex == family) newQueue.supportsGraphics = true; + if (transferFamilyIndex == family) newQueue.supportsTransfer = true; + if (computeFamilyIndex == family) newQueue.supportsCompute = true; + + TRACE("Creating queue from family {}", family); + VkDeviceQueueCreateInfo queueCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .queueFamilyIndex = family, + .queueCount = 1, + .pQueuePriorities = &queuePriority, + }; + queueCreateInfos.push_back(queueCreateInfo); + pimpl->queues.push_back(newQueue); + } + + // check the physical device is compatible with the surface + VkBool32 graphicsQueueCanPresent; + res = vkGetPhysicalDeviceSurfaceSupportKHR(pimpl->physicalDevice, graphicsFamilyIndex.value(), pimpl->surface, &graphicsQueueCanPresent); + assert(res == VK_SUCCESS); + if (graphicsQueueCanPresent != VK_TRUE) { + throw std::runtime_error("The selected queue family does not support this surface"); + } + + VkDeviceCreateInfo deviceCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .queueCreateInfoCount = (uint32_t)queueCreateInfos.size(), + .pQueueCreateInfos = queueCreateInfos.data(), + // IGNORED: .enabledLayerCount + // IGNORED: .ppEnabledLayerNames + .enabledExtensionCount = (uint32_t)requiredDeviceExtensions.size(), + .ppEnabledExtensionNames = requiredDeviceExtensions.data(), + .pEnabledFeatures = nullptr, + }; + + res = vkCreateDevice(pimpl->physicalDevice, &deviceCreateInfo, nullptr, &pimpl->device); + if (res != VK_SUCCESS) { + throw std::runtime_error("Unable to create Vulkan logical device, error code: " + std::to_string(res)); + } + + volkLoadDevice(pimpl->device); + + for (auto& q : pimpl->queues) { + vkGetDeviceQueue(pimpl->device, q.familyIndex, q.queueIndex, &q.handle); + } + + pimpl->presentQueue = getQueueSupporting(pimpl->queues, QueueFlags::TRANSFER); + + pimpl->gfxQueue = getQueueSupporting(pimpl->queues, QueueFlags::GRAPHICS); + + VkCommandPoolCreateInfo gfxCmdPoolInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = pimpl->gfxQueue.familyIndex, + }; + + res = vkCreateCommandPool(pimpl->device, &gfxCmdPoolInfo, nullptr, &pimpl->commandPool); + assert(res == VK_SUCCESS); + + VkCommandBufferAllocateInfo gfxCmdBufInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = pimpl->commandPool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1 + }; + + for (int i = 0; i < FRAMES_IN_FLIGHT; i++) { + res = vkAllocateCommandBuffers(pimpl->device, &gfxCmdBufInfo, &pimpl->commandBuffers[i]); + assert(res == VK_SUCCESS); + } + } + + + + // now make the memory allocator using vk_mem_alloc.h + { + VmaVulkanFunctions functions{ + .vkGetInstanceProcAddr = nullptr, + .vkGetDeviceProcAddr = nullptr, + .vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties, + .vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties, + .vkAllocateMemory = vkAllocateMemory, + .vkFreeMemory = vkFreeMemory, + .vkMapMemory = vkMapMemory, + .vkUnmapMemory = vkUnmapMemory, + .vkFlushMappedMemoryRanges = vkFlushMappedMemoryRanges, + .vkInvalidateMappedMemoryRanges = vkInvalidateMappedMemoryRanges, + .vkBindBufferMemory = vkBindBufferMemory, + .vkBindImageMemory = vkBindImageMemory, + .vkGetBufferMemoryRequirements = vkGetBufferMemoryRequirements, + .vkGetImageMemoryRequirements = vkGetImageMemoryRequirements, + .vkCreateBuffer = vkCreateBuffer, + .vkDestroyBuffer = vkDestroyBuffer, + .vkCreateImage = vkCreateImage, + .vkDestroyImage = vkDestroyImage, + .vkCmdCopyBuffer = vkCmdCopyBuffer, + .vkGetBufferMemoryRequirements2KHR = vkGetBufferMemoryRequirements2, + .vkGetImageMemoryRequirements2KHR = vkGetImageMemoryRequirements2, + .vkBindBufferMemory2KHR = vkBindBufferMemory2, + .vkBindImageMemory2KHR = vkBindImageMemory2, + .vkGetPhysicalDeviceMemoryProperties2KHR = vkGetPhysicalDeviceMemoryProperties2, + .vkGetDeviceBufferMemoryRequirements = vkGetDeviceBufferMemoryRequirements, + .vkGetDeviceImageMemoryRequirements = vkGetDeviceImageMemoryRequirements, + }; + + VmaAllocatorCreateInfo createInfo{ + .flags = 0, + .physicalDevice = pimpl->physicalDevice, + .device = pimpl->device, + .preferredLargeHeapBlockSize = 0, + .pAllocationCallbacks = nullptr, + .pDeviceMemoryCallbacks = nullptr, + .pHeapSizeLimit = nullptr, + .pVulkanFunctions = &functions, + .instance = pimpl->instance, + .vulkanApiVersion = VK_API_VERSION_1_3, + .pTypeExternalMemoryHandleTypes = nullptr + }; + + VkResult res = vmaCreateAllocator(&createInfo, &pimpl->allocator); + assert(res == VK_SUCCESS); + } + + + + // Now make the swapchain + createSwapchain(pimpl->device, pimpl->physicalDevice, pimpl->allocator, pimpl->queues, window, pimpl->surface, &pimpl->swapchain); + + + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + fenceInfo.pNext = nullptr; + for (int i = 0; i < FRAMES_IN_FLIGHT; i++) { + res = vkCreateFence(pimpl->device, &fenceInfo, nullptr, &pimpl->inFlightFences[i]); + assert(res == VK_SUCCESS); + } + + // create uniform buffer stuff + pimpl->uboLayoutBinding.binding = 0; + pimpl->uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + pimpl->uboLayoutBinding.descriptorCount = 1; + pimpl->uboLayoutBinding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; + pimpl->uboLayoutBinding.pImmutableSamplers = nullptr; + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; + descriptorSetLayoutInfo.bindingCount = 1; + descriptorSetLayoutInfo.pBindings = &pimpl->uboLayoutBinding; + res = vkCreateDescriptorSetLayout(pimpl->device, &descriptorSetLayoutInfo, nullptr, &pimpl->uboLayout); + assert(res == VK_SUCCESS); + + + } - Device::~Device() + GFXDevice::~GFXDevice() { - vkDestroyInstance(pimpl->m_instance, nullptr); + TRACE("Destroying GFXDevice..."); + + vkDestroyDescriptorSetLayout(pimpl->device, pimpl->uboLayout, nullptr); + + for (int i = 0; i < FRAMES_IN_FLIGHT; i++) { + + vkDestroyFence(pimpl->device, pimpl->inFlightFences[i], nullptr); + + vkDestroySemaphore(pimpl->device, pimpl->swapchain.releaseSemaphores[i], nullptr); + vkDestroySemaphore(pimpl->device, pimpl->swapchain.acquireSemaphores[i], nullptr); + + } + for (VkImageView view : pimpl->swapchain.imageViews) { + vkDestroyImageView(pimpl->device, view, nullptr); + } + for (VkFramebuffer fb : pimpl->swapchain.framebuffers) { + vkDestroyFramebuffer(pimpl->device, fb, nullptr); + } + destroyDepthBuffer(pimpl->swapchain.depthBuffer, pimpl->device, pimpl->allocator); + vkDestroyRenderPass(pimpl->device, pimpl->swapchain.renderpass, nullptr); + vkDestroySwapchainKHR(pimpl->device, pimpl->swapchain.swapchain, nullptr); + + vmaDestroyAllocator(pimpl->allocator); + + vkDestroyCommandPool(pimpl->device, pimpl->commandPool, nullptr); + vkDestroyDevice(pimpl->device, nullptr); + vkDestroySurfaceKHR(pimpl->instance, pimpl->surface, nullptr); + vkDestroyDebugUtilsMessengerEXT(pimpl->instance, pimpl->debugMessenger, nullptr); + vkDestroyInstance(pimpl->instance, nullptr); + } + + void GFXDevice::getViewportSize(uint32_t *w, uint32_t *h) + { + *w = pimpl->swapchain.extent.width; + *h = pimpl->swapchain.extent.height; + } + + void GFXDevice::draw(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t count, const void* pushConstantData) + { + assert(vertexBuffer->type == gfx::BufferType::VERTEX); + assert(vertexBuffer != nullptr); + assert(indexBuffer == nullptr || indexBuffer->type == gfx::BufferType::INDEX); + + DrawCall call{ + .vertexBuffer = vertexBuffer, + .indexBuffer = indexBuffer, // will be ignored if nullptr + .count = count, + }; + + memcpy(call.pushConstantData, pushConstantData, PUSH_CONSTANT_MAX_SIZE); + + pimpl->drawQueues[pipeline].push(call); + + } + + void GFXDevice::renderFrame() + { + VkResult res; + + const uint32_t frameIndex = pimpl->FRAMECOUNT % FRAMES_IN_FLIGHT; + + res = vkWaitForFences(pimpl->device, 1, &pimpl->inFlightFences[frameIndex], VK_TRUE, UINT64_MAX); + assert(res == VK_SUCCESS); + res = vkResetFences(pimpl->device, 1, &pimpl->inFlightFences[frameIndex]); + assert(res == VK_SUCCESS); + + uint32_t imageIndex = 0; + res = vkAcquireNextImageKHR(pimpl->device, pimpl->swapchain.swapchain, UINT64_MAX, pimpl->swapchain.acquireSemaphores[frameIndex], VK_NULL_HANDLE, &imageIndex); + if (res == VK_ERROR_OUT_OF_DATE_KHR) { + // recreate swapchain + waitIdle(); + createSwapchain(pimpl->device, pimpl->physicalDevice, pimpl->allocator, pimpl->queues, pimpl->window, pimpl->surface, &pimpl->swapchain); + return; + } + else { + assert(res == VK_SUCCESS || res == VK_SUBOPTIMAL_KHR); + } + + res = vkResetCommandBuffer(pimpl->commandBuffers[frameIndex], 0); + assert(res == VK_SUCCESS); + + // now record command buffer + { + VkCommandBufferBeginInfo beginInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + beginInfo.flags = 0; + beginInfo.pInheritanceInfo = nullptr; + res = vkBeginCommandBuffer(pimpl->commandBuffers[frameIndex], &beginInfo); + assert(res == VK_SUCCESS); + + VkRenderPassBeginInfo renderPassInfo{ VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; + renderPassInfo.renderPass = pimpl->swapchain.renderpass; + renderPassInfo.framebuffer = pimpl->swapchain.framebuffers[imageIndex]; + renderPassInfo.renderArea.offset = { 0, 0 }; + renderPassInfo.renderArea.extent = pimpl->swapchain.extent; + + std::array clearValues{}; + clearValues[0].color = { {0.0f, 0.0f, 0.0f, 1.0f} }; + clearValues[1].depthStencil = { 1.0f, 0 }; + renderPassInfo.clearValueCount = (uint32_t)clearValues.size(); + renderPassInfo.pClearValues = clearValues.data(); + + vkCmdBeginRenderPass(pimpl->commandBuffers[frameIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)pimpl->swapchain.extent.width; + viewport.height = (float)pimpl->swapchain.extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(pimpl->commandBuffers[frameIndex], 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = pimpl->swapchain.extent; + vkCmdSetScissor(pimpl->commandBuffers[frameIndex], 0, 1, &scissor); + + // run queued draw calls + + VkDeviceSize offsets[] = { 0 }; + + for (auto& [pipeline, queue] : pimpl->drawQueues) { + vkCmdBindPipeline(pimpl->commandBuffers[frameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->handle); + vkCmdBindDescriptorSets(pimpl->commandBuffers[frameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->layout, 0, 1, &pipeline->descriptorSets[frameIndex], 0, nullptr); + while (queue.empty() == false) { + + DrawCall call = queue.front(); + + vkCmdPushConstants(pimpl->commandBuffers[frameIndex], pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, 0, PUSH_CONSTANT_MAX_SIZE, call.pushConstantData); + + vkCmdBindVertexBuffers(pimpl->commandBuffers[frameIndex], 0, 1, &call.vertexBuffer->buffer, offsets); + + if (call.indexBuffer == nullptr) { + // do a simple draw call + vkCmdDraw(pimpl->commandBuffers[frameIndex], call.count, 1, 0, 0); + } else { + vkCmdBindIndexBuffer(pimpl->commandBuffers[frameIndex], call.indexBuffer->buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdDrawIndexed(pimpl->commandBuffers[frameIndex], call.count, 1, 0, 0, 0); + } + + queue.pop(); + } + } + + pimpl->drawQueues.clear(); + + vkCmdEndRenderPass(pimpl->commandBuffers[frameIndex]); + + res = vkEndCommandBuffer(pimpl->commandBuffers[frameIndex]); + assert(res == VK_SUCCESS); + } + + VkSubmitInfo submitInfo{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; + + VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = &pimpl->swapchain.acquireSemaphores[frameIndex]; + submitInfo.pWaitDstStageMask = waitStages; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &pimpl->commandBuffers[frameIndex]; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &pimpl->swapchain.releaseSemaphores[frameIndex]; + + res = vkQueueSubmit(pimpl->gfxQueue.handle, 1, &submitInfo, pimpl->inFlightFences[frameIndex]); + assert(res == VK_SUCCESS); + + VkPresentInfoKHR presentInfo{ VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &pimpl->swapchain.releaseSemaphores[frameIndex]; + + VkSwapchainKHR swapchains[] = { pimpl->swapchain.swapchain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapchains; + presentInfo.pImageIndices = &imageIndex; + presentInfo.pResults = nullptr; + + res = vkQueuePresentKHR(pimpl->presentQueue.handle, &presentInfo); + if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) { + // recreate swapchain + waitIdle(); + createSwapchain(pimpl->device, pimpl->physicalDevice, pimpl->allocator, pimpl->queues, pimpl->window, pimpl->surface, &pimpl->swapchain); + } + else { + assert(res == VK_SUCCESS); + } + + pimpl->FRAMECOUNT++; + } + + gfx::Pipeline* GFXDevice::createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat, uint64_t uniformBufferSize) + { + + VkResult res; + + gfx::Pipeline* pipeline = new gfx::Pipeline; + + // create uniform buffers + pipeline->uniformBuffers.resize(FRAMES_IN_FLIGHT); + for (int i = 0; i < FRAMES_IN_FLIGHT; i++) { + auto buf = new gfx::Buffer{}; + buf->size = uniformBufferSize; + buf->type = gfx::BufferType::UNIFORM; + + VkBufferCreateInfo bufferInfo{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferInfo.size = buf->size; + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; // prefer CPU memory for uniforms + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + res = vmaCreateBuffer(pimpl->allocator, &bufferInfo, &allocInfo, &buf->buffer, &buf->allocation, nullptr); + assert(res == VK_SUCCESS); + + pipeline->uniformBuffers[i] = buf; + } + + // create descriptor pool for uniform buffers + VkDescriptorPoolSize poolSize{}; + poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSize.descriptorCount = FRAMES_IN_FLIGHT; + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = 1; + poolInfo.pPoolSizes = &poolSize; + poolInfo.maxSets = FRAMES_IN_FLIGHT; + res = vkCreateDescriptorPool(pimpl->device, &poolInfo, nullptr, &pipeline->descriptorPool); + assert(res == VK_SUCCESS); + + std::array layouts; + layouts.fill(pimpl->uboLayout); + VkDescriptorSetAllocateInfo dSetAllocInfo{}; + dSetAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + dSetAllocInfo.descriptorPool = pipeline->descriptorPool; + dSetAllocInfo.descriptorSetCount = FRAMES_IN_FLIGHT; + dSetAllocInfo.pSetLayouts = layouts.data(); + res = vkAllocateDescriptorSets(pimpl->device, &dSetAllocInfo, pipeline->descriptorSets.data()); + + for (int i = 0; i < FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = pipeline->uniformBuffers[i]->buffer; + bufferInfo.offset = 0; + bufferInfo.range = uniformBufferSize; + VkWriteDescriptorSet descriptorWrite{}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = pipeline->descriptorSets[i]; + descriptorWrite.dstBinding = 0; + descriptorWrite.dstArrayElement = 0; + descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrite.descriptorCount = 1; + descriptorWrite.pBufferInfo = &bufferInfo; + descriptorWrite.pImageInfo = nullptr; + descriptorWrite.pTexelBufferView = nullptr; + + vkUpdateDescriptorSets(pimpl->device, 1, &descriptorWrite, 0, nullptr); + } + + // get vertex attrib layout: + VkVertexInputBindingDescription bindingDescription{ }; + bindingDescription.binding = 0; + bindingDescription.stride = vertexFormat.stride; + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + std::vector attribDescs{}; + attribDescs.reserve(vertexFormat.attributeDescriptions.size()); + for (const auto& desc : vertexFormat.attributeDescriptions) { + VkVertexInputAttributeDescription vulkanAttribDesc{}; + vulkanAttribDesc.binding = 0; + vulkanAttribDesc.location = desc.location; + vulkanAttribDesc.offset = desc.offset; + vulkanAttribDesc.format = vkinternal::getVertexAttribFormat(desc.format); + attribDescs.push_back(vulkanAttribDesc); + } + + auto vertShaderCode = readFile(vertShaderPath); + auto fragShaderCode = readFile(fragShaderPath); + INFO("Opened shader: {}", std::filesystem::path(vertShaderPath).filename().string()); + + VkShaderModule vertShaderModule = createShaderModule(pimpl->device, vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(pimpl->device, fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + vertShaderStageInfo.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + fragShaderStageInfo.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo shaderStages[2] = { vertShaderStageInfo, fragShaderStageInfo }; + + // this sets "vertex attribute pointers" + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.vertexAttributeDescriptionCount = attribDescs.size(); + vertexInputInfo.pVertexAttributeDescriptions = attribDescs.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)pimpl->swapchain.extent.width; + viewport.height = (float)pimpl->swapchain.extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = pimpl->swapchain.extent; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = dynamicStates.size(); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; // ignored + rasterizer.depthBiasClamp = 0.0f; // ignored + rasterizer.depthBiasSlopeFactor = 0.0f; // ignored + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.minSampleShading = 1.0f; // ignored + multisampling.pSampleMask = nullptr; // ignored + multisampling.alphaToCoverageEnable = VK_FALSE; // ignored + multisampling.alphaToOneEnable = VK_FALSE; // ignored + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | + VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | + VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // ignored + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // ignored + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // ignored + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // ignored + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // ignored + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // ignored + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; // ignored + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; // ignored + colorBlending.blendConstants[1] = 0.0f; // ignored + colorBlending.blendConstants[2] = 0.0f; // ignored + colorBlending.blendConstants[3] = 0.0f; // ignored + + VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = VK_TRUE; + depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.minDepthBounds = 0.0f; + depthStencil.maxDepthBounds = 1.0f; + depthStencil.stencilTestEnable = VK_FALSE; + depthStencil.front = {}; + depthStencil.back = {}; + + VkPushConstantRange pushConstantRange{}; + pushConstantRange.offset = 0; + pushConstantRange.size = PUSH_CONSTANT_MAX_SIZE; + pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkPipelineLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + layoutInfo.setLayoutCount = 1; + layoutInfo.pSetLayouts = &pimpl->uboLayout; + layoutInfo.pushConstantRangeCount = 1; + layoutInfo.pPushConstantRanges = &pushConstantRange; + + res = vkCreatePipelineLayout(pimpl->device, &layoutInfo, nullptr, &pipeline->layout); + assert(res == VK_SUCCESS); + + VkGraphicsPipelineCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + createInfo.stageCount = 2; + createInfo.pStages = shaderStages; + createInfo.pVertexInputState = &vertexInputInfo; + createInfo.pInputAssemblyState = &inputAssembly; + createInfo.pViewportState = &viewportState; + createInfo.pRasterizationState = &rasterizer; + createInfo.pMultisampleState = &multisampling; + createInfo.pDepthStencilState = &depthStencil; + createInfo.pColorBlendState = &colorBlending; + createInfo.pDynamicState = &dynamicState; + createInfo.layout = pipeline->layout; + createInfo.renderPass = pimpl->swapchain.renderpass; + createInfo.subpass = 0; + createInfo.basePipelineHandle = VK_NULL_HANDLE; + createInfo.basePipelineIndex = -1; + + res = vkCreateGraphicsPipelines(pimpl->device, VK_NULL_HANDLE, 1, &createInfo, nullptr, &pipeline->handle); + assert(res == VK_SUCCESS); + + vkDestroyShaderModule(pimpl->device, fragShaderModule, nullptr); + vkDestroyShaderModule(pimpl->device, vertShaderModule, nullptr); + + return pipeline; + + } + + void GFXDevice::destroyPipeline(const gfx::Pipeline* pipeline) + { + + vkDestroyPipeline(pimpl->device, pipeline->handle, nullptr); + vkDestroyPipelineLayout(pimpl->device, pipeline->layout, nullptr); + + vkDestroyDescriptorPool(pimpl->device, pipeline->descriptorPool, nullptr); + + for (int i = 0; i < FRAMES_IN_FLIGHT; i++) { + destroyBuffer(pipeline->uniformBuffers[i]); + } + + delete pipeline; + } + + void GFXDevice::updateUniformBuffer(const gfx::Pipeline* pipeline, void* data) + { + VkResult res; + + void* uniformDest; + for (gfx::Buffer* buffer : pipeline->uniformBuffers) { + res = vmaMapMemory(pimpl->allocator, buffer->allocation, &uniformDest); + assert(res == VK_SUCCESS); + memcpy(uniformDest, data, buffer->size); + vmaUnmapMemory(pimpl->allocator, buffer->allocation); + } + + } + + gfx::Buffer* GFXDevice::createBuffer(gfx::BufferType type, uint64_t size, const void* data) + { + VkResult res; + + auto out = new gfx::Buffer{}; + out->size = size; + + out->type = type; + + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + // first create the staging buffer + { + VkBufferCreateInfo stagingBufferInfo{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + stagingBufferInfo.size = out->size; + stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + stagingBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + stagingBufferInfo.flags = 0; + + VmaAllocationCreateInfo stagingAllocInfo{}; + stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO; + stagingAllocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + stagingAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + res = vmaCreateBuffer(pimpl->allocator, &stagingBufferInfo, &stagingAllocInfo, &stagingBuffer, &stagingAllocation, nullptr); + assert(res == VK_SUCCESS); + + void* dataDest; + res = vmaMapMemory(pimpl->allocator, stagingAllocation, &dataDest); + assert(res == VK_SUCCESS); + memcpy(dataDest, data, out->size); + vmaUnmapMemory(pimpl->allocator, stagingAllocation); + } + + // create the actual buffer on the GPU + { + VkBufferCreateInfo gpuBufferInfo{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + gpuBufferInfo.size = out->size; + gpuBufferInfo.usage = vkinternal::getBufferUsageFlag(type) | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + gpuBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + gpuBufferInfo.flags = 0; + + VmaAllocationCreateInfo gpuAllocationInfo{}; + gpuAllocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + gpuAllocationInfo.flags = 0; + + res = vmaCreateBuffer(pimpl->allocator, &gpuBufferInfo, &gpuAllocationInfo, &out->buffer, &out->allocation, nullptr); + assert(res == VK_SUCCESS); + } + + // copy the data from the staging buffer to the gpu buffer + copyBuffer(pimpl->device, pimpl->commandPool, pimpl->gfxQueue.handle, stagingBuffer, out->buffer, out->size); + + // destroy staging buffer + vmaDestroyBuffer(pimpl->allocator, stagingBuffer, stagingAllocation); + + return out; + } + + void GFXDevice::destroyBuffer(const gfx::Buffer* buffer) + { + vmaDestroyBuffer(pimpl->allocator, buffer->buffer, buffer->allocation); + delete buffer; + } + + void GFXDevice::waitIdle() + { + vkDeviceWaitIdle(pimpl->device); } } diff --git a/src/input.cpp b/src/input.cpp index ace2186..c65598e 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -5,31 +5,33 @@ #include #include -Input::Input(const Window &win) : m_win(win) -{ - m_enabledDevices.fill(true); -} +namespace engine { -Input::~Input() -{ -} + Input::Input(const Window& win) : m_win(win) + { + m_enabledDevices.fill(true); + } -// private methods + Input::~Input() + { + } -float Input::getDeviceAxis(enum InputDevice device, int axis) const -{ - switch (device) { + // private methods + + float Input::getDeviceAxis(enum InputDevice device, int axis) const + { + switch (device) { case InputDevice::MOUSE: switch (static_cast(axis)) { - case inputs::MouseAxis::X: - return static_cast(m_win.getMouseDX()); - case inputs::MouseAxis::Y: - return static_cast(m_win.getMouseDY()); - case inputs::MouseAxis::X_SCR: - return m_win.getMouseScrollX(); - case inputs::MouseAxis::Y_SCR: - return m_win.getMouseScrollY(); - default: break; + case inputs::MouseAxis::X: + return static_cast(m_win.getMouseDX()); + case inputs::MouseAxis::Y: + return static_cast(m_win.getMouseDY()); + case inputs::MouseAxis::X_SCR: + return m_win.getMouseScrollX(); + case inputs::MouseAxis::Y_SCR: + return m_win.getMouseScrollY(); + default: break; } break; case InputDevice::KEYBOARD: @@ -37,13 +39,13 @@ float Input::getDeviceAxis(enum InputDevice device, int axis) const case InputDevice::CONTROLLER: break; default: break; + } + throw std::runtime_error("Error getting device axis"); } - throw std::runtime_error("Error getting device axis"); -} -bool Input::getDeviceButton(enum InputDevice device, int button) const -{ - switch (device) { + bool Input::getDeviceButton(enum InputDevice device, int button) const + { + switch (device) { case InputDevice::MOUSE: return m_win.getButton(static_cast(button)); case InputDevice::KEYBOARD: @@ -51,13 +53,13 @@ bool Input::getDeviceButton(enum InputDevice device, int button) const case InputDevice::CONTROLLER: break; default: break; + } + throw std::runtime_error("Error getting device button"); } - throw std::runtime_error("Error getting device button"); -} -bool Input::getDeviceButtonDown(enum InputDevice device, int button) const -{ - switch (device) { + bool Input::getDeviceButtonDown(enum InputDevice device, int button) const + { + switch (device) { case InputDevice::MOUSE: return m_win.getButtonPress(static_cast(button)); case InputDevice::KEYBOARD: @@ -65,13 +67,13 @@ bool Input::getDeviceButtonDown(enum InputDevice device, int button) const case InputDevice::CONTROLLER: break; default: break; + } + throw std::runtime_error("Error getting device button"); } - throw std::runtime_error("Error getting device button"); -} -bool Input::getDeviceButtonUp(enum InputDevice device, int button) const -{ - switch (device) { + bool Input::getDeviceButtonUp(enum InputDevice device, int button) const + { + switch (device) { case InputDevice::MOUSE: return m_win.getButtonRelease(static_cast(button)); case InputDevice::KEYBOARD: @@ -79,154 +81,157 @@ bool Input::getDeviceButtonUp(enum InputDevice device, int button) const case InputDevice::CONTROLLER: break; default: break; + } + throw std::runtime_error("Error getting device button"); } - throw std::runtime_error("Error getting device button"); -} -float Input::getButtonAxis(enum InputDevice device, int high, int low) const -{ - float value = 0.0f; - if (getDeviceButton(device, high)) value += 1.0f; - if (low != 0) { - if (getDeviceButton(device, low)) value += -1.0f; + float Input::getButtonAxis(enum InputDevice device, int high, int low) const + { + float value = 0.0f; + if (getDeviceButton(device, high)) value += 1.0f; + if (low != 0) { + if (getDeviceButton(device, low)) value += -1.0f; + } + return value; } - return value; -} -// public methods + // public methods -void Input::addInputButton(const std::string& name, InputDevice device, int button) -{ - m_buttonEntries.push_back( { name, device, button } ); -} + void Input::addInputButton(const std::string& name, InputDevice device, int button) + { + m_buttonEntries.push_back({ name, device, button }); + } -void Input::addInputAxis(const std::string& name, InputDevice device, int axis) -{ - m_axisEntries.push_back( { name, device, axis, false, 0, 0 } ); -} + void Input::addInputAxis(const std::string& name, InputDevice device, int axis) + { + m_axisEntries.push_back({ name, device, axis, false, 0, 0 }); + } -void Input::addInputButtonAsAxis(const std::string& name, InputDevice device, int high, int low) -{ - m_axisEntries.push_back( { name, device, 0, true, high, low } ); -} + void Input::addInputButtonAsAxis(const std::string& name, InputDevice device, int high, int low) + { + m_axisEntries.push_back({ name, device, 0, true, high, low }); + } -// OVERLOADS: + // OVERLOADS: -// Add a mouse input -void Input::addInputButton(const std::string& name, inputs::MouseButton button) -{ - addInputButton(name, InputDevice::MOUSE, static_cast(button)); -} + // Add a mouse input + void Input::addInputButton(const std::string& name, inputs::MouseButton button) + { + addInputButton(name, InputDevice::MOUSE, static_cast(button)); + } -void Input::addInputAxis(const std::string& name, inputs::MouseAxis axis) -{ - addInputAxis(name, InputDevice::MOUSE, static_cast(axis)); -} + void Input::addInputAxis(const std::string& name, inputs::MouseAxis axis) + { + addInputAxis(name, InputDevice::MOUSE, static_cast(axis)); + } -void Input::addInputButtonAsAxis(const std::string& name, inputs::MouseButton high, inputs::MouseButton low) -{ - addInputButtonAsAxis(name, InputDevice::MOUSE, static_cast(high), static_cast(low)); -} + void Input::addInputButtonAsAxis(const std::string& name, inputs::MouseButton high, inputs::MouseButton low) + { + addInputButtonAsAxis(name, InputDevice::MOUSE, static_cast(high), static_cast(low)); + } -// Add a keyboard input (TODO: add KeyboardButton enum class) -void Input::addInputButton(const std::string& name, inputs::Key button) -{ - addInputButton(name, InputDevice::KEYBOARD, static_cast(button)); -} + // Add a keyboard input (TODO: add KeyboardButton enum class) + void Input::addInputButton(const std::string& name, inputs::Key button) + { + addInputButton(name, InputDevice::KEYBOARD, static_cast(button)); + } -void Input::addInputButtonAsAxis(const std::string& name, inputs::Key high, inputs::Key low) -{ - addInputButtonAsAxis(name, InputDevice::KEYBOARD, static_cast(high), static_cast(low)); -} + void Input::addInputButtonAsAxis(const std::string& name, inputs::Key high, inputs::Key low) + { + addInputButtonAsAxis(name, InputDevice::KEYBOARD, static_cast(high), static_cast(low)); + } -void Input::delInputButton(int index) -{ - std::vector::iterator it = m_buttonEntries.begin(); - std::advance(it, index); - m_buttonEntries.erase(it); -} + void Input::delInputButton(int index) + { + std::vector::iterator it = m_buttonEntries.begin(); + std::advance(it, index); + m_buttonEntries.erase(it); + } -void Input::delInputAxis(int index) -{ - std::vector::iterator it = m_axisEntries.begin(); - std::advance(it, index); - m_axisEntries.erase(it); -} + void Input::delInputAxis(int index) + { + std::vector::iterator it = m_axisEntries.begin(); + std::advance(it, index); + m_axisEntries.erase(it); + } -void Input::setDeviceActive(enum InputDevice device, bool active) -{ - m_enabledDevices[static_cast(device)] = active; -} + void Input::setDeviceActive(enum InputDevice device, bool active) + { + m_enabledDevices[static_cast(device)] = active; + } -bool Input::getDeviceActive(enum InputDevice device) const -{ - return m_enabledDevices[static_cast(device)]; -} + bool Input::getDeviceActive(enum InputDevice device) const + { + return m_enabledDevices[static_cast(device)]; + } -float Input::getAxis(const std::string& axisName) const -{ - for (const AxisEntry& e : m_axisEntries) { - if (e.name == axisName) { - if (m_enabledDevices[static_cast(e.device)]) { - if (e.isButtonAxis) { - return getButtonAxis(e.device, e.high, e.low); - } else { - return getDeviceAxis(e.device, e.axis); + float Input::getAxis(const std::string& axisName) const + { + for (const AxisEntry& e : m_axisEntries) { + if (e.name == axisName) { + if (m_enabledDevices[static_cast(e.device)]) { + if (e.isButtonAxis) { + return getButtonAxis(e.device, e.high, e.low); + } + else { + return getDeviceAxis(e.device, e.axis); + } } } } + return 0.0f; // instead of throwing an exception, just return nothing + // throw std::runtime_error("Unable to find mapping in input table"); } - return 0.0f; // instead of throwing an exception, just return nothing -// throw std::runtime_error("Unable to find mapping in input table"); -} -bool Input::getButton(const std::string& buttonName) const -{ - bool isDown = false; + bool Input::getButton(const std::string& buttonName) const + { + bool isDown = false; - for (const ButtonEntry& e : m_buttonEntries) { - if (e.name == buttonName) { - if (m_enabledDevices[static_cast(e.device)]) { - if (getDeviceButton(e.device, e.button) == true) { - isDown = true; - break; + for (const ButtonEntry& e : m_buttonEntries) { + if (e.name == buttonName) { + if (m_enabledDevices[static_cast(e.device)]) { + if (getDeviceButton(e.device, e.button) == true) { + isDown = true; + break; + } } } } + return isDown; } - return isDown; -} -bool Input::getButtonPress(const std::string& buttonName) const -{ - bool isPressed = false; + bool Input::getButtonPress(const std::string& buttonName) const + { + bool isPressed = false; - for (const ButtonEntry& e : m_buttonEntries) { - if (e.name == buttonName) { - if (m_enabledDevices[static_cast(e.device)]) { - if (getDeviceButtonDown(e.device, e.button) == true) { - isPressed = true; - break; + for (const ButtonEntry& e : m_buttonEntries) { + if (e.name == buttonName) { + if (m_enabledDevices[static_cast(e.device)]) { + if (getDeviceButtonDown(e.device, e.button) == true) { + isPressed = true; + break; + } } } } + return isPressed; } - return isPressed; -} -bool Input::getButtonRelease(const std::string& buttonName) const -{ - bool isReleased = false; + bool Input::getButtonRelease(const std::string& buttonName) const + { + bool isReleased = false; - for (const ButtonEntry& e : m_buttonEntries) { - if (e.name == buttonName) { - if (m_enabledDevices[static_cast(e.device)]) { - if (getDeviceButtonUp(e.device, e.button) == true) { - isReleased = true; - break; + for (const ButtonEntry& e : m_buttonEntries) { + if (e.name == buttonName) { + if (m_enabledDevices[static_cast(e.device)]) { + if (getDeviceButtonUp(e.device, e.button) == true) { + isReleased = true; + break; + } } } } + return isReleased; } - return isReleased; -} + +} \ No newline at end of file diff --git a/src/object.cpp b/src/object.cpp index f81c599..46efc52 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -7,127 +7,134 @@ #include -int Object::s_object_count = 0; +#include -Object::Object(std::string name, Object* parent, SceneRoot& root, struct GameIO things) - : m_name(name), m_parent(parent), root(root), - m_gameIO(things), - win(*things.win), - inp(*things.input), - res(*things.resMan) -{ - s_object_count++; -} +namespace engine { -Object::~Object() -{ -} + int Object::s_object_count = 0; -std::string Object::getName() -{ - return m_name; -} + Object::Object(std::string name, Object* parent, SceneRoot& root, struct GameIO things) + : m_name(name), m_parent(parent), root(root), + m_gameIO(things), + win(*things.win), + inp(*things.input), + res(*things.resMan) + { + s_object_count++; + } -Object* Object::getParent() -{ - return m_parent; -} + Object::~Object() + { + } -Object* Object::getChild(std::string name) -{ - for (const auto& child : m_children) { - if (name == child->getName()) { - return child.get(); + std::string Object::getName() + { + return m_name; + } + + Object* Object::getParent() + { + return m_parent; + } + + Object* Object::getChild(std::string name) + { + for (const auto& child : m_children) { + if (name == child->getName()) { + return child.get(); + } + } + return nullptr; + } + + std::vector Object::getChildren() + { + std::vector newVector{}; + for (const auto& child : m_children) { + newVector.push_back(child.get()); + } + return newVector; + } + + Object* Object::createChild(std::string name) + { + if (getChild(name) != nullptr) { + throw std::runtime_error("Attempt to create child object with existing name"); + } + m_children.emplace_back(std::make_unique(name, this, root, m_gameIO)); + return m_children.back().get(); + } + + void Object::deleteChild(std::string name) + { + for (auto itr = m_children.begin(); itr != m_children.end(); ++itr) { + if ((*itr)->getName() == name) { + m_children.erase(itr); + return; + } + } + throw std::runtime_error("Unable to delete child '" + name + "' as it does not exist"); + } + + void Object::printTree(int level) + { + std::string buf; + for (int i = 0; i < level; i++) { + if (i + 1 == level) { + buf += "\\_______"; + } + else { + buf += " "; + } + } + buf += m_name; + INFO(buf); + for (const auto& child : this->getChildren()) { + child->printTree(level + 1); } } - return nullptr; -} -std::vector Object::getChildren() -{ - std::vector newVector{}; - for (const auto& child : m_children) { - newVector.push_back(child.get()); - } - return newVector; -} + void Object::getAllSubComponents(struct CompList& compList, glm::mat4 parentTransform) + { + using namespace components; -Object* Object::createChild(std::string name) -{ - if (getChild(name) != nullptr) { - throw std::runtime_error("Attempt to create child object with existing name"); - } - m_children.emplace_back(std::make_unique(name, this, root, m_gameIO)); - return m_children.back().get(); -} + glm::mat4 objTransform{ 1.0f }; -void Object::deleteChild(std::string name) -{ - for (auto itr = m_children.begin(); itr != m_children.end(); ++itr) { - if ((*itr)->getName() == name) { - m_children.erase(itr); - return; + auto t = transform; + + // rotation + objTransform = glm::mat4_cast(t.rotation); + + // position + reinterpret_cast(objTransform[3]) = t.position; + + // scale (effectively applied first + objTransform = glm::scale(objTransform, t.scale); + + glm::mat4 newTransform = parentTransform * objTransform; + + for (const auto& compUnq : m_components) { + const auto comp = compUnq.get(); + switch (comp->getType()) { + case Component::TypeEnum::CAMERA: + compList.cameras.emplace_back(dynamic_cast(comp), newTransform); + break; + case Component::TypeEnum::RENDERER: + compList.renderers.emplace_back(dynamic_cast(comp), newTransform); + break; + case Component::TypeEnum::UI: + compList.uis.emplace_back(dynamic_cast(comp), newTransform); + break; + case Component::TypeEnum::CUSTOM: + compList.customs.emplace_back(dynamic_cast(comp), newTransform); + break; + default: + break; + } + } + for (const auto& child : m_children) { + child->getAllSubComponents(compList, newTransform); } } - throw std::runtime_error("Unable to delete child '" + name + "' as it does not exist"); -} -void Object::printTree(int level) -{ - std::string buf; - for (int i = 0; i < level; i++) { - if (i+1 == level) { - buf += "\\_______"; - } else { - buf += " "; - } - } - buf += m_name; - INFO(buf); - for (const auto& child : this->getChildren()) { - child->printTree(level+1); - } -} - -void Object::getAllSubComponents(struct CompList& compList, glm::mat4 parentTransform) -{ - using namespace components; - - glm::mat4 objTransform{1.0f}; - - auto t = transform; - - // rotation - objTransform = glm::mat4_cast(t.rotation); - - // position - reinterpret_cast(objTransform[3]) = t.position; - - // scale (effectively applied first - objTransform = glm::scale(objTransform, t.scale); - - const glm::mat4 newTransform = parentTransform * objTransform; - - for (const auto& compUnq : m_components) { - const auto comp = compUnq.get(); - switch (comp->getType()) { - case Component::TypeEnum::CAMERA: - compList.cameras.emplace_back(dynamic_cast(comp), newTransform); - break; - case Component::TypeEnum::RENDERER: - compList.renderers.emplace_back(dynamic_cast(comp), newTransform); - break; - case Component::TypeEnum::UI: - compList.uis.emplace_back(dynamic_cast(comp), newTransform); - break; - case Component::TypeEnum::CUSTOM: - compList.customs.emplace_back(dynamic_cast(comp), newTransform); - break; - default: - break; - } - } - for (const auto& child : m_children) { - child->getAllSubComponents(compList, newTransform); - } -} +} \ No newline at end of file diff --git a/src/resource_manager.cpp b/src/resource_manager.cpp index 6a2da36..6d0ee8c 100644 --- a/src/resource_manager.cpp +++ b/src/resource_manager.cpp @@ -8,68 +8,75 @@ #include "log.hpp" -ResourceManager::ResourceManager() -{ +namespace engine { + + ResourceManager::ResourceManager() + { #ifdef _MSC_VER - CHAR exeDirBuf[MAX_PATH + 1]; - GetModuleFileNameA(NULL, exeDirBuf, MAX_PATH + 1); - std::filesystem::path cwd = std::filesystem::path(exeDirBuf).parent_path(); - (void)_chdir((const char*)std::filesystem::absolute(cwd).c_str()); + CHAR exeDirBuf[MAX_PATH + 1]; + GetModuleFileNameA(NULL, exeDirBuf, MAX_PATH + 1); + std::filesystem::path cwd = std::filesystem::path(exeDirBuf).parent_path(); + (void)_chdir((const char*)std::filesystem::absolute(cwd).c_str()); #else - std::filesystem::path cwd = std::filesystem::current_path(); + std::filesystem::path cwd = std::filesystem::current_path(); #endif - if (std::filesystem::is_directory(cwd / "res")) { - m_resourcesPath = cwd / "res"; - } else { - m_resourcesPath = cwd.parent_path() / "share" / "sdltest"; - } - - if (std::filesystem::is_directory(m_resourcesPath) == false) { - m_resourcesPath = cwd.root_path() / "usr" / "local" / "share" / "sdltest"; - } - - if (std::filesystem::is_directory(m_resourcesPath) == false) { - throw std::runtime_error("Unable to determine resources location. CWD: " + cwd.string()); - } -} - -std::unique_ptr ResourceManager::getResourcesListString() -{ - auto bufPtr = std::make_unique(); - std::string& buf = *bufPtr; - int maxLength = 0; - for (const auto& [name, ptr] : m_resources) { - if (name.length() > maxLength) - maxLength = name.length(); - } - for (const auto& [name, ptr] : m_resources) { - buf += name; - for (int i = 0; i < (maxLength - name.length() + 4); i++) { - buf += " "; + if (std::filesystem::is_directory(cwd / "res")) { + m_resourcesPath = cwd / "res"; } - buf += std::to_string(ptr.use_count()) + "\n"; + else { + m_resourcesPath = cwd.parent_path() / "share" / "sdltest"; + } + + if (std::filesystem::is_directory(m_resourcesPath) == false) { + m_resourcesPath = cwd.root_path() / "usr" / "local" / "share" / "sdltest"; + } + + if (std::filesystem::is_directory(m_resourcesPath) == false) { + throw std::runtime_error("Unable to determine resources location. CWD: " + cwd.string()); + } + + m_resourcesPath = "C:/Users/Bailey/source/repos/game/res"; + } + + std::unique_ptr ResourceManager::getResourcesListString() + { + auto bufPtr = std::make_unique(); + std::string& buf = *bufPtr; + int maxLength = 0; + for (const auto& [name, ptr] : m_resources) { + if (name.length() > maxLength) + maxLength = name.length(); + } + for (const auto& [name, ptr] : m_resources) { + buf += name; + for (int i = 0; i < (maxLength - name.length() + 4); i++) { + buf += " "; + } + buf += std::to_string(ptr.use_count()) + "\n"; + } + return bufPtr; } - return bufPtr; -} -std::vector> ResourceManager::getAllResourcesOfType(const std::string& type) -{ - std::vector> resources; - for (const auto& [name, ptr] : m_resources) { - if (ptr.expired() == false) { - if (ptr.lock()->getType() == type) { - resources.push_back(ptr); + std::vector> ResourceManager::getAllResourcesOfType(const std::string& type) + { + std::vector> resources; + for (const auto& [name, ptr] : m_resources) { + if (ptr.expired() == false) { + if (ptr.lock()->getType() == type) { + resources.push_back(ptr); + } } } + return resources; } - return resources; -} -// private + // private -std::filesystem::path ResourceManager::getFilePath(const std::string& name) -{ - return m_resourcesPath / name; -} + std::filesystem::path ResourceManager::getFilePath(const std::string& name) + { + return m_resourcesPath / name; + } + +} \ No newline at end of file diff --git a/src/resources/font.cpp b/src/resources/font.cpp index 41df828..8a505aa 100644 --- a/src/resources/font.cpp +++ b/src/resources/font.cpp @@ -3,7 +3,7 @@ #include #include FT_FREETYPE_H -namespace resources { +namespace engine::resources { Font::Font(const std::filesystem::path& resPath) : Resource(resPath, "font") { diff --git a/src/resources/mesh.cpp b/src/resources/mesh.cpp index c155882..8d276a9 100644 --- a/src/resources/mesh.cpp +++ b/src/resources/mesh.cpp @@ -1,14 +1,18 @@ #include "resources/mesh.hpp" -namespace resources { +#include "gfx_device.hpp" + +#include "log.hpp" + +namespace engine::resources { struct MeshFileHeader { - unsigned int vertex_count; - unsigned int index_count; - int material; + uint32_t vertex_count; + uint32_t index_count; + int32_t material; }; -static void loadMeshFromFile(const std::filesystem::path& path, std::vector* vertices, std::vector* indices) +static void loadMeshFromFile(const std::filesystem::path& path, std::vector* vertices, std::vector* indices) { // TODO @@ -26,73 +30,43 @@ static void loadMeshFromFile(const std::filesystem::path& path, std::vectorresize(header.index_count); vertices->resize(header.vertex_count); - fread(&(*indices)[0], sizeof(unsigned int) * header.index_count, 1, fp); - fread(&((*vertices)[0].pos[0]), sizeof(float) * 8 * header.vertex_count, 1, fp); - + fread(indices->data(), sizeof(uint32_t) * header.index_count, 1, fp); + fread(vertices->data(), sizeof(float) * 8 * header.vertex_count, 1, fp); fclose(fp); } -static void loadObjFromFile(const std::filesystem::path& path, std::vector* vertices, std::vector* indices) -{ - -} - -// -1 means invalidated -int Mesh::s_active_vao = -1; - -void Mesh::bindVAO() const -{ - if (s_active_vao != m_vao) { - glBindVertexArray(m_vao); - s_active_vao = m_vao; - } -} - void Mesh::initMesh() { - glGenVertexArrays(1, &m_vao); - bindVAO(); - glGenBuffers(1, &m_vbo); - glGenBuffers(1, &m_ebo); - - glBindBuffer(GL_ARRAY_BUFFER, m_vbo); - glBufferData(GL_ARRAY_BUFFER, m_vertices.size()*sizeof(Vertex), &m_vertices[0], GL_STATIC_DRAW); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof(unsigned int), &(m_indices[0]), GL_STATIC_DRAW); + vb = gfxdev->createBuffer(gfx::BufferType::VERTEX, m_vertices.size() * sizeof(Vertex), m_vertices.data()); + ib = gfxdev->createBuffer(gfx::BufferType::INDEX, m_indices.size() * sizeof(uint32_t), m_indices.data()); - // position - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)offsetof(Vertex, pos)); - // normal - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)offsetof(Vertex, norm)); - // uv - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)offsetof(Vertex, uv)); + TRACE("VB PTR in mesh: {}", (void*)vb); -} + TRACE("Vertices:"); -void Mesh::drawMesh(const Shader& shader) -{ - bindVAO(); - shader.makeActive(); -#ifndef SDLTEST_NOGFX - glDrawElements(GL_TRIANGLES, static_cast(m_indices.size()), GL_UNSIGNED_INT, 0); -#endif + for (const auto& v : m_vertices) { + TRACE("pos: {}, {}, {}", v.pos.x, v.pos.y, v.pos.z); + } + + TRACE("Indices:"); + + for (const uint32_t i : m_indices) { + TRACE("\t{}", i); + } } Mesh::Mesh(const std::vector& vertices) : Resource("", "mesh") { // constructor for custom meshes without an index array m_vertices = vertices; // COPY over vertices - for (int i = 0; i < m_vertices.size(); i++) { + for (uint32_t i = 0; i < m_vertices.size(); i++) { m_indices.push_back(i); } initMesh(); } -Mesh::Mesh(const std::vector& vertices, const std::vector& indices) : Resource("", "mesh") +Mesh::Mesh(const std::vector& vertices, const std::vector& indices) : Resource("", "mesh") { m_vertices = vertices; // COPY over vertices m_indices = indices; // COPY over indices; @@ -108,12 +82,8 @@ Mesh::Mesh(const std::filesystem::path& resPath) : Resource(resPath, "mesh") Mesh::~Mesh() { - glDeleteVertexArrays(1, &m_vao); - glDeleteBuffers(1, &m_vbo); - glDeleteBuffers(1, &m_ebo); - if (s_active_vao == m_vao) { - s_active_vao = -1; - } + gfxdev->destroyBuffer(ib); + gfxdev->destroyBuffer(vb); } } diff --git a/src/resources/resource.cpp b/src/resources/resource.cpp index faa40b7..457ecef 100644 --- a/src/resources/resource.cpp +++ b/src/resources/resource.cpp @@ -2,19 +2,23 @@ #include -Resource::Resource(const std::filesystem::path& resPath, const std::string& type) : m_resourcePath(resPath), m_type(type) -{ - if (m_type != "mesh") - TRACE("Creating {} resource: {}", type, resPath.filename().string()); -} +namespace engine { -Resource::~Resource() -{ - if (m_type != "mesh") - TRACE("Destroyed {} resource: {}", m_type, m_resourcePath.filename().string()); -} + Resource::Resource(const std::filesystem::path& resPath, const std::string& type) : m_resourcePath(resPath), m_type(type) + { + if (m_type != "mesh") + TRACE("Creating {} resource: {}", type, resPath.filename().string()); + } -std::string Resource::getType() -{ - return m_type; -} + Resource::~Resource() + { + if (m_type != "mesh") + TRACE("Destroyed {} resource: {}", m_type, m_resourcePath.filename().string()); + } + + std::string Resource::getType() + { + return m_type; + } + +} \ No newline at end of file diff --git a/src/resources/shader.cpp b/src/resources/shader.cpp index 59c1a7f..25c643e 100644 --- a/src/resources/shader.cpp +++ b/src/resources/shader.cpp @@ -1,8 +1,8 @@ #include "resources/shader.hpp" -#include +#include "log.hpp" -#include +#include "gfx_device.hpp" #include #include @@ -24,200 +24,27 @@ static std::unique_ptr> readFile(const char * path) return buf; } -static GLuint compile(const char *path, GLenum type) -{ - auto src = readFile(path); - - // compile shader - GLuint handle = glCreateShader(type); - GLint size = src->size(); - GLchar *data = src->data(); - glShaderSource(handle, 1, &data, &size); - glCompileShader(handle); - - // check for compilation error - GLint compiled; - glGetShaderiv(handle, GL_COMPILE_STATUS, &compiled); - if (compiled == 0) { - GLint log_len; - glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_len); - GLchar *log_msg = (GLchar *)calloc(1, log_len); - if (log_msg == NULL) { - throw std::runtime_error("Error allocating memory for shader compilation error log"); - } - glGetShaderInfoLog(handle, log_len, NULL, log_msg); - throw std::runtime_error("Shader compilation error in " + std::string(path) + " log:\n" + std::string(log_msg)); - } return handle; -} - -namespace resources { - -// I've got to do this because of GL's stupid state machine -GLuint Shader::s_activeProgram = 0; +namespace engine::resources { Shader::Shader(const std::filesystem::path& resPath) : Resource(resPath, "shader") { - const std::string vertexShaderPath = (resPath.parent_path()/std::filesystem::path(resPath.stem().string() + ".vert")).string(); - const std::string fragmentShaderPath = (resPath.parent_path()/std::filesystem::path(resPath.stem().string() + ".frag")).string(); - GLuint vs = compile(vertexShaderPath.c_str(), GL_VERTEX_SHADER); - GLuint fs = compile(fragmentShaderPath.c_str(), GL_FRAGMENT_SHADER); - m_program = glCreateProgram(); - glAttachShader(m_program, vs); - glAttachShader(m_program, fs); - glLinkProgram(m_program); - glValidateProgram(m_program); + gfx::VertexFormat vertexFormat {}; + vertexFormat.stride = 8 * sizeof(float); + vertexFormat.attributeDescriptions.emplace_back(0, gfx::VertexAttribFormat::VEC3, 0); // pos + vertexFormat.attributeDescriptions.emplace_back(1, gfx::VertexAttribFormat::VEC3, sizeof(glm::vec3)); // norm + vertexFormat.attributeDescriptions.emplace_back(2, gfx::VertexAttribFormat::VEC2, sizeof(glm::vec3) + sizeof(glm::vec3)); // uv - // flag shader objects for deletion, this does not take effect until the program is deleted - glDeleteShader(vs); - glDeleteShader(fs); + const std::string vertexShaderPath = (resPath.parent_path()/std::filesystem::path(resPath.stem().string() + ".vert.spv")).string(); + const std::string fragmentShaderPath = (resPath.parent_path()/std::filesystem::path(resPath.stem().string() + ".frag.spv")).string(); - GLint linked, validated; - glGetProgramiv(m_program, GL_LINK_STATUS, &linked); - glGetProgramiv(m_program, GL_VALIDATE_STATUS, &validated); - if (linked == 0 || validated == 0) { - GLint log_len; - glGetProgramiv(m_program, GL_INFO_LOG_LENGTH, &log_len); - GLchar *log_msg = (GLchar *)calloc(1, log_len); - if (log_msg == NULL) { - throw std::runtime_error("Error allocating memory for shader linking error log"); - } - glGetProgramInfoLog(m_program, log_len, NULL, log_msg); - throw std::runtime_error("Program linking error with " + vertexShaderPath + " and " + fragmentShaderPath + " log:\n" + log_msg); - } - - DEBUG("For shader {}:", resPath.filename().string()); - - // now get uniforms - GLint count; - glGetProgramiv(m_program, GL_ACTIVE_UNIFORMS, &count); - for (int i = 0; i < count; i++) { - char nameBuf[64] = {}; - GLint size; - GLenum type; - glGetActiveUniform(m_program, i, 63, NULL, &size, &type, nameBuf); - m_uniforms[nameBuf] = Uniform{size, static_cast(type), (GLuint)i}; - DEBUG("\tuniform {}", nameBuf); -} - - // now get all attributes - glGetProgramiv(m_program, GL_ACTIVE_ATTRIBUTES, &count); - for (int i = 0; i < count; i++) { - char nameBuf[64] = {}; - GLint size; - GLenum type; - glGetActiveAttrib(m_program, i, 63, NULL, &size, &type, nameBuf); - m_attributes[nameBuf] = Attribute{size, static_cast(type), (GLuint)i}; - DEBUG("\tattrib {}", nameBuf); - } + m_pipeline = gfxdev->createPipeline(vertexShaderPath.c_str(), fragmentShaderPath.c_str(), vertexFormat, sizeof(UniformBuffer)); } Shader::~Shader() { - glDeleteProgram(m_program); -} - -void Shader::makeActive() const -{ - if (s_activeProgram != m_program) { - glUseProgram(m_program); - s_activeProgram = m_program; - } -} - -int Shader::getUniformLocation(const std::string& name) const -{ - auto it = m_uniforms.find(name); - if (it != m_uniforms.end()) { - Uniform u = it->second; - return u.location; - } - else { - return -1; - } -} - -bool Shader::setUniform_m4(const std::string& name, const glm::mat4& m) const -{ - makeActive(); - int loc = getUniformLocation(name); - if (loc != -1) { - glUniformMatrix4fv(loc, 1, GL_FALSE, &m[0][0]); - return true; - } - else { - return false; - } -} - -bool Shader::setUniform_v2(const std::string& name, const glm::vec2& v) const -{ - makeActive(); - int loc = getUniformLocation(name); - if (loc != -1) { - glUniform2f(loc, v.x, v.y); - return true; - } - else { - return false; - } - -} - -bool Shader::setUniform_v3(const std::string& name, const glm::vec3& v) const -{ - makeActive(); - int loc = getUniformLocation(name); - if (loc != -1) { - glUniform3f(loc, v.x, v.y, v.z); - return true; - } - else { - return false; - } -} - -bool Shader::setUniform_i(const std::string& name, int n) const -{ - makeActive(); - int loc = getUniformLocation(name); - if (loc != -1) { - glUniform1i(loc, n); - return true; - } - else { - return false; - } -} - -bool Shader::setUniform_f(const std::string& name, float n) const -{ - makeActive(); - int loc = getUniformLocation(name); - if (loc != -1) { - glUniform1f(loc, n); - return true; - } - else { - return false; - } -} - -Shader::UniformType Shader::getUniformType(const std::string& name) const -{ - auto it = m_uniforms.find(name); - if (it != m_uniforms.end()) { - return it->second.type; - } - else { - return UniformType::NOTFOUND; - } -} - -int Shader::getAttribLocation(const std::string& name) const -{ - return m_attributes.at(name).location; + gfxdev->destroyPipeline(m_pipeline); } } diff --git a/src/resources/texture.cpp b/src/resources/texture.cpp index c46afbf..fab9af1 100644 --- a/src/resources/texture.cpp +++ b/src/resources/texture.cpp @@ -7,7 +7,7 @@ #include -namespace resources { +namespace engine::resources { // -1 means invalid / no bind GLuint Texture::s_binded_texture = -1; @@ -28,7 +28,7 @@ static bool readPNG(const std::string& path, std::vector& texbuf, int * return false; } - const size_t size = x * y * n; + const size_t size = (size_t)x * (size_t)y * (size_t)n; texbuf.resize(size); memcpy(texbuf.data(), data, size); @@ -62,7 +62,7 @@ static bool readGLRaw(const std::string& path, std::vector& texbuf, int uint64_t end = ftell(fp); texbuf.resize(end); - fseek(fp, tex_data_offset, SEEK_SET); + fseek(fp, (long)tex_data_offset, SEEK_SET); fread(texbuf.data(), 1, end, fp); fclose(fp); diff --git a/src/sceneroot.cpp b/src/sceneroot.cpp index 3ceba42..5b8f1dc 100644 --- a/src/sceneroot.cpp +++ b/src/sceneroot.cpp @@ -7,6 +7,8 @@ #include "components/mesh_renderer.hpp" #include "components/text_ui_renderer.hpp" +#include "gfx_device.hpp" + #include #include @@ -14,77 +16,75 @@ #include "log.hpp" -SceneRoot::SceneRoot(struct GameIO things) : Object("root", nullptr, *this, things) -{ -} +namespace engine { -SceneRoot::SceneRoot(const std::filesystem::path& file, struct GameIO things) : SceneRoot(things) -{ - // TODO: make this a resource - //loadFromSceneFile(file); -} - -SceneRoot::~SceneRoot() -{ -} - -// private methods - -// public methods - -void SceneRoot::updateStuff() -{ - - using namespace components; - using namespace glm; - - struct CompList compList{}; - - getAllSubComponents(compList, glm::mat4{1.0f}); - - // update - - for (const auto& [c, t] : compList.customs) { - c->onUpdate(t); + SceneRoot::SceneRoot(struct GameIO things) : Object("root", nullptr, *this, things) + { } - // render + SceneRoot::~SceneRoot() + { + } - for (const auto& [c, t] : compList.cameras) { - for (int id : m_activeCameras) { - if (c->getID() == id) { - c->updateCam(t); - for (const auto& [c, t] : compList.renderers) { - c->render(t); + // private methods + + // public methods + + void SceneRoot::updateStuff() + { + + using namespace components; + using namespace glm; + + struct CompList compList {}; + + getAllSubComponents(compList, glm::mat4{ 1.0f }); + + // update + + for (const auto& [c, t] : compList.customs) { + c->onUpdate(t); + } + + // render + + for (const auto& [c, camt] : compList.cameras) { + for (int id : m_activeCameras) { + if (c->getID() == id) { + c->updateCam(camt); + for (const auto& [ren, ren_t] : compList.renderers) { + ren->render(ren_t); + } + + break; } + } + } - break; + for (const auto& [c, t] : compList.uis) { + c->render(t); + } + + } + + void SceneRoot::activateCam(int id) + { + auto& v = m_activeCameras; + + if (std::find(v.begin(), v.end(), id) == v.end()) { + v.push_back(id); + } + } + + void SceneRoot::deactivateCam(int id) + { + auto& v = m_activeCameras; + + for (auto it = v.begin(); it != v.end(); it++) { + if (*it == id) { + v.erase(it); } } } - for (const auto& [c, t] : compList.uis) { - c->render(t); - } - -} - -void SceneRoot::activateCam(int id) -{ - auto& v = m_activeCameras; - - if (std::find(v.begin(), v.end(), id) == v.end()) { - v.push_back(id); - } -} - -void SceneRoot::deactivateCam(int id) -{ - auto& v = m_activeCameras; - - for (auto it = v.begin(); it != v.end(); it++) { - if (*it == id) { - v.erase(it); - } - } -} +} \ No newline at end of file diff --git a/src/window.cpp b/src/window.cpp index 87a6669..0e694be 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1,120 +1,118 @@ #include "window.hpp" -#include +#include "log.hpp" #include #include -#ifdef ENGINE_BUILD_VULKAN -#include -#endif - const uint64_t BILLION = 1000000000; -Window::Window(const std::string& title) : m_title(title) -{ +namespace engine { - // init SDL - if (SDL_Init(SDL_INIT_VIDEO) != 0) { - const std::string errMsg("Unable to initialise SDL: " + std::string(SDL_GetError())); - if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "SDL error", errMsg.c_str(), NULL) != 0) { - std::cerr << errMsg << "\nAre you in a graphical environment?\n"; + Window::Window(const std::string& title, bool resizable) : m_title(title), m_resizable(resizable) + { + + // init SDL + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + const std::string errMsg("Unable to initialise SDL: " + std::string(SDL_GetError())); + if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "SDL error", errMsg.c_str(), NULL) != 0) { + std::cerr << errMsg << "\nAre you in a graphical environment?\n"; + } + throw std::runtime_error(errMsg); } - throw std::runtime_error(errMsg); - } - m_counterFreq = SDL_GetPerformanceFrequency(); - m_startTime = getNanos(); - m_lastFrameStamp = m_startTime - 1; - m_avgFpsStart = m_startTime; + m_counterFreq = SDL_GetPerformanceFrequency(); + m_startTime = getNanos(); + m_lastFrameStamp = m_startTime - 1; + m_avgFpsStart = m_startTime; - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + Uint32 windowFlags = SDL_WINDOW_SHOWN; - // create the window - m_handle = SDL_CreateWindow( +#ifdef ENGINE_BUILD_VULKAN + windowFlags |= SDL_WINDOW_VULKAN; +#endif + + if (m_resizable) { + windowFlags |= SDL_WINDOW_RESIZABLE; + } + + // create the window + m_handle = SDL_CreateWindow( m_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, static_cast(m_winSize.x), static_cast(m_winSize.y), - SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI); - if (m_handle == NULL) { - SDL_Quit(); - throw std::runtime_error("Unable to create window: " + std::string(SDL_GetError())); + windowFlags); + if (m_handle == NULL) { + SDL_Quit(); + throw std::runtime_error("Unable to create window: " + std::string(SDL_GetError())); + } + + // get window size + int winWidth, winHeight; + SDL_GetWindowSize(m_handle, &winWidth, &winHeight); + m_winSize.x = winWidth; + m_winSize.y = winHeight; + + const int WINDOWED_MIN_WIDTH = 640; + const int WINDOWED_MIN_HEIGHT = 480; + SDL_SetWindowMinimumSize(m_handle, WINDOWED_MIN_WIDTH, WINDOWED_MIN_HEIGHT); + + /* + m_glContext = SDL_GL_CreateContext(m_handle); + if (m_glContext == NULL) { + SDL_DestroyWindow(m_handle); + SDL_Quit(); + throw std::runtime_error("Unable to create OpenGL context: " + std::string(SDL_GetError())); + } + + if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) { + SDL_DestroyWindow(m_handle); + SDL_Quit(); + throw std::runtime_error("Unable to initialise GLAD"); + } + */ + + // onResize(m_winSize.x, m_winSize.y); + } - // get window size - int winWidth, winHeight; - SDL_GetWindowSize(m_handle, &winWidth, &winHeight); - m_winSize.x = winWidth; - m_winSize.y = winHeight; - - const int WINDOWED_MIN_WIDTH = 640; - const int WINDOWED_MIN_HEIGHT = 480; - SDL_SetWindowMinimumSize(m_handle, WINDOWED_MIN_WIDTH, WINDOWED_MIN_HEIGHT); - - m_glContext = SDL_GL_CreateContext(m_handle); - if (m_glContext == NULL) { + Window::~Window() + { SDL_DestroyWindow(m_handle); SDL_Quit(); - throw std::runtime_error("Unable to create OpenGL context: " + std::string(SDL_GetError())); } - if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) { - SDL_DestroyWindow(m_handle); - SDL_Quit(); - throw std::runtime_error("Unable to initialise GLAD"); + // private methods + + void Window::onResize(Sint32 width, Sint32 height) + { + // get window size + m_winSize.x = static_cast(width); + m_winSize.y = static_cast(height); + + m_justResized = true; } - onResize(m_winSize.x, m_winSize.y); + void Window::resetInputDeltas() + { + m_justResized = false; -} + m_keyboard.deltas.fill(ButtonDelta::SAME); -Window::~Window() -{ - SDL_GL_DeleteContext(m_glContext); - SDL_DestroyWindow(m_handle); - SDL_Quit(); -} + m_mouse.deltas.fill(ButtonDelta::SAME); + m_mouse.dx = 0; + m_mouse.dy = 0; + m_mouse.xscroll = 0.0f; + m_mouse.yscroll = 0.0f; + } -// private methods + // TODO event methods (like callbacks) -void Window::onResize(Sint32 width, Sint32 height) -{ - // get window size - m_winSize.x = static_cast(width); - m_winSize.y = static_cast(height); + void Window::onWindowEvent(SDL_WindowEvent& e) + { - // get framebuffer size - int fbWidth, fbHeight; - SDL_GL_GetDrawableSize(m_handle, &fbWidth, &fbHeight); - m_fbSize.x = static_cast(fbWidth); - m_fbSize.y = static_cast(fbHeight); - glViewport(0, 0, fbWidth, fbHeight); - - m_justResized = true; -} - -void Window::resetInputDeltas() -{ - m_justResized = false; - - m_keyboard.deltas.fill(ButtonDelta::SAME); - - m_mouse.deltas.fill(ButtonDelta::SAME); - m_mouse.dx = 0.0f; - m_mouse.dy = 0.0f; - m_mouse.xscroll = 0.0f; - m_mouse.yscroll = 0.0f; -} - -// TODO event methods (like callbacks) - -void Window::onWindowEvent(SDL_WindowEvent &e) -{ - - switch (e.event) { + switch (e.event) { case SDL_WINDOWEVENT_SIZE_CHANGED: onResize(e.data1, e.data2); break; @@ -124,23 +122,23 @@ void Window::onWindowEvent(SDL_WindowEvent &e) case SDL_WINDOWEVENT_FOCUS_LOST: m_keyboardFocus = false; break; + } } -} -void Window::onKeyEvent(SDL_KeyboardEvent &e) -{ - bool keyWasDown = m_keyboard.keys[e.keysym.scancode]; - bool keyIsDown = (e.state == SDL_PRESSED); - m_keyboard.keys[e.keysym.scancode] = keyIsDown; - if (keyIsDown != keyWasDown) { // (if key was pressed or released) - m_keyboard.deltas[e.keysym.scancode] = keyIsDown ? ButtonDelta::PRESSED : ButtonDelta::RELEASED; + void Window::onKeyEvent(SDL_KeyboardEvent& e) + { + bool keyWasDown = m_keyboard.keys[e.keysym.scancode]; + bool keyIsDown = (e.state == SDL_PRESSED); + m_keyboard.keys[e.keysym.scancode] = keyIsDown; + if (keyIsDown != keyWasDown) { // (if key was pressed or released) + m_keyboard.deltas[e.keysym.scancode] = keyIsDown ? ButtonDelta::PRESSED : ButtonDelta::RELEASED; + } } -} -void Window::onMouseButtonEvent(SDL_MouseButtonEvent &e) -{ - enum inputs::MouseButton button = inputs::MouseButton::M_INVALID; - switch (e.button) { + void Window::onMouseButtonEvent(SDL_MouseButtonEvent& e) + { + enum inputs::MouseButton button = inputs::MouseButton::M_INVALID; + switch (e.button) { case SDL_BUTTON_LEFT: button = inputs::MouseButton::M_LEFT; break; @@ -156,72 +154,65 @@ void Window::onMouseButtonEvent(SDL_MouseButtonEvent &e) case SDL_BUTTON_X2: button = inputs::MouseButton::M_X2; break; - } - - bool buttonWasDown = m_mouse.buttons[static_cast(button)]; - bool buttonIsDown = (e.state == SDL_PRESSED); - m_mouse.buttons[static_cast(button)] = buttonIsDown; - if (buttonIsDown != buttonWasDown) { // (if button was pressed or released) - // only sets delta if it hasn't already been set this frame (to detect very fast presses) - if (m_mouse.deltas[static_cast(button)] == ButtonDelta::SAME) { - m_mouse.deltas[static_cast(button)] = buttonIsDown ? ButtonDelta::PRESSED : ButtonDelta::RELEASED; + } + int buttonIndex = static_cast(button); + bool buttonWasDown = m_mouse.buttons.at(buttonIndex); + bool buttonIsDown = (e.state == SDL_PRESSED); + m_mouse.buttons.at(buttonIndex) = buttonIsDown; + if (buttonIsDown != buttonWasDown) { // (if button was pressed or released) + // only sets delta if it hasn't already been set this frame (to detect very fast presses) + if (m_mouse.deltas[buttonIndex] == ButtonDelta::SAME) { + m_mouse.deltas[buttonIndex] = buttonIsDown ? ButtonDelta::PRESSED : ButtonDelta::RELEASED; + } } } -} -void Window::onMouseMotionEvent(SDL_MouseMotionEvent &e) -{ - m_mouse.x = e.x; - m_mouse.y = e.y; - m_mouse.dx = e.xrel; - m_mouse.dy = e.yrel; -} - -void Window::onMouseWheelEvent(SDL_MouseWheelEvent &e) -{ - if (e.direction == SDL_MOUSEWHEEL_NORMAL) { - m_mouse.xscroll = e.preciseX; - m_mouse.yscroll = e.preciseY; - } else { // flipped - m_mouse.xscroll = -e.preciseX; - m_mouse.yscroll = -e.preciseY; + void Window::onMouseMotionEvent(SDL_MouseMotionEvent& e) + { + m_mouse.x = e.x; + m_mouse.y = e.y; + m_mouse.dx = e.xrel; + m_mouse.dy = e.yrel; } -} -// public methods - -std::string Window::getTitle() const -{ - return m_title; -} - -void Window::makeContextCurrent() -{ - if (SDL_GL_MakeCurrent(m_handle, m_glContext) != 0) { - throw std::runtime_error("Failed to make GL context current"); + void Window::onMouseWheelEvent(SDL_MouseWheelEvent& e) + { + if (e.direction == SDL_MOUSEWHEEL_NORMAL) { + m_mouse.xscroll = e.preciseX; + m_mouse.yscroll = e.preciseY; + } + else { // flipped + m_mouse.xscroll = -e.preciseX; + m_mouse.yscroll = -e.preciseY; + } } -} -void Window::swapBuffers() -{ -#ifndef SDLTEST_NOGFX - SDL_GL_SwapWindow(m_handle); -#endif - m_frames++; - uint64_t currentFrameStamp = getNanos(); - m_lastFrameTime = currentFrameStamp - m_lastFrameStamp; - m_lastFrameStamp = currentFrameStamp; -} + // public methods -void Window::getInputAndEvents() -{ + SDL_Window* Window::getHandle() const + { + return m_handle; + } - resetInputDeltas(); + std::string Window::getTitle() const + { + return m_title; + } - // loop through all available events - SDL_Event e; - while (SDL_PollEvent(&e)) { - switch (e.type) { + void Window::getInputAndEvents() + { + + m_frames++; + uint64_t currentFrameStamp = getNanos(); + m_lastFrameTime = currentFrameStamp - m_lastFrameStamp; + m_lastFrameStamp = currentFrameStamp; + + resetInputDeltas(); + + // loop through all available events + SDL_Event e; + while (SDL_PollEvent(&e)) { + switch (e.type) { case SDL_QUIT: setCloseFlag(); @@ -249,264 +240,240 @@ void Window::getInputAndEvents() onMouseWheelEvent(e.wheel); break; + } + } + + } + + void Window::setTitle(std::string title) + { + SDL_SetWindowTitle(m_handle, title.c_str()); + } + + bool Window::getWindowResized() const + { + return m_justResized; + } + + void Window::show() + { + SDL_ShowWindow(m_handle); + } + + void Window::hide() + { + SDL_HideWindow(m_handle); + } + + void Window::focus() + { + SDL_RaiseWindow(m_handle); + m_keyboardFocus = true; + } + + bool Window::hasFocus() const + { + return m_keyboardFocus; + } + + void Window::setCloseFlag() + { + m_shouldClose = true; + } + + bool Window::isRunning() const + { + return !m_shouldClose; + } + + void Window::setFullscreen(bool fullscreen, bool exclusive) + { + if (m_resizable) { + if (SDL_SetWindowFullscreen(m_handle, fullscreen ? (exclusive ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP) : 0) != 0) { + throw std::runtime_error("Unable to set window to fullscreen/windowed"); + } + m_fullscreen = fullscreen; + if (fullscreen) { + int width, height; + SDL_GetWindowSize(m_handle, &width, &height); + onResize(width, height); + } } } -} - -void Window::setVSync(bool enable) -{ - if (SDL_GL_SetSwapInterval(enable ? 1 : 0) != 0) { - throw std::runtime_error("Failed to set swap interval"); + void Window::toggleFullscreen() + { + setFullscreen(!m_fullscreen); } -} -bool Window::getVSync() const -{ - return SDL_GL_GetSwapInterval() == 0 ? false : true; -} - -glm::ivec2 Window::getViewportSize() -{ - return m_fbSize; -} - -void Window::setTitle(std::string title) -{ - SDL_SetWindowTitle(m_handle, title.c_str()); -} - -bool Window::getWindowResized() const -{ - return m_justResized; -} - -void Window::show() -{ - SDL_ShowWindow(m_handle); -} - -void Window::hide() -{ - SDL_HideWindow(m_handle); -} - -void Window::focus() -{ - SDL_RaiseWindow(m_handle); - m_keyboardFocus = true; -} - -bool Window::hasFocus() const -{ - return m_keyboardFocus; -} - -void Window::setCloseFlag() -{ - m_shouldClose = true; -} - -bool Window::isRunning() const -{ - return !m_shouldClose; -} - -void Window::setFullscreen(bool fullscreen, bool exclusive) -{ - if (SDL_SetWindowFullscreen(m_handle, fullscreen ? (exclusive ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP) : 0) != 0) { - throw std::runtime_error("Unable to set window to fullscreen/windowed"); + bool Window::isFullscreen() const + { + return m_fullscreen; } - m_fullscreen = fullscreen; - if (fullscreen) { - int width, height; - SDL_GetWindowSize(m_handle, &width, &height); - onResize(width, height); + + bool Window::setRelativeMouseMode(bool enabled) + { + m_mouse.captured = enabled; + int code = SDL_SetRelativeMouseMode(static_cast(enabled)); + if (code != 0) { + throw std::runtime_error("Unable to set relative mouse mode"); + } + else { + return true; + } } -} -void Window::toggleFullscreen() -{ - setFullscreen(!m_fullscreen); -} - -bool Window::isFullscreen() const -{ - return m_fullscreen; -} - -bool Window::setRelativeMouseMode(bool enabled) -{ - m_mouse.captured = enabled; - int code = SDL_SetRelativeMouseMode(static_cast(enabled)); - if (code != 0) { - throw std::runtime_error("Unable to set relative mouse mode"); - } else { - return true; + bool Window::mouseCaptured() + { + return m_mouse.captured; } -} -bool Window::mouseCaptured() -{ - return m_mouse.captured; -} + // getting input -// getting input - -bool Window::getKey(inputs::Key key) const -{ - return m_keyboard.keys[static_cast(key)]; -} - -bool Window::getKeyPress(inputs::Key key) const -{ - return m_keyboard.deltas[static_cast(key)] == ButtonDelta::PRESSED; -} - -bool Window::getKeyRelease(inputs::Key key) const -{ - return m_keyboard.deltas[static_cast(key)] == ButtonDelta::RELEASED; -} - -// TODO mouse input - -bool Window::getButton(inputs::MouseButton button) const -{ - return m_mouse.buttons[static_cast(button)]; -} - -bool Window::getButtonPress(inputs::MouseButton button) const -{ - return m_mouse.deltas[static_cast(button)] == ButtonDelta::PRESSED; -} - -bool Window::getButtonRelease(inputs::MouseButton button) const -{ - return m_mouse.deltas[static_cast(button)] == ButtonDelta::RELEASED; -} - -int Window::getMouseX() const -{ - return static_cast(m_mouse.x); -} - -int Window::getMouseY() const -{ - return static_cast(m_mouse.y); -} - -float Window::getMouseNormX() const -{ - return ((float)m_mouse.x * 2.0f / (float)m_winSize.x) - 1.0f; -} - -float Window::getMouseNormY() const -{ - return ((float)m_mouse.y * -2.0f / (float)m_winSize.y) + 1.0f; -} - -int Window::getMouseDX() const -{ - return static_cast(m_mouse.dx); -} - -int Window::getMouseDY() const -{ - return static_cast(m_mouse.dy); -} - -float Window::getMouseScrollX() const -{ - return m_mouse.xscroll; -} - -float Window::getMouseScrollY() const -{ - return m_mouse.yscroll; -} - -// TODO game pad - -// get timer value -uint64_t Window::getNanos() const -{ - uint64_t count; - - count = SDL_GetPerformanceCounter(); - if (m_counterFreq == BILLION) { - return count; - } else { - return count * (BILLION / m_counterFreq); + bool Window::getKey(inputs::Key key) const + { + return m_keyboard.keys[static_cast(key)]; } -} -uint64_t Window::getLastFrameStamp() const -{ - return m_lastFrameStamp; -} - -uint64_t Window::getFrameCount() const -{ - return m_frames; -} - -uint64_t Window::getStartTime() const -{ - return m_startTime; -} - -float Window::dt() const -{ - return (float)m_lastFrameTime / (float)BILLION; -} - -uint64_t Window::getFPS() const -{ - if (m_lastFrameTime == 0) return 0; - return BILLION / m_lastFrameTime; -} - -uint64_t Window::getAvgFPS() const -{ - uint64_t delta_t = getNanos() - m_avgFpsStart; - if (delta_t == 0) return 0; - return BILLION * (m_frames - m_avgFpsStartCount) / delta_t; -} - -void Window::resetAvgFPS() -{ - m_avgFpsStart = getNanos(); - m_avgFpsStartCount = getFrameCount(); -} - -bool Window::infoBox(const std::string& title, const std::string& msg) -{ - if (isFullscreen() == false) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, title.c_str(), msg.c_str(), m_handle); - return true; - } else { - return false; + bool Window::getKeyPress(inputs::Key key) const + { + return m_keyboard.deltas[static_cast(key)] == ButtonDelta::PRESSED; } -} -std::vector Window::getRequiredVulkanExtensions() const -{ -#ifdef ENGINE_BUILD_VULKAN - unsigned int sdlExtensionCount = 0; - SDL_Vulkan_GetInstanceExtensions(m_handle, &sdlExtensionCount, nullptr); - std::vector requiredExtensions(sdlExtensionCount); - SDL_Vulkan_GetInstanceExtensions(m_handle, &sdlExtensionCount, requiredExtensions.data()); + bool Window::getKeyRelease(inputs::Key key) const + { + return m_keyboard.deltas[static_cast(key)] == ButtonDelta::RELEASED; + } - return requiredExtensions; -#else - return std::vector{}; -#endif -} + // TODO mouse input -/* STATIC METHODS */ + bool Window::getButton(inputs::MouseButton button) const + { + return m_mouse.buttons[static_cast(button)]; + } -// Display an error message box -void Window::errorBox(const std::string& message) -{ - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Game Error", message.c_str(), NULL); -} + bool Window::getButtonPress(inputs::MouseButton button) const + { + return m_mouse.deltas[static_cast(button)] == ButtonDelta::PRESSED; + } + + bool Window::getButtonRelease(inputs::MouseButton button) const + { + return m_mouse.deltas[static_cast(button)] == ButtonDelta::RELEASED; + } + + int Window::getMouseX() const + { + return static_cast(m_mouse.x); + } + + int Window::getMouseY() const + { + return static_cast(m_mouse.y); + } + + float Window::getMouseNormX() const + { + return ((float)m_mouse.x * 2.0f / (float)m_winSize.x) - 1.0f; + } + + float Window::getMouseNormY() const + { + return ((float)m_mouse.y * -2.0f / (float)m_winSize.y) + 1.0f; + } + + int Window::getMouseDX() const + { + return static_cast(m_mouse.dx); + } + + int Window::getMouseDY() const + { + return static_cast(m_mouse.dy); + } + + float Window::getMouseScrollX() const + { + return m_mouse.xscroll; + } + + float Window::getMouseScrollY() const + { + return m_mouse.yscroll; + } + + // TODO game pad + + // get timer value + uint64_t Window::getNanos() const + { + uint64_t count; + + count = SDL_GetPerformanceCounter(); + if (m_counterFreq == BILLION) { + return count; + } + else { + return count * (BILLION / m_counterFreq); + } + } + + uint64_t Window::getLastFrameStamp() const + { + return m_lastFrameStamp; + } + + uint64_t Window::getFrameCount() const + { + return m_frames; + } + + uint64_t Window::getStartTime() const + { + return m_startTime; + } + + float Window::dt() const + { + return (float)m_lastFrameTime / (float)BILLION; + } + + uint64_t Window::getFPS() const + { + if (m_lastFrameTime == 0) return 0; + return BILLION / m_lastFrameTime; + } + + uint64_t Window::getAvgFPS() const + { + uint64_t delta_t = getNanos() - m_avgFpsStart; + if (delta_t == 0) return 0; + return BILLION * (m_frames - m_avgFpsStartCount) / delta_t; + } + + void Window::resetAvgFPS() + { + m_avgFpsStart = getNanos(); + m_avgFpsStartCount = getFrameCount(); + } + + bool Window::infoBox(const std::string& title, const std::string& msg) + { + if (isFullscreen() == false) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, title.c_str(), msg.c_str(), m_handle); + return true; + } + else { + return false; + } + } + + /* STATIC METHODS */ + + // Display an error message box + void Window::errorBox(const std::string& message) + { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Game Error", message.c_str(), NULL); + } + +} \ No newline at end of file