WindowResizeEvent | FlushScene
- 'Renderer' now receives 'WindowResizedEvent' and handle the
      'Framebuffer' and viewport stuff
- Added 'Renderer' now uses the new 'FlushScene' function to flush when
      the mapped vertex buffer is filled
- The viewport is now set by 'RenderCommand' via the 'Renderer' rather
      than the 'GraphicsConntext'
- 'Window' no longer sends 'WindowResizedEvent' to 'GraphicsContext'
- 'GraphicsContext' no longer receives 'OnWindowResize' events
- Note: Sending events should be done by the 'Application'
			
			
This commit is contained in:
		
							parent
							
								
									44b135f157
								
							
						
					
					
						commit
						57616d755c
					
				
					 17 changed files with 86 additions and 63 deletions
				
			
		| 
						 | 
				
			
			@ -5,14 +5,14 @@
 | 
			
		|||
 | 
			
		||||
#include "Events/Event.h"
 | 
			
		||||
 | 
			
		||||
#include "Debug/Instrumentor.h"
 | 
			
		||||
 | 
			
		||||
#include "Graphics/GraphicsContext.h"
 | 
			
		||||
#include "Graphics/Renderer.h"
 | 
			
		||||
#include "Graphics/RenderCommand.h"
 | 
			
		||||
 | 
			
		||||
#include "UserInterface/UserInterface.h"
 | 
			
		||||
 | 
			
		||||
#include "Debug/Instrumentor.h"
 | 
			
		||||
 | 
			
		||||
