Implemented CPU rendering for current ObjectTypes
authorSam Moore <[email protected]>
Mon, 16 Jun 2014 17:01:55 +0000 (01:01 +0800)
committerSam Moore <[email protected]>
Mon, 16 Jun 2014 17:01:55 +0000 (01:01 +0800)
RECT_FILLED (easy), RECT_OUTLINE (ok I need to maths), CIRCLE_FILLED (oh my god how do I maths)

Screen::RenderPixels is FITH but can see the correct(ish) output in "cpu_rendering_last_frame.bmp"

TODO:
 - Fix Screen::RenderPixels
 - Fix reading a BMP from the screen (so we can do "gpu_rendering_last_frame.bmp" as well)
 - Put Beziers in
 - Implement infinite precision document format
 - Profit

src/main.cpp
src/main.h
src/objectrenderer.cpp
src/objectrenderer.h
src/screen.cpp
src/screen.h
src/view.cpp
src/view.h

index 58188d9..94d1cd5 100644 (file)
@@ -81,6 +81,7 @@ int main(int argc, char ** argv)
        }
        else 
        {
+               
                for(int x = 0; x < 8; ++x)
                {
                        for (int y = 0; y < 8; ++y)
@@ -89,6 +90,8 @@ int main(int argc, char ** argv)
                        }
                }
                
+               //doc.Add(IPDF::CIRCLE_FILLED, Rect(0.2,0.2,0.6,0.6));
+               
        }
        Rect bounds(b[0],b[1],b[2],b[3]);
 
index 02a7451..70997a3 100644 (file)
@@ -11,8 +11,9 @@ using namespace IPDF;
 
 inline void OverlayBMP(Document & doc, const char * input, const char * output, 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);
+
        Screen scr;
+       View view(doc, scr, bounds, c);
        if (input != NULL)
                scr.RenderBMP(input);
        view.Render();
