Files
lime/project/src/common/Hardware.cpp
Sean Heber 80bfc72e73 Performance enhancement
Improved tile performance by using reserve() to alloc the size of the
arrays internally all at once instead of on-demand since this is way
faster. In my test case this made a significant impact. Also attempting
to get the compiler to avoid some other more hidden copying.
2014-01-23 21:14:39 -06:00

1438 lines
44 KiB
C++

#include <Graphics.h>
#include "renderer/common/Surface.h"
#include "renderer/common/SimpleSurface.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
namespace lime
{
class HardwareBuilder
{
public:
HardwareBuilder(const GraphicsJob &inJob,const GraphicsPath &inPath,HardwareData &ioData,
HardwareContext &inHardware)
{
mTexture = 0;
bool tile_mode = false;
mElement.mColour = 0xffffffff;
mSolidMode = false;
mPerpLen = 0.5;
bool tessellate_lines = true;
if (inJob.mIsTileJob)
{
mElement.mBitmapRepeat = true;
mElement.mBitmapSmooth = false;
mElement.mPrimType = ptTriangles;
mElement.mScaleMode = ssmNormal;
mElement.mWidth = -1;
GraphicsBitmapFill *bmp = inJob.mFill->AsBitmapFill();
mSurface = bmp->bitmapData->IncRef();
mTexture = mSurface->GetOrCreateTexture(inHardware);
mElement.mBitmapRepeat = false;
mElement.mBitmapSmooth = bmp->smooth;
tile_mode = true;
}
else if (inJob.mFill)
{
mSolidMode = true;
mElement.mPrimType = inJob.mTriangles ? ptTriangles : ptTriangleFan;
mElement.mScaleMode = ssmNormal;
mElement.mWidth = -1;
if (!SetFill(inJob.mFill,inHardware))
return;
}
else if (tessellate_lines && inJob.mStroke->scaleMode==ssmNormal)
{
// ptTriangleStrip?
mElement.mPrimType = ptTriangles;
GraphicsStroke *stroke = inJob.mStroke;
if (!SetFill(stroke->fill,inHardware))
return;
mPerpLen = stroke->thickness * 0.5;
if (mPerpLen<=0.0)
mPerpLen = 0.5;
else if (mPerpLen<0.5)
{
mPerpLen = 0.5;
}
mCaps = stroke->caps;
mJoints = stroke->joints;
if (mJoints==sjMiter)
mMiterLimit = stroke->miterLimit*mPerpLen;
}
else
{
tessellate_lines = false;
mElement.mPrimType = ptLineStrip;
GraphicsStroke *stroke = inJob.mStroke;
mElement.mScaleMode = stroke->scaleMode;
mElement.mWidth = stroke->thickness;
SetFill(stroke->fill,inHardware);
}
mElement.mFirst = 0;
mElement.mCount = 0;
if (inJob.mTriangles)
{
bool has_colour = inJob.mTriangles->mColours.size()>0;
unsigned int flags = 0;
if (inJob.mTriangles->mType == vtVertexUVT)
flags |= HardwareArrays::PERSPECTIVE;
if (inJob.mTriangles->mBlendMode==bmAdd)
flags |= HardwareArrays::BM_ADD;
if (inJob.mTriangles->mBlendMode==bmMultiply)
flags |= HardwareArrays::BM_MULTIPLY;
if (inJob.mTriangles->mBlendMode==bmScreen)
flags |= HardwareArrays::BM_SCREEN;
if (mSurface && (mSurface->GetAlphaMode () == amPremultiplied))
flags |= HardwareArrays::AM_PREMULTIPLIED;
mArrays = &ioData.GetArrays(mSurface,has_colour,flags);
AddTriangles(inJob.mTriangles);
if (inJob.mStroke && inJob.mStroke->fill)
{
mElement.mPrimType = ptLines;
GraphicsStroke *stroke = inJob.mStroke;
if (!SetFill(stroke->fill,inHardware))
return;
mArrays = &ioData.GetArrays(mSurface,false,mGradFlags);
mElement.mFirst = 0;
mElement.mCount = 0;
mElement.mScaleMode = ssmNormal;
mElement.mWidth = stroke->thickness;
AddTriangleLines(inJob.mTriangles);
}
}
else if (tile_mode)
{
bool has_colour = false;
BlendMode bm = bmNormal;
GetTileFlags(&inPath.commands[inJob.mCommand0], inJob.mCommandCount, has_colour, bm);
mArrays = &ioData.GetArrays(mSurface,has_colour,(bm == bmAdd) ? HardwareArrays::BM_ADD : (bm == bmMultiply) ? HardwareArrays::BM_MULTIPLY : (bm == bmScreen) ? HardwareArrays::BM_SCREEN : 0);
AddTiles(&inPath.commands[inJob.mCommand0], inJob.mCommandCount, &inPath.data[inJob.mData0]);
}
else if (tessellate_lines && !mSolidMode)
{
mArrays = &ioData.GetArrays(mSurface,false,(mSurface && (mSurface->GetAlphaMode() == amPremultiplied)) ? mGradFlags | HardwareArrays::AM_PREMULTIPLIED : mGradFlags);
AddLineTriangles(&inPath.commands[inJob.mCommand0], inJob.mCommandCount, &inPath.data[inJob.mData0]);
}
else
{
mArrays = &ioData.GetArrays(mSurface,false,(mSurface && (mSurface->GetAlphaMode() == amPremultiplied)) ? mGradFlags | HardwareArrays::AM_PREMULTIPLIED : mGradFlags);
AddObject(&inPath.commands[inJob.mCommand0], inJob.mCommandCount, &inPath.data[inJob.mData0]);
}
}
bool SetFill(IGraphicsFill *inFill,HardwareContext &inHardware)
{
mSurface = 0;
mElement.mBitmapRepeat = true;
mElement.mBitmapSmooth = false;
mGradFlags = 0;
GraphicsSolidFill *solid = inFill->AsSolidFill();
if (solid)
{
//if (solid -> mRGB.a == 0)
//return false;
mElement.mColour = solid->mRGB.ToInt();
}
else
{
GraphicsGradientFill *grad = inFill->AsGradientFill();
if (grad)
{
mGradReflect = grad->spreadMethod == smReflect;
int w = mGradReflect ? 512 : 256;
mSurface = new SimpleSurface(w,1,pfARGB);
mSurface->IncRef();
grad->FillArray( (ARGB *)mSurface->GetBase(), false);
mElement.mBitmapRepeat = grad->spreadMethod!=smPad;
mElement.mBitmapSmooth = true;
mTextureMapper = grad->matrix.Inverse();
if (!grad->isLinear)
{
mGradFlags |= HardwareArrays::RADIAL;
if (grad->focalPointRatio!=0)
{
int r = fabs(grad->focalPointRatio)*256.0;
if (r>255) r = 255;
mGradFlags |= (r << 8);
if (grad->focalPointRatio<0)
mGradFlags |= HardwareArrays::FOCAL_SIGN;
}
}
//return true;
}
else
{
GraphicsBitmapFill *bmp = inFill->AsBitmapFill();
mTextureMapper = bmp->matrix.Inverse();
mSurface = bmp->bitmapData->IncRef();
mTexture = mSurface->GetOrCreateTexture(inHardware);
mElement.mBitmapRepeat = bmp->repeat;
mElement.mBitmapSmooth = bmp->smooth;
}
}
//return false;
return true;
}
~HardwareBuilder()
{
if (mSurface)
mSurface->DecRef();
}
void CalcTexCoords()
{
Vertices &vertices = mArrays->mVertices;
Vertices &tex = mArrays->mTexCoords;
int v0 = vertices.size();
int t0 = tex.size();
tex.resize(v0);
bool radial = mGradFlags & HardwareArrays::RADIAL;
for(int i=t0;i<v0;i++)
{
UserPoint p = mTextureMapper.Apply(vertices[i].x,vertices[i].y);
if (mTexture)
{
p = mTexture->PixelToTex(p);
}
else
{
// The point will be in the (-819.2 ... 819.2) range...
if (radial)
{
p.x = (p.x +819.2) / 819.2 - 1.0;
p.y = (p.y +819.2) / 819.2 - 1.0;
if (mGradReflect)
{
p.x *= 0.5;
p.y *= 0.5;
}
}
else
{
p.x = (p.x +819.2) / 1638.4;
p.y = 0;
if (mGradReflect)
p.x *= 0.5;
}
}
tex[i] = p;
}
}
void AddTriangles(GraphicsTrianglePath *inPath)
{
Vertices &vertices = mArrays->mVertices;
Colours &colours = mArrays->mColours;
Vertices &tex = mArrays->mTexCoords;
DrawElements &elements = mArrays->mElements;
bool persp = inPath->mType == vtVertexUVT;
mElement.mFirst = vertices.size() / (persp?2:1);
mElement.mPrimType = ptTriangles;
//Just overwriting viewport
mArrays->mViewport = inPath->mViewport;
const float *t = &inPath->mUVT[0];
for(int v=0;v<inPath->mVertices.size();v++)
{
if (!persp)
{
vertices.push_back(inPath->mVertices[v]);
if(inPath->mColours.size()>0)
{
colours.push_back(inPath->mColours[v]);/*mwb*/
}
}
if (inPath->mType != vtVertex)
{
tex.push_back( mTexture->TexToPaddedTex( UserPoint(t[0],t[1]) ) );
t+=2;
if (persp)
{
float w= 1.0/ *t++;
vertices.push_back(inPath->mVertices[v]*w);
vertices.push_back( UserPoint(0,w) );
}
}
}
mElement.mCount = (vertices.size() - mElement.mFirst)/(persp ? 2:1);
elements.push_back(mElement);
}
void AddTriangleLines(GraphicsTrianglePath *inPath)
{
Vertices &vertices = mArrays->mVertices;
DrawElements &elements = mArrays->mElements;
mElement.mFirst = vertices.size();
mElement.mPrimType = ptLines;
//Just overwriting blend mode and viewport
mArrays->mViewport = inPath->mViewport;
int tri_count = inPath->mVertices.size()/3;
UserPoint *tri = &inPath->mVertices[0];
for(int v=0;v<tri_count;v++)
{
vertices.push_back(tri[0]);
vertices.push_back(tri[1]);
vertices.push_back(tri[1]);
vertices.push_back(tri[2]);
vertices.push_back(tri[2]);
vertices.push_back(tri[0]);
tri+=3;
}
mElement.mCount = (vertices.size() - mElement.mFirst);
if (mSurface)
CalcTexCoords();
elements.push_back(mElement);
}
void GetTileFlags(const uint8* inCommands, int inCount,bool &outColour, BlendMode &outBlendMode)
{
for(int i=0;i<inCount;i++)
if (inCommands[i] == pcTileCol || inCommands[i]==pcTileTransCol)
outColour = true;
else if (inCommands[i] == pcBlendModeAdd)
outBlendMode = bmAdd;
else if (inCommands[i] == pcBlendModeMultiply)
outBlendMode = bmMultiply;
else if (inCommands[i] == pcBlendModeScreen)
outBlendMode = bmScreen;
}
void AddTiles(const uint8* inCommands, int inCount, const float *inData)
{
Vertices &vertices = mArrays->mVertices;
Vertices &tex = mArrays->mTexCoords;
Colours &colours = mArrays->mColours;
const UserPoint *point = (const UserPoint *)inData;
mElement.mFirst = vertices.size();
vertices.reserve(vertices.size() + (6 * inCount));
tex.reserve(tex.size() + (6 * inCount));
colours.reserve(colours.size() + (6 * inCount));
for(int i=0;i<inCount;i++)
{
switch(inCommands[i])
{
case pcBeginAt: case pcMoveTo: case pcLineTo:
point++;
break;
case pcCurveTo:
point+=2;
break;
case pcTile:
case pcTileTrans:
case pcTileCol:
case pcTileTransCol:
{
const UserPoint &pos = point[0];
const UserPoint &tex_pos = point[1];
const UserPoint &size = point[2];
point += 3;
if (inCommands[i]&pcTile_Trans_Bit)
{
const UserPoint &trans_x = *point++;
const UserPoint &trans_y = *point++;
const UserPoint p1(pos.x + size.x*trans_x.x,
pos.y + size.x*trans_x.y);
const UserPoint p2(pos.x + size.x*trans_x.x + size.y*trans_y.x,
pos.y + size.x*trans_x.y + size.y*trans_y.y);
const UserPoint p3(pos.x + size.y*trans_y.x,
pos.y + size.y*trans_y.y);
vertices.push_back( pos );
vertices.push_back( p1 );
vertices.push_back( p2 );
vertices.push_back( pos );
vertices.push_back( p2 );
vertices.push_back( p3 );
}
else
{
vertices.push_back( pos );
vertices.push_back( UserPoint(pos.x+size.x,pos.y) );
vertices.push_back( UserPoint(pos.x+size.x,pos.y+size.y) );
vertices.push_back( pos );
vertices.push_back( UserPoint(pos.x+size.x,pos.y+size.y) );
vertices.push_back( UserPoint(pos.x,pos.y+size.y) );
}
const UserPoint tp1 = mTexture->PixelToTex(tex_pos);
const UserPoint tp2 = mTexture->PixelToTex(UserPoint(tex_pos.x+size.x,tex_pos.y+size.y));
tex.push_back( tp1 );
tex.push_back( mTexture->PixelToTex(UserPoint(tex_pos.x+size.x,tex_pos.y)) );
tex.push_back( tp2 );
tex.push_back( tp1 );
tex.push_back( tp2 );
tex.push_back( mTexture->PixelToTex(UserPoint(tex_pos.x,tex_pos.y+size.y)) );
if (inCommands[i]&pcTile_Col_Bit)
{
const UserPoint &rg = *point++;
const UserPoint &ba = *point++;
#ifdef BLACKBERRY
const uint32 col = ((int)(rg.x*255)) |
(((int)(rg.y*255))<<8) |
(((int)(ba.x*255))<<16) |
(((int)(ba.y*255))<<24);
#else
const uint32 col = ((rg.x<0 ? 0 : rg.x>1?255 : (int)(rg.x*255))) |
((rg.y<0 ? 0 : rg.y>1?255 : (int)(rg.y*255))<<8) |
((ba.x<0 ? 0 : ba.x>1?255 : (int)(ba.x*255))<<16) |
((ba.y<0 ? 0 : ba.y>1?255 : (int)(ba.y*255))<<24);
#endif
colours.push_back( col );
colours.push_back( col );
colours.push_back( col );
colours.push_back( col );
colours.push_back( col );
colours.push_back( col );
}
}
}
}
mElement.mCount = vertices.size() - mElement.mFirst;
if (mElement.mCount>0)
mArrays->mElements.push_back(mElement);
}
#define FLAT 0.000001
void AddPolygon(Vertices &inOutline,const QuickVec<int> &inSubPolys)
{
if (mSolidMode && inOutline.size()<3)
return;
Vertices &vertices = mArrays->mVertices;
mElement.mFirst = vertices.size();
bool isConvex = inSubPolys.size()==1;
if (mSolidMode)
{
if (isConvex)
{
UserPoint base = inOutline[0];
int last = inOutline.size()-2;
int i = 0;
bool positive = true;
for( ;i<last;i++)
{
UserPoint v0 = inOutline[i+1]-base;
UserPoint v1 = inOutline[i+2]-base;
double diff = v0.Cross(v1);
if (fabs(diff)>FLAT)
{
positive = diff > 0;
break;
}
}
for(++i;i<last;i++)
{
UserPoint v0 = inOutline[i+1]-base;
UserPoint v1 = inOutline[i+2]-base;
double diff = v0.Cross(v1);
if (fabs(diff)>FLAT && (diff>0)!=positive)
{
isConvex = false;
break;
}
}
}
if (!isConvex)
ConvertOutlineToTriangles(inOutline,inSubPolys);
}
mElement.mCount = inOutline.size();
vertices.resize(mElement.mFirst + mElement.mCount);
for(int i=0;i<inOutline.size();i++)
vertices[i+mElement.mFirst] = inOutline[i];
if (mSurface)
CalcTexCoords();
mArrays->mElements.push_back(mElement);
if (!isConvex)
mArrays->mElements.last().mPrimType = ptTriangles;
}
void AddObject(const uint8* inCommands, int inCount, const float *inData)
{
UserPoint *point = (UserPoint *)inData;
UserPoint last_move;
UserPoint last_point;
int points = 0;
QuickVec<int> sub_poly_start;
Vertices outline;
for(int i=0;i<inCount;i++)
{
switch(inCommands[i])
{
case pcBeginAt:
if (points>0)
{
point++;
continue;
}
// fallthrough
case pcMoveTo:
if (points>1)
{
// Move in the middle of a solid polygon - treat like a line...
if (mSolidMode)
{
sub_poly_start.push_back(outline.size());
outline.push_back(*point);
last_point = *point++;
points++;
break;
}
sub_poly_start.push_back(outline.size());
AddPolygon(outline,sub_poly_start);
}
else if (points==1 && last_move==*point)
{
point++;
continue;
}
outline.resize(0);
sub_poly_start.resize(0);
points = 1;
last_point = *point++;
last_move = last_point;
if (outline.empty()||outline.last()!=last_move)
outline.push_back(last_move);
break;
case pcLineTo:
if (points>0)
{
if (outline.empty() || outline.last()!=*point)
outline.push_back(*point);
last_point = *point++;
points++;
}
break;
case pcCurveTo:
{
double len = ((last_point-point[0]).Norm() + (point[1]-point[0]).Norm()) * 0.25;
if (len==0)
break;
int steps = (int)len;
if (steps<3) steps = 3;
if (steps>100) steps = 100;
double step = 1.0/(steps+1);
double t = 0;
for(int s=0;s<steps;s++)
{
t+=step;
double t_ = 1.0-t;
UserPoint p = last_point * (t_*t_) + point[0] * (2.0*t*t_) + point[1] * (t*t);
if (outline.last()!=p)
outline.push_back(p);
}
last_point = point[1];
if (outline.last()!=last_point)
outline.push_back(last_point);
point += 2;
points++;
}
break;
case pcTile:
case pcTileTrans:
case pcTileCol:
case pcTileTransCol:
point += 3;
if (inCommands[i]&pcTile_Trans_Bit)
point+=2;
if (inCommands[i]&pcTile_Col_Bit)
point+=2;
}
}
if (!outline.empty())
{
int n = outline.size();
if (sub_poly_start.empty() || sub_poly_start.last()!=n)
sub_poly_start.push_back(n);
AddPolygon(outline,sub_poly_start);
}
}
struct Segment
{
inline Segment() { }
inline Segment(const UserPoint &inP) : p(inP), curve(inP) { }
inline Segment(const UserPoint &inP,const UserPoint &inCurve) : p(inP), curve(inCurve) { }
UserPoint getDir0(const UserPoint &inP0) const { return curve-inP0; }
UserPoint getDir1(const UserPoint &inP0) const { return isCurve() ? p-curve : p-inP0; }
UserPoint getDirAverage(const UserPoint &inP0) const { return p-inP0; }
inline bool isCurve() const { return p!=curve; }
UserPoint p;
UserPoint curve;
};
void AddArc(Vertices &vertices,UserPoint inP, double angle, UserPoint inVx, UserPoint inVy, UserPoint p0, UserPoint p1)
{
int steps = 1 + angle*8;
double d_theta = angle / (steps+1);
double theta = d_theta;
for(int i=0;i<steps;i++)
{
UserPoint x = inP + inVx*cos(theta) + inVy*sin(theta);
theta += d_theta;
vertices.push_back(inP);
vertices.push_back(p0);
vertices.push_back(x);
p0 = x;
}
vertices.push_back(inP);
vertices.push_back(p0);
vertices.push_back(p1);
}
void AddMiter(Vertices &vertices,UserPoint inP, UserPoint p0, UserPoint p1, double inAlpha,
UserPoint dir1, UserPoint dir2)
{
if (inAlpha>mMiterLimit)
{
UserPoint corner0 = p0+dir1*mMiterLimit;
UserPoint corner1 = p1-dir2*mMiterLimit;
vertices.push_back(inP);
vertices.push_back(p0);
vertices.push_back(corner0);
vertices.push_back(inP);
vertices.push_back(corner0);
vertices.push_back(corner1);
vertices.push_back(inP);
vertices.push_back(corner1);
vertices.push_back(p1);
}
else
{
UserPoint corner = p0+dir1*inAlpha;
vertices.push_back(inP);
vertices.push_back(p0);
vertices.push_back(corner);
vertices.push_back(inP);
vertices.push_back(corner);
vertices.push_back(p1);
}
}
void AddCurveSegment(Vertices &vertices,UserPoint inP0,UserPoint inP1,UserPoint inP2, UserPoint perp0, UserPoint perp1,
UserPoint p0_left, UserPoint p0_right, UserPoint p1_left, UserPoint p1_right)
{
double len = (inP0 - inP1).Norm() + (inP2 - inP1).Norm();
int steps = (int)len*0.1;
if (len < 50 && steps < 10) steps = 50;
else if (steps < 1) steps = 1;
else if (steps > 100) steps = 100;
double step = 1.0 / (steps);
double t = 0;
UserPoint v0 = p0_right - p0_left;
UserPoint v1 = p1_right - p1_left;
UserPoint last_p_left = inP0 - perp0;
UserPoint last_p_right = inP0 + perp0;
// Clip against end ...
if ( v0.Cross(last_p_left-p0_left)>0 )
last_p_left = p0_left;
if ( v0.Cross(last_p_right-p0_left)>0 )
last_p_right = p0_right;
// TODO - against other end?
for (int s=1; s <= steps; s++)
{
t += step;
double t_ = 1.0 - t;
UserPoint p = inP0 * (t_ * t_) + inP1 * (2.0 * t * t_) + inP2 * (t * t);
UserPoint dir = (inP0 * -t_ + inP1 * (1.0 - 2.0 * t) + inP2 * t);
UserPoint perp = dir.Perp(mPerpLen);
if (s==step)
{
p = inP2;
perp = perp1;
}
UserPoint p_right = p + perp;
UserPoint p_left = p - perp;
if (v0.Cross(dir) < 0 ) // if pointing in same direction (left-handed)
{
if ( v0.Cross(p_left-p0_left)>0)
p_left = p0_left;
if ( v0.Cross(p_right-p0_left)>0)
p_right = p0_right;
}
if (v1.Cross(dir) < 0 ) // if pointing in same direction (left-handed)
{
if ( v1.Cross(p_left-p1_left)<0)
p_left = p1_left;
if ( v1.Cross(p_right-p1_left)<0)
p_right = p1_right;
}
if (p_left==last_p_left)
{
if (p_right!=last_p_right)
{
vertices.push_back(p_left);
vertices.push_back(last_p_right);
vertices.push_back(p_right);
}
}
else if (p_right==last_p_right)
{
if (p_left!=last_p_left)
{
vertices.push_back(last_p_left);
vertices.push_back(p_right);
vertices.push_back(p_left);
}
}
else
{
vertices.push_back(last_p_left);
vertices.push_back(last_p_right);
vertices.push_back(p_left);
vertices.push_back(p_left);
vertices.push_back(last_p_right);
vertices.push_back(p_right);
}
last_p_left = p_left;
last_p_right = p_right;
}
}
void EndCap(Vertices &vertices,UserPoint p0, UserPoint perp)
{
UserPoint back(-perp.y, perp.x);
if (mCaps==scSquare)
{
vertices.push_back(p0+perp);
vertices.push_back(p0+perp + back);
vertices.push_back(p0-perp);
vertices.push_back(p0+perp + back);
vertices.push_back(p0-perp + back);
vertices.push_back(p0-perp);
}
else
{
int n = std::max(2,(int)(mPerpLen * 4));
double dtheta = M_PI / n;
double theta = 0.0;
UserPoint prev(perp);
for(int i=1;i<n;i++)
{
UserPoint p = perp*cos(theta) + back*sin(theta);
vertices.push_back(p0);
vertices.push_back(p0+prev);
vertices.push_back(p0+p);
prev = p;
theta += dtheta;
}
vertices.push_back(p0);
vertices.push_back(p0+prev);
vertices.push_back(p0-perp);
}
}
void AddStrip(const QuickVec<Segment> &inPath, bool inLoop)
{
Vertices &vertices = mArrays->mVertices;
mElement.mFirst = vertices.size();
// Endcap 0 ...
if (!inLoop && (mCaps==scSquare || mCaps==scRound))
{
UserPoint p0 = inPath[0].p;
EndCap(vertices, p0, inPath[1].getDir0(p0).Perp(mPerpLen));
}
double prev_alpha = 0;
for(int i=1;i<inPath.size();i++)
{
const Segment &seg = inPath[i];
UserPoint p0 = inPath[i-1].p;
UserPoint p = seg.p;
UserPoint dir0 = seg.getDir0(p0).Normalized();
UserPoint dir1 = seg.getDir1(p0).Normalized();
UserPoint next_dir;
if (i+1<inPath.size())
next_dir = inPath[i+1].getDir0(p).Normalized();
else if (!inLoop)
{
next_dir = dir1;
//printf("Dup next_dir\n");
}
else
next_dir = inPath[1].getDir0(p).Normalized();
/*
---
---
---
- B- next segment
Z \ ...
| \ ...
D_____\ ...____
| p C ---.
| .\ | ---
| . \ -Y-
| . A-- |
| . |
| . | p = end segment
| . |
| . |
| . |
A = p + next_perp
B = p - next_perp
C = p + perp1
D = p - perp1
Y = A + alpha*next_dir
= C - alpha*dir1
= p + next_perp + alpha*next_dir
= p + perp1 - alpha*dir1
-> next_perp-perp1 = alpha*(dir1+next_dir)
-> alpha = prep1-next_perp in either x or y direction...
---------------
dir1+next_dir
Make the split such that DpY and down belongs to this segment - first add this DpY triangle, then
the remainder of the segment happens below the D-Y line.
On one side (right hand, drawn here) the segments will overlap - on the other side, there
will be a join. Which side this will be depends on the sign of alpha. When reversed, the
roles of Y and Z will change.
BpY is for this upper segment - but there is an equivalent B'p0Y' for this segment.
First we add B'p-Y' triangle, and work from B'Y' line
*/
UserPoint perp0(-dir0.y*mPerpLen, dir0.x*mPerpLen);
UserPoint perp1(-dir1.y*mPerpLen, dir1.x*mPerpLen);
UserPoint next_perp(-next_dir.y*mPerpLen, next_dir.x*mPerpLen);
double denom_x = dir1.x+next_dir.x;
double denom_y = dir1.y+next_dir.y;
double alpha=0;
// Choose the better-conditioned axis
if (fabs(denom_x)>fabs(denom_y))
alpha = denom_x==0 ? 0 : (perp1.x-next_perp.x)/denom_x;
else
alpha = denom_y==0 ? 0 : (perp1.y-next_perp.y)/denom_y;
UserPoint p1_left = p-perp1;
UserPoint p1_right = p+perp1;
// This could start getting dodgy when the line doubles-back
double max_alpha = std::max( (p0-p).Norm()*0.5, mPerpLen );
if (fabs(alpha)>max_alpha)
{
// draw overlapped
}
else if (alpha>0)
{
p1_right -= dir1 * alpha;
vertices.push_back(p1_left);
vertices.push_back(p);
vertices.push_back(p1_right);
}
else if (alpha<0)
{
p1_left += dir1 * alpha;
vertices.push_back(p1_left);
vertices.push_back(p);
vertices.push_back(p1_right);
}
UserPoint p0_left = p0-perp0;
UserPoint p0_right = p0+perp0;
if (i==1 && inLoop)
{
UserPoint prev_dir = inPath[inPath.size()-1].getDir1(inPath[inPath.size()-2].p).Normalized();
UserPoint prev_perp(-prev_dir.y*mPerpLen, prev_dir.x*mPerpLen);
double denom_x = prev_dir.x+dir0.x;
double denom_y = prev_dir.y+dir0.y;
// Choose the better-conditioned axis
if (fabs(denom_x)>fabs(denom_y))
prev_alpha = denom_x==0 ? 0 : (prev_perp.x-perp0.x)/denom_x;
else
prev_alpha = denom_y==0 ? 0 : (prev_perp.y-perp0.y)/denom_y;
}
if (fabs(prev_alpha)>max_alpha)
{
// do nothing
}
else if (prev_alpha>0)
{
p0_right += dir0 * std::min(prev_alpha,max_alpha);
vertices.push_back(p0_left);
vertices.push_back(p0);
vertices.push_back(p0_right);
}
else if (prev_alpha<0)
{
p0_left -= dir0 * std::max(prev_alpha,-max_alpha);
vertices.push_back(p0_left);
vertices.push_back(p0);
vertices.push_back(p0_right);
}
if (alpha)
switch(mPerpLen<1 ? sjBevel : mJoints)
{
case sjRound:
{
double angle = acos(dir1.Dot(next_dir));
if (angle<0) angle += M_PI;
if (alpha>0) // left
AddArc(vertices, p, angle, -perp1, dir1*mPerpLen, p1_left, p-next_perp );
else // right
AddArc(vertices, p, angle, perp1, dir1*mPerpLen, p1_right, p+next_perp );
}
break;
case sjMiter:
if (alpha>0) // left
AddMiter(vertices, p, p-perp1, p-next_perp, alpha, dir1, next_dir);
else // Right
AddMiter(vertices, p, p+perp1, p+next_perp, -alpha, dir1, next_dir);
break;
case sjBevel:
if (alpha>0) // left
{
vertices.push_back(p1_left);
vertices.push_back(p);
vertices.push_back(p-next_perp);
}
else
{
vertices.push_back(p1_right);
vertices.push_back(p);
vertices.push_back(p+next_perp);
}
break;
}
if (seg.isCurve())
{
AddCurveSegment(vertices,p0,seg.curve,seg.p, perp0, perp1, p0_left, p0_right, p1_left, p1_right);
}
else
{
vertices.push_back(p0_left);
vertices.push_back(p0_right);
vertices.push_back(p1_right);
vertices.push_back(p0_left);
vertices.push_back(p1_right);
vertices.push_back(p1_left);
}
// Endcap end ...
if (!inLoop && (i+1==inPath.size()) && (mCaps==scSquare || mCaps==scRound))
EndCap(vertices, p, dir1.Perp(-mPerpLen));
prev_alpha = alpha;
}
mElement.mCount = vertices.size()-mElement.mFirst;
if (mSurface)
CalcTexCoords();
mArrays->mElements.push_back(mElement);
}
void AddLineTriangles(const uint8* inCommands, int inCount, const float *inData)
{
UserPoint *point = (UserPoint *)inData;
// It is a loop if the path has no breaks, it has more than 2 points
// and it finishes where it starts...
UserPoint first;
UserPoint prev;
QuickVec<Segment> strip;
for(int i=0;i<inCount;i++)
{
switch(inCommands[i])
{
case pcWideMoveTo:
point++;
case pcBeginAt:
case pcMoveTo:
if (strip.size()==1 && prev==*point)
{
point++;
continue;
}
if (strip.size()>1)
AddStrip(strip,false);
strip.resize(0);
strip.push_back(Segment(*point));
prev = *point;
first = *point++;
break;
case pcWideLineTo:
point++;
case pcLineTo:
{
if (strip.size()>0 && *point==prev)
{
point++;
continue;
}
strip.push_back(Segment(*point));
// Implicit loop closing...
if (strip.size()>2 && *point==first)
{
AddStrip(strip,true);
strip.resize(0);
first = *point;
}
prev = *point;
point++;
}
break;
case pcCurveTo:
{
if (strip.size()>0 && *point==prev && point[1]==prev)
{
point+=2;
continue;
}
strip.push_back(Segment(point[1],point[0]));
// Implicit loop closing...
if (strip.size()>2 && point[1]==first)
{
AddStrip(strip,true);
strip.resize(0);
first = point[1];
}
prev = point[1];
point +=2;
}
break;
case pcTile: point+=3; break;
case pcTileTrans: point+=4; break;
case pcTileCol: point+=5; break;
case pcTileTransCol: point+=6; break;
}
}
if (strip.size()>0)
AddStrip(strip,false);
}
HardwareArrays *mArrays;
Surface *mSurface;
DrawElement mElement;
Texture *mTexture;
bool mGradReflect;
unsigned int mGradFlags;
bool mSolidMode;
double mMiterLimit;
double mPerpLen;
Matrix mTextureMapper;
StrokeCaps mCaps;
StrokeJoints mJoints;
};
void CreatePointJob(const GraphicsJob &inJob,const GraphicsPath &inPath,HardwareData &ioData,
HardwareContext &inHardware)
{
DrawElement elem;
elem.mColour = 0xffffffff;
GraphicsSolidFill *fill = inJob.mFill ? inJob.mFill->AsSolidFill() : 0;
if (fill)
elem.mColour = fill->mRGB.ToInt();
GraphicsStroke *stroke = inJob.mStroke;
if (stroke)
{
elem.mScaleMode = stroke->scaleMode;
elem.mWidth = stroke->thickness;
}
else
{
elem.mScaleMode = ssmNormal;
elem.mWidth = -1;
}
elem.mPrimType = ptPoints;
elem.mCount = inJob.mDataCount / (fill ? 2 : 3);
HardwareArrays *arrays = &ioData.GetArrays(0,fill==0, /* TODO: bm add ? */ 0);
Vertices &vertices = arrays->mVertices;
elem.mFirst = vertices.size();
vertices.resize( elem.mFirst + elem.mCount );
memcpy( &vertices[elem.mFirst], &inPath.data[ inJob.mData0 ], elem.mCount*sizeof(UserPoint) );
if (!fill)
{
Colours &colours = arrays->mColours;
colours.resize( elem.mFirst + elem.mCount );
const int * src = (const int *)(&inPath.data[ inJob.mData0 + elem.mCount*2]);
int * dest = &colours[elem.mFirst];
int n = elem.mCount;
for(int i=0;i<n;i++)
{
int s = src[i];
dest[i] = (s & 0xff00ff00) | ((s>>16)&0xff) | ((s<<16) & 0xff0000);
}
}
arrays->mElements.push_back(elem);
}
void BuildHardwareJob(const GraphicsJob &inJob,const GraphicsPath &inPath,HardwareData &ioData,
HardwareContext &inHardware)
{
if (inJob.mIsPointJob)
CreatePointJob(inJob,inPath,ioData,inHardware);
else
{
HardwareBuilder builder(inJob,inPath,ioData,inHardware);
}
}
// --- HardwareArrays ---------------------------------------------------------------------
HardwareArrays::HardwareArrays(Surface *inSurface,unsigned int inFlags)
{
mFlags = inFlags;
mSurface = inSurface;
if (inSurface)
inSurface->IncRef();
}
HardwareArrays::~HardwareArrays()
{
if (mSurface)
mSurface->DecRef();
}
bool HardwareArrays::ColourMatch(bool inWantColour)
{
if (mVertices.empty())
return true;
return mColours.empty() != inWantColour;
}
// --- HardwareData ---------------------------------------------------------------------
HardwareData::~HardwareData()
{
mCalls.DeleteAll();
}
HardwareArrays &HardwareData::GetArrays(Surface *inSurface,bool inWithColour,unsigned int inFlags)
{
if (mCalls.empty() || mCalls.last()->mSurface != inSurface ||
!mCalls.last()->ColourMatch(inWithColour) ||
mCalls.last()->mFlags != inFlags )
{
HardwareArrays *arrays = new HardwareArrays(inSurface,inFlags);
mCalls.push_back(arrays);
}
return *mCalls.last();
}
// --- Texture -----------------------------
void Texture::Dirty(const Rect &inRect)
{
if (!mDirtyRect.HasPixels())
mDirtyRect = inRect;
else
mDirtyRect = mDirtyRect.Union(inRect);
}
// --- HardwareContext -----------------------------
// Cache line thickness transforms...
static Matrix sLastMatrix;
double sLineScaleV = -1;
double sLineScaleH = -1;
double sLineScaleNormal = -1;
bool HardwareContext::Hits(const RenderState &inState, const HardwareCalls &inCalls )
{
if (inState.mClipRect.w!=1 || inState.mClipRect.h!=1)
return false;
UserPoint screen(inState.mClipRect.x, inState.mClipRect.y);
UserPoint pos = inState.mTransform.mMatrix->ApplyInverse(screen);
if (sLastMatrix!=*inState.mTransform.mMatrix)
{
sLastMatrix=*inState.mTransform.mMatrix;
sLineScaleV = -1;
sLineScaleH = -1;
sLineScaleNormal = -1;
}
for(int c=0;c<inCalls.size();c++)
{
HardwareArrays &arrays = *inCalls[c];
Vertices &vert = arrays.mVertices;
// TODO: include extent in HardwareArrays
DrawElements &elements = arrays.mElements;
for(int e=0;e<elements.size();e++)
{
DrawElement draw = elements[e];
if (draw.mPrimType == ptLineStrip)
{
if ( draw.mCount < 2 || draw.mWidth==0)
continue;
double width = 1;
Matrix &m = sLastMatrix;
switch(draw.mScaleMode)
{
case ssmNone: width = draw.mWidth; break;
case ssmNormal:
case ssmOpenGL:
if (sLineScaleNormal<0)
sLineScaleNormal =
sqrt( 0.5*( m.m00*m.m00 + m.m01*m.m01 +
m.m10*m.m10 + m.m11*m.m11 ) );
width = draw.mWidth*sLineScaleNormal;
break;
case ssmVertical:
if (sLineScaleV<0)
sLineScaleV =
sqrt( m.m00*m.m00 + m.m01*m.m01 );
width = draw.mWidth*sLineScaleV;
break;
case ssmHorizontal:
if (sLineScaleH<0)
sLineScaleH =
sqrt( m.m10*m.m10 + m.m11*m.m11 );
width = draw.mWidth*sLineScaleH;
break;
}
double x0 = pos.x - width;
double x1 = pos.x + width;
double y0 = pos.y - width;
double y1 = pos.y + width;
double w2 = width*width;
UserPoint *v = &vert[ draw.mFirst ];
UserPoint p0 = *v;
int prev = 0;
if (p0.x<x0) prev |= 0x01;
if (p0.x>x1) prev |= 0x02;
if (p0.y<y0) prev |= 0x04;
if (p0.y>y1) prev |= 0x08;
if (prev==0 && pos.Dist2(p0)<=w2)
return true;
for(int i=1;i<draw.mCount;i++)
{
UserPoint p = v[i];
int flags = 0;
if (p.x<x0) flags |= 0x01;
if (p.x>x1) flags |= 0x02;
if (p.y<y0) flags |= 0x04;
if (p.y>y1) flags |= 0x08;
if (flags==0 && pos.Dist2(p)<=w2)
return true;
if ( !(flags & prev) )
{
// Line *may* pass though the point...
UserPoint vec = p-p0;
double len = sqrt(vec.x*vec.x + vec.y*vec.y);
if (len>0)
{
double a = vec.Dot(pos-p0)/len;
if (a>0 && a<1)
{
if ( (p0 + vec*a).Dist2(pos) < w2 )
return true;
}
}
}
prev = flags;
p0 = p;
}
}
else if (draw.mPrimType == ptTriangleFan)
{
if (draw.mCount<3)
continue;
UserPoint *v = &vert[ draw.mFirst ];
UserPoint p0 = *v;
int count_left = 0;
for(int i=1;i<=draw.mCount;i++)
{
UserPoint p = v[i%draw.mCount];
if ( (p.y<pos.y) != (p0.y<pos.y) )
{
// Crosses, but to the left?
double ratio = (pos.y-p0.y)/(p.y-p0.y);
double x = p0.x + (p.x-p0.x) * ratio;
if (x<pos.x)
count_left++;
}
p0 = p;
}
if (count_left & 1)
return true;
}
else if (draw.mPrimType == ptTriangles)
{
if (draw.mCount<3)
continue;
UserPoint *v = &vert[ draw.mFirst ];
int numTriangles = draw.mCount / 3;
for(int i=0;i<numTriangles;i++)
{
UserPoint base = *v++;
bool bgx = pos.x>base.x;
if ( bgx!=(pos.x>v[0].x) || bgx!=(pos.x>v[1].x) )
{
bool bgy = pos.y>base.y;
if ( bgy!=(pos.y>v[0].y) || bgy!=(pos.y>v[1].y) )
{
UserPoint v0 = v[0] - base;
UserPoint v1 = v[1] - base;
UserPoint v2 = pos - base;
double dot00 = v0.Dot(v0);
double dot01 = v0.Dot(v1);
double dot02 = v0.Dot(v2);
double dot11 = v1.Dot(v1);
double dot12 = v1.Dot(v2);
// Compute barycentric coordinates
double denom = (dot00 * dot11 - dot01 * dot01);
if (denom!=0)
{
denom = 1 / denom;
double u = (dot11 * dot02 - dot01 * dot12) * denom;
if (u>=0)
{
double v = (dot00 * dot12 - dot01 * dot02) * denom;
// Check if point is in triangle
if ( (v >= 0) && (u + v < 1) )
return true;
}
}
}
}
v+=2;
}
}
}
}
return false;
}
} // end namespace lime