From f8ef964f021d1d6da6ea46bbb1fe8f0250a5be8c Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Mon, 16 Jun 2014 22:19:32 +0800 Subject: [PATCH] Refactor Rendering of Objects (prepare for CPU rendering) New abstract class ObjectRenderer and derived classes for each type of Object. It's a bit more complex but hopefully easier to build on now. There are probably a heap of bugs in this, but I can see the test pattern again, so I'll commit before it gets worse. Note: We now have to make sure Screen is initialised first or the segfaults will hit the fan. (Now it makes sense why all those things weren't in constructors in the first place :S) It also now segfaults if you get View and Screen the wrong way round. --- src/Makefile | 2 +- src/bufferbuilder.h | 3 +- src/common.h | 5 + src/document.h | 1 + src/framebuffer.cpp | 10 +- src/framebuffer.h | 9 +- src/graphicsbuffer.cpp | 6 +- src/graphicsbuffer.h | 2 +- src/ipdf.h | 13 +- src/main.h | 11 +- src/objectrenderer.cpp | 98 ++++++++++++ src/objectrenderer.h | 81 ++++++++++ src/screen.cpp | 9 +- src/screen.h | 1 + src/shaderprogram.cpp | 59 ++++--- src/shaderprogram.h | 25 +-- src/shaders/circle_frag.glsl | Bin 209 -> 208 bytes src/vfpu.h | 24 +-- src/view.cpp | 290 +++++++++++++++++------------------ src/view.h | 70 +++++---- 20 files changed, 463 insertions(+), 256 deletions(-) create mode 100644 src/objectrenderer.cpp create mode 100644 src/objectrenderer.h diff --git a/src/Makefile b/src/Makefile index c39d7dc..206183f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -3,7 +3,7 @@ ARCH := $(shell uname -m) # TODO: stb_truetype doesn't compile with some of these warnings. CXX = g++ -std=gnu++0x -g -Wall -Werror -Wshadow -pedantic MAIN = main.o -OBJ = log.o real.o document.o view.o screen.o vfpu.o graphicsbuffer.o framebuffer.o shaderprogram.o stb_truetype.o gl_core44.o +OBJ = log.o real.o document.o objectrenderer.o view.o screen.o vfpu.o graphicsbuffer.o framebuffer.o shaderprogram.o stb_truetype.o gl_core44.o LIB_x86_64 = ../contrib/lib/libSDL2-2.0.so.0 -lGL LIB_i386 = ../contrib/lib32/libSDL2-2.0.so.0 -lGL diff --git a/src/bufferbuilder.h b/src/bufferbuilder.h index 385369c..aa3006f 100644 --- a/src/bufferbuilder.h +++ b/src/bufferbuilder.h @@ -11,10 +11,11 @@ namespace IPDF { public: BufferBuilder(void *data, size_t size) : m_bufferData((T*)data), m_bufferSize(size), m_bufferOffset(0) {}; + ~BufferBuilder() {} // Append an item to the buffer, returning its index. size_t Add(const T& item) {m_bufferData[m_bufferOffset] = item; m_bufferOffset++; return m_bufferOffset-1;} bool Free(size_t num = 1) const {return ((m_bufferOffset + num) * sizeof(T)) < m_bufferSize;} - private: + //private: // bah who needs privacy T *m_bufferData; size_t m_bufferSize; // In bytes, 'cause why make things easy? size_t m_bufferOffset; // In elements, 'cause why make things consistant? diff --git a/src/common.h b/src/common.h index 5536f75..aa79974 100644 --- a/src/common.h +++ b/src/common.h @@ -1,3 +1,8 @@ +/** + * @file common.h + * @brief Include common headers + */ + // C++ STD includes #include #include diff --git a/src/document.h b/src/document.h index 89e854a..54ccdd0 100644 --- a/src/document.h +++ b/src/document.h @@ -17,6 +17,7 @@ namespace IPDF void DebugDumpObjects(); unsigned ObjectCount() const {return m_count;} + inline const Objects & GetObjects() const {return m_objects;} bool operator==(const Document & equ) const; bool operator!=(const Document & equ) const {return !(this->operator==(equ));} diff --git a/src/framebuffer.cpp b/src/framebuffer.cpp index 2302b6c..2f5005e 100644 --- a/src/framebuffer.cpp +++ b/src/framebuffer.cpp @@ -7,6 +7,7 @@ void FrameBuffer::Create(int w, int h) { if (m_render_texture) { + // FrameBuffer was already Created, destroy it before creating again Destroy(); } m_width = w; @@ -27,25 +28,26 @@ void FrameBuffer::Create(int w, int h) void FrameBuffer::Destroy() { - if (!m_render_texture) return; + if (!m_render_texture) return; // wasn't Created (or already Destroyed) glDeleteFramebuffers(1, &m_render_fbo); glDeleteTextures(1, &m_render_texture); } void FrameBuffer::Bind() { + // will draw to this FrameBuffer glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_render_fbo); } void FrameBuffer::UnBind() { - glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); // will both draw to and read pixels from screen } void FrameBuffer::Blit() { - glBindFramebuffer(GL_READ_FRAMEBUFFER, m_render_fbo); - glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, m_render_fbo); // read pixels from this FrameBuffer + glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_COLOR_BUFFER_BIT, GL_NEAREST); // draw pixels } void FrameBuffer::Clear(float r, float g, float b, float a) diff --git a/src/framebuffer.h b/src/framebuffer.h index 1da3216..33c5fe1 100644 --- a/src/framebuffer.h +++ b/src/framebuffer.h @@ -7,8 +7,9 @@ namespace IPDF { - /* + /** * The "FrameBuffer" class represents an offscreen render target. + * FrameBuffer::Create needs to be called to initialise it; constructor is trivial */ class FrameBuffer { @@ -17,9 +18,9 @@ namespace IPDF ~FrameBuffer() { Destroy(); } void Create(int w, int h); void Destroy(); - void Bind(); - void UnBind(); - void Blit(); + void Bind(); // set as render target + void UnBind(); // set render target to screen + void Blit(); // blit this FrameBuffer to current render target void Clear(float r=1.0, float g=1.0, float b=1.0, float a=1.0); int GetWidth() { return m_width; } int GetHeight() { return m_height; } diff --git a/src/graphicsbuffer.cpp b/src/graphicsbuffer.cpp index 82c6a30..eb98450 100644 --- a/src/graphicsbuffer.cpp +++ b/src/graphicsbuffer.cpp @@ -36,7 +36,7 @@ static GLenum BufferUsageToGLUsage(GraphicsBuffer::BufferUsage buffer_usage) usage = GL_STREAM_COPY; break; default: - SDL_assert(false && "Unknown buffer usage type."); + SDL_assert(false && "Unknown buffer usage type."); //WTF? usage = GL_DYNAMIC_DRAW; } return usage; @@ -66,7 +66,7 @@ static GLenum BufferTypeToGLType(GraphicsBuffer::BufferType buffer_type) GraphicsBuffer::GraphicsBuffer() { m_invalidated = true; - m_map_pointer = nullptr; + m_map_pointer = NULL; m_buffer_size = 0; m_buffer_shape_dirty = true; m_buffer_handle = 0; @@ -104,7 +104,7 @@ void GraphicsBuffer::Invalidate() if (!m_buffer_shape_dirty) { // Orphan the block of memory we're pointing to. - Upload(m_buffer_size, nullptr); + Upload(m_buffer_size, NULL); } // Apparently not supported. //glInvalidateBufferData(m_buffer_handle); diff --git a/src/graphicsbuffer.h b/src/graphicsbuffer.h index dd25617..9513cb7 100644 --- a/src/graphicsbuffer.h +++ b/src/graphicsbuffer.h @@ -7,7 +7,7 @@ namespace IPDF { - /* + /** * Implementation of an OpenGL buffer, with some extra cleverness. */ class GraphicsBuffer diff --git a/src/ipdf.h b/src/ipdf.h index 196331d..5a4f35c 100644 --- a/src/ipdf.h +++ b/src/ipdf.h @@ -17,13 +17,22 @@ namespace IPDF } typedef unsigned ObjectID; - typedef enum {RECT_FILLED, RECT_OUTLINE, CIRCLE_FILLED} ObjectType; + /** Type of object + * NOTE: Extra entry in the enum so we can use this as an array index + */ + typedef enum + { + RECT_FILLED = 0, + RECT_OUTLINE, + CIRCLE_FILLED, + NUMBER_OF_OBJECT_TYPES + } ObjectType; enum DocChunkTypes { CT_NUMOBJS, CT_OBJTYPES, - CT_OBJBOUNDS + CT_OBJBOUNDS, }; struct Rect diff --git a/src/main.h b/src/main.h index 332b021..02a7451 100644 --- a/src/main.h +++ b/src/main.h @@ -23,8 +23,9 @@ inline void OverlayBMP(Document & doc, const char * input, const char * output, inline void MainLoop(Document & doc, const Rect & bounds = Rect(0,0,1,1), const Colour & c = Colour(0.f,0.f,0.f,1.f)) { - View view(doc,bounds, c); + // order is important... segfaults occur when screen (which inits GL) is not constructed first -_- Screen scr; + View view(doc,bounds, c); scr.DebugFontInit("DejaVuSansMono.ttf"); scr.SetMouseHandler([&](int x, int y, int buttons, int wheel) // [?] wtf { @@ -80,6 +81,14 @@ inline void MainLoop(Document & doc, const Rect & bounds = Rect(0,0,1,1), const { scr.DebugFontPrint("Doing coordinate transform on the CPU.\n"); } + if (view.UsingGPURendering()) + { + scr.DebugFontPrint("Doing rendering using GPU.\n"); + } + else + { + scr.DebugFontPrint("Doing rendering using CPU.\n"); + } scr.Present(); } } diff --git a/src/objectrenderer.cpp b/src/objectrenderer.cpp new file mode 100644 index 0000000..ff40b1f --- /dev/null +++ b/src/objectrenderer.cpp @@ -0,0 +1,98 @@ +/** + * @file objectrenderer.cpp + * @brief Implements ObjectRenderer and derived classes + */ + +#include "objectrenderer.h" + +namespace IPDF +{ + +/** + * ObjectRenderer constructor + * Note the ShaderProgram constructor which compiles the shaders for GPU rendering (if they exist) + */ +ObjectRenderer::ObjectRenderer(const ObjectType & type, + const char * vert_glsl_file, const char * frag_glsl_file, const char * geom_glsl_file) + : m_type(type), m_shader_program(), m_indexes(), m_buffer_builder(NULL) +{ + m_shader_program.InitialiseShaders(vert_glsl_file, frag_glsl_file, geom_glsl_file); + m_shader_program.Use(); + glUniform4f(m_shader_program.GetUniformLocation("colour"), 0,0,0,1); //TODO: Allow different colours +} + +/** + * Render using GPU + */ +void ObjectRenderer::RenderUsingGPU() +{ + if (!m_shader_program.Valid()) + Warn("Shader is invalid (objects are of type %d)", m_type); + m_shader_program.Use(); + m_ibo.Bind(); + glDrawElements(GL_LINES, m_indexes.size()*2, GL_UNSIGNED_INT, 0); +} + +/** + * Default implementation for rendering using CPU + */ +void ObjectRenderer::RenderUsingCPU() +{ + Error("Cannot render objects of type %d on CPU", m_type); +} + +/** + * Prepare index buffers for both CPU and GPU rendering to receive indexes (but don't add any yet!) + */ +void ObjectRenderer::PrepareBuffers(unsigned max_objects) +{ + if (m_buffer_builder != NULL) // We already have a BufferBuilder + { + Fatal("Has been called before, without FinaliseBuffers being called since!"); + } + // Empty and reserve the indexes vector (for CPU rendering) + m_indexes.clear(); + m_indexes.reserve(max_objects); //TODO: Can probably make this smaller? Or leave it out? Do we care? + + // Initialise and resize the ibo (for GPU rendering) + m_ibo.SetUsage(GraphicsBuffer::BufferUsageStaticDraw); + m_ibo.SetType(GraphicsBuffer::BufferTypeIndex); + m_ibo.Resize(max_objects * 2 * sizeof(uint32_t)); + // BufferBuilder is used to construct the ibo + m_buffer_builder = new BufferBuilder(m_ibo.Map(false, true, true), m_ibo.GetSize()); // new matches delete in ObjectRenderer::FinaliseBuffers + +} + +/** + * Add object index to the buffers for CPU and GPU rendering + */ +void ObjectRenderer::AddObjectToBuffers(unsigned index) +{ + if (m_buffer_builder == NULL) // No BufferBuilder! + { + Fatal("Called without calling PrepareBuffers"); + } + m_buffer_builder->Add(2*index); // ibo for GPU rendering + m_buffer_builder->Add(2*index+1); + m_indexes.push_back(index); // std::vector of indices for CPU rendering +} + +/** + * Finalise the index buffers for CPU and GPU rendering + */ +void ObjectRenderer::FinaliseBuffers() +{ + if (m_buffer_builder == NULL) // No BufferBuilder! + { + Fatal("Called without calling PrepareBuffers"); + } + // For GPU rendering, UnMap the ibo + m_ibo.UnMap(); + // ... and delete the BufferBuilder used to create it + delete m_buffer_builder; // delete matches new in ObjectRenderer::PrepareBuffers + m_buffer_builder = NULL; + + // Nothing is necessary for CPU rendering +} + +} diff --git a/src/objectrenderer.h b/src/objectrenderer.h new file mode 100644 index 0000000..6e5176b --- /dev/null +++ b/src/objectrenderer.h @@ -0,0 +1,81 @@ +/** + * @file objectrenderer.h + * @brief Definition of ObjectRenderer class + */ + +#include "ipdf.h" +#include "graphicsbuffer.h" +#include "shaderprogram.h" +#include "bufferbuilder.h" + +namespace IPDF +{ + /** + * Abstract Base class representing how a particular type of object will be rendered + * Includes GPU rendering and CPU rendering + * For GPU rendering, pass GLSL shader source files to constructor in the constructor of a base class + * To leave unimplemented, just pass NULL filename strings + * For CPU rendering, implement RenderUsingCPU in the base class + * To leave unimplemented, just call ObjectRenderer::RenderUsingCPU in the base class + * The View class uses ObjectRenderer's; see view.h + */ + class ObjectRenderer + { + public: + /** Construct the ObjectRenderer **/ + ObjectRenderer(const ObjectType & type, const char * vert_glsl_file, const char * frag_glsl_file, const char * geom_glsl_file = ""); + virtual ~ObjectRenderer() {} + + /** + * Use the GPU to render the objects - GLSL shader approach + * This way is definitely faster, but subject to the GPU's limitations on precision + */ + void RenderUsingGPU(); + + /** + * Use the CPU to render the objects - "make a bitmap and convert it to a texture" approach + * This way is definitely slower, but gives us more control over the number representations than a GPU + */ + virtual void RenderUsingCPU() = 0; + + + + const ObjectType m_type; /** Type of objects **/ + private: + friend class View; //View is a friendly fellow in the world of IPDF + void PrepareBuffers(unsigned max_size); + void FinaliseBuffers(); + void AddObjectToBuffers(unsigned index); + + + ShaderProgram m_shader_program; /** GLSL shaders for GPU **/ + GraphicsBuffer m_ibo; /** Index Buffer Object for GPU rendering **/ + std::vector m_indexes; /** Index vector for CPU rendering **/ + BufferBuilder * m_buffer_builder; /** A BufferBuilder is temporarily used when preparing the ibo and std::vector **/ + }; + + /** Renderer for filled rectangles **/ + class RectFilledRenderer : public ObjectRenderer + { + public: + RectFilledRenderer() : ObjectRenderer(RECT_FILLED, "shaders/rect_vert.glsl", "shaders/rect_frag.glsl","shaders/rect_filled_geom.glsl") {} + virtual ~RectFilledRenderer() {} + virtual void RenderUsingCPU() {ObjectRenderer::RenderUsingCPU();} //TODO: Implement + }; + /** Renderer for outlined rectangles **/ + class RectOutlineRenderer : public ObjectRenderer + { + public: + RectOutlineRenderer() : ObjectRenderer(RECT_OUTLINE, "shaders/rect_vert.glsl", "shaders/rect_frag.glsl", "shaders/rect_outline_geom.glsl") {} + virtual ~RectOutlineRenderer() {} + virtual void RenderUsingCPU() {ObjectRenderer::RenderUsingCPU();} //TODO: Implement + }; + /** Renderer for filled circles **/ + class CircleFilledRenderer : public ObjectRenderer + { + public: + CircleFilledRenderer() : ObjectRenderer(CIRCLE_FILLED, "shaders/rect_vert.glsl", "shaders/circle_frag.glsl", "shaders/circle_filled_geom.glsl") {} + virtual ~CircleFilledRenderer() {} + virtual void RenderUsingCPU() {ObjectRenderer::RenderUsingCPU();} //TODO: Implement + }; +} diff --git a/src/screen.cpp b/src/screen.cpp index b5b6882..7ac0678 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -22,6 +22,7 @@ static void opengl_debug_callback(GLenum source, GLenum type, GLuint id, GLenum Screen::Screen() { + SDL_Init(SDL_INIT_VIDEO); m_window = SDL_CreateWindow("IPDF", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); @@ -68,10 +69,7 @@ Screen::Screen() glGenVertexArrays(1, &default_vao); glBindVertexArray(default_vao); - //TODO: Error checking. - m_texture_prog.AttachVertexProgram(BASICTEX_VERT); - m_texture_prog.AttachFragmentProgram(BASICTEX_FRAG); - m_texture_prog.Link(); + m_texture_prog.InitialiseShaders(BASICTEX_VERT, BASICTEX_FRAG); m_texture_prog.Use(); // We always want to use the texture bound to texture unit 0. @@ -226,9 +224,10 @@ void Screen::RenderPixels(int x, int y, int w, int h, void *pixels) const GraphicsBuffer quad_vertex_buffer; quad_vertex_buffer.SetUsage(GraphicsBuffer::BufferUsageStaticDraw); quad_vertex_buffer.SetType(GraphicsBuffer::BufferTypeVertex); + //rectangular texture == 2 triangles GLfloat quad[] = { 0, 0, (float)x, (float)y, - 1, 0, (float)(x+w), y, + 1, 0, (float)(x+w), (float)y, 1, 1, (float)(x+w), (float)(y+h), 0, 1, (float)x, (float)(y+h) }; diff --git a/src/screen.h b/src/screen.h index f66c7aa..f2a98c2 100644 --- a/src/screen.h +++ b/src/screen.h @@ -59,6 +59,7 @@ namespace IPDF void ScreenShot(const char * filename) const; void RenderBMP(const char * filename) const; + void RenderPixels(int x, int y, int w, int h, void * pixels) const; // Returns the CPU time (in seconds) it took to render the last completed frame. double GetLastFrameTimeCPU() const { return m_last_frame_time / SDL_GetPerformanceFrequency(); } diff --git a/src/shaderprogram.cpp b/src/shaderprogram.cpp index 1f5e9e2..0a5cb2e 100644 --- a/src/shaderprogram.cpp +++ b/src/shaderprogram.cpp @@ -3,27 +3,58 @@ using namespace IPDF; -ShaderProgram::~ShaderProgram() +/** + * Initialise a shader program using GLSL source files + * @param geometry_file GLSL source for Geometry shader (optional) + * @param vertex_file GLSL source for vertex shader + * @param fragment_file GLSL source for fragment shader + * If a filename is the empty string it will be ignored + */ +bool ShaderProgram::InitialiseShaders(const char * vertex_file, const char * fragment_file, const char * geometry_file) { - for(auto shader : m_shaders) + if (m_valid) { - glDetachShader(m_program, shader.obj); - glDeleteShader(shader.obj); + Error("Shader already valid?"); + } + m_program = glCreateProgram(); + if (m_program == 0) + { + Error("glCreateProgram failed"); + m_valid = false; + return m_valid; } + m_valid = true; + if (geometry_file != NULL && geometry_file[0] != '\0') + m_valid &= AttachShader(geometry_file, GL_GEOMETRY_SHADER); + if (vertex_file != NULL && vertex_file[0] != '\0') + m_valid &= AttachShader(vertex_file, GL_VERTEX_SHADER); + if (fragment_file != NULL && fragment_file[0] != '\0') + m_valid &= AttachShader(fragment_file, GL_FRAGMENT_SHADER); - if (m_program) - glDeleteProgram(m_program); + if (!m_valid) + { + Warn("One or more AttachShader calls failed but we will link the shader anyway"); + } + glLinkProgram(m_program); + return m_valid; } + + /** - * This is only called once, does it need a function? + * Destroy a shader program */ -void ShaderProgram::LazyCreateProgram() +ShaderProgram::~ShaderProgram() { - if (!m_program) + m_valid = false; + for(auto shader : m_shaders) { - m_program = glCreateProgram(); + glDetachShader(m_program, shader.obj); + glDeleteShader(shader.obj); } + + if (m_program) + glDeleteProgram(m_program); } /** @@ -96,18 +127,10 @@ bool ShaderProgram::AttachShader(const char * src_file, GLenum type) } m_shaders.push_back(Shader{type, shader_obj}); - LazyCreateProgram(); // um... why? glAttachShader(m_program, shader_obj); return true; } - -bool ShaderProgram::Link() -{ - glLinkProgram(m_program); - return true; -} - const void ShaderProgram::Use() const { glUseProgram(m_program); diff --git a/src/shaderprogram.h b/src/shaderprogram.h index 9cedab6..44b0973 100644 --- a/src/shaderprogram.h +++ b/src/shaderprogram.h @@ -13,22 +13,13 @@ namespace IPDF class ShaderProgram { public: - ShaderProgram() : m_program(0) {} + ShaderProgram() : m_program(0), m_valid(false) {}; ~ShaderProgram(); - - inline bool AttachGeometryProgram(const char * geometry_file) {return AttachShader(geometry_file, GL_GEOMETRY_SHADER);} - inline bool AttachVertexProgram(const char * vertex_file) {return AttachShader(vertex_file, GL_VERTEX_SHADER);} - inline bool AttachFragmentProgram(const char * fragment_file) {return AttachShader(fragment_file, GL_FRAGMENT_SHADER);} - - /** Read shaders from files and attach them - * @returns false if any of the shaders cannot be attached - */ - inline bool AttachShaderPrograms(const char * geometry_file, const char * vertex_file, const char * fragment_file) - { - return AttachGeometryProgram(geometry_file) && AttachVertexProgram(vertex_file) && AttachFragmentProgram(fragment_file); - } - bool Link(); // currently always returns true? + bool InitialiseShaders(const char * vert_glsl_file, const char * frag_glsl_file, const char * geom_glsl_file = ""); const void Use() const; + + bool Valid() const {return m_valid;} + // Unfortunately, we don't require GL 4.3/ARB_explicit_uniform_location // which would make this obsolete. One uday Mesa will support it. // NOTE: We could actually get away with this by only using UBOs, as @@ -37,10 +28,9 @@ namespace IPDF const GLint GetUniformLocation(const char *uniform_name) const; private: - void LazyCreateProgram(); - /** Read shader source from src_file and attach it as type **/ - bool AttachShader(const char * src_file, GLenum type); char * GetShaderSource(const char * src_file) const; + bool AttachShader(const char * src_file, GLenum type); + GLuint m_program; struct Shader { @@ -48,6 +38,7 @@ namespace IPDF GLuint obj; }; std::vector m_shaders; + bool m_valid; }; } diff --git a/src/shaders/circle_frag.glsl b/src/shaders/circle_frag.glsl index b4fab1002ee5df7c8b06d1cb9c3c28c56eb6eac0..c1c40919fefc6bfbc9fe1c9d2ebe14ea4433d180 100644 GIT binary patch delta 6 Ncmcb}c!6=k1po>p0|Wp7 delta 8 Pcmcb>c#(0!1x5w{5G(@) diff --git a/src/vfpu.h b/src/vfpu.h index 12ac9bc..d77c1e3 100644 --- a/src/vfpu.h +++ b/src/vfpu.h @@ -1,31 +1,19 @@ -#ifndef _VFPU_H -#define _VFPU_H - /** - * Implements a terrible and hacky interface to use a virtual FPU to do floating point operations + * @file vfpu.h + * @brief Implements a terrible and hacky interface to use a virtual FPU to do floating point operations */ -#include +#ifndef _VFPU_H +#define _VFPU_H +#include namespace VFPU { extern int Start(const char * vcd_output = NULL); // Starts the VFPU extern int Halt(); // Halts the VFPU - -/** - -- 000 = add, - -- 001 = substract, - -- 010 = multiply, - -- 011 = divide, - -- 100 = square root - -- 101 = unused - -- 110 = unused - -- 111 = unused - */ typedef enum {ADD=0x000, SUB=0x001, MULT=0x010, DIV=0x011, SQRT=0x100} Opcode; - typedef enum {EVEN=0x00, ZERO=0x01, UP=0x10, DOWN=0x11} Rmode; + typedef enum {EVEN=0x00, ZERO=0x01, UP=0x10, DOWN=0x11} Rmode; // Rounding mode; to even, towards zero, always up, always down typedef std::bitset<32> Register; - extern Register Exec(const Register & a, const Register & b, Opcode op, Rmode rmode = EVEN); // operate with registers extern float Exec(float a, float b, Opcode op, Rmode rmode = EVEN); //converts floats into registers and back } diff --git a/src/view.cpp b/src/view.cpp index fd26b13..38b2c17 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -5,13 +5,51 @@ using namespace IPDF; using namespace std; -#define RECT_VERT "shaders/rect_vert.glsl" -#define RECT_FRAG "shaders/rect_frag.glsl" -#define RECT_OUTLINE_GEOM "shaders/rect_outline_geom.glsl" -#define RECT_FILLED_GEOM "shaders/rect_filled_geom.glsl" -#define CIRCLE_FILLED_GEOM "shaders/circle_filled_geom.glsl" -#define CIRCLE_FRAG "shaders/circle_frag.glsl" +/** + * Constructs a view + * Allocates memory for ObjectRenderers + * @param document - The document to associate the View with + * @param bounds - Initial bounds of the View + * @param colour - Colour to use for rendering this view. TODO: Make sure this actually works, or just remove it + */ +View::View(Document & document, const Rect & bounds, const Colour & colour) + : m_use_gpu_transform(USE_GPU_TRANSFORM), m_use_gpu_rendering(USE_GPU_RENDERING), m_bounds_dirty(true), m_buffer_dirty(true), + m_render_dirty(true), m_document(document), m_cached_display(), m_bounds(bounds), m_colour(colour), m_bounds_ubo(), + m_objbounds_vbo(), m_object_renderers(NUMBER_OF_OBJECT_TYPES) +{ + Debug("View Created - Bounds => {%s}", m_bounds.Str().c_str()); + + // Create ObjectRenderers - new's match delete's in View::~View + // Ok, look, this may seem disgusting, but go look at View::PrepareRender before you murder me + m_object_renderers[RECT_FILLED] = new RectFilledRenderer(); + m_object_renderers[RECT_OUTLINE] = new RectOutlineRenderer(); + m_object_renderers[CIRCLE_FILLED] = new CircleFilledRenderer(); + + // To add rendering for a new type of object; + // 1. Add enum to ObjectType in ipdf.h + // 2. Implement class inheriting from ObjectRenderer using that type in objectrenderer.h and objectrenderer.cpp + // 3. Add it here + // 4. Profit +} + +/** + * Destroy a view + * Frees memory used by ObjectRenderers + */ +View::~View() +{ + for (unsigned i = 0; i < m_object_renderers.size(); ++i) + { + //delete m_object_renderers[i]; + } + m_object_renderers.clear(); +} + +/** + * Translate the view + * @param x, y - Amount to translate + */ void View::Translate(Real x, Real y) { x *= m_bounds.w; @@ -20,13 +58,16 @@ void View::Translate(Real x, Real y) m_bounds.y += y; Debug("View Bounds => %s", m_bounds.Str().c_str()); if (!m_use_gpu_transform) - { m_buffer_dirty = true; - } m_bounds_dirty = true; } -void View::ScaleAroundPoint(Real x, Real y, Real scaleAmt) +/** + * Scale the View at a point + * @param x, y - Coordinates to scale at (eg: Mouse cursor position) + * @param scale_amount - Amount to scale by + */ +void View::ScaleAroundPoint(Real x, Real y, Real scale_amount) { // x and y are coordinates in the window // Convert to local coords. @@ -35,144 +76,124 @@ void View::ScaleAroundPoint(Real x, Real y, Real scaleAmt) x += m_bounds.x; y += m_bounds.y; - //Debug("Mouse wheel event %f %f %f\n", Float(x), Float(y), Float(scaleAmt)); - Real top = y - m_bounds.y; Real left = x - m_bounds.x; - top *= scaleAmt; - left *= scaleAmt; + top *= scale_amount; + left *= scale_amount; m_bounds.x = x - left; m_bounds.y = y - top; - m_bounds.w *= scaleAmt; - m_bounds.h *= scaleAmt; + m_bounds.w *= scale_amount; + m_bounds.h *= scale_amount; Debug("View Bounds => %s", m_bounds.Str().c_str()); if (!m_use_gpu_transform) m_buffer_dirty = true; m_bounds_dirty = true; } +/** + * Transform a point in the document to a point relative to the top left corner of the view + * This is the CPU coordinate transform code; used only if the CPU is doing coordinate transforms + * @param inp - Input Rect {x,y,w,h} in the document + * @returns output Rect {x,y,w,h} in the View + */ Rect View::TransformToViewCoords(const Rect& inp) const { Rect out; out.x = (inp.x - m_bounds.x) / m_bounds.w; out.y = (inp.y - m_bounds.y) / m_bounds.h; - out.w = inp.w / m_bounds.w; out.h = inp.h / m_bounds.h; return out; } -void View::DrawGrid() -{ - //TODO: Implement this with OpenGL 3.1+ -#if 0 - // Draw some grid lines at fixed pixel positions - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0.0, 1.0, 1.0, 0.0, -1.f, 1.f); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glColor4f(0.9,0.9,0.9,0.1); - const float num_lines = 50.0; - for (float i = 0; i < num_lines; ++i) - { - glBegin(GL_LINES); - glVertex2f(i*(1.0/num_lines), 0.0); - glVertex2f(i*(1.0/num_lines), 1.0); - glEnd(); - glBegin(GL_LINES); - glVertex2f(0.0,i*(1.0/num_lines)); - glVertex2f(1.0,i*(1.0/num_lines)); - glEnd(); - - } -#endif -} - +/** + * Render the view + * Updates FrameBuffer if the document, object bounds, or view bounds have changed, then Blits it + * Otherwise just Blits the cached FrameBuffer + * @param width - Width of View to render + * @param height - Height of View to render + */ void View::Render(int width, int height) { + // View dimensions have changed (ie: Window was resized) if (width != m_cached_display.GetWidth() || height != m_cached_display.GetHeight()) { m_cached_display.Create(width, height); m_bounds_dirty = true; } + // View bounds have not changed; blit the FrameBuffer as it is if (!m_bounds_dirty) { m_cached_display.UnBind(); m_cached_display.Blit(); return; } - m_cached_display.Bind(); + + // Bind FrameBuffer for rendering, and clear it + m_cached_display.Bind(); //NOTE: This is redundant; Clear already calls Bind m_cached_display.Clear(); - if (!m_render_inited) + + if (m_render_dirty) // document has changed PrepareRender(); - if (m_buffer_dirty) + if (m_buffer_dirty) // object bounds have changed UpdateObjBoundsVBO(); - if (m_bounds_dirty) + if (m_use_gpu_transform) { - if (m_use_gpu_transform) - { - GLfloat glbounds[] = {static_cast(Float(m_bounds.x)), static_cast(Float(m_bounds.y)), static_cast(Float(m_bounds.w)), static_cast(Float(m_bounds.h))}; - m_bounds_ubo.Upload(sizeof(float)*4, glbounds); - } - else - { - GLfloat glbounds[] = {0.0f, 0.0f, 1.0f, 1.0f}; - m_bounds_ubo.Upload(sizeof(float)*4, glbounds); - } + GLfloat glbounds[] = {static_cast(Float(m_bounds.x)), static_cast(Float(m_bounds.y)), static_cast(Float(m_bounds.w)), static_cast(Float(m_bounds.h))}; + m_bounds_ubo.Upload(sizeof(float)*4, glbounds); } - m_bounds_dirty = false; - - if (m_colour.a < 1.0f) + else { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GLfloat glbounds[] = {0.0f, 0.0f, 1.0f, 1.0f}; + m_bounds_ubo.Upload(sizeof(float)*4, glbounds); } - m_objbounds_vbo.Bind(); - m_bounds_ubo.Bind(); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); - - // Filled Circles - m_circle_filled_shader.Use(); - m_circle_ibo.Bind(); - glDrawElements(GL_LINES, m_rendered_circle*2, GL_UNSIGNED_INT, 0); - - // Filled Rectangles - m_rect_filled_shader.Use(); - m_filled_ibo.Bind(); - glDrawElements(GL_LINES, m_rendered_filled*2, GL_UNSIGNED_INT, 0); + m_bounds_dirty = false; - // Rectangle Outlines - m_rect_outline_shader.Use(); - m_outline_ibo.Bind(); - glDrawElements(GL_LINES, m_rendered_outline*2, GL_UNSIGNED_INT, 0); - glDisableVertexAttribArray(0); - if (m_colour.a < 1.0f) + // Render using GPU + if (m_use_gpu_rendering) { - glDisable(GL_BLEND); + if (m_colour.a < 1.0f) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + m_objbounds_vbo.Bind(); + m_bounds_ubo.Bind(); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); + + for (unsigned i = 0; i < m_object_renderers.size(); ++i) + { + m_object_renderers[i]->RenderUsingGPU(); + } + + glDisableVertexAttribArray(0); + if (m_colour.a < 1.0f) + { + glDisable(GL_BLEND); + } } - m_cached_display.UnBind(); - m_cached_display.Blit(); - + else // Rasterise on CPU then blit texture to GPU + { + for (unsigned i = 0; i < m_object_renderers.size(); ++i) + { + m_object_renderers[i]->RenderUsingCPU(); + } + } + m_cached_display.UnBind(); // resets render target to the screen + m_cached_display.Blit(); // blit FrameBuffer to screen } -struct GPUObjBounds -{ - float x0, y0; - float x1, y1; -}; - void View::UpdateObjBoundsVBO() { + Debug("Called"); m_objbounds_vbo.Invalidate(); m_objbounds_vbo.SetType(GraphicsBuffer::BufferTypeVertex); if (m_use_gpu_transform) @@ -210,71 +231,40 @@ void View::UpdateObjBoundsVBO() m_objbounds_vbo.UnMap(); m_buffer_dirty = false; } - +/** + * Prepare the document for rendering + * Will be called on View::Render if m_render_dirty is set + * (Called at least once, on the first Render) + */ void View::PrepareRender() { - // TODO: Error check here. - - m_rect_outline_shader.AttachShaderPrograms(RECT_OUTLINE_GEOM, RECT_VERT, RECT_FRAG); - m_rect_outline_shader.Link(); - m_rect_outline_shader.Use(); - glUniform4f(m_rect_outline_shader.GetUniformLocation("colour"), m_colour.r, m_colour.g, m_colour.b, m_colour.a); - - m_rect_filled_shader.AttachShaderPrograms(RECT_FILLED_GEOM, RECT_VERT, RECT_FRAG); - m_rect_filled_shader.Link(); - m_rect_filled_shader.Use(); - glUniform4f(m_rect_filled_shader.GetUniformLocation("colour"), m_colour.r, m_colour.g, m_colour.b, m_colour.a); - - m_circle_filled_shader.AttachShaderPrograms(CIRCLE_FILLED_GEOM, RECT_VERT, CIRCLE_FRAG); - m_circle_filled_shader.Link(); - m_circle_filled_shader.Use(); - glUniform4f(m_circle_filled_shader.GetUniformLocation("colour"), m_colour.r, m_colour.g, m_colour.b, m_colour.a); - + // Prepare bounds vbo m_bounds_ubo.SetType(GraphicsBuffer::BufferTypeUniform); m_bounds_ubo.SetUsage(GraphicsBuffer::BufferUsageStreamDraw); + + // Instead of having each ObjectRenderer go through the whole document + // we initialise them, go through the document once adding to the appropriate Renderers + // and then finalise them + // This will totally be efficient if we have like, a lot of distinct ObjectTypes. Which could totally happen. You never know. - m_outline_ibo.SetUsage(GraphicsBuffer::BufferUsageStaticDraw); - m_outline_ibo.SetType(GraphicsBuffer::BufferTypeIndex); - m_outline_ibo.Resize(m_document.ObjectCount() * 2 * sizeof(uint32_t)); - BufferBuilder outline_builder(m_outline_ibo.Map(false, true, true), m_outline_ibo.GetSize()); - - m_filled_ibo.SetUsage(GraphicsBuffer::BufferUsageStaticDraw); - m_filled_ibo.SetType(GraphicsBuffer::BufferTypeIndex); - m_filled_ibo.Resize(m_document.ObjectCount() * 2 * sizeof(uint32_t)); - BufferBuilder filled_builder(m_filled_ibo.Map(false, true, true), m_filled_ibo.GetSize()); - - m_circle_ibo.SetUsage(GraphicsBuffer::BufferUsageStaticDraw); - m_circle_ibo.SetType(GraphicsBuffer::BufferTypeIndex); - m_circle_ibo.Resize(m_document.ObjectCount() * 2 * sizeof(uint32_t)); - BufferBuilder circle_builder(m_circle_ibo.Map(false, true, true), m_circle_ibo.GetSize()); + // Prepare the buffers + for (unsigned i = 0; i < m_object_renderers.size(); ++i) + { + m_object_renderers[i]->PrepareBuffers(m_document.ObjectCount()); + } - m_rendered_filled = m_rendered_outline = m_rendered_circle = 0; - uint32_t currentIndex = 0; + // Add objects from Document to buffers for (unsigned id = 0; id < m_document.ObjectCount(); ++id) { - if (m_document.m_objects.types[id] == RECT_OUTLINE) - { - outline_builder.Add(currentIndex++); - outline_builder.Add(currentIndex++); - m_rendered_outline++; - } - else if (m_document.m_objects.types[id] == RECT_FILLED) - { - filled_builder.Add(currentIndex++); - filled_builder.Add(currentIndex++); - m_rendered_filled++; - } - else - { - circle_builder.Add(currentIndex++); - circle_builder.Add(currentIndex++); - m_rendered_circle++; - } - + ObjectType type = m_document.m_objects.types[id]; + m_object_renderers.at(type)->AddObjectToBuffers(id); // Use at() in case the document is corrupt TODO: Better error handling? + // (Also, Wow I just actually used std::vector::at()) } - m_outline_ibo.UnMap(); - m_filled_ibo.UnMap(); - m_circle_ibo.UnMap(); - m_render_inited = true; + // Finish the buffers + for (unsigned i = 0; i < m_object_renderers.size(); ++i) + { + m_object_renderers[i]->FinaliseBuffers(); + } + m_render_dirty = false; } diff --git a/src/view.h b/src/view.h index 9e3017c..ee0fdcd 100644 --- a/src/view.h +++ b/src/view.h @@ -3,60 +3,68 @@ #include "ipdf.h" #include "document.h" -#include "graphicsbuffer.h" #include "framebuffer.h" -#include "shaderprogram.h" +#include "objectrenderer.h" + +#define USE_GPU_TRANSFORM true +#define USE_GPU_RENDERING true namespace IPDF { + /** + * The View class manages a rectangular view into the document. + * It is responsible for coordinate transforms and rendering the document. + * ObjectRenderer's for each type of Object should be created in the constructor. + */ class View { public: - View(Document & document, const Rect & bounds = Rect(0,0,1,1), const Colour & colour = Colour(0.f,0.f,0.f,1.f)) - : m_use_gpu_transform(false), m_bounds_dirty(true), m_buffer_dirty(true), m_render_inited(false), m_document(document), m_bounds(bounds), m_colour(colour) - { - Debug("View Created - Bounds => {%s}", m_bounds.Str().c_str()); - } - virtual ~View() {} + View(Document & document, const Rect & bounds = Rect(0,0,1,1), const Colour & colour = Colour(0.f,0.f,0.f,1.f)); + virtual ~View(); void Render(int width = 0, int height = 0); void Translate(Real x, Real y); - void ScaleAroundPoint(Real x, Real y, Real scaleAmt); + void ScaleAroundPoint(Real x, Real y, Real scale_amount); Rect TransformToViewCoords(const Rect& inp) const; const Rect& GetBounds() const { return m_bounds; } - const bool UsingGPUTransform() const { return m_use_gpu_transform; } + const bool UsingGPUTransform() const { return m_use_gpu_transform; } // whether view transform calculated on CPU or GPU + const bool UsingGPURendering() const { return m_use_gpu_rendering; } // whether GPU shaders are used or CPU rendering void ToggleGPUTransform() { m_use_gpu_transform = (!m_use_gpu_transform); m_bounds_dirty = true; m_buffer_dirty = true; } + void ToggleGPURendering() { m_use_gpu_rendering = (!m_use_gpu_rendering); m_bounds_dirty = true; m_buffer_dirty = true; } private: - void PrepareRender(); - void UpdateObjBoundsVBO(); - void DrawGrid(); + struct GPUObjBounds + { + float x0, y0; + float x1, y1; + }; + + void PrepareRender(); // call when m_render_dirty is true + void UpdateObjBoundsVBO(); // call when m_buffer_dirty is true + bool m_use_gpu_transform; - bool m_bounds_dirty; - bool m_buffer_dirty; - bool m_render_inited; - ShaderProgram m_rect_outline_shader; - ShaderProgram m_rect_filled_shader; - ShaderProgram m_circle_filled_shader; - // Stores the view bounds. - GraphicsBuffer m_bounds_ubo; - // Stores the bounds for _all_ objects. - GraphicsBuffer m_objbounds_vbo; - // Stores indices into the objbounds vbo for each type of object. - GraphicsBuffer m_outline_ibo; // Rectangle outline - GraphicsBuffer m_filled_ibo; // Filled rectangle - GraphicsBuffer m_circle_ibo; // Filled circle - FrameBuffer m_cached_display; + bool m_use_gpu_rendering; + bool m_bounds_dirty; // the view bounds has changed (occurs when changing view) + bool m_buffer_dirty; // the object bounds have changed (also occurs when changing view, but only when not using GPU transforms) + bool m_render_dirty; // the document has changed (occurs when document first loaded) Document & m_document; + FrameBuffer m_cached_display; Rect m_bounds; Colour m_colour; - uint32_t m_rendered_filled; - uint32_t m_rendered_outline; - uint32_t m_rendered_circle; + + // Stores the view bounds. + GraphicsBuffer m_bounds_ubo; //bounds_dirty means this one has changed + // Stores the bounds for _all_ objects. + GraphicsBuffer m_objbounds_vbo; //buffer_dirty means this one has changed + + // ObjectRenderers to be initialised in constructor + // Trust me it will be easier to generalise things this way. Even though there are pointers. + std::vector m_object_renderers; + }; } -- 2.20.1