@@ -25,13 +26,13 @@ inline void MainLoop(Document & doc, const Rect & bounds = Rect(0,0,1,1), const
 {
        // order is important... segfaults occur when screen (which inits GL) is not constructed first -_-
        Screen scr;
-       View view(doc,bounds, c);
+       View view(doc,scr, bounds, c);
        scr.DebugFontInit("DejaVuSansMono.ttf");
        scr.SetMouseHandler([&](int x, int y, int buttons, int wheel) // [?] wtf
        {
                static bool oldButtonDown = false;
                static int oldx, oldy;
-               if (buttons > 1 && !oldButtonDown)
+               if (buttons == 3 && !oldButtonDown)
                {
                        oldButtonDown = true;
                        view.ToggleGPUTransform();
@@ -39,6 +40,13 @@ inline void MainLoop(Document & doc, const Rect & bounds = Rect(0,0,1,1), const
                        oldy = y;
                        return;
                }
+               if (buttons == 2 && !oldButtonDown)
+               {
+                       oldButtonDown = true;
+                       view.ToggleGPURendering();
+                       oldx = x;
+                       oldy = y;
+               }
                if (buttons && !oldButtonDown)
                {
                        // We're beginning a drag.
index ff40b1f..1757ae0 100644 (file)
@@ -4,6 +4,9 @@
  */
 
 #include "objectrenderer.h"
+#include "view.h"
+
+using namespace std;
 
 namespace IPDF
 {
@@ -33,10 +36,23 @@ 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 * target.w;
+       y = view_bounds.y * target.h;
+       w = view_bounds.w * target.w;
+       h = view_bounds.h * target.h;
+}
+
 /**
  * Default implementation for rendering using CPU
  */
-void ObjectRenderer::RenderUsingCPU()
+void ObjectRenderer::RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target)
 {
        Error("Cannot render objects of type %d on CPU", m_type);
 }
@@ -95,4 +111,121 @@ void ObjectRenderer::FinaliseBuffers()
        // Nothing is necessary for CPU rendering
 }
 
+
+/**
+ * Rectangle (filled)
+ */
+void RectFilledRenderer::RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target)
+{
+       for (unsigned i = 0; i < m_indexes.size(); ++i)
+       {
+               CPURenderBounds bounds(objects.bounds[m_indexes[i]], view, target);
+               for (int x = max(0, bounds.x); x < min(bounds.x+bounds.w, target.w); ++x)
+               {
+                       for (int y = max(0, bounds.y); y < min(bounds.y+bounds.h, target.h); ++y)
+                       {
+                               int index = (x+target.w*y)*4;
+                               target.pixels[index+0] = 0;
+                               target.pixels[index+1] = 0;
+                               target.pixels[index+2] = 0;
+                               target.pixels[index+3] = 255;
+                       }
+               }
+       }
+}
+
+/**
+ * Rectangle (outine)
+ */
+void RectOutlineRenderer::RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target)
+{
+       for (unsigned i = 0; i < m_indexes.size(); ++i)
+       {
+               CPURenderBounds bounds(objects.bounds[m_indexes[i]], view, target);
+               for (int x = max(0, bounds.x); x < min(bounds.x+bounds.w, target.w); ++x)
+               {
+                       int top = (x+target.w*max(0, bounds.y))*4;
+                       int bottom = (x+target.w*min(bounds.y+bounds.h, target.h))*4;
+                       for (int j = 0; j < 3; ++j)
+                       {
+                               target.pixels[top+j] = 0;
+                               target.pixels[bottom+j] = 0;
+                       }
+                       target.pixels[top+3] = 255;
+                       target.pixels[bottom+3] = 255;
+               }
+
+               for (int y = max(0, bounds.y); y < min(bounds.y+bounds.h, target.h); ++y)
+               {
+                       int left = (max(0, bounds.x)+target.w*y)*4;
+                       int right = (min(bounds.x+bounds.w, target.w)+target.w*y)*4;
+                       for (int j = 0; j < 3; ++j)
+                       {
+                               target.pixels[left+j] = 0;
+                               target.pixels[right+j] = 0;
+                       }
+                       target.pixels[left+3] = 255;
+                       target.pixels[right+3] = 255;
+                       
+               }
+       }
+}
+
+/**
+ * Circle (filled)
+ */
+void CircleFilledRenderer::RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target)
+{
+       for (unsigned i = 0; i < m_indexes.size(); ++i)
+       {
+               CPURenderBounds bounds(objects.bounds[m_indexes[i]], view, target);
+               int centre_x = bounds.x + bounds.w / 2;
+               int centre_y = bounds.y + bounds.h / 2;
+               
+               Debug("Centre is %d, %d", centre_x, centre_y);
+               Debug("Bounds are %d,%d,%d,%d", bounds.x, bounds.y, bounds.w, bounds.h);
+               Debug("Windos is %d,%d", target.w, target.h);
+               for (int x = max(0, bounds.x); x < min(bounds.x+bounds.w, target.w); ++x)
+               {
+                       for (int y = max(0, bounds.y); y < min(bounds.y + bounds.h, target.h); ++y)
+                       {
+                               double dx = 2.0*(double)(x - centre_x)/(double)(bounds.w);
+                               double dy = 2.0*(double)(y - centre_y)/(double)(bounds.h);
+                               int index = (x+target.w*y)*4;
+                               
+                               if (dx*dx + dy*dy <= 1.0)
+                               {
+                                       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
+ */
+void ObjectRenderer::SaveBMP(const CPURenderTarget & target, const char * filename)
+{
+       SDL_Surface * surf = SDL_CreateRGBSurfaceFrom(target.pixels, target.w, target.h, 8*4, target.w*4,
+       #if SDL_BYTEORDER == SDL_LIL_ENDIAN
+               0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
+       #else
+               0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff
+       #endif //SDL_BYTEORDER  
+       );      
+       if (surf == NULL)
+               Fatal("SDL_CreateRGBSurfaceFrom(pixels...) failed - %s", SDL_GetError());
+       if (SDL_SaveBMP(surf, filename) != 0)
+               Fatal("SDL_SaveBMP failed - %s", SDL_GetError());
+
+       // Cleanup
+       SDL_FreeSurface(surf);
+}
+
 }
index 6e5176b..0790d85 100644 (file)
@@ -3,6 +3,9 @@
  * @brief Definition of ObjectRenderer class
  */
 
+#ifndef _OBJECT_RENDERER_H
+#define _OBJECT_RENDERER_H
+
 #include "ipdf.h"
 #include "graphicsbuffer.h"
 #include "shaderprogram.h"
@@ -10,6 +13,7 @@
 
 namespace IPDF
 {
+       class View;
        /**
         * Abstract Base class representing how a particular type of object will be rendered
         * Includes GPU rendering and CPU rendering
@@ -36,12 +40,28 @@ namespace IPDF
                         * 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;
+
+                       struct CPURenderTarget
+                       {
+                               uint8_t * pixels;
+                               int w;
+                               int h;
+                       };
+                       struct CPURenderBounds
+                       {
+                               int x; int y; int w; int h;
+                               CPURenderBounds(const Rect & bounds, const View & view, const CPURenderTarget & target);
+                       };
+
+                       static void SaveBMP(const CPURenderTarget & target, const char * filename);
+
+
+                       virtual void RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target) = 0;
                        
                        
                        
                        const ObjectType m_type; /** Type of objects **/
-               private:
+               protected:
                        friend class View; //View is a friendly fellow in the world of IPDF
                        void PrepareBuffers(unsigned max_size);
                        void FinaliseBuffers();
@@ -60,7 +80,7 @@ namespace IPDF
                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
+                       virtual void RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target);
        };
        /** Renderer for outlined rectangles **/
        class RectOutlineRenderer : public ObjectRenderer
@@ -68,7 +88,7 @@ namespace IPDF
                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
+                       virtual void RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target);
        };
        /** Renderer for filled circles **/
        class CircleFilledRenderer : public ObjectRenderer
@@ -76,6 +96,8 @@ namespace IPDF
                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
+                       virtual void RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target);
        };
 }
