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
}
else
{
+
for(int x = 0; x < 8; ++x)
{
for (int y = 0; y < 8; ++y)
}
}
+ //doc.Add(IPDF::CIRCLE_FILLED, Rect(0.2,0.2,0.6,0.6));
+
}
Rect bounds(b[0],b[1],b[2],b[3]);
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();
{
// 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();
oldy = y;
return;
}
+ if (buttons == 2 && !oldButtonDown)
+ {
+ oldButtonDown = true;
+ view.ToggleGPURendering();
+ oldx = x;
+ oldy = y;
+ }
if (buttons && !oldButtonDown)
{
// We're beginning a drag.
*/
#include "objectrenderer.h"
+#include "view.h"
+
+using namespace std;
namespace IPDF
{
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);
}
// 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);
+}
+
}
* @brief Definition of ObjectRenderer class
*/
+#ifndef _OBJECT_RENDERER_H
+#define _OBJECT_RENDERER_H
+
#include "ipdf.h"
#include "graphicsbuffer.h"
#include "shaderprogram.h"
namespace IPDF
{
+ class View;
/**
* Abstract Base class representing how a particular type of object will be rendered
* Includes GPU rendering and CPU rendering
* 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();
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
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
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
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;
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(); }
#include "view.h"
#include "bufferbuilder.h"
-
+#include "screen.h"
#include "gl_core44.h"
using namespace IPDF;
* @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());
{
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;
}
/**
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;
}
// 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
}
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)
}
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
namespace IPDF
{
+ class Screen;
/**
* The View class manages a rectangular view into the document.
* It is responsible for coordinate transforms and rendering the document.
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);
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
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;
// 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
};
}