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.
# 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
{
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?
+/**
+ * @file common.h
+ * @brief Include common headers
+ */
+
// C++ STD includes
#include <iostream>
#include <vector>
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));}
{
if (m_render_texture)
{
+ // FrameBuffer was already Created, destroy it before creating again
Destroy();
}
m_width = w;
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)
namespace IPDF
{
- /*
+ /**
* The "FrameBuffer" class represents an offscreen render target.
+ * FrameBuffer::Create needs to be called to initialise it; constructor is trivial
*/
class FrameBuffer
{
~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; }
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;
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;
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);
namespace IPDF
{
- /*
+ /**
* Implementation of an OpenGL buffer, with some extra cleverness.
*/
class GraphicsBuffer
}
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
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
{
{
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();
}
}
--- /dev/null
+/**
+ * @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<uint32_t>(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
+}
+
+}
--- /dev/null
+/**
+ * @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<unsigned> m_indexes; /** Index vector for CPU rendering **/
+ BufferBuilder<uint32_t> * 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
+ };
+}
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);
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.
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)
};
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(); }
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);
}
/**
}
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);
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
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
{
GLuint obj;
};
std::vector<Shader> m_shaders;
+ bool m_valid;
};
}
-#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 <bitset>
+#ifndef _VFPU_H
+#define _VFPU_H
+#include <bitset>
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
}
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;
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.
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<GLfloat>(Float(m_bounds.x)), static_cast<GLfloat>(Float(m_bounds.y)), static_cast<GLfloat>(Float(m_bounds.w)), static_cast<GLfloat>(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<GLfloat>(Float(m_bounds.x)), static_cast<GLfloat>(Float(m_bounds.y)), static_cast<GLfloat>(Float(m_bounds.w)), static_cast<GLfloat>(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)
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<uint32_t> 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<uint32_t> 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<uint32_t> 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;
}
#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<ObjectRenderer*> m_object_renderers;
+
};
}