diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d77ba11f9a102053cd85d5e50bbe9c785ea1cb98
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+build
+.vscode
+imgui.ini
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..ee86091d2d5b528452bcf4dafe0e156f29f55cde
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "libs/spdlog"]
+	path = libs/spdlog
+	url = https://github.com/gabime/spdlog/
+[submodule "libs/imgui"]
+	path = libs/imgui
+	url = https://github.com/ocornut/imgui
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6e0c1a4b8c47d0f7e973a5aa99af84dbd8c46661
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 3.10)
+project(QPong VERSION 0.0.1 LANGUAGES C CXX)
+
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_BUILD_TYPE Release)
+set(CMAKE_CXX_FLAGS "-O3")
+
+option(LOG_DEBUG_INFO "Enable logging t console" OFF)
+if(LOG_DEBUG_INFO)
+    add_compile_definitions(ENABLE_LOG_DEBUG_INFO)
+endif(LOG_DEBUG_INFO)
+
+# Check for PkgConfig
+find_package(PkgConfig REQUIRED)
+
+# GUI/Window 
+add_subdirectory(GuiCore)
+
+# QPong App sources
+add_subdirectory(QPongApp)
+
+# Libraries the project depends on:
+add_subdirectory(libs/spdlog)
diff --git a/GuiCore/Application.cpp b/GuiCore/Application.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..35ccc77582faf1a09a9dc513d4684d56a791aa95
--- /dev/null
+++ b/GuiCore/Application.cpp
@@ -0,0 +1,111 @@
+/// @file   Application.cpp
+/// @author Armin Co
+///
+
+#include <GLFW/glfw3.h>
+
+#include "Application.hpp"
+#include "Log.hpp"
+#include "ApplicationEvent.hpp"
+
+
+namespace GuiCore
+{
+    Application* Application::s_instance = nullptr;
+
+    Application::Application(const std::string &name, uint32_t width, uint32_t height)
+    {
+        if (s_instance == nullptr)
+        {
+            Log::setup();
+        }
+        s_instance = this;
+        m_window = std::unique_ptr<IWindow>(IWindow::create({name, width, height}));
+        m_window->setEventCallback([this](auto &&... args) -> decltype(auto) { return this->onEvent(std::forward<decltype(args)>(args)...); });
+
+        m_guiLayer = std::make_shared<ImGuiLayer>();
+        pushLayer(m_guiLayer);
+    }
+
+    void Application::pushLayer(std::shared_ptr<Layer> layer)
+    {
+        m_layerStack.pushLayer(layer);
+    }
+
+    void Application::popLayer(std::shared_ptr<Layer> layer)
+    {
+        m_layerStack.popLayer(layer);
+    }
+
+    void Application::onEvent(Event &event)
+    {
+        Log::get()->trace("{}", event.toString());
+        EventDispatcher dispatcher(event);
+        dispatcher.dispatch<WindowCloseEvent>(
+            [this](auto &&... args) -> decltype(auto) { return this->onWindowClose(std::forward<decltype(args)>(args)...); });
+        dispatcher.dispatch<WindowResizeEvent>(
+            [this](auto &&... args) -> decltype(auto) { return this->onWindowResize(std::forward<decltype(args)>(args)...); });
+        
+        for (auto it = m_layerStack.rbegin(); it != m_layerStack.rend(); ++it)
+        {
+            if (event.handled)
+            {
+                break;
+            }
+            (*it)->onEvent(event);
+        }
+    }
+
+    void Application::run()
+    {
+        while (m_isRunnig)
+        {
+            float time = static_cast<float>(glfwGetTime());
+            Timestep timeStep = time - m_lastFrameTime;
+            m_lastFrameTime = time;
+
+            if (!m_minimzed)
+            {
+                for (auto layer : m_layerStack)
+                {
+                    layer->onUpdate(timeStep);
+                }
+            }
+
+            m_guiLayer->begin();
+            for (auto layer: m_layerStack)
+            {
+                layer->onGuiRender();
+            }
+            m_guiLayer->end();
+
+            m_window->onUpdate();
+        }
+    }
+
+    bool Application::onWindowClose(Event &event)
+    {
+        m_isRunnig = false;
+        return true;
+    }
+
+    bool Application::onWindowResize(Event &event)
+    {
+        WindowResizeEvent *rsEvent = static_cast<WindowResizeEvent*>(&event);
+        Log::get()->trace("Window minimized, {0}, {1}", rsEvent->getWidth(), rsEvent->getHeight());
+        glViewport(0,0, rsEvent->getWidth(), rsEvent->getHeight());
+
+        return false;
+    }
+
+    Application& Application::get()
+    {
+        return *s_instance;
+    }
+
+    IWindow& Application::getWindow()
+    {
+        return *m_window;
+    }
+}
+
diff --git a/GuiCore/Application.hpp b/GuiCore/Application.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ad48c239feda5139a856edd86811142a668e8bbf
--- /dev/null
+++ b/GuiCore/Application.hpp
@@ -0,0 +1,47 @@
+/// @file   Application.hpp
+/// @author Armin Co
+///
+
+#ifndef GUI_CORE_APPLICATION_HPP
+#define GUI_CORE_APPLICATION_HPP
+
+#include <string>
+#include <memory>
+
+#include "Event.hpp"
+#include "IWindow.hpp"
+#include "LayerStack.hpp"
+#include "ImGuiLayer.hpp"
+#include "ApplicationEvent.hpp"
+
+namespace GuiCore
+{
+    class Application
+    {
+    public:
+        Application(const std::string &name = "Application", uint32_t width = 1280, uint32_t height = 720);
+        virtual ~Application() {}
+
+        void run();
+        void onEvent(Event &e);
+        void pushLayer(std::shared_ptr<Layer> layer);
+        void popLayer(std::shared_ptr<Layer> layer);
+        IWindow& getWindow();
+        bool onWindowClose(Event &e);
+        bool onWindowResize(Event &e);
+
+        static Application& get();
+
+    private:
+        std::unique_ptr<IWindow> m_window;
+        bool m_isRunnig = true;
+        bool m_minimzed = false;
+        LayerStack m_layerStack;
+        float m_lastFrameTime = 0.0f;
+        std::shared_ptr<ImGuiLayer> m_guiLayer;
+
+        static Application *s_instance;
+    };
+}
+
+#endif
\ No newline at end of file
diff --git a/GuiCore/ApplicationEvent.hpp b/GuiCore/ApplicationEvent.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b71ec60974387776611c6d6020b003c57fcd3381
--- /dev/null
+++ b/GuiCore/ApplicationEvent.hpp
@@ -0,0 +1,49 @@
+/// @file   ApplicationEvent.hpp
+/// @author Armin Co
+///
+
+#ifndef APPLICATION_EVENT_HPP
+#define APPLICATION_EVENT_HPP
+
+#include <sstream>
+#include "Event.hpp"
+
+namespace GuiCore
+{
+    class WindowResizeEvent : public Event
+    {
+    public:
+        WindowResizeEvent(uint32_t width, uint32_t height)
+            : m_width{width}
+            , m_height{height}
+        {
+        }
+
+        uint32_t getWidth() const {return m_width;}
+        uint32_t getHeight() const {return m_height;}
+
+        std::string toString() const override
+        {
+            std::stringstream ss;
+            ss << "WindowResizeEvent: " << m_width << ", " << m_height;
+            return ss.str();
+        }
+
+        EVENT_CLASS_TYPE(WindowResize)
+        EVENT_CLASS_CATEGORY(EventCategoryApplication)
+
+    private:
+        uint32_t m_width;
+        uint32_t m_height;
+    };
+
+    class WindowCloseEvent : public Event
+    {
+    public:
+        WindowCloseEvent() = default;
+        EVENT_CLASS_TYPE(WindowClose)
+        EVENT_CLASS_CATEGORY(EventCategoryApplication)
+    };
+}
+
+#endif
\ No newline at end of file
diff --git a/GuiCore/CMakeLists.txt b/GuiCore/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..38492720572ddb2a19b8c15a664c54716efa18dc
--- /dev/null
+++ b/GuiCore/CMakeLists.txt
@@ -0,0 +1,61 @@
+find_package( Freetype REQUIRED)
+
+# GuiCore library
+add_library(GuiCore
+    Application.cpp
+    Input.cpp
+    Layer.cpp
+    LayerStack.cpp
+    Log.cpp
+    Timer.cpp
+    Timestep.cpp
+    Glyphs.cpp
+)
+
+target_include_directories( GuiCore PUBLIC
+    ../libs/spdlog/include/
+    ${FREETYPE_INCLUDE_DIRS}
+)
+
+target_link_libraries( GuiCore
+    spdlog
+    ${FREETYPE_LIBRARIES}
+)
+add_compile_definitions(IMGUI_IMPL_OPENGL_LOADER_GL3W)
+
+# ImGui is a dependency of GuiCore
+add_library( ImGui
+    Shader.cpp
+    ImGuiLayer.cpp
+    ../libs/imgui/imgui.cpp
+    ../libs/imgui/imgui_draw.cpp
+    ../libs/imgui/imgui_widgets.cpp
+    ../libs/imgui/imgui_tables.cpp
+    ../libs/imgui/backends/imgui_impl_glfw.cpp
+    ../libs/imgui/backends/imgui_impl_opengl3.cpp
+    ../libs/imgui/examples/libs/gl3w/GL/gl3w.c
+)
+
+target_include_directories( ImGui PUBLIC
+    ../libs
+    ../libs/imgui # for glfw
+    ../libs/imgui/backends
+    ../libs/imgui/examples/libs/gl3w
+    ../libs/spdlog/include    
+)
+
+
+target_link_libraries( GuiCore 
+    ImGui
+)
+
+# Check for GLFW3 and link
+pkg_check_modules(GLFW glfw3)
+target_include_directories(GuiCore PUBLIC ${GLFW_INCLUDE_DIRS})
+target_link_libraries(GuiCore ${GLFW_LIBRARIES})
+
+
+# Check for OpenGL
+set(OpenGL_GL_PREFERENCE GLVND)
+find_package(OpenGL REQUIRED)
+target_link_libraries(GuiCore OpenGL::GL ${CMAKE_DL_LIBS})
diff --git a/GuiCore/Event.hpp b/GuiCore/Event.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c1390bd22f14913b3c29e5ae15a2f3b710350fff
--- /dev/null
+++ b/GuiCore/Event.hpp
@@ -0,0 +1,83 @@
+/// @file   Event.hpp
+/// @author Armin Co
+///
+
+#ifndef GUI_CORE_EVENT_HPP
+#define GUI_CORE_EVENT_HPP
+
+#include <string>
+#include "Log.hpp"
+
+#define BIT(x) (1 << x)
+
+namespace GuiCore
+{
+    enum class EventType
+    {
+        None = 0,
+        WindowClose, WindowResize, WindowFocus, WindowLostFocus, WindowMoved,
+		AppTick, AppUpdate, AppRender,
+		KeyPressed, KeyReleased, KeyTyped,
+		MouseButtonPressed, MouseButtonReleased, MouseMoved, MouseScrolled
+    };
+    
+    enum EventCategory
+	{
+		None = 0,
+		EventCategoryApplication    = BIT(0),
+		EventCategoryInput          = BIT(1),
+		EventCategoryKeyboard       = BIT(2),
+		EventCategoryMouse          = BIT(3),
+		EventCategoryMouseButton    = BIT(4)
+	};
+
+#define EVENT_CLASS_TYPE(type) static EventType getStaticType() { return EventType::type; }\
+								virtual EventType getEventType() const override { return getStaticType(); }\
+								virtual const char* getName() const override { return #type; }
+#define EVENT_CLASS_CATEGORY(category) virtual int getCategoryFlags() const override { return category; }
+
+    class Event
+    {
+    public:
+        bool handled = false;
+
+        virtual EventType getEventType() const = 0;
+        virtual const char * getName() const = 0;
+        virtual int getCategoryFlags() const = 0;
+        virtual std::string toString() const {return getName();}
+
+        inline bool isInCategory(EventCategory category)
+        {
+            return getCategoryFlags() & category;
+        }
+    };
+
+    class EventDispatcher
+    {
+    public:
+        EventDispatcher(Event &event)
+            : m_event{event}
+        {
+        }
+
+        template<typename T, typename F>
+        bool dispatch(const F& function)
+        {
+            if (m_event.getEventType() == T::getStaticType())
+            {
+                m_event.handled = function(static_cast<T&>(m_event));
+                return true;
+            }
+            return false;
+        }
+    private:
+        Event &m_event;
+    };
+
+    inline std::ostream& operator<<(std::ostream& os, const Event &e)
+    {
+        return os << e.toString();
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/GuiCore/Glyphs.cpp b/GuiCore/Glyphs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9d30992c002a8cc8ffbfe67f74bd3b5359cd7b08
--- /dev/null
+++ b/GuiCore/Glyphs.cpp
@@ -0,0 +1,130 @@
+/// @file   Glyphs.cpp
+/// @author Armin Co
+///
+
+#include "Glyphs.hpp"
+#include <GLFW/glfw3.h>
+#include "Log.hpp"
+#include <glm/gtc/type_ptr.hpp>
+
+
+extern "C" {
+#include <ft2build.h>
+#include FT_FREETYPE_H
+}
+
+
+std::map<char, GuiCore::Character> GuiCore::Glyphs::s_characters;
+GuiCore::Shader* GuiCore::Glyphs::m_shader;
+GLuint GuiCore::Glyphs::m_vertexArray;
+GLuint GuiCore::Glyphs::m_vertexBuffer;
+glm::mat4 GuiCore::Glyphs::projection = glm::ortho(-3.2f, 3.2f, -1.8f, 1.8f, -1.0f, 1.0f);
+
+void GuiCore::Glyphs::setup()
+{
+    m_shader = GuiCore::Shader::fromGLSLTextFiles(
+        "assets/shaders/glyph.vert.glsl",
+        "assets/shaders/glyph.frag.glsl"
+    );
+
+    glGenVertexArrays(1, &m_vertexArray);
+    glGenBuffers(1, &m_vertexBuffer);
+    glBindVertexArray(m_vertexArray);
+    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
+    glEnableVertexAttribArray(0);
+    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+    glBindVertexArray(0);
+
+    FT_Library ft;
+    if (FT_Init_FreeType(&ft))
+    {
+        GuiCore::Log::get()->error("ERROR::FREETYPE: Could not init FreeType Library");
+    }
+
+    FT_Face face;
+    if (FT_New_Face(ft, "assets/Font.ttf", 0, &face))
+    {
+        GuiCore::Log::get()->error("ERROR::FREETYPE: Failed to load font");
+    }
+    FT_Set_Pixel_Sizes(face, 0, 48);  
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+    for (unsigned char c = 0; c < 128; c++)
+    {
+        // load character glyph
+        auto error = FT_Load_Char(face, c, FT_LOAD_RENDER);
+        if (error)
+        {
+            GuiCore::Log::get()->error("ERROR::FREETYTPE: Failed to load Glyph {0} {1}", c, error);
+            continue;
+        }
+        // generate texture
+        unsigned int texture;
+        glGenTextures(1, &texture);
+        glBindTexture(GL_TEXTURE_2D, texture);
+        glTexImage2D(
+            GL_TEXTURE_2D,
+            0,
+            GL_RED,
+            face->glyph->bitmap.width,
+            face->glyph->bitmap.rows,
+            0,
+            GL_RED,
+            GL_UNSIGNED_BYTE,
+            face->glyph->bitmap.buffer
+        );
+        // set texture options
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+        // now store character for later use
+        Character character = {
+            texture, 
+            glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
+            glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
+            static_cast<uint32_t>(face->glyph->advance.x)
+        };
+        s_characters.insert(std::pair<char, Character>(c, character));
+    }
+}
+
+void GuiCore::Glyphs::renderText(std::string text, float x, float y, float scale, glm::vec3 color)
+{
+    glUseProgram(m_shader->getRendererID());
+    glUniformMatrix4fv(glGetUniformLocation(m_shader->getRendererID(), "projection"), 1, GL_FALSE, glm::value_ptr(projection));
+    glUniform3f(glGetUniformLocation(m_shader->getRendererID(), "textColor"), color.r, color.g, color.b);
+    glActiveTexture(GL_TEXTURE0);
+    glBindVertexArray(m_vertexArray);
+
+    std::string::const_iterator c;
+    for (c = text.begin(); c != text.end(); c++)
+    {
+        Character ch = s_characters[*c];
+
+        float xPos = x + ch.bearing.x * scale;
+        float yPos = y - (ch.size.y - ch.bearing.y) * scale;
+
+        float w = ch.size.x * scale;
+        float h = ch.size.y * scale;
+
+        float vertices[6][4] = {
+            { xPos,     yPos + h,   0.0f, 0.0f },            
+            { xPos,     yPos,       0.0f, 1.0f },
+            { xPos + w, yPos,       1.0f, 1.0f },
+
+            { xPos,     yPos + h,   0.0f, 0.0f },
+            { xPos + w, yPos,       1.0f, 1.0f },
+            { xPos + w, yPos + h,   1.0f, 0.0f }
+        };
+        glBindTexture(GL_TEXTURE_2D, ch.textureId);
+        glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
+        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
+        glBindBuffer(GL_ARRAY_BUFFER, 0);
+        glDrawArrays(GL_TRIANGLES, 0, 6);
+        x += (ch.advance >> 6) * scale;
+    }
+    glBindVertexArray(0);
+    glBindTexture(GL_TEXTURE_2D, 0);
+}
\ No newline at end of file
diff --git a/GuiCore/Glyphs.hpp b/GuiCore/Glyphs.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..75c395393f183f99d8feb1415a4aef4fc441494d
--- /dev/null
+++ b/GuiCore/Glyphs.hpp
@@ -0,0 +1,39 @@
+/// @file   Glyphs.hpp
+/// @author Armin Co
+///
+
+#ifndef GUI_CORE_GLYPHS_HPP
+#define GUI_CORE_GLYPHS_HPP
+
+#include <map>
+#include <glm/glm.hpp>
+
+
+#include "Shader.hpp"
+
+namespace GuiCore
+{
+struct Character {
+    unsigned int textureId;  // ID handle of the glyph texture
+    glm::ivec2   size;       // Size of glyph
+    glm::ivec2   bearing;    // Offset from baseline to left/top of glyph
+    unsigned int advance;    // Offset to advance to next glyph
+};
+
+class Glyphs
+{
+public: 
+    static void setup();
+    static void renderText(std::string text, float x, float y, float scale, glm::vec3 color);
+
+private:
+    static std::map<char, Character> s_characters;
+    static Shader *m_shader;
+    static GLuint m_vertexArray;
+    static GLuint m_vertexBuffer;
+    static glm::mat4 projection;
+};
+
+}
+
+#endif
\ No newline at end of file
diff --git a/GuiCore/IWindow.hpp b/GuiCore/IWindow.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4dbd713c0bbebd65808a263043e34488cfd29aae
--- /dev/null
+++ b/GuiCore/IWindow.hpp
@@ -0,0 +1,46 @@
+/// @file   IWindow.hpp
+/// @author Armin Co
+///
+
+#ifndef GUI_CORE_WINDOW_HPP
+#define GUI_CORE_WINDOW_HPP
+
+#include <string>
+#include <functional>
+
+#include "Event.hpp"
+
+namespace GuiCore
+{
+    struct WindowProperties
+    {
+        std::string title;
+        uint32_t width;
+        uint32_t height;
+
+        WindowProperties(const std::string &t = "Application", uint32_t w = 1920, uint32_t h = 1080)
+            : title{t}
+            , width{w}
+            , height{h}
+            {
+            }
+    };
+
+    class IWindow
+    {
+    public:
+        virtual ~IWindow() {}
+        virtual void onUpdate() = 0;
+        virtual uint32_t getWidth() const = 0;
+        virtual uint32_t getHeight() const = 0;
+
+        virtual void setEventCallback(const std::function<void(GuiCore::Event&)>) = 0;
+        virtual void setVSync(bool enabled) = 0;
+        virtual bool isVSyncEnabled() const = 0;
+        virtual void* getNativeWindow() const = 0;
+
+        static IWindow* create(const WindowProperties &props = WindowProperties());
+    };
+}
+
+#endif
\ No newline at end of file
diff --git a/GuiCore/ImGuiLayer.cpp b/GuiCore/ImGuiLayer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..411c6259ff1d98efd9158e792ef536cbd9606bc7
--- /dev/null
+++ b/GuiCore/ImGuiLayer.cpp
@@ -0,0 +1,76 @@
+/// @file   ImGuiLayer.cpp
+/// @author Armin Co
+///
+
+#include <GLFW/glfw3.h>
+
+#include <imgui/imgui.h>
+#include <imgui/backends/imgui_impl_glfw.h>
+#include <imgui/backends/imgui_impl_opengl3.h>
+
+#include "Application.hpp"
+#include "ImGuiLayer.hpp"
+#include "Log.hpp"
+
+using namespace GuiCore;
+
+ImGuiLayer::ImGuiLayer()
+    : Layer("ImGuiLayer")
+{
+}
+
+ImGuiLayer::~ImGuiLayer()
+{
+}
+
+void ImGuiLayer::onAttach()
+{
+    GuiCore::Log::get()->trace("onAttach ImGuiLayer");
+    IMGUI_CHECKVERSION();
+    ImGui::CreateContext();
+    ImGuiIO& io = ImGui::GetIO();
+
+
+    ImGui::StyleColorsDark();
+
+    Application& app = Application::get();
+
+    GLFWwindow *window = static_cast<GLFWwindow*>(app.getWindow().getNativeWindow());
+    ImGui_ImplGlfw_InitForOpenGL(window, true);
+    ImGui_ImplOpenGL3_Init("#version 330");
+}
+
+
+void ImGuiLayer::onDetach()
+{
+    ImGui_ImplOpenGL3_Shutdown();
+    ImGui_ImplGlfw_Shutdown();
+    ImGui::DestroyContext();
+}
+
+void ImGuiLayer::begin()
+{
+    ImGui_ImplOpenGL3_NewFrame();
+    ImGui_ImplGlfw_NewFrame();
+    ImGui::NewFrame();
+}
+
+void ImGuiLayer::end()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    Application& app = Application::get();
+    io.DisplaySize = ImVec2((float)app.getWindow().getWidth(), (float)app.getWindow().getHeight());
+
+    ImGui::Render();
+    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+}
+
+void ImGuiLayer::onEvent(Event &event)
+{
+
+}
+
+bool ImGuiLayer::onMouseButtonPressed(Event &event)
+{
+    return false;
+}
diff --git a/GuiCore/ImGuiLayer.hpp b/GuiCore/ImGuiLayer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1156f8a23c81309dfc33f4ff9f36e6b3acaaf6e3
--- /dev/null
+++ b/GuiCore/ImGuiLayer.hpp
@@ -0,0 +1,35 @@
+/// @file   ImGuiLayer.hpp
+/// @author Armin Co
+///
+
+#ifndef IM_GUI_LAYER_HPP
+#define IM_GUI_LAYER_HPP
+
+#include "Layer.hpp"
+#include "Event.hpp"
+
+namespace GuiCore
+{
+
+    class ImGuiLayer : public Layer
+    {
+    public:
+        ImGuiLayer();
+        virtual ~ImGuiLayer();
+
+        virtual void onAttach() override;
+        virtual void onDetach() override;
+
+        void begin();
+        void end();
+
+        virtual void onEvent(Event &event);
+        bool onMouseButtonPressed(Event &event);
+
+    private:
+        float m_time = 0.0f;
+    };
+
+}
+
+#endif
\ No newline at end of file
diff --git a/GuiCore/Input.cpp b/GuiCore/Input.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2f1e7a13d217a677798c21ec8f1cdf2ebb13d577
--- /dev/null
+++ b/GuiCore/Input.cpp
@@ -0,0 +1,48 @@
+/// @file   Input.cpp
+/// @author Armin Co
+///
+
+#include "Input.hpp"
+#include "Application.hpp"
+
+#include <GLFW/glfw3.h>
+
+namespace GuiCore
+{
+    Input* Input::s_instance = new Input();
+
+    bool Input::isKeyPressed(int keyCode)
+    {
+        auto window = static_cast<GLFWwindow*>(Application::get().getWindow().getNativeWindow());
+        auto state = glfwGetKey(window, keyCode);
+        return state == GLFW_PRESS || state == GLFW_REPEAT;
+    }
+
+    bool Input::isMouseButtonPressed(int button)
+    {
+        auto window = static_cast<GLFWwindow*>(Application::get().getWindow().getNativeWindow());
+        auto state = glfwGetMouseButton(window, button);
+        return state == GLFW_PRESS;
+    }
+
+    std::pair<float, float> Input::getMousePosition()
+    {
+        auto window = static_cast<GLFWwindow*>(Application::get().getWindow().getNativeWindow());
+        double xPos {0};
+        double yPos {0};
+        glfwGetCursorPos(window, &xPos, &yPos);
+        return {static_cast<float>(xPos), static_cast<float>(yPos)};
+    }
+
+    float Input::getMouseX()
+    {
+        auto[x, y] = getMousePosition();
+        return x;
+    }
+
+    float Input::getMouseY()
+    {
+        auto[x, y] = getMousePosition();
+        return y;
+    }
+}
\ No newline at end of file
diff --git a/GuiCore/Input.hpp b/GuiCore/Input.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a311d20a84991950bf8cb7b0e90d8518c99c32d5
--- /dev/null
+++ b/GuiCore/Input.hpp
@@ -0,0 +1,66 @@
+/// @file   Input.hpp
+/// @author Armin Co
+///
+
+#ifndef GUI_CORE_INPUT_HPP
+#define GUI_CORE_INPUT_HPP
+
+#include <utility>
+
+namespace GuiCore
+{
+    class Input
+    {
+        protected:
+            Input() = default;
+        public:
+            Input(const Input &) = delete;
+            Input& operator=(const Input&) = delete;
+
+            static bool isKeyPressed(int keyCode);
+
+            static bool isMouseButtonPressed(int button);
+            static std::pair<float, float> getMousePosition();
+            static float getMouseX();
+            static float getMouseY();
+        private:
+            static Input* s_instance;
+    };
+}
+
+namespace Key
+{
+    constexpr int A {65};
+    constexpr int B {66};
+    constexpr int C {67};
+    constexpr int D {68};
+    constexpr int E {69};
+    constexpr int F {70};
+    constexpr int G {71};
+    constexpr int H {72};
+    constexpr int I {73};
+    constexpr int J {74};
+    constexpr int K {75};
+    constexpr int L {76};
+    constexpr int M {77};
+    constexpr int N {78};
+    constexpr int O {79};
+    constexpr int P {80};
+    constexpr int Q {81};
+    constexpr int R {82};
+    constexpr int S {83};
+    constexpr int T {84};
+    constexpr int U {85};
+    constexpr int V {86};
+    constexpr int W {87};
+    constexpr int X {88};
+    constexpr int Z {89};
+    constexpr int Y {90};
+    
+    constexpr int Right {262};
+    constexpr int Left  {263};
+    constexpr int Down  {264};
+    constexpr int Up    {265};
+}
+
+#endif
\ No newline at end of file
diff --git a/GuiCore/Layer.cpp b/GuiCore/Layer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..152287f27ad277e5c9bde934bc0f923589e90f75
--- /dev/null
+++ b/GuiCore/Layer.cpp
@@ -0,0 +1,23 @@
+/// @file   Layer.cpp
+/// @author Armin Co
+///
+
+#include "Layer.hpp"
+
+namespace GuiCore
+{
+    Layer::Layer(const std::string &debugName)
+        : m_debugName{debugName}
+    {
+    }
+
+    Layer::~Layer()
+    {
+    }
+
+    const std::string &Layer::getName() const
+    {
+        return m_debugName;
+    }
+}
+
diff --git a/GuiCore/Layer.hpp b/GuiCore/Layer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..73687a1cb8568c263caed48cb8b99a03066b7809
--- /dev/null
+++ b/GuiCore/Layer.hpp
@@ -0,0 +1,35 @@
+/// @file   Layer.hpp
+/// @author Armin Co
+///
+
+#ifndef GUI_CORE_LAYER_HPP
+#define GUI_CORE_LAYER_HPP
+
+#include <string>
+
+#include "Event.hpp"
+#include "Timestep.hpp"
+
+namespace GuiCore
+{
+    class Layer
+    {
+    public:
+        Layer(const std::string &name = "Layer");
+        virtual ~Layer();
+
+        virtual void onAttach() {}
+        virtual void onDetach() {}
+        virtual void onUpdate(Timestep ts) {}
+        virtual void onGuiRender() {}
+        virtual void onEvent(Event &event){}
+
+        const std::string &getName() const;
+
+    protected:
+        std::string m_debugName;
+    };
+
+}
+
+#endif
\ No newline at end of file
diff --git a/GuiCore/LayerStack.cpp b/GuiCore/LayerStack.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8dd6c65241e8e7be9d2595ee613e5a51489cd9b9
--- /dev/null
+++ b/GuiCore/LayerStack.cpp
@@ -0,0 +1,70 @@
+/// @file   LayerStack.cpp
+/// @author Armin Co
+///
+
+#include <algorithm>
+
+#include "LayerStack.hpp"
+
+namespace GuiCore
+{
+    LayerStack::LayerStack()
+    {
+    }
+
+    LayerStack::~LayerStack()
+    {
+        for (auto layer : m_layers)
+        {
+            layer->~Layer();
+        }
+    }
+
+    void LayerStack::pushLayer(std::shared_ptr<Layer> layer)
+    {
+        m_layers.emplace(lastLayer(), layer);
+        ++m_layerIndex;
+        layer->onAttach();
+    }
+
+    void LayerStack::popLayer(std::shared_ptr<Layer> layer)
+    {
+        auto it = std::find(firstLayer(), lastLayer(), layer);
+        if (it != lastLayer())
+        {
+            layer->onDetach();
+            m_layers.erase(it);
+            --m_layerIndex;
+        }
+    }
+
+    Layers::iterator LayerStack::begin()
+    {
+        return m_layers.begin();
+    }
+
+    Layers::iterator LayerStack::end()
+    {
+        return m_layers.end();
+    }
+
+    Layers::iterator LayerStack::firstLayer()
+    {
+        return m_layers.begin();
+    }
+
+    Layers::iterator LayerStack::lastLayer()
+    {
+        return m_layers.begin() + m_layerIndex;
+    }
+
+    Layers::reverse_iterator LayerStack::rbegin()
+    {
+        return m_layers.rbegin();
+    }
+
+    Layers::reverse_iterator LayerStack::rend()
+    {
+        return m_layers.rend();
+    }
+}
diff --git a/GuiCore/LayerStack.hpp b/GuiCore/LayerStack.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3e5bb14839777a7455594830343ac226771ad82f
--- /dev/null
+++ b/GuiCore/LayerStack.hpp
@@ -0,0 +1,40 @@
+/// @file   LayerStack.hpp
+/// @author Armin Co
+///
+
+#ifndef GUI_CORE_LAYERSTACK_HPP
+#define GUI_CORE_LAYERSTACK_HPP
+
+#include <vector>
+#include <memory>
+
+#include "Layer.hpp"
+
+namespace GuiCore
+{
+    using Layers = std::vector<std::shared_ptr<Layer>>;
+
+    class LayerStack
+    {
+    public:
+        LayerStack();
+        ~LayerStack();
+
+        void pushLayer(std::shared_ptr<Layer> layer);
+        void popLayer(std::shared_ptr<Layer> layer);
+
+        Layers::iterator begin();
+        Layers::iterator end();
+        Layers::reverse_iterator rbegin();
+        Layers::reverse_iterator rend();
+
+    private:
+        Layers::iterator firstLayer();
+        Layers::iterator lastLayer();
+
+        Layers m_layers;
+        uint32_t m_layerIndex{0};
+    };
+}
+
+#endif
\ No newline at end of file
diff --git a/GuiCore/Log.cpp b/GuiCore/Log.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d4482d30044eedb86a77a1c7a60f9bfada24d019
--- /dev/null
+++ b/GuiCore/Log.cpp
@@ -0,0 +1,28 @@
+/// @file   Log.cpp
+/// @author Armin Co
+///
+
+#include "spdlog/sinks/stdout_color_sinks.h"
+
+#include "Log.hpp"
+
+namespace GuiCore
+{
+    std::shared_ptr<spdlog::logger> Log::s_logger;
+
+    void Log::setup()
+    {
+        spdlog::set_pattern("%^[%T] %n: %v%$");
+        s_logger = spdlog::stdout_color_mt("GuiCore");
+#ifdef ENABLE_LOG_DEBUG_INFO
+        s_logger->set_level(spdlog::level::trace);
+#else
+        s_logger->set_level(spdlog::level::info);
+#endif
+    }
+
+    std::shared_ptr<spdlog::logger> Log::get()
+    {
+        return s_logger;
+    }
+}
diff --git a/GuiCore/Log.hpp b/GuiCore/Log.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f962214ff8524b2615a8e61988dbc71b92677123
--- /dev/null
+++ b/GuiCore/Log.hpp
@@ -0,0 +1,25 @@
+/// @file   Log.hpp
+/// @author Armin Co
+///
+
+#ifndef GUI_CORE_LOG_HPP
+#define GUI_CORE_LOG_HPP
+
+#include <memory>
+
+#include <spdlog/spdlog.h>
+
+namespace GuiCore
+{
+    class Log
+    {
+    public:
+        static void setup();
+        static std::shared_ptr<spdlog::logger> get();
+
+    private:
+        static std::shared_ptr<spdlog::logger> s_logger;
+    };
+}
+
+#endif
\ No newline at end of file
diff --git a/GuiCore/Shader.cpp b/GuiCore/Shader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..65d24ee9a302a0f0389467253cdc0c8cdedf4d5c
--- /dev/null
+++ b/GuiCore/Shader.cpp
@@ -0,0 +1,119 @@
+/// @file   Shader.cpp
+/// @author Armin Co
+///
+
+#include <fstream>
+#include <sstream>
+
+#include "Shader.hpp"
+#include "Log.hpp"
+
+
+
+namespace GuiCore
+{
+    static std::string readFileAsString(const std::string &filepath)
+    {
+        std::string result;
+        std::ifstream in(filepath, std::ios::in);
+        if (in.is_open())
+        {
+            std::stringstream ss;
+            ss << in.rdbuf();
+            result = ss.str();
+        }
+        else
+        {
+            Log::get()->trace("Shader::readFileAsString");
+            Log::get()->error("Could not open file '{0}'", filepath);
+        }
+        return result;
+    }
+
+    Shader::~Shader()
+    {
+        glDeleteProgram(m_rendererID);
+    }
+
+    GLuint Shader::compileShader(GLenum type, const std::string &source)
+    {
+        GLuint shader = glCreateShader(type);
+
+        const GLchar *sourceCStr = source.c_str();
+        Log::get()->trace("Shader::compileShader");
+        // Log::get()->debug("{0}", sourceCStr);
+        glShaderSource(shader, 1, &sourceCStr, nullptr);
+
+        glCompileShader(shader);
+
+        GLint isCompiled = 0;
+        glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
+        if (isCompiled == GL_FALSE)
+        {
+            GLint maxLength = 0;
+            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
+
+            std::vector<GLchar> infoLog(maxLength);
+            glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);
+
+            glDeleteShader(shader);
+
+            Log::get()->trace("Shader::compileShader");
+            Log::get()->error("{0}", infoLog.data());
+        }
+        return shader;
+    }
+
+    Shader* Shader::fromGLSLTextFiles(const std::string& vertexShaderPath, const std::string &fragmentShaderPath)
+    {
+        Shader *shader = new Shader();
+        shader->loadFromGLSLTextFiles(vertexShaderPath, fragmentShaderPath);
+        return shader;
+    }
+
+    void Shader::loadFromGLSLTextFiles(const std::string &vertexShaderPath, const std::string &fragmentShaderPath)
+    {
+        std::string vertexSource = readFileAsString(vertexShaderPath);
+        std::string fragmentSource = readFileAsString(fragmentShaderPath);
+
+        GLuint program = glCreateProgram();
+
+        GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexSource);
+        glAttachShader(program, vertexShader);
+        GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentSource);
+        glAttachShader(program, fragmentShader);
+
+        glLinkProgram(program);
+
+        GLint isLinked = 0;
+        glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);
+		if (isLinked == GL_FALSE)
+		{
+			GLint maxLength = 0;
+			glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);
+
+			std::vector<GLchar> infoLog(maxLength);
+			glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);
+
+			glDeleteProgram(program);
+
+			glDeleteShader(vertexShader);
+			glDeleteShader(fragmentShader);
+
+            Log::get()->trace("Shader::loadFromGLSLTextFiles");
+			Log::get()->error("{0}", infoLog.data());
+		}
+
+        glDetachShader(program, vertexShader);
+		glDetachShader(program, fragmentShader);
+		glDeleteShader(vertexShader);
+		glDeleteShader(fragmentShader);
+
+		m_rendererID = program;
+    }
+
+    GLuint Shader::getRendererID()
+    {
+        return m_rendererID;
+    }
+}
diff --git a/GuiCore/Shader.hpp b/GuiCore/Shader.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6963e4bab96674101d20f7eadd0f47084c9bf3b1
--- /dev/null
+++ b/GuiCore/Shader.hpp
@@ -0,0 +1,29 @@
+/// @file   Shader.hpp
+/// @author Armin Co
+///
+
+#ifndef OPEN_GL_SHADER_HPP
+#define OPEN_GL_SHADER_HPP
+
+#include <string>
+#include <GL/gl3w.h>
+#include <GLFW/glfw3.h>
+namespace GuiCore
+{
+    class Shader
+    {
+    public:
+        ~Shader();
+
+        GLuint getRendererID();
+        static Shader* fromGLSLTextFiles(const std::string& vertexShaderPath, const std::string &fragmentShaderPath);
+    private:
+        Shader() = default;
+        void loadFromGLSLTextFiles(const std::string &vertexShaderPath, const std::string &fragmentShaderPath);
+        GLuint compileShader(GLenum type, const std::string &source);
+    private:
+        GLuint m_rendererID;
+    };
+}
+
+#endif
\ No newline at end of file
diff --git a/GuiCore/Timer.cpp b/GuiCore/Timer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4d070e37f1298a6ea57f3f5df6923652dbfd2cc2
--- /dev/null
+++ b/GuiCore/Timer.cpp
@@ -0,0 +1,34 @@
+/// @file   Timer.cpp
+/// @author Armin Co
+///
+
+#include "Timer.hpp"
+#include "Log.hpp"
+
+using namespace GuiCore;
+
+Timer::Timer(const char* name)
+    : m_name{name}
+{
+    m_startTimepoint = std::chrono::high_resolution_clock::now();
+}
+
+Timer::~Timer()
+{
+    stop();
+}
+
+double Timer::stop()
+{
+    auto endTimepoint = std::chrono::high_resolution_clock::now();
+    
+    auto start = std::chrono::time_point_cast<std::chrono::microseconds>(m_startTimepoint).time_since_epoch().count();
+    auto end = std::chrono::time_point_cast<std::chrono::microseconds>(endTimepoint).time_since_epoch().count();
+
+    auto duration = end - start;
+    double ms = duration * 0.001;
+
+    Log::get()->debug("{0}: {1}ms", m_name, ms);
+
+    return ms;
+}
\ No newline at end of file
diff --git a/GuiCore/Timer.hpp b/GuiCore/Timer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5508fe1e0fce97eb160ef611a628203e29b61c75
--- /dev/null
+++ b/GuiCore/Timer.hpp
@@ -0,0 +1,32 @@
+/// @file   Timer.hpp
+/// @author Armmin Co
+///
+
+#ifndef GUI_CORE_TIMER_HPP
+#define GUI_CORE_TIMER_HPP
+
+#include <chrono>
+
+namespace GuiCore
+{
+
+class Timer
+{
+public:
+    /// @brief Start timer 
+    ///
+    Timer(const char* name = "Timer");
+    
+    /// @brief Stops timer.
+    ~Timer();
+
+    double stop();
+private:
+    std::chrono::time_point< std::chrono::high_resolution_clock > m_startTimepoint;
+    const char* m_name;
+};
+
+}
+
+
+#endif
\ No newline at end of file
diff --git a/GuiCore/Timestep.cpp b/GuiCore/Timestep.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..aabd704e303cf320b44a8e9b18f06577c822cf3e
--- /dev/null
+++ b/GuiCore/Timestep.cpp
@@ -0,0 +1,23 @@
+/// @file   Timestep.cpp
+/// @author Armin Co
+///
+
+#include "Timestep.hpp"
+
+namespace GuiCore
+{
+    Timestep::Timestep(float time)
+        : m_time {time}
+    {
+    }
+
+    float Timestep::getSeconds() const
+    {
+        return m_time;
+    }
+
+    float Timestep::getMilliseconds() const
+    {
+        return m_time * 1000.0f;
+    }
+}
\ No newline at end of file
diff --git a/GuiCore/Timestep.hpp b/GuiCore/Timestep.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6eb4053e3da5d9f2955fb80b523668613b8bba6e
--- /dev/null
+++ b/GuiCore/Timestep.hpp
@@ -0,0 +1,23 @@
+/// @file   Timestep.hpp
+/// @author Armin Co
+///
+
+#ifndef GUI_CORE_TIMESTEP_HPP
+#define GUI_CORE_TIMESTEP_HPP
+
+namespace GuiCore
+{
+    class Timestep
+    {
+    public:
+        Timestep(float time = 0.0f);
+    
+        float getSeconds() const;
+        float getMilliseconds() const;
+
+    private:
+        float m_time;
+    };
+}
+
+#endif
\ No newline at end of file
diff --git a/QPongApp/CMakeLists.txt b/QPongApp/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..99d4a3cc077976f3b64dbce5d42dfc07e3bb778f
--- /dev/null
+++ b/QPongApp/CMakeLists.txt
@@ -0,0 +1,25 @@
+add_executable( QPongApp 
+    GameLayer.cpp
+    Log.cpp
+    MainMenuLayer.cpp
+    QPongApp.cpp 
+    Window.cpp
+)
+
+target_include_directories( QPongApp PUBLIC
+    .
+    ..
+    ../libs
+    ../libs/imgui/examples/libs/gl3w
+)
+
+target_link_libraries( QPongApp PUBLIC 
+    GuiCore
+    fftw3
+    fftw3_omp
+)
+
+add_custom_command(TARGET QPongApp
+    POST_BUILD
+    COMMAND ln -sf ${PROJECT_SOURCE_DIR}/assets ${CMAKE_BINARY_DIR}/QPongApp
+)
\ No newline at end of file
diff --git a/QPongApp/GameLayer.cpp b/QPongApp/GameLayer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c2bcbd0b159d0bc854f923f88cb20023c94a8917
--- /dev/null
+++ b/QPongApp/GameLayer.cpp
@@ -0,0 +1,697 @@
+/// @file   GameLayer.cpp
+/// @author Armin Co
+///
+
+#include <GL/gl3w.h>
+#include <GLFW/glfw3.h>
+
+#include <imgui/imgui.h>
+
+#include <glm/gtc/type_ptr.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+
+#include <GuiCore/Log.hpp>
+#include <GuiCore/Input.hpp>
+#include <GuiCore/Glyphs.hpp>
+#include <GuiCore/Timer.hpp>
+
+#include "GameLayer.hpp"
+#include "Log.hpp"
+
+#include <string>
+#include <thread>
+
+
+
+double QPong::GameLayer::GameLayer::Bat::s_speedStepSize = 0.005;
+
+template <typename T>
+const T pow2(const T v)
+{
+    return v * v;
+}
+
+QPong::GameLayer::GameLayer::Bat::Bat(double x, double y, int keyUp, int keyDown)
+    : m_x{x}
+    , m_y{y}
+    , m_vx{0.0}
+    , m_vy{0.0}
+    , m_height{0.21}
+    , m_width{0.0}
+    , m_keyUp{keyUp}
+    , m_keyDown{keyDown}
+{
+
+}
+
+void QPong::GameLayer::GameLayer::Bat::onUpdate(GuiCore::Timestep ts)
+{
+    constexpr double speed = 20.0;
+    if (GuiCore::Input::isKeyPressed(m_keyDown))
+    {
+        m_vy += s_speedStepSize * speed; 
+    }
+    else if (GuiCore::Input::isKeyPressed(m_keyUp))
+    {
+        m_vy -= s_speedStepSize * speed;
+    }
+    else
+    {
+        m_vy = 0.0;
+    }
+
+    constexpr double maxSpeedFactor = 300.0;
+    if (m_vy > s_speedStepSize * maxSpeedFactor)
+    {
+        m_vy = s_speedStepSize * maxSpeedFactor;
+    }
+    if (m_vy < -s_speedStepSize * maxSpeedFactor)
+    {
+        m_vy = -s_speedStepSize * maxSpeedFactor;
+    }
+    m_y += (m_vy * ts.getSeconds());
+
+    constexpr double maxY {1.3};
+    if (m_y > maxY)
+    {
+        m_y = maxY;
+    }
+    if (m_y < -maxY)
+    {
+        m_y = -maxY;
+    }
+}
+
+double QPong::GameLayer::Bat::getPotential(double x, double y) const
+{
+    y -= m_y;
+    double dist = 0.0;
+    double scale = 0.0000001;
+
+    if (y >= m_height)
+    {
+        dist = pow2(x - m_x) + pow2(y - m_height) + m_width;
+    }
+    else if (y <= (-m_height))
+    {
+        dist = pow2(x - m_x) + pow2((-y) - m_height) + m_width;
+    }
+    else
+    {
+        dist = pow2(m_x - x) + m_width;
+    }
+    return 1.0 / (pow2(dist) ) * scale;
+}
+
+double QPong::GameLayer::Bat::getX() const
+{
+    return m_x;
+}
+
+QPong::GameLayer::GameLayer(QPong::GameLayer::Properties props)
+    :Layer("GameLayer")
+    , m_vertexShaderPath{"assets/shaders/particle.vert.glsl"}
+    , m_fragmentShaderPath{"assets/shaders/particle.frag.glsl"}
+    , m_arraySize{props.resolution}
+    , m_propagate{false}
+    , m_leftBat(-0.8, 0.0, Key::W, Key::S)
+    , m_rightBat(0.8, 0.0, Key::Up, Key::Down)
+    , m_speedScale{3.0f}
+    , m_randomMomentum {true}
+{
+    srand (time(NULL));
+    m_arrayElements = m_arraySize * m_arraySize;
+    m_numberOfQuads = (m_arraySize - 1) * (m_arraySize - 1);
+    m_numberOfIndices = m_numberOfQuads * 6;
+
+    m_initialParticleProperties.position.x = 0.0;
+    m_initialParticleProperties.position.y = 0.0;
+    m_initialParticleProperties.position.z = 0.0;
+    m_initialParticleProperties.momentum.x = 1.0;
+    m_initialParticleProperties.momentum.y = 0.5;
+    m_initialParticleProperties.momentum.z = 0.00;
+    m_initialParticleProperties.pointUnsharpness = 60.0;
+    m_initialParticleProperties.startValue = 0.01;
+    m_initialParticleProperties.hbar = 0.0002;
+}
+
+void QPong::GameLayer::calculatePointPositions(float *positions)
+{
+    constexpr float size{3.175f};
+    constexpr float xOffset{0.0f};
+    constexpr float yOffset{-0.175f};
+    
+    constexpr float width{size};
+    constexpr float xMin{-width / 2.0};
+    constexpr float xMax{width / 2.0};
+    constexpr float height{width};
+    constexpr float yMin{-height / 2.0};
+    constexpr float yMax{(height / 2.0)};
+
+    float dx = (xMax - xMin) / (m_arraySize - 1.0f); // "spacing"
+    float dy = (yMax - yMin) / (m_arraySize - 1.0f);
+    
+    uint32_t i {0}; // array index
+    for (int yId{0}; yId < m_arraySize; ++yId)
+    {
+        for (int xId{0}; xId < m_arraySize; ++xId)
+        {
+            positions[i++] = xMin + xOffset + (static_cast<float>(xId) * dx); // x-Pos
+            positions[i++] = yMax + yOffset - (static_cast<float>(yId) * dy); // y-Pos
+            positions[i++] = 0.0f;             // z-Pos
+        }
+    }
+}
+
+void QPong::GameLayer::calculateMomentumViewPositions(float *positions)
+{
+    constexpr float xOffset {1.5f}; // has to be the same as in the shader
+    float xMin {2.2f + xOffset};
+    float xMax {0.2f + xOffset};
+    float yMin {-1.0f};
+    float yMax {1.0f};
+    float dx = (xMax - xMin) / (m_arraySize - 1.0f); // "spacing"
+    float dy = (yMax - yMin) / (m_arraySize - 1.0f);
+    {
+        uint32_t i {0}; // array index
+        for (int yId{0}; yId < m_arraySize; ++yId)
+        {
+            for (int xId{0}; xId < m_arraySize; ++xId)
+            {
+                positions[i++] = xMin + (static_cast<float>(xId) * dx); // x-Pos
+                positions[i++] = yMax - (static_cast<float>(yId) * dy); // y-Pos
+                positions[i++] = 0.0f;             // z-Pos
+            }
+        }
+    }
+}
+
+void QPong::GameLayer::calculateTriangleIndices(uint32_t *indices)
+{
+    uint32_t i{0};
+    for (int row {0}; row < (m_arraySize - 1); ++row)
+    {
+        for (int k {0}; k < (m_arraySize - 1); ++k)
+        {
+            int offset = row * m_arraySize;
+            indices[i++] = offset + k;
+            indices[i++] = offset + k + 1;
+            indices[i++] = offset + k + m_arraySize;
+            indices[i++] = offset + k + 1;
+            indices[i++] = offset + k + m_arraySize;
+            indices[i++] = offset + k + m_arraySize + 1;
+        }
+    }
+}
+
+void QPong::GameLayer::calculateBorderPotential(float *potential)
+{
+    double y {0.0};
+    for (int i {0}; i < m_arrayElements; ++i)
+    {
+        y = m_yAt[i];
+        float pot = pow2(y) * pow2(y) * pow2(y) * pow2(y) * pow2(y) * pow2(y) * pow2(y) * 0.02;
+        potential[i] = pot;
+    }
+}
+
+double QPong::GameLayer::posAt(int pos) const
+{
+    return 2.0 * pos / m_arraySize - 1.0;
+}
+
+double  QPong::GameLayer::xAtIndex(int i) const
+{
+    return posAt(i % m_arraySize);
+}
+
+double QPong::GameLayer::yAtIndex(int i) const
+{
+    return posAt(i / m_arraySize);
+}
+
+     
+double QPong::GameLayer::potentialAt(int pos) const
+{
+    if (pos > m_arraySize / 2)
+    {
+        pos -= m_arraySize;
+    }
+    return pos * M_PI * m_initialParticleProperties.hbar;
+}
+
+double QPong::GameLayer::pxAtIndex(int i) const
+{
+    return potentialAt(i % m_arraySize);
+}
+
+double QPong::GameLayer::pyAtIndex(int i) const
+{
+    return potentialAt(i / m_arraySize);
+}
+
+
+
+void QPong::GameLayer::initialiseParticleMomentum()
+{
+    constexpr std::complex<double> ci {0.0, 1.0}; // complex number
+    const double hbar = m_initialParticleProperties.hbar;
+    const double A0 = m_initialParticleProperties.startValue;
+    const double px0 = m_initialParticleProperties.momentum.x / 20.0;
+    const double py0 = m_initialParticleProperties.momentum.y / 20.0;
+    const double x0 = m_initialParticleProperties.position.x;
+    const double y0 = m_initialParticleProperties.position.y;
+    const double lambda = m_initialParticleProperties.pointUnsharpness;
+
+    double sum {0.0};
+    m_particleAbsouluteSumStart = 0.0;
+    double y{0.0}; 
+    double x{0.0}; 
+    for (auto i {0}; i < m_arrayElements; ++i)
+    {
+        x = m_xAt[i];
+        y = m_yAt[i];
+        m_psi[i] = A0 * exp( ci / hbar * ( x * px0 + y * py0) - lambda * (pow2(x-x0) + pow2(y-y0)));
+        sum += pow2(std::real(m_psi[i])) + pow2(std::imag(m_psi[i]));
+    }
+    for (auto i {0}; i < m_arrayElements; ++i)
+    {
+        m_psi[i] *= (10.0 * (m_arraySize / 512.0)) / sqrt(sum);
+        m_particleAbsouluteSumStart += pow2(std::real(m_psi[i])) + pow2(std::imag(m_psi[i]));
+    }
+    Log::get()->trace("Particle initialised");
+}
+
+double QPong::GameLayer::absorbParticle(int i, double c)
+{
+    double normPrevious = pow2(std::real(m_psi[i])) + pow2(std::imag(m_psi[i]));
+    double absorb = pow2(std::cos(c * 10));
+    m_psi[i] *= absorb;
+    double delta = normPrevious - (pow2(std::real(m_psi[i])) + pow2(std::imag(m_psi[i])));
+    return delta;
+}
+
+void QPong::GameLayer::applyPotentials(int indexBegin, int indexEnd, double dt)
+{
+    constexpr std::complex<double> ci {0.0, 1.0};
+    const double hbar = m_initialParticleProperties.hbar;
+    double x {0.0};
+    double y {0.0};
+    const double potential = 1.0;
+    for (int i {indexBegin}; i < indexEnd; ++i)
+    {
+        double potSum = 1.0;
+        if (m_potentialsEnabled)
+        {
+            potSum = 0.0;
+            x = m_xAt[i];
+            y = m_yAt[i];
+            if (m_removeParticleEnabled)
+            {    
+                if (x < m_leftBat.getX())
+                {
+                    double distanceToEdge = -m_leftBat.getX() + x;
+                    double c = distanceToEdge * (M_PI / 2.0) / (1.0 + m_leftBat.getX());
+                    m_playerTwo.currentlyScored += absorbParticle(i, c);
+                }
+                if (x>m_rightBat.getX())
+                {
+                    double distanceToEdge = x - m_rightBat.getX();
+                    double c = distanceToEdge * (M_PI / 2.0) / (1.0 + m_rightBat.getX());
+                    m_playerOne.currentlyScored += absorbParticle(i, c);
+                }
+            }
+            double potLeftBat = m_leftBat.getPotential(x, y);
+            double potRightBat = m_rightBat.getPotential(x, y);
+            potSum = potLeftBat + potRightBat + m_staticBorderPotential[i];
+        }
+        auto psi = m_psi[i] * exp(-ci * dt/hbar * potential * potSum);
+        m_psi[i] = psi;
+        if (m_potentialsEnabled)
+        {
+            m_dynamicPotential[i] = potSum;
+        }
+    }
+}
+
+void QPong::GameLayer::moveForward(int indexBegin, int indexEnd, double dt)
+{
+    constexpr std::complex<double> ci {0.0, 1.0};
+    const double hbar = m_initialParticleProperties.hbar;
+    double px {0.0};
+    double py {0.0};
+    constexpr double m = 1.0;   
+    const double N = 1.0 / static_cast<double>(m_arrayElements);
+    for (int i{indexBegin}; i<indexEnd; ++i)
+    {
+        px = m_pxAt[i];
+        py = m_pyAt[i];
+        double E = (pow2(px) + pow2(py)) / (2.0 * m);
+        m_psi[i] *= exp(-ci * dt / hbar * E) * N;
+    }
+}
+
+void QPong::GameLayer::updateMomentumView(int indexBegin, int indexEnd)
+{
+    int x {0};
+    int y {0};
+    for (int i {indexBegin}; i< indexEnd; ++i)
+    {
+        x = i % m_arraySize;
+        y = i / m_arraySize;
+        auto halfArraySize = m_arraySize / 2;
+        
+        if (y > halfArraySize)
+        {
+            y = y - halfArraySize;
+        }
+        else
+        {
+            y = y + halfArraySize;
+        }
+
+        if (x > halfArraySize)
+        {
+            x = x - halfArraySize;
+        }
+        else
+        {
+            x = x + halfArraySize;
+        }
+        m_momentum[i] = m_psi[(y * m_arraySize )- x] * 100.0;
+    }
+}
+
+void QPong::GameLayer::propagateStep(double dt)
+{
+    applyPotentials(0, m_arrayElements, dt);
+
+    {
+        GuiCore::Timer t("fftw_execute(forward)");
+        fftw_execute(m_planForward);
+    }
+
+    moveForward(0, m_arrayElements, dt);
+
+    updateMomentumView(0, m_arrayElements);
+    {
+        GuiCore::Timer t("fftw_execute(backwards");
+        fftw_execute(m_planBackwards);
+    }
+}
+
+void QPong::GameLayer::onAttach()
+{
+    GuiCore::Timer timer("GameLayer::onAttach()");
+    GuiCore::Glyphs::setup();
+
+    m_xAt = new double[m_arrayElements];
+    m_yAt = new double[m_arrayElements];
+    m_pxAt = new double[m_arrayElements];
+    m_pyAt = new double[m_arrayElements];
+
+    for (int i {0}; i < m_arrayElements; ++i)
+    {
+        m_xAt[i] = xAtIndex(i);
+        m_yAt[i] = yAtIndex(i);
+        m_pxAt[i] = pxAtIndex(i);
+        m_pyAt[i] = pyAtIndex(i);
+    }
+
+    m_renderMomentum = false;
+    m_removeParticleEnabled = true;
+    m_potentialsEnabled = true;
+    m_playerOne.totalScore = 0;
+    m_playerTwo.totalScore = 0;
+
+    m_shader = GuiCore::Shader::fromGLSLTextFiles(
+        m_vertexShaderPath,
+        m_fragmentShaderPath
+    );
+
+    // static verticie positions
+    m_particleViewPositions = new float[m_arrayElements * 3];
+    calculatePointPositions(m_particleViewPositions);
+
+    // Vertex array to store all buffers
+    glGenVertexArrays(1, &m_vertexArray);
+    glBindVertexArray(m_vertexArray);
+
+    // Buffer of all point locations
+    glGenBuffers(1, &m_pointsBuffer);
+    glBindBuffer(GL_ARRAY_BUFFER, m_pointsBuffer);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * (m_arrayElements), m_particleViewPositions, GL_DYNAMIC_DRAW);
+    glEnableVertexAttribArray(0);
+    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, 0);
+
+    // Setup indies buffer
+    uint32_t *indices = new uint32_t[m_numberOfIndices];
+    calculateTriangleIndices(indices);
+
+    glGenBuffers(1, &m_indicesBuffer);
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indicesBuffer);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * m_numberOfIndices, indices, GL_STATIC_DRAW);
+    delete(indices);
+
+    // Setup particle buffer
+    m_memorySize = sizeof(fftw_complex) * m_arrayElements;
+    m_psi = static_cast<std::complex<double>*>(fftw_malloc(m_memorySize));
+    m_momentum = static_cast<std::complex<double>*>(fftw_malloc(m_memorySize));
+
+    glGenBuffers(1, &m_colorBuffer);
+    glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);
+    glBufferData(GL_ARRAY_BUFFER, m_memorySize, nullptr, GL_DYNAMIC_DRAW);
+    glEnableVertexAttribArray(1);
+    glVertexAttribPointer(1, 2, GL_DOUBLE, GL_FALSE, sizeof(fftw_complex), 0);
+
+    // Setup momentum view
+    m_momentumViewPositions = new float[m_arrayElements * 3];
+    calculateMomentumViewPositions(m_momentumViewPositions);
+
+    // Initialise Borders
+    m_staticBorderPotential = new float[m_arrayElements];
+    calculateBorderPotential(m_staticBorderPotential);
+    m_dynamicPotential = new float[m_arrayElements];
+    glGenBuffers(1, &m_potentialBuffer);
+    glBindBuffer(GL_ARRAY_BUFFER, m_potentialBuffer);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * m_arrayElements, m_staticBorderPotential, GL_STATIC_DRAW);
+    glEnableVertexAttribArray(2);
+    glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(float), 0);
+
+    // Set orthografic view projectin "camera" which will not change.
+    m_viewProjectionPosition = glm::ortho(-3.2f, 3.2f, -1.8f, 1.8f, -1.0f, 1.0f);
+    m_viewProjectionLocation = glGetUniformLocation(m_shader->getRendererID(), "u_viewProjection");
+    m_showPotentialsLocation = glGetUniformLocation(m_shader->getRendererID(), "u_showPotential");
+    m_isMomentumEnabled = glGetUniformLocation(m_shader->getRendererID(), "u_isMomentumEnabled");
+
+    fftw_init_threads();
+    m_planForward = fftw_plan_dft_2d(m_arraySize, m_arraySize, (fftw_complex *)m_psi, (fftw_complex *)m_psi, FFTW_FORWARD, FFTW_MEASURE);
+    m_planBackwards = fftw_plan_dft_2d(m_arraySize, m_arraySize, (fftw_complex *)m_psi, (fftw_complex *)m_psi, FFTW_BACKWARD, FFTW_MEASURE);
+    
+    reset();
+}
+
+void QPong::GameLayer::onDetach()
+{   
+    glDeleteVertexArrays(1, &m_vertexArray);
+    glDeleteBuffers(1, &m_pointsBuffer);
+    glDeleteBuffers(1, &m_indicesBuffer);
+    glDeleteBuffers(1, &m_colorBuffer);
+    fftw_free(m_psi);
+    delete(m_momentum);
+    delete(m_dynamicPotential);
+    delete(m_staticBorderPotential);
+    delete(m_xAt);
+    delete(m_yAt);
+    delete(m_pxAt);
+    delete(m_pyAt);
+}
+
+void QPong::GameLayer::onEvent(GuiCore::Event& event)
+{
+}
+
+void QPong::GameLayer::reset()
+{
+    if (m_randomMomentum)
+    {
+        int xRand = rand() % 100;
+        int yRand = rand() % 100;
+        int positive = rand() % 2;
+        m_initialParticleProperties.momentum.x = (1.0f - (2.0f * positive)) * (1.0 + (float)xRand / 120.0f);
+        positive = rand() % 2;
+        m_initialParticleProperties.momentum.y = (1.0f - (2.0f * positive)) * (1.0 + (float)yRand / 120.0f);
+    }
+    initialiseParticleMomentum();
+    m_restart = false;
+    m_gameCounts = true;
+    m_playerOne.currentlyScored = 0.0;
+    m_playerTwo.currentlyScored = 0.0;
+
+}
+
+void QPong::GameLayer::onUpdate(GuiCore::Timestep ts)
+{
+    if (m_restart)
+    {
+        reset();
+    }
+    if (m_propagate)
+    {
+        m_leftBat.onUpdate(ts);
+        m_rightBat.onUpdate(ts);
+        propagateStep(ts.getSeconds() * m_speedScale);
+
+        if (m_gameCounts)
+        {
+            if (m_playerOne.currentlyScored > 35.0)
+            {
+                m_playerOne.totalScore++;
+                m_gameCounts = false;
+                m_propagate = false;
+            }
+            if (m_playerTwo.currentlyScored > 35.0)
+            {
+                m_playerTwo.totalScore++;
+                m_gameCounts = false;
+                m_propagate = false;
+            }
+        }
+    }
+    
+    // Particle
+    glBindBuffer(GL_ARRAY_BUFFER, m_pointsBuffer);
+    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * m_arrayElements * 3, m_particleViewPositions);
+    
+    glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);
+    glBufferSubData(GL_ARRAY_BUFFER, 0, m_memorySize, m_psi);
+    
+    glBindBuffer(GL_ARRAY_BUFFER, m_potentialBuffer);
+    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * m_arrayElements, m_dynamicPotential);
+
+    glUseProgram(m_shader->getRendererID());
+    glUniform1i(m_showPotentialsLocation, static_cast<int>(m_potentialsEnabled));
+    glUniform1i(m_isMomentumEnabled, static_cast<int>(m_renderMomentum));
+    glUniformMatrix4fv(m_viewProjectionLocation, 1, GL_FALSE, &m_viewProjectionPosition[0][0]);
+    glBindVertexArray(m_vertexArray);
+    glDrawElements(GL_TRIANGLES, m_numberOfIndices, GL_UNSIGNED_INT, nullptr);
+
+    // Momentum
+    if (m_renderMomentum)
+    {    
+        glBindBuffer(GL_ARRAY_BUFFER, m_pointsBuffer);
+        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * m_arrayElements * 3, m_momentumViewPositions);
+        
+        glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);
+        glBufferSubData(GL_ARRAY_BUFFER, 0, m_memorySize, m_momentum);
+        
+        glUseProgram(m_shader->getRendererID());
+        glUniform1i(m_showPotentialsLocation, 0);
+        glBindVertexArray(m_vertexArray);
+        glDrawElements(GL_TRIANGLES, m_numberOfIndices, GL_UNSIGNED_INT, nullptr);
+    }
+
+    std::stringstream totalScore;
+    totalScore << m_playerOne.totalScore << " : " << m_playerTwo.totalScore;
+    GuiCore::Glyphs::renderText(totalScore.str(), -0.32f, 1.45f, 0.0075f, {1.0, 1.0, 1.0});
+}
+
+void QPong::GameLayer::onGuiRender()
+{
+    // GUI settings for the particle
+    ImGui::Begin("Particle Settings");
+    
+    // Select initial momentum for particle at restart
+    ImGui::DragFloat2("Momentum [x,y]", glm::value_ptr(m_initialParticleProperties.momentum), 0.01f, -3.0f, 3.0f);
+    ImGui::Checkbox("Random Momentum", &m_randomMomentum);
+
+    float pointUnsharpness = m_initialParticleProperties.pointUnsharpness;
+    if (ImGui::DragFloat("Sharpness", &pointUnsharpness, 1.0f, 5.0f, 1000.0f))
+    {
+        m_initialParticleProperties.pointUnsharpness = pointUnsharpness;
+    }
+    // Select hbar, changes apply on the fly.
+    float hbar = m_initialParticleProperties.hbar;
+    if (ImGui::DragFloat("hbar", &hbar, 0.000001f, 0.000001f, 1.0f, "%.6f"))
+    {
+        m_initialParticleProperties.hbar = hbar;
+    }
+
+    // This value increases or decreases the time step made every iteration.
+    ImGui::DragFloat("Speed Factor", &m_speedScale, 0.1, 0.1, 20.0);
+    if (ImGui::IsItemHovered()) {
+        ImGui::SetTooltip("Change the size of the timestep (dt)\nfor every iteration of the simulation.");
+    }
+
+    // Decrease the 
+    if (ImGui::Button("Less threads"))
+    {
+        if (m_renderingThreadsNumber > 1)
+        {
+            bool isPowerOf2 = false;
+            while (!isPowerOf2)
+            {
+                m_renderingThreadsNumber--;
+                for (int i = 0; i < 10; i++)
+                {
+                    if (m_renderingThreadsNumber == pow(2, i))
+                    {
+                        isPowerOf2 = true;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    ImGui::SameLine();
+    if (ImGui::Button("More Threads"))
+    {
+        if (m_renderingThreadsNumber <  200)
+        {
+            bool isPowerOf2 = false;
+            while (!isPowerOf2)
+            {
+                m_renderingThreadsNumber++;
+                for (int i = 0; i < 10; i++)
+                {
+                    if (m_renderingThreadsNumber == pow(2, i))
+                    {
+                        isPowerOf2 = true;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    ImGui::SameLine();
+    ImGui::Text("%d", m_renderingThreadsNumber);
+    ImGui::Checkbox("Render Momentum", &m_renderMomentum);
+    ImGui::Checkbox("Enable Potentials", &m_potentialsEnabled);
+    ImGui::Checkbox("Enable Particle absorption", &m_removeParticleEnabled);
+    // Start/Stop propagating
+    if (m_propagate)
+    {
+        if (ImGui::Button("Pause"))
+        {
+            m_propagate = false;
+        }
+    }
+    else
+    {
+        if (ImGui::Button("Play"))
+        {
+            m_propagate = true;
+        }
+    }
+    // Restart game
+    if (ImGui::Button("Restart"))
+    {
+        m_restart = true;
+    }
+    ImGui::Text("Player 1: %d", m_playerOne.totalScore); ImGui::SameLine();
+    ImGui::Text(" | Live Score: %.1f\%%",  m_playerOne.currentlyScored);
+    
+    ImGui::Text("Player 2: %d", m_playerTwo.totalScore); ImGui::SameLine();
+    ImGui::Text(" | Live Score: %.1f\%%",  m_playerTwo.currentlyScored);
+    ImGui::End();
+}
\ No newline at end of file
diff --git a/QPongApp/GameLayer.hpp b/QPongApp/GameLayer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..296c08a7e18504fa4a4746a6353ab5fdfdd11fef
--- /dev/null
+++ b/QPongApp/GameLayer.hpp
@@ -0,0 +1,146 @@
+/// @file   GameLayer.hpp
+/// @author Armin Co
+///
+
+#ifndef QPONG_GAME_LAYER_HPP
+#define QPONG_GAME_LAYER_HPP
+
+#include <glm/glm.hpp>
+
+#include <GuiCore/Layer.hpp>
+#include <GuiCore/Shader.hpp>
+#include <GuiCore/Timestep.hpp>
+
+#include <complex>
+#include <fftw3.h>
+
+namespace QPong
+{
+class GameLayer : public GuiCore::Layer
+{
+public:
+    struct Properties
+    {
+        int resolution;
+        Properties(int res) : resolution {res}{};
+    };
+
+    struct ParticleProps
+    {
+        glm::vec3 position;
+        glm::vec3 momentum;
+        double pointUnsharpness;
+        double startValue;
+        double hbar;
+    };
+
+    struct Player
+    {
+        double currentlyScored;
+        int totalScore;
+    };
+
+    class Bat
+    {
+    public:
+        Bat(double x, double y, int keyUp, int keyDown);
+        double getX() const;
+        double getY() const;
+        void onUpdate(GuiCore::Timestep ts);
+        double getPotential(double x, double y) const;
+    private:
+        double m_x;
+        double m_y;
+        double m_vx;
+        double m_vy;
+        double m_height;
+        double m_width;
+        int m_keyUp;
+        int m_keyDown;
+        static double s_speedStepSize;
+    };
+
+    GameLayer(Properties props);
+    virtual ~GameLayer(){};
+    virtual void onAttach() override;
+    virtual void onDetach() override;
+    virtual void onUpdate(GuiCore::Timestep ts) override;
+    virtual void onGuiRender() override;
+    virtual void onEvent(GuiCore::Event &event) override;
+
+private:
+    void reset();
+
+    void calculatePointPositions(float *points);
+    void calculateMomentumViewPositions(float *points);
+    void calculateTriangleIndices(uint32_t *indices);
+    void calculateBorderPotential(float *potential);
+    void initialiseParticleMomentum();
+    void propagateStep(double dt);
+    void applyPotentials(int indexBegin, int indexEnd, double dt);
+    void moveForward(int indexBegin, int indexEnd, double dt);
+    void updateMomentumView(int indexBegin, int indexEnd);
+    double absorbParticle(int i, double c);
+    // positions at array index
+    inline double posAt(int pos) const;
+    inline double xAtIndex(int i) const;
+    inline double yAtIndex(int i) const;
+    inline double potentialAt(int pos) const;
+    inline double pxAtIndex(int i) const;
+    inline double pyAtIndex(int i) const;
+
+    GuiCore::Shader *m_shader;
+    std::string m_vertexShaderPath;
+    std::string m_fragmentShaderPath;
+
+    int m_arraySize;
+    int m_arrayElements;
+    int m_numberOfQuads;
+    int m_numberOfIndices;
+
+    GLuint m_vertexArray;       ///< OpenGL object that "knows" all buffers.
+    GLuint m_pointsBuffer;      ///< Buffer with the location for each element in the particle array
+    GLuint m_indicesBuffer;     ///< Indicies to draw trinagles from the points to fill the "canvas"
+    GLuint m_colorBuffer;       ///< Reference for OpenGL to load the paritcle data to the GPU
+    GLuint m_potentialBuffer;   ///< Borders and bats
+
+    float *m_particleViewPositions;
+    float *m_momentumViewPositions;
+    float *m_staticBorderPotential;
+    float *m_dynamicPotential;
+
+    double *m_xAt;
+    double *m_yAt;
+    double *m_pxAt;
+    double *m_pyAt;
+
+    glm::mat4 m_viewProjectionPosition;
+    int m_viewProjectionLocation;
+    GLuint m_showPotentialsLocation;
+    GLuint m_isMomentumEnabled;
+
+    ParticleProps m_initialParticleProperties;
+    double m_particleAbsouluteSumStart;
+    fftw_plan m_planForward;
+    fftw_plan m_planBackwards;
+
+    Bat m_leftBat;
+    Bat m_rightBat;
+    Player m_playerOne;
+    Player m_playerTwo;
+    bool m_gameCounts;
+
+    bool m_restart;
+    bool m_propagate;
+    bool m_renderMomentum;
+    bool m_randomMomentum;
+    bool m_removeParticleEnabled;
+    bool m_potentialsEnabled;
+    int m_renderingThreadsNumber {8};
+    size_t m_memorySize {0};
+    float m_speedScale {1.0f};
+    std::complex<double> *m_psi;
+    std::complex<double> *m_momentum;
+};
+}
+#endif
\ No newline at end of file
diff --git a/QPongApp/Log.cpp b/QPongApp/Log.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f410f33ea88a41311f9e1f8925c3068c1958e071
--- /dev/null
+++ b/QPongApp/Log.cpp
@@ -0,0 +1,23 @@
+/// @file   Log.cpp
+/// @author Armin Co
+///
+
+#include "Log.hpp"
+
+#include <spdlog/sinks/stdout_color_sinks.h>
+
+using namespace QPong;
+
+std::shared_ptr<spdlog::logger> Log::s_logger;
+
+void Log::setup()
+{
+    spdlog::set_pattern("%^[%T] %n: %v%$");
+    s_logger = spdlog::stdout_color_mt("QPong");
+    s_logger->set_level(spdlog::level::info);
+}
+
+std::shared_ptr<spdlog::logger> Log::get()
+{
+    return s_logger;
+}
diff --git a/QPongApp/Log.hpp b/QPongApp/Log.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5a196a1f3a74ebb969c72c55060670de1f68c841
--- /dev/null
+++ b/QPongApp/Log.hpp
@@ -0,0 +1,25 @@
+/// @file   Log.hpp
+/// @author Armin Co
+///
+
+#ifndef QPONG_LOG_HPP
+#define QPONG_LOG_HPP
+
+#include <memory>
+
+#include <spdlog/spdlog.h>
+
+namespace QPong
+{
+    class Log
+    {
+    public:
+        static void setup();
+        static std::shared_ptr<spdlog::logger> get();
+
+    private:
+        static std::shared_ptr<spdlog::logger> s_logger;
+    };
+}
+
+#endif
\ No newline at end of file
diff --git a/QPongApp/MainMenuLayer.cpp b/QPongApp/MainMenuLayer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..75bd232c009e55911b465768543f3b3f2bd7c55d
--- /dev/null
+++ b/QPongApp/MainMenuLayer.cpp
@@ -0,0 +1,136 @@
+/// @file   MainMenuLayer.cpp
+/// @author Armin Co
+///
+
+#include "MainMenuLayer.hpp"
+
+#include <imgui/imgui.h>
+#include <GuiCore/Application.hpp>
+
+#include "GameLayer.hpp"
+#include <glm/gtc/type_ptr.hpp>
+
+using namespace QPong;
+
+MainMenuLayer::MainMenuLayer()
+    : m_selectedResolution {512}
+    , m_powOfResolution {9}
+    , m_forceIndividualResolution {false}
+    , m_resetOnce {false} 
+    , m_gameAttached {false}
+{
+}
+
+void MainMenuLayer::onAttach()
+{
+    glEnable(GL_DEPTH_TEST);
+    glDepthFunc(GL_LESS);
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    m_backgroundColor = {0.2f, 0.2f, 0.2f};
+    createNewGame();
+}
+
+void MainMenuLayer::onDetach()
+{
+
+}
+
+void MainMenuLayer::createNewGame()
+{
+     m_resetOnce = false;
+    if (m_gameAttached)
+    {
+        GuiCore::Application::get().popLayer(m_gameLayer);
+        m_gameAttached = false;
+    }
+    m_gameLayer = std::make_shared<GameLayer>(m_selectedResolution);
+    GuiCore::Application::get().pushLayer(m_gameLayer);
+    m_gameAttached = true;
+}
+
+void MainMenuLayer::onUpdate(GuiCore::Timestep ts)
+{
+    m_fps = 1000.0f / ts.getMilliseconds();
+    glClearColor(m_backgroundColor.r, m_backgroundColor.g, m_backgroundColor.b, 1.0f);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    if (m_resetOnce)
+    {
+       createNewGame();
+    }
+}
+
+#include <math.h>
+
+void MainMenuLayer::guiShowFPS()
+{
+    ImGui::Text("FPS: %3.1f", m_fps);
+}
+
+void MainMenuLayer::guiSelectResolution()
+{
+    constexpr int minPowOfResolution {4};
+    constexpr int maxPowOfResolution {12};
+
+    if (!m_forceIndividualResolution)
+    {
+        std::stringstream ssRes;
+        ssRes << "Array resoultion: ";
+        ssRes << m_selectedResolution << "x" << m_selectedResolution << std::endl;
+        ImGui::Text(ssRes.str().c_str());
+        
+        if (ImGui::Button("Lower Resolution"))
+        {
+            m_powOfResolution--;
+        }
+        ImGui::SameLine();
+        if (ImGui::Button("Higher Resolution"))
+        {
+            m_powOfResolution++;
+        }
+        m_powOfResolution = (m_powOfResolution < minPowOfResolution) ? minPowOfResolution : m_powOfResolution;
+        m_powOfResolution = (m_powOfResolution > maxPowOfResolution) ? maxPowOfResolution : m_powOfResolution;
+        
+        m_selectedResolution = std::pow<int>(2, m_powOfResolution);
+    }
+    else
+    {
+        constexpr int maxResolution = std::pow<int>(2, maxPowOfResolution);
+        ImGui::DragInt("Forced Resolution", &m_selectedResolution, 1, 32, maxResolution);
+    }
+    ImGui::Checkbox("Force other resolution", &m_forceIndividualResolution);
+    if (ImGui::IsItemHovered())
+    {
+        ImGui::SetTooltip("Warning!\nSelecting other resoultions can decrease the performance significantly.");
+    }
+}
+
+void MainMenuLayer::guiResetGame()
+{
+    if (ImGui::Button("Reset Game")){ m_resetOnce = true; }
+
+    if (ImGui::IsItemHovered()) {
+        ImGui::SetTooltip("Reinitialise Simulation with selected array resolution.");
+    }
+}
+
+
+void MainMenuLayer::onGuiRender()
+{
+    // Main menu controls and info
+    ImGui::Begin("Main Menu");
+    
+    guiShowFPS();
+    guiSelectResolution();
+    guiResetGame();
+
+    ImGui::ColorEdit3("Background Color", glm::value_ptr(m_backgroundColor));
+
+    ImGui::End();
+}
+
+void MainMenuLayer::onEvent(GuiCore::Event &event)
+{
+
+}
diff --git a/QPongApp/MainMenuLayer.hpp b/QPongApp/MainMenuLayer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4105159e42d4c6abc8c553d854b35d61df78e97a
--- /dev/null
+++ b/QPongApp/MainMenuLayer.hpp
@@ -0,0 +1,48 @@
+/// @file   MainMenuLayer.hpp
+/// @author Armin Co
+///
+
+#ifndef QPONG_MAIN_MENU_LAYER_HPP
+#define QPONG_MAIN_MENU_LAYER_HPP
+
+#include <GuiCore/Layer.hpp>
+#include <GuiCore/Timestep.hpp>
+
+#include <glm/glm.hpp>
+
+namespace QPong
+{
+/// @brief The main menu holds the controls to restart the game
+///         with different simulation settings.
+///
+class MainMenuLayer : public GuiCore::Layer
+{
+public:
+    MainMenuLayer();
+    virtual ~MainMenuLayer(){};
+    virtual void onAttach() override;
+    virtual void onDetach() override;
+    virtual void onUpdate(GuiCore::Timestep ts) override;
+    virtual void onGuiRender() override;
+    virtual void onEvent(GuiCore::Event &event) override;
+
+private:
+    void createNewGame();
+
+    void guiShowFPS();
+    void guiSelectResolution();
+    void guiResetGame();
+
+    // Resolution
+    int m_powOfResolution; ///< choose only from resolutions with 2^pow
+    int m_selectedResolution; ///< selected res in the gui
+    bool m_forceIndividualResolution; ///< Override resoultion
+    
+    bool m_resetOnce; ///< restart simulation if set
+    std::shared_ptr<GuiCore::Layer> m_gameLayer; ///< holds ref to the game
+    bool m_gameAttached;
+    float m_fps;
+    glm::vec3 m_backgroundColor;
+};
+}
+#endif
\ No newline at end of file
diff --git a/QPongApp/QPongApp.cpp b/QPongApp/QPongApp.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1c6a6b70c6021564763b072a72aa860f07b5dc89
--- /dev/null
+++ b/QPongApp/QPongApp.cpp
@@ -0,0 +1,31 @@
+/// @file   QPongApp.cpp
+/// @author Armin Co
+///
+
+#include <memory>
+
+#include <GuiCore/Application.hpp>
+
+#include "MainMenuLayer.hpp"
+#include "GameLayer.hpp"
+#include "Log.hpp"
+class App : public GuiCore::Application
+{
+public:
+    App()
+        : Application("QPong Application")
+    {
+        QPong::Log::setup();
+        pushLayer(std::make_shared<QPong::MainMenuLayer>());
+    }
+};
+
+int main()
+{
+    std::unique_ptr<App> app = std::make_unique<App>();
+    
+    QPong::Log::get()->info("Have fun!");
+    
+    app->run();
+    return 0;
+}
\ No newline at end of file
diff --git a/QPongApp/Window.cpp b/QPongApp/Window.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e20adb3de76a10731a4bb297efff1a9f683eadf
--- /dev/null
+++ b/QPongApp/Window.cpp
@@ -0,0 +1,128 @@
+/// @file   Window.cpp
+/// @author Armin Co
+///
+
+
+#include <GL/gl3w.h> 
+#include <GuiCore/Log.hpp>
+#include <GuiCore/ApplicationEvent.hpp>
+
+#include "Window.hpp"
+
+
+static bool s_GLFW_Initialized = false;
+
+static void GLFWErrorCallback(int error, const char *description)
+{
+    /// @todo add GuiCore::Logger
+}
+
+GuiCore::IWindow* GuiCore::IWindow::create(const WindowProperties &properties)
+{
+    return new Window(properties);
+}
+
+Window::Window(const GuiCore::WindowProperties &properties)
+{
+    setup(properties);
+}
+
+Window::~Window()
+{
+    shutdown();
+}
+
+void Window::setup(const GuiCore::WindowProperties &properties)
+{
+    m_data.title = properties.title;
+    m_data.width = properties.width;
+    m_data.height = properties.height;
+
+    if (!s_GLFW_Initialized)
+    {
+        glfwSetErrorCallback(GLFWErrorCallback);
+        auto success = glfwInit();
+        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); 
+
+        s_GLFW_Initialized = true;
+    }
+
+    m_window = glfwCreateWindow(properties.width, properties.height, m_data.title.c_str(), nullptr, nullptr);
+    
+    glfwMakeContextCurrent(m_window);
+    glfwSetWindowUserPointer(m_window, &m_data);
+
+    setVSync(true);
+
+    gl3wInit();
+
+    glfwSetWindowSizeCallback(m_window, [](GLFWwindow * window, int width, int height)
+    {
+        Data &data = *(Data*)glfwGetWindowUserPointer(window);
+        data.width = width;
+        data.height = height;
+
+        GuiCore::WindowResizeEvent event(width, height);
+        data.eventCallback(event);
+    });
+
+    using namespace GuiCore;
+    glfwSetWindowCloseCallback(m_window, [](GLFWwindow* window)
+    {
+        Data &data = *(Data*)glfwGetWindowUserPointer(window);
+        WindowCloseEvent event;
+        data.eventCallback(event);
+    });
+}
+
+void Window::shutdown()
+{
+    glfwDestroyWindow(m_window);
+}
+
+void Window::onUpdate()
+{
+    glfwPollEvents();
+    glfwSwapBuffers(m_window);
+}
+
+void Window::setVSync(bool enabled)
+{
+    if (enabled)
+    {
+        glfwSwapInterval(1);
+    }
+    else
+    {
+        glfwSwapInterval(0);
+    }
+    m_data.vSyncEnabled = enabled;
+}
+
+bool Window::isVSyncEnabled() const
+{
+    return m_data.vSyncEnabled;
+}
+
+uint32_t Window::getWidth() const
+{
+    return m_data.width;
+}
+
+uint32_t Window::getHeight() const
+{
+    return m_data.height;
+}
+
+void* Window::getNativeWindow() const
+{
+    return m_window;
+}
+
+void Window::setEventCallback(const std::function<void(GuiCore::Event&)> callback)
+{
+    GuiCore::Log::get()->trace("Window::setEventCallback");
+    m_data.eventCallback = callback;
+}
\ No newline at end of file
diff --git a/QPongApp/Window.hpp b/QPongApp/Window.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8935dc4bf86327900adc4f535b39fdcc09c3afc8
--- /dev/null
+++ b/QPongApp/Window.hpp
@@ -0,0 +1,48 @@
+/// @file   Window.hpp
+/// @author Armin Co
+///
+
+#ifndef QPONG_APP_WINDOW_HPP
+#define QPONG_APP_WINDOW_HPP
+
+
+#include <GLFW/glfw3.h>
+#include <GuiCore/IWindow.hpp>
+
+#include <GuiCore/Event.hpp>
+
+class Window : public GuiCore::IWindow
+{
+public:
+    Window(const GuiCore::WindowProperties &properties);
+    virtual ~Window();
+
+    void onUpdate() override;
+
+    uint32_t getWidth() const override;
+    uint32_t getHeight() const override;
+
+    virtual void setEventCallback(const std::function<void(GuiCore::Event&)>) override;
+    void setVSync(bool enabled) override;
+    bool isVSyncEnabled() const override;
+
+    void* getNativeWindow() const override;
+
+private:
+    virtual void setup(const GuiCore::WindowProperties &properties);
+    virtual void shutdown();
+
+    GLFWwindow* m_window;
+
+    struct Data
+    {
+        std::string title;
+        uint32_t width;
+        uint32_t height;
+        bool vSyncEnabled;
+
+        std::function<void(GuiCore::Event&)> eventCallback;
+    };
+    Data m_data;
+};
+#endif
\ No newline at end of file
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..6ec03a6f60161fb0bd80b72bf6365dd95451536f
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,29 @@
+# QPong
+
+## Clone
+The QPong repository includes two submodules (spdlog and imgui) which can be easily pulled while cloning with:
+```
+git clone https://gitlab.cvh-server.de/aco/qpong --recursive
+```
+
+## Build
+The required packages are listed in the `dependencies` file.
+On Debian based systems you can run the following command to install all the required packages.
+```
+cd qpong
+cat dependencies | xargs sudo apt install
+```
+Please let me know if you needed to install any additional packages on your specific system.
+
+
+```
+mkdir build && cd build
+cmake ..
+make -j14
+cd ..
+```
+
+## Run
+```
+./build/QPongApp/QPongApp
+```
\ No newline at end of file
diff --git a/assets/Font.ttf b/assets/Font.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..6652fa7007b13ac3f6f3954dd6d1d39ba8564aae
Binary files /dev/null and b/assets/Font.ttf differ
diff --git a/assets/shaders/glyph.frag.glsl b/assets/shaders/glyph.frag.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..abb0c7d56d62636669542def466588957f7dad64
--- /dev/null
+++ b/assets/shaders/glyph.frag.glsl
@@ -0,0 +1,11 @@
+#version 330 core
+in vec2 textureCoordinates;
+out vec4 color;
+
+uniform sampler2D text;
+uniform vec3 textColor;
+
+void main()
+{
+    color = vec4(textColor, texture(text, textureCoordinates).r) ;
+}
\ No newline at end of file
diff --git a/assets/shaders/glyph.vert.glsl b/assets/shaders/glyph.vert.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..db2695ffb8f3a297108d9dceeb96b6e6d93d255f
--- /dev/null
+++ b/assets/shaders/glyph.vert.glsl
@@ -0,0 +1,11 @@
+#version 330 core
+layout (location = 0) in vec4 vertex;
+out vec2 textureCoordinates;
+
+uniform mat4 projection;
+
+void main()
+{
+    gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
+    textureCoordinates = vertex.zw;
+}
\ No newline at end of file
diff --git a/assets/shaders/particle.frag.glsl b/assets/shaders/particle.frag.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..e3749afd34cf18193600ea9cd2e4f71b470fa899
--- /dev/null
+++ b/assets/shaders/particle.frag.glsl
@@ -0,0 +1,7 @@
+#version 330 core
+in vec3 fragmentColor;
+out vec4 pixelColor;
+void main()
+{
+    pixelColor = vec4(fragmentColor, 1.0);
+}
\ No newline at end of file
diff --git a/assets/shaders/particle.vert.glsl b/assets/shaders/particle.vert.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..61a8b04c98e6a7002a7de9fef988a48bda95c7fa
--- /dev/null
+++ b/assets/shaders/particle.vert.glsl
@@ -0,0 +1,40 @@
+#version 330 core
+// position to draw
+layout (location = 0) in vec3 v_position;
+// value of the particle to calculate color
+layout (location = 1) in vec2 v_particle_complex;
+// potential at this position
+layout (location = 2) in float potential;
+
+// orthografic projection matrix
+uniform mat4 u_viewProjection;
+// "bool" show potential or not
+uniform int u_showPotential;
+// "bool" if momentum view is enabled
+uniform int u_isMomentumEnabled;
+
+// color for the fragment shader
+out vec3 fragmentColor;
+
+void main()
+{
+    // Move the particle view to the left if the momentum is shown too.
+    vec3 pos = vec3(v_position.x - (1.5f * u_isMomentumEnabled), v_position.y, v_position.z);
+
+    // Determine final postion
+    gl_Position = u_viewProjection * vec4(pos, 1.0f);
+    
+    // Calculate "color" value of the momentum
+    float pot = potential * 120.0 * u_showPotential;
+
+    // Generate color from data
+    float real = v_particle_complex[0];
+    float imag = v_particle_complex[1];
+    float real2 = real * real;
+    float imag2 = imag * imag;
+    
+    fragmentColor = vec3(
+                        sqrt(real2) * 5.0 + pot,                            // red
+                        sqrt(real2) * 5.0 + sqrt(imag2) * 5.0 + pot / 2.0,  // green
+                        sqrt(imag2) * 5.0);                                 // blue
+}
\ No newline at end of file
diff --git a/dependencies b/dependencies
new file mode 100644
index 0000000000000000000000000000000000000000..71359605660f5f6f10e76008ee9665179c117471
--- /dev/null
+++ b/dependencies
@@ -0,0 +1,8 @@
+build-essential
+cmake
+libfreetype-dev
+libopengl-dev
+libglx-dev
+libglfw3-dev
+libglm-dev
+libfftw3-dev