├── .gitattributes ├── engine ├── Curve.cpp ├── Particle.cpp ├── Vertex.cpp ├── test_main.cpp ├── telemetry.h ├── Area.h ├── Particle.h ├── ShaderLib.h ├── AreaList.h ├── ITexture.h ├── shaders │ ├── shader.frag │ └── shader.vert ├── ParticleSystem.h ├── FontManager.h ├── AtlasTexture.h ├── ShaderLib.cpp ├── AreaList.cpp ├── AtlasTexture.cpp ├── Glyph.h ├── FontManager.cpp ├── Font.h ├── ParticleSystem.cpp ├── DebugMessenger.h ├── Texture.h ├── TextureAtlas.h ├── telemetry.cpp ├── AreaAllocator.h ├── Font.cpp ├── Area.cpp ├── CMakeLists.txt ├── AreaList_test.cpp ├── TextureAtlas_test.cpp ├── DebugMessenger.cpp ├── Vertex.h ├── ParticleEmitter.h ├── Glyph.cpp ├── ParticleSystem_test.cpp ├── Area_test.cpp ├── AreaAllocator_test.cpp ├── Texture.cpp ├── Logger.h ├── TextureAtlas.cpp ├── AreaAllocator.cpp ├── Curve.h ├── Curve_test.cpp ├── Logger.cpp ├── FontManager_test.cpp ├── ParticleEmitter.cpp ├── Renderer_test.cpp └── Renderer.h ├── resources ├── 1.png ├── 2.png ├── circle.png ├── texture.jpg └── montserrat │ ├── Montserrat-Bold.ttf │ ├── Montserrat-Regular.ttf │ └── OFL.txt ├── samples ├── breakout │ ├── AppState.cpp │ ├── test_main.cpp │ ├── main.cpp │ ├── Movable.cpp │ ├── Movable.h │ ├── BrickCollection.h │ ├── Paddle.h │ ├── GameOver.h │ ├── SplashScreen.h │ ├── MainMenu.h │ ├── Ball.h │ ├── BrickCollection.cpp │ ├── Paddle.cpp │ ├── Gameplay.h │ ├── AppState.h │ ├── GameOver.cpp │ ├── BreakOutApp.h │ ├── SplashScreen.cpp │ ├── BrickCollection_test.cpp │ ├── MainMenu.cpp │ ├── Paddle_test.cpp │ ├── Ball.cpp │ ├── BreakOutApp.cpp │ ├── Gameplay.cpp │ └── Ball_test.cpp ├── blendmodes │ ├── main.cpp │ ├── BlendModesApp.h │ └── BlendModesApp.cpp ├── first │ ├── main.cpp │ ├── FirstApp.h │ └── FirstApp.cpp ├── second │ ├── main.cpp │ ├── SecondApp.h │ └── SecondApp.cpp ├── text │ ├── main.cpp │ ├── TextApp.h │ └── TextApp.cpp ├── curves │ ├── main.cpp │ ├── CurvesApp.h │ └── CurvesApp.cpp ├── textureatlas │ ├── main.cpp │ ├── TextureAtlasApp.h │ └── TextureAtlasApp.cpp └── particles │ ├── main.cpp │ ├── ParticlesApp.h │ └── ParticlesApp.cpp ├── README.md └── CMakeLists.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /engine/Curve.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 26.10.2018. 3 | // 4 | 5 | #include "Curve.h" 6 | -------------------------------------------------------------------------------- /resources/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snorristurluson/vulkan-sprites/HEAD/resources/1.png -------------------------------------------------------------------------------- /resources/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snorristurluson/vulkan-sprites/HEAD/resources/2.png -------------------------------------------------------------------------------- /engine/Particle.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 25.10.2018. 3 | // 4 | 5 | #include "Particle.h" 6 | -------------------------------------------------------------------------------- /resources/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snorristurluson/vulkan-sprites/HEAD/resources/circle.png -------------------------------------------------------------------------------- /resources/texture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snorristurluson/vulkan-sprites/HEAD/resources/texture.jpg -------------------------------------------------------------------------------- /engine/Vertex.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 24/08/2018. 3 | // 4 | 5 | #include "Vertex.h" 6 | -------------------------------------------------------------------------------- /engine/test_main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 16.10.2018. 3 | // 4 | 5 | #define CATCH_CONFIG_MAIN 6 | #include "catch.hpp" 7 | -------------------------------------------------------------------------------- /samples/breakout/AppState.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 12.10.2018. 3 | // 4 | 5 | #include 6 | #include "AppState.h" 7 | -------------------------------------------------------------------------------- /resources/montserrat/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snorristurluson/vulkan-sprites/HEAD/resources/montserrat/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /samples/breakout/test_main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 16.10.2018. 3 | // 4 | 5 | #define CATCH_CONFIG_MAIN 6 | #include "catch.hpp" 7 | -------------------------------------------------------------------------------- /resources/montserrat/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snorristurluson/vulkan-sprites/HEAD/resources/montserrat/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /samples/blendmodes/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "BlendModesApp.h" 3 | #include "Renderer.h" 4 | 5 | int main() 6 | { 7 | BlendModesApp app; 8 | Renderer renderer; 9 | 10 | app.CreateWindow(800, 600); 11 | app.Run(); 12 | 13 | return 0; 14 | } -------------------------------------------------------------------------------- /samples/first/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "FirstApp.h" 3 | #include "../../engine/Renderer.h" 4 | 5 | int main() 6 | { 7 | FirstApp app; 8 | Renderer renderer; 9 | 10 | app.CreateWindow(800, 600); 11 | app.Run(); 12 | 13 | return 0; 14 | } -------------------------------------------------------------------------------- /samples/breakout/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 12.10.2018. 3 | // 4 | 5 | #include "../../engine/Renderer.h" 6 | #include "BreakOutApp.h" 7 | 8 | int main() 9 | { 10 | BreakOutApp app; 11 | Renderer renderer; 12 | 13 | app.CreateWindow(1200, 900); 14 | app.Run(); 15 | 16 | return 0; 17 | } -------------------------------------------------------------------------------- /engine/telemetry.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 14.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_TELEMETRY_H 6 | #define VULKAN_SPRITES_TELEMETRY_H 7 | 8 | #if TELEMETRY_ENABLED 9 | #include "rad_tm.h" 10 | #else 11 | #define tmFunction(...) 12 | #define tmTick(...) 13 | #endif 14 | 15 | bool ConnectTelemetry(); 16 | 17 | #endif //VULKAN_SPRITES_TELEMETRY_H 18 | -------------------------------------------------------------------------------- /engine/Area.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 24/09/2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_AREA_H 6 | #define VULKAN_SPRITES_AREA_H 7 | 8 | struct Area { 9 | int x; 10 | int y; 11 | int width; 12 | int height; 13 | 14 | bool IsAdjacent(const Area &other) const; 15 | bool CombineWith(const Area& other); 16 | }; 17 | 18 | #endif //VULKAN_SPRITES_AREA_H 19 | -------------------------------------------------------------------------------- /samples/second/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "SecondApp.h" 3 | 4 | #include "../../engine/Renderer.h" 5 | #include "../../engine/Logger.h" 6 | 7 | static auto logger = GetLogger("main"); 8 | 9 | int main() 10 | { 11 | logger->debug("Starting SecondApp"); 12 | 13 | SecondApp app; 14 | 15 | app.CreateWindow(800, 600); 16 | app.Run(); 17 | 18 | return 0; 19 | } -------------------------------------------------------------------------------- /engine/Particle.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 25.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_PARTICLE_H 6 | #define VULKAN_SPRITES_PARTICLE_H 7 | 8 | #define GLM_FORCE_RADIANS 9 | #include 10 | 11 | struct Particle { 12 | float timeRemaining; 13 | glm::vec2 position; 14 | glm::vec2 velocity; 15 | glm::vec4 color; 16 | }; 17 | 18 | 19 | #endif //VULKAN_SPRITES_PARTICLE_H 20 | -------------------------------------------------------------------------------- /samples/text/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 10.10.2018. 3 | // 4 | 5 | #include 6 | #include "TextApp.h" 7 | 8 | #include "../../engine/Renderer.h" 9 | #include "../../engine/Logger.h" 10 | 11 | static auto logger = GetLogger("main"); 12 | 13 | int main(int argc, char* argv[]) 14 | { 15 | logger->debug("Starting TextApp"); 16 | 17 | TextApp app; 18 | 19 | app.CreateWindow(800, 600); 20 | app.Run(); 21 | 22 | return 0; 23 | } -------------------------------------------------------------------------------- /samples/curves/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 26.10.2018. 3 | // 4 | 5 | #include "CurvesApp.h" 6 | 7 | #include "Renderer.h" 8 | #include "Logger.h" 9 | #include "telemetry.h" 10 | 11 | static auto logger = GetLogger("main"); 12 | 13 | int main(int argc, char* argv[]) 14 | { 15 | ConnectTelemetry(); 16 | 17 | logger->debug("Starting CurvesApp"); 18 | 19 | CurvesApp app; 20 | 21 | app.CreateWindow(800, 600); 22 | app.Run(); 23 | 24 | return 0; 25 | } -------------------------------------------------------------------------------- /engine/ShaderLib.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 26.8.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_SHADERLIB_H 6 | #define VULKAN_SPRITES_SHADERLIB_H 7 | 8 | 9 | #include 10 | #include 11 | 12 | class ShaderLib { 13 | public: 14 | static uint8_t* GetVertexShader(); 15 | static size_t GetVertexShaderSize(); 16 | 17 | static uint8_t* GetPixelShader(); 18 | static size_t GetPixelShaderSize(); 19 | }; 20 | 21 | 22 | #endif //VULKAN_SPRITES_SHADERLIB_H 23 | -------------------------------------------------------------------------------- /samples/textureatlas/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 8.10.2018. 3 | // 4 | 5 | #include 6 | #include "TextureAtlasApp.h" 7 | 8 | #include "../../engine/Renderer.h" 9 | #include "../../engine/Logger.h" 10 | 11 | static auto logger = GetLogger("main"); 12 | 13 | int main(int argc, char* argv[]) 14 | { 15 | logger->debug("Starting TextureAtlasApp"); 16 | 17 | TextureAtlasApp app(argv[1]); 18 | 19 | app.CreateWindow(800, 600); 20 | app.Run(); 21 | 22 | return 0; 23 | } -------------------------------------------------------------------------------- /samples/text/TextApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 10.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_TEXTAPP_H 6 | #define VULKAN_SPRITES_TEXTAPP_H 7 | 8 | #define GLFW_INCLUDE_VULKAN 9 | #include 10 | 11 | class TextApp { 12 | public: 13 | TextApp(); 14 | 15 | void CreateWindow(int width, int height); 16 | 17 | void Run(); 18 | 19 | protected: 20 | GLFWwindow *m_window; 21 | int m_width; 22 | int m_height; 23 | }; 24 | 25 | #endif //VULKAN_SPRITES_TEXTAPP_H 26 | -------------------------------------------------------------------------------- /samples/curves/CurvesApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 26.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_CURVESAPP_H 6 | #define VULKAN_SPRITES_CURVESAPP_H 7 | 8 | #define GLFW_INCLUDE_VULKAN 9 | #include 10 | 11 | class CurvesApp { 12 | public: 13 | CurvesApp(); 14 | 15 | void CreateWindow(int width, int height); 16 | 17 | void Run(); 18 | 19 | protected: 20 | GLFWwindow *m_window; 21 | int m_width; 22 | int m_height; 23 | }; 24 | 25 | #endif //VULKAN_SPRITES_CURVESAPP_H 26 | -------------------------------------------------------------------------------- /samples/particles/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 25.10.2018. 3 | // 4 | 5 | #include 6 | #include "ParticlesApp.h" 7 | 8 | #include "Renderer.h" 9 | #include "Logger.h" 10 | #include "telemetry.h" 11 | 12 | static auto logger = GetLogger("main"); 13 | 14 | int main(int argc, char* argv[]) 15 | { 16 | ConnectTelemetry(); 17 | 18 | logger->debug("Starting ParticlesApp"); 19 | 20 | ParticlesApp app; 21 | 22 | app.CreateWindow(800, 600); 23 | app.Run(); 24 | 25 | return 0; 26 | } -------------------------------------------------------------------------------- /engine/AreaList.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 24/09/2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_AREALIST_H 6 | #define VULKAN_SPRITES_AREALIST_H 7 | 8 | 9 | #include "Area.h" 10 | #include 11 | 12 | typedef std::list AreaPtrList_t; 13 | 14 | class AreaList : public AreaPtrList_t 15 | { 16 | public: 17 | AreaList::const_iterator FindAdjacent(const Area &area); 18 | 19 | AreaList::const_iterator FindArea(int width, int height); 20 | }; 21 | 22 | 23 | #endif //VULKAN_SPRITES_AREALIST_H 24 | -------------------------------------------------------------------------------- /samples/particles/ParticlesApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 25.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_PARTICLESAPP_H 6 | #define VULKAN_SPRITES_PARTICLESAPP_H 7 | 8 | #define GLFW_INCLUDE_VULKAN 9 | #include 10 | 11 | class ParticlesApp { 12 | public: 13 | ParticlesApp(); 14 | 15 | void CreateWindow(int width, int height); 16 | 17 | void Run(); 18 | 19 | protected: 20 | GLFWwindow *m_window; 21 | int m_width; 22 | int m_height; 23 | }; 24 | 25 | 26 | #endif //VULKAN_SPRITES_PARTICLESAPP_H 27 | -------------------------------------------------------------------------------- /engine/ITexture.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 8.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_ITEXTURE_H 6 | #define VULKAN_SPRITES_ITEXTURE_H 7 | 8 | #include 9 | 10 | struct TextureWindow { 11 | float x0; 12 | float y0; 13 | float x1; 14 | float y1; 15 | }; 16 | 17 | struct ITexture { 18 | virtual VkDescriptorSet GetDescriptorSet() = 0; 19 | virtual TextureWindow GetTextureWindow() = 0; 20 | virtual int GetWidth() = 0; 21 | virtual int GetHeight() = 0; 22 | }; 23 | 24 | #endif //VULKAN_SPRITES_ITEXTURE_H 25 | -------------------------------------------------------------------------------- /engine/shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(set = 1, binding = 0) uniform sampler2D texSampler; 5 | 6 | layout(location = 0) in vec4 fragColor; 7 | layout(location = 1) in vec2 fragTexCoord; 8 | layout(location = 2) in float fragOpacity; 9 | 10 | layout(location = 0) out vec4 outColor; 11 | 12 | void main() { 13 | vec4 texel = texture(texSampler, fragTexCoord); 14 | outColor.rgb = texel.rgb * fragColor.rgb * fragColor.a * texel.a; 15 | outColor.a = texel.a * fragColor.a * fragOpacity; 16 | } 17 | -------------------------------------------------------------------------------- /samples/breakout/Movable.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 15.10.2018. 3 | // 4 | 5 | #include "Movable.h" 6 | 7 | Movable::Movable() : m_position({0, 0}), m_velocity({0, 0}) {} 8 | 9 | void Movable::SetPosition(glm::vec2 pos) { 10 | m_position = pos; 11 | } 12 | 13 | glm::vec2 Movable::GetPosition() const { 14 | return m_position; 15 | } 16 | 17 | void Movable::SetVelocity(glm::vec2 v) { 18 | m_velocity = v; 19 | } 20 | 21 | glm::vec2 Movable::GetVelocity() const { 22 | return m_velocity; 23 | } 24 | 25 | void Movable::Update(float td) { 26 | m_position += m_velocity * td; 27 | } 28 | -------------------------------------------------------------------------------- /samples/breakout/Movable.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 15.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_MOVABLE_H 6 | #define VULKAN_SPRITES_MOVABLE_H 7 | 8 | 9 | #include 10 | 11 | class Movable { 12 | public: 13 | Movable(); 14 | 15 | void SetPosition(glm::vec2 pos); 16 | glm::vec2 GetPosition() const; 17 | 18 | void SetVelocity(glm::vec2 v); 19 | glm::vec2 GetVelocity() const; 20 | 21 | virtual void Update(float td); 22 | 23 | protected: 24 | glm::vec2 m_position; 25 | glm::vec2 m_velocity; 26 | }; 27 | 28 | 29 | #endif //VULKAN_SPRITES_MOVABLE_H 30 | -------------------------------------------------------------------------------- /samples/blendmodes/BlendModesApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 20/08/2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_APP_H 6 | #define VULKAN_SPRITES_APP_H 7 | 8 | #define GLFW_INCLUDE_VULKAN 9 | #include 10 | #include 11 | 12 | class BlendModesApp 13 | { 14 | public: 15 | BlendModesApp(); 16 | 17 | void CreateWindow(int width, int height); 18 | GLFWwindow* GetWindow(); 19 | 20 | void Run(); 21 | 22 | protected: 23 | GLFWwindow *m_window; 24 | 25 | void DrawOverlappingSprites(Renderer &r, glm::vec2 offset) const; 26 | }; 27 | 28 | 29 | #endif //VULKAN_SPRITES_APP_H 30 | -------------------------------------------------------------------------------- /samples/first/FirstApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 20/08/2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_APP_H 6 | #define VULKAN_SPRITES_APP_H 7 | 8 | #define GLFW_INCLUDE_VULKAN 9 | #include 10 | 11 | class FirstApp 12 | { 13 | public: 14 | FirstApp(); 15 | 16 | void CreateWindow(int width, int height); 17 | GLFWwindow* GetWindow(); 18 | 19 | void Run(); 20 | 21 | static void framebufferResizeCallback(GLFWwindow *window, int width, int height); 22 | 23 | 24 | protected: 25 | GLFWwindow *m_window; 26 | bool m_framebufferResized; 27 | }; 28 | 29 | 30 | #endif //VULKAN_SPRITES_APP_H 31 | -------------------------------------------------------------------------------- /engine/ParticleSystem.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 25.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_PARTICLESYSTEM_H 6 | #define VULKAN_SPRITES_PARTICLESYSTEM_H 7 | 8 | #include 9 | #include 10 | #include "ParticleEmitter.h" 11 | 12 | class ParticleSystem { 13 | public: 14 | int GetNumParticles(); 15 | int GetNumEmitters(); 16 | 17 | std::shared_ptr AddEmitter(); 18 | 19 | void Update(float td); 20 | 21 | void Render(Renderer &r); 22 | 23 | protected: 24 | std::vector> m_emitters; 25 | }; 26 | 27 | 28 | #endif //VULKAN_SPRITES_PARTICLESYSTEM_H 29 | -------------------------------------------------------------------------------- /engine/FontManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 10.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_FONTMANAGER_H 6 | #define VULKAN_SPRITES_FONTMANAGER_H 7 | 8 | #include 9 | #include FT_FREETYPE_H 10 | 11 | #include "Font.h" 12 | #include 13 | #include 14 | 15 | class TextureAtlas; 16 | 17 | class FontManager { 18 | public: 19 | explicit FontManager(std::shared_ptr ta); 20 | 21 | std::shared_ptr GetFont(const std::string& fontname, int pt); 22 | 23 | protected: 24 | FT_Library m_library; 25 | std::shared_ptr m_textureAtlas; 26 | }; 27 | 28 | 29 | #endif //VULKAN_SPRITES_FONTMANAGER_H 30 | -------------------------------------------------------------------------------- /engine/AtlasTexture.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 8.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_ATLASTEXTURE_H 6 | #define VULKAN_SPRITES_ATLASTEXTURE_H 7 | 8 | #include "ITexture.h" 9 | 10 | class Area; 11 | class TextureAtlas; 12 | 13 | class AtlasTexture : public ITexture { 14 | public: 15 | explicit AtlasTexture(TextureAtlas* owner, Area* area); 16 | 17 | VkDescriptorSet GetDescriptorSet() override; 18 | TextureWindow GetTextureWindow() override; 19 | 20 | int GetWidth() override; 21 | 22 | int GetHeight() override; 23 | 24 | protected: 25 | TextureAtlas* m_owner; 26 | Area* m_area; 27 | }; 28 | 29 | 30 | #endif //VULKAN_SPRITES_ATLASTEXTURE_H 31 | -------------------------------------------------------------------------------- /engine/ShaderLib.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 26.8.2018. 3 | // 4 | 5 | #include "ShaderLib.h" 6 | 7 | namespace { 8 | uint8_t s_vertexShader[] = { 9 | #include "../engine/shaders/shader.vert.array" 10 | }; 11 | uint8_t s_pixelShader[] = { 12 | #include "../engine/shaders/shader.frag.array" 13 | }; 14 | } 15 | 16 | uint8_t *ShaderLib::GetVertexShader() { 17 | return s_vertexShader; 18 | } 19 | 20 | size_t ShaderLib::GetVertexShaderSize() { 21 | return sizeof(s_vertexShader); 22 | } 23 | 24 | uint8_t *ShaderLib::GetPixelShader() { 25 | return s_pixelShader; 26 | } 27 | 28 | size_t ShaderLib::GetPixelShaderSize() { 29 | return sizeof(s_pixelShader); 30 | } 31 | -------------------------------------------------------------------------------- /samples/breakout/BrickCollection.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 18.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_BRICKCOLLECTION_H 6 | #define VULKAN_SPRITES_BRICKCOLLECTION_H 7 | 8 | #include 9 | #include "Ball.h" 10 | 11 | class Renderer; 12 | 13 | struct Brick { 14 | glm::vec2 pos; 15 | float width; 16 | float height; 17 | }; 18 | 19 | class BrickCollection { 20 | public: 21 | int GetBrickCount(); 22 | 23 | void AddBrick(glm::vec2 pos, float width, float height); 24 | 25 | void CollideBallWithBricks(Ball &ball); 26 | 27 | void Render(Renderer& r); 28 | 29 | protected: 30 | std::list m_bricks; 31 | }; 32 | 33 | 34 | #endif //VULKAN_SPRITES_BRICKCOLLECTION_H 35 | -------------------------------------------------------------------------------- /engine/AreaList.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 24/09/2018. 3 | // 4 | 5 | #include "AreaList.h" 6 | 7 | AreaList::const_iterator AreaList::FindAdjacent(const Area &area) 8 | { 9 | for(auto it = cbegin(); it != cend(); ++it) { 10 | if(area.IsAdjacent(**it)) { 11 | return it; 12 | } 13 | } 14 | return cend(); 15 | } 16 | 17 | AreaList::const_iterator AreaList::FindArea(int width, int height) 18 | { 19 | auto foundIt = cend(); 20 | 21 | for(auto it = cbegin(); it != cend(); ++it) { 22 | if((*it)->width >= width && (*it)->height >= height) { 23 | foundIt = it; 24 | break; 25 | } 26 | } 27 | 28 | return foundIt; 29 | } 30 | -------------------------------------------------------------------------------- /samples/breakout/Paddle.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 15.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_PADDLE_H 6 | #define VULKAN_SPRITES_PADDLE_H 7 | 8 | 9 | #include 10 | #include "Movable.h" 11 | 12 | class Paddle : public Movable { 13 | public: 14 | void SetWidth(float w); 15 | float GetWidth() const; 16 | 17 | void SetHeight(float h); 18 | float GetHeight() const; 19 | 20 | void Render(Renderer &r); 21 | 22 | void Update(float td) override; 23 | 24 | void SetBounds(float min, float max); 25 | 26 | protected: 27 | float m_width = 1; 28 | float m_height = 1; 29 | float m_minX = -10000; 30 | float m_maxX = 10000; 31 | }; 32 | 33 | 34 | #endif //VULKAN_SPRITES_PADDLE_H 35 | -------------------------------------------------------------------------------- /samples/breakout/GameOver.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 14.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_GAMEOVER_H 6 | #define VULKAN_SPRITES_GAMEOVER_H 7 | 8 | 9 | #include "AppState.h" 10 | 11 | class GameOver : public AppState { 12 | public: 13 | GameOver(App *a); 14 | 15 | void Init(Renderer &r) override; 16 | 17 | void Enter(Renderer &r) override; 18 | 19 | void Render(Renderer &r) override; 20 | 21 | void Exit(Renderer &r) override; 22 | 23 | void HandleKey(int key, int scancode, int action, int mods) override; 24 | 25 | void HandleCursorPosition(double xpos, double ypos) override; 26 | 27 | protected: 28 | int m_frameCounter; 29 | std::shared_ptr m_titleFont; 30 | }; 31 | 32 | 33 | #endif //VULKAN_SPRITES_GAMEOVER_H 34 | -------------------------------------------------------------------------------- /samples/breakout/SplashScreen.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 12.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_SPLASHSCREEN_H 6 | #define VULKAN_SPRITES_SPLASHSCREEN_H 7 | 8 | #include "AppState.h" 9 | #include "Font.h" 10 | 11 | class SplashScreen : public AppState { 12 | public: 13 | SplashScreen(App *a); 14 | 15 | void Init(Renderer &r) override; 16 | void Enter(Renderer &r) override; 17 | void Render(Renderer &r) override; 18 | void Exit(Renderer &r) override; 19 | void HandleKey(int key, int scancode, int action, int mods) override; 20 | 21 | void HandleCursorPosition(double xpos, double ypos) override; 22 | 23 | protected: 24 | int m_frameCounter; 25 | 26 | std::shared_ptr m_font; 27 | }; 28 | 29 | #endif //VULKAN_SPRITES_SPLASHSCREEN_H 30 | -------------------------------------------------------------------------------- /samples/breakout/MainMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 12.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_MAINMENU_H 6 | #define VULKAN_SPRITES_MAINMENU_H 7 | 8 | 9 | #include "AppState.h" 10 | 11 | class MainMenu : public AppState { 12 | public: 13 | MainMenu(App *a); 14 | 15 | void Init(Renderer &r) override; 16 | void Enter(Renderer &r) override; 17 | void Render(Renderer &r) override; 18 | void Exit(Renderer &r) override; 19 | void HandleKey(int key, int scancode, int action, int mods) override; 20 | 21 | void HandleCursorPosition(double xpos, double ypos) override; 22 | 23 | protected: 24 | int m_frameCounter; 25 | 26 | std::shared_ptr m_titleFont; 27 | std::shared_ptr m_itemFont; 28 | }; 29 | 30 | 31 | #endif //VULKAN_SPRITES_MAINMENU_H 32 | -------------------------------------------------------------------------------- /samples/second/SecondApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 17.9.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_APP_H 6 | #define VULKAN_SPRITES_APP_H 7 | 8 | #define GLFW_INCLUDE_VULKAN 9 | #include 10 | 11 | class SecondApp 12 | { 13 | public: 14 | SecondApp(); 15 | 16 | void CreateWindow(int width, int height); 17 | GLFWwindow* GetWindow(); 18 | 19 | void Run(); 20 | 21 | static void framebufferResizeCallback(GLFWwindow *window, int width, int height); 22 | 23 | 24 | void HandleKey(GLFWwindow *window, int key, int scancode, int action, int mods); 25 | 26 | protected: 27 | GLFWwindow *m_window; 28 | bool m_framebufferResized; 29 | 30 | int m_width; 31 | int m_height; 32 | 33 | float m_x; 34 | float m_y; 35 | 36 | float m_dx; 37 | float m_vx; 38 | }; 39 | 40 | #endif //VULKAN_SPRITES_APP_H 41 | -------------------------------------------------------------------------------- /engine/AtlasTexture.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 8.10.2018. 3 | // 4 | 5 | #include "AtlasTexture.h" 6 | #include "TextureAtlas.h" 7 | 8 | AtlasTexture::AtlasTexture(TextureAtlas* owner, Area* area) : m_owner(owner), m_area(area) { 9 | 10 | } 11 | 12 | VkDescriptorSet AtlasTexture::GetDescriptorSet() { 13 | return m_owner->GetDescriptorSet(); 14 | } 15 | 16 | TextureWindow AtlasTexture::GetTextureWindow() { 17 | float x0 = float(m_area->x) / float(m_owner->GetWidth()); 18 | float y0 = float(m_area->y) / float(m_owner->GetHeight()); 19 | float x1 = x0 + float(m_area->width) / float(m_owner->GetWidth()); 20 | float y1 = y0 + float(m_area->height) / float(m_owner->GetHeight()); 21 | return TextureWindow{x0, y0, x1, y1}; 22 | } 23 | 24 | int AtlasTexture::GetWidth() { 25 | return m_area->width; 26 | } 27 | 28 | int AtlasTexture::GetHeight() { 29 | return m_area->height; 30 | } 31 | -------------------------------------------------------------------------------- /engine/Glyph.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 10.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_GLYPH_H 6 | #define VULKAN_SPRITES_GLYPH_H 7 | 8 | #include 9 | #include FT_FREETYPE_H 10 | 11 | #include 12 | 13 | class ITexture; 14 | class TextureAtlas; 15 | 16 | class Glyph { 17 | public: 18 | Glyph(FT_Face face, FT_UInt ix, std::shared_ptr ta); 19 | 20 | int GetLeft(); 21 | int GetTop(); 22 | int GetWidth(); 23 | int GetHeight(); 24 | int GetAdvance(); 25 | 26 | std::shared_ptr GetTexture(); 27 | 28 | protected: 29 | FT_Face m_face; 30 | FT_UInt m_glyphIndex; 31 | int m_left; 32 | int m_top; 33 | int m_width; 34 | int m_height; 35 | int m_advance; 36 | std::shared_ptr m_texture; 37 | 38 | void CreateTextureFromBitmap(std::shared_ptr &ta); 39 | }; 40 | 41 | 42 | #endif //VULKAN_SPRITES_GLYPH_H 43 | -------------------------------------------------------------------------------- /engine/FontManager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // 4 | // Created by snorri on 10.10.2018. 5 | // 6 | 7 | #include "FontManager.h" 8 | 9 | FontManager::FontManager(std::shared_ptr ta) : m_textureAtlas(std::move(ta)) { 10 | auto error = FT_Init_FreeType(&m_library); 11 | if(error) { 12 | throw std::runtime_error("couldn't initialize FreeType library"); 13 | } 14 | } 15 | 16 | std::shared_ptr FontManager::GetFont(const std::string& fontname, int pt) { 17 | FT_Face face; 18 | auto error = FT_New_Face(m_library, fontname.c_str(), 0, &face); 19 | if(!error) { 20 | error = FT_Set_Pixel_Sizes(face, 0, static_cast(pt)); 21 | if(error) { 22 | throw std::runtime_error("couldn't set pixel sizes"); 23 | } 24 | return std::make_shared(face, m_textureAtlas); 25 | } 26 | 27 | throw std::runtime_error("couldn't load font"); 28 | } 29 | -------------------------------------------------------------------------------- /engine/Font.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 10.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_FONT_H 6 | #define VULKAN_SPRITES_FONT_H 7 | 8 | #include 9 | #include FT_FREETYPE_H 10 | #include "Glyph.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | class TextureAtlas; 17 | 18 | struct TextDimensions { 19 | int width; 20 | int height; 21 | }; 22 | 23 | class Renderer; 24 | class Font { 25 | public: 26 | Font(const FT_Face& face, std::shared_ptr ta); 27 | 28 | int GetNumGlyphs(); 29 | std::shared_ptr GetGlyph(uint16_t c); 30 | 31 | TextDimensions Measure(const std::string& text); 32 | void Draw(Renderer &r, float x, float y, const std::string &text); 33 | 34 | protected: 35 | FT_Face m_face; 36 | std::shared_ptr m_textureAtlas; 37 | std::map> m_glyphs; 38 | }; 39 | 40 | 41 | #endif //VULKAN_SPRITES_FONT_H 42 | -------------------------------------------------------------------------------- /samples/textureatlas/TextureAtlasApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 8.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_TEXTUREATLASAPP_H 6 | #define VULKAN_SPRITES_TEXTUREATLASAPP_H 7 | 8 | #define GLFW_INCLUDE_VULKAN 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "../../engine/Renderer.h" 14 | 15 | class TextureAtlas; 16 | class ITexture; 17 | 18 | class TextureAtlasApp { 19 | public: 20 | TextureAtlasApp(const std::string& root); 21 | 22 | void CreateWindow(int width, int height); 23 | GLFWwindow* GetWindow(); 24 | 25 | void Run(); 26 | 27 | protected: 28 | GLFWwindow *m_window; 29 | int m_width; 30 | int m_height; 31 | std::string m_rootDirectory; 32 | 33 | void LoadTextures(Renderer &renderer, std::shared_ptr &ta, 34 | std::vector> &textures) const; 35 | }; 36 | 37 | 38 | #endif //VULKAN_SPRITES_TEXTUREATLASAPP_H 39 | -------------------------------------------------------------------------------- /engine/ParticleSystem.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 25.10.2018. 3 | // 4 | 5 | #include "ParticleSystem.h" 6 | #include "Renderer.h" 7 | #include "telemetry.h" 8 | 9 | int ParticleSystem::GetNumParticles() { 10 | int n = 0; 11 | for(auto& em: m_emitters) { 12 | n += em->GetNumParticles(); 13 | } 14 | 15 | return n; 16 | } 17 | 18 | int ParticleSystem::GetNumEmitters() { 19 | return static_cast(m_emitters.size()); 20 | } 21 | 22 | std::shared_ptr ParticleSystem::AddEmitter() { 23 | auto em = std::make_shared(); 24 | m_emitters.emplace_back(em); 25 | return em; 26 | } 27 | 28 | void ParticleSystem::Update(float td) { 29 | tmFunction(0, 0); 30 | 31 | for(auto& em: m_emitters) { 32 | em->Update(td); 33 | } 34 | } 35 | 36 | void ParticleSystem::Render(Renderer &r) { 37 | tmFunction(0, 0); 38 | 39 | for(auto& em: m_emitters) { 40 | em->Render(r); 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /samples/breakout/Ball.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 15.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_BALL_H 6 | #define VULKAN_SPRITES_BALL_H 7 | 8 | 9 | #include 10 | #include "Movable.h" 11 | #include "Paddle.h" 12 | 13 | class Brick; 14 | 15 | class Ball : public Movable { 16 | public: 17 | Ball(); 18 | 19 | void SetRadius(float r); 20 | float GetRadius(); 21 | 22 | void SetBounds(float minX, float minY, float maxX, float maxY); 23 | 24 | void CollideWithPaddle(const Paddle& p); 25 | bool CollideWithBrick(const Brick& brick); 26 | 27 | void Render(Renderer &r); 28 | 29 | void Update(float td) override; 30 | 31 | void SetTextureForParticles(std::shared_ptr tex); 32 | 33 | protected: 34 | float m_radius; 35 | float m_minX; 36 | float m_minY; 37 | float m_maxX; 38 | float m_maxY; 39 | ParticleSystem m_particleSystem; 40 | std::shared_ptr m_emitter; 41 | }; 42 | 43 | 44 | #endif //VULKAN_SPRITES_BALL_H 45 | -------------------------------------------------------------------------------- /samples/breakout/BrickCollection.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 18.10.2018. 3 | // 4 | 5 | #include "BrickCollection.h" 6 | 7 | int BrickCollection::GetBrickCount() { 8 | return static_cast(m_bricks.size()); 9 | } 10 | 11 | void BrickCollection::AddBrick(glm::vec2 pos, float width, float height) { 12 | m_bricks.emplace_back(Brick {pos, width, height}); 13 | } 14 | 15 | void BrickCollection::CollideBallWithBricks(Ball &ball) { 16 | for(auto it = m_bricks.begin(); it != m_bricks.end(); ) { 17 | if(ball.CollideWithBrick(*it)) { 18 | it = m_bricks.erase(it); 19 | } else { 20 | ++it; 21 | } 22 | } 23 | } 24 | 25 | void BrickCollection::Render(Renderer &r) { 26 | r.SetColor({1.0f, 0.0f, 0.0f, 1.0f}); 27 | 28 | for(auto brick: m_bricks) { 29 | r.DrawSprite( 30 | brick.pos.x - brick.width/2.0f, 31 | brick.pos.y - brick.height/2.0f, 32 | brick.width, 33 | brick.height); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /engine/DebugMessenger.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 18.9.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_DEBUGMESSENGER_H 6 | #define VULKAN_SPRITES_DEBUGMESSENGER_H 7 | 8 | #include 9 | 10 | class DebugMessenger { 11 | public: 12 | DebugMessenger(); 13 | 14 | int GetErrorAndWarningCount(); 15 | int GetErrorCount(); 16 | int GetWarningCount(); 17 | int GetInfoCount(); 18 | 19 | static VKAPI_ATTR VkBool32 VKAPI_CALL 20 | Log(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, 21 | const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, void *pUserData); 22 | protected: 23 | int m_errorCount; 24 | int m_warningCount; 25 | int m_infoCount; 26 | 27 | VkBool32 28 | Log(const VkDebugUtilsMessageSeverityFlagBitsEXT &messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, 29 | const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData); 30 | }; 31 | 32 | 33 | #endif //VULKAN_SPRITES_DEBUGMESSENGER_H 34 | -------------------------------------------------------------------------------- /engine/Texture.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 25.8.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_TEXTURE_H 6 | #define VULKAN_SPRITES_TEXTURE_H 7 | 8 | 9 | #include 10 | #include 11 | #include "Renderer.h" 12 | #include "ITexture.h" 13 | 14 | class Texture : public ITexture { 15 | public: 16 | Texture(Renderer *renderer, const std::string &filename); 17 | Texture(Renderer *renderer, uint32_t width, uint32_t height, uint8_t *pixels); 18 | 19 | ~Texture(); 20 | 21 | VkDescriptorSet GetDescriptorSet() override; 22 | TextureWindow GetTextureWindow() override; 23 | int GetWidth() override; 24 | int GetHeight() override; 25 | 26 | protected: 27 | Renderer* m_renderer; 28 | VkImageView m_imageView; 29 | VkSampler m_sampler; 30 | uint32_t m_width; 31 | uint32_t m_height; 32 | BoundImage m_boundImage; 33 | VkDescriptorSet m_descriptorSet; 34 | 35 | void init(uint32_t width, uint32_t height, uint8_t *pixels); 36 | }; 37 | 38 | 39 | #endif //VULKAN_SPRITES_TEXTURE_H 40 | -------------------------------------------------------------------------------- /samples/breakout/Paddle.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 15.10.2018. 3 | // 4 | 5 | #include "Paddle.h" 6 | 7 | void Paddle::SetWidth(float w) { 8 | m_width = w; 9 | } 10 | 11 | float Paddle::GetWidth() const { 12 | return m_width; 13 | } 14 | 15 | void Paddle::SetHeight(float h) { 16 | m_height = h; 17 | } 18 | 19 | float Paddle::GetHeight() const { 20 | return m_height; 21 | } 22 | 23 | void Paddle::Render(Renderer &r) { 24 | r.SetColor({1,1,1,1}); 25 | r.SetTexture(nullptr); 26 | r.DrawSprite(m_position.x - m_width/2.0f, m_position.y - m_height/2.0f, m_width, m_height); 27 | } 28 | 29 | void Paddle::Update(float td) { 30 | Movable::Update(td); 31 | 32 | auto halfWidth = m_width / 2.0f; 33 | if(m_position.x < m_minX + halfWidth) { 34 | m_position.x = m_minX + halfWidth; 35 | } 36 | if(m_position.x > m_maxX - halfWidth) { 37 | m_position.x = m_maxX - halfWidth; 38 | } 39 | } 40 | 41 | void Paddle::SetBounds(float min, float max) { 42 | m_minX = min; 43 | m_maxX = max; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /samples/breakout/Gameplay.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 14.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_GAMEPLAY_H 6 | #define VULKAN_SPRITES_GAMEPLAY_H 7 | 8 | 9 | #include "AppState.h" 10 | #include "Paddle.h" 11 | #include "Ball.h" 12 | #include "BrickCollection.h" 13 | 14 | class Gameplay : public AppState { 15 | public: 16 | Gameplay(App *a); 17 | 18 | void Init(Renderer &r) override; 19 | void Enter(Renderer &r) override; 20 | void Render(Renderer &r) override; 21 | void Exit(Renderer &r) override; 22 | void HandleKey(int key, int scancode, int action, int mods) override; 23 | 24 | void HandleCursorPosition(double xpos, double ypos) override; 25 | 26 | protected: 27 | int m_frameCounter; 28 | std::shared_ptr m_titleFont; 29 | 30 | Paddle m_paddle; 31 | float m_paddleSpeed; 32 | 33 | Ball m_ball; 34 | 35 | BrickCollection m_bricks; 36 | 37 | bool m_leftPressed; 38 | bool m_rightPressed; 39 | 40 | float m_fieldWidth; 41 | float m_fieldHeight; 42 | }; 43 | 44 | 45 | #endif //VULKAN_SPRITES_GAMEPLAY_H 46 | -------------------------------------------------------------------------------- /samples/breakout/AppState.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 12.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_APPSTATE_H 6 | #define VULKAN_SPRITES_APPSTATE_H 7 | 8 | #include 9 | #include 10 | 11 | class Renderer; 12 | class Font; 13 | class GLFWwindow; 14 | 15 | class App { 16 | public: 17 | virtual void ChangeState(int newState) = 0; 18 | virtual std::shared_ptr GetFont(int pt) = 0; 19 | virtual GLFWwindow *GetWindow() = 0; 20 | }; 21 | 22 | class AppState { 23 | public: 24 | AppState(App* a) : m_app(a) {} 25 | 26 | virtual void Init(Renderer& r) = 0; 27 | virtual void Enter(Renderer& r) = 0; 28 | virtual void Render(Renderer& r) = 0; 29 | virtual void Exit(Renderer& r) = 0; 30 | virtual void HandleKey(int key, int scancode, int action, int mods) = 0; 31 | virtual void HandleCursorPosition(double xpos, double ypos) = 0; 32 | 33 | protected: 34 | App* m_app; 35 | }; 36 | 37 | typedef enum { 38 | SPLASH_SCREEN, 39 | MAIN_MENU, 40 | GAMEPLAY, 41 | GAME_OVER, 42 | 43 | NUM_STATES, 44 | 45 | QUIT 46 | } State; 47 | 48 | #endif //VULKAN_SPRITES_APPSTATE_H 49 | -------------------------------------------------------------------------------- /samples/breakout/GameOver.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 14.10.2018. 3 | // 4 | 5 | #include 6 | #include 7 | #include "Font.h" 8 | #include "GameOver.h" 9 | #include "telemetry.h" 10 | 11 | static auto logger = GetLogger("mainmenu"); 12 | 13 | GameOver::GameOver(App *a) : AppState(a) { 14 | 15 | } 16 | 17 | void GameOver::Init(Renderer &r) { 18 | m_titleFont = m_app->GetFont(48); 19 | } 20 | 21 | void GameOver::Enter(Renderer &r) { 22 | tmFunction(0, 0); 23 | 24 | logger->debug(__PRETTY_FUNCTION__); 25 | m_frameCounter = 0; 26 | 27 | } 28 | 29 | void GameOver::Render(Renderer &r) { 30 | tmFunction(0, 0); 31 | 32 | m_frameCounter++; 33 | 34 | m_titleFont->Draw(r, 32, 128, "GAME OVER"); 35 | } 36 | 37 | void GameOver::Exit(Renderer &r) { 38 | tmFunction(0, 0); 39 | 40 | logger->debug(__PRETTY_FUNCTION__); 41 | } 42 | 43 | void GameOver::HandleKey(int key, int scancode, int action, int mods) { 44 | if(action == GLFW_PRESS) { 45 | m_app->ChangeState(MAIN_MENU); 46 | } 47 | 48 | } 49 | 50 | void GameOver::HandleCursorPosition(double xpos, double ypos) { 51 | 52 | } 53 | -------------------------------------------------------------------------------- /engine/TextureAtlas.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 7.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_TEXTUREATLAS_H 6 | #define VULKAN_SPRITES_TEXTUREATLAS_H 7 | 8 | #include "Renderer.h" 9 | #include "ITexture.h" 10 | #include "AreaAllocator.h" 11 | #include "AtlasTexture.h" 12 | 13 | class TextureAtlas : public ITexture { 14 | 15 | public: 16 | TextureAtlas(); 17 | 18 | ~TextureAtlas(); 19 | 20 | void Initialize(Renderer *pRenderer, uint32_t width, uint32_t height); 21 | 22 | std::shared_ptr Add(const std::string & filename); 23 | 24 | std::shared_ptr Add(uint32_t width, uint32_t height, uint8_t *pixels); 25 | 26 | VkDescriptorSet GetDescriptorSet() override; 27 | TextureWindow GetTextureWindow() override; 28 | int GetWidth() override; 29 | int GetHeight() override; 30 | 31 | protected: 32 | uint32_t m_width; 33 | uint32_t m_height; 34 | Renderer *m_renderer; 35 | BoundImage m_boundImage; 36 | VkImageView m_imageView; 37 | VkSampler m_sampler; 38 | VkDescriptorSet m_descriptorSet; 39 | AreaAllocator m_allocator; 40 | 41 | protected: 42 | }; 43 | 44 | 45 | #endif //VULKAN_SPRITES_TEXTUREATLAS_H 46 | -------------------------------------------------------------------------------- /samples/breakout/BreakOutApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 12.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_BREAKOUTAPP_H 6 | #define VULKAN_SPRITES_BREAKOUTAPP_H 7 | 8 | #define GLFW_INCLUDE_VULKAN 9 | #include 10 | #include "SplashScreen.h" 11 | #include "MainMenu.h" 12 | #include "Gameplay.h" 13 | #include "GameOver.h" 14 | 15 | #include 16 | #include 17 | 18 | class BreakOutApp : public App { 19 | public: 20 | BreakOutApp(); 21 | 22 | void CreateWindow(int width, int height); 23 | void Run(); 24 | 25 | void ChangeState(int newState) override; 26 | std::shared_ptr GetFont(int pt) override; 27 | 28 | GLFWwindow *GetWindow() override; 29 | 30 | void HandleKey(GLFWwindow *window, int key, int scancode, int action, int mods); 31 | void HandleCursorPosition(GLFWwindow* window, double xpos, double ypos); 32 | 33 | protected: 34 | GLFWwindow *m_window; 35 | std::unique_ptr m_fontManager; 36 | 37 | SplashScreen m_splashScreen; 38 | MainMenu m_mainMenu; 39 | Gameplay m_gameplay; 40 | GameOver m_gameOver; 41 | 42 | std::vector m_states; 43 | AppState* m_currentState; 44 | AppState* m_nextState; 45 | }; 46 | 47 | 48 | #endif //VULKAN_SPRITES_BREAKOUTAPP_H 49 | -------------------------------------------------------------------------------- /engine/telemetry.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 14.10.2018. 3 | // 4 | 5 | #include "telemetry.h" 6 | #include "Logger.h" 7 | 8 | static auto logger = GetLogger("telemetry"); 9 | 10 | #if TELEMETRY_ENABLED 11 | bool ConnectTelemetry() { 12 | tm_int32 telemetry_memory_size = 8 * 1024 * 1024; 13 | char* telemetry_memory = (char*)malloc(telemetry_memory_size); 14 | tmInitialize(telemetry_memory_size, telemetry_memory); 15 | 16 | char exename[1024]; 17 | auto result = readlink("/proc/self/exe", exename, 1024); 18 | exename[result] = 0; 19 | 20 | auto err = tmOpen( 21 | 0, 22 | exename, 23 | __DATE__ " " __TIME__, 24 | "localhost", 25 | TMCT_TCP, 26 | 4719, 27 | TMOF_INIT_NETWORKING, 28 | 100 29 | ); 30 | 31 | logger->debug("tmOpen returned {}", err); 32 | 33 | tmThreadName( 34 | 0, // Capture mask (0 means capture everything) 35 | 0, // Thread id (0 means use the current thread) 36 | "Main Thread" // Name of the thread 37 | ); 38 | 39 | return err == TM_OK; 40 | } 41 | 42 | #else 43 | bool ConnectTelemetry() { 44 | return false; 45 | } 46 | #endif 47 | 48 | -------------------------------------------------------------------------------- /samples/breakout/SplashScreen.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 12.10.2018. 3 | // 4 | 5 | #include 6 | #include "Renderer.h" 7 | #include "SplashScreen.h" 8 | #include "telemetry.h" 9 | 10 | static auto logger = GetLogger("splashscreen"); 11 | 12 | SplashScreen::SplashScreen(App *a) : AppState(a) {} 13 | 14 | void SplashScreen::Init(Renderer &r) { 15 | m_font = m_app->GetFont(96); 16 | } 17 | 18 | void SplashScreen::Enter(Renderer &r) { 19 | tmFunction(0, 0); 20 | 21 | logger->debug(__PRETTY_FUNCTION__); 22 | 23 | m_frameCounter = 0; 24 | } 25 | 26 | void SplashScreen::Render(Renderer &r) { 27 | tmFunction(0, 0); 28 | 29 | m_frameCounter++; 30 | 31 | m_font->Draw(r, 32, 256, "Vulkan BreakOut"); 32 | } 33 | 34 | void SplashScreen::Exit(Renderer &r) { 35 | tmFunction(0, 0); 36 | 37 | logger->debug(__PRETTY_FUNCTION__); 38 | } 39 | 40 | void SplashScreen::HandleKey(int key, int scancode, int action, int mods) { 41 | tmFunction(0, 0); 42 | 43 | if(action == GLFW_PRESS) { 44 | if(key == GLFW_KEY_ESCAPE) { 45 | m_app->ChangeState(QUIT); 46 | } else { 47 | m_app->ChangeState(MAIN_MENU); 48 | } 49 | } 50 | } 51 | 52 | void SplashScreen::HandleCursorPosition(double xpos, double ypos) { 53 | 54 | } 55 | -------------------------------------------------------------------------------- /engine/AreaAllocator.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 23/09/2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_AREAALLOCATOR_H 6 | #define VULKAN_SPRITES_AREAALLOCATOR_H 7 | 8 | #include 9 | #include "Area.h" 10 | #include "AreaList.h" 11 | 12 | // AreaAllocator allocates areas from a two-dimensional field. This serves as the basis for a texture 13 | // atlas - this class focuses on the allocation and de-allocation of the areas, not concerning itself 14 | // with the details of Vulkan buffers. 15 | class AreaAllocator 16 | { 17 | public: 18 | AreaAllocator(); 19 | 20 | void Initialize(int width, int height); 21 | 22 | int GetTotalWidth(); 23 | int GetTotalHeight(); 24 | int GetTotalAreaSize(); 25 | int GetFreeAreaCount(); 26 | int GetFreeAreaSize(); 27 | int GetAllocatedAreaCount(); 28 | int GetAllocatedAreaSize(); 29 | 30 | Area* Allocate(int width, int height); 31 | 32 | void Free(Area *area); 33 | 34 | protected: 35 | int m_width; 36 | int m_height; 37 | 38 | AreaList m_freeAreas; 39 | AreaList m_allocatedAreas; 40 | 41 | int accumulateAreaSize(const AreaList &areaList) const; 42 | 43 | Area *getFreeArea(int width, int height); 44 | 45 | void collapseFreeAreas(); 46 | }; 47 | 48 | 49 | #endif //VULKAN_SPRITES_AREAALLOCATOR_H 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vulkan sprite rendering engine 2 | The primary driver for this project is to learn to use Vulkan, but I 3 | also want to try some things in the overall architecture 4 | and infrastructure of a game engine. 5 | 6 | Follow [my blog](https://snorristurluson.github.io/) for 7 | discussions about my learnings along the way. 8 | 9 | The initial iteration of the renderer is based on the Vulkan tutorial 10 | found at https://vulkan-tutorial.com/ - I highly recommend it if you 11 | are new to Vulkan, and I've looked to 12 | [Sascha Willems](https://github.com/SaschaWillems/Vulkan) for 13 | reference as well. 14 | 15 | ## Dependencies 16 | 17 | ### Vulkan 18 | https://vulkan.lunarg.com/ 19 | 20 | Install according to the instructions per platform and adjust the CMakeLists.txt file 21 | accordingly. 22 | 23 | ### GLFW 24 | On macOS: 25 | ```bash 26 | brew install glfw --HEAD 27 | ``` 28 | 29 | ### glm 30 | On Linux: 31 | ```bash 32 | sudo apt install libglm-dev 33 | ``` 34 | 35 | ### stb_image 36 | 37 | ### spdlog 38 | On Linux: 39 | ```bash 40 | sudo apt install libspdlog-dev 41 | ``` 42 | 43 | 44 | ## Running on Mac 45 | ```bash 46 | VK_LAYER_PATH=~/vulkansdk-macos-1.1.82.0/macOS/etc/vulkan/explicit_layer.d 47 | VK_ICD_FILENAMES=~/vulkansdk-macos-1.1.82.0/macOS/etc/vulkan/icd.d/MoltenVK_icd.json 48 | ``` 49 | Remember to do this for any target you want to run, including tests. 50 | -------------------------------------------------------------------------------- /samples/breakout/BrickCollection_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 18.10.2018. 3 | // 4 | 5 | #include "catch.hpp" 6 | #include "BrickCollection.h" 7 | #include "Ball.h" 8 | 9 | TEST_CASE("BrickCollection") { 10 | SECTION("constructor") { 11 | BrickCollection bc; 12 | 13 | SECTION("newly constructed is empty") { 14 | REQUIRE(bc.GetBrickCount() == 0); 15 | } 16 | } 17 | 18 | SECTION("AddBrick") { 19 | BrickCollection bc; 20 | bc.AddBrick({0, 0}, 64, 32); 21 | REQUIRE(bc.GetBrickCount() == 1); 22 | } 23 | 24 | SECTION("CollideBallWithBricks") { 25 | BrickCollection bc; 26 | 27 | Ball b; 28 | b.SetRadius(1.0f); 29 | 30 | SECTION("empty") { 31 | b.SetPosition({0.0f, 1.0f}); 32 | b.SetVelocity({0.0f, 1.0f}); 33 | 34 | bc.CollideBallWithBricks(b); 35 | 36 | REQUIRE(b.GetVelocity().x == 0.0f); 37 | REQUIRE(b.GetVelocity().y == 1.0f); 38 | } 39 | 40 | SECTION("simple") { 41 | b.SetPosition({0.0f, 0.0f}); 42 | b.SetVelocity({0.0f, 1.0f}); 43 | 44 | bc.AddBrick({0.0f, 5.0f}, 10.0f, 10.0f); 45 | bc.CollideBallWithBricks(b); 46 | 47 | REQUIRE(bc.GetBrickCount() == 0); 48 | REQUIRE(b.GetVelocity().x == 0.0f); 49 | REQUIRE(b.GetVelocity().y == -1.0f); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /engine/shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | const int BM_NONE = 0; 5 | const int BM_BLEND = 1; 6 | const int BM_ADD = 2; 7 | const int BM_ADDX2 = 3; 8 | 9 | layout(binding = 0) uniform UniformBufferObject { 10 | vec2 extent; 11 | } ubo; 12 | 13 | layout(location = 0) in vec2 inPosition; 14 | layout(location = 1) in vec4 inColor; 15 | layout(location = 2) in vec2 inTexCoord; 16 | layout(location = 3) in ivec4 inData; 17 | 18 | layout(location = 0) out vec4 fragColor; 19 | layout(location = 1) out vec2 fragTexCoord; 20 | layout(location = 2) out float fragOpacity; 21 | 22 | out gl_PerVertex { 23 | vec4 gl_Position; 24 | }; 25 | 26 | void main() { 27 | int blendMode = inData[0]; 28 | float opacity = 0.0f; 29 | vec4 color = inColor; 30 | 31 | if(blendMode == BM_NONE) { 32 | opacity = 1.0f; 33 | color.a = 1.0f; 34 | } else if(blendMode == BM_BLEND) { 35 | opacity = color.a; 36 | } else if(blendMode == BM_ADD) { 37 | opacity = 0.0f; 38 | } else if(blendMode == BM_ADDX2) { 39 | opacity = 0.0f; 40 | color *= 2.0f; 41 | } 42 | 43 | float halfWidth = ubo.extent.x / 2.0f; 44 | float halfHeight = ubo.extent.y / 2.0f; 45 | gl_Position = vec4(inPosition.x / halfWidth - 1.0f, inPosition.y / halfHeight - 1.0f, 0.0, 1.0); 46 | 47 | fragColor = color; 48 | fragTexCoord = inTexCoord; 49 | fragOpacity = opacity; 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /samples/breakout/MainMenu.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 12.10.2018. 3 | // 4 | 5 | #include 6 | #include "MainMenu.h" 7 | #include "Renderer.h" 8 | #include 9 | #include "telemetry.h" 10 | 11 | static auto logger = GetLogger("mainmenu"); 12 | 13 | MainMenu::MainMenu(App *a) : AppState(a) {} 14 | 15 | void MainMenu::Init(Renderer &r) { 16 | m_titleFont = m_app->GetFont(48); 17 | m_itemFont = m_app->GetFont(32); 18 | } 19 | 20 | void MainMenu::Enter(Renderer &r) { 21 | tmFunction(0, 0); 22 | 23 | logger->debug(__PRETTY_FUNCTION__); 24 | m_frameCounter = 0; 25 | } 26 | 27 | void MainMenu::Render(Renderer &r) { 28 | tmFunction(0, 0); 29 | 30 | m_frameCounter++; 31 | 32 | m_titleFont->Draw(r, 32, 128, "Main menu"); 33 | m_itemFont->Draw(r, 32, 256, "1) Play"); 34 | m_itemFont->Draw(r, 32, 304, "2) Quit"); 35 | } 36 | 37 | void MainMenu::Exit(Renderer &r) { 38 | tmFunction(0, 0); 39 | 40 | logger->debug(__PRETTY_FUNCTION__); 41 | } 42 | 43 | void MainMenu::HandleKey(int key, int scancode, int action, int mods) { 44 | tmFunction(0, 0); 45 | 46 | if(action == GLFW_PRESS) { 47 | switch(key) { 48 | case GLFW_KEY_1: 49 | m_app->ChangeState(GAMEPLAY); 50 | break; 51 | case GLFW_KEY_2: 52 | m_app->ChangeState(SPLASH_SCREEN); 53 | break; 54 | } 55 | } 56 | } 57 | 58 | void MainMenu::HandleCursorPosition(double xpos, double ypos) { 59 | 60 | } 61 | -------------------------------------------------------------------------------- /samples/first/FirstApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 20/08/2018. 3 | // 4 | 5 | #include "FirstApp.h" 6 | #include "../../engine/Renderer.h" 7 | #include "../../engine/Texture.h" 8 | 9 | #include 10 | #include 11 | 12 | FirstApp::FirstApp() : m_window(nullptr) 13 | { 14 | glfwInit(); 15 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 16 | } 17 | 18 | void FirstApp::CreateWindow(int width, int height) 19 | { 20 | m_window = glfwCreateWindow(width, height, "Vulkan Sprites", nullptr, nullptr); 21 | glfwSetWindowUserPointer(m_window, this); 22 | glfwSetFramebufferSizeCallback(m_window, framebufferResizeCallback); 23 | } 24 | 25 | void FirstApp::framebufferResizeCallback(GLFWwindow *window, int width, int height) 26 | { 27 | auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); 28 | app->m_framebufferResized = true; 29 | } 30 | 31 | void FirstApp::Run() 32 | { 33 | Renderer r; 34 | r.Initialize(m_window, Renderer::ENABLE_VALIDATION); 35 | r.SetClearColor({0.0f, 0.0f, 1.0f, 1.0f}); 36 | auto t = r.CreateTexture("resources/texture.jpg"); 37 | while (!glfwWindowShouldClose(m_window)) { 38 | glfwPollEvents(); 39 | if(r.StartFrame()) { 40 | r.SetTexture(t); 41 | r.DrawSprite(32, 32, t->GetWidth(), t->GetHeight()); 42 | r.EndFrame(); 43 | } 44 | } 45 | r.WaitUntilDeviceIdle(); 46 | } 47 | 48 | GLFWwindow *FirstApp::GetWindow() 49 | { 50 | return m_window; 51 | } 52 | -------------------------------------------------------------------------------- /engine/Font.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // 4 | // Created by snorri on 10.10.2018. 5 | // 6 | 7 | #include "Font.h" 8 | #include "Renderer.h" 9 | 10 | Font::Font(const FT_Face& face, std::shared_ptr ta) : m_face(face), m_textureAtlas(std::move(ta)) { 11 | 12 | } 13 | 14 | int Font::GetNumGlyphs() { 15 | return static_cast(m_face->num_glyphs); 16 | } 17 | 18 | std::shared_ptr Font::GetGlyph(uint16_t c) { 19 | auto foundIt = m_glyphs.find(c); 20 | if(foundIt != m_glyphs.end()) { 21 | return foundIt->second; 22 | } 23 | 24 | auto glyphIndex = FT_Get_Char_Index(m_face, c); 25 | auto glyph = std::make_shared(m_face, glyphIndex, m_textureAtlas); 26 | 27 | m_glyphs[c] = glyph; 28 | return glyph; 29 | } 30 | 31 | TextDimensions Font::Measure(const std::string& text) { 32 | int width = 0; 33 | int height = 0; 34 | 35 | for(auto c: text) { 36 | auto glyph = GetGlyph(c); 37 | width += glyph->GetAdvance(); 38 | height = std::max(height, glyph->GetTop() + glyph->GetHeight()); 39 | } 40 | 41 | return TextDimensions{width, height}; 42 | } 43 | 44 | void Font::Draw(Renderer &r, float x, float y, const std::string &text) { 45 | int pos = x; 46 | 47 | for(auto c: text) { 48 | auto glyph = GetGlyph(c); 49 | auto texture = glyph->GetTexture(); 50 | if(texture) { 51 | r.SetTexture(texture); 52 | float x0 = pos + glyph->GetLeft(); 53 | float y0 = y - glyph->GetTop(); 54 | r.DrawSprite(x0, y0, texture->GetWidth(), texture->GetHeight()); 55 | } 56 | pos += glyph->GetAdvance(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /samples/text/TextApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 10.10.2018. 3 | // 4 | 5 | #include "TextApp.h" 6 | #include "../../engine/Renderer.h" 7 | #include "../../engine/TextureAtlas.h" 8 | #include "../../engine/FontManager.h" 9 | 10 | TextApp::TextApp() { 11 | glfwInit(); 12 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 13 | } 14 | 15 | void TextApp::CreateWindow(int width, int height) { 16 | m_window = glfwCreateWindow(width, height, "Text", nullptr, nullptr); 17 | 18 | glfwGetFramebufferSize(m_window, &m_width, &m_height); 19 | 20 | m_width *= 2; 21 | m_height *= 2; 22 | 23 | glfwSetWindowUserPointer(m_window, this); 24 | } 25 | 26 | void TextApp::Run() { 27 | Renderer r; 28 | r.Initialize(m_window, Renderer::ENABLE_VALIDATION); 29 | 30 | auto ta = r.CreateTextureAtlas(512, 512); 31 | FontManager fm(ta); 32 | auto font = fm.GetFont("resources/montserrat/Montserrat-Bold.ttf", 36); 33 | r.SetClearColor({1.0f, 1.0f, 1.0f, 1.0f}); 34 | while (!glfwWindowShouldClose(m_window)) { 35 | glfwPollEvents(); 36 | if(r.StartFrame()) { 37 | r.SetColor({0.0f, 0.0f, 0.0f, 1.0f}); 38 | font->Draw(r, 80, 100, "This is a test"); 39 | 40 | r.SetColor({1.0f, 0.0f, 0.0f, 1.0f}); 41 | font->Draw(r, 30, 140, "This is a red test"); 42 | 43 | r.SetColor({0.0f, 0.0f, 1.0f, 1.0f}); 44 | font->Draw(r, 30, 180, "The quick brown fox"); 45 | font->Draw(r, 60, 220, "jumps over the lazy dog"); 46 | 47 | r.SetTexture(ta); 48 | r.DrawSprite(2048, 0, 2048, 2048); 49 | 50 | r.EndFrame(); 51 | } 52 | } 53 | r.WaitUntilDeviceIdle(); 54 | 55 | } 56 | -------------------------------------------------------------------------------- /engine/Area.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 24/09/2018. 3 | // 4 | 5 | #include "Area.h" 6 | 7 | bool Area::IsAdjacent(const Area &other) const 8 | { 9 | if (x == other.x && width == other.width && y + height == other.y) { 10 | // Other is immediately below 11 | return true; 12 | } 13 | 14 | if (x == other.x && width == other.width && other.y + other.height == y) { 15 | // Other is immediately above 16 | return true; 17 | } 18 | 19 | if(y == other.y && height == other.height && x + width == other.x) { 20 | // Other is immediately to the right 21 | return true; 22 | } 23 | 24 | if(y == other.y && height == other.height && other.x + other.width == x) { 25 | // Other is immediately to the left 26 | return true; 27 | } 28 | 29 | return false; 30 | } 31 | 32 | bool Area::CombineWith(const Area &other) 33 | { 34 | if (x == other.x && width == other.width && y + height == other.y) { 35 | // Other is immediately below 36 | height += other.height; 37 | return true; 38 | } 39 | 40 | if (x == other.x && width == other.width && other.y + other.height == y) { 41 | // Other is immediately above 42 | y -= other.height; 43 | height += other.height; 44 | return true; 45 | } 46 | 47 | if(y == other.y && height == other.height && x + width == other.x) { 48 | // Other is immediately to the right 49 | width += other.width; 50 | return true; 51 | } 52 | 53 | if(y == other.y && height == other.height && other.x + other.width == x) { 54 | // Other is immediately to the left 55 | x -= other.width; 56 | width += other.width; 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | -------------------------------------------------------------------------------- /samples/breakout/Paddle_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 15.10.2018. 3 | // 4 | 5 | #include "catch.hpp" 6 | #include "Paddle.h" 7 | 8 | 9 | TEST_CASE("Paddle") { 10 | SECTION("constructor") { 11 | Paddle p; 12 | } 13 | 14 | SECTION("newly created has zero velocity") { 15 | Paddle p; 16 | REQUIRE(p.GetVelocity().x == 0.0f); 17 | REQUIRE(p.GetVelocity().y == 0.0f); 18 | } 19 | 20 | SECTION("SetPosition") { 21 | Paddle p; 22 | p.SetPosition({3.14f, 42.0f}); 23 | 24 | glm::vec2 expected {3.14f, 42.0f}; 25 | REQUIRE(p.GetPosition() == expected); 26 | } 27 | 28 | SECTION("SetWidth") { 29 | Paddle p; 30 | p.SetWidth(10.0f); 31 | 32 | REQUIRE(p.GetWidth() == 10.0f); 33 | } 34 | 35 | SECTION("Update") { 36 | Paddle p; 37 | p.SetPosition({0, 0}); 38 | p.SetVelocity({10, 0}); 39 | p.Update(1); 40 | 41 | glm::vec2 expected {10, 0}; 42 | REQUIRE(p.GetPosition().x == expected.x); 43 | REQUIRE(p.GetPosition().y == expected.y); 44 | } 45 | 46 | SECTION("SetBounds") { 47 | SECTION("left") { 48 | Paddle p; 49 | p.SetWidth(10.0f); 50 | p.SetBounds(0, 1000); 51 | p.SetPosition({0, 0}); 52 | p.SetVelocity({-10, 0}); 53 | p.Update(1); 54 | 55 | glm::vec2 expected {5.0f, 0}; 56 | REQUIRE(p.GetPosition().x == expected.x); 57 | REQUIRE(p.GetPosition().y == expected.y); 58 | } 59 | 60 | SECTION("right") { 61 | Paddle p; 62 | p.SetWidth(10.0f); 63 | p.SetBounds(0, 1000); 64 | p.SetPosition({990, 0}); 65 | p.SetVelocity({10, 0}); 66 | p.Update(1); 67 | 68 | glm::vec2 expected {995.0f, 0}; 69 | REQUIRE(p.GetPosition().x == expected.x); 70 | REQUIRE(p.GetPosition().y == expected.y); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /engine/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Freetype REQUIRED) 2 | include_directories(${FREETYPE_INCLUDE_DIRS}) 3 | 4 | add_library( 5 | engine 6 | ../extern/stb_image.h 7 | Renderer.cpp Renderer.h 8 | Vertex.cpp Vertex.h 9 | Texture.cpp Texture.h 10 | ShaderLib.cpp ShaderLib.h 11 | DebugMessenger.cpp DebugMessenger.h 12 | Logger.cpp Logger.h 13 | AreaAllocator.cpp AreaAllocator.h 14 | AreaList.cpp AreaList.h 15 | Area.cpp Area.h 16 | TextureAtlas.cpp TextureAtlas.h 17 | ITexture.h 18 | AtlasTexture.cpp AtlasTexture.h 19 | FontManager.cpp FontManager.h 20 | Glyph.cpp Glyph.h 21 | Font.cpp Font.h 22 | telemetry.cpp telemetry.h 23 | ParticleSystem.cpp ParticleSystem.h 24 | ParticleEmitter.cpp ParticleEmitter.h 25 | Particle.cpp Particle.h Curve.cpp Curve.h) 26 | 27 | # Shaders 28 | file(GLOB_RECURSE GLSL_SOURCE_FILES 29 | "shaders/*.frag" 30 | "shaders/*.vert" 31 | ) 32 | 33 | foreach(GLSL ${GLSL_SOURCE_FILES}) 34 | get_filename_component(FILE_NAME ${GLSL} NAME) 35 | set(SPIRV "${PROJECT_BINARY_DIR}/shaders/${FILE_NAME}.spv") 36 | add_custom_command( 37 | OUTPUT ${SPIRV} 38 | COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/shaders/" 39 | COMMAND ${GLSL_VALIDATOR} -V ${GLSL} -o ${SPIRV} 40 | COMMAND cat ${SPIRV} | xxd -i > ${GLSL}.array 41 | DEPENDS ${GLSL}) 42 | list(APPEND SPIRV_BINARY_FILES ${SPIRV}) 43 | endforeach(GLSL) 44 | 45 | add_custom_target( 46 | Shaders 47 | DEPENDS ${SPIRV_BINARY_FILES} 48 | ) 49 | 50 | add_dependencies(engine Shaders) 51 | 52 | add_custom_command(TARGET engine PRE_BUILD 53 | COMMAND ${CMAKE_COMMAND} -E make_directory "$/shaders/" 54 | COMMAND ${CMAKE_COMMAND} -E copy_directory 55 | "${PROJECT_BINARY_DIR}/shaders" 56 | "$/shaders" 57 | ) 58 | -------------------------------------------------------------------------------- /engine/AreaList_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 24/09/2018. 3 | // 4 | 5 | #include "catch.hpp" 6 | #include "AreaList.h" 7 | 8 | TEST_CASE("AreaList") { 9 | SECTION("constructor") { 10 | AreaList al; 11 | } 12 | 13 | SECTION("basics") { 14 | AreaList al; 15 | 16 | auto a1 = new Area {0, 0, 32, 32}; 17 | al.emplace_back(a1); 18 | auto a2 = new Area {32, 0, 32, 32}; 19 | al.emplace_back(a2); 20 | } 21 | 22 | SECTION("FindAdjacent") { 23 | AreaList al; 24 | 25 | auto area = new Area {0, 0, 32, 32}; 26 | 27 | auto a1 = new Area {32, 0, 32, 32}; 28 | al.emplace_back(a1); 29 | auto a2 = new Area {0, 32, 32, 32}; 30 | al.emplace_back(a2); 31 | auto a3 = new Area {32, 32, 32, 32}; 32 | al.emplace_back(a3); 33 | 34 | auto it = al.FindAdjacent(*area); 35 | 36 | REQUIRE(*it == a1); 37 | } 38 | 39 | SECTION("FindArea") { 40 | SECTION("empty") { 41 | AreaList al; 42 | 43 | auto area = al.FindArea(32, 32); 44 | 45 | REQUIRE(area == al.end()); 46 | } 47 | 48 | SECTION("single fitting") { 49 | AreaList al; 50 | 51 | auto a1 = new Area {0, 0, 32, 32}; 52 | al.emplace_back(a1); 53 | 54 | auto area = al.FindArea(32, 32); 55 | REQUIRE(area == al.begin()); 56 | } 57 | 58 | SECTION("single too small") { 59 | AreaList al; 60 | 61 | auto a1 = new Area {0, 0, 32, 32}; 62 | al.emplace_back(a1); 63 | 64 | auto area = al.FindArea(64, 64); 65 | REQUIRE(area == al.end()); 66 | } 67 | 68 | SECTION("single larger than needed") { 69 | AreaList al; 70 | 71 | auto a1 = new Area {0, 0, 32, 32}; 72 | al.emplace_back(a1); 73 | 74 | auto area = al.FindArea(24, 24); 75 | REQUIRE(area == al.begin()); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /engine/TextureAtlas_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 7.10.2018. 3 | // 4 | 5 | #include "catch.hpp" 6 | 7 | #define GLFW_INCLUDE_VULKAN 8 | #include 9 | #include "Renderer.h" 10 | #include "TextureAtlas.h" 11 | 12 | 13 | TEST_CASE("TextureAtlas") { 14 | glfwInit(); 15 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 16 | auto window = glfwCreateWindow(800, 600, "TextureAtlasTest", nullptr, nullptr); 17 | 18 | Renderer r; 19 | r.Initialize(window, Renderer::ENABLE_VALIDATION); 20 | 21 | auto ta = r.CreateTextureAtlas(256, 256); 22 | REQUIRE(ta); 23 | 24 | SECTION("can create") { 25 | REQUIRE(ta->GetWidth() == 256); 26 | REQUIRE(ta->GetHeight() == 256); 27 | } 28 | 29 | SECTION("add a single texture") { 30 | auto at = ta->Add("resources/1.png"); 31 | REQUIRE(at); 32 | 33 | auto tw = at->GetTextureWindow(); 34 | REQUIRE(tw.x0 == 0.0f); 35 | REQUIRE(tw.y0 == 0.0f); 36 | REQUIRE(tw.x1 == 0.25f); 37 | REQUIRE(tw.y1 == 0.25f); 38 | } 39 | 40 | SECTION("add a single texture, too large") { 41 | auto at = ta->Add("resources/texture.jpg"); 42 | REQUIRE(!at); 43 | } 44 | 45 | SECTION("add two textures") { 46 | auto at1 = ta->Add("resources/1.png"); 47 | auto at2 = ta->Add("resources/2.png"); 48 | 49 | REQUIRE(at1); 50 | REQUIRE(at2); 51 | } 52 | 53 | SECTION("add a single texture in a frame") { 54 | r.StartFrame(); 55 | auto at1 = ta->Add("resources/1.png"); 56 | r.EndFrame(); 57 | r.WaitUntilDeviceIdle(); 58 | 59 | REQUIRE(at1); 60 | } 61 | 62 | SECTION("add a single texture in a frame with render") { 63 | r.StartFrame(); 64 | auto at1 = ta->Add("resources/1.png"); 65 | REQUIRE(at1); 66 | 67 | r.SetTexture(at1); 68 | r.DrawSprite(0, 0, at1->GetWidth(), at1->GetHeight()); 69 | r.EndFrame(); 70 | r.WaitUntilDeviceIdle(); 71 | } 72 | 73 | glfwDestroyWindow(window); 74 | 75 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 76 | } 77 | -------------------------------------------------------------------------------- /engine/DebugMessenger.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 18.9.2018. 3 | // 4 | 5 | #include "DebugMessenger.h" 6 | #include "Logger.h" 7 | #include 8 | 9 | #define UNUSED(x) (void(x)) 10 | 11 | static auto logger = GetLogger("vulkan"); 12 | 13 | VkBool32 14 | DebugMessenger::Log(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, 15 | const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, void *pUserData) { 16 | auto pThis = static_cast(pUserData); 17 | return pThis->Log(messageSeverity, messageType, pCallbackData); 18 | 19 | } 20 | 21 | VkBool32 DebugMessenger::Log(const VkDebugUtilsMessageSeverityFlagBitsEXT &messageSeverity, 22 | VkDebugUtilsMessageTypeFlagsEXT messageType, 23 | const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData) { 24 | UNUSED(messageType); 25 | 26 | std::string severity; 27 | if(messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { 28 | severity = "info"; 29 | m_infoCount++; 30 | logger->info(pCallbackData->pMessage); 31 | } 32 | if(messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { 33 | severity = "warning"; 34 | m_warningCount++; 35 | logger->warn(pCallbackData->pMessage); 36 | } 37 | if(messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { 38 | severity = "error"; 39 | m_errorCount++; 40 | logger->error(pCallbackData->pMessage); 41 | } 42 | 43 | if (!severity.empty()) { 44 | std::cerr << severity << ": " << pCallbackData->pMessage << std::endl; 45 | } 46 | 47 | return VK_FALSE; 48 | } 49 | 50 | DebugMessenger::DebugMessenger() : m_errorCount(0), m_warningCount(0), m_infoCount(0) { 51 | } 52 | 53 | int DebugMessenger::GetErrorAndWarningCount() { 54 | return m_errorCount + m_warningCount; 55 | } 56 | 57 | int DebugMessenger::GetErrorCount() { 58 | return m_errorCount; 59 | } 60 | 61 | int DebugMessenger::GetWarningCount() { 62 | return m_warningCount; 63 | } 64 | 65 | int DebugMessenger::GetInfoCount() { 66 | return m_infoCount; 67 | } 68 | -------------------------------------------------------------------------------- /engine/Vertex.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 24/08/2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_VERTEX_H 6 | #define VULKAN_SPRITES_VERTEX_H 7 | 8 | #include 9 | 10 | #define GLM_FORCE_RADIANS 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | struct Vertex 17 | { 18 | glm::vec2 pos; 19 | glm::vec4 color; 20 | glm::vec2 texCoord; 21 | union { 22 | struct { 23 | int32_t blendMode; 24 | }; 25 | int32_t data[4]; 26 | }; 27 | 28 | static VkVertexInputBindingDescription getBindingDescription() 29 | { 30 | VkVertexInputBindingDescription bindingDescription = {}; 31 | bindingDescription.binding = 0; 32 | bindingDescription.stride = sizeof(Vertex); 33 | bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; 34 | 35 | return bindingDescription; 36 | } 37 | 38 | static std::array getAttributeDescriptions() 39 | { 40 | std::array attributeDescriptions = {}; 41 | 42 | attributeDescriptions[0].binding = 0; 43 | attributeDescriptions[0].location = 0; 44 | attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; 45 | attributeDescriptions[0].offset = offsetof(Vertex, pos); 46 | 47 | attributeDescriptions[1].binding = 0; 48 | attributeDescriptions[1].location = 1; 49 | attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT; 50 | attributeDescriptions[1].offset = offsetof(Vertex, color); 51 | 52 | attributeDescriptions[2].binding = 0; 53 | attributeDescriptions[2].location = 2; 54 | attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; 55 | attributeDescriptions[2].offset = offsetof(Vertex, texCoord); 56 | 57 | attributeDescriptions[3].binding = 0; 58 | attributeDescriptions[3].location = 3; 59 | attributeDescriptions[3].format = VK_FORMAT_R32G32B32A32_SINT; 60 | attributeDescriptions[3].offset = offsetof(Vertex, data); 61 | 62 | return attributeDescriptions; 63 | } 64 | }; 65 | 66 | 67 | #endif //VULKAN_SPRITES_VERTEX_H 68 | -------------------------------------------------------------------------------- /samples/blendmodes/BlendModesApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 20/08/2018. 3 | // 4 | 5 | #include "BlendModesApp.h" 6 | #include "Renderer.h" 7 | #include "Texture.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | BlendModesApp::BlendModesApp() : m_window(nullptr) 14 | { 15 | glfwInit(); 16 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 17 | } 18 | 19 | void BlendModesApp::CreateWindow(int width, int height) 20 | { 21 | m_window = glfwCreateWindow(width, height, "Vulkan Sprites", nullptr, nullptr); 22 | glfwSetWindowUserPointer(m_window, this); 23 | } 24 | 25 | void BlendModesApp::Run() 26 | { 27 | Renderer r; 28 | r.Initialize(m_window, Renderer::ENABLE_VALIDATION); 29 | 30 | r.SetClearColor({0.0f, 0.0f, 0.0f, 1.0f}); 31 | 32 | auto tex = r.CreateTexture("resources/circle.png"); 33 | while (!glfwWindowShouldClose(m_window)) { 34 | glfwPollEvents(); 35 | if(r.StartFrame()) { 36 | r.SetTexture(tex); 37 | 38 | r.SetBlendMode(BM_NONE); 39 | DrawOverlappingSprites(r, {0.0f, 0.0f}); 40 | 41 | r.SetBlendMode(BM_BLEND); 42 | DrawOverlappingSprites(r, {300.0f, 0.0f}); 43 | 44 | r.SetBlendMode(BM_ADD); 45 | DrawOverlappingSprites(r, {0.0f, 300.0f}); 46 | 47 | r.SetBlendMode(BM_ADDX2); 48 | DrawOverlappingSprites(r, {300.0f, 300.0f}); 49 | 50 | r.EndFrame(); 51 | 52 | if(r.GetNumDrawCommands() > 1) { 53 | throw std::runtime_error("Too many draw commands"); 54 | } 55 | } 56 | } 57 | r.WaitUntilDeviceIdle(); 58 | } 59 | 60 | void BlendModesApp::DrawOverlappingSprites(Renderer &r, glm::vec2 offset) const { 61 | r.SetColor({1.0f, 0.0f, 0.0f, 0.5f}); 62 | r.DrawSprite(32 + offset.x, 32 + offset.y, 256, 256); 63 | r.SetColor({0.0f, 1.0f, 0.0f, 0.5f}); 64 | r.DrawSprite(64 + offset.x, 32 + offset.y, 256, 256); 65 | r.SetColor({0.0f, 0.0f, 1.0f, 0.5f}); 66 | r.DrawSprite(64 + offset.x, 64 + offset.y, 256, 256); 67 | r.SetColor({0.5f, 0.5f, 0.5f, 0.5f}); 68 | r.DrawSprite(32 + offset.x, 64 + offset.y, 256, 256); 69 | } 70 | 71 | GLFWwindow *BlendModesApp::GetWindow() 72 | { 73 | return m_window; 74 | } 75 | -------------------------------------------------------------------------------- /engine/ParticleEmitter.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 25.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_PARTICLEEMITTER_H 6 | #define VULKAN_SPRITES_PARTICLEEMITTER_H 7 | 8 | #include "Particle.h" 9 | #include "ITexture.h" 10 | #include "Vertex.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | class Renderer; 18 | class ParticleEmitter { 19 | public: 20 | ParticleEmitter() : m_rng(-1.0f, 1.0f) {} 21 | 22 | int GetNumParticles() const; 23 | 24 | float GetEmissionRate() const; 25 | void SetEmissionRate(float rate); 26 | 27 | float GetLifespan() const; 28 | void SetLifespan(float ls); 29 | 30 | glm::vec2 GetPosition() const; 31 | void SetPosition(glm::vec2 pos); 32 | 33 | float GetDirection() const; 34 | void SetDirection(float a); 35 | 36 | float GetDirectionRange() const; 37 | void SetDirectionRange(float r); 38 | 39 | float GetSpeed() const; 40 | void SetSpeed(float s); 41 | 42 | std::shared_ptr GetTexture() const; 43 | void SetTexture(std::shared_ptr tex); 44 | 45 | void Update(float td); 46 | void Render(Renderer &renderer); 47 | 48 | protected: 49 | float m_timeToEmit = 0.0f; 50 | float m_emissionRate = 0.0f; 51 | float m_lifespan = 1.0f; 52 | 53 | // Emission direction, as an angle (in radians) from the positive 54 | // x-axis (0 means to the right, pi/2 is up, pi is to left... 55 | float m_direction = 0.0f; 56 | 57 | // Range of emission direction - and individual particle's direction 58 | // will be the in the range [m_direction - m_directionRange, m_direction + m_directionRange] 59 | float m_directionRange = 0.0f; 60 | 61 | float m_speed = 0.0f; 62 | 63 | glm::vec2 m_position = glm::vec2 {std::numeric_limits::max(), std::numeric_limits::max()}; 64 | glm::vec2 m_lastPosition = glm::vec2 {std::numeric_limits::max(), std::numeric_limits::max()}; 65 | std::vector m_particles; 66 | std::vector m_indices; 67 | std::vector m_vertices; 68 | int m_numParticlesAlive; 69 | 70 | std::default_random_engine m_random_engine; 71 | std::uniform_real_distribution m_rng; 72 | 73 | std::shared_ptr m_texture; 74 | }; 75 | 76 | #endif //VULKAN_SPRITES_PARTICLEEMITTER_H 77 | -------------------------------------------------------------------------------- /engine/Glyph.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 10.10.2018. 3 | // 4 | 5 | #include 6 | #include "Glyph.h" 7 | #include "TextureAtlas.h" 8 | 9 | Glyph::Glyph(FT_Face face, FT_UInt ix, std::shared_ptr ta) : m_face(face), m_glyphIndex(ix) { 10 | auto error = FT_Load_Glyph(m_face, m_glyphIndex, FT_LOAD_DEFAULT); 11 | if(error) { 12 | throw std::runtime_error("failed to load glyph"); 13 | } 14 | 15 | FT_GlyphSlot glyphSlot = m_face->glyph; 16 | error = FT_Render_Glyph(glyphSlot, FT_RENDER_MODE_NORMAL); 17 | if(error) { 18 | throw std::runtime_error("failed to render glyph"); 19 | } 20 | 21 | m_left = glyphSlot->bitmap_left; 22 | m_top = glyphSlot->bitmap_top; 23 | m_width = static_cast(glyphSlot->metrics.width / 64); 24 | m_height = static_cast(glyphSlot->metrics.height / 64); 25 | m_advance = static_cast(glyphSlot->advance.x / 64); 26 | 27 | CreateTextureFromBitmap(ta); 28 | } 29 | 30 | void Glyph::CreateTextureFromBitmap(std::shared_ptr &ta) { 31 | FT_GlyphSlot glyphSlot = m_face->glyph; 32 | 33 | if(glyphSlot->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY || glyphSlot->bitmap.num_grays != 256) { 34 | throw std::runtime_error("unsupported pixel mode"); 35 | } 36 | 37 | auto width = glyphSlot->bitmap.width; 38 | auto height = glyphSlot->bitmap.rows; 39 | auto bufferSize = width*height*4; 40 | 41 | if(bufferSize == 0) { 42 | return; 43 | } 44 | 45 | std::vector buffer(bufferSize); 46 | 47 | uint8_t* src = glyphSlot->bitmap.buffer; 48 | uint8_t* startOfLine = src; 49 | int dst = 0; 50 | 51 | for(int y = 0; y < height; ++y) { 52 | src = startOfLine; 53 | for(int x = 0; x < width; ++x) { 54 | auto value = *src; 55 | src++; 56 | 57 | buffer[dst++] = 0xff; 58 | buffer[dst++] = 0xff; 59 | buffer[dst++] = 0xff; 60 | buffer[dst++] = value; 61 | } 62 | startOfLine += glyphSlot->bitmap.pitch; 63 | } 64 | m_texture = ta->Add(width, height, buffer.data()); 65 | } 66 | 67 | int Glyph::GetLeft() { 68 | return m_left; 69 | } 70 | 71 | int Glyph::GetTop() { 72 | return m_top; 73 | } 74 | 75 | std::shared_ptr Glyph::GetTexture() { 76 | return m_texture; 77 | } 78 | 79 | int Glyph::GetWidth() { 80 | return m_width; 81 | } 82 | 83 | int Glyph::GetHeight() { 84 | return m_height; 85 | } 86 | 87 | int Glyph::GetAdvance() { 88 | return m_advance; 89 | } 90 | -------------------------------------------------------------------------------- /engine/ParticleSystem_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 25.10.2018. 3 | // 4 | 5 | #include "catch.hpp" 6 | 7 | #define GLFW_INCLUDE_VULKAN 8 | #include 9 | #include "Renderer.h" 10 | #include "ParticleSystem.h" 11 | #include "Particle.h" 12 | 13 | TEST_CASE("ParticleSystem basics") { 14 | SECTION("can create") { 15 | ParticleSystem ps; 16 | } 17 | 18 | SECTION("newly constructed is empty") { 19 | ParticleSystem ps; 20 | 21 | REQUIRE(ps.GetNumParticles() == 0); 22 | REQUIRE(ps.GetNumEmitters() == 0); 23 | } 24 | 25 | SECTION("Update") { 26 | ParticleSystem ps; 27 | 28 | SECTION("when empty") { 29 | ps.Update(1.0f); 30 | REQUIRE(ps.GetNumParticles() == 0); 31 | } 32 | } 33 | 34 | SECTION("Emitter") { 35 | ParticleSystem ps; 36 | auto em = ps.AddEmitter(); 37 | 38 | SECTION("AddEmitter") { 39 | REQUIRE(ps.GetNumEmitters() == 1); 40 | 41 | REQUIRE(em->GetEmissionRate() == 0.0f); 42 | REQUIRE(em->GetNumParticles() == 0); 43 | REQUIRE(em->GetLifespan() == 1.0f); 44 | REQUIRE(em->GetDirection() == 0.0f); 45 | REQUIRE(em->GetDirectionRange() == 0.0f); 46 | REQUIRE(em->GetSpeed() == 0.0f); 47 | } 48 | 49 | SECTION("SetEmissionRate") { 50 | em->SetEmissionRate(1.0f); 51 | REQUIRE(em->GetEmissionRate() == 1.0f); 52 | } 53 | 54 | SECTION("SetLifespan") { 55 | em->SetLifespan(15.0f); 56 | REQUIRE(em->GetLifespan() == 15.0f); 57 | } 58 | 59 | SECTION("SetPosition") { 60 | em->SetPosition({3.14f, 42.0f}); 61 | REQUIRE(em->GetPosition() == glm::vec2 {3.14f, 42.0f}); 62 | } 63 | 64 | SECTION("Update") { 65 | em->SetEmissionRate(1.0f); 66 | em->Update(10.0f); 67 | REQUIRE(em->GetNumParticles() == 10); 68 | } 69 | 70 | SECTION("SetDirection") { 71 | em->SetDirection(0.12f); 72 | REQUIRE(em->GetDirection() == 0.12f); 73 | } 74 | 75 | SECTION("SetDirectionRange") { 76 | em->SetDirectionRange(0.12f); 77 | REQUIRE(em->GetDirectionRange() == 0.12f); 78 | } 79 | 80 | SECTION("SetSpeed") { 81 | em->SetSpeed(0.34f); 82 | REQUIRE(em->GetSpeed() == 0.34f); 83 | } 84 | } 85 | } 86 | 87 | TEST_CASE("Particle") { 88 | SECTION("can create") { 89 | Particle p; 90 | } 91 | } -------------------------------------------------------------------------------- /engine/Area_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 24/09/2018. 3 | // 4 | 5 | #include "catch.hpp" 6 | #include "Area.h" 7 | 8 | TEST_CASE("Area") { 9 | SECTION("IsAdjacent") { 10 | SECTION("true when vertical") { 11 | Area area1 {0, 0, 32, 32}; 12 | Area area2 {0, 32, 32, 32}; 13 | 14 | REQUIRE(area1.IsAdjacent(area2)); 15 | REQUIRE(area2.IsAdjacent(area1)); 16 | } 17 | 18 | SECTION("true when horizontal") { 19 | Area area1 {0, 0, 32, 32}; 20 | Area area2 {32, 0, 32, 32}; 21 | 22 | REQUIRE(area1.IsAdjacent(area2)); 23 | REQUIRE(area2.IsAdjacent(area1)); 24 | } 25 | 26 | SECTION("false") { 27 | Area area1 {0, 0, 32, 32}; 28 | Area area2 {33, 0, 32, 32}; 29 | 30 | REQUIRE(!area1.IsAdjacent(area2)); 31 | REQUIRE(!area2.IsAdjacent(area1)); 32 | } 33 | } 34 | 35 | SECTION("CombineWith") { 36 | SECTION("to the right") { 37 | Area area1 {0, 0, 32, 32}; 38 | Area area2 {32, 0, 32, 32}; 39 | 40 | bool result = area1.CombineWith(area2); 41 | 42 | REQUIRE(result); 43 | REQUIRE(area1.x == 0); 44 | REQUIRE(area1.width == 64); 45 | } 46 | 47 | SECTION("to the left") { 48 | Area area1 {32, 0, 32, 32}; 49 | Area area2 {0, 0, 32, 32}; 50 | 51 | bool result = area1.CombineWith(area2); 52 | 53 | REQUIRE(result); 54 | REQUIRE(area1.x == 0); 55 | REQUIRE(area1.width == 64); 56 | } 57 | 58 | SECTION("below") { 59 | Area area1 {0, 0, 32, 32}; 60 | Area area2 {0, 32, 32, 32}; 61 | 62 | bool result = area1.CombineWith(area2); 63 | 64 | REQUIRE(result); 65 | REQUIRE(area1.y == 0); 66 | REQUIRE(area1.height == 64); 67 | } 68 | 69 | SECTION("above") { 70 | Area area1 {0, 32, 32, 32}; 71 | Area area2 {0, 0, 32, 32}; 72 | 73 | bool result = area1.CombineWith(area2); 74 | 75 | REQUIRE(result); 76 | REQUIRE(area1.y == 0); 77 | REQUIRE(area1.height == 64); 78 | } 79 | 80 | SECTION("false") { 81 | Area area1 {0, 0, 32, 32}; 82 | Area area2 {33, 0, 32, 32}; 83 | 84 | bool result = area1.CombineWith(area2); 85 | 86 | REQUIRE(!result); 87 | REQUIRE(area1.x == 0); 88 | REQUIRE(area1.width == 32); 89 | REQUIRE(area1.y == 0); 90 | REQUIRE(area1.height == 32); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /samples/particles/ParticlesApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 25.10.2018. 3 | // 4 | 5 | #include "Renderer.h" 6 | #include "FontManager.h" 7 | #include "TextureAtlas.h" 8 | #include "ParticleSystem.h" 9 | #include "ParticlesApp.h" 10 | #include "telemetry.h" 11 | 12 | ParticlesApp::ParticlesApp() { 13 | glfwInit(); 14 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 15 | } 16 | 17 | void ParticlesApp::CreateWindow(int width, int height) { 18 | m_window = glfwCreateWindow(width, height, "Particles", nullptr, nullptr); 19 | 20 | glfwGetFramebufferSize(m_window, &m_width, &m_height); 21 | 22 | m_width *= 2; 23 | m_height *= 2; 24 | 25 | glfwSetWindowUserPointer(m_window, this); 26 | } 27 | 28 | void ParticlesApp::Run() { 29 | Renderer r; 30 | r.Initialize(m_window, Renderer::ENABLE_VALIDATION); 31 | 32 | auto ta = r.CreateTextureAtlas(512, 512); 33 | FontManager fm(ta); 34 | auto font = fm.GetFont("resources/montserrat/Montserrat-Bold.ttf", 36); 35 | 36 | ParticleSystem ps; 37 | 38 | std::default_random_engine random_engine; 39 | std::uniform_real_distribution rng; 40 | 41 | std::vector> emitters; 42 | 43 | for(int i = 0; i < 1000; ++i) { 44 | auto em = ps.AddEmitter(); 45 | em->SetEmissionRate(rng(random_engine) * 500.0f + 10.0f); 46 | em->SetPosition({rng(random_engine) * 800, rng(random_engine) * 600}); 47 | em->SetLifespan(rng(random_engine) * 1.0f + 0.5f); 48 | em->SetDirection(rng(random_engine) * M_PI * 2.0f); 49 | em->SetDirectionRange(0.1f); 50 | em->SetSpeed(rng(random_engine) * 100.0f + 10.0f); 51 | emitters.push_back(em); 52 | } 53 | 54 | auto emIt = emitters.begin(); 55 | 56 | r.SetClearColor({0.0f, 0.0f, 0.0f, 1.0f}); 57 | while (!glfwWindowShouldClose(m_window)) { 58 | tmZone(0, 0, "main loop"); 59 | 60 | glfwPollEvents(); 61 | 62 | if(rng(random_engine) < 0.4f) { 63 | (*emIt)->SetDirection(rng(random_engine) * M_PI * 2.0f); 64 | emIt++; 65 | if(emIt == emitters.end()) { 66 | emIt = emitters.begin(); 67 | } 68 | } 69 | float td = 1.0f / 60.0f; 70 | ps.Update(td); 71 | 72 | if(r.StartFrame()) { 73 | ps.Render(r); 74 | 75 | auto n = ps.GetNumParticles(); 76 | char buffer[32]; 77 | sprintf(buffer, "Num particles: %d", n); 78 | r.SetColor({1.0f, 0.5f, 0.5f, 1}); 79 | r.SetBlendMode(BM_BLEND); 80 | font->Draw(r, 32, 32, buffer); 81 | r.EndFrame(); 82 | } 83 | } 84 | r.WaitUntilDeviceIdle(); 85 | 86 | } 87 | -------------------------------------------------------------------------------- /samples/curves/CurvesApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 26.10.2018. 3 | // 4 | 5 | #include "CurvesApp.h" 6 | #include "Curve.h" 7 | #include "Renderer.h" 8 | #include "FontManager.h" 9 | #include "telemetry.h" 10 | 11 | #include 12 | #include 13 | 14 | CurvesApp::CurvesApp() { 15 | glfwInit(); 16 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 17 | } 18 | 19 | void CurvesApp::CreateWindow(int width, int height) { 20 | m_window = glfwCreateWindow(width, height, "Curves", nullptr, nullptr); 21 | 22 | glfwGetFramebufferSize(m_window, &m_width, &m_height); 23 | 24 | m_width *= 2; 25 | m_height *= 2; 26 | 27 | glfwSetWindowUserPointer(m_window, this); 28 | } 29 | 30 | void CurvesApp::Run() { 31 | Renderer r; 32 | r.Initialize(m_window, Renderer::ENABLE_VALIDATION); 33 | 34 | auto ta = r.CreateTextureAtlas(512, 512); 35 | FontManager fm(ta); 36 | auto font = fm.GetFont("resources/montserrat/Montserrat-Bold.ttf", 36); 37 | 38 | float time = 0.0f; 39 | 40 | Curve c1; 41 | c1.AddKey(0.0f, 42.0f); 42 | c1.AddKey(5.0f, 3.14f); 43 | c1.SetExtrapolationType(CurveExtrapolation::CYCLE); 44 | 45 | Curve c2; 46 | c2.AddKey(0.0f, {100, 100}); 47 | c2.AddKey(1.0f, {300, 100}); 48 | c2.AddKey(2.0f, {300, 300}); 49 | c2.AddKey(3.0f, {100, 300}); 50 | c2.AddKey(4.0f, {100, 100}); 51 | c2.SetExtrapolationType(CurveExtrapolation::CYCLE); 52 | 53 | Curve c3; 54 | c3.AddKey(0.0f, {0, 0, 0, 1}); 55 | c3.AddKey(2.0f, {1, 0, 0, 1}); 56 | c3.AddKey(3.0f, {0, 0, 1, 1}); 57 | c3.SetExtrapolationType(CurveExtrapolation::MIRROR); 58 | 59 | r.SetClearColor({0.0f, 0.0f, 0.0f, 1.0f}); 60 | while (!glfwWindowShouldClose(m_window)) { 61 | tmZone(0, 0, "main loop"); 62 | 63 | glfwPollEvents(); 64 | 65 | float td = 1.0f / 60.0f; 66 | if(r.StartFrame()) { 67 | char buffer[256]; 68 | 69 | sprintf(buffer, "%4.2f", time); 70 | r.SetColor({1,1,1,1}); 71 | font->Draw(r, 20, 40, buffer); 72 | 73 | sprintf(buffer, "%4.2f", c1.GetValue(time)); 74 | r.SetColor({1,1,1,1}); 75 | font->Draw(r, 20, 80, buffer); 76 | 77 | r.SetTexture(nullptr); 78 | for(int i = 0; i < 32; ++i) { 79 | r.SetColor(c3.GetValue(time + i*0.01f)); 80 | r.DrawSprite(20 + i*20, 300, 20, 200); 81 | } 82 | 83 | auto pos = c2.GetValue(time); 84 | r.SetTexture(nullptr); 85 | r.SetColor({1,1,1,1}); 86 | r.DrawSprite(pos.x, pos.y, 16, 16); 87 | 88 | time += td; 89 | r.EndFrame(); 90 | } 91 | } 92 | r.WaitUntilDeviceIdle(); 93 | 94 | } 95 | -------------------------------------------------------------------------------- /engine/AreaAllocator_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 23/09/2018. 3 | // 4 | 5 | #include "catch.hpp" 6 | #include "AreaAllocator.h" 7 | 8 | TEST_CASE("AreaAllocator") { 9 | SECTION("constructor") { 10 | AreaAllocator aa; 11 | } 12 | 13 | SECTION("Initialize") { 14 | AreaAllocator aa; 15 | 16 | aa.Initialize(2048, 2048); 17 | 18 | REQUIRE(aa.GetTotalWidth() == 2048); 19 | REQUIRE(aa.GetTotalHeight() == 2048); 20 | REQUIRE(aa.GetFreeAreaCount() == 1); 21 | REQUIRE(aa.GetFreeAreaSize() == 2048*2048); 22 | REQUIRE(aa.GetAllocatedAreaCount() == 0); 23 | REQUIRE(aa.GetAllocatedAreaSize() == 0); 24 | } 25 | 26 | SECTION("Allocate") { 27 | AreaAllocator aa; 28 | aa.Initialize(2048, 2048); 29 | 30 | SECTION("simple case") { 31 | auto sizeBefore = aa.GetFreeAreaSize(); 32 | 33 | Area* a = aa.Allocate(64, 64); 34 | REQUIRE(a->width == 64); 35 | REQUIRE(a->height == 64); 36 | 37 | auto sizeAfter = aa.GetFreeAreaSize(); 38 | REQUIRE(sizeAfter == sizeBefore - 64*64); 39 | 40 | REQUIRE(aa.GetFreeAreaSize() + aa.GetAllocatedAreaSize() == aa.GetTotalAreaSize()); 41 | } 42 | 43 | SECTION("too large returns nullptr") { 44 | auto sizeBefore = aa.GetFreeAreaSize(); 45 | Area* a = aa.Allocate(4096, 4096); 46 | 47 | REQUIRE(!a); 48 | REQUIRE(aa.GetFreeAreaSize() + aa.GetAllocatedAreaSize() == aa.GetTotalAreaSize()); 49 | } 50 | 51 | SECTION("four quadrants") { 52 | auto a1 = aa.Allocate(1024, 1024); 53 | auto a2 = aa.Allocate(1024, 1024); 54 | auto a3 = aa.Allocate(1024, 1024); 55 | auto a4 = aa.Allocate(1024, 1024); 56 | 57 | REQUIRE(a1); 58 | REQUIRE(a2); 59 | REQUIRE(a3); 60 | REQUIRE(a4); 61 | 62 | REQUIRE(aa.GetFreeAreaSize() == 0); 63 | REQUIRE(aa.GetFreeAreaSize() + aa.GetAllocatedAreaSize() == aa.GetTotalAreaSize()); 64 | } 65 | 66 | SECTION("collapse areas") { 67 | auto a1 = aa.Allocate(1024, 1024); 68 | aa.Free(a1); 69 | 70 | auto a2 = aa.Allocate(2048, 2048); 71 | REQUIRE(a2); 72 | REQUIRE(aa.GetFreeAreaSize() == 0); 73 | REQUIRE(aa.GetFreeAreaSize() + aa.GetAllocatedAreaSize() == aa.GetTotalAreaSize()); 74 | } 75 | } 76 | 77 | SECTION("Free") { 78 | AreaAllocator aa; 79 | aa.Initialize(2048, 2048); 80 | 81 | Area* a = aa.Allocate(64, 64); 82 | aa.Free(a); 83 | 84 | REQUIRE(aa.GetFreeAreaSize() + aa.GetAllocatedAreaSize() == aa.GetTotalAreaSize()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /samples/second/SecondApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 17.9.2018. 3 | // 4 | 5 | #include "SecondApp.h" 6 | #include "../../engine/Renderer.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace { 12 | void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { 13 | SecondApp* app = reinterpret_cast(glfwGetWindowUserPointer(window)); 14 | app->HandleKey(window, key, scancode, action, mods); 15 | } 16 | } 17 | 18 | SecondApp::SecondApp() : 19 | m_window(nullptr), 20 | m_x(0), 21 | m_y(0.0f), 22 | m_dx(0.0f), 23 | m_vx(0.2f) 24 | { 25 | glfwInit(); 26 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 27 | } 28 | 29 | void SecondApp::CreateWindow(int width, int height) 30 | { 31 | m_window = glfwCreateWindow(width, height, "Second", nullptr, nullptr); 32 | 33 | glfwGetFramebufferSize(m_window, &m_width, &m_height); 34 | 35 | m_width *= 2; 36 | m_height *= 2; 37 | 38 | m_x = m_width / 2; 39 | m_y = m_height - 16; 40 | m_dx = 0.0f; 41 | 42 | glfwSetWindowUserPointer(m_window, this); 43 | glfwSetFramebufferSizeCallback(m_window, framebufferResizeCallback); 44 | glfwSetKeyCallback(m_window, keyCallback); 45 | } 46 | 47 | void SecondApp::framebufferResizeCallback(GLFWwindow *window, int width, int height) 48 | { 49 | auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); 50 | app->m_framebufferResized = true; 51 | } 52 | 53 | void SecondApp::Run() 54 | { 55 | Renderer r; 56 | r.Initialize(m_window, Renderer::ENABLE_VALIDATION); 57 | r.SetClearColor({0.0f, 0.0f, 1.0f, 1.0f}); 58 | while (!glfwWindowShouldClose(m_window)) { 59 | m_x += m_dx * m_vx; 60 | if(m_x < 0.0f) { 61 | m_x = 0.0f; 62 | } 63 | if(m_x > m_width) { 64 | m_x = m_width; 65 | } 66 | if(r.StartFrame()) { 67 | r.SetColor({1.0f, 0.0f, 0.0f, 1.0f}); 68 | r.DrawSprite(64, 32, 64, 32); 69 | r.SetColor({1.0f, 1.0f, 1.0f, 1.0f}); 70 | r.DrawSprite(m_x - 32, m_y, 64, 16); 71 | r.EndFrame(); 72 | } 73 | 74 | glfwPollEvents(); 75 | } 76 | r.WaitUntilDeviceIdle(); 77 | } 78 | 79 | GLFWwindow *SecondApp::GetWindow() 80 | { 81 | return m_window; 82 | } 83 | 84 | void SecondApp::HandleKey(GLFWwindow *window, int key, int scancode, int action, int mods) { 85 | switch(key) { 86 | case GLFW_KEY_Z: 87 | if(action == GLFW_PRESS) { 88 | m_dx = -1.0f; 89 | } else if(action == GLFW_RELEASE) { 90 | m_dx = 0.0f; 91 | } 92 | break; 93 | 94 | case GLFW_KEY_X: 95 | if(action == GLFW_PRESS) { 96 | m_dx = 1.0f; 97 | } else if(action == GLFW_RELEASE) { 98 | m_dx = 0.0f; 99 | } 100 | break; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /engine/Texture.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 25.8.2018. 3 | // 4 | 5 | #include 6 | #include "Texture.h" 7 | 8 | #define STB_IMAGE_IMPLEMENTATION 9 | #include "../extern/stb_image.h" 10 | 11 | Texture::Texture(Renderer *renderer, const std::string &filename) : m_renderer(renderer) 12 | { 13 | int width; 14 | int height; 15 | int channels; 16 | stbi_uc* pixels = stbi_load(filename.c_str(), &width, &height, &channels, STBI_rgb_alpha); 17 | if(!pixels) { 18 | throw std::runtime_error("failed to load texture image"); 19 | } 20 | 21 | init(static_cast(width), static_cast(height), pixels); 22 | 23 | stbi_image_free(pixels); 24 | } 25 | 26 | Texture::Texture(Renderer *renderer, uint32_t width, uint32_t height, uint8_t *pixels) : m_renderer(renderer) { 27 | init(width, height, pixels); 28 | } 29 | 30 | void Texture::init(uint32_t width, uint32_t height, uint8_t *pixels) { 31 | m_width = width; 32 | m_height = height; 33 | VkDeviceSize imageSize = m_width * m_height * 4; 34 | auto stagingBuffer = m_renderer->CreateBuffer( 35 | imageSize, 36 | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 37 | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); 38 | 39 | m_renderer->CopyToBufferMemory(stagingBuffer.bufferMemory, pixels, imageSize); 40 | 41 | VkFormat format = VK_FORMAT_R8G8B8A8_UNORM; 42 | m_boundImage = m_renderer->CreateImage( 43 | m_width, m_height, 44 | format, VK_IMAGE_TILING_OPTIMAL, 45 | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 46 | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); 47 | 48 | m_renderer->TransitionImageLayout( 49 | m_boundImage.image, 50 | VK_IMAGE_LAYOUT_UNDEFINED, 51 | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); 52 | 53 | m_renderer->CopyBufferToImage(stagingBuffer.buffer, m_boundImage.image, m_width, m_height); 54 | m_renderer->TransitionImageLayout( 55 | m_boundImage.image, 56 | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 57 | VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); 58 | 59 | m_renderer->DestroyBufferLater(stagingBuffer); 60 | 61 | m_imageView = m_renderer->CreateImageView(m_boundImage.image, format); 62 | m_sampler = m_renderer->CreateSampler(); 63 | m_descriptorSet = m_renderer->CreateTextureDescriptorSet(m_imageView, m_sampler); 64 | } 65 | 66 | Texture::~Texture() { 67 | if(m_imageView) { 68 | m_renderer->DestroyImageView(m_imageView); 69 | } 70 | if(m_sampler) { 71 | m_renderer->DestroySampler(m_sampler); 72 | } 73 | m_renderer->DestroyImage(m_boundImage); 74 | } 75 | 76 | int Texture::GetWidth() { 77 | return m_width; 78 | } 79 | 80 | int Texture::GetHeight() { 81 | return m_height; 82 | } 83 | 84 | VkDescriptorSet Texture::GetDescriptorSet() { 85 | return m_descriptorSet; 86 | } 87 | 88 | TextureWindow Texture::GetTextureWindow() { 89 | return TextureWindow{0.0f, 0.0f, 1.0f, 1.0f}; 90 | } 91 | -------------------------------------------------------------------------------- /samples/textureatlas/TextureAtlasApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 8.10.2018. 3 | // 4 | 5 | #include "TextureAtlasApp.h" 6 | #include "../../engine/Renderer.h" 7 | #include "../../engine/TextureAtlas.h" 8 | #include "../../engine/Texture.h" 9 | #include "../../engine/Logger.h" 10 | 11 | #include 12 | namespace fs = std::experimental::filesystem; 13 | 14 | static auto logger = GetLogger("main"); 15 | 16 | TextureAtlasApp::TextureAtlasApp(const std::string& root) : m_rootDirectory(root) { 17 | glfwInit(); 18 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 19 | } 20 | 21 | void TextureAtlasApp::CreateWindow(int width, int height) { 22 | m_window = glfwCreateWindow(width, height, "TextureAtlas", nullptr, nullptr); 23 | 24 | glfwGetFramebufferSize(m_window, &m_width, &m_height); 25 | 26 | m_width *= 2; 27 | m_height *= 2; 28 | 29 | glfwSetWindowUserPointer(m_window, this); 30 | } 31 | 32 | bool endsWith(const std::string& s, const std::string& suffix) 33 | { 34 | return s.rfind(suffix) == (s.size()-suffix.size()); 35 | } 36 | 37 | void TextureAtlasApp::Run() { 38 | Renderer r; 39 | r.Initialize(m_window, Renderer::ENABLE_VALIDATION); 40 | 41 | auto ta = r.CreateTextureAtlas(4096, 2048); 42 | 43 | std::vector> textures; 44 | LoadTextures(r, ta, textures); 45 | 46 | r.SetClearColor({0.0f, 0.0f, 1.0f, 1.0f}); 47 | while (!glfwWindowShouldClose(m_window)) { 48 | glfwGetFramebufferSize(m_window, &m_width, &m_height); 49 | 50 | m_width *= 2; 51 | m_height *= 2; 52 | 53 | int x = 0; 54 | int y = 0; 55 | int textureIndex = 0; 56 | 57 | if (r.StartFrame()) { 58 | for(int i = 0; i < 5000; ++i) { 59 | auto& texture = textures[textureIndex]; 60 | r.SetTexture(texture); 61 | r.DrawSprite(x, y, 64, 64); 62 | 63 | textureIndex += 1; 64 | textureIndex %= textures.size(); 65 | 66 | x += 47; 67 | if(x > m_width) { 68 | y += 35; 69 | x -= m_width; 70 | } 71 | } 72 | 73 | 74 | r.EndFrame(); 75 | } 76 | 77 | glfwPollEvents(); 78 | } 79 | r.WaitUntilDeviceIdle(); 80 | } 81 | 82 | void 83 | TextureAtlasApp::LoadTextures(Renderer &renderer, std::shared_ptr &ta, 84 | std::vector> &textures) const { 85 | for(auto& p: fs::directory_iterator(m_rootDirectory)) { 86 | std::string s = p.path(); 87 | 88 | if(endsWith(s, ".png")) { 89 | auto t = ta->Add(s); 90 | //auto t = renderer.CreateTexture(s); 91 | if(t) { 92 | logger->debug("Added {} to atlas", s); 93 | textures.emplace_back(t); 94 | } else { 95 | logger->debug("{} didn't fit", s); 96 | } 97 | } else { 98 | logger->debug("{} is not a texture", s); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /samples/breakout/Ball.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 15.10.2018. 3 | // 4 | 5 | #include 6 | #include "Ball.h" 7 | #include "BrickCollection.h" 8 | 9 | Ball::Ball() : 10 | m_radius(1.0f), 11 | m_minX(0.0f), 12 | m_minY(0.0f), 13 | m_maxX(1.0f), 14 | m_maxY(1.0f) 15 | { 16 | m_emitter = m_particleSystem.AddEmitter(); 17 | m_emitter->SetLifespan(0.7f); 18 | //m_emitter->SetSpeed(250.0f); 19 | m_emitter->SetEmissionRate(329.0f); 20 | } 21 | 22 | void Ball::SetRadius(float r) { 23 | m_radius = r; 24 | } 25 | 26 | float Ball::GetRadius() { 27 | return m_radius; 28 | } 29 | 30 | void Ball::CollideWithPaddle(const Paddle &p) { 31 | auto paddlePos = p.GetPosition(); 32 | auto paddleHalfWidth = p.GetWidth() / 2.0f; 33 | auto paddleHalfHeight = p.GetHeight() / 2.0f; 34 | if(m_position.x + m_radius < paddlePos.x - paddleHalfWidth) { 35 | return; 36 | } 37 | if(m_position.x - m_radius > paddlePos.x + paddleHalfWidth) { 38 | return; 39 | } 40 | if(m_position.y + m_radius < paddlePos.y + paddleHalfHeight) { 41 | return; 42 | } 43 | if(m_position.y > paddlePos.y && m_velocity.y > 0.0f) { 44 | return; 45 | } 46 | m_velocity.y *= -1.0f; 47 | } 48 | 49 | void Ball::Render(Renderer &r) { 50 | r.SetTexture(nullptr); 51 | r.SetColor({0.5f, 1.0f, 0.5f, 1.0f}); 52 | r.DrawSprite(m_position.x - m_radius, m_position.y - m_radius, m_radius, m_radius); 53 | 54 | m_particleSystem.Render(r); 55 | } 56 | 57 | void Ball::SetBounds(float minX, float minY, float maxX, float maxY) { 58 | m_minX = minX; 59 | m_minY = minY; 60 | m_maxX = maxX; 61 | m_maxY = maxY; 62 | } 63 | 64 | void Ball::Update(float td) { 65 | Movable::Update(td); 66 | 67 | if(m_position.x < m_minX) { 68 | m_position.x = m_minX; 69 | m_velocity.x *= -1.0f; 70 | } 71 | 72 | if(m_position.x > m_maxX) { 73 | m_position.x = m_maxX; 74 | m_velocity.x *= -1.0f; 75 | } 76 | 77 | if(m_position.y < m_minY) { 78 | m_position.y = m_minY; 79 | m_velocity.y *= -1.0f; 80 | } 81 | 82 | if(m_position.y > m_maxY) { 83 | m_position.y = m_maxY; 84 | m_velocity.y *= -1.0f; 85 | } 86 | 87 | float direction = atan2(-m_velocity.y, -m_velocity.x); 88 | m_emitter->SetDirection(direction); 89 | m_emitter->SetPosition(m_position); 90 | m_particleSystem.Update(td); 91 | } 92 | 93 | bool Ball::CollideWithBrick(const Brick &brick) { 94 | auto halfWidth = brick.width / 2.0f; 95 | auto halfHeight = brick.height / 2.0f; 96 | if(m_position.x + m_radius < brick.pos.x - halfWidth) { 97 | return false; 98 | } 99 | if(m_position.x - m_radius > brick.pos.x + halfWidth) { 100 | return false; 101 | } 102 | if(m_position.y + m_radius < brick.pos.y - halfHeight) { 103 | return false; 104 | } 105 | if(m_position.y - m_radius > brick.pos.y + halfWidth) { 106 | return false; 107 | } 108 | auto dx = fabs(m_position.x - brick.pos.x); 109 | auto dy = fabs(m_position.y - brick.pos.y); 110 | 111 | if(dx > dy) { 112 | m_velocity.x *= -1.0f; 113 | } else { 114 | m_velocity.y *= -1.0f; 115 | } 116 | return true; 117 | } 118 | 119 | void Ball::SetTextureForParticles(std::shared_ptr tex) { 120 | m_emitter->SetTexture(tex); 121 | } 122 | -------------------------------------------------------------------------------- /engine/Logger.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 19.9.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_LOGGER_H 6 | #define VULKAN_SPRITES_LOGGER_H 7 | 8 | #include 9 | #include 10 | 11 | class Logger 12 | { 13 | public: 14 | static Logger* instance(); 15 | 16 | enum LogSeverity 17 | { 18 | SEVERITY_INFO, 19 | SEVERITY_NOTICE, 20 | SEVERITY_WARN, 21 | SEVERITY_ERR, 22 | 23 | SEVERITY_COUNT, 24 | }; 25 | 26 | void connectToHost(); 27 | void connectToHost(const std::string& server, int64_t pid, const std::string& machineName, const std::string& executablePath); 28 | void disconnnect(); 29 | 30 | void log(LogSeverity severity, const std::string& module, const std::string& channel, const std::string& message); 31 | 32 | private: 33 | 34 | struct ConnectionMessage 35 | { 36 | static const int MESSAGE_MAX_PATH = 260; 37 | 38 | uint32_t version; 39 | int64_t pid; 40 | char machineName[32]; 41 | char executablePath[MESSAGE_MAX_PATH]; 42 | }; 43 | 44 | struct TextMessage 45 | { 46 | static const int TEXT_SIZE = 256; 47 | 48 | uint64_t timestamp; 49 | uint32_t severity; 50 | char module[32]; 51 | char channel[32]; 52 | char message[TEXT_SIZE]; 53 | }; 54 | 55 | struct RawLogMessage 56 | { 57 | uint32_t type; 58 | union 59 | { 60 | ConnectionMessage connection; 61 | TextMessage text; 62 | }; 63 | }; 64 | 65 | 66 | int m_socket; 67 | int64_t m_pid; 68 | std::string m_machineName; 69 | std::string m_executablePath; 70 | 71 | enum State 72 | { 73 | Disconnected, 74 | Connecting, 75 | Connected, 76 | }; 77 | 78 | State m_state; 79 | }; 80 | 81 | #include "spdlog/sinks/base_sink.h" 82 | #include "spdlog/fmt/fmt.h" 83 | 84 | template 85 | class LogLiteSink : public spdlog::sinks::base_sink 86 | { 87 | public: 88 | LogLiteSink() 89 | { 90 | m_logger = Logger::instance(); 91 | } 92 | 93 | protected: 94 | Logger* m_logger; 95 | 96 | void _sink_it(const spdlog::details::log_msg& msg) override 97 | { 98 | Logger::LogSeverity severity; 99 | switch(msg.level) { 100 | case spdlog::level::trace: 101 | case spdlog::level::debug: 102 | severity = Logger::SEVERITY_INFO; 103 | break; 104 | case spdlog::level::info: 105 | severity = Logger::SEVERITY_NOTICE; 106 | break; 107 | case spdlog::level::warn: 108 | severity = Logger::SEVERITY_WARN; 109 | break; 110 | case spdlog::level::err: 111 | severity = Logger::SEVERITY_ERR; 112 | break; 113 | default: 114 | return; 115 | } 116 | m_logger->log(severity, "module", *msg.logger_name, msg.raw.c_str()); 117 | } 118 | void _flush() override 119 | { 120 | } 121 | }; 122 | 123 | #include "spdlog/details/null_mutex.h" 124 | #include 125 | using LogLiteSink_mt = LogLiteSink; 126 | using LogLiteSink_st = LogLiteSink; 127 | 128 | #include "spdlog/spdlog.h" 129 | 130 | std::shared_ptr GetLogger(const char *name); 131 | 132 | #endif //VULKAN_SPRITES_LOGGER_H 133 | -------------------------------------------------------------------------------- /samples/breakout/BreakOutApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 12.10.2018. 3 | // 4 | 5 | #include "BreakOutApp.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static auto logger = GetLogger("main"); 12 | 13 | namespace { 14 | void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { 15 | BreakOutApp* app = reinterpret_cast(glfwGetWindowUserPointer(window)); 16 | app->HandleKey(window, key, scancode, action, mods); 17 | } 18 | 19 | void cursorPositionCallback(GLFWwindow* window, double xpos, double ypos) { 20 | BreakOutApp* app = reinterpret_cast(glfwGetWindowUserPointer(window)); 21 | app->HandleCursorPosition(window, xpos, ypos); 22 | } 23 | } 24 | 25 | BreakOutApp::BreakOutApp() : m_splashScreen(this), m_mainMenu(this), m_gameplay(this), m_gameOver(this) 26 | { 27 | glfwInit(); 28 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 29 | 30 | m_states.resize(NUM_STATES); 31 | m_states[SPLASH_SCREEN] = &m_splashScreen; 32 | m_states[MAIN_MENU] = &m_mainMenu; 33 | m_states[GAMEPLAY] = &m_gameplay; 34 | m_states[GAME_OVER] = &m_gameOver; 35 | } 36 | 37 | void BreakOutApp::CreateWindow(int width, int height) 38 | { 39 | m_window = glfwCreateWindow(width, height, "Vulkan BreakOut", nullptr, nullptr); 40 | glfwSetWindowUserPointer(m_window, this); 41 | glfwSetKeyCallback(m_window, keyCallback); 42 | glfwSetCursorPosCallback(m_window, cursorPositionCallback); 43 | } 44 | 45 | void BreakOutApp::Run() 46 | { 47 | ConnectTelemetry(); 48 | 49 | Renderer r; 50 | r.Initialize(m_window, Renderer::ENABLE_VALIDATION); 51 | r.SetClearColor({0.0f, 0.0f, 0.0f, 1.0f}); 52 | 53 | auto ta = r.CreateTextureAtlas(1024, 1024); 54 | m_fontManager = std::make_unique(ta); 55 | 56 | for(auto state: m_states) { 57 | state->Init(r); 58 | } 59 | 60 | m_currentState = &m_splashScreen; 61 | m_nextState = m_currentState; 62 | m_currentState->Enter(r); 63 | 64 | while (!glfwWindowShouldClose(m_window) && m_currentState) { 65 | tmZone(0, 0, "main loop"); 66 | glfwPollEvents(); 67 | if(r.StartFrame()) { 68 | while(m_nextState != m_currentState) { 69 | m_currentState->Exit(r); 70 | if(m_nextState) { 71 | m_nextState->Enter(r); 72 | } 73 | m_currentState = m_nextState; 74 | } 75 | if(m_currentState) { 76 | m_currentState->Render(r); 77 | } 78 | r.EndFrame(); 79 | } 80 | tmTick(0); 81 | } 82 | r.WaitUntilDeviceIdle(); 83 | } 84 | 85 | void BreakOutApp::ChangeState(int newState) { 86 | if(newState == QUIT) { 87 | m_nextState = nullptr; 88 | } else { 89 | m_nextState = m_states[newState]; 90 | } 91 | } 92 | 93 | void BreakOutApp::HandleKey(GLFWwindow *window, int key, int scancode, int action, int mods) { 94 | m_currentState->HandleKey(key, scancode, action, mods); 95 | } 96 | 97 | void BreakOutApp::HandleCursorPosition(GLFWwindow *window, double xpos, double ypos) { 98 | m_currentState->HandleCursorPosition(xpos, ypos); 99 | } 100 | 101 | std::shared_ptr BreakOutApp::GetFont(int pt) { 102 | return m_fontManager->GetFont("resources/montserrat/Montserrat-Bold.ttf", pt); 103 | } 104 | 105 | GLFWwindow *BreakOutApp::GetWindow() { 106 | return m_window; 107 | } 108 | -------------------------------------------------------------------------------- /engine/TextureAtlas.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 7.10.2018. 3 | // 4 | 5 | #include "TextureAtlas.h" 6 | #include "Renderer.h" 7 | 8 | #include "../extern/stb_image.h" 9 | #include 10 | 11 | TextureAtlas::TextureAtlas() : m_width(0), m_height(0), m_renderer(nullptr) 12 | {} 13 | 14 | TextureAtlas::~TextureAtlas() { 15 | m_renderer->DestroyImageView(m_imageView); 16 | m_renderer->DestroySampler(m_sampler); 17 | m_renderer->DestroyImage(m_boundImage); 18 | } 19 | 20 | void TextureAtlas::Initialize(Renderer *pRenderer, uint32_t width, uint32_t height) { 21 | m_renderer = pRenderer; 22 | 23 | m_width = width; 24 | m_height = height; 25 | 26 | VkFormat format = VK_FORMAT_R8G8B8A8_UNORM; 27 | m_boundImage = m_renderer->CreateImage( 28 | m_width, m_height, 29 | format, VK_IMAGE_TILING_OPTIMAL, 30 | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 31 | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); 32 | 33 | m_renderer->TransitionImageLayout( 34 | m_boundImage.image, 35 | VK_IMAGE_LAYOUT_UNDEFINED, 36 | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); 37 | m_renderer->TransitionImageLayout( 38 | m_boundImage.image, 39 | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 40 | VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); 41 | 42 | m_imageView = m_renderer->CreateImageView(m_boundImage.image, format); 43 | m_sampler = m_renderer->CreateSampler(); 44 | m_descriptorSet = m_renderer->CreateTextureDescriptorSet(m_imageView, m_sampler); 45 | 46 | m_allocator.Initialize(m_width, m_height); 47 | } 48 | 49 | int TextureAtlas::GetWidth() { 50 | return m_width; 51 | } 52 | 53 | int TextureAtlas::GetHeight() { 54 | return m_height; 55 | } 56 | 57 | std::shared_ptr TextureAtlas::Add(const std::string &filename) { 58 | int width; 59 | int height; 60 | int channels; 61 | stbi_uc* pixels = stbi_load(filename.c_str(), &width, &height, &channels, STBI_rgb_alpha); 62 | if(!pixels) { 63 | throw std::runtime_error("failed to load texture image"); 64 | } 65 | 66 | auto result = Add(static_cast(width), static_cast(height), pixels); 67 | 68 | stbi_image_free(pixels); 69 | 70 | return result; 71 | } 72 | 73 | std::shared_ptr TextureAtlas::Add(uint32_t width, uint32_t height, uint8_t *pixels) { 74 | auto area = m_allocator.Allocate(width, height); 75 | if(!area) { 76 | return nullptr; 77 | } 78 | 79 | VkDeviceSize imageSize = width * height * 4; 80 | auto stagingBuffer = m_renderer->CreateBuffer( 81 | imageSize, 82 | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 83 | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); 84 | 85 | m_renderer->CopyToBufferMemory(stagingBuffer.bufferMemory, pixels, imageSize); 86 | 87 | m_renderer->TransitionImageLayout( 88 | m_boundImage.image, 89 | VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 90 | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); 91 | m_renderer->CopyBufferToImage(stagingBuffer.buffer, m_boundImage.image, width, height, area->x, area->y); 92 | m_renderer->TransitionImageLayout( 93 | m_boundImage.image, 94 | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 95 | VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); 96 | 97 | m_renderer->DestroyBufferLater(stagingBuffer); 98 | 99 | 100 | return std::make_shared(this, area); 101 | } 102 | 103 | VkDescriptorSet TextureAtlas::GetDescriptorSet() { 104 | return m_descriptorSet; 105 | } 106 | 107 | TextureWindow TextureAtlas::GetTextureWindow() { 108 | return TextureWindow{0.0f, 0.0f, 1.0f, 1.0f}; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /samples/breakout/Gameplay.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 14.10.2018. 3 | // 4 | 5 | #include 6 | #include 7 | #include "Font.h" 8 | #include "Gameplay.h" 9 | #include "telemetry.h" 10 | #include "Texture.h" 11 | 12 | static auto logger = GetLogger("gameplay"); 13 | 14 | 15 | Gameplay::Gameplay(App *a) : AppState(a) { 16 | 17 | } 18 | 19 | void Gameplay::Init(Renderer &r) { 20 | m_titleFont = m_app->GetFont(48); 21 | 22 | m_paddleSpeed = 500.0f; 23 | m_fieldWidth = 1200.0f; 24 | m_fieldHeight = 800.0f; 25 | 26 | m_paddle.SetPosition({m_fieldWidth/2.0f, m_fieldHeight - 40}); 27 | m_paddle.SetVelocity({0, 0}); 28 | m_paddle.SetWidth(100); 29 | m_paddle.SetHeight(10); 30 | m_paddle.SetBounds(0.0f, m_fieldWidth); 31 | 32 | m_ball.SetPosition({m_fieldWidth/2.0f, m_fieldHeight/2.0f}); 33 | m_ball.SetVelocity({500, 500}); 34 | m_ball.SetRadius(10); 35 | m_ball.SetBounds(0.0f, 0.0f, m_fieldWidth, m_fieldHeight); 36 | 37 | auto tex = r.CreateTexture("resources/ps1.png"); 38 | m_ball.SetTextureForParticles(tex); 39 | 40 | 41 | for(int y = 0; y < 5; ++y) { 42 | for(int x = 0; x < 24; ++x) { 43 | m_bricks.AddBrick({x*50 + 23, y*30 + 100}, 40, 20); 44 | } 45 | } 46 | 47 | m_leftPressed = false; 48 | m_rightPressed = false; 49 | } 50 | 51 | void Gameplay::Enter(Renderer &r) { 52 | tmFunction(0, 0); 53 | 54 | logger->debug(__PRETTY_FUNCTION__); 55 | m_frameCounter = 0; 56 | 57 | glfwSetInputMode(m_app->GetWindow(), GLFW_CURSOR, GLFW_CURSOR_HIDDEN); 58 | } 59 | 60 | void Gameplay::Render(Renderer &r) { 61 | tmFunction(0, 0); 62 | 63 | m_frameCounter++; 64 | 65 | 66 | float v = 0.0f; 67 | if(m_leftPressed) { 68 | v = -m_paddleSpeed; 69 | } else if(m_rightPressed) { 70 | v = m_paddleSpeed; 71 | } 72 | m_paddle.SetVelocity({v, 0}); 73 | 74 | r.SetTexture(nullptr); 75 | r.SetColor({0.3f, 0.3f, 0.3f, 1.0f}); 76 | r.DrawSprite(0, 0, m_fieldWidth, m_fieldHeight); 77 | 78 | auto td = 1 / 60.0f; 79 | m_paddle.Update(td); 80 | m_ball.Update(td); 81 | 82 | m_ball.CollideWithPaddle(m_paddle); 83 | m_bricks.CollideBallWithBricks(m_ball); 84 | 85 | m_bricks.Render(r); 86 | m_paddle.Render(r); 87 | m_ball.Render(r); 88 | 89 | } 90 | 91 | void Gameplay::Exit(Renderer &r) { 92 | tmFunction(0, 0); 93 | 94 | logger->debug(__PRETTY_FUNCTION__); 95 | glfwSetInputMode(m_app->GetWindow(), GLFW_CURSOR, GLFW_CURSOR_NORMAL); 96 | } 97 | 98 | void Gameplay::HandleKey(int key, int scancode, int action, int mods) { 99 | tmFunction(0, 0); 100 | 101 | switch(action) { 102 | case GLFW_PRESS: 103 | switch(key) { 104 | case GLFW_KEY_ESCAPE: 105 | m_app->ChangeState(GAME_OVER); 106 | break; 107 | 108 | case GLFW_KEY_Z: 109 | m_leftPressed = true; 110 | break; 111 | 112 | case GLFW_KEY_X: 113 | m_rightPressed = true; 114 | break; 115 | default:break; 116 | } 117 | break; 118 | 119 | case GLFW_RELEASE: 120 | switch(key) { 121 | case GLFW_KEY_Z: 122 | m_leftPressed = false; 123 | break; 124 | 125 | case GLFW_KEY_X: 126 | m_rightPressed = false; 127 | break; 128 | default:break; 129 | } 130 | break; 131 | default:break; 132 | } 133 | } 134 | 135 | void Gameplay::HandleCursorPosition(double xpos, double ypos) { 136 | m_paddle.SetPosition({xpos, m_paddle.GetPosition().y}); 137 | } 138 | -------------------------------------------------------------------------------- /engine/AreaAllocator.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 23/09/2018. 3 | // 4 | 5 | #include "AreaAllocator.h" 6 | 7 | AreaAllocator::AreaAllocator() : m_width(0), m_height(0) 8 | {} 9 | 10 | void AreaAllocator::Initialize(int width, int height) 11 | { 12 | m_width = width; 13 | m_height = height; 14 | 15 | m_freeAreas.emplace_back(new Area{0, 0, width, height}); 16 | } 17 | 18 | int AreaAllocator::GetTotalWidth() 19 | { 20 | return m_width; 21 | } 22 | 23 | int AreaAllocator::GetTotalHeight() 24 | { 25 | return m_height; 26 | } 27 | 28 | Area* AreaAllocator::Allocate(int width, int height) 29 | { 30 | Area *oldArea = getFreeArea(width, height); 31 | 32 | if(!oldArea) { 33 | collapseFreeAreas(); 34 | oldArea = getFreeArea(width, height); 35 | } 36 | 37 | if(!oldArea) { 38 | return nullptr; 39 | } 40 | 41 | auto newArea = new Area {oldArea->x, oldArea->y, width, height}; 42 | m_allocatedAreas.emplace_front(newArea); 43 | 44 | 45 | if(oldArea->width > width) { 46 | // Add an area to the right of newly allocated area 47 | m_freeAreas.emplace_back(new Area {oldArea->x + width, oldArea->y, oldArea->width - width, height}); 48 | } 49 | if(oldArea->height > height) { 50 | // Add an area below the newly allocated area 51 | m_freeAreas.emplace_back(new Area {oldArea->x, oldArea->y + height, width, oldArea->height - height}); 52 | } 53 | if(oldArea->width > width && oldArea->height > height) { 54 | // Add an area diagonally to the right and below the newly allocated area 55 | m_freeAreas.emplace_back(new Area {oldArea->x + width, oldArea->y + height, oldArea->width - width, oldArea->height - height}); 56 | } 57 | return newArea; 58 | } 59 | 60 | Area *AreaAllocator::getFreeArea(int width, int height) 61 | { 62 | Area* oldArea = nullptr; 63 | auto foundIt = m_freeAreas.FindArea(width, height); 64 | if(foundIt != m_freeAreas.end()) { 65 | oldArea = *foundIt; 66 | m_freeAreas.erase(foundIt); 67 | } 68 | return oldArea; 69 | } 70 | 71 | int AreaAllocator::GetFreeAreaCount() 72 | { 73 | return static_cast(m_freeAreas.size()); 74 | } 75 | 76 | int AreaAllocator::GetFreeAreaSize() 77 | { 78 | return accumulateAreaSize(m_freeAreas); 79 | } 80 | 81 | int AreaAllocator::accumulateAreaSize(const AreaList &areaList) const 82 | { 83 | int size = 0; 84 | for(const auto& area: areaList) { 85 | size += area->width*area->height; 86 | } 87 | return size; 88 | } 89 | 90 | int AreaAllocator::GetAllocatedAreaCount() 91 | { 92 | return static_cast(m_allocatedAreas.size()); 93 | } 94 | 95 | int AreaAllocator::GetAllocatedAreaSize() 96 | { 97 | return accumulateAreaSize(m_allocatedAreas); 98 | } 99 | 100 | void AreaAllocator::Free(Area *area) 101 | { 102 | m_allocatedAreas.remove(area); 103 | m_freeAreas.emplace_back(area); 104 | } 105 | 106 | int AreaAllocator::GetTotalAreaSize() 107 | { 108 | return m_width * m_height; 109 | } 110 | 111 | void AreaAllocator::collapseFreeAreas() 112 | { 113 | if( m_freeAreas.size() < 2 ) 114 | { 115 | return; 116 | } 117 | 118 | int collapsed = 0; 119 | do { 120 | collapsed = 0; 121 | AreaList collapsedAreas; 122 | while(!m_freeAreas.empty()) { 123 | auto first = m_freeAreas.front(); 124 | m_freeAreas.pop_front(); 125 | while(!m_freeAreas.empty()) { 126 | auto other = m_freeAreas.FindAdjacent(*first); 127 | if(other != m_freeAreas.end()) { 128 | first->CombineWith(**other); 129 | delete *other; 130 | m_freeAreas.erase(other); 131 | ++collapsed; 132 | } else { 133 | break; 134 | } 135 | } 136 | collapsedAreas.emplace_back(first); 137 | } 138 | m_freeAreas = collapsedAreas; 139 | } while(collapsed > 0); 140 | } 141 | -------------------------------------------------------------------------------- /engine/Curve.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 26.10.2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_CURVESCALAR_H 6 | #define VULKAN_SPRITES_CURVESCALAR_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace CurveExtrapolation 13 | { 14 | // Curve behavior when requesting value outside of the time 15 | // range defined for the curve 16 | enum Type 17 | { 18 | // Use first/last values 19 | CLAMP = 0, 20 | // Cycle the curve 21 | CYCLE = 1, 22 | // Cycle the curve with mirroring 23 | MIRROR = 2, 24 | // Linear extrapolation 25 | LINEAR = 3 26 | }; 27 | } 28 | 29 | template 30 | struct CurveKey { 31 | float time; 32 | T value; 33 | 34 | bool operator<(const CurveKey& other) { 35 | return time < other.time; 36 | } 37 | }; 38 | 39 | template 40 | class Curve { 41 | public: 42 | T GetValue(float t) { 43 | if(m_keys.empty()) { 44 | return T(); 45 | } 46 | 47 | if(m_keys.size() == 1) { 48 | return m_keys[0].value; 49 | } 50 | 51 | auto n = m_keys.size(); 52 | 53 | float startTime = m_keys.front().time; 54 | float endTime = m_keys.back().time; 55 | float duration = endTime - startTime; 56 | 57 | double intPart; 58 | 59 | if(t < startTime) { 60 | auto fracPart = static_cast(modf(-(t - startTime) / duration, &intPart)); 61 | switch(m_extrapolationType) { 62 | case CurveExtrapolation::CLAMP: 63 | return m_keys.front().value; 64 | case CurveExtrapolation::CYCLE: 65 | t = fracPart * duration; 66 | break; 67 | case CurveExtrapolation::MIRROR: 68 | if(int(intPart) % 2 != 0) { 69 | fracPart = 1.0f - fracPart; 70 | } 71 | t = fracPart * duration; 72 | break; 73 | case CurveExtrapolation::LINEAR: 74 | return GetSegmentValue(t, m_keys[0], m_keys[1]); 75 | } 76 | } else if(t > endTime) { 77 | auto fracPart = static_cast(modf((t - startTime) / duration, &intPart)); 78 | switch(m_extrapolationType) { 79 | case CurveExtrapolation::CLAMP: 80 | return m_keys.back().value; 81 | case CurveExtrapolation::CYCLE: 82 | t = fracPart * duration; 83 | break; 84 | case CurveExtrapolation::MIRROR: 85 | if(int(intPart) % 2 != 0) { 86 | fracPart = 1.0f - fracPart; 87 | } 88 | t = fracPart * duration; 89 | break; 90 | case CurveExtrapolation::LINEAR: 91 | return GetSegmentValue(t, m_keys[n-2], m_keys[n-1]); 92 | } 93 | } 94 | 95 | for(int i = 0; i < n - 1; ++i) { 96 | auto& k0 = m_keys[i]; 97 | auto& k1 = m_keys[i + 1]; 98 | if(t >= k0.time && t <= k1.time) { 99 | return GetSegmentValue(t, k0, k1); 100 | } 101 | } 102 | 103 | return T(); 104 | } 105 | 106 | int GetNumKeys() const { 107 | return static_cast(m_keys.size()); 108 | } 109 | 110 | void AddKey(float t, const T& v) { 111 | m_keys.push_back({t, v}); 112 | 113 | std::sort(m_keys.begin(), m_keys.end()); 114 | } 115 | 116 | CurveExtrapolation::Type GetExtrapolationType() const { 117 | return m_extrapolationType; 118 | } 119 | 120 | void SetExtrapolationType(CurveExtrapolation::Type et) { 121 | m_extrapolationType = et; 122 | } 123 | 124 | protected: 125 | std::vector> m_keys; 126 | 127 | T GetSegmentValue(float t, const CurveKey &k0, const CurveKey &k1) const { 128 | auto p = (t - k0.time) / (k1.time - k0.time); 129 | auto value = k0.value + p*(k1.value - k0.value); 130 | return value; 131 | } 132 | 133 | CurveExtrapolation::Type m_extrapolationType = CurveExtrapolation::CLAMP; 134 | }; 135 | 136 | 137 | 138 | #endif //VULKAN_SPRITES_CURVESCALAR_H 139 | -------------------------------------------------------------------------------- /resources/montserrat/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012, Julieta Ulanovsky (julieta.ulanovsky@gmail.com), with Reserved Font Names 'Montserrat' 2 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 3 | This license is copied below, and is also available with a FAQ at: 4 | http://scripts.sil.org/OFL 5 | 6 | 7 | ----------------------------------------------------------- 8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 9 | ----------------------------------------------------------- 10 | 11 | PREAMBLE 12 | The goals of the Open Font License (OFL) are to stimulate worldwide 13 | development of collaborative font projects, to support the font creation 14 | efforts of academic and linguistic communities, and to provide a free and 15 | open framework in which fonts may be shared and improved in partnership 16 | with others. 17 | 18 | The OFL allows the licensed fonts to be used, studied, modified and 19 | redistributed freely as long as they are not sold by themselves. The 20 | fonts, including any derivative works, can be bundled, embedded, 21 | redistributed and/or sold with any software provided that any reserved 22 | names are not used by derivative works. The fonts and derivatives, 23 | however, cannot be released under any other type of license. The 24 | requirement for fonts to remain under this license does not apply 25 | to any document created using the fonts or their derivatives. 26 | 27 | DEFINITIONS 28 | "Font Software" refers to the set of files released by the Copyright 29 | Holder(s) under this license and clearly marked as such. This may 30 | include source files, build scripts and documentation. 31 | 32 | "Reserved Font Name" refers to any names specified as such after the 33 | copyright statement(s). 34 | 35 | "Original Version" refers to the collection of Font Software components as 36 | distributed by the Copyright Holder(s). 37 | 38 | "Modified Version" refers to any derivative made by adding to, deleting, 39 | or substituting -- in part or in whole -- any of the components of the 40 | Original Version, by changing formats or by porting the Font Software to a 41 | new environment. 42 | 43 | "Author" refers to any designer, engineer, programmer, technical 44 | writer or other person who contributed to the Font Software. 45 | 46 | PERMISSION & CONDITIONS 47 | Permission is hereby granted, free of charge, to any person obtaining 48 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 49 | redistribute, and sell modified and unmodified copies of the Font 50 | Software, subject to the following conditions: 51 | 52 | 1) Neither the Font Software nor any of its individual components, 53 | in Original or Modified Versions, may be sold by itself. 54 | 55 | 2) Original or Modified Versions of the Font Software may be bundled, 56 | redistributed and/or sold with any software, provided that each copy 57 | contains the above copyright notice and this license. These can be 58 | included either as stand-alone text files, human-readable headers or 59 | in the appropriate machine-readable metadata fields within text or 60 | binary files as long as those fields can be easily viewed by the user. 61 | 62 | 3) No Modified Version of the Font Software may use the Reserved Font 63 | Name(s) unless explicit written permission is granted by the corresponding 64 | Copyright Holder. This restriction only applies to the primary font name as 65 | presented to the users. 66 | 67 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 68 | Software shall not be used to promote, endorse or advertise any 69 | Modified Version, except to acknowledge the contribution(s) of the 70 | Copyright Holder(s) and the Author(s) or with their explicit written 71 | permission. 72 | 73 | 5) The Font Software, modified or unmodified, in part or in whole, 74 | must be distributed entirely under this license, and must not be 75 | distributed under any other license. The requirement for fonts to 76 | remain under this license does not apply to any document created 77 | using the Font Software. 78 | 79 | TERMINATION 80 | This license becomes null and void if any of the above conditions are 81 | not met. 82 | 83 | DISCLAIMER 84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 88 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 92 | OTHER DEALINGS IN THE FONT SOFTWARE. 93 | -------------------------------------------------------------------------------- /engine/Curve_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 26.10.2018. 3 | // 4 | 5 | #include 6 | #include 7 | #include "catch.hpp" 8 | #include "Curve.h" 9 | 10 | TEST_CASE("Curves") { 11 | SECTION("Curve") { 12 | SECTION("float") { 13 | Curve c; 14 | 15 | SECTION("empty curve") { 16 | REQUIRE(c.GetNumKeys() == 0); 17 | REQUIRE(c.GetValue(0) == 0); 18 | REQUIRE(c.GetExtrapolationType() == CurveExtrapolation::CLAMP); 19 | } 20 | 21 | SECTION("single key") { 22 | c.AddKey(0, 3.14f); 23 | REQUIRE(c.GetNumKeys() == 1); 24 | REQUIRE(c.GetValue(0) == 3.14f); 25 | } 26 | 27 | SECTION("two keys, linear interpolation") { 28 | c.AddKey(0, 1.0f); 29 | c.AddKey(1, 2.0f); 30 | REQUIRE(c.GetNumKeys() == 2); 31 | REQUIRE(c.GetValue(0.5f) == Approx(1.5f)); 32 | } 33 | 34 | SECTION("three keys") { 35 | c.AddKey(0, 1.0f); 36 | c.AddKey(1, 2.0f); 37 | c.AddKey(2, -1.0f); 38 | 39 | REQUIRE(c.GetNumKeys() == 3); 40 | REQUIRE(c.GetValue(1.5f) == Approx(0.5f)); 41 | } 42 | 43 | SECTION("three keys added out of order") { 44 | c.AddKey(1, 2.0f); 45 | c.AddKey(0, 1.0f); 46 | c.AddKey(2, -1.0f); 47 | 48 | REQUIRE(c.GetNumKeys() == 3); 49 | REQUIRE(c.GetValue(1.5f) == Approx(0.5f)); 50 | } 51 | 52 | SECTION("extrapolation") { 53 | c.AddKey(0, 1.0f); 54 | c.AddKey(1, 2.0f); 55 | c.AddKey(2, -1.0f); 56 | 57 | SECTION("clamp") { 58 | c.SetExtrapolationType(CurveExtrapolation::CLAMP); 59 | REQUIRE(c.GetValue(-1.0f) == 1.0f ); 60 | REQUIRE(c.GetValue(3.0f) == -1.0f ); 61 | } 62 | 63 | SECTION("cycle") { 64 | c.SetExtrapolationType(CurveExtrapolation::CYCLE); 65 | REQUIRE(c.GetValue(-1.0f) == 2.0f ); 66 | REQUIRE(c.GetValue(3.0f) == 2.0f ); 67 | REQUIRE(c.GetValue(2.5f) == 1.5f ); 68 | } 69 | 70 | SECTION("mirror") { 71 | c.SetExtrapolationType(CurveExtrapolation::MIRROR); 72 | REQUIRE(c.GetValue(-1.0f) == 2.0f ); 73 | REQUIRE(c.GetValue(3.0f) == 2.0f ); 74 | REQUIRE(c.GetValue(2.5f) == 0.5f ); 75 | } 76 | 77 | SECTION("linear") { 78 | c.SetExtrapolationType(CurveExtrapolation::LINEAR); 79 | REQUIRE(c.GetValue(-1.0f) == 0.0f ); 80 | REQUIRE(c.GetValue(3.0f) == -4.0f ); 81 | } 82 | 83 | } 84 | } 85 | 86 | SECTION("vec2") { 87 | Curve c; 88 | 89 | SECTION("empty curve") { 90 | REQUIRE(c.GetNumKeys() == 0); 91 | REQUIRE(c.GetExtrapolationType() == CurveExtrapolation::CLAMP); 92 | 93 | auto value = c.GetValue(0); 94 | REQUIRE(value.x == 0.0f); 95 | REQUIRE(value.y == 0.0f); 96 | } 97 | 98 | SECTION("single key") { 99 | c.AddKey(0, glm::vec2 {3.14f, 42.0f}); 100 | 101 | REQUIRE(c.GetNumKeys() == 1); 102 | 103 | auto value = c.GetValue(0); 104 | REQUIRE(value.x == 3.14f); 105 | REQUIRE(value.y == 42.0f); 106 | } 107 | 108 | } 109 | 110 | SECTION("vec3") { 111 | Curve c; 112 | 113 | SECTION("empty curve") { 114 | REQUIRE(c.GetNumKeys() == 0); 115 | REQUIRE(c.GetExtrapolationType() == CurveExtrapolation::CLAMP); 116 | 117 | auto value = c.GetValue(0); 118 | REQUIRE(value.x == 0.0f); 119 | REQUIRE(value.y == 0.0f); 120 | REQUIRE(value.z == 0.0f); 121 | } 122 | 123 | SECTION("single key") { 124 | c.AddKey(0, glm::vec3 {3.14f, 42.0f, -1.0f}); 125 | 126 | REQUIRE(c.GetNumKeys() == 1); 127 | 128 | auto value = c.GetValue(0); 129 | REQUIRE(value.x == 3.14f); 130 | REQUIRE(value.y == 42.0f); 131 | REQUIRE(value.z == -1.0f); 132 | } 133 | 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /engine/Logger.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 19.9.2018. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "Logger.h" 10 | 11 | namespace 12 | { 13 | 14 | enum MessageType 15 | { 16 | CONNECTION_MESSAGE, 17 | SIMPLE_MESSAGE, 18 | LARGE_MESSAGE, 19 | CONTINUATION_MESSAGE, 20 | CONTINUATION_END_MESSAGE, 21 | }; 22 | 23 | template 24 | void fillString(char (&destination)[size], const std::string& src) 25 | { 26 | strncpy(destination, src.data(), src.size()); 27 | if (src.size() < size) 28 | { 29 | destination[src.size()] = 0; 30 | } 31 | else 32 | { 33 | destination[size - 1] = 0; 34 | } 35 | } 36 | 37 | } 38 | 39 | void Logger::connectToHost() { 40 | char hostname[1024]; 41 | gethostname(hostname, 1024); 42 | 43 | char exename[1024]; 44 | auto result = readlink("/proc/self/exe", exename, 1024); 45 | exename[result + 1] = 0; 46 | 47 | connectToHost("127.0.0.1", getpid(), hostname, exename); 48 | } 49 | 50 | void Logger::connectToHost( 51 | const std::string &server, 52 | int64_t pid, 53 | const std::string &machineName, 54 | const std::string &executablePath) 55 | { 56 | m_state = Disconnected; 57 | m_machineName = machineName; 58 | m_pid = pid; 59 | m_executablePath = executablePath; 60 | 61 | m_socket = socket(AF_INET, SOCK_STREAM, 0); 62 | if(m_socket < 0) { 63 | return; 64 | } 65 | 66 | struct sockaddr_in serv_addr{}; 67 | 68 | serv_addr.sin_family = AF_INET; 69 | serv_addr.sin_port = htons(0xCC9); 70 | 71 | if(inet_pton(AF_INET, server.c_str(), &serv_addr.sin_addr) <= 0) 72 | { 73 | return; 74 | } 75 | 76 | if( connect(m_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) 77 | { 78 | return; 79 | } 80 | 81 | RawLogMessage msg{}; 82 | msg.type = CONNECTION_MESSAGE; 83 | msg.connection.pid = m_pid; 84 | msg.connection.version = 2; 85 | fillString(msg.connection.machineName, m_machineName); 86 | fillString(msg.connection.executablePath, m_executablePath); 87 | 88 | send(m_socket, reinterpret_cast(&msg), sizeof(msg), 0); 89 | 90 | m_state = Connected; 91 | } 92 | 93 | void Logger::log( 94 | Logger::LogSeverity severity, 95 | const std::string &module, 96 | const std::string &channel, 97 | const std::string &message) 98 | { 99 | if (m_state == Disconnected) 100 | { 101 | return; 102 | } 103 | 104 | RawLogMessage msg{}; 105 | 106 | struct timespec tp{}; 107 | clockid_t clk_id = CLOCK_REALTIME; 108 | clock_gettime(clk_id, &tp); 109 | 110 | msg.text.timestamp = static_cast(tp.tv_sec) * 1000 + tp.tv_nsec / 1000000; 111 | msg.text.severity = severity; 112 | fillString(msg.text.module, module); 113 | fillString(msg.text.channel, channel); 114 | 115 | if (message.size() >= TextMessage::TEXT_SIZE) 116 | { 117 | msg.type = LARGE_MESSAGE; 118 | } 119 | else 120 | { 121 | msg.type = SIMPLE_MESSAGE; 122 | } 123 | int offset = 0; 124 | do 125 | { 126 | strncpy(msg.text.message, message.data() + offset, TextMessage::TEXT_SIZE - 1); 127 | msg.text.message[TextMessage::TEXT_SIZE - 1] = 0; 128 | offset += TextMessage::TEXT_SIZE - 1; 129 | 130 | if (m_state == Connected) 131 | { 132 | send(m_socket, reinterpret_cast(&msg), sizeof(msg), 0); 133 | } 134 | if (offset + TextMessage::TEXT_SIZE < message.size()) 135 | { 136 | msg.type = CONTINUATION_MESSAGE; 137 | } 138 | else 139 | { 140 | msg.type = CONTINUATION_END_MESSAGE; 141 | } 142 | } 143 | while (offset < message.size()); 144 | } 145 | 146 | void Logger::disconnnect() { 147 | close(m_socket); 148 | m_state = Disconnected; 149 | m_socket = 0; 150 | } 151 | 152 | Logger *Logger::instance() { 153 | static Logger* s_instance = nullptr; 154 | if(!s_instance) { 155 | s_instance = new Logger; 156 | s_instance->connectToHost(); 157 | } 158 | return s_instance; 159 | } 160 | 161 | std::shared_ptr GetLogger(const char *name) { 162 | auto logger = spdlog::get(name); 163 | if(logger) { 164 | return logger; 165 | } 166 | auto sink = std::make_shared(); 167 | logger = std::make_shared(name, sink); 168 | logger->set_level(spdlog::level::debug); 169 | spdlog::register_logger(logger); 170 | return logger; 171 | } 172 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(vulkan-sprites) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | link_libraries(glfw) 7 | link_libraries(vulkan) 8 | 9 | 10 | # macOS specific settings 11 | # Needs MoltenVK, plus brew installed packages go in a different location 12 | if(APPLE) 13 | set(VULKAN_DIR $ENV{HOME}/vulkansdk-macos-1.1.82.0/macOS) 14 | include_directories(${VULKAN_DIR}/include) 15 | link_directories(${VULKAN_DIR}/lib) 16 | link_libraries(MoltenVK) 17 | include_directories(/usr/local/include) 18 | link_directories(/usr/local/lib) 19 | elseif(UNIX) 20 | set(VULKAN_DIR "$ENV{HOME}/vulkansdk-linux-1.1.82.1/x86_64") 21 | include_directories($ENV{HOME}/telemetry/3.0.1.1461/include) 22 | link_directories($ENV{HOME}/telemetry/3.0.1.1461/lib) 23 | link_libraries(rad_tm_linux_x64) 24 | add_compile_definitions(TELEMETRY_ENABLED=1) 25 | endif(UNIX) 26 | 27 | 28 | set(GLSL_VALIDATOR "${VULKAN_DIR}/bin/glslangValidator") 29 | 30 | add_subdirectory(engine) 31 | 32 | set(THREADS_PREFER_PTHREAD_FLAG ON) 33 | find_package(Threads REQUIRED) 34 | 35 | find_package(Freetype REQUIRED) 36 | include_directories(${FREETYPE_INCLUDE_DIRS}) 37 | 38 | include_directories(engine) 39 | include_directories(extern) 40 | 41 | add_executable( 42 | first 43 | samples/first/main.cpp 44 | samples/first/FirstApp.cpp samples/first/FirstApp.h 45 | ) 46 | target_link_libraries(first engine) 47 | target_link_libraries(first Threads::Threads) 48 | 49 | add_executable( 50 | second 51 | samples/second/main.cpp 52 | samples/second/SecondApp.cpp 53 | samples/second/SecondApp.h 54 | ) 55 | target_link_libraries(second engine) 56 | target_link_libraries(second Threads::Threads) 57 | 58 | add_executable( 59 | text 60 | samples/text/main.cpp 61 | samples/text/TextApp.cpp samples/text/TextApp.h) 62 | target_link_libraries(text engine Threads::Threads ${FREETYPE_LIBRARIES}) 63 | 64 | add_executable( 65 | textureatlas 66 | samples/textureatlas/main.cpp 67 | samples/textureatlas/TextureAtlasApp.cpp 68 | samples/textureatlas/TextureAtlasApp.h 69 | ) 70 | target_link_libraries(textureatlas engine) 71 | target_link_libraries(textureatlas Threads::Threads stdc++fs) 72 | 73 | add_executable( 74 | blendmodes 75 | samples/blendmodes/main.cpp 76 | samples/blendmodes/BlendModesApp.cpp 77 | samples/blendmodes/BlendModesApp.h 78 | ) 79 | target_link_libraries(blendmodes engine Threads::Threads ${FREETYPE_LIBRARIES}) 80 | 81 | add_library(breakoutlib 82 | samples/breakout/BreakOutApp.cpp 83 | samples/breakout/BreakOutApp.h 84 | samples/breakout/AppState.cpp 85 | samples/breakout/AppState.h 86 | samples/breakout/SplashScreen.cpp 87 | samples/breakout/SplashScreen.h 88 | samples/breakout/MainMenu.cpp 89 | samples/breakout/MainMenu.h 90 | samples/breakout/Gameplay.cpp 91 | samples/breakout/Gameplay.h 92 | samples/breakout/GameOver.cpp 93 | samples/breakout/GameOver.h 94 | samples/breakout/Movable.cpp 95 | samples/breakout/Movable.h 96 | samples/breakout/Paddle.cpp 97 | samples/breakout/Paddle.h 98 | samples/breakout/Ball.cpp 99 | samples/breakout/Ball.h samples/breakout/BrickCollection.cpp samples/breakout/BrickCollection.h) 100 | 101 | add_executable( 102 | breakout 103 | samples/breakout/main.cpp 104 | ) 105 | target_link_libraries(breakout breakoutlib engine ${FREETYPE_LIBRARIES}) 106 | 107 | add_executable( 108 | breakout_test 109 | samples/breakout/test_main.cpp 110 | samples/breakout/Paddle_test.cpp 111 | samples/breakout/Ball_test.cpp 112 | samples/breakout/BrickCollection_test.cpp) 113 | target_link_libraries(breakout_test breakoutlib engine ${FREETYPE_LIBRARIES}) 114 | 115 | add_executable( 116 | particles 117 | samples/particles/main.cpp 118 | samples/particles/ParticlesApp.cpp samples/particles/ParticlesApp.h) 119 | target_link_libraries(particles engine ${FREETYPE_LIBRARIES}) 120 | 121 | add_executable( 122 | curves 123 | samples/curves/main.cpp samples/curves/CurvesApp.cpp samples/curves/CurvesApp.h) 124 | target_link_libraries(curves engine ${FREETYPE_LIBRARIES}) 125 | 126 | add_executable( 127 | engine_test 128 | engine/test_main.cpp 129 | engine/Area_test.cpp 130 | engine/AreaAllocator_test.cpp 131 | engine/AreaList_test.cpp 132 | engine/Renderer_test.cpp 133 | engine/TextureAtlas_test.cpp 134 | engine/FontManager_test.cpp 135 | engine/ParticleSystem_test.cpp 136 | engine/Curve_test.cpp) 137 | target_link_libraries(engine_test engine ${FREETYPE_LIBRARIES}) 138 | -------------------------------------------------------------------------------- /engine/FontManager_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 10.10.2018. 3 | // 4 | 5 | #include "catch.hpp" 6 | 7 | #define GLFW_INCLUDE_VULKAN 8 | #include 9 | #include "Renderer.h" 10 | #include "FontManager.h" 11 | #include "Font.h" 12 | 13 | static const char *const FONTNAME = "resources/montserrat/Montserrat-Bold.ttf"; 14 | 15 | TEST_CASE("FontManager") { 16 | 17 | glfwInit(); 18 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 19 | auto window = glfwCreateWindow(800, 600, "FontManagerTest", nullptr, nullptr); 20 | 21 | Renderer r; 22 | r.Initialize(window, Renderer::ENABLE_VALIDATION); 23 | auto textureAtlas = r.CreateTextureAtlas(256, 256); 24 | 25 | SECTION("constructor") { 26 | FontManager fm(textureAtlas); 27 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 28 | } 29 | 30 | SECTION("GetFont") { 31 | FontManager fm(textureAtlas); 32 | 33 | auto font = fm.GetFont(FONTNAME, 16); 34 | REQUIRE(font); 35 | } 36 | 37 | SECTION("Font") { 38 | FontManager fm(textureAtlas); 39 | auto font = fm.GetFont(FONTNAME, 16); 40 | 41 | SECTION("GetNumGlyphs") { 42 | auto n = font->GetNumGlyphs(); 43 | REQUIRE(n == 263); 44 | } 45 | 46 | SECTION("GetGlyph") { 47 | SECTION("regular") { 48 | auto glyph = font->GetGlyph('a'); 49 | REQUIRE(glyph); 50 | 51 | SECTION("GetLeft") { 52 | REQUIRE(glyph->GetLeft() == 0); 53 | } 54 | 55 | SECTION("GetTop") { 56 | REQUIRE(glyph->GetTop() == 9); 57 | } 58 | 59 | SECTION("GetWidth") { 60 | REQUIRE(glyph->GetWidth() == 9); 61 | } 62 | 63 | SECTION("GetTexture") { 64 | auto tex = glyph->GetTexture(); 65 | REQUIRE(tex); 66 | REQUIRE(tex->GetWidth() == 9); 67 | REQUIRE(tex->GetHeight() == 10); 68 | } 69 | } 70 | 71 | SECTION("space") { 72 | auto glyph = font->GetGlyph(' '); 73 | REQUIRE(glyph); 74 | 75 | SECTION("GetLeft") { 76 | REQUIRE(glyph->GetLeft() == 0); 77 | } 78 | 79 | SECTION("GetTop") { 80 | REQUIRE(glyph->GetTop() == 0); 81 | } 82 | 83 | SECTION("GetWidth") { 84 | REQUIRE(glyph->GetWidth() == 0); 85 | } 86 | 87 | SECTION("GetTexture") { 88 | auto tex = glyph->GetTexture(); 89 | REQUIRE(!tex); 90 | } 91 | } 92 | } 93 | 94 | SECTION("Measure") { 95 | auto m = font->Measure("This is a test"); 96 | REQUIRE(m.width == 100); 97 | REQUIRE(m.height == 26); 98 | } 99 | 100 | SECTION("Render") { 101 | SECTION("simple") { 102 | r.StartFrame(); 103 | font->Draw(r, 0, 128, "This is a test"); 104 | r.EndFrame(); 105 | r.WaitUntilDeviceIdle(); 106 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 107 | } 108 | 109 | SECTION("multiple frames") { 110 | r.StartFrame(); 111 | font->Draw(r, 0, 128, "This is a test"); 112 | r.EndFrame(); 113 | r.StartFrame(); 114 | font->Draw(r, 0, 128, "This is a test"); 115 | r.EndFrame(); 116 | r.StartFrame(); 117 | font->Draw(r, 0, 128, "This is a test"); 118 | r.EndFrame(); 119 | r.StartFrame(); 120 | font->Draw(r, 0, 128, "This is a test"); 121 | r.EndFrame(); 122 | r.StartFrame(); 123 | font->Draw(r, 0, 128, "This is a test"); 124 | r.EndFrame(); 125 | r.WaitUntilDeviceIdle(); 126 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 127 | } 128 | 129 | SECTION("two fonts, multiple frames") { 130 | auto font1 = fm.GetFont(FONTNAME, 16); 131 | auto font2 = fm.GetFont(FONTNAME, 32); 132 | 133 | r.StartFrame(); 134 | font1->Draw(r, 0, 128, "This is a test"); 135 | font2->Draw(r, 0, 256, "This is also"); 136 | font2->Draw(r, 0, 384, " a test"); 137 | r.EndFrame(); 138 | r.StartFrame(); 139 | font1->Draw(r, 0, 128, "This is a test"); 140 | font2->Draw(r, 0, 256, "This is also a test"); 141 | r.EndFrame(); 142 | r.StartFrame(); 143 | font1->Draw(r, 0, 128, "This is a test"); 144 | font2->Draw(r, 0, 256, "This is also a test"); 145 | r.EndFrame(); 146 | r.StartFrame(); 147 | font1->Draw(r, 0, 128, "This is a test"); 148 | font2->Draw(r, 0, 256, "This is also a test"); 149 | r.EndFrame(); 150 | r.StartFrame(); 151 | font1->Draw(r, 0, 128, "This is a test"); 152 | font2->Draw(r, 0, 256, "This is also a test"); 153 | r.EndFrame(); 154 | r.WaitUntilDeviceIdle(); 155 | 156 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 157 | } 158 | } 159 | } 160 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 161 | 162 | glfwDestroyWindow(window); 163 | } 164 | -------------------------------------------------------------------------------- /engine/ParticleEmitter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // 4 | // Created by snorri on 25.10.2018. 5 | // 6 | 7 | #include "ParticleSystem.h" 8 | #include "ParticleEmitter.h" 9 | #include "Renderer.h" 10 | 11 | float ParticleEmitter::GetEmissionRate() const { 12 | return m_emissionRate; 13 | } 14 | 15 | void ParticleEmitter::SetEmissionRate(float rate) { 16 | m_emissionRate = rate; 17 | } 18 | 19 | int ParticleEmitter::GetNumParticles() const { 20 | return static_cast(m_particles.size()); 21 | } 22 | 23 | float ParticleEmitter::GetLifespan() const { 24 | return m_lifespan; 25 | } 26 | 27 | void ParticleEmitter::SetLifespan(float ls) { 28 | m_lifespan = ls; 29 | } 30 | 31 | glm::vec2 ParticleEmitter::GetPosition() const { 32 | return m_position; 33 | } 34 | 35 | void ParticleEmitter::SetPosition(glm::vec2 pos) { 36 | static glm::vec2 initialValue {std::numeric_limits::max(), std::numeric_limits::max()}; 37 | if(m_position == initialValue) { 38 | m_lastPosition = pos; 39 | } else { 40 | m_lastPosition = m_position; 41 | } 42 | m_position = pos; 43 | } 44 | 45 | void ParticleEmitter::Update(float td) { 46 | std::vector::iterator> dead; 47 | 48 | m_numParticlesAlive = 0; 49 | for(auto pIt = m_particles.begin(); pIt != m_particles.end(); ++pIt) { 50 | pIt->timeRemaining -= td; 51 | if(pIt->timeRemaining <= 0.0f) { 52 | pIt->timeRemaining = 0.0f; 53 | dead.emplace_back(pIt); 54 | } else { 55 | pIt->position += pIt->velocity*td; 56 | pIt->color = glm::vec4 {1.0f, 1.0f, 1.0f, 1.0f} * pIt->timeRemaining; 57 | m_numParticlesAlive++; 58 | } 59 | } 60 | 61 | if(m_timeToEmit <= 0.0f) { 62 | glm::vec2 d = m_lastPosition - m_position; 63 | auto deadIt = dead.begin(); 64 | while(m_timeToEmit < td) { 65 | Particle* p; 66 | if(deadIt != dead.end()) { 67 | p = &(**deadIt++); 68 | } else { 69 | auto& newParticle = m_particles.emplace_back(); 70 | p = &newParticle; 71 | } 72 | p->timeRemaining = m_lifespan - m_timeToEmit; 73 | float x = cosf(m_direction + m_directionRange * m_rng(m_random_engine)); 74 | float y = sinf(m_direction + m_directionRange * m_rng(m_random_engine)); 75 | p->velocity = glm::vec2 {m_speed * x, m_speed * y}; 76 | p->position = m_position; 77 | float relativeAge = m_timeToEmit / td; 78 | p->position += relativeAge * d; 79 | p->color = glm::vec4 {1.0f, 1.0f, 1.0f, 1.0f}; 80 | 81 | m_numParticlesAlive++; 82 | m_timeToEmit += 1.0f/m_emissionRate; 83 | } 84 | } 85 | m_timeToEmit -= td; 86 | } 87 | 88 | void ParticleEmitter::Render(Renderer &r) { 89 | r.SetBlendMode(BM_ADD); 90 | r.SetTexture(m_texture); 91 | 92 | auto tw = m_texture ? m_texture->GetTextureWindow() : TextureWindow {0, 0, 1, 1}; 93 | 94 | Vertex* vertexWrite = nullptr; 95 | uint16_t* indexWrite = nullptr; 96 | int numIndices = m_numParticlesAlive*6; 97 | int numVertices = m_numParticlesAlive*4; 98 | uint16_t baseIndex = 0; 99 | if(r.ReserveTriangles(numIndices, numVertices, indexWrite, vertexWrite, baseIndex)) { 100 | uint16_t localBaseIndex = 0; 101 | 102 | for(auto& p: m_particles) { 103 | if(p.timeRemaining > 0.0f) { 104 | 105 | *indexWrite++ = 0 + localBaseIndex + baseIndex; 106 | *indexWrite++ = 1 + localBaseIndex + baseIndex; 107 | *indexWrite++ = 3 + localBaseIndex + baseIndex; 108 | *indexWrite++ = 3 + localBaseIndex + baseIndex; 109 | *indexWrite++ = 1 + localBaseIndex + baseIndex; 110 | *indexWrite++ = 2 + localBaseIndex + baseIndex; 111 | 112 | vertexWrite->pos.x = p.position.x - 8.0f; 113 | vertexWrite->pos.y = p.position.y - 8.0f; 114 | vertexWrite->color = p.color; 115 | vertexWrite->texCoord.x = tw.x0; 116 | vertexWrite->texCoord.y = tw.y0; 117 | vertexWrite->blendMode = BM_ADD; 118 | vertexWrite++; 119 | 120 | vertexWrite->pos.x = p.position.x + 8.0f; 121 | vertexWrite->pos.y = p.position.y - 8.0f; 122 | vertexWrite->color = p.color; 123 | vertexWrite->texCoord.x = tw.x1; 124 | vertexWrite->texCoord.y = tw.y0; 125 | vertexWrite->blendMode = BM_ADD; 126 | vertexWrite++; 127 | 128 | vertexWrite->pos.x = p.position.x + 8.0f; 129 | vertexWrite->pos.y = p.position.y + 8.0f; 130 | vertexWrite->color = p.color; 131 | vertexWrite->texCoord.x = tw.x1; 132 | vertexWrite->texCoord.y = tw.y1; 133 | vertexWrite->blendMode = BM_ADD; 134 | vertexWrite++; 135 | 136 | vertexWrite->pos.x = p.position.x - 8.0f; 137 | vertexWrite->pos.y = p.position.y + 8.0f; 138 | vertexWrite->color = p.color; 139 | vertexWrite->texCoord.x = tw.x0; 140 | vertexWrite->texCoord.y = tw.y1; 141 | vertexWrite->blendMode = BM_ADD; 142 | vertexWrite++; 143 | 144 | localBaseIndex += 4; 145 | } 146 | } 147 | 148 | r.CommitTriangles(numIndices, numVertices); 149 | } 150 | } 151 | 152 | float ParticleEmitter::GetDirection() const { 153 | return m_direction; 154 | } 155 | 156 | void ParticleEmitter::SetDirection(float a) { 157 | m_direction = a; 158 | } 159 | 160 | float ParticleEmitter::GetDirectionRange() const { 161 | return m_directionRange; 162 | } 163 | 164 | void ParticleEmitter::SetDirectionRange(float r) { 165 | m_directionRange = r; 166 | } 167 | 168 | float ParticleEmitter::GetSpeed() const { 169 | return m_speed; 170 | } 171 | 172 | void ParticleEmitter::SetSpeed(float s) { 173 | m_speed = s; 174 | } 175 | 176 | std::shared_ptr ParticleEmitter::GetTexture() const { 177 | return m_texture; 178 | } 179 | 180 | void ParticleEmitter::SetTexture(std::shared_ptr tex) { 181 | m_texture = std::move(tex); 182 | } 183 | 184 | -------------------------------------------------------------------------------- /samples/breakout/Ball_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 15.10.2018. 3 | // 4 | 5 | #include "catch.hpp" 6 | #include 7 | #include "Ball.h" 8 | #include "Paddle.h" 9 | #include "BrickCollection.h" 10 | 11 | TEST_CASE("Ball") { 12 | SECTION("constructor") { 13 | Ball b; 14 | } 15 | SECTION("SetRadius") { 16 | Ball b; 17 | b.SetRadius(4.0f); 18 | REQUIRE(b.GetRadius() == 4.0f); 19 | } 20 | 21 | SECTION("SetPosition") { 22 | Ball b; 23 | 24 | b.SetPosition({3.14f, 42.0f}); 25 | 26 | glm::vec2 expected {3.14f, 42.0f}; 27 | REQUIRE(b.GetPosition() == expected); 28 | } 29 | 30 | SECTION("CollideWithPaddle") { 31 | SECTION("hits") { 32 | Ball b; 33 | b.SetRadius(2.0f); 34 | b.SetPosition({0.0f, 1.5f}); 35 | b.SetVelocity({0.0f, -1.0f}); 36 | 37 | Paddle p; 38 | p.SetPosition({0.0f, 0.0f}); 39 | p.SetWidth(10.0f); 40 | 41 | b.CollideWithPaddle(p); 42 | 43 | REQUIRE(b.GetVelocity().y == 1.0f); 44 | } 45 | SECTION("misses when ball is to the right") { 46 | Ball b; 47 | b.SetRadius(2.0f); 48 | b.SetPosition({50.0f, 1.0f}); 49 | b.SetVelocity({0.0f, 1.0f}); 50 | 51 | Paddle p; 52 | p.SetPosition({0.0f, 0.0f}); 53 | p.SetWidth(10.0f); 54 | 55 | b.CollideWithPaddle(p); 56 | 57 | REQUIRE(b.GetVelocity().y == 1.0f); 58 | } 59 | 60 | SECTION("misses when ball is to the left") { 61 | Ball b; 62 | b.SetRadius(2.0f); 63 | b.SetPosition({-50.0f, 1.0f}); 64 | b.SetVelocity({0.0f, 1.0f}); 65 | 66 | Paddle p; 67 | p.SetPosition({0.0f, 0.0f}); 68 | p.SetWidth(10.0f); 69 | 70 | b.CollideWithPaddle(p); 71 | 72 | REQUIRE(b.GetVelocity().y == 1.0f); 73 | } 74 | 75 | SECTION("misses when ball is above") { 76 | Ball b; 77 | b.SetRadius(2.0f); 78 | b.SetPosition({0.0f, -10.0f}); 79 | b.SetVelocity({0.0f, 1.0f}); 80 | 81 | Paddle p; 82 | p.SetPosition({0.0f, 0.0f}); 83 | p.SetWidth(10.0f); 84 | 85 | b.CollideWithPaddle(p); 86 | 87 | REQUIRE(b.GetVelocity().y == 1.0f); 88 | } 89 | 90 | 91 | SECTION("misses when ball is moving away") { 92 | Ball b; 93 | b.SetRadius(2.0f); 94 | b.SetPosition({0.0f, 1.0f}); 95 | b.SetVelocity({0.0f, 1.0f}); 96 | 97 | Paddle p; 98 | p.SetPosition({0.0f, 0.0f}); 99 | p.SetWidth(10.0f); 100 | 101 | b.CollideWithPaddle(p); 102 | 103 | REQUIRE(b.GetVelocity().y == 1.0f); 104 | } 105 | } 106 | 107 | SECTION("CollideWithBrick") { 108 | Ball b; 109 | b.SetRadius(1.0f); 110 | 111 | SECTION("no hit") { 112 | b.SetPosition({0.0f, 50.0f}); 113 | b.SetVelocity({0.0f, -1.0f}); 114 | 115 | Brick brick {{0.0f, 0.0f}, 10.0f, 10.0f}; 116 | 117 | auto didCollide = b.CollideWithBrick(brick); 118 | 119 | REQUIRE(!didCollide); 120 | REQUIRE(b.GetVelocity().x == 0.0f); 121 | REQUIRE(b.GetVelocity().y == -1.0f); 122 | } 123 | SECTION("hits from above") { 124 | b.SetPosition({0.0f, 5.0f}); 125 | b.SetVelocity({0.0f, -1.0f}); 126 | 127 | Brick brick {{0.0f, 0.0f}, 10.0f, 10.0f}; 128 | 129 | auto didCollide = b.CollideWithBrick(brick); 130 | 131 | REQUIRE(didCollide); 132 | REQUIRE(b.GetVelocity().x == 0.0f); 133 | REQUIRE(b.GetVelocity().y == 1.0f); 134 | } 135 | SECTION("hits from below") { 136 | b.SetPosition({0.0f, 0.0f}); 137 | b.SetVelocity({0.0f, 1.0f}); 138 | 139 | Brick brick {{0.0f, 5.0f}, 10.0f, 10.0f}; 140 | 141 | auto didCollide = b.CollideWithBrick(brick); 142 | 143 | REQUIRE(didCollide); 144 | REQUIRE(b.GetVelocity().x == 0.0f); 145 | REQUIRE(b.GetVelocity().y == -1.0f); 146 | } 147 | SECTION("hits from the left") { 148 | b.SetPosition({0.0f, 0.0f}); 149 | b.SetVelocity({1.0f, 0.0f}); 150 | 151 | Brick brick {{5.0f, 0.0f}, 10.0f, 10.0f}; 152 | 153 | auto didCollide = b.CollideWithBrick(brick); 154 | 155 | REQUIRE(didCollide); 156 | REQUIRE(b.GetVelocity().x == -1.0f); 157 | REQUIRE(b.GetVelocity().y == 0.0f); 158 | } 159 | SECTION("hits from the right") { 160 | b.SetPosition({5.0f, 0.0f}); 161 | b.SetVelocity({-1.0f, 0.0f}); 162 | 163 | Brick brick {{0.0f, 0.0f}, 10.0f, 10.0f}; 164 | 165 | auto didCollide = b.CollideWithBrick(brick); 166 | 167 | REQUIRE(didCollide); 168 | REQUIRE(b.GetVelocity().x == 1.0f); 169 | REQUIRE(b.GetVelocity().y == 0.0f); 170 | } 171 | } 172 | 173 | SECTION("SetBounds") { 174 | Ball b; 175 | b.SetRadius(1.0f); 176 | b.SetBounds(0.0f, 0.0f, 100.0f, 100.0f); 177 | 178 | SECTION("inside bounds") { 179 | b.SetPosition({50.0f, 50.0f}); 180 | b.SetVelocity({1.0f, 1.0f}); 181 | b.Update(1.0f); 182 | 183 | glm::vec2 expected {51.0f, 51.0f}; 184 | REQUIRE(b.GetPosition() == expected); 185 | } 186 | 187 | SECTION("minx") { 188 | b.SetPosition({0.5f, 50.0f}); 189 | b.SetVelocity({-1.0f, 0.0f}); 190 | b.Update(1.0f); 191 | 192 | REQUIRE(b.GetPosition().x == 0.0f); 193 | REQUIRE(b.GetPosition().y == 50.0f); 194 | REQUIRE(b.GetVelocity().x == 1.0f); 195 | REQUIRE(b.GetVelocity().y == 0.0f); 196 | } 197 | 198 | SECTION("maxx") { 199 | b.SetPosition({99.5f, 50.0f}); 200 | b.SetVelocity({1.0f, 0.0f}); 201 | b.Update(1.0f); 202 | 203 | REQUIRE(b.GetPosition().x == 100.0f); 204 | REQUIRE(b.GetPosition().y == 50.0f); 205 | REQUIRE(b.GetVelocity().x == -1.0f); 206 | REQUIRE(b.GetVelocity().y == 0.0f); 207 | } 208 | 209 | SECTION("miny") { 210 | b.SetPosition({50.0f, 0.5f}); 211 | b.SetVelocity({0.0f, -1.0f}); 212 | b.Update(1.0f); 213 | 214 | REQUIRE(b.GetPosition().x == 50.0f); 215 | REQUIRE(b.GetPosition().y == 0.0f); 216 | REQUIRE(b.GetVelocity().x == 0.0f); 217 | REQUIRE(b.GetVelocity().y == 1.0f); 218 | } 219 | 220 | SECTION("maxy") { 221 | b.SetPosition({50.0f, 99.5f}); 222 | b.SetVelocity({0.0f, 1.0f}); 223 | b.Update(1.0f); 224 | 225 | REQUIRE(b.GetPosition().x == 50.0f); 226 | REQUIRE(b.GetPosition().y == 100.0f); 227 | REQUIRE(b.GetVelocity().x == 0.0f); 228 | REQUIRE(b.GetVelocity().y == -1.0f); 229 | } 230 | } 231 | 232 | } 233 | 234 | 235 | -------------------------------------------------------------------------------- /engine/Renderer_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snorri on 25.8.2018. 3 | // 4 | 5 | #include "catch.hpp" 6 | 7 | #define GLFW_INCLUDE_VULKAN 8 | #include 9 | #include "Renderer.h" 10 | #include "Texture.h" 11 | #include "Vertex.h" 12 | 13 | TEST_CASE("Renderer basics") { 14 | SECTION("can create") { 15 | Renderer r; 16 | } 17 | 18 | SECTION("fresh instance is uninitialized") { 19 | Renderer r; 20 | REQUIRE(!r.IsInitialized()); 21 | REQUIRE(!r.GetDebugMessenger()); 22 | } 23 | } 24 | 25 | TEST_CASE("Renderer") { 26 | glfwInit(); 27 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 28 | auto window = glfwCreateWindow(800, 600, "TextureAtlasTest", nullptr, nullptr); 29 | 30 | SECTION("Initialize") { 31 | SECTION("without validation") { 32 | Renderer r; 33 | r.Initialize(window, Renderer::DISABLE_VALIDATION); 34 | REQUIRE(r.IsInitialized()); 35 | REQUIRE(!r.GetDebugMessenger()); 36 | } 37 | 38 | SECTION("with validation") { 39 | Renderer r; 40 | r.Initialize(window, Renderer::ENABLE_VALIDATION); 41 | REQUIRE(r.IsInitialized()); 42 | REQUIRE(r.GetDebugMessenger()); 43 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 44 | } 45 | 46 | SECTION("multiple calls throw") { 47 | Renderer r; 48 | r.Initialize(window, Renderer::ENABLE_VALIDATION); 49 | REQUIRE_THROWS_AS(r.Initialize(window, Renderer::ENABLE_VALIDATION), std::runtime_error); 50 | } 51 | } 52 | 53 | Renderer r; 54 | r.Initialize(window, Renderer::ENABLE_VALIDATION); 55 | 56 | SECTION("SetClearColor") { 57 | glm::vec4 color {0.1f, 0.2f, 0.3f, 0.4f}; 58 | r.SetClearColor(color); 59 | auto returned = r.GetClearColor(); 60 | REQUIRE(returned == color); 61 | 62 | r.WaitUntilDeviceIdle(); 63 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 64 | } 65 | 66 | SECTION("CreateTexture") { 67 | SECTION("simple case") { 68 | auto t = r.CreateTexture("resources/texture.jpg"); 69 | REQUIRE(t); 70 | REQUIRE(t->GetWidth() == 512); 71 | REQUIRE(t->GetHeight() == 512); 72 | 73 | r.WaitUntilDeviceIdle(); 74 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 75 | } 76 | 77 | SECTION("in frame") { 78 | r.StartFrame(); 79 | auto t = r.CreateTexture("resources/texture.jpg"); 80 | r.EndFrame(); 81 | 82 | REQUIRE(t); 83 | REQUIRE(t->GetWidth() == 512); 84 | REQUIRE(t->GetHeight() == 512); 85 | 86 | r.WaitUntilDeviceIdle(); 87 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 88 | } 89 | } 90 | 91 | SECTION("StartFrame") { 92 | r.StartFrame(); 93 | r.EndFrame(); 94 | REQUIRE(r.GetNumDrawCommands() == 0); 95 | 96 | r.WaitUntilDeviceIdle(); 97 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 98 | } 99 | 100 | SECTION("DrawSprite") { 101 | SECTION("throws outside of frame") { 102 | REQUIRE_THROWS_AS(r.DrawSprite(0, 0, 100, 100), std::runtime_error); 103 | } 104 | 105 | SECTION("throws outside of frame (after EndFrame)") { 106 | r.StartFrame(); 107 | r.EndFrame(); 108 | REQUIRE_THROWS_AS(r.DrawSprite(0, 0, 100, 100), std::runtime_error); 109 | 110 | r.WaitUntilDeviceIdle(); 111 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 112 | } 113 | 114 | SECTION("simple case") { 115 | r.StartFrame(); 116 | r.DrawSprite(0, 0, 100, 100); 117 | 118 | REQUIRE(r.GetNumIndices() == 6); 119 | REQUIRE(r.GetNumVertices() == 4); 120 | 121 | r.EndFrame(); 122 | REQUIRE(r.GetNumDrawCommands() == 1); 123 | 124 | r.WaitUntilDeviceIdle(); 125 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 126 | } 127 | 128 | SECTION("multiple sprites, one draw command") { 129 | r.StartFrame(); 130 | r.DrawSprite(0, 0, 100, 100); 131 | r.DrawSprite(10, 10, 100, 100); 132 | r.DrawSprite(20, 20, 100, 100); 133 | r.EndFrame(); 134 | 135 | REQUIRE(r.GetNumDrawCommands() == 1); 136 | 137 | r.WaitUntilDeviceIdle(); 138 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 139 | } 140 | 141 | SECTION("one draw command per texture switch") { 142 | auto t1 = r.CreateTexture("resources/1.png"); 143 | auto t2 = r.CreateTexture("resources/2.png"); 144 | 145 | r.StartFrame(); 146 | r.SetTexture(t1); 147 | r.DrawSprite(0, 0, 100, 100); 148 | r.DrawSprite(0, 100, 100, 100); 149 | r.SetTexture(t2); 150 | r.DrawSprite(10, 10, 100, 100); 151 | r.DrawSprite(10, 110, 100, 100); 152 | r.SetTexture(t1); 153 | r.DrawSprite(0, 0, 100, 100); 154 | r.DrawSprite(0, 100, 100, 100); 155 | r.EndFrame(); 156 | 157 | REQUIRE(r.GetNumDrawCommands() == 3); 158 | 159 | r.WaitUntilDeviceIdle(); 160 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 161 | } 162 | } 163 | 164 | SECTION("DrawTriangles") { 165 | SECTION("simple case") { 166 | std::array indices {0, 1, 3, 3, 1, 2}; 167 | std::array vertices { 168 | { 169 | {{ 0, 0}, {1, 1, 1, 1}, {0, 0}}, 170 | {{ 0, 100}, {1, 1, 1, 1}, {0, 1}}, 171 | {{100, 100}, {1, 1, 1, 1}, {1, 1}}, 172 | {{100, 0}, {1, 1, 1, 1}, {1, 0}}, 173 | } 174 | }; 175 | 176 | r.StartFrame(); 177 | r.DrawTriangles(indices.data(), indices.size(), vertices.data(), vertices.size()); 178 | r.EndFrame(); 179 | 180 | REQUIRE(r.GetNumDrawCommands() == 1); 181 | 182 | r.WaitUntilDeviceIdle(); 183 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 184 | } 185 | } 186 | 187 | SECTION("SetTexture") { 188 | SECTION("simple case") { 189 | auto t = r.CreateTexture("resources/texture.jpg"); 190 | 191 | r.StartFrame(); 192 | r.SetTexture(t); 193 | r.DrawSprite(0, 0, 100, 100); 194 | r.EndFrame(); 195 | 196 | r.WaitUntilDeviceIdle(); 197 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 198 | } 199 | 200 | SECTION("nullptr") { 201 | r.StartFrame(); 202 | r.SetTexture(nullptr); 203 | r.DrawSprite(0, 0, 100, 100); 204 | r.EndFrame(); 205 | 206 | r.WaitUntilDeviceIdle(); 207 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 208 | } 209 | } 210 | SECTION("Blend mode") { 211 | SECTION("initial blend mode is none") { 212 | REQUIRE(r.GetBlendMode() == BM_NONE); 213 | } 214 | 215 | SECTION("SetBlendMode") { 216 | r.SetBlendMode(BM_ADD); 217 | REQUIRE(r.GetBlendMode() == BM_ADD); 218 | } 219 | } 220 | 221 | 222 | r.WaitUntilDeviceIdle(); 223 | REQUIRE(r.GetDebugMessenger()->GetErrorAndWarningCount() == 0); 224 | 225 | glfwDestroyWindow(window); 226 | } 227 | -------------------------------------------------------------------------------- /engine/Renderer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Snorri Sturluson on 20/08/2018. 3 | // 4 | 5 | #ifndef VULKAN_SPRITES_RENDERER_H 6 | #define VULKAN_SPRITES_RENDERER_H 7 | 8 | #define GLFW_INCLUDE_VULKAN 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include "DebugMessenger.h" 15 | #include "ITexture.h" 16 | 17 | class Texture; 18 | class TextureAtlas; 19 | class Vertex; 20 | 21 | struct BoundImage { 22 | VkImage image; 23 | VkDeviceMemory imageMemory; 24 | }; 25 | 26 | struct BoundBuffer { 27 | VkBuffer buffer; 28 | VkDeviceMemory bufferMemory; 29 | }; 30 | 31 | enum BlendMode { 32 | BM_NONE, 33 | BM_BLEND, 34 | BM_ADD, 35 | BM_ADDX2, 36 | 37 | NUM_BLENDMODES 38 | }; 39 | 40 | class Renderer 41 | { 42 | public: 43 | enum ValidationState { 44 | DISABLE_VALIDATION, 45 | ENABLE_VALIDATION 46 | }; 47 | 48 | Renderer(); 49 | ~Renderer(); 50 | 51 | bool IsInitialized(); 52 | 53 | void Initialize(GLFWwindow *window, Renderer::ValidationState validation); 54 | 55 | DebugMessenger* GetDebugMessenger(); 56 | 57 | std::shared_ptr CreateTexture(const std::string &filename); 58 | 59 | std::shared_ptr CreateTextureAtlas(uint32_t width, uint32_t height); 60 | 61 | BoundBuffer CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties); 62 | void DestroyBuffer(BoundBuffer buffer); 63 | void DestroyBufferLater(BoundBuffer buffer); 64 | 65 | BoundImage CreateImage( 66 | uint32_t width, uint32_t height, 67 | VkFormat format, VkImageTiling tiling, 68 | VkImageUsageFlags usage, VkMemoryPropertyFlags properties); 69 | void DestroyImage(BoundImage image); 70 | 71 | void CopyToBufferMemory(VkDeviceMemory bufferMemory, uint8_t *data, VkDeviceSize size); 72 | 73 | void CopyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height, int offsetX = 0, 74 | int offsetY = 0); 75 | 76 | void TransitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout); 77 | 78 | VkImageView CreateImageView(VkImage image, VkFormat format); 79 | void DestroyImageView(VkImageView imageView); 80 | 81 | VkSampler CreateSampler(); 82 | void DestroySampler(VkSampler sampler); 83 | 84 | VkDescriptorSet CreateTextureDescriptorSet(VkImageView imageView, VkSampler sampler); 85 | 86 | void WaitUntilDeviceIdle(); 87 | 88 | bool StartFrame(); 89 | 90 | void EndFrame(); 91 | 92 | void DrawSprite(float x, float y, float width, float height); 93 | void DrawTriangles(const uint16_t* indices, size_t numIndices, const Vertex* vertices, size_t numVertices); 94 | 95 | bool ReserveTriangles(size_t numIndices, size_t numVertices, uint16_t *&indices, Vertex *&vertices, 96 | u_int16_t &baseIndex); 97 | void CommitTriangles(size_t numIndices, size_t numVertices); 98 | 99 | VkDeviceSize GetNumIndices(); 100 | VkDeviceSize GetNumVertices(); 101 | 102 | glm::vec4 GetClearColor() const; 103 | void SetClearColor(const glm::vec4 &clearColor); 104 | 105 | void SetColor(const glm::vec4& color); 106 | 107 | void SetTexture(std::shared_ptr texture); 108 | 109 | unsigned long GetNumDrawCommands(); 110 | 111 | void SetBlendMode(BlendMode bm); 112 | BlendMode GetBlendMode() const; 113 | 114 | protected: 115 | GLFWwindow *m_window; 116 | 117 | bool m_isInitialized = false; 118 | 119 | VkInstance m_instance; 120 | VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; 121 | VkDevice m_device; 122 | VkSurfaceKHR m_surface; 123 | 124 | VkQueue m_graphicsQueue; 125 | VkQueue m_presentQueue; 126 | 127 | VkSwapchainKHR m_swapChain; 128 | std::vector m_swapChainImages; 129 | VkFormat m_swapChainImageFormat; 130 | VkExtent2D m_swapChainExtent; 131 | std::vector m_swapChainImageViews; 132 | std::vector m_swapChainFramebuffers; 133 | 134 | bool m_enableValidationLayers; 135 | VkDebugUtilsMessengerEXT m_callback; 136 | std::unique_ptr m_debugMessenger; 137 | 138 | VkRenderPass m_renderPass; 139 | 140 | VkDescriptorSetLayout m_perFrameDescriptorSetLayout; 141 | VkDescriptorSetLayout m_perTextureDescriptorSetLayout; 142 | 143 | VkPipelineLayout m_pipelineLayout; 144 | VkPipeline m_graphicsPipeline; 145 | 146 | VkCommandPool m_commandPool; 147 | std::vector m_perFrameCommandPool; 148 | std::vector> m_perFrameCommandBuffer; 149 | VkCommandBuffer m_currentCommandBuffer; 150 | 151 | VkDescriptorPool m_descriptorPool; 152 | std::vector m_perFrameDescriptorSets; 153 | 154 | std::vector m_uniformBuffers; 155 | 156 | std::vector m_imageAvailableSemaphores; 157 | std::vector m_renderFinishedSemaphores; 158 | std::vector m_inFlightFences; 159 | uint32_t m_currentFrame = 1; 160 | uint32_t m_nextFrame; 161 | 162 | glm::vec4 m_clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; 163 | 164 | int m_currentBufferIndex = 0; 165 | 166 | std::vector> m_indexBuffers; 167 | std::vector> m_indexStagingBuffers; 168 | std::vector> m_vertexBuffers; 169 | std::vector> m_vertexStagingBuffers; 170 | 171 | uint16_t* m_indexWriteStart; 172 | uint16_t* m_currentIndexWrite; 173 | uint16_t* m_indexWriteEnd; 174 | Vertex* m_vertexWriteStart; 175 | Vertex* m_currentVertexWrite; 176 | Vertex* m_vertexWriteEnd; 177 | 178 | uint16_t m_numIndices = 0; 179 | uint16_t m_indexOffset = 0; 180 | uint16_t m_numVertices = 0; 181 | uint16_t m_vertexOffset = 0; 182 | 183 | std::shared_ptr m_defaultTexture; 184 | VkDescriptorSet m_currentDescriptorSet; 185 | TextureWindow m_currentTextureWindow; 186 | 187 | glm::vec4 m_currentColor; 188 | BlendMode m_currentBlendMode; 189 | 190 | struct DrawCommand { 191 | DrawCommand(VkDescriptorSet ds, uint16_t bi, uint16_t ni, int buf) : 192 | descriptorSet(ds), 193 | baseIndex(bi), 194 | numIndices(ni), 195 | bufferIndex(buf) 196 | {} 197 | VkDescriptorSet descriptorSet; 198 | uint16_t baseIndex; 199 | uint16_t numIndices; 200 | int bufferIndex; 201 | }; 202 | 203 | std::vector m_drawCommands; 204 | unsigned long m_numDrawCommands; 205 | 206 | std::vector> m_buffersToDestroyLater; 207 | 208 | protected: 209 | void createInstance(); 210 | void setupDebugCallback(); 211 | void setSurfaceFromWindow(GLFWwindow *window); 212 | 213 | struct QueueFamilyIndices 214 | { 215 | uint32_t graphicsFamily = std::numeric_limits::max(); 216 | uint32_t presentFamily = std::numeric_limits::max(); 217 | 218 | bool isComplete() 219 | { 220 | return graphicsFamily != std::numeric_limits::max() && 221 | presentFamily != std::numeric_limits::max(); 222 | } 223 | }; 224 | 225 | struct SwapChainSupportDetails 226 | { 227 | VkSurfaceCapabilitiesKHR capabilities; 228 | std::vector formats; 229 | std::vector presentModes; 230 | }; 231 | 232 | QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device); 233 | bool isDeviceSuitable(VkPhysicalDevice device); 234 | SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device); 235 | void pickPhysicalDevice(); 236 | void createLogicalDevice(); 237 | 238 | void createSwapChain(); 239 | 240 | void createImageViews(); 241 | 242 | uint32_t getMaxFramesInFlight(); 243 | 244 | VkImageView createImageView(VkImage image, VkFormat format); 245 | 246 | void createRenderPass(); 247 | 248 | void createDescriptorSetLayouts(); 249 | 250 | void createGraphicsPipeline(); 251 | 252 | VkShaderModule createShaderModule(uint8_t* code, size_t codeSize); 253 | 254 | void createFramebuffers(); 255 | 256 | void createCommandPool(); 257 | 258 | uint32_t findMemoryType(uint32_t bits, VkMemoryPropertyFlags properties); 259 | 260 | VkCommandBuffer beginSingleTimeCommands(); 261 | 262 | void endSingleTimeCommands(VkCommandBuffer commandBuffer); 263 | 264 | void createDescriptorPool(); 265 | 266 | void createUniformBuffers(); 267 | 268 | void createPerFrameDescriptorSets(); 269 | 270 | void createSyncObjects(); 271 | 272 | void cleanup(); 273 | 274 | void cleanupSwapChain(); 275 | 276 | void cleanupSyncObjects(); 277 | 278 | void cleanupUniformBuffers(); 279 | 280 | void cleanupCommandPools(); 281 | 282 | void recreateSwapChain(); 283 | 284 | void createIndexAndVertexBuffers(); 285 | 286 | VkDeviceSize getMaxNumIndices(); 287 | 288 | VkDeviceSize getMaxNumVertices(); 289 | 290 | void cleanupIndexAndVertexBuffers(); 291 | 292 | void startMainCommandBuffer(); 293 | 294 | VkDeviceSize getIndexBufferSize(); 295 | 296 | VkDeviceSize getVertexBufferSize(); 297 | 298 | void mapStagingBufferMemory(); 299 | 300 | void unmapStagingBuffers(); 301 | 302 | void copyStagingBuffersToDevice(VkCommandBuffer commandBuffer); 303 | 304 | void updateUniformBuffer() const; 305 | 306 | bool queueDrawCommand(); 307 | 308 | void cleanupPendingDestroyBuffers(); 309 | 310 | void beginRenderPass(); 311 | 312 | void endRenderPass() const; 313 | 314 | void cleanupBuffers(const std::vector &buffers); 315 | 316 | BoundBuffer & getIndexStagingBuffer(); 317 | 318 | BoundBuffer & getVertexStagingBuffer(); 319 | 320 | BoundBuffer &getIndexBuffer(); 321 | 322 | BoundBuffer &getVertexBuffer(); 323 | 324 | void queueCurrentBatch(); 325 | 326 | void drawBatches(); 327 | }; 328 | 329 | 330 | #endif //VULKAN_SPRITES_RENDERER_H 331 | --------------------------------------------------------------------------------