+
+#endif //_OBJECT_RENDERER_H
index 7ac0678..08e4a26 100644 (file)
@@ -216,7 +216,7 @@ double Screen::GetLastFrameTimeGPU() const
        return frame_time_ns/1000000000.0;
 }
 
-void Screen::RenderPixels(int x, int y, int w, int h, void *pixels) const
+void Screen::RenderPixels(int x, int y, int w, int h, uint8_t *pixels) const
 {
        GLenum texture_format = GL_RGBA;
 
index f2a98c2..c76a554 100644 (file)
@@ -59,7 +59,8 @@ 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;
+               void RenderPixels(int x, int y, int w, int h, uint8_t * 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(); }
index 38b2c17..6bae54e 100644 (file)
@@ -1,6 +1,6 @@
 #include "view.h"
 #include "bufferbuilder.h"
-
+#include "screen.h"
 #include "gl_core44.h"
 
 using namespace IPDF;
@@ -13,10 +13,10 @@ using namespace std;
  * @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)
+View::View(Document & document, Screen & screen, 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)
+               m_render_dirty(true), m_document(document), m_screen(screen), m_cached_display(), m_bounds(bounds), m_colour(colour), m_bounds_ubo(), 
+               m_objbounds_vbo(), m_object_renderers(NUMBER_OF_OBJECT_TYPES), m_cpu_rendering_pixels(NULL)
 {
        Debug("View Created - Bounds => {%s}", m_bounds.Str().c_str());
 
@@ -41,9 +41,10 @@ View::~View()
 {
        for (unsigned i = 0; i < m_object_renderers.size(); ++i)
        {
-               //delete m_object_renderers[i];
+               delete m_object_renderers[i];
        }
        m_object_renderers.clear();
+       delete [] m_cpu_rendering_pixels;
 }
 
 /**
@@ -118,7 +119,9 @@ Rect View::TransformToViewCoords(const Rect& inp) const
 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())
+       int prev_width = m_cached_display.GetWidth();
+       int prev_height = m_cached_display.GetHeight();
+       if (width != prev_width || height != prev_height)
        {
                m_cached_display.Create(width, height);
                m_bounds_dirty = true;
@@ -133,8 +136,6 @@ void View::Render(int width, int height)
        }
 
        // 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_dirty) // document has changed
@@ -155,6 +156,9 @@ void View::Render(int width, int height)
        }
        m_bounds_dirty = false;
 
+       m_cached_display.Bind(); //NOTE: This is redundant; Clear already calls Bind
+       m_cached_display.Clear();
+
 
        // Render using GPU
        if (m_use_gpu_rendering) 
@@ -182,10 +186,25 @@ void View::Render(int width, int height)
        }
        else // Rasterise on CPU then blit texture to GPU
        {
+               // Dynamically resize CPU rendering target pixels if needed
+               if (m_cpu_rendering_pixels == NULL || width*height > prev_width*prev_height)
+               {
+                       delete [] m_cpu_rendering_pixels;
+                       m_cpu_rendering_pixels = new uint8_t[width*height*4];
+                       if (m_cpu_rendering_pixels == NULL)
+                               Fatal("Could not allocate %d*%d*4 = %d bytes for cpu rendered pixels", width, height, width*height*4);
+               }
+               // Clear CPU rendering pixels
+               for (int i = 0; i < width*height*4; ++i)
+                       m_cpu_rendering_pixels[i] = 255;
+
                for (unsigned i = 0; i < m_object_renderers.size(); ++i)
                {
-                       m_object_renderers[i]->RenderUsingCPU();
+                       m_object_renderers[i]->RenderUsingCPU(m_document.m_objects, *this, {m_cpu_rendering_pixels, width, height});
                }
+               m_screen.RenderPixels(0,0,width, height, m_cpu_rendering_pixels); //TODO: Make this work :(
+               // Debug for great victory (do something similar for GPU and compare?)
+               ObjectRenderer::SaveBMP({m_cpu_rendering_pixels, width, height}, "cpu_rendering_last_frame.bmp");
        }
        m_cached_display.UnBind(); // resets render target to the screen
        m_cached_display.Blit(); // blit FrameBuffer to screen
index ee0fdcd..5bfced8 100644 (file)
@@ -11,6 +11,7 @@
 
 namespace IPDF
 {
+       class Screen;
        /**
         * The View class manages a rectangular view into the document.
         * It is responsible for coordinate transforms and rendering the document.
@@ -19,7 +20,7 @@ namespace IPDF
        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));
+                       View(Document & document, Screen & screen, 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);
@@ -35,6 +36,7 @@ namespace IPDF
                        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:
                        struct GPUObjBounds
@@ -52,6 +54,7 @@ namespace IPDF
                        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;
+                       Screen & m_screen;
                        FrameBuffer m_cached_display;
                        Rect m_bounds;
                        Colour m_colour;
@@ -64,7 +67,7 @@ namespace IPDF
                        // 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; 
-                       
+                       uint8_t * m_cpu_rendering_pixels; // pixels to be used for CPU rendering
        };
 }
 

UCC git Repository :: git.ucc.asn.au