Skip to content
Browse files

Rework graphics architecture a bit

In preparation of BitmapData::draw support. See README.GRAPHICS for
details.
  • Loading branch information...
1 parent cf3ddea commit bfa53b3f0d875c3b863129b38d991fc79b361a3c @alexp-sssup alexp-sssup committed May 3, 2012
View
30 README.GRAPHICS
@@ -0,0 +1,30 @@
+Overview of graphics pipeline
+-----------------------------
+Lightspark tries to be efficient on the rendering side using both caching and
+multi threaded rasterization.
+
+Most of the graphic assets come in vectorial form. Cairo is used to transform
+vectorial data in raster (bitmap format). PangoCairo is used for dynamic text.
+Rendered data is then transferred onto OpenGL textures using Pixel Buffer
+Objects (that hopefully are implemented using DMA). Textures are only loaded
+when an object changes, so static objects are always cached in VRAM.
+
+Pipeline for Stage:
+1) When an object changes and needs to be redrawn it will call its
+DisplayObject::requestInvalidation method. The method needs an InvalidateQueue
+parameter and will add the object to such queue.
+2) Invalidation requests are gathered together in the queue, but not executed
+until the currently handled event ends, to make sure we are rendering a
+consistent state.
+3) At the end of the event all objects in the queue will have their ::invalidate
+method called. This method returns an instance of IDrawable or NULL is rendering
+is not necessary. It's important that the IDrawable instance contains a copy of
+all the needed object state since the object may change while the rendering
+happens. In the future Copy On Write could be useful to save the copy of data in
+some cases.
+4) The returned IDrawable and a reference to the object itself will then be
+stored inside an AsyncDrawJob. The reference to the objects is necessary to make
+sure the CachedSurface of the object, which stores information about the OpenGL
+texture where the data must end, survives until the end of the rendering.
+5) The AsyncDrawJob in added to the Thread Pool, to be executed. It will use
+IDrawable::getPixelBuffer to get the raw pixels and upload them to the GPU.
View
154 src/backends/graphics.cpp
@@ -291,48 +291,9 @@ bool TextureChunk::resizeIfLargeEnough(uint32_t w, uint32_t h)
return false;
}
-CairoRenderer::CairoRenderer(ASObject* _o, CachedSurface& _t, const MATRIX& _m,
- int32_t _x, int32_t _y, int32_t _w, int32_t _h, float _s, float _a)
- : owner(_o),surface(_t),matrix(_m),xOffset(_x),yOffset(_y),alpha(_a),width(_w),height(_h),
- surfaceBytes(NULL),scaleFactor(_s),uploadNeeded(true)
+CairoRenderer::CairoRenderer(const MATRIX& _m, int32_t _x, int32_t _y, int32_t _w, int32_t _h, float _s, float _a)
+ : IDrawable(_w, _h, _x, _y, _a), matrix(_m), scaleFactor(_s)
{
- owner->incRef();
-}
-
-CairoRenderer::~CairoRenderer()
-{
- delete[] surfaceBytes;
- owner->decRef();
-}
-
-void CairoRenderer::sizeNeeded(uint32_t& w, uint32_t& h) const
-{
- w=width;
- h=height;
-}
-
-void CairoRenderer::upload(uint8_t* data, uint32_t w, uint32_t h) const
-{
- if(surfaceBytes)
- memcpy(data,surfaceBytes,w*h*4);
-}
-
-const TextureChunk& CairoRenderer::getTexture()
-{
- /* This is called in the render thread,
- * so we need no locking for surface */
- //Verify that the texture is large enough
- if(!surface.tex.resizeIfLargeEnough(width, height))
- surface.tex=getSys()->getRenderThread()->allocateTexture(width, height,false);
- surface.xOffset=xOffset;
- surface.yOffset=yOffset;
- surface.alpha=alpha;
- return surface.tex;
-}
-
-void CairoRenderer::uploadFence()
-{
- delete this;
}
cairo_matrix_t CairoRenderer::MATRIXToCairo(const MATRIX& matrix)
@@ -347,21 +308,6 @@ cairo_matrix_t CairoRenderer::MATRIXToCairo(const MATRIX& matrix)
return ret;
}
-void CairoRenderer::threadAbort()
-{
- //Nothing special to be done
-}
-
-void CairoRenderer::jobFence()
-{
- //If the data must be uploaded (there were no errors) the Job add itself to the upload queue.
- //Otherwise it destroys itself
- if(uploadNeeded)
- getSys()->getRenderThread()->addUploadJob(this);
- else
- delete this;
-}
-
void CairoTokenRenderer::quadraticBezier(cairo_t* cr, double control_x, double control_y, double end_x, double end_y)
{
double start_x, start_y;
@@ -659,13 +605,12 @@ void CairoRenderer::cairoClean(cairo_t* cr)
cairo_paint(cr);
}
-cairo_surface_t* CairoRenderer::allocateSurface()
+cairo_surface_t* CairoRenderer::allocateSurface(uint8_t*& buf)
{
int32_t cairoWidthStride=cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
assert(cairoWidthStride==width*4);
- assert(surfaceBytes==NULL);
- surfaceBytes=new uint8_t[cairoWidthStride*height];
- return cairo_image_surface_create_for_data(surfaceBytes, CAIRO_FORMAT_ARGB32, width, height, cairoWidthStride);
+ buf=new uint8_t[cairoWidthStride*height];
+ return cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_ARGB32, width, height, cairoWidthStride);
}
void CairoTokenRenderer::executeDraw(cairo_t* cr)
@@ -682,38 +627,39 @@ StaticMutex CairoRenderer::cairoMutex;
StaticMutex CairoRenderer::cairoMutex = GLIBMM_STATIC_MUTEX_INIT;
#endif
-/* This implements IThreadJob::execute */
-void CairoRenderer::execute()
+uint8_t* CairoRenderer::getPixelBuffer()
{
Mutex::Lock l(cairoMutex);
if(width==0 || height==0 || !Config::getConfig()->isRenderingEnabled())
- {
- uploadNeeded = false;
- return;
- }
+ return NULL;
int32_t windowWidth=getSys()->getRenderThread()->windowWidth;
int32_t windowHeight=getSys()->getRenderThread()->windowHeight;
//Discard stuff that it's outside the visible part
if(xOffset >= windowWidth || yOffset >= windowHeight
|| xOffset + width <= 0 || yOffset + height <= 0)
{
- uploadNeeded = false;
- return;
+ return NULL;
}
- //TODO:clip on the right and bottom also
if(xOffset<0)
+ {
width+=xOffset;
+ xOffset=0;
+ }
if(yOffset<0)
+ {
height+=yOffset;
+ yOffset=0;
+ }
//Clip the size to the screen borders
- if((xOffset>=0) && (width+xOffset) > windowWidth)
+ if((xOffset>0) && (width+xOffset) > windowWidth)
width=windowWidth-xOffset;
if((yOffset>0) && (height+yOffset) > windowHeight)
height=windowHeight-yOffset;
- cairo_surface_t* cairoSurface=allocateSurface();
+ uint8_t* ret=NULL;
+ cairo_surface_t* cairoSurface=allocateSurface(ret);
cairo_t* cr=cairo_create(cairoSurface);
cairo_surface_destroy(cairoSurface); /* cr has an reference to it */
@@ -732,6 +678,7 @@ void CairoRenderer::execute()
executeDraw(cr);
cairo_destroy(cr);
+ return ret;
}
bool CairoTokenRenderer::hitTest(const std::vector<GeomToken>& tokens, float scaleFactor, number_t x, number_t y)
@@ -934,3 +881,68 @@ bool CairoPangoRenderer::getBounds(const TextData& _textData, uint32_t& w, uint3
return (h!=0) && (w!=0);
}
+
+AsyncDrawJob::AsyncDrawJob(IDrawable* d, _R<DisplayObject> o):drawable(d),owner(o),surfaceBytes(NULL),uploadNeeded(false)
+{
+}
+
+AsyncDrawJob::~AsyncDrawJob()
+{
+ delete drawable;
+ delete[] surfaceBytes;
+}
+
+void AsyncDrawJob::execute()
+{
+ surfaceBytes=drawable->getPixelBuffer();
+ if(surfaceBytes)
+ uploadNeeded=true;
+}
+
+void AsyncDrawJob::threadAbort()
+{
+ //Nothing special to be done
+}
+
+void AsyncDrawJob::jobFence()
+{
+ //If the data must be uploaded (there were no errors) the Job add itself to the upload queue.
+ //Otherwise it destroys itself
+ if(uploadNeeded)
+ getSys()->getRenderThread()->addUploadJob(this);
+ else
+ delete this;
+}
+
+void AsyncDrawJob::upload(uint8_t* data, uint32_t w, uint32_t h) const
+{
+ assert(surfaceBytes);
+ memcpy(data, surfaceBytes, w*h*4);
+}
+
+void AsyncDrawJob::sizeNeeded(uint32_t& w, uint32_t& h) const
+{
+ w=drawable->getWidth();
+ h=drawable->getHeight();
+}
+
+const TextureChunk& AsyncDrawJob::getTexture()
+{
+ /* This is called in the render thread,
+ * so we need no locking for surface */
+ CachedSurface& surface=owner->cachedSurface;
+ uint32_t width=drawable->getWidth();
+ uint32_t height=drawable->getHeight();
+ //Verify that the texture is large enough
+ if(!surface.tex.resizeIfLargeEnough(width, height))
+ surface.tex=getSys()->getRenderThread()->allocateTexture(width, height,false);
+ surface.xOffset=drawable->getXOffset();
+ surface.yOffset=drawable->getYOffset();
+ surface.alpha=drawable->getAlpha();
+ return surface.tex;
+}
+
+void AsyncDrawJob::uploadFence()
+{
+ delete this;
+}
View
104 src/backends/graphics.h
@@ -182,28 +182,11 @@ class ITextureUploadable
virtual void uploadFence()=0;
};
-/**
- The base class for render jobs based on cairo
- Stores an internal copy of the data to be rendered
-*/
-class CairoRenderer: public ITextureUploadable, public IThreadJob
+class IDrawable
{
protected:
- virtual ~CairoRenderer();
- /**
- * The ASObject owning this render request. We incRef/decRef it
- * in our constructor/destructor to make sure that it does no go away
- * (especially the CachedSurface reference below) while we do our work.
- */
- ASObject* owner;
- /**
- The target texture for the rendering, must be non const as the operation will update the size
- */
- CachedSurface& surface;
- /**
- The whole transformation matrix that is applied to the rendered object
- */
- MATRIX matrix;
+ int32_t width;
+ int32_t height;
/*
The minimal x coordinate for all the points being drawn, in local coordinates
*/
@@ -213,18 +196,65 @@ class CairoRenderer: public ITextureUploadable, public IThreadJob
*/
int32_t yOffset;
float alpha;
- int32_t width;
- int32_t height;
+public:
+ IDrawable(int32_t w, int32_t h, int32_t x, int32_t y, float a):
+ width(w),height(h),xOffset(x),yOffset(y),alpha(a){}
+ virtual ~IDrawable(){}
+ virtual uint8_t* getPixelBuffer()=0;
+ int32_t getWidth() const { return width; }
+ int32_t getHeight() const { return height; }
+ int32_t getXOffset() const { return xOffset; }
+ int32_t getYOffset() const { return yOffset; }
+ float getAlpha() const { return alpha; }
+};
+
+class AsyncDrawJob: public IThreadJob, public ITextureUploadable
+{
+private:
+ IDrawable* drawable;
+ /**
+ * The DisplayObject owning this render request. We incRef/decRef it
+ * in our constructor/destructor to make sure that it does not go away
+ */
+ _R<DisplayObject> owner;
+ uint8_t* surfaceBytes;
+ bool uploadNeeded;
+public:
/*
- A pointer to a memory buffer where cairo will draw
+ * @param o The DisplayObject that is being rendered. It is a reference to
+ * make sure the object survives until the end of the rendering
+ * @param d IDrawable to be rendered asynchronously. The pointer is now
+ * owned by this instance
+ */
+ AsyncDrawJob(IDrawable* d, _R<DisplayObject> o);
+ ~AsyncDrawJob();
+ //IThreadJob interface
+ void execute();
+ void threadAbort();
+ void jobFence();
+ //ITextureUploadable interface
+ void upload(uint8_t* data, uint32_t w, uint32_t h) const;
+ void sizeNeeded(uint32_t& w, uint32_t& h) const;
+ const TextureChunk& getTexture();
+ void uploadFence();
+};
+
+/**
+ The base class for render jobs based on cairo
+ Stores an internal copy of the data to be rendered
+*/
+class CairoRenderer: public IDrawable
+{
+protected:
+ /**
+ The whole transformation matrix that is applied to the rendered object
*/
- uint8_t* surfaceBytes;
+ MATRIX matrix;
/*
The scale to be applied in both the x and y axis.
Useful to adapt points defined in pixels and twips (1/20 of pixel)
*/
const float scaleFactor;
- bool uploadNeeded;
/*
* There are reports (http://lists.freedesktop.org/archives/cairo/2011-September/022247.html)
* that cairo is not threadsafe, and I have encountered some spurious crashes, too.
@@ -235,20 +265,12 @@ class CairoRenderer: public ITextureUploadable, public IThreadJob
static StaticMutex cairoMutex;
static cairo_matrix_t MATRIXToCairo(const MATRIX& matrix);
static void cairoClean(cairo_t* cr);
- cairo_surface_t* allocateSurface();
+ cairo_surface_t* allocateSurface(uint8_t*& buf);
virtual void executeDraw(cairo_t* cr)=0;
public:
- CairoRenderer(ASObject* _o, CachedSurface& _t, const MATRIX& _m,
- int32_t _x, int32_t _y, int32_t _w, int32_t _h, float _s, float _a);
- //ITextureUploadable interface
- void sizeNeeded(uint32_t& w, uint32_t& h) const;
- void upload(uint8_t* data, uint32_t w, uint32_t h) const;
- const TextureChunk& getTexture();
- void uploadFence();
- //IThreadJob interface
- void threadAbort();
- void jobFence();
- void execute();
+ CairoRenderer(const MATRIX& _m, int32_t _x, int32_t _y, int32_t _w, int32_t _h, float _s, float _a);
+ //IDrawable interface
+ uint8_t* getPixelBuffer();
/*
* Converts data (which is in RGB format) to the format internally used by cairo.
*/
@@ -286,9 +308,9 @@ class CairoTokenRenderer : public CairoRenderer
@param _s The scale factor to be applied in both the x and y axis
@param _a The alpha factor to be applied
*/
- CairoTokenRenderer(ASObject* _o, CachedSurface& _t, const std::vector<GeomToken>& _g, const MATRIX& _m,
+ CairoTokenRenderer(const std::vector<GeomToken>& _g, const MATRIX& _m,
int32_t _x, int32_t _y, int32_t _w, int32_t _h, float _s, float _a)
- : CairoRenderer(_o,_t,_m,_x,_y,_w,_h,_s,_a), tokens(_g) {}
+ : CairoRenderer(_m,_x,_y,_w,_h,_s,_a), tokens(_g) {}
/*
Hit testing helper. Uses cairo to find if a point in inside the shape
@@ -344,9 +366,9 @@ class CairoPangoRenderer : public CairoRenderer
TextData textData;
static void pangoLayoutFromData(PangoLayout* layout, const TextData& tData);
public:
- CairoPangoRenderer(ASObject* _o, CachedSurface& _t, const TextData& _textData, const MATRIX& _m,
+ CairoPangoRenderer(const TextData& _textData, const MATRIX& _m,
int32_t _x, int32_t _y, int32_t _w, int32_t _h, float _s, float _a)
- : CairoRenderer(_o,_t,_m,_x,_y,_w,_h,_s,_a), textData(_textData) {}
+ : CairoRenderer(_m,_x,_y,_w,_h,_s,_a), textData(_textData) {}
/**
Helper. Uses Pango to find the size of the textdata
@param _texttData The textData being tested
View
2 src/scripting/flash/display/DisplayObject.cpp
@@ -403,7 +403,7 @@ void DisplayObject::computeDeviceBoundsForRect(number_t xmin, number_t xmax, num
outHeight=ceil(maxy-miny);
}
-void DisplayObject::invalidate()
+IDrawable* DisplayObject::invalidate()
{
//Not supposed to be called
throw RunTimeException("DisplayObject::invalidate");
View
8 src/scripting/flash/display/DisplayObject.h
@@ -76,8 +76,6 @@ friend std::ostream& operator<<(std::ostream& s, const DisplayObject& r);
bool skipRender(bool maskEnabled) const;
float clippedAlpha() const;
bool visible;
- /* cachedSurface may only be read/written from within the render thread */
- CachedSurface cachedSurface;
void defaultRender(RenderContext& ctxt, bool maskEnabled) const;
DisplayObject(const DisplayObject& d);
@@ -104,6 +102,10 @@ friend std::ostream& operator<<(std::ostream& s, const DisplayObject& r);
UI16_SWF Ratio;
UI16_SWF ClipDepth;
CLIPACTIONS ClipActions;
+ /* cachedSurface may only be read/written from within the render thread
+ * It is the cached version of the object for fast draw on the Stage
+ */
+ CachedSurface cachedSurface;
_NR<DisplayObjectContainer> getParent() const { return parent; }
void setParent(_NR<DisplayObjectContainer> p);
/*
@@ -113,7 +115,7 @@ friend std::ostream& operator<<(std::ostream& s, const DisplayObject& r);
DisplayObject(Class_base* c);
void finalize();
MATRIX getMatrix() const;
- virtual void invalidate();
+ virtual IDrawable* invalidate();
virtual void requestInvalidation(InvalidateQueue* q);
MATRIX getConcatenatedMatrix() const;
void localToGlobal(number_t xin, number_t yin, number_t& xout, number_t& yout) const;
View
9 src/scripting/flash/display/TokenContainer.cpp
@@ -123,24 +123,23 @@ void TokenContainer::requestInvalidation(InvalidateQueue* q)
q->addToInvalidateQueue(_MR(owner));
}
-void TokenContainer::invalidate()
+IDrawable* TokenContainer::invalidate()
{
int32_t x,y;
uint32_t width,height;
number_t bxmin,bxmax,bymin,bymax;
if(boundsRect(bxmin,bxmax,bymin,bymax)==false)
{
//No contents, nothing to do
- return;
+ return NULL;
}
owner->computeDeviceBoundsForRect(bxmin,bxmax,bymin,bymax,x,y,width,height);
if(width==0 || height==0)
- return;
- CairoRenderer* r=new CairoTokenRenderer(owner, owner->cachedSurface, tokens,
+ return NULL;
+ return new CairoTokenRenderer(tokens,
owner->getConcatenatedMatrix(), x, y, width, height, scaling,
owner->getConcatenatedAlpha());
- getSys()->addJob(r);
}
bool TokenContainer::isOpaqueImpl(number_t x, number_t y) const
View
2 src/scripting/flash/display/TokenContainer.h
@@ -54,7 +54,7 @@ class TokenContainer
protected:
TokenContainer(DisplayObject* _o);
TokenContainer(DisplayObject* _o, const tokensVector& _tokens, float _scaling);
- void invalidate();
+ IDrawable* invalidate();
void requestInvalidation(InvalidateQueue* q);
bool boundsRect(number_t& xmin, number_t& xmax, number_t& ymin, number_t& ymax) const;
_NR<InteractiveObject> hitTestImpl(_NR<InteractiveObject> last, number_t x, number_t y, DisplayObject::HIT_TYPE type) const;
View
6 src/scripting/flash/display/flashdisplay.h
@@ -230,7 +230,7 @@ class Shape: public DisplayObject, public TokenContainer
ASFUNCTION(_getGraphics);
bool isOpaque(number_t x, number_t y) const;
void requestInvalidation(InvalidateQueue* q) { TokenContainer::requestInvalidation(q); }
- void invalidate() { TokenContainer::invalidate(); }
+ IDrawable* invalidate() { return TokenContainer::invalidate(); }
};
class MorphShape: public DisplayObject
@@ -363,7 +363,7 @@ friend class DisplayObject;
{
return 0;
}
- void invalidate() { TokenContainer::invalidate(); }
+ IDrawable* invalidate() { return TokenContainer::invalidate(); }
void requestInvalidation(InvalidateQueue* q);
bool isOpaque(number_t x, number_t y) const;
};
@@ -653,7 +653,7 @@ friend class CairoTokenRenderer;
_NR<InteractiveObject> hitTestImpl(_NR<InteractiveObject> last, number_t x, number_t y, DisplayObject::HIT_TYPE type);
virtual IntSize getBitmapSize() const;
void requestInvalidation(InvalidateQueue* q) { TokenContainer::requestInvalidation(q); }
- void invalidate() { TokenContainer::invalidate(); }
+ IDrawable* invalidate() { return TokenContainer::invalidate(); }
};
class AVM1Movie: public DisplayObject
View
9 src/scripting/flash/text/flashtext.cpp
@@ -277,20 +277,20 @@ void TextField::requestInvalidation(InvalidateQueue* q)
q->addToInvalidateQueue(_MR(this));
}
-void TextField::invalidate()
+IDrawable* TextField::invalidate()
{
int32_t x,y;
uint32_t width,height;
number_t bxmin,bxmax,bymin,bymax;
if(boundsRect(bxmin,bxmax,bymin,bymax)==false)
{
//No contents, nothing to do
- return;
+ return NULL;
}
computeDeviceBoundsForRect(bxmin,bxmax,bymin,bymax,x,y,width,height);
if(width==0 || height==0)
- return;
+ return NULL;
MATRIX mat = getConcatenatedMatrix();
if(mat.ScaleX != 1 || mat.ScaleY != 1)
LOG(LOG_NOT_IMPLEMENTED, "TextField when scaled is not correctly implemented");
@@ -299,10 +299,9 @@ void TextField::invalidate()
Width changes do not change the font size, and do nothing when autosize is on and wordwrap off.
Currently, the TextField is stretched in case of scaling.
*/
- CairoPangoRenderer* r=new CairoPangoRenderer(this, cachedSurface, *this,
+ return new CairoPangoRenderer(*this,
getConcatenatedMatrix(), x, y, width, height, 1.0f,
getConcatenatedAlpha());
- getSys()->addJob(r);
}
void TextField::renderImpl(RenderContext& ctxt, bool maskEnabled, number_t t1, number_t t2, number_t t3, number_t t4) const
View
4 src/scripting/flash/text/flashtext.h
@@ -42,7 +42,7 @@ class TextField: public InteractiveObject, public TextData
_NR<InteractiveObject> hitTestImpl(_NR<InteractiveObject> last, number_t x, number_t y, HIT_TYPE type);
void renderImpl(RenderContext& ctxt, bool maskEnabled, number_t t1, number_t t2, number_t t3, number_t t4) const;
bool boundsRect(number_t& xmin, number_t& xmax, number_t& ymin, number_t& ymax) const;
- void invalidate();
+ IDrawable* invalidate();
void requestInvalidation(InvalidateQueue* q);
void updateText(const tiny_string& new_text);
//Computes and changes (text)width and (text)height using Pango
@@ -130,7 +130,7 @@ class StaticText: public DisplayObject, public TokenContainer
DisplayObject(c),TokenContainer(this, tokens, 1.0f/1024.0f/20.0f/20.0f) {};
static void sinit(Class_base* c);
void requestInvalidation(InvalidateQueue* q) { TokenContainer::requestInvalidation(q); }
- void invalidate() { TokenContainer::invalidate(); }
+ IDrawable* invalidate(){ return TokenContainer::invalidate(); }
};
};
View
8 src/swf.cpp
@@ -950,7 +950,13 @@ void SystemState::flushInvalidationQueue()
_NR<DisplayObject> cur=invalidateQueueHead;
while(!cur.isNull())
{
- cur->invalidate();
+ IDrawable* d=cur->invalidate();
+ //Check if the drawable is valid and forge a new job to
+ //render it and upload it to GPU
+ if(d)
+ {
+ addJob(new AsyncDrawJob(d,cur));
+ }
_NR<DisplayObject> next=cur->invalidateQueueNext;
cur->invalidateQueueNext=NullRef;
cur=next;

0 comments on commit bfa53b3

Please sign in to comment.
Something went wrong with that request. Please try again.