diff --git a/UnitTests/VorbGraphics.cpp b/UnitTests/VorbGraphics.cpp index cc647318a..9f59c541d 100644 --- a/UnitTests/VorbGraphics.cpp +++ b/UnitTests/VorbGraphics.cpp @@ -33,19 +33,19 @@ struct ImageTestFormats { class ImageViewer : public vui::IGameScreen { public: - virtual i32 getNextScreen() const { + virtual i32 getNextScreen() const override { return SCREEN_INDEX_NO_SCREEN; } - virtual i32 getPreviousScreen() const { + virtual i32 getPreviousScreen() const override { return SCREEN_INDEX_NO_SCREEN; } - virtual void build() { + virtual void build() override { } - virtual void destroy(const vui::GameTime& gameTime) { + virtual void destroy(const vui::GameTime& gameTime) override { } - virtual void onEntry(const vui::GameTime& gameTime) { + virtual void onEntry(const vui::GameTime& gameTime) override { m_imageFormat = m_testFormats[0]; m_hooks.addAutoHook(vui::InputDispatcher::window.onFile, [&] (Sender, const vui::WindowFileEvent& e) { auto bmp = vg::ImageIO().load(e.file, m_imageFormat.format); @@ -95,13 +95,16 @@ class ImageViewer : public vui::IGameScreen { vg::SamplerState::LINEAR_WRAP.set(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } - virtual void onExit(const vui::GameTime& gameTime) { + virtual void onExit(const vui::GameTime& gameTime) override { m_sb.dispose(); if (m_bmp.data) vg::ImageIO::free(m_bmp); if (m_tex.id != 0) glDeleteTextures(1, &m_tex.id); } - virtual void update(const vui::GameTime& gameTime) { + virtual void registerRendering(vg::Renderer& renderer) override { + } + + virtual void update(const vui::GameTime& gameTime) override { if (m_bmp.data) { glBindTexture(GL_TEXTURE_2D, m_tex.id); m_tex.width = m_bmp.width; @@ -117,7 +120,7 @@ class ImageViewer : public vui::IGameScreen { m_bmp = {}; } } - virtual void draw(const vui::GameTime& gameTime) { + virtual void onRenderFrame(const vui::GameTime& gameTime) override { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -183,26 +186,29 @@ class FontViewer : public vui::IGameScreen { return SCREEN_INDEX_NO_SCREEN; } - virtual void build() { + virtual void build() override { // Empty } - virtual void destroy(const vui::GameTime& gameTime) { + virtual void destroy(const vui::GameTime& gameTime) override { // Empty } - virtual void onEntry(const vui::GameTime& gameTime) { + virtual void onEntry(const vui::GameTime& gameTime) override { batch.init(); font.init("Data/chintzy.ttf", 32); } - virtual void onExit(const vui::GameTime& gameTime) { + virtual void onExit(const vui::GameTime& gameTime) override { batch.dispose(); font.dispose(); } - virtual void update(const vui::GameTime& gameTime) { + virtual void registerRendering(vg::Renderer& renderer) override { + } + + virtual void update(const vui::GameTime& gameTime) override { // Empty } - virtual void draw(const vui::GameTime& gameTime) { + virtual void onRenderFrame(const vui::GameTime& gameTime) override { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); batch.begin(); @@ -217,14 +223,14 @@ class FontViewer : public vui::IGameScreen { class TorusViewer : public vui::IGameScreen { public: - virtual i32 getNextScreen() const { + virtual i32 getNextScreen() const override { return SCREEN_INDEX_NO_SCREEN; } - virtual i32 getPreviousScreen() const { + virtual i32 getPreviousScreen() const override { return SCREEN_INDEX_NO_SCREEN; } - virtual void build() { + virtual void build() override { pitchInput = 0; yawInput = 0; pool.addAutoHook(vui::InputDispatcher::key.onKeyDown, [&] (Sender, const vui::KeyEvent& e) { @@ -245,11 +251,11 @@ class TorusViewer : public vui::IGameScreen { } }); } - virtual void destroy(const vui::GameTime& gameTime) { + virtual void destroy(const vui::GameTime& gameTime) override { pool.dispose(); } - virtual void onEntry(const vui::GameTime& gameTime) { + virtual void onEntry(const vui::GameTime& gameTime) override { glGenBuffers(1, &verts); glGenBuffers(1, &inds); glGenVertexArrays(1, &vdecl); @@ -307,7 +313,7 @@ void main() { spriteBatch.init(); } - virtual void onExit(const vui::GameTime& gameTime) { + virtual void onExit(const vui::GameTime& gameTime) override { glDeleteBuffers(1, &verts); glDeleteBuffers(1, &inds); glDeleteVertexArrays(1, &vdecl); @@ -315,13 +321,16 @@ void main() { program.dispose(); } - virtual void update(const vui::GameTime& gameTime) { + virtual void registerRendering(vg::Renderer& renderer) override { + } + + virtual void update(const vui::GameTime& gameTime) override { yaw += (f32)(gameTime.elapsed * yawInput); pitch += (f32)(gameTime.elapsed * pitchInput); yaw = fmod(yaw + 6.28f, 6.28f); pitch = fmod(pitch + 6.28f, 6.28f); } - virtual void draw(const vui::GameTime& gameTime) { + virtual void onRenderFrame(const vui::GameTime& gameTime) override { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); f32v3 eye; @@ -376,21 +385,21 @@ void main() { class SpriteBatchTester : public vui::IGameScreen { public: - virtual i32 getNextScreen() const { + virtual i32 getNextScreen() const override { return SCREEN_INDEX_NO_SCREEN; } - virtual i32 getPreviousScreen() const { + virtual i32 getPreviousScreen() const override { return SCREEN_INDEX_NO_SCREEN; } - virtual void build() { + virtual void build() override { // Empty } - virtual void destroy(const vui::GameTime& gameTime) { + virtual void destroy(const vui::GameTime& gameTime) override { // Empty } - virtual void onEntry(const vui::GameTime& gameTime) { + virtual void onEntry(const vui::GameTime& gameTime) override { batch.init(); // Init sprites @@ -414,14 +423,17 @@ class SpriteBatchTester : public vui::IGameScreen { vg::SamplerState::POINT_WRAP.set(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } - virtual void onExit(const vui::GameTime& gameTime) { + virtual void onExit(const vui::GameTime& gameTime) override { batch.dispose(); } - virtual void update(const vui::GameTime& gameTime) { + virtual void registerRendering(vg::Renderer& renderer) override { + } + + virtual void update(const vui::GameTime& gameTime) override { angle += 0.1f; } - virtual void draw(const vui::GameTime& gameTime) { + virtual void onRenderFrame(const vui::GameTime& gameTime) override { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); timer.start(); @@ -521,19 +533,19 @@ class AnimViewer : public vui::IGameScreen { mWorld[bone.index] = mWorld[bone.index] * mRestInv[bone.index]; } - virtual i32 getNextScreen() const { + virtual i32 getNextScreen() const override { return SCREEN_INDEX_NO_SCREEN; } - virtual i32 getPreviousScreen() const { + virtual i32 getPreviousScreen() const override { return SCREEN_INDEX_NO_SCREEN; } - virtual void build() { + virtual void build() override { } - virtual void destroy(const vui::GameTime& gameTime) { + virtual void destroy(const vui::GameTime& gameTime) override { } - virtual void onEntry(const vui::GameTime& gameTime) { + virtual void onEntry(const vui::GameTime& gameTime) override { std::unordered_map attrmap; vg::VertexAttributeIndexed vai; vai.type = vg::VertexAttributeUsage::Position; @@ -665,7 +677,7 @@ void main() { glClearColor(1, 1, 1, 1); glClearDepth(1.0); } - virtual void onExit(const vui::GameTime& gameTime) { + virtual void onExit(const vui::GameTime& gameTime) override { delete[] skeleton.bones; delete[] skeleton.frames; delete[] skeleton.childrenArray; @@ -673,9 +685,12 @@ void main() { delete[] mRestInv; } - virtual void update(const vui::GameTime& gameTime) { + virtual void registerRendering(vg::Renderer& renderer) override { + } + + virtual void update(const vui::GameTime& gameTime) override { } - virtual void draw(const vui::GameTime& gameTime) { + virtual void onRenderFrame(const vui::GameTime& gameTime) override { vg::DepthState::FULL.set(); vg::RasterizerState::CULL_NONE.set(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); diff --git a/UnitTests/VorbUI.cpp b/UnitTests/VorbUI.cpp index eeef7daee..d8ffccf6a 100644 --- a/UnitTests/VorbUI.cpp +++ b/UnitTests/VorbUI.cpp @@ -30,39 +30,41 @@ struct Vertex { class TestScreen : public vui::IGameScreen { public: - virtual i32 getNextScreen() const { + virtual i32 getNextScreen() const override { return SCREEN_INDEX_NO_SCREEN; } - virtual i32 getPreviousScreen() const { + virtual i32 getPreviousScreen() const override { return SCREEN_INDEX_NO_SCREEN; } - virtual void build() { + virtual void build() override { } - virtual void destroy(const vui::GameTime& gameTime) { + virtual void destroy(const vui::GameTime& gameTime) override { } - virtual void onEntry(const vui::GameTime& gameTime) { + virtual void onEntry(const vui::GameTime& gameTime) override { } - virtual void onExit(const vui::GameTime& gameTime) { + virtual void onExit(const vui::GameTime& gameTime) override { } - virtual void update(const vui::GameTime& gameTime) { + virtual void registerRendering(vg::Renderer& renderer) override { } - virtual void draw(const vui::GameTime& gameTime) { + virtual void onRenderFrame(const vui::GameTime& gameTime) override { + } + virtual void update(const vui::GameTime& gameTime) override { } }; class WidgetTestScreen : public vui::IGameScreen { public: - virtual i32 getNextScreen() const { + virtual i32 getNextScreen() const override { return SCREEN_INDEX_NO_SCREEN; } - virtual i32 getPreviousScreen() const { + virtual i32 getPreviousScreen() const override { return SCREEN_INDEX_NO_SCREEN; } - virtual void build() { + virtual void build() override { } - virtual void destroy(const vui::GameTime& gameTime) { + virtual void destroy(const vui::GameTime& gameTime) override { } - virtual void onEntry(const vui::GameTime& gameTime) { + virtual void onEntry(const vui::GameTime& gameTime) override { font.init("Data/chintzy.ttf", 32); form.init("main", this, f32v4(0.0f, 0.0f, (f32)m_viewportDims.x, (f32)m_viewportDims.y)); @@ -90,14 +92,17 @@ class WidgetTestScreen : public vui::IGameScreen { // env.init(&form, &m_game->getWindow()); // env.loadForm("data/scripts/Form1.lua"); } - virtual void onExit(const vui::GameTime& gameTime) { + virtual void onExit(const vui::GameTime& gameTime) override { form.dispose(); // font.dispose(); } - virtual void update(const vui::GameTime& gameTime) { + virtual void registerRendering(vg::Renderer& renderer) override { + // Empty + } + virtual void update(const vui::GameTime& gameTime) override { // form.update(); } - virtual void draw(const vui::GameTime& gameTime) { + virtual void onRenderFrame(const vui::GameTime& gameTime) override { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); form.draw(); } diff --git a/Vorb.vcxproj b/Vorb.vcxproj index 010325f3d..02453504a 100644 --- a/Vorb.vcxproj +++ b/Vorb.vcxproj @@ -294,6 +294,7 @@ $(SolutionDir)DepsBuildCopy.bat + @@ -314,6 +315,9 @@ $(SolutionDir)DepsBuildCopy.bat + + + @@ -352,6 +356,7 @@ $(SolutionDir)DepsBuildCopy.bat + @@ -416,6 +421,7 @@ $(SolutionDir)DepsBuildCopy.bat + @@ -430,6 +436,9 @@ $(SolutionDir)DepsBuildCopy.bat + + + @@ -498,6 +507,7 @@ $(SolutionDir)DepsBuildCopy.bat + @@ -517,7 +527,10 @@ $(SolutionDir)DepsBuildCopy.bat + + + @@ -537,6 +550,7 @@ $(SolutionDir)DepsBuildCopy.bat + diff --git a/Vorb.vcxproj.filters b/Vorb.vcxproj.filters index fe503bcf6..f2b3a8b90 100644 --- a/Vorb.vcxproj.filters +++ b/Vorb.vcxproj.filters @@ -279,6 +279,21 @@ Examples + + Graphics + + + Graphics + + + Graphics + + + Graphics + + + UI\Input + @@ -715,6 +730,30 @@ Core\Math + + Graphics + + + Graphics + + + Graphics + + + Graphics + + + Voxel + + + Voxel + + + Voxel + + + UI\Input + @@ -870,6 +909,9 @@ Core\Math + + Graphics + diff --git a/include/Graphics.h b/include/Graphics.h index 106442583..7d1987d54 100644 --- a/include/Graphics.h +++ b/include/Graphics.h @@ -20,6 +20,49 @@ #if defined(VORB_IMPL_GRAPHICS_OPENGL) #include + +// GL Error checking +// TODO(Ben): This could go somewhere else. +namespace vorb { + namespace graphics { + inline bool checkGlError(const nString& descriptor, OUT nString& errorMsg) { + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + switch (error) { + case GL_INVALID_ENUM: + errorMsg = ("GL error at " + descriptor + ". Error code 1280: GL_INVALID_ENUM"); + break; + case GL_INVALID_VALUE: + errorMsg = ("GL error at " + descriptor + ". Error code 1281: GL_INVALID_VALUE"); + break; + case GL_INVALID_OPERATION: + errorMsg = ("GL error at " + descriptor + ". Error code 1282: GL_INVALID_OPERATION"); + break; + case GL_STACK_OVERFLOW: + errorMsg = ("GL error at " + descriptor + ". Error code 1283: GL_STACK_OVERFLOW"); + break; + case GL_STACK_UNDERFLOW: + errorMsg = ("GL error at " + descriptor + ". Error code 1284: GL_STACK_UNDERFLOW"); + break; + case GL_OUT_OF_MEMORY: + errorMsg = ("GL error at " + descriptor + ". Error code 1285: GL_OUT_OF_MEMORY"); + break; + case GL_INVALID_FRAMEBUFFER_OPERATION: + errorMsg = ("GL error at " + descriptor + ". Error code 1285: GL_INVALID_FRAMEBUFFER_OPERATION"); + break; + default: + errorMsg = ("GL error at " + descriptor + ". Error code " + std::to_string(error) + ": UNKNOWN"); + break; + } + return true; + } + return false; + } + } +} +namespace vg = vorb::graphics; + + #elif defined(VORB_IMPL_GRAPHICS_D3D) // Make sure we get correct DirectX version #if defined(VORB_DX_11) diff --git a/include/graphics/Camera.h b/include/graphics/Camera.h new file mode 100644 index 000000000..a4739168b --- /dev/null +++ b/include/graphics/Camera.h @@ -0,0 +1,149 @@ +// +// Camera.h +// Vorb Engine +// +// Created by Benjamin Arnold on 20 Jun 2016 +// Copyright 2014 Regrowth Studios +// All Rights Reserved +// + +/*! \file Camera.h +* @brief +* Basic 3D camera implementation. +* +*/ + +#pragma once + +#ifndef Vorb_Camera_h__ +//! @cond DOXY_SHOW_HEADER_GUARDS +#define Vorb_Camera_h__ +//! @endcond + +#ifndef VORB_USING_PCH +#include "types.h" +#endif // !VORB_USING_PCH + +#include "Frustum.h" + +namespace vorb { +namespace graphics { + + class Camera { + public: + Camera(); + virtual ~Camera(); + + /*! @brief Sets up the camera. + * Does not set up the view or projection matrices, you must call update(). + */ + virtual void init(f32 aspectRatio); + + /*! @brief Updates the camera matrices. Call every frame before render. + */ + virtual void update(); + + /*! @brief Check if a point is in the camera frustum. + */ + bool pointInFrustum (const f32v3& pos) const { return m_frustum.pointInFrustum(pos); } + /*! @brief Check if a sphere is in the camera frustum. + */ + bool sphereInFrustum(const f32v3& pos, f32 radius) const { return m_frustum.sphereInFrustum(pos, radius); } + + /************************************************************************/ + /* Mutators */ + /************************************************************************/ + /*! @brief Translate the camera. + */ + void offsetPosition(const f32v3& offset); + void offsetPosition(const f64v3& offset); + /*! @brief Apply a quaternion rotation to the camera. + */ + virtual void rotate(const f32q& rot); + /*! @brief Rotate with pitch and yaw. + */ + virtual void rotate(f32 yaw, f32 pitch); + /*! @brief Rotate with pitch and yaw in an FPS style with (0, 1, 0) as the UP vector. + * @param clampVerticalRotation: Set to true to not allow rotation beyond absolute up or down. + */ + virtual void rotateAbsoluteUp(f32 yaw, f32 pitch, bool clampVerticalRotation = false); + + /*! @brief Apply roll rotation only. + */ + virtual void roll(f32 roll); + + /*! @brief Sets orientation using a direction. Up and direction need not be orthogonal, direction will take priority. + */ + void setOrientation(const f32v3& direction, const f32v3& up); + /*! @brief Sets orientation to a quaternion. + */ + void setOrientation(const f64q& orientation); + + void setFocalPoint (const f64v3& focalPoint) { m_focalPoint = focalPoint; m_viewChanged = true; } + void setPosition (const f64v3& position) { m_focalPoint = m_position = position; m_focalLength = 0; m_viewChanged = true; } + void setDirection (const f32v3& direction) { m_direction = direction; m_viewChanged = true; } + void setLeft (const f32v3& left) { m_left = left; m_viewChanged = true; } + void setUp (const f32v3& up) { m_up = up; m_viewChanged = true; } + void setClippingPlane(f32 zNear, f32 zFar) { m_zNear = zNear; m_zFar = zFar; m_projectionChanged = true; } + void setFieldOfView (f32 fieldOfView) { m_fieldOfView = fieldOfView; m_projectionChanged = true; } + void setFocalLength (f32 focalLength) { m_focalLength = focalLength; m_viewChanged = true; } + void setAspectRatio (f32 aspectRatio) { m_aspectRatio = aspectRatio; m_projectionChanged = true; } + + /************************************************************************/ + /* Accessors */ + /************************************************************************/ + const f64v3& getPosition () const { return m_position; } + const f64v3& getFocalPoint () const { return m_focalPoint; } + const f64& getFocalLength () const { return m_focalLength; } + const f64& getMaxFocalLength() const { return m_maxFocalLength; } + + const f32v3& getDirection() const { return m_direction; } + const f32v3& getLeft () const { return m_left; } + const f32v3& getUp () const { return m_up; } + + const f32m4& getProjectionMatrix () const { return m_projectionMatrix; } + const f32m4& getViewMatrix () const { return m_viewMatrix; } + const f32m4& getViewProjectionMatrix() const { return m_viewProjectionMatrix; } + + const f32& getNearClip () const { return m_zNear; } + const f32& getFarClip () const { return m_zFar; } + const f32& getFieldOfView() const { return m_fieldOfView; } + const f32& getAspectRatio() const { return m_aspectRatio; } + + const Frustum& getFrustum() const { return m_frustum; } + private: + void updateView (); + void updateProjection(); + + f64v3 m_focalPoint = f64v3(0.0); + f64v3 m_position = f64v3(0.0); + f64 m_focalLength = 0.0; + f64 m_maxFocalLength = 0.0; + + f32 m_zNear = 0.1f; + f32 m_zFar = 10000.0f; + f32 m_fieldOfView = 75.0f; + f32 m_aspectRatio = 0.0f; + + f32v3 m_direction = f32v3(1.0f, 0.0f, 0.0f); + f32v3 m_left = f32v3(0.0f, 0.0f, 1.0f); + f32v3 m_up = f32v3(0.0f, 1.0f, 0.0f); + + f32m4 m_projectionMatrix; + f32m4 m_viewMatrix; + f32m4 m_viewProjectionMatrix; + + bool m_viewChanged = true; + bool m_projectionChanged = true; + + Frustum m_frustum; + + static const f32v3 UP_ABSOLUTE; + }; + +} +} +namespace vg = vorb::graphics; + +#endif // !Vorb_Camera_h__ + diff --git a/include/graphics/FullQuadVBO.h b/include/graphics/FullQuadVBO.h index ba56995be..c2bbc0d87 100644 --- a/include/graphics/FullQuadVBO.h +++ b/include/graphics/FullQuadVBO.h @@ -22,28 +22,35 @@ #include "../types.h" #endif // !VORB_USING_PCH +#include "gtypes.h" + namespace vorb { namespace graphics { /// Wrapper over common functionality to draw a quad across the entire screen class FullQuadVBO { public: - /// Initialize all OpenGL resources - /// @param attrLocation: Position attribute location for VAO + /*! @brief Initialize all graphics resources. + * @param attrLocation: Position attribute location for VAO + */ void init(i32 attrLocation = 0); - /// Dispose all OpenGL resources + /*! @brief Dispose all graphics resources + */ void dispose(); - /// Binds vertex array, index buffer, and issues a draw command + /*! @brief Binds vertex array, index buffer, and issues a draw command. + */ void draw(); + + bool isInitialized() const { return m_vao != 0; } private: union { struct { - ui32 m_vb; ///< Vertex buffer ID - ui32 m_ib; ///< Index buffer ID + VGVertexBuffer m_vb; + VGIndexBuffer m_ib; }; - ui32 m_buffers[2]; ///< Storage for both buffers used by this mesh + VGVertexBuffer m_buffers[2]; }; - ui32 m_vao; ///< VAO with vertex attribute pointing to 0 + VGVertexArray m_vao = 0; }; } } diff --git a/include/graphics/GBuffer.h b/include/graphics/GBuffer.h index b4c60b7c2..71f0671d6 100644 --- a/include/graphics/GBuffer.h +++ b/include/graphics/GBuffer.h @@ -69,17 +69,16 @@ namespace vorb { /// Create the value-based render targets /// @return Self - GBuffer& init(const Array& attachments, vg::TextureInternalFormat lightFormat); + GBuffer& init(const std::vector& attachments, vg::TextureInternalFormat lightFormat); /// Attach a depth buffer to this GBuffer /// @param depthFormat: Precision used for depth buffer /// @return Self GBuffer& initDepth(TextureInternalFormat depthFormat = TextureInternalFormat::DEPTH_COMPONENT32); - /// Attack a depth and stencil buffer to this GBuffer + /// Attach a depth and stencil buffer to this GBuffer /// @param depthFormat: Precision used for depth and stencil buffer /// @return Self GBuffer& initDepthStencil(TextureInternalFormat depthFormat = TextureInternalFormat::DEPTH24_STENCIL8); - void initTarget(const ui32v2& _size, const ui32& texID, const GBufferAttachment& attachment); /// Destroy all render targets void dispose(); @@ -139,12 +138,15 @@ namespace vorb { return m_texDepth; } private: + + void initTarget(const ui32v2& _size, const ui32& texID, const GBufferAttachment& attachment); + ui32v2 m_size; ///< The width and height of the GBuffer - VGFramebuffer m_fboGeom; ///< The rendering target for geometry - VGFramebuffer m_fboLight; ///< The rendering target for light - Array m_textures; ///< An array of all the textures - VGTexture m_texDepth = 0; ///< Depth texture of GBuffer + VGFramebuffer m_fboGeom; ///< The rendering target for geometry + VGFramebuffer m_fboLight; ///< The rendering target for light + std::vector m_textures; ///< An array of all the textures + VGTexture m_texDepth = 0; ///< Depth texture of GBuffer }; } } diff --git a/include/graphics/PostProcess.h b/include/graphics/PostProcess.h new file mode 100644 index 000000000..903f9894b --- /dev/null +++ b/include/graphics/PostProcess.h @@ -0,0 +1,197 @@ +// +// PostProcess.h +// Vorb Engine +// +// Created by Benjamin Arnold on 22 Jun 2016 +// Copyright 2014 Regrowth Studios +// All Rights Reserved +// + +/*! \file PostProcess.h +* @brief A PostProcess stage for processing a drawn FBO. +* +* +*/ + +#pragma once + +#ifndef Vorb_PostProcess_h__ +//! @cond DOXY_SHOW_HEADER_GUARDS +#define Vorb_PostProcess_h__ +//! @endcond + +#ifndef VORB_USING_PCH +#include "types.h" +#endif // !VORB_USING_PCH + +#include "FullQuadVBO.h" +#include "GLProgram.h" +#include "GLRenderTarget.h" + +namespace vorb { +namespace graphics { + + // Forward Declare + class GLRenderTarget; + class Renderer; + class Camera; + + // TODO(Ben): Visual shader programming tool with live recompiling. + // TODO(Ben): FBO Cacheing. + + /************************************************************************/ + /* Base */ + /************************************************************************/ + class IPostProcess { + public: + friend class Renderer; + + /*! @brief Loads shaders and other graphical objects. + * Gets called by SceneRenderer. + */ + virtual void load() = 0; + + /*! @brief Renders the post process + * Gets called by SceneRenderer. + */ + virtual void render() = 0; + + /*! @brief Frees resources. + */ + virtual void dispose() = 0; + + /*! @brief Unregisters from the SceneRenderer + */ + virtual void unregister(); + + /*! @brief Gets and sets the input textures. + * Derived classes must allocate input texture space in m_inputTextures or setInputTexture will crash. + */ + virtual void setInputTexture (ui32 index, VGTexture inputTexture) { m_inputTextures.at(index) = inputTexture; } + virtual void setInputTextures(std::vector inputTextures) { m_inputTextures = inputTextures; } + virtual const std::vector& getInputTextures() const { return m_inputTextures; } + + /*! @brief Gets and sets the input textures. + * It is assumed that the outputFBO is a format that works for this PostProcess. + */ + virtual void setOutputFBO(VGFramebuffer outputFBO) { m_outputFBO = outputFBO; } + virtual VGFramebuffer getOutputFBO() const { return m_outputFBO; } + + /*! @brief Checks if quad is created, and creates it if not. + * This must be called to initialize the shared quad. Recommend calling in + * any shader that needs the quad. + * @return False if the quad is already initialized. + */ + static bool tryInitQuad(); + + static vg::FullQuadVBO quad; ///< Optional shared VBO. Call dispose manually. + + protected: + + Renderer* m_renderer = nullptr; ///< Renderer that owns this. + + std::vector m_inputTextures; ///< Optional input textures, some PostProcesses may not use. + + VGFramebuffer m_outputFBO = 0; ///< FBO that we will write to. + }; + + /************************************************************************/ + /* Bloom */ + /************************************************************************/ + /*! @brief Renders the bloom post process: A gaussian blur of bright objects. + * + * @input Texture 0 as color. + * @output[0] Color. + */ + class PostProcessBloom : public IPostProcess { + public: + void init (ui32 windowWidth, ui32 windowHeight, VGTexture inputColorTexture); + void setParams(ui32 gaussianN = 20, f32 gaussianVariance = 36.0f, f32 lumaThreshold = 0.75f); + void load () override; + virtual void render () override; + virtual void dispose () override; + + private: + virtual void uploadShaderUniforms(); + + void renderStage (vg::GLProgram& program); + + /// Shaders + vg::GLProgram m_programLuma; + vg::GLProgram m_programGaussianFirst; + vg::GLProgram m_programGaussianSecond; + + vg::GLRenderTarget m_fbos[2]; + + ui32 m_windowWidth = 0; + ui32 m_windowHeight = 0; + + /// Parameters + ui32 m_gaussianN = 20; ///< Threshold for filtering image luma for bloom bluring + f32 m_gaussianVariance = 36.0f; ///< Radius number for gaussian blur. Must be less than 50. + f32 m_lumaThreshold = 0.75f; ///< Gaussian variance for blur pass + }; + + /************************************************************************/ + /* SSAO */ + /************************************************************************/ + /*! @brief Renders the SSAO post process: Screen space ambient occlusion. + */ + class PostProcessSSAO : public IPostProcess { + public: + void init(ui32 windowWidth, ui32 windowHeight, + VGTexture inputColorTexture, + VGTexture inputDepthTexture, + VGTexture inputNormalTexture, + vg::Camera* camera); + + void load() override; + virtual void render() override; + virtual void dispose() override; + + private: + virtual void uploadShaderUniforms(); + + /// Shaders + vg::GLProgram m_programSSAO; + vg::GLProgram m_programBlur; + + vg::Camera* m_camera = nullptr; + + VGTexture m_noiseTexture; + + vg::GLRenderTarget m_ssaoTarget; + + ui32 m_windowWidth = 0; + ui32 m_windowHeight = 0; + + std::vector m_sampleKernel; + }; + + /************************************************************************/ + /* Passthrough */ + /************************************************************************/ + /*! @brief Simply renders a 2D texture from one FBO to another. + * + * @input Texture. + * @output[0] Texture. + */ + class PostProcessPassthrough : public IPostProcess { + public: + void init(VGTexture inputTexture); + virtual void load() override; + virtual void render() override; + virtual void dispose() override; + virtual void uploadShaderUniforms(); + + private: + vg::GLProgram m_program; + }; + + // TODO(Ben): Gaussian blur. +} +} +namespace vg = vorb::graphics; + +#endif // !Vorb_PostProcess_h__ + diff --git a/include/graphics/Renderer.h b/include/graphics/Renderer.h new file mode 100644 index 000000000..bfea03e00 --- /dev/null +++ b/include/graphics/Renderer.h @@ -0,0 +1,140 @@ +// +// Renderer.h +// Vorb Engine +// +// Created by Benjamin Arnold on 22 Jun 2016 +// Copyright 2014 Regrowth Studios +// All Rights Reserved +// + +/*! \file Renderer.h +* @brief Handles rendering of scenes and post processes to a vui::GameWindow. +* +* +*/ + +#pragma once + +#ifndef Vorb_Renderer_h__ +//! @cond DOXY_SHOW_HEADER_GUARDS +#define Vorb_Renderer_h__ +//! @endcond + +#ifndef VORB_USING_PCH +#include "types.h" +#endif // !VORB_USING_PCH + +#include "../ui/GameWindow.h" +#include "../VorbPreDecl.inl" + +DECL_VUI(struct GameTime); + +namespace vorb { + namespace graphics { + + enum class GBUFFER_TEXTURE_UNITS { + COLOR = 0, + POSITION, + NORMAL, + COUNT + }; + + // Forward Declare + class GBuffer; + class IScene; + class IPostProcess; + class PostProcessPassthrough; + + /// Manages storage and rendering of scenes and post processes + class Renderer { + public: + Renderer(); + virtual ~Renderer(); + + /*! @brief Initializes the SceneRenderer for the GameWindow. + * Do not call twice without first destroying. + */ + virtual bool init(vui::GameWindow* window); + + /*! @brief Loads any scenes or postProcessed that need to be loaded. + */ + virtual void load(); + + /*! @brief Registers a scene for load and then rendering. + */ + virtual void registerScene(IScene* scene); + + /*! @brief Unregisters a scene for rendering. + * Does not dispose the scene. + * @return true on success. + */ + virtual bool unregisterScene(IScene* scene); + + /*! @brief Registers a PostProcess for load and then rendering. + */ + virtual void registerPostProcess(IPostProcess* postProcess); + + /*! @brief Unregisters a PostProcess for rendering. + * Does not dispose the PostProcess. + * @return true on success. + */ + virtual bool unregisterPostProcess(IPostProcess* postProcess); + + /*! @Brief sets background color for window + */ + virtual void setBackgroundColor(const color4& color); + virtual void setBackgroundColor(const f32v4& color); + + /*! @Brief clears depth and color buffer and sets viewport. + */ + virtual void beginRenderFrame(); + + /*! @Brief Sync the window and display to the screen. + */ + virtual void sync(ui32 frameTime) { m_window->sync(frameTime); } + + /*! @brief Renders all scenes in order that they were registered + */ + virtual void renderScenes(const vui::GameTime& gameTime); + + /*! @brief Renders all postprocess stages. Call after renderScenes. + * You must call this even if you don't use post processing in order to render the gbuffer. + */ + virtual void renderPostProcesses(); + + /*! @brief Cleans up the renderer and disposes all scenes and postprocesses. + */ + virtual void dispose(); + + /*! + */ + + /************************************************************************/ + /* Accessors */ + /************************************************************************/ + const std::vector& getScenes() const { return m_scenes; } + const std::vector& getPostProcesses() const { return m_postProcesses; } + bool isInitialized() const { return m_isInitialized; } + const vg::GBuffer* getGBuffer() const { return m_gBuffer.get(); } + + protected: + bool m_isInitialized = false; + vui::GameWindow* m_window = nullptr; ///< The window we are targeting. + + std::unique_ptr m_postProcessPassthrough = nullptr; ///< Default post process when none are active + + // TODO(Ben): FBO Cache + std::unique_ptr m_gBuffer; + + std::vector m_scenes; ///< Scenes that are loaded and ready to render + std::vector m_sceneLoadQueue; ///< Scenes that need to load + std::vector m_postProcesses; ///< PostPorcesses that are loaded and ready to render + std::vector m_postProcessesLoadQueue; ///< PostProcesses that need to load + }; + } +} +namespace vg = vorb::graphics; + + +#endif // !Vorb_Renderer_h__ + diff --git a/include/graphics/Scene.h b/include/graphics/Scene.h new file mode 100644 index 000000000..f009fd18d --- /dev/null +++ b/include/graphics/Scene.h @@ -0,0 +1,84 @@ +// +// Scene.h +// Vorb Engine +// +// Created by Benjamin Arnold on 22 Jun 2016 +// Copyright 2014 Regrowth Studios +// All Rights Reserved +// + +/*! \file Scene.h +* @brief A complete graphical scene for rendering to the screen. +* +* +*/ + +#pragma once + +#ifndef Vorb_Scene_h__ +//! @cond DOXY_SHOW_HEADER_GUARDS +#define Vorb_Scene_h__ +//! @endcond + +#ifndef VORB_USING_PCH +#include "types.h" +#endif // !VORB_USING_PCH + +#include "../VorbPreDecl.inl" + +DECL_VUI(struct GameTime); + +namespace vorb { + namespace graphics { + + // Forward Declare + class Camera; + class GLRenderTarget; + class Renderer; + + /// A scene, complete with its own camera and rendering logic. + class IScene { + public: + friend class Renderer; + + IScene(); + virtual ~IScene(); + + // You should implement your own initialization logic + + /*! @brief Allocates the Camera. + * Override this to provide a custom camera implementation or set up camera location. + * Does not get called by renderer, you must call this manually. + */ + virtual void initCamera(f32 aspectRatio); + + /* @brief Load all graphics objects. + * Gets called by SceneRenderer. + */ + virtual void load() = 0; + + /*! @brief Renders a scene. + * + */ + virtual void render(const vui::GameTime& gameTime) = 0; + + /*! @brief Unregisters from the SceneRenderer + */ + virtual void unregister(); + + /*! @brief Frees any resources and resets state. + */ + virtual void dispose(); + + Camera* getCamera() { return m_camera.get(); } + + protected: + std::unique_ptr m_camera = nullptr; + Renderer* m_renderer = nullptr; ///< Renderer that owns this. + }; + } +} +namespace vg = vorb::graphics; + +#endif // !Vorb_Scene_h__ + diff --git a/include/graphics/ShaderCommon.inl b/include/graphics/ShaderCommon.inl new file mode 100644 index 000000000..7552504c0 --- /dev/null +++ b/include/graphics/ShaderCommon.inl @@ -0,0 +1,72 @@ +// +// ShaderCommon.inl +// Vorb Engine +// +// Created by Benjamin Arnold on 25 Jun 2016 +// Copyright 2014 Regrowth Studios +// All Rights Reserved +// + +/*! \file ShaderCommon.inl +* @brief Common shader code to be reused. +* +* +*/ + +#pragma once + +#ifndef Vorb_ShaderCommon_inl__ +//! @cond DOXY_SHOW_HEADER_GUARDS +#define Vorb_ShaderCommon_inl__ +//! @endcond + +#ifndef VORB_USING_PCH +#include "types.h" +#endif // !VORB_USING_PCH + +namespace vorb { +namespace graphics { +namespace shadercommon { + + /// For just passing UV through the vertex shader + const cString const PASSTHROUGH_2D_VERT_SRC = R"( +// Input +in vec2 vPosition; // Position in screen space + +// Output +out vec2 fUV; + +void main() { + fUV = (vPosition + 1.0) / 2.0; + gl_Position = vec4(vPosition, 0, 1); +})"; + + const cString const TEXTURE_FRAG_SRC = R"( +// Input +in vec2 fUV; + +uniform sampler2D unSampler; + +out vec4 pColor; + +void main() { + pColor = vec4(texture(unSampler, fUV).rgb, 1.0); // *TEMP Force alpha to 1 +})"; + + const cString const COLOR_FRAG_SRC = R"( +// Input +in vec4 fColor; + +out vec4 pColor; + +void main() { + pColor = fColor; +})"; + +} +} +} +namespace vg = vorb::graphics; + +#endif // !Vorb_ShaderCommon_inl__ + diff --git a/include/graphics/ShaderManager.h b/include/graphics/ShaderManager.h index c180dadc7..2d2f509a7 100644 --- a/include/graphics/ShaderManager.h +++ b/include/graphics/ShaderManager.h @@ -27,13 +27,13 @@ #include "../Events.hpp" #include "../VorbPreDecl.inl" #include "../io/Path.h" +#include "GLProgram.h" DECL_VIO(class IOManager) namespace vorb { namespace graphics { - class GLProgram; typedef std::map GLProgramMap; /// Static class that handles caching, creation, and destruction of GLPrograms @@ -44,11 +44,13 @@ namespace vorb { /// Does not register to global cache. /// @param vertSrc: Source code for vertex shader /// @param fragSrc: Source code for fragment shader + /// @param version: Optional shader version /// @param vertIOM: Optional IOManager for vert #include lookups /// @param fragIOM: Optional IOManager for frag #include lookups /// @param defines: #defines for the program /// @return the created program. static GLProgram createProgram(const cString vertSrc, const cString fragSrc, + ShaderLanguageVersion version = DEFAULT_SHADING_LANGUAGE_VERSION, vio::IOManager* vertIOM = nullptr, vio::IOManager* fragIOM = nullptr, cString defines = nullptr); @@ -56,10 +58,12 @@ namespace vorb { /// Does not register to global cache. /// @param vertPath: Path to vertex shader /// @param fragPath: Path to fragment shader + /// @param version: Optional shader version /// @param iom: Optional IOManager for loading /// @param defines: #defines for the program /// @return the created program. static GLProgram createProgramFromFile(const vio::Path& vertPath, const vio::Path& fragPath, + ShaderLanguageVersion version = DEFAULT_SHADING_LANGUAGE_VERSION, vio::IOManager* iom = nullptr, cString defines = nullptr); /// Disposes and deallocates all globally cached programs and clears the cache diff --git a/include/graphics/ShaderParser.h b/include/graphics/ShaderParser.h index 94fd699ee..e00581345 100644 --- a/include/graphics/ShaderParser.h +++ b/include/graphics/ShaderParser.h @@ -37,6 +37,7 @@ DECL_VIO(class IOManager); namespace vorb { namespace graphics { + // TODO(Ben): Detect output location based on chronological order. class ShaderParser { public: /// Parses includes and semantics for a vertex shader diff --git a/include/graphics/SpriteBatch.h b/include/graphics/SpriteBatch.h index 530455b5d..3815be246 100644 --- a/include/graphics/SpriteBatch.h +++ b/include/graphics/SpriteBatch.h @@ -91,7 +91,7 @@ namespace vorb { static void disposeProgram(); private: struct Glyph; struct Vertex; - typedef void(SpriteBatch::*QuadBuildFunc)(const Glyph*, Vertex*); + typedef void(*QuadBuildFunc)(const Glyph*, Vertex*); struct Glyph { Glyph(QuadBuildFunc f, VGTexture tex, const f32v4& uvRect, const f32v2& uvTiling, const f32v2& position, const f32v2& offset, const f32v2& size, f32 rotation, const color4& tint, f32 depth); @@ -133,12 +133,12 @@ namespace vorb { static bool SSMBackToFront(Glyph* g1, Glyph* g2) { return g1->depth > g2->depth; } /// Quad builders - void buildQuad(const Glyph* g, Vertex* verts); - void buildQuadOffset(const Glyph* g, Vertex* verts); - void buildQuadRotated(const Glyph* g, Vertex* verts); + static void buildQuad(const Glyph* g, Vertex* verts); + static void buildQuadOffset(const Glyph* g, Vertex* verts); + static void buildQuadRotated(const Glyph* g, Vertex* verts); /// For color gradients - void calcColor(Vertex& vtl, Vertex& vtr, Vertex& vbl, Vertex& vbr, const Glyph* g); + static void calcColor(Vertex& vtl, Vertex& vtr, Vertex& vbl, Vertex& vbr, const Glyph* g); std::vector m_glyphs; ///< Glyph data std::vector m_glyphPtrs; ///< Pointers to glyphs for fast sorting diff --git a/include/ui/GameWindow.h b/include/ui/GameWindow.h index 9b94bf354..2f4434988 100644 --- a/include/ui/GameWindow.h +++ b/include/ui/GameWindow.h @@ -193,6 +193,7 @@ namespace vorb { void setPosition(int x, int y); void setMaxFPS(f32 fpsLimit); void setTitle(const cString title) const; + void setRelativeMouseMode(bool relative) const; void sync(ui32 frameTime); diff --git a/include/ui/IGameScreen.h b/include/ui/IGameScreen.h index 7d0f9e696..b1070c9a2 100644 --- a/include/ui/IGameScreen.h +++ b/include/ui/IGameScreen.h @@ -17,31 +17,27 @@ #include "MainGame.h" #include "FocusController.h" +#include "../VorbPreDecl.inl" + +DECL_VG(class Renderer); namespace vorb { namespace ui { #define SCREEN_INDEX_NO_SCREEN -1 #define SCREEN_INDEX_NO_START_SELECTED -2 - // A Screen Must Be In One Of These States enum class ScreenState { - // The Screen Is Doing Nothing At The Moment NONE, - // The Screen Is Running (Current Active) RUNNING, - // Exit Request To The Main Game EXIT_APPLICATION, - // Request To Move To The Next Screen - CHANGE_NEXT, - // Request To Move To The Previous Screen - CHANGE_PREVIOUS + CHANGE_NEXT, ///< Go to next screen. + CHANGE_PREVIOUS ///< Go to previous screen. }; - // Common Interface For A Game Screen + // Common interface for a game screen class IGameScreen { public: - IGameScreen() - : m_state(ScreenState::NONE) { + IGameScreen() { // empty } @@ -49,22 +45,28 @@ namespace vorb { // empty } - // All Screens Should Have A Parent + /*! @brief All screens should have a parent. + */ void setParentGame(MainGame* game, i32 index) { m_game = game; m_index = index; } - // The Screen's Location In The List + /*! @brief The screen's location in the list. + */ i32 getIndex() const { return m_index; } - // Returns Screen Index When Called To Change Screens + /*! @brief Returns screen index when called to change to next screen. + */ virtual i32 getNextScreen() const = 0; + /*! @brief Returns screen index when called to change to previous screen. + */ virtual i32 getPreviousScreen() const = 0; - // Screen State Functions + /*! @brief Screen state functions + */ ScreenState getState() const { return m_state; } @@ -72,23 +74,39 @@ namespace vorb { m_state = ScreenState::RUNNING; } - // Called At The Beginning And End Of The Application + /*! @brief Called At the beginning and end of the application. + */ virtual void build() = 0; + + /*! @brief Destroys all resources + */ virtual void destroy(const vui::GameTime& gameTime) = 0; - // Called When A Screen Enters And Exits Focus + /*! @brief Called when a screen enters and exits focus. + */ virtual void onEntry(const vui::GameTime& gameTime) = 0; virtual void onExit(const vui::GameTime& gameTime) = 0; + + /*! @brief Initializes and register any scenes for rendering. + * @param renderer: Register scenes and postprocesses with this to enable rendering. + */ + virtual void registerRendering(vg::Renderer& renderer) = 0; + + /*! @brief Called after the screen clear, but before the Renderer begins to render. + * It is recommended to do rendering via Scenes and PostProcesses rather than here, + * but you can optionally do rendering here. + */ + virtual void onRenderFrame(const vui::GameTime& gameTime) = 0; - // Called In The Main Update Loop + /*! @brief Called In the main update loop. + */ virtual void update(const vui::GameTime& gameTime) = 0; - virtual void draw(const vui::GameTime& gameTime) = 0; protected: - ScreenState m_state; - MainGame* m_game = nullptr; + ScreenState m_state = ScreenState::NONE; + MainGame* m_game = nullptr; FocusController m_focusController; private: - // Location In The ScreenList + // Location in the screenList i32 m_index = -1; }; diff --git a/include/ui/InputMapper.h b/include/ui/InputMapper.h new file mode 100644 index 000000000..2a02d8bc9 --- /dev/null +++ b/include/ui/InputMapper.h @@ -0,0 +1,141 @@ +/// +/// InputMapper.h +/// Seed of Andromeda +/// +/// Created by Frank McCoy +/// Refactored by Ben Arnold on Mar 25 2015 +/// Copyright 2014 Regrowth Studios +/// All Rights Reserved +/// +/// Summary: +/// Handles mapping of input for keys and buttons. +/// + +#pragma once + +#ifndef Input_Manager_h +#define Input_Manager_h + +#include "../Events.hpp" +#include "InputDispatcher.h" + +#define INPUTMAPPER_DEFAULT_CONFIG_LOCATION "Data/KeyConfig.yml" + +namespace vorb { +namespace ui { + + + /// Handles all the user input through the mouse, keyboard and gamepad. + /// @author Frank McCoy and Ben Arnold + class InputMapper { + public: + typedef Event::Listener Listener; + typedef i32 InputID; + + /// Constructor. + InputMapper(); + /// Destructor. + ~InputMapper(); + + /// The data for a single Input. + class Input { + public: + Input(InputID ID, const nString& nm, VirtualKey defKey, InputMapper* parent) : + id(ID), + name(nm), + defaultKey(defKey), + key(defKey), + upEvent(parent), + downEvent(parent){ + // Empty + } + InputID id; + nString name; ///< The name of the input. + VirtualKey defaultKey; ///< The default key. + VirtualKey key; ///< The actual key. + Event upEvent; ///< The event for when the key is released + Event downEvent; ///< The event for when the key is pressed + }; + typedef std::vector InputList; + typedef std::unordered_map InputMap; + + /// Returns the state of an input + /// @param id: The id of the input which is being looked up. + /// @return The state of the positive key in the input. + bool getInputState(const InputID id); + + /// Creates a single key input. + /// If the input already exists the old ID is returned and no data is modified. + /// @param inputName: The name of the input to create. + /// @param defaultKey: The default key(positive) for the new input. + /// @return The id of the new input. + InputID createInput(const nString& inputName, VirtualKey defaultKey); // Single key + + + /// Get the positive key of the supplied input. + /// If the input does not exist return UINT32_MAX. + /// @param id: The id of the input to look up. + /// @return The id of the positive key of the input. + VirtualKey getKey(const InputID id); + + /// Set the positive key of the supplied input. + /// @param id: The id of the input to look up. + /// @param key: The key to set the keys' positive key to. + void setKey(const InputID id, VirtualKey key); + + /// Resets the axes' positive key to the default. + /// @param id: The input to reset to default. + void setKeyToDefault(const InputID id); + + /// Gets the input ID for the supplied input. + /// If supplied an invalid inputName the function returns -1. + /// @param inputName: The name of the input to look up. + /// @return The id of the supplied input. + InputID getInputID(const nString& inputName) const; + + /// Reads all the axes stored in a given ini file. + /// @param filePath: The local path to the file to load axes from. + void loadInputs(const nString& filePath = INPUTMAPPER_DEFAULT_CONFIG_LOCATION); + + /// Saves currently stored axes to the given file path. + /// @param filePath: The local filePath to the file to save the loaded axes into. + void saveInputs(const nString& filePath = INPUTMAPPER_DEFAULT_CONFIG_LOCATION); + + /// Begins receiving input events from dispatcher + void startInput(); + /// Stops receiving input events from dispatcher + void stopInput(); + + const bool& isRecievingInput() const { return m_receivingInput; } + + /// Gets the input associated with the InputID + Input& get(InputID i) { + return m_inputs[i]; + } + Input& operator[](InputID i) { + return m_inputs[i]; + } + + const InputMap& getInputLookup() const { return m_inputLookup; } + + private: + void onMouseButtonDown(Sender, const vui::MouseButtonEvent& e); + void onMouseButtonUp(Sender, const vui::MouseButtonEvent& e); + void onKeyDown(Sender, const vui::KeyEvent& e); + void onKeyUp(Sender, const vui::KeyEvent& e); + + InputList m_inputs; ///< All the stored axes. + InputMap m_inputLookup; ///< A map of input names to input IDs for quick look up. + std::unordered_map > m_keyCodeMap; ///< Map of keycodes to active input + + bool m_keyStates[VKEY_HIGHEST_VALUE]; ///< The state of the keys and mouse buttons this frame. + + bool m_receivingInput = false; ///< Tracks input reception state + AutoDelegatePool m_inputHooks; ///< Stores input reception function hooks for deallocation + }; + +} +} +namespace vui = vorb::ui; + +#endif //Input_Manager_h diff --git a/include/ui/Keys.inl b/include/ui/Keys.inl index 6dc78b3cf..e197f3f0b 100644 --- a/include/ui/Keys.inl +++ b/include/ui/Keys.inl @@ -241,6 +241,12 @@ enum VirtualKey : ui16 { VKEY_KBDILLUMUP, VKEY_EJECT, VKEY_SLEEP, + VKEY_MOUSE_UNKNOWN, + VKEY_MOUSE_LEFT, + VKEY_MOUSE_RIGHT, + VKEY_MOUSE_MIDDLE, + VKEY_MOUSE_X1, + VKEY_MOUSE_X2, VKEY_HIGHEST_VALUE }; diff --git a/include/ui/MainGame.h b/include/ui/MainGame.h index 55d45b666..fbf272c6a 100644 --- a/include/ui/MainGame.h +++ b/include/ui/MainGame.h @@ -14,7 +14,7 @@ #pragma once #ifndef Vorb_MainGame_h__ -//! @cond DOXY_SHOW_HEADER_GUARDS +//! @cond DOXY_SHOW_HEADER_GUARDS2 #define Vorb_MainGame_h__ //! @endcond @@ -26,6 +26,7 @@ #include "../Events.hpp" #include "GameWindow.h" #include "ScreenList.h" +#include "../graphics/Renderer.h" namespace vorb { namespace ui { @@ -54,23 +55,6 @@ namespace vorb { */ virtual ~MainGame(); - /*! @brief Retrieve this application's window. - * - * @warning Do not attempt to set the window to a value other than its own. - * - * @return This game's window reference. - */ - GameWindow& getWindow() { - return m_window; - } - /*! @brief Retrieve this application's window. - * - * @return This game's window reference. - */ - const GameWindow& getWindow() const { - return m_window; - } - /*! @brief Initialize UI libraries and begin running the main loop. * * The run method blocks the thread on which it is called and it makes it the @@ -102,6 +86,29 @@ namespace vorb { const f32& getFps() const { return m_fps; } + + /************************************************************************/ + /* Accessors */ + /************************************************************************/ + /*! @brief Retrieve this application's window. + * + * @warning Do not attempt to set the window to a value other than its own. + * + * @return This game's window reference. + */ + GameWindow& getWindow() { + return m_window; + } + /*! @brief Retrieve this application's window. + * + * @return This game's window reference. + */ + const GameWindow& getWindow() const { + return m_window; + } + + vg::Renderer& getRenderer() { return m_renderer; } + protected: VORB_NON_COPYABLE(MainGame); VORB_NON_MOVABLE(MainGame); @@ -120,16 +127,19 @@ namespace vorb { void onUpdateFrame(); void onRenderFrame(); - GameWindow m_window; ///< The application's window + GameWindow m_window; ///< The application's window. + + vg::Renderer m_renderer; ///< Renders scenes to m_window. f32 m_fps = 0.0f; ///< Cached FPS value of the main loop UNIT_SPACE(MILLISECONDS) ui32 m_lastMS = 0; ///< Value of the last obtained clock time. + GameTime m_curTime; ///< The current known application time. GameTime m_lastTime; ///< The last time value, used for delta purposes. - bool m_isRunning = false; ///< Flag if the application thinks it's running. - ScreenList m_screenList; ///< List of the managed screens. - IGameScreen* m_screen = nullptr; ///< The active screen. + bool m_isRunning = false; ///< Flag if the application thinks it's running. + IGameScreen* m_screen = nullptr; ///< The active screen. + ScreenList m_screenList; ///< List of the managed screens. volatile bool m_signalQuit = false; ///< Tracks application's request to quit. }; diff --git a/include/ui/MouseInputDispatcher.h b/include/ui/MouseInputDispatcher.h index 3280e44b7..44f90c2f5 100644 --- a/include/ui/MouseInputDispatcher.h +++ b/include/ui/MouseInputDispatcher.h @@ -37,7 +37,7 @@ namespace vorb { /// The known mouse buttons enum class MouseButton { - UNKNOWN, ///< An unknown mouse button + UNKNOWN = 0, ///< An unknown mouse button LEFT, ///< The left mouse button MIDDLE, ///< The middle mouse button RIGHT, ///< The right mouse button diff --git a/include/voxel/BlockPack.h b/include/voxel/BlockPack.h new file mode 100644 index 000000000..82d7a1fb8 --- /dev/null +++ b/include/voxel/BlockPack.h @@ -0,0 +1,137 @@ +// +// BlockPack.h +// Vorb Engine +// +// Created by Benjamin Arnold on 3 Jul 2016 +// Copyright 2014 Regrowth Studios +// All Rights Reserved +// + +/*! \file BlockPack.h +* @brief Flyweight container for blocks. +* +* +*/ + +#pragma once + +#ifndef Vorb_BlockPack_h__ +//! @cond DOXY_SHOW_HEADER_GUARDS +#define Vorb_BlockPack_h__ +//! @endcond + +#ifndef VORB_USING_PCH +#include "types.h" +#endif // !VORB_USING_PCH + +#include "../Events.hpp" + +namespace vorb { +namespace voxel { + + typedef nString BlockIdentifier; ///< Unique identifier key for blocks + + /*! @brief All custom Block types should inherit from this. + */ + template + class Block { + public: + virtual ~Block() {}; + BlockIdentifier SID; + BLOCKID ID; + }; + + /*! @brief A pack of blocks. + * You should define a BLOCK type that inherits from vvox::Block. + * BLOCKINDEX should be an unsigned integer type, that is larger than the maximum number of unique blocks. + */ + template + class BlockPack { + public: + BlockPack() {} + + /*! @brief Add a block to the pack, and overwrite a block of the same BlockIdentifier + * Will invalidate existing BLOCK* pointers. Store BlockIDs instead. + * @return the BLOCKID of the new block, or of the existing block if this block is already added. + */ + BLOCKID append(BLOCK& block) { + const BLOCK* curBlock; + BLOCKID rv; + if (curBlock = hasBlock(block.SID)) { + rv = curBlock->ID; + block.ID = rv; + // Overwrite block + *const_cast(curBlock) = block; + } else { + rv = m_blockList.size(); + block.ID = rv; + // Add a new block + m_blockList.push_back(block); + // Set the correct index + m_blockMap[block.SID] = rv; + } + onBlockAddition(block.ID); + return rv; + } + + void reserveID(const BlockIdentifier& sid, const BLOCKID& id) { + if (id >= m_blockList.size()) m_blockList.resize(id + 1); + m_blockMap[sid] = id; + m_blockList[id].ID = id; + } + + + const BLOCK* hasBlock(const BLOCKID& id) const { + if (id >= m_blockList.size()) { + return nullptr; + } else { + return &m_blockList[id]; + } + } + const BLOCK* hasBlock(const BlockIdentifier& sid) const { + auto v = m_blockMap.find(sid); + if (v == m_blockMap.end()) { + return nullptr; + } else { + return &m_blockList[v->second]; + } + } + + size_t size() const { + return m_blockList.size(); + } + + /************************************************************************/ + /* BLOCK accessors */ + /************************************************************************/ + BLOCK& operator[](const size_t& index) { + return m_blockList[index]; + } + const BLOCK& operator[](const size_t& index) const { + return m_blockList[index]; + } + BLOCK& operator[](const BlockIdentifier& sid) { + return m_blockList[m_blockMap.at(sid)]; + } + const BLOCK& operator[](const BlockIdentifier& sid) const { + return m_blockList[m_blockMap.at(sid)]; + } + const BLOCKINDEX& getBlockIndex(const BlockIdentifier& sid) const { + return m_blockMap.at(sid); + } + + const std::unordered_map& getBlockMap() const { return m_blockMap; } + const std::vector& getBlockList() const { return m_blockList; } + + Event onBlockAddition; ///< Signaled when a block is loaded + private: + std::unordered_map m_blockMap; ///< Blocks indices organized by identifiers + std::vector m_blockList; ///< BLOCK data list + }; + +} +} +namespace vvox = vorb::voxel; + +#endif // !Vorb_BlockPack_h__ + diff --git a/include/voxel/Chunk.h b/include/voxel/Chunk.h new file mode 100644 index 000000000..07997b53c --- /dev/null +++ b/include/voxel/Chunk.h @@ -0,0 +1,50 @@ +// +// Chunk.h +// Vorb Engine +// +// Created by Benjamin Arnold on 3 Jul 2016 +// Copyright 2014 Regrowth Studios +// All Rights Reserved +// + +/*! \file Chunk.h +* @brief Defines a voxel chunk for the vorb engine. +* +* +*/ + +#pragma once + +#ifndef Vorb_Chunk_h__ +//! @cond DOXY_SHOW_HEADER_GUARDS +#define Vorb_Chunk_h__ +//! @endcond + +#ifndef VORB_USING_PCH +#include "types.h" +#endif // !VORB_USING_PCH + +#include "SmartVoxelContainer.hpp" + +namespace vorb { +namespace voxel { + + // Type for block data and tertiary data + template + class Chunk { + public: + + const ui32 width = WIDTH; + const ui32 size = WIDTH * WIDTH * WIDTH; + + SmartVoxelContainer blockData; + SmartVoxelContainer tertiaryData; + + }; + + +} +} +namespace vvox = vorb::voxel; + +#endif // !Vorb_Chunk_h__ diff --git a/include/voxel/SmartVoxelContainer.hpp b/include/voxel/SmartVoxelContainer.hpp new file mode 100644 index 000000000..61554475a --- /dev/null +++ b/include/voxel/SmartVoxelContainer.hpp @@ -0,0 +1,360 @@ +// +// SmartVoxelContainer.h +// Vorb Engine +// +// Created by Benjamin Arnold on 14 Nov 2014 +// Copyright 2014 Regrowth Studios +// All Rights Reserved +// +// Summary: +// +// + +#pragma once + +#ifndef SmartVoxelContainer_h__ +#define SmartVoxelContainer_h__ + +#include + +#include "../FixedSizeArrayRecycler.hpp" +#include "IntervalTree.h" + +#define QUIET_FRAMES_UNTIL_COMPRESS 60 +#define ACCESS_COUNT_UNTIL_DECOMPRESS 5 + +// TODO(Cristian): We'll see how to fit it into Vorb +namespace vorb { + namespace voxel { + + const ui32 DEFAULT_CHUNK_SIZE = 32 * 32 * 32; + + static ui32 MAX_COMPRESSIONS_PER_FRAME = UINT_MAX; ///< You can optionally set this in order to limit changes per frame + static ui32 totalContainerCompressions = 1; ///< Set this to 1 each frame + + template class SmartVoxelContainer; + + template + class SmartHandle { + friend class SmartVoxelContainer; + public: + operator const T&() const; + + SmartHandle& operator= (T data); + SmartHandle& operator= (const SmartHandle& o); + + SmartHandle(SmartHandle&& o) : + m_container(o.m_container), + m_index(o.m_index) { + // TODO: Empty for now try to null it out + } + SmartHandle(const SmartHandle& o) = delete; + SmartHandle& operator= (SmartHandle&& o) = delete; + private: + SmartHandle(SmartVoxelContainer& container, size_t index) : + m_container(container), + m_index(index) { + // Empty + } + + SmartVoxelContainer& m_container; ///< The parent container that created the handle + size_t m_index; ///< The index of this handle into the smart container + }; + + /// This should be called once per frame to reset totalContainerChanges + inline void clearContainerCompressionsCounter() { + totalContainerCompressions = 1; ///< Start at 1 so that integer overflow handles the default case + } + + enum class VoxelStorageState { + FLAT_ARRAY = 0, + INTERVAL_TREE = 1 + }; + + template + class SmartVoxelContainer { + friend class SmartHandle; + public: + /// Constructor + SmartVoxelContainer() { + // Empty + } + /*! @brief Construct the container with a provided recycler. + * + * @param arrayRecycler: The recycler to be used in place of the default generated recycler. + */ + SmartVoxelContainer(vcore::FixedSizeArrayRecycler* arrayRecycler) { + setArrayRecycler(arrayRecycler); + } + + /*! @brief Change the array recycler. + * + * @param arrayRecycler: The recycler to be used in place of the default generated recycler. + */ + void setArrayRecycler(vcore::FixedSizeArrayRecycler* arrayRecycler) { + _arrayRecycler = arrayRecycler; + } + + SmartHandle operator[] (size_t index) { + return std::move(SmartHandle(*this, index)); + } + const T& operator[] (size_t index) const { + return (getters[(size_t)_state])(this, index); + } + + /*! @brief Initializes the container + * + * @param state: Determines if compression or flat array should be used. + */ + inline void init(VoxelStorageState state) { + _state = state; + if (_state == VoxelStorageState::FLAT_ARRAY) { + _dataArray = _arrayRecycler->create(); + } + } + + /*! @brief Initializes the container + * + * @param state: Determines if compression or flat array should be used. + * @param value: Value to initialize all voxel data to. + */ + inline void init(VoxelStorageState state, T value) { + _state = state; + if (_state == VoxelStorageState::FLAT_ARRAY) { + _dataArray = _arrayRecycler->create(); + for (size_t i = 0; i < SIZE; i++) { + _dataArray[i] = value; + } + } + } + + /// Creates the tree using a sorted array of data. + /// The number of voxels should add up to the size + /// @param state: Initial state of the container + /// @param data: The sorted array used to populate the container + inline void initFromSortedArray(VoxelStorageState state, + const std::vector ::LNode>& data) { + _state = state; + _accessCount = 0; + _quietFrames = 0; + if (_state == VoxelStorageState::INTERVAL_TREE) { + _dataTree.initFromSortedArray(data); + _dataTree.checkTreeValidity(); + } else { + _dataArray = _arrayRecycler->create(); + int index = 0; + for (size_t i = 0; i < data.size(); i++) { + // TODO(Ben): Optimize + for (int j = 0; j < data[i].length; j++) { + _dataArray[index++] = data[i].data; + } + } + } + } + inline void initFromSortedArray(VoxelStorageState state, + const typename IntervalTree::LNode data[], size_t size) { + _state = state; + _accessCount = 0; + _quietFrames = 0; + if (_state == VoxelStorageState::INTERVAL_TREE) { + _dataTree.initFromSortedArray(data, size); + _dataTree.checkTreeValidity(); + } else { + _dataArray = _arrayRecycler->create(); + int index = 0; + for (size_t i = 0; i < size; i++) { + for (int j = 0; j < data[i].length; j++) { + _dataArray[index++] = data[i].data; + } + } + } + } + + inline void changeState(VoxelStorageState newState, std::mutex& dataLock) { + if (newState == _state) return; + if (newState == VoxelStorageState::INTERVAL_TREE) { + compress(dataLock); + } else { + uncompress(dataLock); + } + _quietFrames = 0; + _accessCount = 0; + } + + /// Updates the container. Call once per frame + /// @param dataLock: The mutex that guards the data + inline void update(std::mutex& dataLock) { + // If access count is higher than the threshold, this is not a quiet frame + if (_accessCount >= ACCESS_COUNT_UNTIL_DECOMPRESS) { + _quietFrames = 0; + } else { + _quietFrames++; + } + + if (_state == VoxelStorageState::INTERVAL_TREE) { + // Check if we should uncompress the data + if (_quietFrames == 0) { + uncompress(dataLock); + } + } else { + // Check if we should compress the data + if (_quietFrames >= QUIET_FRAMES_UNTIL_COMPRESS && totalContainerCompressions <= MAX_COMPRESSIONS_PER_FRAME) { + compress(dataLock); + } + } + _accessCount = 0; + } + + /// Clears the container and frees memory + inline void clear() { + _accessCount = 0; + _quietFrames = 0; + if (_state == VoxelStorageState::INTERVAL_TREE) { + _dataTree.clear(); + } else if (_dataArray) { + _arrayRecycler->recycle(_dataArray); + _dataArray = nullptr; + } + } + + /// Uncompressed the interval tree into a buffer. + /// May only be called when getState() == VoxelStorageState::INTERVAL_TREE + /// or you will get a null access violation. + /// @param buffer: Buffer of memory to store the result + inline void uncompressIntoBuffer(T* buffer) { _dataTree.uncompressIntoBuffer(buffer); } + + /// Getters + const VoxelStorageState& getState() const { + return _state; + } + T* getDataArray() { + return _dataArray; + } + const T* getDataArray() const { + return _dataArray; + } + IntervalTree& getTree() { + return _dataTree; + } + const IntervalTree& getTree() const { + return _dataTree; + } + + /// Gets the element at index + /// @param index: must be (0, SIZE] + /// @return The element + inline const T& get(size_t index) const { + return (getters[(size_t)_state])(this, index); + } + /// Sets the element at index + /// @param index: must be (0, SIZE] + /// @param value: The value to set at index + inline void set(size_t index, T value) { + _accessCount++; + (setters[(size_t)_state])(this, index, value); + } + private: + typedef const T& (*Getter)(const SmartVoxelContainer*, size_t); + typedef void(*Setter)(SmartVoxelContainer*, size_t, T); + + static const T& getInterval(const SmartVoxelContainer* container, size_t index) { + return container->_dataTree.getData(index); + } + static const T& getFlat(const SmartVoxelContainer* container, size_t index) { + return container->_dataArray[index]; + } + static void setInterval(SmartVoxelContainer* container, size_t index, T data) { + container->_dataTree.insert(index, data); + } + static void setFlat(SmartVoxelContainer* container, size_t index, T data) { + container->_dataArray[index] = data; + } + + static Getter getters[2]; + static Setter setters[2]; + + inline void uncompress(std::mutex& dataLock) { + dataLock.lock(); + _dataArray = _arrayRecycler->create(); + uncompressIntoBuffer(_dataArray); + // Free memory + _dataTree.clear(); + // Set the new state + _state = VoxelStorageState::FLAT_ARRAY; + dataLock.unlock(); + } + inline void compress(std::mutex& dataLock) { + dataLock.lock(); + // Sorted array for creating the interval tree + // Using stack array to avoid allocations, beware stack overflow + IntervalTree::LNode data[SIZE]; + int index = 0; + data[0].set(0, 1, _dataArray[0]); + // Set the data + for (int i = 1; i < SIZE; ++i) { + if (_dataArray[i] == data[index].data) { + ++(data[index].length); + } else { + data[++index].set(i, 1, _dataArray[i]); + } + } + // Set new state + _state = VoxelStorageState::INTERVAL_TREE; + // Create the tree + _dataTree.initFromSortedArray(data, index + 1); + + dataLock.unlock(); + + // Recycle memory + _arrayRecycler->recycle(_dataArray); + _dataArray = nullptr; + + totalContainerCompressions++; + } + + IntervalTree _dataTree; ///< Interval tree of voxel data + + T* _dataArray = nullptr; ///< pointer to an array of voxel data + int _accessCount = 0; ///< Number of times the container was accessed this frame + int _quietFrames = 0; ///< Number of frames since we have had heavy updates + + VoxelStorageState _state = VoxelStorageState::FLAT_ARRAY; ///< Current data structure state + + vcore::FixedSizeArrayRecycler* _arrayRecycler = nullptr; ///< For recycling the voxel arrays + }; + + /*template + inline SmartHandle::operator const T&() const { + return m_container[m_index]; + }*/ + template + inline SmartHandle::operator const T&() const { + return (m_container.getters[(size_t)m_container.getState()])(&m_container, m_index); + } + template + inline SmartHandle& SmartHandle::operator= (T data) { + m_container.set(m_index, data); + return *this; + } + template + inline SmartHandle& SmartHandle::operator= (const SmartHandle& o) { + m_container.set(m_index, o.m_container[o.m_index]); + return *this; + } + + template + typename SmartVoxelContainer::Getter SmartVoxelContainer::getters[2] = { + SmartVoxelContainer::getFlat, + SmartVoxelContainer::getInterval + }; + template + typename SmartVoxelContainer::Setter SmartVoxelContainer::setters[2] = { + SmartVoxelContainer::setFlat, + SmartVoxelContainer::setInterval + }; + + } +} +namespace vvox = vorb::voxel; + +#endif // SmartVoxelContainer_h__ \ No newline at end of file diff --git a/include/voxel/VoxCommon.h b/include/voxel/VoxCommon.h index 028a20539..744374907 100644 --- a/include/voxel/VoxCommon.h +++ b/include/voxel/VoxCommon.h @@ -51,13 +51,19 @@ namespace vorb { /// Create a cardinal direction /// @param a: Axis - /// @param positive: True if facing in positive direction /// @return Cardinal direction - Cardinal toCardinal(const Axis& a, const bool& positive); + inline Cardinal toCardinalPositive(const Axis& a) { + return (Cardinal)((ui8)a << 1) | Cardinal::POSITIVE; + } + inline Cardinal toCardinalNegative(const Axis& a) { + return (Cardinal)((ui8)a << 1) | Cardinal::NEGATIVE; + } /// Extract axis information from cardinal direction /// @param c: Cardinal direction /// @return Axis of the cardinal direction - Axis toAxis(const Cardinal& c); + inline Axis toAxis(const Cardinal& c) { + return (Axis)((ui8)c >> 1); + } } } namespace vvox = vorb::voxel; diff --git a/include/voxel/VoxelMeshAlg.h b/include/voxel/VoxelMeshAlg.h index 8f698dcd2..8c9138b59 100644 --- a/include/voxel/VoxelMeshAlg.h +++ b/include/voxel/VoxelMeshAlg.h @@ -30,8 +30,8 @@ namespace vorb { /// Two flags specifying which faces are visible struct VoxelFaces { public: - bool block1Face : 1; ///< First block face's visibility - bool block2Face : 1; ///< Second block face's visibility + bool face1; ///< First block face's visibility + bool face2; ///< Second block face's visibility }; /// A quad created in a voxel mesh surface @@ -49,20 +49,19 @@ namespace vorb { /// @param startIndex: The index of the first vertex /// @return Array of quad vertex indices template - CALLER_DELETE T* generateQuadIndices(const ui32& quads, T startIndex = 0) { + void generateQuadIndices(OUT std::vector& inds, ui32 quads, T startIndex = 0) { size_t ic = quads * 6; - T* inds = new T[ic]; + inds.resize(ic); T vi = startIndex; for (size_t ii = 0; ii < ic;) { inds[ii++] = vi; - inds[ii++] = vi + 2; - inds[ii++] = vi + 1; inds[ii++] = vi + 1; inds[ii++] = vi + 2; + inds[ii++] = vi + 2; inds[ii++] = vi + 3; + inds[ii++] = vi; vi += 4; } - return inds; } } } diff --git a/include/voxel/VoxelMesherCulled.h b/include/voxel/VoxelMesherCulled.h index ae46eebcd..c28272f91 100644 --- a/include/voxel/VoxelMesherCulled.h +++ b/include/voxel/VoxelMesherCulled.h @@ -28,6 +28,11 @@ namespace vorb { namespace voxel { namespace meshalg { + enum class VoxelOcclusion { + DRAW_A = 0, + DRAW_B = 1, + OCCLUDE = 2 + }; /// Construct a voxel mesh, generating and culling desired face pairs /// @tparam T: Voxel data type /// @tparam API: Type of API object that handles culled meshing @@ -48,8 +53,8 @@ namespace vorb { }; ui32v3 pos; - size_t l1 = size.x; - size_t l2 = l1 * size.z; + size_t rowSize = size.x; + size_t layerSize = rowSize * size.z; for (size_t axis = 0; axis < 3; axis++) { ui32& fAxis = pos[SWEEPS[axis].x]; @@ -58,31 +63,34 @@ namespace vorb { ui32v3 sizes(size[SWEEPS[axis].x], size[SWEEPS[axis].y], size[SWEEPS[axis].z]); VoxelQuad qNeg, qPos; - qPos.direction = toCardinal(AXES[axis], true); - qNeg.direction = toCardinal(AXES[axis], false); + qPos.direction = toCardinalPositive(AXES[axis]); + qNeg.direction = toCardinalNegative(AXES[axis]); qNeg.size = qPos.size = ui32v2(1, 1); - for (fAxis = 1; fAxis < sizes.x; fAxis++) { - for (uAxis = 1; uAxis < sizes.y - 1; uAxis++) { - for (vAxis = 1; vAxis < sizes.z - 1; vAxis++) { + for (fAxis = 1; fAxis < sizes.x - 1; fAxis++) { + for (uAxis = 0; uAxis < sizes.y; uAxis++) { + for (vAxis = 0; vAxis < sizes.z; vAxis++) { fAxis--; qPos.voxelPosition = pos; - qPos.startIndex = pos.y * l2 + pos.z * l1 + pos.x; + qPos.startIndex = pos.y * layerSize + pos.z * rowSize + pos.x; const T& v1 = data[qPos.startIndex]; fAxis++; qNeg.voxelPosition = pos; - qNeg.startIndex = pos.y * l2 + pos.z * l1 + pos.x; + qNeg.startIndex = pos.y * layerSize + pos.z * rowSize + pos.x; const T& v2 = data[qNeg.startIndex]; VoxelFaces f = api->occludes(v1, v2, AXES[axis]); - if (f.block1Face && fAxis != 1) api->result(qPos); - if (f.block2Face && fAxis != sizes.x - 1) api->result(qNeg); + if (f.face1) api->result(qPos, v1); + if (f.face2) api->result(qNeg, v2); } } } + + //TODO(Ben) Outer Edge } } + } } } diff --git a/src/graphics/Camera.cpp b/src/graphics/Camera.cpp new file mode 100644 index 000000000..fdf9bb4d5 --- /dev/null +++ b/src/graphics/Camera.cpp @@ -0,0 +1,109 @@ +#include "stdafx.h" +#include "graphics/Camera.h" + +const f32v3 vg::Camera::UP_ABSOLUTE = f32v3(0.0f, 1.0f, 0.0f); + +vg::Camera::Camera() { + // Empty +} + +vg::Camera::~Camera() { + +} + +void vg::Camera::init(f32 aspectRatio) { + m_aspectRatio = aspectRatio; +} + +void vg::Camera::update() { + bool updateFrustum = false; + if (m_viewChanged) { + updateView(); + m_viewChanged = false; + updateFrustum = true; + } + if (m_projectionChanged) { + updateProjection(); + m_projectionChanged = false; + updateFrustum = true; + } + + if (updateFrustum) { + m_viewProjectionMatrix = m_projectionMatrix * m_viewMatrix; + m_frustum.updateFromWVP(m_viewProjectionMatrix); + } +} + +void vg::Camera::offsetPosition(const f32v3& offset) { + m_position += offset; + m_viewChanged = true; +} + +void vg::Camera::offsetPosition(const f64v3& offset) { + m_position += offset; + m_viewChanged = true; +} + +void vg::Camera::rotate(const f32q& rot) { + m_direction = rot * m_direction; + m_left = rot * m_left; + m_up = vmath::normalize(vmath::cross(m_direction, m_left)); + + m_viewChanged = true; +} + +void vg::Camera::rotate(f32 yaw, f32 pitch) { + f32q upQuat = vmath::angleAxis(pitch, m_left); + f32q rightQuat = vmath::angleAxis(yaw, m_up); + + rotate(upQuat * rightQuat); +} + +void vg::Camera::rotateAbsoluteUp(f32 yaw, f32 pitch, bool clampVerticalRotation /*= false*/) { + f32q upQuat = vmath::angleAxis(pitch, m_left); + f32q rightQuat = vmath::angleAxis(yaw, UP_ABSOLUTE); + + f32v3 previousDirection = m_direction; + f32v3 previousUp = m_up; + f32v3 previousRight = m_left; + + rotate(upQuat * rightQuat); + + if (clampVerticalRotation && m_up.y < 0) { + m_direction = previousDirection; + m_up = previousUp; + m_left = previousRight; + rotateAbsoluteUp(yaw, 0.0f); + } +} + +void vg::Camera::roll(f32 roll) { + f32q frontQuat = vmath::angleAxis(roll, m_direction); + + rotate(frontQuat); +} + +void vg::Camera::setOrientation(const f32v3& direction, const f32v3& up) { + m_direction = vmath::normalize(direction); + m_up = vmath::normalize(up); + m_left = vmath::cross(m_up, m_direction); + // We calculate up again to guarantee orthogonality + m_up = vmath::cross(m_direction, m_left); +} + +void vg::Camera::setOrientation(const f64q& orientation) { + m_direction = orientation * f64v3(0.0, 0.0, 1.0); + m_left = orientation * f64v3(1.0, 0.0, 0.0); + m_up = orientation * f64v3(0.0, 1.0, 0.0); + m_viewChanged = true; +} + +void vg::Camera::updateView() { + // TODO(Ben): Allow camera relative view matrix. + m_viewMatrix = vmath::lookAt(f32v3(m_position), f32v3(m_position) + m_direction, m_up); +} + +void vg::Camera::updateProjection() { + m_frustum.setCamInternals(vmath::radians(m_fieldOfView), m_aspectRatio, m_zNear, m_zFar); + m_projectionMatrix = vmath::perspective(vmath::radians(m_fieldOfView), m_aspectRatio, m_zNear, m_zFar); +} diff --git a/src/graphics/FullQuadVBO.cpp b/src/graphics/FullQuadVBO.cpp index 926ee296c..99a47a7a8 100644 --- a/src/graphics/FullQuadVBO.cpp +++ b/src/graphics/FullQuadVBO.cpp @@ -25,13 +25,13 @@ void vg::FullQuadVBO::init(i32 attrLocation /*= 0*/) { } void vg::FullQuadVBO::dispose() { - if (m_buffers[0]) { + if (m_vao) { glDeleteBuffers(2, m_buffers); m_buffers[0] = 0; m_buffers[1] = 0; - } - if (m_vao) { + glDeleteVertexArrays(1, &m_vao); + m_vao = 0; ///< So we know we aren't initialized. } } diff --git a/src/graphics/GBuffer.cpp b/src/graphics/GBuffer.cpp index a68f47df3..7c04d1032 100644 --- a/src/graphics/GBuffer.cpp +++ b/src/graphics/GBuffer.cpp @@ -18,9 +18,9 @@ void vg::GBuffer::initTarget(const ui32v2& _size, const ui32& texID, const vg::G SamplerState::POINT_CLAMP.set(GL_TEXTURE_2D); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachment.number, GL_TEXTURE_2D, texID, 0); } -vg::GBuffer& vg::GBuffer::init(const Array& attachments, vg::TextureInternalFormat lightFormat) { +vg::GBuffer& vg::GBuffer::init(const std::vector& attachments, vg::TextureInternalFormat lightFormat) { // Create texture targets - m_textures.setData(attachments.size() + 1); + m_textures.resize(attachments.size() + 1); glGenTextures((GLsizei)m_textures.size(), &m_textures[0]); // Make the framebuffer @@ -105,8 +105,8 @@ void vg::GBuffer::dispose() { m_fboLight = 0; } if (m_textures.size() != 0) { - glDeleteTextures((GLsizei)m_textures.size(), &m_textures[0]); - m_textures.setData(0); + glDeleteTextures((GLsizei)m_textures.size(), m_textures.data()); + std::vector().swap(m_textures); } if (m_texDepth != 0) { glDeleteTextures(1, &m_texDepth); diff --git a/src/graphics/PostProcess.cpp b/src/graphics/PostProcess.cpp new file mode 100644 index 000000000..628117849 --- /dev/null +++ b/src/graphics/PostProcess.cpp @@ -0,0 +1,554 @@ +#include "stdafx.h" +#include "graphics/PostProcess.h" +#include "VorbAssert.hpp" +#include "math/VorbMath.hpp" +#include "graphics/SamplerState.h" +#include "graphics/ShaderCommon.inl" +#include "graphics/ShaderManager.h" +#include "graphics/Renderer.h" +#include "graphics/Camera.h" + +#include + + +vg::FullQuadVBO vg::IPostProcess::quad; + +/************************************************************************/ +/* IPostProcess */ +/************************************************************************/ + +void vg::IPostProcess::unregister() { + vorb_assert(m_renderer, "PostProcess unregistered without having renderer."); + + m_renderer->unregisterPostProcess(this); +} + +bool vg::IPostProcess::tryInitQuad() { + if (quad.isInitialized()) return false; + quad.init(); + return true; +} + +#define TEXTURE_SLOT_INPUT0 4 +#define TEXTURE_SLOT_INPUT1 5 +#define TEXTURE_SLOT_INPUT2 6 +#define TEXTURE_SLOT_INPUT3 7 +static_assert(TEXTURE_SLOT_INPUT0 >= (ui32)vg::GBUFFER_TEXTURE_UNITS::COUNT, "PostProcess input will collide with GBuffer"); + +/************************************************************************/ +/* Bloom */ +/************************************************************************/ + +#define BLOOM_TEXTURE_SLOT_LUMA TEXTURE_SLOT_INPUT2 // texture slot to bind luma texture +#define BLOOM_TEXTURE_SLOT_BLUR TEXTURE_SLOT_INPUT3 // texture slot to bind blur texture + +#pragma region BloomShaderCode + +/// Bloom shaders created by Isaque Dutra on 2 June 2015 +/// Optimized and ported to Vorb by Ben Arnold on 15 June 2016 +/// Copyright 2015 Regrowth Studios +/// All Rights Reserved + +const cString BLOOM_LUMA_FRAG_SRC = R"( +/// This fragment shader filters out the image's parts with luma (brightness) +/// stronger than uniform unLumaThresh and scales it proportionally passing it +/// to be blurred by next stages. +/// Taken from OpenGL 4.0 Shading Language Cookbook, First Edition, by David Wolff + +// input +in vec2 fUV; + +// output +out vec4 pColor; + +// uniforms +uniform sampler2D unTexColor; // Texture with rendering color from previous stage +uniform float unLumaThresh; // Threshold for filtering image luma for bloom bluring + +// returns the average brightness of a pixel +float luma(vec3 color) { + return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b; +} + +void main() { + vec3 val = texture(unTexColor, fUV).rgb; + pColor = vec4(val * clamp(luma(val.rgb) - unLumaThresh, 0.0, 1.0) / (1.0 - unLumaThresh), 1.0); +})"; + +const cString BLOOM_GAUSS1_FRAG_SRC = R"( +/// This fragment shader implements first stage of two-pass gaussian blur. +/// It blurs only the brighest parts filtered out by the Luma stage. +/// Taken from OpenGL 4.0 Shading Language Cookbook, First Edition, by David Wolff + +// input +in vec2 fUV; + +//output +out vec4 pColor; + +// uniforms +uniform float unInvHeight; // 1 / Window height +uniform sampler2D unTexLuma; // Texture with brighest parts of image +uniform int unGaussianN; // Radius for gaussian blur +uniform float unWeight[50]; // Gaussian weights + +// first pass of gaussian blur, in the y direction +void main() { + + vec3 sum = texture(unTexLuma, fUV).rgb * unWeight[0]; + float offset = unInvHeight; + for(int i = 1; i < unGaussianN; i++) { + sum += (texture(unTexLuma, vec2(fUV.x, fUV.y + offset)).rgb + + texture(unTexLuma, vec2(fUV.x, fUV.y - offset)).rgb) * unWeight[i]; + offset += unInvHeight; + } + pColor = vec4(sum, 1.0); +} +)"; + +const cString BLOOM_GAUSS2_FRAG_SRC = R"( +/// This fragment shader implements second stage of two-pass gaussian blur. +/// It blurs on the x-axis the parts already blurred from the first pass, +/// then sums to original color image. +/// Taken from OpenGL 4.0 Shading Language Cookbook, First Edition, by David Wolff + +// input +in vec2 fUV; +// output +out vec4 pColor; + +// uniforms +uniform float unInvWidth; // 1 / Window width +uniform sampler2D unTexColor; // Original color texture from pass before bloom pass +uniform sampler2D unTexBlur; // Blur texture from first blur pass +uniform int unGaussianN; // Radius for gaussian blur +uniform float unWeight[50]; // Gaussian weights + +// returns the average brightness of a pixel +float luma(vec3 color) { + return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b; +} + +// Blurs image on x-axis and sums into the original image +void main() { + + vec3 val = texture(unTexColor, fUV).rgb; + vec3 sum = texture(unTexBlur, fUV).rgb * unWeight[0]; + float offset = unInvWidth; + for(int i = 1; i < unGaussianN; i++) { + sum += (texture(unTexBlur, vec2(fUV.x + offset, fUV.y)).rgb + + texture(unTexBlur, vec2(fUV.x - offset, fUV.y)).rgb) * unWeight[i]; + offset += unInvWidth; + } + + pColor = vec4(val + sum, 1.0); +} +)"; + +/************************************************************************/ +/* SSAO */ +/************************************************************************/ + +#define SSAO_NOISE_TEXTURE_SIZE 4 +#define SSAO_SAMPLE_KERNEL_SIZE 32 +#define SSAO_BLUR_AMOUNT 2.0f + +const cString SSAO_FRAG_SRC = R"( +// Uniforms +uniform sampler2D unTexDepth; +uniform sampler2D unTexNormal; +uniform sampler2D unTexNoise; +const int SAMPLE_KERNEL_SIZE = 32; +uniform vec3 unSampleKernel[SAMPLE_KERNEL_SIZE]; +uniform vec2 unNoiseScale; +uniform float unRadius = 1.0; + +uniform mat4 unViewMatrix; +uniform mat4 unProjectionMatrix; +uniform mat4 unInvProjectionMatrix; + +// Input +in vec2 fUV; + +// Output +out float pColor; + +vec3 viewSpaceCoordinate(float depth) { + vec4 screenSpaceCoordinate = vec4(fUV.x * 2.0 - 1.0, fUV.y * 2.0 - 1.0, depth, 1.0); + screenSpaceCoordinate = unInvProjectionMatrix * screenSpaceCoordinate; + return screenSpaceCoordinate.xyz / screenSpaceCoordinate.w; +} + +// From this http://john-chapman-graphics.blogspot.de/2013/01/ssao-tutorial.html + +void main() { + float depth = texture(unTexDepth, fUV).r; + vec3 origin = viewSpaceCoordinate(depth); + + vec3 normal = (unViewMatrix * vec4(normalize(texture(unTexNormal, fUV).xyz), 1.0)).xyz; + + // Random sample kernel rotation + vec3 rotationVector = normalize(vec3(texture(unTexNoise, fUV * unNoiseScale).xy, 0.0)); + vec3 tangent = normalize(rotationVector - normal * dot(rotationVector, normal)); + vec3 bitangent = cross(normal, tangent); + mat3 tbn = mat3(tangent, bitangent, normal); + + float totalOcclusion = 0.0; + for (int i = 0; i < SAMPLE_KERNEL_SIZE; ++i) { + // Get sample position + vec3 sample = (tbn * unSampleKernel[i]) * unRadius + origin; + + // Project sample position + vec4 screenSpaceSample = unProjectionMatrix * vec4(sample, 1.0); + screenSpaceSample.xy /= screenSpaceSample.w; + screenSpaceSample.xy = screenSpaceSample.xy * 0.5 + 0.5; + // Get sample depth + float sampleDepth = texture(unTexDepth, screenSpaceSample.xy).r; + // Range check and accumulate + // TODO(Ben): No branching? + float rangeCheck = abs(depth - sampleDepth) < unRadius ? 1.0 : 0.0; + totalOcclusion += (sampleDepth < depth ? 1.0 : 0.0) * rangeCheck; + } + + pColor = (1.0 - totalOcclusion / float(SAMPLE_KERNEL_SIZE)); +} +)"; + +// TODO(Ben): 2 pass optimization? +const cString SSAO_BLUR_FRAG_SRC = R"( +// Uniforms +uniform sampler2D unTexSSAO; +uniform sampler2D unTexColor; +uniform float unBlurAmount; + +// Input +in vec2 fUV; + +// Output +out vec4 pColor; +out vec4 pNormal; + +void main() { + float blurSSAO = 0.0f; + vec2 texelSize = 1.0 / textureSize(unTexSSAO, 0); + int samples = 0; + for (float x = fUV.x - texelSize.x * unBlurAmount; x <= fUV.x + texelSize.x * unBlurAmount; x += texelSize.x) { + for (float y = fUV.y - texelSize.y * unBlurAmount; y <= fUV.y + texelSize.y * unBlurAmount; y += texelSize.y) { + blurSSAO += texture(unTexSSAO, vec2(x, y)).r; + samples++; + } + } + blurSSAO /= float(samples); + + vec4 textureColor = texture(unTexColor, fUV); + pColor = vec4(textureColor.rgb * blurSSAO, textureColor.a); +} +)"; + +#pragma endregion + +inline f32 gauss(int i, f32 sigma2) { + return 1.0 / vmath::sqrt(2 * 3.14159265 * sigma2) * vmath::exp(-(i*i) / (2 * sigma2)); +} + +void vg::PostProcessBloom::init(ui32 windowWidth, ui32 windowHeight, VGTexture inputColorTexture) { + m_windowWidth = windowWidth; + m_windowHeight = windowHeight; + m_inputTextures.resize(1, inputColorTexture); +} + +void vg::PostProcessBloom::setParams(ui32 gaussianN /* = 20*/, float gaussianVariance /*= 36.0f*/, float lumaThreshold /*= 0.75f*/) { + + vorb_assert(gaussianN <= 50, "Gaussian Radius for PostProcessBloom::setParams has to be less than 50."); + + m_gaussianN = gaussianN; + m_gaussianVariance = gaussianVariance; + m_lumaThreshold = lumaThreshold; + + uploadShaderUniforms(); +} + +void vg::PostProcessBloom::load() { + + vorb_assert(m_windowWidth != 0 && m_windowHeight != 0, "PostProcessBloom was not initialized."); + + tryInitQuad(); + // initialize FBOs + m_fbos[0].setSize(m_windowWidth, m_windowHeight); + m_fbos[1].setSize(m_windowWidth, m_windowHeight); + m_fbos[0].init(vg::TextureInternalFormat::RGBA16F, 0, vg::TextureFormat::RGBA, vg::TexturePixelType::FLOAT); + m_fbos[1].init(vg::TextureInternalFormat::RGBA16F, 0, vg::TextureFormat::RGBA, vg::TexturePixelType::FLOAT); + + // Load shaders + // TODO(Ben): Error checking + // TODO(Ben): Re-use compiled vertex shader + m_programLuma = ShaderManager::createProgram(shadercommon::PASSTHROUGH_2D_VERT_SRC, BLOOM_LUMA_FRAG_SRC); + m_programGaussianFirst = ShaderManager::createProgram(shadercommon::PASSTHROUGH_2D_VERT_SRC, BLOOM_GAUSS1_FRAG_SRC); + m_programGaussianSecond = ShaderManager::createProgram(shadercommon::PASSTHROUGH_2D_VERT_SRC, BLOOM_GAUSS2_FRAG_SRC); + + uploadShaderUniforms(); +} + +void vg::PostProcessBloom::render() { + // No depth testing + // TODO(Ben): Don't fuck with existing state. Need state manager. + glDisable(GL_DEPTH_TEST); + + // luma pass rendering on temporary FBO 1 using the color gbuffer + m_fbos[0].use(); + glActiveTexture(GL_TEXTURE0 + TEXTURE_SLOT_INPUT0); + glBindTexture(GL_TEXTURE_2D, m_inputTextures.at(0)); + renderStage(m_programLuma); + + // first gaussian blur pass rendering on temporary FBO 2 + m_fbos[1].use(); + glActiveTexture(GL_TEXTURE0 + BLOOM_TEXTURE_SLOT_LUMA); + m_fbos[0].bindTexture(); + renderStage(m_programGaussianFirst); + m_fbos[1].unuse(m_windowWidth, m_windowHeight); + + // second gaussian blur pass rendering. Sum initial color with blur color. + glActiveTexture(GL_TEXTURE0 + BLOOM_TEXTURE_SLOT_BLUR); + m_fbos[1].bindTexture(); + + // Bind output FBO + glBindFramebuffer(GL_FRAMEBUFFER, m_outputFBO); + renderStage(m_programGaussianSecond); + + // TODO(Ben): Need state manager + glEnable(GL_DEPTH_TEST); +} + +void vg::PostProcessBloom::dispose() { + m_programLuma.dispose(); + m_programGaussianFirst.dispose(); + m_programGaussianSecond.dispose(); + m_fbos[0].dispose(); + m_fbos[1].dispose(); + + // Unregister if we need to. + if (m_renderer) { + m_renderer->unregisterPostProcess(this); + } +} + +void vg::PostProcessBloom::uploadShaderUniforms() { + if (m_programGaussianSecond.isLinked()) { + m_programLuma.use(); + glUniform1i(m_programLuma.getUniform("unTexColor"), TEXTURE_SLOT_INPUT0); + glUniform1f(m_programLuma.getUniform("unLumaThresh"), m_lumaThreshold); + m_programLuma.unuse(); + + m_programGaussianFirst.use(); + glUniform1i(m_programGaussianFirst.getUniform("unTexLuma"), BLOOM_TEXTURE_SLOT_LUMA); + glUniform1f(m_programGaussianFirst.getUniform("unInvHeight"), 1.0f / (f32)m_windowHeight); + glUniform1i(m_programGaussianFirst.getUniform("unGaussianN"), m_gaussianN); + m_programGaussianFirst.unuse(); + + m_programGaussianSecond.use(); + glUniform1i(m_programGaussianSecond.getUniform("unTexColor"), TEXTURE_SLOT_INPUT0); + glUniform1i(m_programGaussianSecond.getUniform("unTexBlur"), BLOOM_TEXTURE_SLOT_BLUR); + glUniform1f(m_programGaussianSecond.getUniform("unInvWidth"), 1.0f / (f32)m_windowWidth); + glUniform1i(m_programGaussianSecond.getUniform("unGaussianN"), m_gaussianN); + m_programGaussianSecond.unuse(); + + // Calculate gaussian weights + f32 weights[50], sum; + weights[0] = gauss(0, m_gaussianVariance); + sum = weights[0]; + for (ui32 i = 1; i < m_gaussianN; i++) { + weights[i] = gauss(i, m_gaussianVariance); + sum += 2 * weights[i]; + } + for (ui32 i = 0; i < m_gaussianN; i++) { + weights[i] = weights[i] / sum; + } + m_programGaussianFirst.use(); + glUniform1fv(m_programGaussianFirst.getUniform("unWeight[0]"), m_gaussianN, weights); + m_programGaussianFirst.unuse(); + m_programGaussianSecond.use(); + glUniform1fv(m_programGaussianSecond.getUniform("unWeight[0]"), m_gaussianN, weights); + m_programGaussianSecond.unuse(); + } +} + +void vg::PostProcessBloom::renderStage(vg::GLProgram& program) { + + program.use(); + program.enableVertexAttribArrays(); + + quad.draw(); + + program.disableVertexAttribArrays(); + program.unuse(); +} + +void vg::PostProcessSSAO::init(ui32 windowWidth, ui32 windowHeight, + VGTexture inputColorTexture, + VGTexture inputDepthTexture, + VGTexture inputNormalTexture, + vg::Camera* camera) { + m_windowWidth = windowWidth; + m_windowHeight = windowHeight; + m_inputTextures.resize(3, 0); + m_inputTextures[0] = inputColorTexture; + m_inputTextures[1] = inputDepthTexture; + m_inputTextures[2] = inputNormalTexture; + m_camera = camera; +} + +void vg::PostProcessSSAO::load() { + std::mt19937 randGenerator; + std::uniform_real_distribution range1(-1.0f, 1.0f); + std::uniform_real_distribution range2(0.0f, 1.0f); + + // Generate random data + i32 pixCount = SSAO_NOISE_TEXTURE_SIZE * SSAO_NOISE_TEXTURE_SIZE; + std::vector data(pixCount); + + for (i32 i = 0; i < pixCount; i++) { + // TODO(Ben): vec3? + data[i].x = range1(randGenerator); + data[i].y = range1(randGenerator); + data[i] = vmath::normalize(data[i]); + } + + // Build noise texture + glGenTextures(1, &m_noiseTexture); + glBindTexture(GL_TEXTURE_2D, m_noiseTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, SSAO_NOISE_TEXTURE_SIZE, SSAO_NOISE_TEXTURE_SIZE, 0, GL_RG, GL_FLOAT, data.data()); + vg::SamplerState::POINT_WRAP.set(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + + m_ssaoTarget.setSize(m_windowWidth, m_windowHeight); + m_ssaoTarget.init(vg::TextureInternalFormat::R32F); + + m_sampleKernel.resize(SSAO_SAMPLE_KERNEL_SIZE); + for (unsigned int i = 0; i < SSAO_SAMPLE_KERNEL_SIZE; i++) { + m_sampleKernel[i] = vmath::normalize(f32v3(range1(randGenerator), + range1(randGenerator), + range2(randGenerator))); + // Use accelerating interpolation + f32 scale = (f32)i / (f32)SSAO_SAMPLE_KERNEL_SIZE; + scale = lerp(0.1f, 1.0f, scale * scale); + m_sampleKernel[i] *= scale; + } + + m_programSSAO = ShaderManager::createProgram(shadercommon::PASSTHROUGH_2D_VERT_SRC, SSAO_FRAG_SRC); + m_programBlur = ShaderManager::createProgram(shadercommon::PASSTHROUGH_2D_VERT_SRC, SSAO_BLUR_FRAG_SRC); + + uploadShaderUniforms(); +} + +void vg::PostProcessSSAO::render() { + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + + const f32m4& projectionMatrix = m_camera->getProjectionMatrix(); + const f32m4& viewMatrix = m_camera->getViewMatrix(); + + { // SSAO pass + m_ssaoTarget.use(); + glClear(GL_COLOR_BUFFER_BIT); + + // Bind textures + glActiveTexture(GL_TEXTURE0 + TEXTURE_SLOT_INPUT0); + glBindTexture(GL_TEXTURE_2D, m_inputTextures.at(1)); + glActiveTexture(GL_TEXTURE0 + TEXTURE_SLOT_INPUT1); + glBindTexture(GL_TEXTURE_2D, m_inputTextures.at(2)); + glActiveTexture(GL_TEXTURE0 + TEXTURE_SLOT_INPUT2); + glBindTexture(GL_TEXTURE_2D, m_noiseTexture); + + m_programSSAO.use(); + + glUniformMatrix4fv(m_programSSAO.getUniform("unViewMatrix"), 1, false, &viewMatrix[0][0]); + glUniformMatrix4fv(m_programSSAO.getUniform("unProjectionMatrix"), 1, false, &projectionMatrix[0][0]); + glUniformMatrix4fv(m_programSSAO.getUniform("unInvProjectionMatrix"), 1, false, &vmath::inverse(projectionMatrix)[0][0]); + + quad.draw(); + } + + { // Blur pass + glBindFramebuffer(GL_FRAMEBUFFER, m_outputFBO); + glActiveTexture(GL_TEXTURE0 + TEXTURE_SLOT_INPUT0); + glBindTexture(GL_TEXTURE_2D, m_inputTextures.at(0)); + glActiveTexture(GL_TEXTURE0 + TEXTURE_SLOT_INPUT1); + glBindTexture(GL_TEXTURE_2D, m_ssaoTarget.getTextureID()); + + m_programBlur.use(); + + quad.draw(); + + vg::GLProgram::unuse(); + } + + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); +} + +void vg::PostProcessSSAO::dispose() { + if (m_noiseTexture) { + glDeleteTextures(1, &m_noiseTexture); + m_noiseTexture = 0; + } + m_programSSAO.dispose(); + m_ssaoTarget.dispose(); +} + +void vg::PostProcessSSAO::uploadShaderUniforms() { + m_programSSAO.use(); + glUniform1i(m_programSSAO.getUniform("unTexDepth"), TEXTURE_SLOT_INPUT0); + glUniform1i(m_programSSAO.getUniform("unTexNormal"), TEXTURE_SLOT_INPUT1); + glUniform1i(m_programSSAO.getUniform("unTexNoise"), TEXTURE_SLOT_INPUT2); + glUniform3fv(glGetUniformLocation(m_programSSAO.getID(), "unSampleKernel"), m_sampleKernel.size(), &m_sampleKernel.data()->x); + glUniform2f(m_programSSAO.getUniform("unNoiseScale"), + (f32)m_ssaoTarget.getWidth() / SSAO_NOISE_TEXTURE_SIZE, + (f32)m_ssaoTarget.getHeight() / SSAO_NOISE_TEXTURE_SIZE); + + m_programBlur.use(); + glUniform1i(m_programBlur.getUniform("unTexColor"), TEXTURE_SLOT_INPUT0); + glUniform1i(m_programBlur.getUniform("unTexSSAO"), TEXTURE_SLOT_INPUT1); + glUniform1f(m_programBlur.getUniform("unBlurAmount"), SSAO_BLUR_AMOUNT); + m_programBlur.unuse(); +} + +void vg::PostProcessPassthrough::init(VGTexture inputTexture) { + m_inputTextures.resize(1, inputTexture); +} + +void vg::PostProcessPassthrough::load() { + m_program = ShaderManager::createProgram(shadercommon::PASSTHROUGH_2D_VERT_SRC, shadercommon::TEXTURE_FRAG_SRC); + + uploadShaderUniforms(); + tryInitQuad(); +} + +void vg::PostProcessPassthrough::render() { + + glDisable(GL_DEPTH_TEST); + + glBindFramebuffer(GL_FRAMEBUFFER, m_outputFBO); + + glActiveTexture(GL_TEXTURE0 + TEXTURE_SLOT_INPUT0); + glBindTexture(GL_TEXTURE_2D, m_inputTextures.at(0)); + + m_program.use(); + quad.draw(); + m_program.unuse(); + + + glEnable(GL_DEPTH_TEST); +} + +void vg::PostProcessPassthrough::dispose() { + m_program.dispose(); +} + +void vg::PostProcessPassthrough::uploadShaderUniforms() { + if (m_program.isLinked()) { + // Can interrupt outer state + m_program.use(); + glUniform1i(m_program.getUniform("unSampler"), TEXTURE_SLOT_INPUT0); + m_program.unuse(); + } +} diff --git a/src/graphics/Renderer.cpp b/src/graphics/Renderer.cpp new file mode 100644 index 000000000..0178ec1fd --- /dev/null +++ b/src/graphics/Renderer.cpp @@ -0,0 +1,245 @@ +#include "stdafx.h" +#include "graphics/Renderer.h" +#include "graphics/Scene.h" +#include "graphics/PostProcess.h" +#include "graphics/GBuffer.h" + +vg::Renderer::Renderer() { + // Empty +} + +vg::Renderer::~Renderer() { + if (!m_isInitialized) { + dispose(); + } +} + +bool vg::Renderer::init(vui::GameWindow* window) { + + vorb_assert(!m_isInitialized, "Renderer was initialized twice."); + + m_window = window; + m_isInitialized = true; + + // Set up GBuffer for deferred rendering. + m_gBuffer = std::make_unique(m_window->getWidth(), m_window->getHeight()); + + std::vector attachments; + { // Color + attachments.emplace_back(); + GBufferAttachment& a = attachments.back(); + a.format = GBUFFER_INTERNAL_FORMAT_COLOR; + a.pixelFormat = vg::TextureFormat::RGBA; + a.pixelType = vg::TexturePixelType::FLOAT; + a.number = (ui32)GBUFFER_TEXTURE_UNITS::COLOR; + } + { // Position + attachments.emplace_back(); + GBufferAttachment& a = attachments.back(); + a.format = vg::TextureInternalFormat::RGB16F; + a.pixelFormat = vg::TextureFormat::RGB; + a.pixelType = vg::TexturePixelType::FLOAT; + a.number = (ui32)GBUFFER_TEXTURE_UNITS::POSITION; + } + { // Normal + attachments.emplace_back(); + GBufferAttachment& a = attachments.back(); + a.format = vg::TextureInternalFormat::RGB16F; + a.pixelFormat = vg::TextureFormat::RGB; + a.pixelType = vg::TexturePixelType::FLOAT; + a.number = (ui32)GBUFFER_TEXTURE_UNITS::NORMAL; + } + m_gBuffer->init(attachments, GBUFFER_INTERNAL_FORMAT_LIGHT); + m_gBuffer->initDepth(); +} + +void vg::Renderer::load() { + for (IScene* s : m_sceneLoadQueue) { + s->load(); + m_scenes.push_back(s); + } + m_sceneLoadQueue.clear(); + + for (IPostProcess* p : m_postProcessesLoadQueue) { + p->load(); + m_postProcesses.push_back(p); + } + m_postProcessesLoadQueue.clear(); +} + +void vg::Renderer::registerScene(IScene* scene) { + // Make sure the scene doesn't exist + vorb_assert(std::find(m_scenes.begin(), m_scenes.end(), scene) == m_scenes.end(), "Scene already added to SceneRenderer."); + + scene->m_renderer = this; + + m_sceneLoadQueue.push_back(scene); +} + +bool vg::Renderer::unregisterScene(IScene* scene) { + auto& it = std::find(m_scenes.begin(), m_scenes.end(), scene); + if (it != m_scenes.end()) { + // Remove the scene + m_scenes.erase(it); + scene->m_renderer = nullptr; + return true; + } else { + // Check the load list instead + auto& it2 = std::find(m_sceneLoadQueue.begin(), m_sceneLoadQueue.end(), scene); + if (it != m_scenes.end()) { + m_sceneLoadQueue.erase(it2); + scene->m_renderer = nullptr; + return true; + } + } + return false; +} + +void vg::Renderer::registerPostProcess(IPostProcess* postProcess) { + // Make sure the PostProcess doesn't exist + vorb_assert(std::find(m_postProcesses.begin(), m_postProcesses.end(), postProcess) == m_postProcesses.end(), "PostProcess already added to SceneRenderer."); + + postProcess->m_renderer = this; + + m_postProcessesLoadQueue.push_back(postProcess); +} + +bool vg::Renderer::unregisterPostProcess(IPostProcess* postProcess) { + auto& it = std::find(m_postProcesses.begin(), m_postProcesses.end(), postProcess); + if (it != m_postProcesses.end()) { + // Remove the PostProcess + m_postProcesses.erase(it); + postProcess->m_renderer = nullptr; + return true; + } else { + // Check the load list instead + auto& it2 = std::find(m_postProcessesLoadQueue.begin(), m_postProcessesLoadQueue.end(), postProcess); + if (it != m_postProcesses.end()) { + m_postProcessesLoadQueue.erase(it2); + postProcess->m_renderer = nullptr; + return true; + } + } + return false; +} + +void vg::Renderer::setBackgroundColor(const color4& color) { + setBackgroundColor(f32v4((f32)color.r * 255.0f, + (f32)color.g * 255.0f, + (f32)color.b * 255.0f, + (f32)color.a * 255.0f)); +} + +void vg::Renderer::setBackgroundColor(const f32v4& color) { +#if defined(VORB_IMPL_GRAPHICS_OPENGL) + glClearColor(color.r, color.g, color.b, color.a); +#else + throw "Only OpenGL background color is supported by the renderer" +#endif +} + +void vg::Renderer::beginRenderFrame() { + + // Bind the GBuffer for drawing + m_gBuffer->useGeometry(); + +#if defined(VORB_IMPL_GRAPHICS_OPENGL) + + // TODO(Ben): Allow optional clearing + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + +#elif defined(VORB_IMPL_GRAPHICS_D3D) + + { + +#if defined(VORB_DX_9) + + D3DVIEWPORT9 vp; + vp.X = 0; + vp.Y = 0; + vp.Width = m_window.getWidth(); + vp.Height = m_window.getHeight(); + vp.MinZ = 0.0f; + vp.MaxZ = 1.0f; + VG_DX_DEVICE(m_window.getContext())->SetViewport(&vp); + +#endif + + } + +#if defined(VORB_DX_9) + + VG_DX_DEVICE(m_window.getContext())->BeginScene(); + +#endif +#endif +} + +void vg::Renderer::renderScenes(const vui::GameTime& gameTime) { + for (IScene* s : m_scenes) { + s->render(gameTime); + } +} + +void vg::Renderer::renderPostProcesses() { + + // Disable FBO (Render to screen) + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(0, 0, m_window->getWidth(), m_window->getHeight()); + + // Bind gbuffer textures + m_gBuffer->bindGeometryTexture((ui32)GBUFFER_TEXTURE_UNITS::COLOR, (ui32)GBUFFER_TEXTURE_UNITS::COLOR); + m_gBuffer->bindGeometryTexture((ui32)GBUFFER_TEXTURE_UNITS::POSITION, (ui32)GBUFFER_TEXTURE_UNITS::POSITION); + m_gBuffer->bindGeometryTexture((ui32)GBUFFER_TEXTURE_UNITS::NORMAL, (ui32)GBUFFER_TEXTURE_UNITS::NORMAL); + + // Do all post processing + for (IPostProcess* p : m_postProcesses) { + p->render(); + } + + // If we have no post processes, we have to render the color to the screen manually. + if (m_postProcesses.empty()) { + // Lazy load + if (!m_postProcessPassthrough) { + m_postProcessPassthrough = std::make_unique(); + m_postProcessPassthrough->init((ui32)GBUFFER_TEXTURE_UNITS::COLOR); + m_postProcessPassthrough->load(); + } + // Bind color + m_gBuffer->bindGeometryTexture(0, 0); + // Render color to screen + m_postProcessPassthrough->render(); + } + + // Unset FBO + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(0, 0, m_window->getWidth(), m_window->getHeight()); +} + +void vg::Renderer::dispose() { + for (IScene* s : m_scenes) { + s->dispose(); + } + std::vector().swap(m_scenes); + for (IScene* s : m_sceneLoadQueue) { + s->dispose(); + } + std::vector().swap(m_sceneLoadQueue); + + for (IPostProcess* p : m_postProcesses) { + p->dispose(); + } + std::vector().swap(m_postProcesses); + for (IPostProcess* p : m_postProcessesLoadQueue) { + p->dispose(); + } + std::vector().swap(m_postProcessesLoadQueue); + + if (m_postProcessPassthrough) { + m_postProcessPassthrough->dispose(); + m_postProcessPassthrough.reset(); + m_postProcessPassthrough = nullptr; + } + + m_isInitialized = false; +} diff --git a/src/graphics/Scene.cpp b/src/graphics/Scene.cpp new file mode 100644 index 000000000..027a3daa1 --- /dev/null +++ b/src/graphics/Scene.cpp @@ -0,0 +1,32 @@ +#include "stdafx.h" +#include "graphics/Scene.h" +#include "graphics/Camera.h" +#include "graphics/Renderer.h" + +vg::IScene::IScene() { + // Empty +} + +vg::IScene::~IScene() { + // Empty +} + +void vg::IScene::initCamera(f32 aspectRatio) { + m_camera = std::make_unique(); + m_camera->init(aspectRatio); +} + +void vg::IScene::unregister() { + vorb_assert(m_renderer, "Scene unregistered without having renderer."); + + m_renderer->unregisterScene(this); +} + +void vg::IScene::dispose() { + m_camera.reset(); + + // Unregister if we need to. + if (m_renderer) { + m_renderer->unregisterScene(this); + } +} \ No newline at end of file diff --git a/src/graphics/ShaderManager.cpp b/src/graphics/ShaderManager.cpp index e38cdb071..4ce3fd4f6 100644 --- a/src/graphics/ShaderManager.cpp +++ b/src/graphics/ShaderManager.cpp @@ -1,6 +1,5 @@ #include "stdafx.h" #include "graphics/ShaderManager.h" -#include "graphics/GLProgram.h" #include "graphics/ShaderParser.h" #include "io/IOManager.h" @@ -13,6 +12,7 @@ vg::GLProgramMap vg::ShaderManager::m_programMap; vg::GLProgram vg::ShaderManager::m_nilProgram; vg::GLProgram vg::ShaderManager::createProgram(const cString vertSrc, const cString fragSrc, + ShaderLanguageVersion version /*= DEFAULT_SHADING_LANGUAGE_VERSION*/, vio::IOManager* vertIOM /*= nullptr*/, vio::IOManager* fragIOM /*= nullptr*/, cString defines /*= nullptr*/) { @@ -38,6 +38,7 @@ vg::GLProgram vg::ShaderManager::createProgram(const cString vertSrc, const cStr // Create vertex shader ShaderSource srcVert; srcVert.stage = vg::ShaderType::VERTEX_SHADER; + srcVert.version = version; if (defines) srcVert.sources.push_back(defines); srcVert.sources.push_back(parsedVertSrc.c_str()); if (!program.addShader(srcVert)) { @@ -51,6 +52,7 @@ vg::GLProgram vg::ShaderManager::createProgram(const cString vertSrc, const cStr // Create the fragment shader ShaderSource srcFrag; srcFrag.stage = vg::ShaderType::FRAGMENT_SHADER; + srcFrag.version = version; if (defines) srcFrag.sources.push_back(defines); srcFrag.sources.push_back(parsedFragSrc.c_str()); if (!program.addShader(srcFrag)) { @@ -74,6 +76,7 @@ vg::GLProgram vg::ShaderManager::createProgram(const cString vertSrc, const cStr } vg::GLProgram vg::ShaderManager::createProgramFromFile(const vio::Path& vertPath, const vio::Path& fragPath, + ShaderLanguageVersion version /*= DEFAULT_SHADING_LANGUAGE_VERSION*/, vio::IOManager* iom /*= nullptr*/, cString defines /*= nullptr*/) { vio::IOManager ioManager; vio::Path vertSearchDir; @@ -110,7 +113,7 @@ vg::GLProgram vg::ShaderManager::createProgramFromFile(const vio::Path& vertPath return m_nilProgram; } - return createProgram(vertSrc.c_str(), fragSrc.c_str(), &vertIOM, &fragIOM, defines); + return createProgram(vertSrc.c_str(), fragSrc.c_str(), version, &vertIOM, &fragIOM, defines); } void vg::ShaderManager::disposeAllPrograms() { diff --git a/src/graphics/SpriteBatch.cpp b/src/graphics/SpriteBatch.cpp index 872a9b6fe..da8e41478 100644 --- a/src/graphics/SpriteBatch.cpp +++ b/src/graphics/SpriteBatch.cpp @@ -372,7 +372,7 @@ void vg::SpriteBatch::generateBatches() { m_batches.back().set(indexCount, g->tex); } // Call builder function - (this->*g->func)(g, verts + vi); + g->func(g, verts + vi); vi += VERTS_PER_QUAD; indexCount += INDICES_PER_QUAD; } diff --git a/src/ui/GameWindow.cpp b/src/ui/GameWindow.cpp index a6402b9ca..666e809fc 100644 --- a/src/ui/GameWindow.cpp +++ b/src/ui/GameWindow.cpp @@ -444,6 +444,16 @@ void vui::GameWindow::setTitle(const cString title) const { #endif } +void vui::GameWindow::setRelativeMouseMode(bool relative) const { +#if defined(VORB_IMPL_UI_SDL) + SDL_SetRelativeMouseMode(static_cast(relative)); +#elif defined(VORB_IMPL_UI_GLFW) + // TODO +#elif defined(VORB_IMPL_UI_SFML) + // TODO +#endif +} + void vui::GameWindow::setPosition(int x, int y) { #if defined(VORB_IMPL_UI_SDL) SDL_SetWindowPosition((SDL_Window*)m_window, x, y); diff --git a/src/ui/InputMapper.cpp b/src/ui/InputMapper.cpp new file mode 100644 index 000000000..0dd15606a --- /dev/null +++ b/src/ui/InputMapper.cpp @@ -0,0 +1,215 @@ +#include "stdafx.h" +#include "ui/InputMapper.h" + +#include "io/Keg.h" +#include "io/IOManager.h" + +struct InputKegArray { + Array defaultKey; + Array key; +}; +KEG_TYPE_DECL(InputKegArray); +KEG_TYPE_DEF(InputKegArray, InputKegArray, kt) { + using namespace keg; + kt.addValue("defaultKey", Value::array(offsetof(InputKegArray, defaultKey), Value::custom(0, "VirtualKey", true))); + kt.addValue("key", Value::array(offsetof(InputKegArray, key), Value::custom(0, "VirtualKey", true))); +} +vui::InputMapper::InputMapper() { + memset(m_keyStates, 0, sizeof(m_keyStates)); +} + +vui::InputMapper::~InputMapper() { + stopInput(); +} + +bool vui::InputMapper::getInputState(const InputID id) { + // Check Input + if (id < 0 || id >= (int)m_inputs.size()) return false; + return m_keyStates[m_inputs.at(id).key]; +} + +vui::InputMapper::InputID vui::InputMapper::createInput(const nString& inputName, VirtualKey defaultKey) { + InputID id = getInputID(inputName); + if (id >= 0) return id; + id = m_inputs.size(); + m_inputLookup[inputName] = id; + m_inputs.emplace_back(id, inputName, defaultKey, this); + m_keyCodeMap[m_inputs.back().key].push_back(id); + return id; +} + +vui::InputMapper::InputID vui::InputMapper::getInputID(const nString& inputName) const { + auto iter = m_inputLookup.find(inputName); + + if (iter != m_inputLookup.end()) { + return iter->second; + } else { + return -1; + } +} + +void vui::InputMapper::loadInputs(const nString &location /* = INPUTMAPPER_DEFAULT_CONFIG_LOCATION */) { + vio::IOManager iom; //TODO PASS IN + nString data; + + // If the file doesn't exist, just make it with defaults + if (!iom.fileExists(location)) { + saveInputs(location); + return; + } + + iom.readFileToString(location.c_str(), data); + + if (data.length() == 0) { + fprintf(stderr, "Failed to load %s", location.c_str()); + throw 33; + } + + keg::ReadContext context; + context.env = keg::getGlobalEnvironment(); + context.reader.init(data.c_str()); + keg::Node node = context.reader.getFirst(); + if (keg::getType(node) != keg::NodeType::MAP) { + perror(location.c_str()); + context.reader.dispose(); + throw 34; + } + + // Manually parse yml file + auto f = makeFunctor([&] (Sender, const nString& name, keg::Node value) { + InputKegArray kegArray; + + keg::parse((ui8*)&kegArray, value, context, &KEG_GET_TYPE(InputKegArray)); + + // TODO(Ben): Somehow do multikey support + // Right now its only using the first key + InputID id = m_inputs.size(); + m_inputs.emplace_back(id, name, kegArray.defaultKey[0], this); + m_keyCodeMap[m_inputs.back().key].push_back(id); + + if (kegArray.key.size()) { + m_inputs.back().key = kegArray.key[0]; + } else { + m_inputs.back().key = kegArray.defaultKey[0]; + } + + m_inputLookup[name] = id; + + }); + context.reader.forAllInMap(node, f); + delete f; + context.reader.dispose(); +} + +void vui::InputMapper::startInput() { + if (!m_receivingInput) { + vui::InputDispatcher::mouse.onButtonDown += makeDelegate(*this, &vui::InputMapper::onMouseButtonDown); + vui::InputDispatcher::mouse.onButtonUp += makeDelegate(*this, &vui::InputMapper::onMouseButtonDown); + vui::InputDispatcher::key.onKeyDown += makeDelegate(*this, &vui::InputMapper::onKeyDown); + vui::InputDispatcher::key.onKeyUp += makeDelegate(*this, &vui::InputMapper::onKeyUp); + m_receivingInput = true; + } +} +void vui::InputMapper::stopInput() { + if (m_receivingInput) { + vui::InputDispatcher::mouse.onButtonDown -= makeDelegate(*this, &vui::InputMapper::onMouseButtonDown); + vui::InputDispatcher::mouse.onButtonUp -= makeDelegate(*this, &vui::InputMapper::onMouseButtonDown); + vui::InputDispatcher::key.onKeyDown -= makeDelegate(*this, &vui::InputMapper::onKeyDown); + vui::InputDispatcher::key.onKeyUp -= makeDelegate(*this, &vui::InputMapper::onKeyUp); + m_receivingInput = false; + } +} + +void vui::InputMapper::saveInputs(const nString &filePath /* = INPUTMAPPER_DEFAULT_CONFIG_LOCATION */) { + //TODO(Ben): Implement + // vio::IOManager iom; + // Just build the data string manually then write it + + /* bool tmp; + keg::Enum enm; + nString data = ""; + for (auto& input : m_inputs) { + data += input->name + ":\n"; + data += " defaultKey:\n"; + data += " - " + keg::getEnum(tmp, .getValue() + }*/ +} + +VirtualKey vui::InputMapper::getKey(const InputID id) { + if (id < 0 || id >= (int)m_inputs.size()) return VKEY_HIGHEST_VALUE; + return m_inputs.at(id).key; +} + +void vui::InputMapper::setKey(const InputID id, VirtualKey key) { + // Need to remove old key state + VirtualKey oldKey = m_inputs.at(id).key; + auto& it = m_keyCodeMap.find(oldKey); + auto& vec = it->second; + for (size_t i = 0; i < vec.size(); i++) { + // Remove the input from the vector keyed on VirtualKey + if (vec[i] == id) { + vec[i] = vec.back(); + vec.pop_back(); + break; + } + } + // Set new key + m_keyCodeMap[key].push_back(id); + m_inputs[id].key = key; +} + +void vui::InputMapper::setKeyToDefault(const InputID id) { + if (id < 0 || id >= (int)m_inputs.size()) return; + setKey(id, m_inputs.at(id).defaultKey); +} + +void vui::InputMapper::onMouseButtonDown(Sender, const vui::MouseButtonEvent& e) { + // These must map 1:1 + ui32 code = VKEY_MOUSE_UNKNOWN + (ui32)e.button; + if (!m_keyStates[code]) { + m_keyStates[code] = true; + auto& it = m_keyCodeMap.find((VirtualKey)code); + if (it != m_keyCodeMap.end()) { + // Call all events mapped to that virtual key + for (auto& id : it->second) { + m_inputs[id].downEvent(code); + } + } + } +} + +void vui::InputMapper::onMouseButtonUp(Sender, const vui::MouseButtonEvent& e) { + ui32 code = VKEY_MOUSE_UNKNOWN + (ui32)e.button; + m_keyStates[code] = false; + auto& it = m_keyCodeMap.find((VirtualKey)code); + if (it != m_keyCodeMap.end()) { + // Call all events mapped to that virtual key + for (auto& id : it->second) { + m_inputs[id].downEvent(code); + } + } +} + +void vui::InputMapper::onKeyDown(Sender, const vui::KeyEvent& e) { + if (!m_keyStates[e.keyCode]) { + m_keyStates[e.keyCode] = true; + auto& it = m_keyCodeMap.find((VirtualKey)e.keyCode); + if (it != m_keyCodeMap.end()) { + // Call all events mapped to that virtual key + for (auto& id : it->second) { + m_inputs[id].downEvent(e.keyCode); + } + } + } +} + +void vui::InputMapper::onKeyUp(Sender, const vui::KeyEvent& e) { + m_keyStates[e.keyCode] = false; + auto& it = m_keyCodeMap.find((VirtualKey)e.keyCode); + if (it != m_keyCodeMap.end()) { + // Call all events mapped to that virtual key + for (auto& id : it->second) { + m_inputs[id].upEvent(e.keyCode); + } + } +} diff --git a/src/ui/MainGame.cpp b/src/ui/MainGame.cpp index 0ae8965fb..9d4b4dd3c 100644 --- a/src/ui/MainGame.cpp +++ b/src/ui/MainGame.cpp @@ -2,6 +2,7 @@ #include "ui/MainGame.h" #include +#include #if defined(VORB_IMPL_UI_SDL) #if defined(VORB_OS_WINDOWS) @@ -63,6 +64,7 @@ bool vui::MainGame::init() { // Run The First Game Screen m_screen->setRunning(); m_screen->onEntry(m_lastTime); + m_screen->registerRendering(m_renderer); } // Set last known time @@ -81,8 +83,11 @@ bool vui::MainGame::initSystems() { // Set A Default OpenGL State vg::DepthState::FULL.set(); vg::RasterizerState::CULL_CLOCKWISE.set(); -#elif defined(VORB_IMPL_GRAPHICS_D3D) + m_renderer.init(&m_window); + +#elif defined(VORB_IMPL_GRAPHICS_D3D) + #endif return true; @@ -93,6 +98,7 @@ void vui::MainGame::exitGame() { m_screen = nullptr; } m_screenList.destroy(m_lastTime); + m_renderer.dispose(); onExit(); m_window.dispose(); m_isRunning = false; @@ -112,6 +118,7 @@ bool vui::MainGame::checkScreenChange() { if (m_screen != nullptr) { m_screen->setRunning(); m_screen->onEntry(m_curTime); + m_screen->registerRendering(m_renderer); } return true; case ScreenState::CHANGE_PREVIOUS: @@ -120,6 +127,7 @@ bool vui::MainGame::checkScreenChange() { if (m_screen != nullptr) { m_screen->setRunning(); m_screen->onEntry(m_curTime); + m_screen->registerRendering(m_renderer); } return true; case ScreenState::EXIT_APPLICATION: @@ -162,7 +170,7 @@ void vui::MainGame::run() { // Refresh time information for this frame refreshElapsedTime(); - // Scree logic + // Screen logic if (!checkScreenChange()) { // Update onUpdateFrame(); @@ -174,7 +182,7 @@ void vui::MainGame::run() { // Swap buffers and synchronize time-step and window input ui32 curMS = MS_TIME; - m_window.sync(curMS - m_lastMS); + m_renderer.sync(curMS - m_lastMS); // Get the FPS m_fps = fpsCounter.endFrame(); @@ -202,35 +210,31 @@ void vui::MainGame::refreshElapsedTime() { m_curTime.elapsed = et; m_curTime.total += et; } + void vui::MainGame::onUpdateFrame() { // Perform the screen's update logic m_screen->update(m_curTime); } + void vui::MainGame::onRenderFrame() { + // Load anything that needs loading + // TODO(Ben): Support asynchronous. + m_renderer.load(); + + m_renderer.beginRenderFrame(); + + m_screen->onRenderFrame(m_curTime); + m_renderer.renderScenes(m_curTime); + m_renderer.renderPostProcesses(); + + // Check for OpenGL errors #if defined(VORB_IMPL_GRAPHICS_OPENGL) - // TODO: Investigate Removing This - glViewport(0, 0, m_window.getWidth(), m_window.getHeight()); -#elif defined(VORB_IMPL_GRAPHICS_D3D) - { -#if defined(VORB_DX_9) - D3DVIEWPORT9 vp; - vp.X = 0; - vp.Y = 0; - vp.Width = m_window.getWidth(); - vp.Height = m_window.getHeight(); - vp.MinZ = 0.0f; - vp.MaxZ = 1.0f; - VG_DX_DEVICE(m_window.getContext())->SetViewport(&vp); -#endif - } -#if defined(VORB_DX_9) - VG_DX_DEVICE(m_window.getContext())->BeginScene(); +#ifndef defined(NDEBUG) + nString msg; + vorb_assert(!vg::checkGlError("vui::MainGame::onRenderFrame()", msg), msg.c_str()); #endif #endif - // Draw the screen - m_screen->draw(m_curTime); - #if defined(VORB_IMPL_GRAPHICS_D3D) #if defined(VORB_DX_9) VG_DX_DEVICE(m_window.getContext())->EndScene(); diff --git a/src/voxel/VoxCommon.cpp b/src/voxel/VoxCommon.cpp index 1a55358b4..6146c142b 100644 --- a/src/voxel/VoxCommon.cpp +++ b/src/voxel/VoxCommon.cpp @@ -1,9 +1,3 @@ #include "stdafx.h" #include "voxel/VoxCommon.h" -vvox::Cardinal vvox::toCardinal(const Axis& a, const bool& positive) { - return (Cardinal)(a << 1) | (positive ? Cardinal::POSITIVE : Cardinal::NEGATIVE); -} -vvox::Axis vvox::toAxis(const Cardinal& a) { - return (Axis)(a >> 1); -} \ No newline at end of file