From: Sam Moore Date: Mon, 16 Jun 2014 17:01:55 +0000 (+0800) Subject: Implemented CPU rendering for current ObjectTypes X-Git-Url: https://git.ucc.asn.au/?p=ipdf%2Fcode.git;a=commitdiff_plain;h=cfe7da763b5d8ef4252ddb94558abb080bbd893d Implemented CPU rendering for current ObjectTypes 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 --- diff --git a/src/main.cpp b/src/main.cpp index 58188d9..94d1cd5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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]); diff --git a/src/main.h b/src/main.h index 02a7451..70997a3 100644 --- a/src/main.h +++ b/src/main.h @@ -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. diff --git a/src/objectrenderer.cpp b/src/objectrenderer.cpp index ff40b1f..1757ae0 100644 --- a/src/objectrenderer.cpp +++ b/src/objectrenderer.cpp @@ -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); +} + } diff --git a/src/objectrenderer.h b/src/objectrenderer.h index 6e5176b..0790d85 100644 --- a/src/objectrenderer.h +++ b/src/objectrenderer.h @@ -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 diff --git a/src/screen.cpp b/src/screen.cpp index 7ac0678..08e4a26 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -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; diff --git a/src/screen.h b/src/screen.h index f2a98c2..c76a554 100644 --- a/src/screen.h +++ b/src/screen.h @@ -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(); } diff --git a/src/view.cpp b/src/view.cpp index 38b2c17..6bae54e 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -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 diff --git a/src/view.h b/src/view.h index ee0fdcd..5bfced8 100644 --- a/src/view.h +++ b/src/view.h @@ -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 m_object_renderers; - + uint8_t * m_cpu_rendering_pixels; // pixels to be used for CPU rendering }; }