From f59f24dff392428d7219ba2d6be5e1e81c344ee0 Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Wed, 18 Jun 2014 16:41:40 +0800 Subject: [PATCH] =?utf8?q?Be=CC=81ziers?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit It's hard to type the ́e so I will just call them Beziers from now. New struct represents a cubic bezier, can be evaluated. The Objects struct contains a vector of beziers, and a vector of indices for each object. If an ObjectType is BEZIER than the index can be used to look up the bezier control points. Control points are relative to the bounding rectangle; so we can reuse the same curves (eg: For fonts). Rendering happens on CPU only, sub divide and use Bresenham lines. Bresenham lines are not quite optimal but I eventually gave up. So we don't have a "line" type, but you can make one by creating a Bezier where x1,y1 == x0,y0 They look kind of wobbly. Save/Load not tested. It might break. But it will have to be pretty heavily rewritten soon anyway. --- src/Makefile | 2 +- src/bezier.cpp | 48 ++++++++++ src/bezier.h | 52 ++++++++++ src/document.cpp | 37 ++++++- src/document.h | 6 +- src/ipdf.h | 40 +++++--- src/main.cpp | 11 ++- src/main.h | 2 +- src/objectrenderer.cpp | 213 +++++++++++++++++++++++++++++++---------- src/objectrenderer.h | 31 ++++-- src/rect.h | 24 +++++ src/view.cpp | 8 +- 12 files changed, 389 insertions(+), 85 deletions(-) create mode 100644 src/bezier.cpp create mode 100644 src/bezier.h create mode 100644 src/rect.h diff --git a/src/Makefile b/src/Makefile index ac23acc..6dbb2e1 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 objectrenderer.o view.o screen.o vfpu.o graphicsbuffer.o framebuffer.o shaderprogram.o stb_truetype.o gl_core44.o +OBJ = log.o real.o bezier.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/bezier.cpp b/src/bezier.cpp new file mode 100644 index 0000000..15a5069 --- /dev/null +++ b/src/bezier.cpp @@ -0,0 +1,48 @@ +#include "bezier.h" + +#include +#include + +using namespace std; + +namespace IPDF +{ + +/** + * Factorial + * Use dynamic programming / recursion + */ +int Factorial(int n) +{ + static unordered_map dp; + static bool init = false; + if (!init) + { + init = true; + dp[0] = 1; + } + auto it = dp.find(n); + if (it != dp.end()) + return it->second; + int result = n*Factorial(n-1); + dp[n] = result; + return result; +} + +/** + * Binomial coefficients + */ +int BinomialCoeff(int n, int k) +{ + return Factorial(n) / Factorial(k) / Factorial(n-k); +} + +/** + * Bernstein Basis Polynomial + */ +Real Bernstein(int k, int n, const Real & u) +{ + return Real(BinomialCoeff(n, k)) * pow(u, k) * pow(Real(1.0) - u, n-k); +} + +} diff --git a/src/bezier.h b/src/bezier.h new file mode 100644 index 0000000..4e1c404 --- /dev/null +++ b/src/bezier.h @@ -0,0 +1,52 @@ +#ifndef _BEZIER_H +#define _BEZIER_H + +#include "real.h" +#include "rect.h" +namespace IPDF +{ + extern int Factorial(int n); + extern int BinomialCoeff(int n, int k); + extern Real Bernstein(int k, int n, const Real & u); + + /** A _cubic_ bezier. I really want to make them an arbitrary size but that might upset David. **/ + struct Bezier + { + Real x0; Real y0; + Real x1; Real y1; + Real x2; Real y2; + Bezier() = default; // Needed so we can fread/fwrite this struct... for now. + Bezier(Real _x0, Real _y0, Real _x1, Real _y1, Real _x2, Real _y2) : x0(_x0), y0(_y0), x1(_x1), y1(_y1), x2(_x2), y2(_y2) {} + std::string Str() const + { + std::stringstream s; + s << "Bezier{" << Float(x0) << "," << Float(y0) << " -> " << Float(x1) << "," << Float(y1) << " -> " << Float(x2) << "," << Float(y2) << "}"; + return s.str(); + } + Bezier(const Bezier & cpy, const Rect & t = Rect(0,0,1,1)) : x0(cpy.x0+t.x), y0(cpy.y0+t.y), x1(cpy.x1+t.x), y1(cpy.y1+t.y), x2(cpy.x2+t.x),y2(cpy.y2+t.y) + { + x1 = x0 + (x1-x0)*t.w; + y1 = y0 + (y1-y0)*t.h; + x2 = x0 + (x2-x0)*t.w; + y2 = y0 + (y2-y0)*t.h; + } + + Rect ToRect() {return Rect(x0,y0,x2-x0,y2-y0);} + + /** Evaluate the Bezier at parametric parameter u, puts resultant point in (x,y) **/ + void Evaluate(Real & x, Real & y, const Real & u) + { + Real coeff[3]; + for (unsigned i = 0; i < 3; ++i) + coeff[i] = Bernstein(i,2,u); + x = x0*coeff[0] + x1*coeff[1] + x2*coeff[2]; + y = y0*coeff[0] + y1*coeff[1] + y2*coeff[2]; + } + + }; + + + +} + +#endif //_BEZIER_H diff --git a/src/document.cpp b/src/document.cpp index c7ef01c..921763a 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -1,10 +1,12 @@ #include "document.h" - +#include "bezier.h" #include using namespace IPDF; using namespace std; +//TODO: Make this work for variable sized Reals + // Loads an std::vector of size num_elements from a file. template static void LoadStructVector(FILE *src_file, size_t num_elems, std::vector& dest) @@ -68,6 +70,14 @@ void Document::Save(const string & filename) WriteChunkHeader(file, CT_OBJBOUNDS, m_objects.bounds.size() * sizeof(Rect)); SaveStructVector(file, m_objects.bounds); + Debug("Object data indices..."); + WriteChunkHeader(file, CT_OBJINDICES, m_objects.data_indices.size() * sizeof(unsigned)); + SaveStructVector(file, m_objects.data_indices); + + Debug("Bezier data..."); + WriteChunkHeader(file, CT_OBJBEZIERS, m_objects.beziers.size() * sizeof(uint8_t)); + SaveStructVector(file, m_objects.beziers); + int err = fclose(file); if (err != 0) Fatal("Failed to close file \"%s\" - %s", filename.c_str(), strerror(err)); @@ -111,18 +121,34 @@ void Document::Load(const string & filename) Debug("Object bounds..."); LoadStructVector(file, chunk_size/sizeof(Rect), m_objects.bounds); break; + case CT_OBJINDICES: + Debug("Object data indices..."); + LoadStructVector(file, chunk_size/sizeof(unsigned), m_objects.data_indices); + break; + case CT_OBJBEZIERS: + Debug("Bezier data..."); + LoadStructVector(file, chunk_size/sizeof(Bezier), m_objects.beziers); + break; } } Debug("Successfully loaded %u objects from \"%s\"", ObjectCount(), filename.c_str()); } -void Document::Add(ObjectType type, const Rect & bounds) +void Document::Add(ObjectType type, const Rect & bounds, unsigned data_index) { m_objects.types.push_back(type); m_objects.bounds.push_back(bounds); - m_count++; + m_objects.data_indices.push_back(data_index); + ++m_count; // Why can't we just use the size of types or something? } +unsigned Document::AddBezierData(const Bezier & bezier) +{ + m_objects.beziers.push_back(bezier); + return m_objects.beziers.size()-1; +} + + void Document::DebugDumpObjects() { Debug("Objects for Document %p are:", this); @@ -134,5 +160,8 @@ void Document::DebugDumpObjects() bool Document::operator==(const Document & equ) const { - return (ObjectCount() == equ.ObjectCount() && memcmp(m_objects.bounds.data(), equ.m_objects.bounds.data(), ObjectCount() * sizeof(Rect)) == 0); + return (ObjectCount() == equ.ObjectCount() + && memcmp(m_objects.bounds.data(), equ.m_objects.bounds.data(), ObjectCount() * sizeof(Rect)) == 0 + && memcmp(m_objects.data_indices.data(), equ.m_objects.data_indices.data(), ObjectCount() * sizeof(unsigned)) == 0 + && memcmp(m_objects.beziers.data(), equ.m_objects.beziers.data(), m_objects.beziers.size() * sizeof(Bezier)) == 0); } diff --git a/src/document.h b/src/document.h index 54ccdd0..5ec5dad 100644 --- a/src/document.h +++ b/src/document.h @@ -13,7 +13,6 @@ namespace IPDF void Load(const std::string & filename = ""); void Save(const std::string & filename); - void Add(ObjectType type, const Rect & bounds); void DebugDumpObjects(); unsigned ObjectCount() const {return m_count;} @@ -22,10 +21,15 @@ namespace IPDF bool operator==(const Document & equ) const; bool operator!=(const Document & equ) const {return !(this->operator==(equ));} + void Add(ObjectType type, const Rect & bounds, unsigned data_index = 0); + unsigned AddBezierData(const Bezier & bezier); + private: friend class View; Objects m_objects; unsigned m_count; + + }; } diff --git a/src/ipdf.h b/src/ipdf.h index 211f2c3..cd6ef6f 100644 --- a/src/ipdf.h +++ b/src/ipdf.h @@ -3,10 +3,13 @@ #include "common.h" #include "real.h" +#include "bezier.h" +#include "rect.h" #define C_RED Colour(1,0,0,1) #define C_GREEN Colour(0,1,0,1) #define C_BLUE Colour(0,0,1,1) +#define C_BLACK Colour(0,0,0,1); namespace IPDF { @@ -25,6 +28,7 @@ namespace IPDF CIRCLE_FILLED = 0, RECT_FILLED, RECT_OUTLINE, + BEZIER, NUMBER_OF_OBJECT_TYPES } ObjectType; @@ -33,22 +37,15 @@ namespace IPDF CT_NUMOBJS, CT_OBJTYPES, CT_OBJBOUNDS, + CT_OBJINDICES, + CT_OBJBEZIERS + //CT_OBJGROUPS }; - struct Rect - { - Real x; Real y; Real w; Real h; - Rect() = default; // Needed so we can fread/fwrite this struct - Rect(Real _x, Real _y, Real _w, Real _h) : x(_x), y(_y), w(_w), h(_h) {} - std::string Str() const - { - std::stringstream s; - // float conversion needed because it is fucking impossible to get ostreams working with template classes - s << "{" << Float(x) << ", " << Float(y) << ", " << Float(w) << ", " << Float(h) << "}"; - return s.str(); - } - }; + + + struct Colour { float r; float g; float b; float a; @@ -56,10 +53,23 @@ namespace IPDF Colour(float _r, float _g, float _b, float _a) : r(_r), g(_g), b(_b), a(_a) {} }; + struct ObjectData + { + Colour colour; + + }; + struct Objects { - std::vector types; - std::vector bounds; + /** Used by all objects **/ + std::vector types; // types of objects + std::vector bounds; // rectangle bounds of objects + + /** Used by BEZIER to identify data position in relevant vector **/ + std::vector data_indices; + + /** Used by BEZIER only **/ + std::vector beziers; // bezier curves - look up by data_indices }; class View; diff --git a/src/main.cpp b/src/main.cpp index 91ce90b..c14a586 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -81,17 +81,22 @@ int main(int argc, char ** argv) } else { + doc.AddBezierData(Bezier(0,0,0,1,1,0)); + doc.AddBezierData(Bezier(0,0,1,0,0,1)); + doc.AddBezierData(Bezier(0,0,1,1,1,0)); + doc.AddBezierData(Bezier(0,1,1,0,0,1)); + + for(int x = 0; x < 8; ++x) { for (int y = 0; y < 8; ++y) { - doc.Add(static_cast((x^y)%3), Rect(0.2+x-4.0,0.2+y-4.0,0.6,0.6)); + //doc.Add(static_cast((x^y)%3), Rect(0.2+x-4.0,0.2+y-4.0,0.6,0.6)); + doc.Add(BEZIER, Rect(0.2+x-4.0, 0.2+y-4.0, 0.6,0.6), (x^y)%3); } } - //doc.Add(IPDF::RECT_OUTLINE, Rect(0.4,0.4,0.6,0.6)); - } Rect bounds(b[0],b[1],b[2],b[3]); diff --git a/src/main.h b/src/main.h index 0d181c5..65eabb6 100644 --- a/src/main.h +++ b/src/main.h @@ -83,7 +83,7 @@ inline void MainLoop(Document & doc, const Rect & bounds = Rect(0,0,1,1), const clock_gettime(CLOCK_MONOTONIC_RAW, &real_clock_start); real_clock_now = real_clock_start; double frames = 0; - double data_rate = 1; // period between data output to stdout (if <= 0 there will be no output) + double data_rate = -1; // period between data output to stdout (if <= 0 there will be no output) uint64_t data_points = 0; setbuf(stdout, NULL); while (scr.PumpEvents()) diff --git a/src/objectrenderer.cpp b/src/objectrenderer.cpp index 25f94a1..c552bd8 100644 --- a/src/objectrenderer.cpp +++ b/src/objectrenderer.cpp @@ -13,7 +13,7 @@ namespace IPDF /** * ObjectRenderer constructor - * Note the ShaderProgram constructor which compiles the shaders for GPU rendering (if they exist) + * Note we cannot compile the shaders in the constructor because the Screen class needs to initialise GL and it has a ShaderProgram member */ ObjectRenderer::ObjectRenderer(const ObjectType & type, const char * vert_glsl_file, const char * frag_glsl_file, const char * geom_glsl_file) @@ -36,19 +36,6 @@ void ObjectRenderer::RenderUsingGPU() glDrawElements(GL_LINES, m_indexes.size()*2, GL_UNSIGNED_INT, 0); } -/** - * Helper structuretransforms coordinates to pixels - */ - -ObjectRenderer::CPURenderBounds::CPURenderBounds(const Rect & bounds, const View & view, const CPURenderTarget & target) -{ - Rect view_bounds = view.TransformToViewCoords(bounds); - x = view_bounds.x * Real(target.w); - y = view_bounds.y * Real(target.h); - w = view_bounds.w * Real(target.w); - h = view_bounds.h * Real(target.h); - //Debug("CPURenderBounds %s -> %s -> {%li,%li,%li,%li}", bounds.Str().c_str(), view_bounds.Str().c_str(), x, y, w, h); -} /** * Default implementation for rendering using CPU @@ -56,6 +43,7 @@ ObjectRenderer::CPURenderBounds::CPURenderBounds(const Rect & bounds, const View void ObjectRenderer::RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target) { Error("Cannot render objects of type %d on CPU", m_type); + //TODO: Render a rect or something instead? } /** @@ -121,7 +109,7 @@ void RectFilledRenderer::RenderUsingCPU(const Objects & objects, const View & vi { for (unsigned i = 0; i < m_indexes.size(); ++i) { - CPURenderBounds bounds(objects.bounds[m_indexes[i]], view, target); + PixelBounds bounds(CPURenderBounds(objects.bounds[m_indexes[i]], view, target)); for (int64_t x = max(0L, bounds.x); x <= min(bounds.x+bounds.w, target.w-1); ++x) { for (int64_t y = max(0L, bounds.y); y <= min(bounds.y+bounds.h, target.h-1); ++y) @@ -141,45 +129,24 @@ void RectFilledRenderer::RenderUsingCPU(const Objects & objects, const View & vi */ void RectOutlineRenderer::RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target) { + //Debug("Render %u outlined rectangles on CPU", m_indexes.size()); for (unsigned i = 0; i < m_indexes.size(); ++i) { - CPURenderBounds bounds(objects.bounds[m_indexes[i]], view, target); - for (int64_t x = max(0L, bounds.x); x <= min(bounds.x+bounds.w, target.w-1); ++x) - { - int64_t top = (x+target.w*bounds.y)*4; - int64_t bottom = (x+target.w*(bounds.y+bounds.h))*4; - - if (top >= 0L && top <4*target.w*target.h) - { - for (int j = 0; j < 3; ++j) - target.pixels[top+j] = 0; - target.pixels[top+3] = 255; - } - if (bottom >= 0L && bottom <4*target.w*target.h) - { - for (int j = 0; j < 3; ++j) - target.pixels[bottom+j] = 0; - target.pixels[bottom+3] = 255; - } - } + PixelBounds bounds(CPURenderBounds(objects.bounds[m_indexes[i]], view, target)); + + // Using bresenham's lines now mainly because I want to see if they work + // top + ObjectRenderer::RenderLineOnCPU(bounds.x, bounds.y, bounds.x+bounds.w, bounds.y, target); + // bottom + ObjectRenderer::RenderLineOnCPU(bounds.x, bounds.y+bounds.h, bounds.x+bounds.w, bounds.y+bounds.h, target); + // left + ObjectRenderer::RenderLineOnCPU(bounds.x, bounds.y, bounds.x, bounds.y+bounds.h, target); + // right + ObjectRenderer::RenderLineOnCPU(bounds.x+bounds.w, bounds.y, bounds.x+bounds.w, bounds.y+bounds.h, target); - for (int64_t y = max(0L, bounds.y); y <= min(bounds.y+bounds.h, target.h-1); ++y) - { - int64_t left = (bounds.x >= 0L && bounds.x < target.w) ? (bounds.x + target.w*y)*4 : -1L; - int64_t right = (bounds.x+bounds.w >= 0L && bounds.x+bounds.w < target.w) ? (bounds.x+bounds.w + target.w*y)*4 : -1L; - if (left >= 0L && left <4*target.w*target.h) - { - for (int j = 0; j < 3; ++j) - target.pixels[left+j] = 0; - target.pixels[left+3] = 255; - } - if (right >= 0L && right <4*target.w*target.h) - { - for (int j = 0; j < 3; ++j) - target.pixels[right+j] = 0; - target.pixels[right+3] = 255; - } - } + // Diagonal for testing (from bottom left to top right) + //ObjectRenderer::RenderLineOnCPU(bounds.x,bounds.y+bounds.h, bounds.x+bounds.w, bounds.y,target, C_BLUE); + //ObjectRenderer::RenderLineOnCPU(bounds.x+bounds.w, bounds.y+bounds.h, bounds.x, bounds.y, target,C_GREEN); } } @@ -190,7 +157,7 @@ void CircleFilledRenderer::RenderUsingCPU(const Objects & objects, const View & { for (unsigned i = 0; i < m_indexes.size(); ++i) { - CPURenderBounds bounds(objects.bounds[m_indexes[i]], view, target); + PixelBounds bounds(CPURenderBounds(objects.bounds[m_indexes[i]], view, target)); int64_t centre_x = bounds.x + bounds.w / 2; int64_t centre_y = bounds.y + bounds.h / 2; @@ -218,6 +185,71 @@ void CircleFilledRenderer::RenderUsingCPU(const Objects & objects, const View & } } +Rect ObjectRenderer::CPURenderBounds(const Rect & bounds, const View & view, const CPURenderTarget & target) +{ + Rect result = view.TransformToViewCoords(bounds); + result.x *= Real(target.w); + result.y *= Real(target.h); + result.w *= Real(target.w); + result.h *= Real(target.h); + return result; +} + + +/** + * Bezier curve + * Not sure how to apply De'Casteljau, will just use a bunch of Bresnham lines for now. + */ +void BezierRenderer::RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target) +{ + //Warn("Rendering Beziers on CPU. Things may explode."); + for (unsigned i = 0; i < m_indexes.size(); ++i) + { + Rect bounds(CPURenderBounds(objects.bounds[m_indexes[i]], view, target)); + PixelBounds pix_bounds(bounds); + + + Bezier control(objects.beziers[objects.data_indices[m_indexes[i]]], bounds); + //Debug("%s -> %s via %s", objects.beziers[objects.data_indices[m_indexes[i]]].Str().c_str(), control.Str().c_str(), bounds.Str().c_str()); + // Draw a rectangle around the bezier for debugging the coord transforms + //ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y, target); + //ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y+pix_bounds.h, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target); + //ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x, pix_bounds.y+pix_bounds.h, target); + //ObjectRenderer::RenderLineOnCPU(pix_bounds.x+pix_bounds.w, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target); + + // Draw lines between the control points for debugging + //ObjectRenderer::RenderLineOnCPU((int64_t)control.x0, (int64_t)control.y0, (int64_t)control.x1, (int64_t)control.y1,target); + //ObjectRenderer::RenderLineOnCPU((int64_t)control.x1, (int64_t)control.y1, (int64_t)control.x2, (int64_t)control.y2,target); + + + + Real x[2]; Real y[2]; + control.Evaluate(x[0], y[0], Real(0)); + for (unsigned j = 1; j <= 100; ++j) + { + control.Evaluate(x[j % 2],y[j % 2], Real(0.01)*j); + ObjectRenderer::RenderLineOnCPU((int64_t)x[0],(int64_t)y[0], (int64_t)x[1],(int64_t)y[1], target); + } + + /* + Real u(0); + while (u < Real(1)) + { + u += Real(1e-6); + Real x; Real y; control.Evaluate(x,y,u); + int64_t index = ((int64_t)x + (int64_t)y*target.w)*4; + if (index >= 0 && index < 4*(target.w*target.h)) + { + target.pixels[index+0] = 0; + target.pixels[index+1] = 0; + target.pixels[index+2] = 0; + target.pixels[index+3] = 255; + } + } + */ + + } +} /** * For debug, save pixels to bitmap @@ -240,4 +272,81 @@ void ObjectRenderer::SaveBMP(const CPURenderTarget & target, const char * filena SDL_FreeSurface(surf); } +/** + * Bresenham's lines + */ +void ObjectRenderer::RenderLineOnCPU(int64_t x0, int64_t y0, int64_t x1, int64_t y1, const CPURenderTarget & target, const Colour & colour, bool transpose) +{ + int64_t dx = x1 - x0; + int64_t dy = y1 - y0; + bool neg_m = (dy*dx < 0); + dy = abs(dy); + dx = abs(dx); + + // If positive slope > 1, just swap x and y + if (dy > dx) + { + RenderLineOnCPU(y0,x0,y1,x1,target,colour,!transpose); + return; + } + + int64_t two_dy = 2*dy; + int64_t p = two_dy - dx; + int64_t two_dxdy = 2*(dy-dx); + int64_t x; int64_t y; int64_t x_end; + int64_t width = (transpose ? target.h : target.w); + int64_t height = (transpose ? target.w : target.h); + + uint8_t rgba[4]; + rgba[0] = 255*colour.r; + rgba[1] = 255*colour.g; + rgba[2] = 255*colour.b; + rgba[3] = 255*colour.a; + + if (x0 > x1) + { + x = x1; + y = y1; + x_end = x0; + } + else + { + x = x0; + y = y0; + x_end = x1; + } + + if (x < 0) + { + if (x_end < 0) return; + y = (neg_m ? y - (dy*-x)/dx : y + (dy*-x)/dx); + x = 0; + } + + if (x_end > width) + { + if (x > width) return; + x_end = width-1; + } + + // TODO: Avoid extra inner conditionals + do + { + if (x >= 0 && x < width && y >= 0 && y < height) + { + int64_t index = (transpose ? (y + x*target.w)*4 : (x + y*target.w)*4); + for (int i = 0; i < 4; ++i) + target.pixels[index+i] = rgba[i]; + } + + if (p < 0) + p += two_dy; + else + { + if (neg_m) --y; else ++y; + p += two_dxdy; + } + } while (++x < x_end); +} + } diff --git a/src/objectrenderer.h b/src/objectrenderer.h index 6dd97ae..4dd7fe1 100644 --- a/src/objectrenderer.h +++ b/src/objectrenderer.h @@ -19,8 +19,8 @@ namespace IPDF * 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 + * For CPU rendering, implement RenderUsingCPU in the derived class + * To leave unimplemented, just call ObjectRenderer::RenderUsingCPU in the derived class * The View class uses ObjectRenderer's; see view.h */ class ObjectRenderer @@ -34,7 +34,7 @@ namespace IPDF * 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(); + virtual void RenderUsingGPU(); /** * Use the CPU to render the objects - "make a bitmap and convert it to a texture" approach @@ -47,12 +47,15 @@ namespace IPDF int64_t w; int64_t h; }; - struct CPURenderBounds + struct PixelBounds { int64_t x; int64_t y; int64_t w; int64_t h; - CPURenderBounds(const Rect & bounds, const View & view, const CPURenderTarget & target); + PixelBounds(const Rect & bounds) : x(bounds.x), y(bounds.y), w(bounds.w), h(bounds.h) {} }; + static Rect CPURenderBounds(const Rect & bounds, const View & view, const CPURenderTarget & target); + + static void SaveBMP(const CPURenderTarget & target, const char * filename); @@ -66,7 +69,9 @@ namespace IPDF void PrepareBuffers(unsigned max_size); void FinaliseBuffers(); void AddObjectToBuffers(unsigned index); - + + /** Helper for CPU rendering that will render a line using Bresenham's algorithm. Do not use the transpose argument. **/ + static void RenderLineOnCPU(int64_t x0, int64_t y0, int64_t x1, int64_t y1, const CPURenderTarget & target, const Colour & colour = Colour(0,0,0,1), bool transpose = false); ShaderProgram m_shader_program; /** GLSL shaders for GPU **/ GraphicsBuffer m_ibo; /** Index Buffer Object for GPU rendering **/ @@ -98,6 +103,20 @@ namespace IPDF virtual ~CircleFilledRenderer() {} virtual void RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target); }; + + /** Renderer for bezier curves **/ + class BezierRenderer : public ObjectRenderer + { + public: + BezierRenderer() : ObjectRenderer(BEZIER, "shaders/rect_vert.glsl", "shaders/rect_frag.glsl", "shaders/rect_outline_geom.glsl") {} + virtual ~BezierRenderer() {} + virtual void RenderUsingGPU() + { + Error("Cannot render beziers on the GPU; they will appear as outlined rectangles."); + ObjectRenderer::RenderUsingGPU(); + } + virtual void RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target); + }; } #endif //_OBJECT_RENDERER_H diff --git a/src/rect.h b/src/rect.h new file mode 100644 index 0000000..a79f6ac --- /dev/null +++ b/src/rect.h @@ -0,0 +1,24 @@ +#ifndef _RECT_H +#define _RECT_H + +#include "common.h" +#include "real.h" + +namespace IPDF +{ + struct Rect + { + Real x; Real y; Real w; Real h; + Rect() = default; // Needed so we can fread/fwrite this struct + Rect(Real _x, Real _y, Real _w, Real _h) : x(_x), y(_y), w(_w), h(_h) {} + std::string Str() const + { + std::stringstream s; + // float conversion needed because it is fucking impossible to get ostreams working with template classes + s << "{" << Float(x) << ", " << Float(y) << ", " << Float(w) << ", " << Float(h) << "}"; + return s.str(); + } + }; +} + +#endif //_RECT_H diff --git a/src/view.cpp b/src/view.cpp index 770ecf6..cbcdbf4 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -21,10 +21,11 @@ View::View(Document & document, Screen & screen, const Rect & bounds, const Colo 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 + //TODO: Don't forget to put new renderers here or things will be segfaultastic m_object_renderers[RECT_FILLED] = new RectFilledRenderer(); m_object_renderers[RECT_OUTLINE] = new RectOutlineRenderer(); m_object_renderers[CIRCLE_FILLED] = new CircleFilledRenderer(); + m_object_renderers[BEZIER] = new BezierRenderer(); // To add rendering for a new type of object; // 1. Add enum to ObjectType in ipdf.h @@ -41,7 +42,7 @@ View::~View() { for (unsigned i = 0; i < m_object_renderers.size(); ++i) { - delete m_object_renderers[i]; + delete m_object_renderers[i]; // delete's match new's in constructor } m_object_renderers.clear(); delete [] m_cpu_rendering_pixels; @@ -256,6 +257,7 @@ void View::UpdateObjBoundsVBO() */ void View::PrepareRender() { + Debug("Recreate buffers with %u objects", m_document.ObjectCount()); // Prepare bounds vbo m_bounds_ubo.Invalidate(); m_bounds_ubo.SetType(GraphicsBuffer::BufferTypeUniform); @@ -278,6 +280,8 @@ void View::PrepareRender() 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()) + // (Also, I just managed to make it throw an exception because I'm a moron) + Debug("Object of type %d", type); } // Finish the buffers -- 2.20.1