#include "Time/Timer.h"
 | 
			
		||||
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
| 
						 | 
				
			
			@ -94,8 +94,13 @@ namespace Light {
 | 
			
		|||
	{
 | 
			
		||||
		// window
 | 
			
		||||
		if (event.HasCategory(WindowEventCategory))
 | 
			
		||||
		{
 | 
			
		||||
			m_Window->OnEvent(event);
 | 
			
		||||
 | 
			
		||||
			if(event.GetEventType() == EventType::WindowResized)
 | 
			
		||||
				m_Window->GetGfxContext()->GetRenderer()->OnWindowResize((const WindowResizedEvent&)event);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// user interface
 | 
			
		||||
		if (event.HasCategory(InputEventCategory))
 | 
			
		||||
			m_Window->GetGfxContext()->GetUserInterface()->OnInput(event);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,8 +44,6 @@ namespace Light {
 | 
			
		|||
 | 
			
		||||
		virtual ~GraphicsContext();
 | 
			
		||||
 | 
			
		||||
		virtual void OnWindowResize(const WindowResizedEvent& event) = 0;
 | 
			
		||||
 | 
			
		||||
		virtual void LogDebugData() = 0;
 | 
			
		||||
 | 
			
		||||
		static inline GraphicsAPI GetGraphicsAPI() { return s_Context->m_GraphicsAPI; }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,8 @@ namespace Light {
 | 
			
		|||
		virtual void Draw(unsigned int count) = 0;
 | 
			
		||||
		virtual void DrawIndexed(unsigned int count) = 0;
 | 
			
		||||
 | 
			
		||||
		virtual void SetViewport(unsigned int x, unsigned int y, unsigned int width, unsigned int height) = 0;
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		RenderCommand() = default;
 | 
			
		||||
	};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,8 @@
 | 
			
		|||
#include "Texture.h"
 | 
			
		||||
#include "RenderCommand.h"
 | 
			
		||||
 | 
			
		||||
#include "Events/WindowEvents.h"
 | 
			
		||||
 | 
			
		||||
#include "Camera/Camera.h"
 | 
			
		||||
 | 
			
		||||
#include <glm/glm.hpp>
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +30,7 @@ namespace Light {
 | 
			
		|||
		m_ViewProjectionBuffer = std::unique_ptr<ConstantBuffer>(ConstantBuffer::Create(ConstantBufferIndex::ViewProjection, sizeof(glm::mat4), sharedContext));
 | 
			
		||||
 | 
			
		||||
		m_Blender = std::unique_ptr<Blender>(Blender::Create(sharedContext));
 | 
			
		||||
		m_Blender->Enable(BlendFactor::SRC_ALPHA, BlendFactor::INVERSE_SRC_ALPHA);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Renderer* Renderer::Create(GLFWwindow* windowHandle, std::shared_ptr<SharedContext> sharedContext)
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +38,11 @@ namespace Light {
 | 
			
		|||
		return new Renderer(windowHandle, sharedContext);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void Renderer::OnWindowResize(const WindowResizedEvent& event)
 | 
			
		||||
	{
 | 
			
		||||
		m_RenderCommand->SetViewport(0u, 0u, event.GetSize().x, event.GetSize().y);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void Renderer::DrawQuadImpl(const glm::vec3& position, const glm::vec2& size, const glm::vec4& tint)
 | 
			
		||||
	{
 | 
			
		||||
		// locals
 | 
			
		||||
| 
						 | 
				
			
			@ -64,8 +72,8 @@ namespace Light {
 | 
			
		|||
		// advance
 | 
			
		||||
		if (!m_QuadRenderer.Advance())
 | 
			
		||||
		{
 | 
			
		||||
			EndFrame();
 | 
			
		||||
			BeginFrame();
 | 
			
		||||
			LT_ENGINE_WARN("Renderer::DrawQuadImpl: exceeded LT_MAX_QUAD_RENDERER_VERTICES: {}", LT_MAX_QUAD_RENDERER_VERTICES);
 | 
			
		||||
			FlushScene();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -101,38 +109,42 @@ namespace Light {
 | 
			
		|||
		// advance
 | 
			
		||||
		if (!m_TextureRenderer.Advance())
 | 
			
		||||
		{
 | 
			
		||||
			EndFrame();
 | 
			
		||||
			BeginFrame();
 | 
			
		||||
			LT_ENGINE_WARN("Renderer::DrawQuadImpl: exceeded LT_MAX_TEXTURE_RENDERER_VERTICES: {}", LT_MAX_TEXTURE_RENDERER_VERTICES);
 | 
			
		||||
			FlushScene();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void Renderer::BeginFrame()
 | 
			
		||||
	{
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void Renderer::EndFrame()
 | 
			
		||||
	{
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void Renderer::BeginSceneImpl(const Camera& camera)
 | 
			
		||||
	void Renderer::BeginSceneImpl(const std::shared_ptr<Camera>& camera)
 | 
			
		||||
	{
 | 
			
		||||
		glm::mat4* map = (glm::mat4*)m_ViewProjectionBuffer->Map();
 | 
			
		||||
		map[0] = camera.GetProjection() * camera.GetView();
 | 
			
		||||
		map[0] = camera->GetProjection() * camera->GetView();
 | 
			
		||||
		m_ViewProjectionBuffer->UnMap();
 | 
			
		||||
 | 
			
		||||
		m_QuadRenderer.Map();
 | 
			
		||||
		m_TextureRenderer.Map();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void Renderer::FlushScene()
 | 
			
		||||
	{
 | 
			
		||||
		EndScene();
 | 
			
		||||
 | 
			
		||||
		m_QuadRenderer.Map();
 | 
			
		||||
		m_TextureRenderer.Map();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void Renderer::EndSceneImpl()
 | 
			
		||||
	{
 | 
			
		||||
		m_QuadRenderer.UnMap();
 | 
			
		||||
		m_TextureRenderer.UnMap();
 | 
			
		||||
 | 
			
		||||
		m_Blender->Enable(BlendFactor::SRC_ALPHA, BlendFactor::INVERSE_SRC_ALPHA);
 | 
			
		||||
 | 
			
		||||
		//** QUAD_RENDERER **//
 | 
			
		||||
		if (m_QuadRenderer.GetQuadCount())
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,19 +22,23 @@ namespace Light {
 | 
			
		|||
 | 
			
		||||
	class Texture;
 | 
			
		||||
 | 
			
		||||
	class WindowResizedEvent;
 | 
			
		||||
 | 
			
		||||
	class SharedContext;
 | 
			
		||||
 | 
			
		||||
	class Renderer
 | 
			
		||||
	{
 | 
			
		||||
	private:
 | 
			
		||||
		static Renderer* s_Context;
 | 
			
		||||
 | 
			
		||||
		
 | 
			
		||||
		// renderer programs
 | 
			
		||||
		QuadRendererProgram m_QuadRenderer;
 | 
			
		||||
		TextureRendererProgram m_TextureRenderer;
 | 
			
		||||
 | 
			
		||||
		std::unique_ptr<RenderCommand> m_RenderCommand;
 | 
			
		||||
		// constant buffers
 | 
			
		||||
		std::unique_ptr<ConstantBuffer> m_ViewProjectionBuffer;
 | 
			
		||||
 | 
			
		||||
		std::unique_ptr<RenderCommand> m_RenderCommand;
 | 
			
		||||
		std::unique_ptr<Blender> m_Blender;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
| 
						 | 
				
			
			@ -43,8 +47,10 @@ namespace Light {
 | 
			
		|||
		static inline void DrawQuad(const glm::vec3& position, const glm::vec2& size, const glm::vec4& tint) { s_Context->DrawQuadImpl(position, size, tint); }
 | 
			
		||||
		static inline void DrawQuad(const glm::vec3& position, const glm::vec2& size, std::shared_ptr<Texture> texture) { s_Context->DrawQuadImpl(position, size, texture); }
 | 
			
		||||
 | 
			
		||||
		static inline void BeginScene(const Camera& camera) { s_Context->BeginSceneImpl(camera); }
 | 
			
		||||
		static inline void BeginScene(const std::shared_ptr<Camera>& camera) { s_Context->BeginSceneImpl(camera); }
 | 
			
		||||
		static inline void EndScene() { s_Context->EndSceneImpl(); }
 | 
			
		||||
		
 | 
			
		||||
		void OnWindowResize(const WindowResizedEvent& event);
 | 
			
		||||
 | 
			
		||||
		void BeginFrame();
 | 
			
		||||
		void EndFrame();
 | 
			
		||||
| 
						 | 
				
			
			@ -55,7 +61,8 @@ namespace Light {
 | 
			
		|||
		void DrawQuadImpl(const glm::vec3& position, const glm::vec2& size, const glm::vec4& tint);
 | 
			
		||||
		void DrawQuadImpl(const glm::vec3& position, const glm::vec2& size, std::shared_ptr<Texture> texture);
 | 
			
		||||
 | 
			
		||||
		void BeginSceneImpl(const Camera& camera);
 | 
			
		||||
		void BeginSceneImpl(const std::shared_ptr<Camera>& camera);
 | 
			
		||||
		void FlushScene();
 | 
			
		||||
		void EndSceneImpl();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,9 +6,10 @@ namespace Light {
 | 
			
		|||
 | 
			
		||||
	class SharedContext;
 | 
			
		||||
 | 
			
		||||
	// #todo: improve textures
 | 
			
		||||
	class Texture
 | 
			
		||||
	{
 | 
			
		||||
	public:
 | 
			
		||||
	public:	
 | 
			
		||||
		static Texture* Create(unsigned int width, unsigned int height, unsigned int components, unsigned char* pixels, std::shared_ptr<SharedContext> sharedContext);
 | 
			
		||||
 | 
			
		||||
		Texture(const Texture&) = delete;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,11 +31,6 @@ namespace Light {
 | 
			
		|||
		m_SharedContext = std::make_shared<dxSharedContext>(m_Device, m_DeviceContext, m_SwapChain, m_RenderTargetView);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void dxGraphicsContext::OnWindowResize(const WindowResizedEvent& event)
 | 
			
		||||
	{
 | 
			
		||||
		SetResolution(event.GetSize().x, event.GetSize().y);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void dxGraphicsContext::SetupDeviceAndSwapChain(GLFWwindow* windowHandle)
 | 
			
		||||
	{
 | 
			
		||||
		// swap chain desc
 | 
			
		||||
| 
						 | 
				
			
			@ -129,23 +124,6 @@ namespace Light {
 | 
			
		|||
#endif
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void dxGraphicsContext::SetResolution(unsigned int width, unsigned int height)
 | 
			
		||||
	{
 | 
			
		||||
		// viewport
 | 
			
		||||
		D3D11_VIEWPORT viewport;
 | 
			
		||||
 | 
			
		||||
		viewport.Width = width;
 | 
			
		||||
		viewport.Height = height;
 | 
			
		||||
 | 
			
		||||
		viewport.MinDepth = 0.0f;
 | 
			
		||||
		viewport.MaxDepth = 1.0f;
 | 
			
		||||
		viewport.TopLeftX = 0.0f;
 | 
			
		||||
		viewport.TopLeftY = 0.0f;
 | 
			
		||||
 | 
			
		||||
		// set viewport
 | 
			
		||||
		m_DeviceContext->RSSetViewports(1u, &viewport);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void dxGraphicsContext::LogDebugData()
 | 
			
		||||
	{
 | 
			
		||||
		// locals 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,17 +25,12 @@ namespace Light {
 | 
			
		|||
	public:
 | 
			
		||||
		dxGraphicsContext(GLFWwindow* windowHandle);
 | 
			
		||||
 | 
			
		||||
		virtual void OnWindowResize(const WindowResizedEvent& event) override;
 | 
			
		||||
 | 
			
		||||
		virtual void LogDebugData() override;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void SetupDeviceAndSwapChain(GLFWwindow* windowHandle);
 | 
			
		||||
		void SetupRenderTargets();
 | 
			
		||||
		void SetupDebugInterface();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		void SetResolution(unsigned int width, unsigned int height);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -42,4 +42,22 @@ namespace Light {
 | 
			
		|||
		m_Context->GetDeviceContext()->DrawIndexed(count, 0u, 0u);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void dxRenderCommand::SetViewport(unsigned int x, unsigned int y, unsigned int width, unsigned int height)
 | 
			
		||||
	{
 | 
			
		||||
		// create viewport
 | 
			
		||||
		D3D11_VIEWPORT viewport;
 | 
			
		||||
 | 
			
		||||
		viewport.TopLeftX = x;
 | 
			
		||||
		viewport.TopLeftY = y;
 | 
			
		||||
 | 
			
		||||
		viewport.Width = width;
 | 
			
		||||
		viewport.Height = height;
 | 
			
		||||
 | 
			
		||||
		viewport.MinDepth = 0.0f;
 | 
			
		||||
		viewport.MaxDepth = 1.0f;
 | 
			
		||||
 | 
			
		||||
		// set viewport
 | 
			
		||||
		m_Context->GetDeviceContext()->RSSetViewports(1u, &viewport);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +23,8 @@ namespace Light {
 | 
			
		|||
 | 
			
		||||
		virtual void Draw(unsigned int count) override;
 | 
			
		||||
		virtual void DrawIndexed(unsigned int count) override;
 | 
			
		||||
 | 
			
		||||
		virtual void SetViewport(unsigned int x, unsigned int y, unsigned int width, unsigned int height) override;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -29,17 +29,6 @@ namespace Light {
 | 
			
		|||
		SetDebugMessageCallback();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void glGraphicsContext::OnWindowResize(const WindowResizedEvent& event)
 | 
			
		||||
	{
 | 
			
		||||
		if (event.GetSize().x < 0 || event.GetSize().y < 0)
 | 
			
		||||
		{
 | 
			
		||||
			LT_ENGINE_ERROR("glGraphicsContext::OnWindowResize: 'width'/'height' cannot be negative: [{}x{}]", event.GetSize().x, event.GetSize().y);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		glViewport(0, 0, event.GetSize().x, event.GetSize().y);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void glGraphicsContext::LogDebugData()
 | 
			
		||||
	{
 | 
			
		||||
		// #todo: log more information
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,8 +17,6 @@ namespace Light {
 | 
			
		|||
	public:
 | 
			
		||||
		glGraphicsContext(GLFWwindow* windowHandle);
 | 
			
		||||
 | 
			
		||||
		virtual void OnWindowResize(const WindowResizedEvent& event) override;
 | 
			
		||||
 | 
			
		||||
		virtual void LogDebugData() override;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,4 +31,9 @@ namespace Light {
 | 
			
		|||
		glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, nullptr);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void glRenderCommand::SetViewport(unsigned int x, unsigned int y, unsigned int width, unsigned int height)
 | 
			
		||||
	{
 | 
			
		||||
		glViewport(x, y, width, height);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +18,8 @@ namespace Light {
 | 
			
		|||
 | 
			
		||||
		void Draw(unsigned int count) override;
 | 
			
		||||
		void DrawIndexed(unsigned int count) override;
 | 
			
		||||
 | 
			
		||||
		virtual void SetViewport(unsigned int x, unsigned int y, unsigned int width, unsigned int height) override;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ namespace Light {
 | 
			
		|||
		glfwSetWindowUserPointer(m_Handle, &m_EventCallback);
 | 
			
		||||
		BindGlfwEvents();
 | 
			
		||||
		
 | 
			
		||||
		// create graphics context
 | 
			
		||||
		// create graphics contextG
 | 
			
		||||
		m_GraphicsContext = std::unique_ptr<GraphicsContext>(GraphicsContext::Create(GraphicsAPI::OpenGL, m_Handle));
 | 
			
		||||
		LT_ENGINE_ASSERT(m_GraphicsContext, "lWindow::lWindow: failed to create 'GraphicsContext'");
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -62,11 +62,16 @@ namespace Light {
 | 
			
		|||
 | 
			
		||||
		// resized
 | 
			
		||||
		case EventType::WindowResized:
 | 
			
		||||
			m_GraphicsContext->OnWindowResize((const WindowResizedEvent&)event);
 | 
			
		||||
			OnWindowResize((const WindowResizedEvent&)event);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	void lWindow::OnWindowResize(const WindowResizedEvent& event)
 | 
			
		||||
	{
 | 
			
		||||
		m_Properties.size = event.GetSize();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void lWindow::SetProperties(const WindowProperties& properties, bool affectsVisiblity /* = false */)
 | 
			
		||||
	{
 | 
			
		||||
		// save the visibility status and re-assign if 'affectVisibility' is false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ struct GLFWwindow;
 | 
			
		|||
namespace Light {
 | 
			
		||||
	
 | 
			
		||||
	class Event;
 | 
			
		||||
	class WindowResizedEvent;
 | 
			
		||||
	
 | 
			
		||||
	class lWindow : public Window
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +36,8 @@ namespace Light {
 | 
			
		|||
		
 | 
			
		||||
	private:
 | 
			
		||||
		void BindGlfwEvents();
 | 
			
		||||
 | 
			
		||||
		void OnWindowResize(const WindowResizedEvent& event);
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -8,16 +8,17 @@ struct GLFWwindow;
 | 
			
		|||
namespace Light {
 | 
			
		||||
 | 
			
		||||
	class Event;
 | 
			
		||||
	class WindowResizedEvent;
 | 
			
		||||
 | 
			
		||||
	class wWindow : public Window
 | 
			
		||||
	{
 | 
			
		||||
	private:
 | 
			
		||||
		// #todo: don't handle Windows's window with glfw, create it yourself
 | 
			
		||||
		// #todo: don't handle Windows's window with glfw, create an HWND
 | 
			
		||||
		GLFWwindow* m_Handle = nullptr;
 | 
			
		||||
 | 
			
		||||
		std::function<void(Event&)> m_EventCallback;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
	public: 
 | 
			
		||||
		wWindow(std::function<void(Event&)> callback);
 | 
			
		||||
 | 
			
		||||
		~wWindow();
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +38,8 @@ namespace Light {
 | 
			
		|||
 | 
			
		||||
	private:
 | 
			
		||||
		void BindGlfwEvents();
 | 
			
		||||
 | 
			
		||||
		void OnWindowResize(const WindowResizedEvent& event);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue