
Before this commit, the first time NewFrame() is called, the background color would not be filled, leading to interference with whatever the GUI toolkit decided to put there.
1030 lines
32 KiB
C++
1030 lines
32 KiB
C++
//-----------------------------------------------------------------------------
|
|
// OpenGL ES 2.0 and OpenGL 3.0 based rendering interface.
|
|
//
|
|
// Copyright 2016 Aleksey Egorov
|
|
//-----------------------------------------------------------------------------
|
|
#include "solvespace.h"
|
|
#include "gl2shader.h"
|
|
|
|
namespace SolveSpace {
|
|
|
|
class TextureCache {
|
|
public:
|
|
std::map<std::weak_ptr<const Pixmap>, GLuint,
|
|
std::owner_less<std::weak_ptr<const Pixmap>>> items;
|
|
|
|
bool Lookup(std::shared_ptr<const Pixmap> ptr, GLuint *result) {
|
|
auto it = items.find(ptr);
|
|
if(it == items.end()) {
|
|
GLuint id;
|
|
glGenTextures(1, &id);
|
|
items[ptr] = id;
|
|
*result = id;
|
|
return false;
|
|
}
|
|
|
|
*result = it->second;
|
|
return true;
|
|
}
|
|
|
|
void CleanupUnused() {
|
|
for(auto it = items.begin(); it != items.end();) {
|
|
if(it->first.expired()) {
|
|
glDeleteTextures(1, &it->second);
|
|
it = items.erase(it);
|
|
continue;
|
|
}
|
|
it++;
|
|
}
|
|
}
|
|
};
|
|
|
|
// A canvas that uses the core OpenGL 2 profile, for desktop systems.
|
|
class OpenGl2Renderer : public ViewportCanvas {
|
|
public:
|
|
struct SEdgeListItem {
|
|
hStroke h;
|
|
SEdgeList lines;
|
|
|
|
void Clear() { lines.Clear(); }
|
|
};
|
|
|
|
struct SMeshListItem {
|
|
hFill h;
|
|
SIndexedMesh mesh;
|
|
|
|
void Clear() { mesh.Clear(); }
|
|
};
|
|
|
|
struct SPointListItem {
|
|
hStroke h;
|
|
SIndexedMesh points;
|
|
|
|
void Clear() { points.Clear(); }
|
|
};
|
|
|
|
IdList<SEdgeListItem, hStroke> lines;
|
|
IdList<SMeshListItem, hFill> meshes;
|
|
IdList<SPointListItem, hStroke> points;
|
|
|
|
TextureCache pixmapCache;
|
|
std::shared_ptr<Pixmap> masks[3];
|
|
|
|
bool initialized;
|
|
StippleAtlas atlas;
|
|
MeshRenderer meshRenderer;
|
|
IndexedMeshRenderer imeshRenderer;
|
|
EdgeRenderer edgeRenderer;
|
|
OutlineRenderer outlineRenderer;
|
|
|
|
Camera camera;
|
|
Lighting lighting;
|
|
// Cached OpenGL state.
|
|
struct {
|
|
hStroke hcs;
|
|
Stroke *stroke;
|
|
hFill hcf;
|
|
Fill *fill;
|
|
std::weak_ptr<const Pixmap> texture;
|
|
} current;
|
|
|
|
OpenGl2Renderer() :
|
|
lines(), meshes(), points(), pixmapCache(), masks(),
|
|
initialized(), atlas(), meshRenderer(), imeshRenderer(),
|
|
edgeRenderer(), outlineRenderer(), camera(), lighting(),
|
|
current() {}
|
|
|
|
void Init();
|
|
|
|
const Camera &GetCamera() const override { return camera; }
|
|
|
|
void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override;
|
|
void DrawEdges(const SEdgeList &el, hStroke hcs) override;
|
|
bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { return false; }
|
|
void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs mode) override;
|
|
void DrawVectorText(const std::string &text, double height,
|
|
const Vector &o, const Vector &u, const Vector &v,
|
|
hStroke hcs) override;
|
|
|
|
void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
|
|
hFill hcf) override;
|
|
void DrawPoint(const Vector &o, hStroke hs) override;
|
|
void DrawPolygon(const SPolygon &p, hFill hcf) override;
|
|
void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) override;
|
|
void DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) override;
|
|
void DrawPixmap(std::shared_ptr<const Pixmap> pm,
|
|
const Vector &o, const Vector &u, const Vector &v,
|
|
const Point2d &ta, const Point2d &tb, hFill hcf) override;
|
|
void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override;
|
|
|
|
std::shared_ptr<BatchCanvas> CreateBatch() override;
|
|
|
|
Stroke *SelectStroke(hStroke hcs);
|
|
Fill *SelectFill(hFill hcf);
|
|
void SelectMask(FillPattern pattern);
|
|
void SelectTexture(std::shared_ptr<const Pixmap> pm);
|
|
void DoFatLineEndcap(const Vector &p, const Vector &u, const Vector &v);
|
|
void DoFatLine(const Vector &a, const Vector &b, double width);
|
|
void DoLine(const Vector &a, const Vector &b, hStroke hcs);
|
|
void DoPoint(Vector p, hStroke hs);
|
|
void DoStippledLine(const Vector &a, const Vector &b, hStroke hcs);
|
|
|
|
void UpdateProjection();
|
|
void SetCamera(const Camera &c) override;
|
|
void SetLighting(const Lighting &l) override;
|
|
|
|
void NewFrame() override;
|
|
void FlushFrame() override;
|
|
std::shared_ptr<Pixmap> ReadFrame() override;
|
|
|
|
void GetIdent(const char **vendor, const char **renderer, const char **version) override;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Thin wrappers around OpenGL functions to fix bugs, adapt them to our
|
|
// data structures, etc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static void ssglDepthRange(Canvas::Layer layer, int zIndex) {
|
|
switch(layer) {
|
|
case Canvas::Layer::NORMAL:
|
|
case Canvas::Layer::FRONT:
|
|
case Canvas::Layer::BACK:
|
|
glDepthFunc(GL_LEQUAL);
|
|
glDepthMask(GL_TRUE);
|
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
break;
|
|
|
|
case Canvas::Layer::DEPTH_ONLY:
|
|
glDepthFunc(GL_LEQUAL);
|
|
glDepthMask(GL_TRUE);
|
|
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
|
break;
|
|
|
|
case Canvas::Layer::OCCLUDED:
|
|
glDepthFunc(GL_GREATER);
|
|
glDepthMask(GL_FALSE);
|
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
break;
|
|
}
|
|
|
|
switch(layer) {
|
|
case Canvas::Layer::FRONT:
|
|
glDepthRangef(0.0f, 0.0f);
|
|
break;
|
|
|
|
case Canvas::Layer::BACK:
|
|
glDepthRangef(1.0f, 1.0f);
|
|
break;
|
|
|
|
case Canvas::Layer::NORMAL:
|
|
case Canvas::Layer::DEPTH_ONLY:
|
|
case Canvas::Layer::OCCLUDED:
|
|
// The size of this step depends on the resolution of the Z buffer; for
|
|
// a 16-bit buffer, this should be fine.
|
|
double offset = 1.0 / (65535 * 0.8) * zIndex;
|
|
glDepthRangef((float)(0.1 - offset), (float)(1.0 - offset));
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// A simple OpenGL state tracker to group consecutive draw calls.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
Canvas::Stroke *OpenGl2Renderer::SelectStroke(hStroke hcs) {
|
|
if(current.hcs.v == hcs.v) return current.stroke;
|
|
|
|
Stroke *stroke = strokes.FindById(hcs);
|
|
ssglDepthRange(stroke->layer, stroke->zIndex);
|
|
|
|
current.hcs = hcs;
|
|
current.stroke = stroke;
|
|
current.hcf = {};
|
|
current.fill = NULL;
|
|
current.texture.reset();
|
|
return stroke;
|
|
}
|
|
|
|
void OpenGl2Renderer::SelectMask(FillPattern pattern) {
|
|
if(!masks[0]) {
|
|
masks[0] = Pixmap::Create(Pixmap::Format::A, 32, 32);
|
|
masks[1] = Pixmap::Create(Pixmap::Format::A, 32, 32);
|
|
masks[2] = Pixmap::Create(Pixmap::Format::A, 32, 32);
|
|
|
|
for(int x = 0; x < 32; x++) {
|
|
for(int y = 0; y < 32; y++) {
|
|
masks[0]->data[y * 32 + x] = ((x / 2) % 2 == 0 && (y / 2) % 2 == 0) ? 0xFF : 0x00;
|
|
masks[1]->data[y * 32 + x] = ((x / 2) % 2 == 1 && (y / 2) % 2 == 1) ? 0xFF : 0x00;
|
|
masks[2]->data[y * 32 + x] = 0xFF;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch(pattern) {
|
|
case Canvas::FillPattern::SOLID:
|
|
SelectTexture(masks[2]);
|
|
break;
|
|
|
|
case Canvas::FillPattern::CHECKERED_A:
|
|
SelectTexture(masks[0]);
|
|
break;
|
|
|
|
case Canvas::FillPattern::CHECKERED_B:
|
|
SelectTexture(masks[1]);
|
|
break;
|
|
|
|
default: ssassert(false, "Unexpected fill pattern");
|
|
}
|
|
}
|
|
|
|
Canvas::Fill *OpenGl2Renderer::SelectFill(hFill hcf) {
|
|
if(current.hcf.v == hcf.v) return current.fill;
|
|
|
|
Fill *fill = fills.FindById(hcf);
|
|
ssglDepthRange(fill->layer, fill->zIndex);
|
|
|
|
current.hcs = {};
|
|
current.stroke = NULL;
|
|
current.hcf = hcf;
|
|
current.fill = fill;
|
|
if(fill->pattern != FillPattern::SOLID) {
|
|
SelectMask(fill->pattern);
|
|
} else if(fill->texture) {
|
|
SelectTexture(fill->texture);
|
|
} else {
|
|
SelectMask(FillPattern::SOLID);
|
|
}
|
|
return fill;
|
|
}
|
|
|
|
void OpenGl2Renderer::InvalidatePixmap(std::shared_ptr<const Pixmap> pm) {
|
|
GLuint id;
|
|
pixmapCache.Lookup(pm, &id);
|
|
glBindTexture(GL_TEXTURE_2D, id);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
|
|
GLenum format;
|
|
switch(pm->format) {
|
|
case Pixmap::Format::RGBA: format = GL_RGBA; break;
|
|
case Pixmap::Format::RGB: format = GL_RGB; break;
|
|
#if defined(HAVE_GLES)
|
|
case Pixmap::Format::A: format = GL_ALPHA; break;
|
|
#else
|
|
case Pixmap::Format::A: format = GL_RED; break;
|
|
#endif
|
|
case Pixmap::Format::BGRA:
|
|
case Pixmap::Format::BGR:
|
|
ssassert(false, "Unexpected pixmap format");
|
|
}
|
|
glTexImage2D(GL_TEXTURE_2D, 0, format, pm->width, pm->height, 0,
|
|
format, GL_UNSIGNED_BYTE, &pm->data[0]);
|
|
}
|
|
|
|
void OpenGl2Renderer::SelectTexture(std::shared_ptr<const Pixmap> pm) {
|
|
if(current.texture.lock() == pm) return;
|
|
|
|
GLuint id;
|
|
if(!pixmapCache.Lookup(pm, &id)) {
|
|
InvalidatePixmap(pm);
|
|
}
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, id);
|
|
current.texture = pm;
|
|
}
|
|
|
|
void OpenGl2Renderer::DoLine(const Vector &a, const Vector &b, hStroke hcs) {
|
|
SEdgeListItem *eli = lines.FindByIdNoOops(hcs);
|
|
if(eli == NULL) {
|
|
SEdgeListItem item = {};
|
|
item.h = hcs;
|
|
lines.Add(&item);
|
|
eli = lines.FindByIdNoOops(hcs);
|
|
}
|
|
|
|
eli->lines.AddEdge(a, b);
|
|
}
|
|
|
|
void OpenGl2Renderer::DoPoint(Vector p, hStroke hs) {
|
|
SPointListItem *pli = points.FindByIdNoOops(hs);
|
|
if(pli == NULL) {
|
|
SPointListItem item = {};
|
|
item.h = hs;
|
|
points.Add(&item);
|
|
pli = points.FindByIdNoOops(hs);
|
|
}
|
|
|
|
pli->points.AddPoint(p);
|
|
}
|
|
|
|
void OpenGl2Renderer::DoStippledLine(const Vector &a, const Vector &b, hStroke hcs) {
|
|
Stroke *stroke = strokes.FindById(hcs);
|
|
if(stroke->stipplePattern != StipplePattern::FREEHAND &&
|
|
stroke->stipplePattern != StipplePattern::ZIGZAG)
|
|
{
|
|
DoLine(a, b, hcs);
|
|
return;
|
|
}
|
|
|
|
const char *patternSeq;
|
|
Stroke s = *stroke;
|
|
s.stipplePattern = StipplePattern::CONTINUOUS;
|
|
hcs = GetStroke(s);
|
|
switch(stroke->stipplePattern) {
|
|
case StipplePattern::CONTINUOUS: DoLine(a, b, hcs); return;
|
|
case StipplePattern::SHORT_DASH: patternSeq = "- "; break;
|
|
case StipplePattern::DASH: patternSeq = "- "; break;
|
|
case StipplePattern::LONG_DASH: patternSeq = "_ "; break;
|
|
case StipplePattern::DASH_DOT: patternSeq = "-."; break;
|
|
case StipplePattern::DASH_DOT_DOT: patternSeq = "-.."; break;
|
|
case StipplePattern::DOT: patternSeq = "."; break;
|
|
case StipplePattern::FREEHAND: patternSeq = "~"; break;
|
|
case StipplePattern::ZIGZAG: patternSeq = "~__"; break;
|
|
}
|
|
|
|
Vector dir = b.Minus(a);
|
|
double len = dir.Magnitude();
|
|
dir = dir.WithMagnitude(1.0);
|
|
|
|
const char *si = patternSeq;
|
|
double end = len;
|
|
double ss = stroke->stippleScale / 2.0;
|
|
do {
|
|
double start = end;
|
|
switch(*si) {
|
|
case ' ':
|
|
end -= 1.0 * ss;
|
|
break;
|
|
|
|
case '-':
|
|
start = max(start - 0.5 * ss, 0.0);
|
|
end = max(start - 2.0 * ss, 0.0);
|
|
if(start == end) break;
|
|
DoLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), hcs);
|
|
end = max(end - 0.5 * ss, 0.0);
|
|
break;
|
|
|
|
case '_':
|
|
end = max(end - 4.0 * ss, 0.0);
|
|
DoLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), hcs);
|
|
break;
|
|
|
|
case '.':
|
|
end = max(end - 0.5 * ss, 0.0);
|
|
if(end == 0.0) break;
|
|
DoPoint(a.Plus(dir.ScaledBy(end)), hcs);
|
|
end = max(end - 0.5 * ss, 0.0);
|
|
break;
|
|
|
|
case '~': {
|
|
Vector ab = b.Minus(a);
|
|
Vector gn = (camera.projRight).Cross(camera.projUp);
|
|
Vector abn = (ab.Cross(gn)).WithMagnitude(1);
|
|
abn = abn.Minus(gn.ScaledBy(gn.Dot(abn)));
|
|
double pws = 2.0 * stroke->width / camera.scale;
|
|
|
|
end = max(end - 0.5 * ss, 0.0);
|
|
Vector aa = a.Plus(dir.ScaledBy(start));
|
|
Vector bb = a.Plus(dir.ScaledBy(end))
|
|
.Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss)));
|
|
DoLine(aa, bb, hcs);
|
|
if(end == 0.0) break;
|
|
|
|
start = end;
|
|
end = max(end - 1.0 * ss, 0.0);
|
|
aa = a.Plus(dir.ScaledBy(end))
|
|
.Plus(abn.ScaledBy(pws))
|
|
.Minus(abn.ScaledBy(2.0 * pws * (start - end) / ss));
|
|
DoLine(bb, aa, hcs);
|
|
if(end == 0.0) break;
|
|
|
|
start = end;
|
|
end = max(end - 0.5 * ss, 0.0);
|
|
bb = a.Plus(dir.ScaledBy(end))
|
|
.Minus(abn.ScaledBy(pws))
|
|
.Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss)));
|
|
DoLine(aa, bb, hcs);
|
|
break;
|
|
}
|
|
|
|
default: ssassert(false, "Unexpected stipple pattern element");
|
|
}
|
|
if(*(++si) == 0) si = patternSeq;
|
|
} while(end > 0.0);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// A canvas implemented using OpenGL 2 vertex buffer objects.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void OpenGl2Renderer::Init() {
|
|
atlas.Init();
|
|
edgeRenderer.Init(&atlas);
|
|
outlineRenderer.Init(&atlas);
|
|
meshRenderer.Init();
|
|
imeshRenderer.Init();
|
|
|
|
#if !defined(HAVE_GLES) && !defined(__APPLE__)
|
|
GLuint array;
|
|
glGenVertexArrays(1, &array);
|
|
glBindVertexArray(array);
|
|
#endif
|
|
UpdateProjection();
|
|
}
|
|
|
|
void OpenGl2Renderer::DrawLine(const Vector &a, const Vector &b, hStroke hcs) {
|
|
DoStippledLine(a, b, hcs);
|
|
}
|
|
|
|
void OpenGl2Renderer::DrawEdges(const SEdgeList &el, hStroke hcs) {
|
|
for(const SEdge &e : el.l) {
|
|
DoStippledLine(e.a, e.b, hcs);
|
|
}
|
|
}
|
|
|
|
void OpenGl2Renderer::DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs mode) {
|
|
if(ol.l.n == 0) return;
|
|
|
|
Stroke *stroke = SelectStroke(hcs);
|
|
ssassert(stroke->stipplePattern != StipplePattern::ZIGZAG &&
|
|
stroke->stipplePattern != StipplePattern::FREEHAND,
|
|
"ZIGZAG and FREEHAND not supported for outlines");
|
|
|
|
outlineRenderer.SetStroke(*stroke, 1.0 / camera.scale);
|
|
outlineRenderer.Draw(ol, mode);
|
|
}
|
|
|
|
void OpenGl2Renderer::DrawVectorText(const std::string &text, double height,
|
|
const Vector &o, const Vector &u, const Vector &v,
|
|
hStroke hcs) {
|
|
SEdgeListItem *eli = lines.FindByIdNoOops(hcs);
|
|
if(eli == NULL) {
|
|
SEdgeListItem item = {};
|
|
item.h = hcs;
|
|
lines.Add(&item);
|
|
eli = lines.FindByIdNoOops(hcs);
|
|
}
|
|
SEdgeList &lines = eli->lines;
|
|
auto traceEdge = [&](Vector a, Vector b) { lines.AddEdge(a, b); };
|
|
VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera);
|
|
}
|
|
|
|
void OpenGl2Renderer::DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
|
|
hFill hcf) {
|
|
SMeshListItem *li = meshes.FindByIdNoOops(hcf);
|
|
if(li == NULL) {
|
|
SMeshListItem item = {};
|
|
item.h = hcf;
|
|
meshes.Add(&item);
|
|
li = meshes.FindByIdNoOops(hcf);
|
|
}
|
|
li->mesh.AddQuad(a, b, c, d);
|
|
}
|
|
|
|
void OpenGl2Renderer::DrawPoint(const Vector &o, hStroke hs) {
|
|
DoPoint(o, hs);
|
|
}
|
|
|
|
void OpenGl2Renderer::DrawPolygon(const SPolygon &p, hFill hcf) {
|
|
Fill *fill = SelectFill(hcf);
|
|
|
|
SMesh m = {};
|
|
p.TriangulateInto(&m);
|
|
meshRenderer.UseFilled(*fill);
|
|
meshRenderer.Draw(m);
|
|
m.Clear();
|
|
}
|
|
|
|
void OpenGl2Renderer::DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) {
|
|
ssassert(false, "Not implemented");
|
|
}
|
|
|
|
void OpenGl2Renderer::DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) {
|
|
if(faces.empty()) return;
|
|
|
|
Fill *fill = SelectFill(hcf);
|
|
|
|
SMesh facesMesh = {};
|
|
for(uint32_t f : faces) {
|
|
for(const STriangle &t : m.l) {
|
|
if(f != t.meta.face) continue;
|
|
facesMesh.l.Add(&t);
|
|
}
|
|
}
|
|
|
|
meshRenderer.UseFilled(*fill);
|
|
meshRenderer.Draw(facesMesh);
|
|
facesMesh.Clear();
|
|
}
|
|
|
|
void OpenGl2Renderer::DrawPixmap(std::shared_ptr<const Pixmap> pm,
|
|
const Vector &o, const Vector &u, const Vector &v,
|
|
const Point2d &ta, const Point2d &tb, hFill hcf) {
|
|
Fill fill = *fills.FindById(hcf);
|
|
fill.texture = pm;
|
|
hcf = GetFill(fill);
|
|
|
|
SMeshListItem *mli = meshes.FindByIdNoOops(hcf);
|
|
if(mli == NULL) {
|
|
SMeshListItem item = {};
|
|
item.h = hcf;
|
|
meshes.Add(&item);
|
|
mli = meshes.FindByIdNoOops(hcf);
|
|
}
|
|
|
|
mli->mesh.AddPixmap(o, u, v, ta, tb);
|
|
}
|
|
|
|
void OpenGl2Renderer::UpdateProjection() {
|
|
glViewport(0, 0, camera.width, camera.height);
|
|
|
|
double mat1[16];
|
|
double mat2[16];
|
|
|
|
double sx = camera.scale * 2.0 / camera.width;
|
|
double sy = camera.scale * 2.0 / camera.height;
|
|
double sz = camera.scale * 1.0 / 30000;
|
|
|
|
MakeMatrix(mat1,
|
|
sx, 0, 0, 0,
|
|
0, sy, 0, 0,
|
|
0, 0, sz, 0,
|
|
0, 0, 0, 1
|
|
);
|
|
|
|
// Last thing before display is to apply the perspective
|
|
double clp = camera.tangent * camera.scale;
|
|
MakeMatrix(mat2,
|
|
1, 0, 0, 0,
|
|
0, 1, 0, 0,
|
|
0, 0, 1, 0,
|
|
0, 0, clp, 1
|
|
);
|
|
|
|
double projection[16];
|
|
MultMatrix(mat1, mat2, projection);
|
|
|
|
// Before that, we apply the rotation
|
|
Vector u = camera.projRight,
|
|
v = camera.projUp,
|
|
n = camera.projUp.Cross(camera.projRight);
|
|
MakeMatrix(mat1,
|
|
u.x, u.y, u.z, 0,
|
|
v.x, v.y, v.z, 0,
|
|
n.x, n.y, n.z, 0,
|
|
0, 0, 0, 1
|
|
);
|
|
|
|
// And before that, the translation
|
|
Vector o = camera.offset;
|
|
MakeMatrix(mat2,
|
|
1, 0, 0, o.x,
|
|
0, 1, 0, o.y,
|
|
0, 0, 1, o.z,
|
|
0, 0, 0, 1
|
|
);
|
|
|
|
double modelview[16];
|
|
MultMatrix(mat1, mat2, modelview);
|
|
|
|
imeshRenderer.SetProjection(projection);
|
|
imeshRenderer.SetModelview(modelview);
|
|
meshRenderer.SetProjection(projection);
|
|
meshRenderer.SetModelview(modelview);
|
|
edgeRenderer.SetProjection(projection);
|
|
edgeRenderer.SetModelview(modelview);
|
|
outlineRenderer.SetProjection(projection);
|
|
outlineRenderer.SetModelview(modelview);
|
|
|
|
glClearDepthf(1.0f);
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
}
|
|
|
|
void OpenGl2Renderer::NewFrame() {
|
|
if(!initialized) {
|
|
Init();
|
|
initialized = true;
|
|
}
|
|
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glEnable(GL_BLEND);
|
|
|
|
glDepthFunc(GL_LEQUAL);
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
RgbaColor backgroundColor = lighting.backgroundColor;
|
|
glClearColor(backgroundColor.redF(), backgroundColor.greenF(),
|
|
backgroundColor.blueF(), backgroundColor.alphaF());
|
|
glClearDepthf(1.0);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
glPolygonOffset(2.0, 1.0);
|
|
}
|
|
|
|
void OpenGl2Renderer::FlushFrame() {
|
|
for(SMeshListItem &li : meshes) {
|
|
Fill *fill = SelectFill(li.h);
|
|
|
|
imeshRenderer.UseFilled(*fill);
|
|
imeshRenderer.Draw(li.mesh);
|
|
li.mesh.Clear();
|
|
}
|
|
meshes.Clear();
|
|
|
|
for(SEdgeListItem &eli : lines) {
|
|
Stroke *stroke = SelectStroke(eli.h);
|
|
|
|
edgeRenderer.SetStroke(*stroke, 1.0 / camera.scale);
|
|
edgeRenderer.Draw(eli.lines);
|
|
eli.lines.Clear();
|
|
}
|
|
lines.Clear();
|
|
|
|
for(SPointListItem &li : points) {
|
|
Stroke *stroke = SelectStroke(li.h);
|
|
|
|
imeshRenderer.UsePoint(*stroke, 1.0 / camera.scale);
|
|
imeshRenderer.Draw(li.points);
|
|
li.points.Clear();
|
|
}
|
|
points.Clear();
|
|
|
|
glFinish();
|
|
|
|
GLenum error = glGetError();
|
|
if(error != GL_NO_ERROR) {
|
|
dbp("glGetError() == 0x%X", error);
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<Pixmap> OpenGl2Renderer::ReadFrame() {
|
|
std::shared_ptr<Pixmap> pixmap =
|
|
Pixmap::Create(Pixmap::Format::RGB, (size_t)camera.width, (size_t)camera.height);
|
|
glReadPixels(0, 0, camera.width, camera.height, GL_RGB, GL_UNSIGNED_BYTE, &pixmap->data[0]);
|
|
return pixmap;
|
|
}
|
|
|
|
void OpenGl2Renderer::GetIdent(const char **vendor, const char **renderer, const char **version) {
|
|
*vendor = (const char *)glGetString(GL_VENDOR);
|
|
*renderer = (const char *)glGetString(GL_RENDERER);
|
|
*version = (const char *)glGetString(GL_VERSION);
|
|
}
|
|
|
|
void OpenGl2Renderer::SetCamera(const Camera &c) {
|
|
camera = c;
|
|
if(initialized) {
|
|
UpdateProjection();
|
|
}
|
|
}
|
|
|
|
void OpenGl2Renderer::SetLighting(const Lighting &l) {
|
|
lighting = l;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// A batch canvas implemented using OpenGL 2 vertex buffer objects.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class DrawCall {
|
|
public:
|
|
virtual Canvas::Layer GetLayer() const = 0;
|
|
virtual int GetZIndex() const = 0;
|
|
|
|
virtual void Draw(OpenGl2Renderer *renderer) = 0;
|
|
virtual void Remove(OpenGl2Renderer *renderer) = 0;
|
|
};
|
|
|
|
class EdgeDrawCall : public DrawCall {
|
|
public:
|
|
// Key
|
|
Canvas::Stroke stroke;
|
|
// Data
|
|
EdgeRenderer::Handle handle;
|
|
|
|
virtual Canvas::Layer GetLayer() const override { return stroke.layer; };
|
|
virtual int GetZIndex() const override { return stroke.zIndex; };
|
|
|
|
static std::shared_ptr<DrawCall> Create(OpenGl2Renderer *renderer, const SEdgeList &el,
|
|
Canvas::Stroke *stroke) {
|
|
EdgeDrawCall *dc = new EdgeDrawCall();
|
|
dc->stroke = *stroke;
|
|
dc->handle = renderer->edgeRenderer.Add(el);
|
|
return std::shared_ptr<DrawCall>(dc);
|
|
}
|
|
|
|
void Draw(OpenGl2Renderer *renderer) override {
|
|
ssglDepthRange(stroke.layer, stroke.zIndex);
|
|
renderer->edgeRenderer.SetStroke(stroke, 1.0 / renderer->camera.scale);
|
|
renderer->edgeRenderer.Draw(handle);
|
|
}
|
|
|
|
void Remove(OpenGl2Renderer *renderer) override {
|
|
renderer->edgeRenderer.Remove(handle);
|
|
}
|
|
};
|
|
|
|
class OutlineDrawCall : public DrawCall {
|
|
public:
|
|
// Key
|
|
Canvas::Stroke stroke;
|
|
// Data
|
|
OutlineRenderer::Handle handle;
|
|
Canvas::DrawOutlinesAs drawAs;
|
|
|
|
virtual Canvas::Layer GetLayer() const override { return stroke.layer; };
|
|
virtual int GetZIndex() const override { return stroke.zIndex; };
|
|
|
|
static std::shared_ptr<DrawCall> Create(OpenGl2Renderer *renderer, const SOutlineList &ol,
|
|
Canvas::Stroke *stroke,
|
|
Canvas::DrawOutlinesAs drawAs) {
|
|
OutlineDrawCall *dc = new OutlineDrawCall();
|
|
dc->stroke = *stroke;
|
|
dc->handle = renderer->outlineRenderer.Add(ol);
|
|
dc->drawAs = drawAs;
|
|
return std::shared_ptr<DrawCall>(dc);
|
|
}
|
|
|
|
void Draw(OpenGl2Renderer *renderer) override {
|
|
ssglDepthRange(stroke.layer, stroke.zIndex);
|
|
renderer->outlineRenderer.SetStroke(stroke, 1.0 / renderer->camera.scale);
|
|
renderer->outlineRenderer.Draw(handle, drawAs);
|
|
}
|
|
|
|
void Remove(OpenGl2Renderer *renderer) override {
|
|
renderer->outlineRenderer.Remove(handle);
|
|
}
|
|
};
|
|
|
|
class PointDrawCall : public DrawCall {
|
|
public:
|
|
// Key
|
|
Canvas::Stroke stroke;
|
|
// Data
|
|
IndexedMeshRenderer::Handle handle;
|
|
|
|
virtual Canvas::Layer GetLayer() const override { return stroke.layer; };
|
|
virtual int GetZIndex() const override { return stroke.zIndex; };
|
|
|
|
static std::shared_ptr<DrawCall> Create(OpenGl2Renderer *renderer, const SIndexedMesh &im,
|
|
Canvas::Stroke *stroke) {
|
|
PointDrawCall *dc = new PointDrawCall();
|
|
dc->stroke = *stroke;
|
|
dc->handle = renderer->imeshRenderer.Add(im);
|
|
return std::shared_ptr<DrawCall>(dc);
|
|
}
|
|
|
|
void Draw(OpenGl2Renderer *renderer) override {
|
|
ssglDepthRange(stroke.layer, stroke.zIndex);
|
|
renderer->imeshRenderer.UsePoint(stroke, 1.0 / renderer->camera.scale);
|
|
renderer->imeshRenderer.Draw(handle);
|
|
}
|
|
|
|
void Remove(OpenGl2Renderer *renderer) override {
|
|
renderer->imeshRenderer.Remove(handle);
|
|
}
|
|
};
|
|
|
|
class MeshDrawCall : public DrawCall {
|
|
public:
|
|
// Key
|
|
Canvas::Fill fillFront;
|
|
// Data
|
|
MeshRenderer::Handle handle;
|
|
Canvas::Fill fillBack;
|
|
bool hasFillBack;
|
|
bool isShaded;
|
|
|
|
virtual Canvas::Layer GetLayer() const override { return fillFront.layer; };
|
|
virtual int GetZIndex() const override { return fillFront.zIndex; };
|
|
|
|
static std::shared_ptr<DrawCall> Create(OpenGl2Renderer *renderer, const SMesh &m,
|
|
Canvas::Fill *fillFront, Canvas::Fill *fillBack = NULL,
|
|
bool isShaded = false) {
|
|
MeshDrawCall *dc = new MeshDrawCall();
|
|
dc->fillFront = *fillFront;
|
|
dc->handle = renderer->meshRenderer.Add(m);
|
|
dc->fillBack = *fillBack;
|
|
dc->isShaded = isShaded;
|
|
dc->hasFillBack = (fillBack != NULL);
|
|
return std::shared_ptr<DrawCall>(dc);
|
|
}
|
|
|
|
void DrawFace(OpenGl2Renderer *renderer, GLenum cullFace, Canvas::Fill *fill) {
|
|
glCullFace(cullFace);
|
|
ssglDepthRange(fill->layer, fill->zIndex);
|
|
if(fill->pattern != Canvas::FillPattern::SOLID) {
|
|
renderer->SelectMask(fill->pattern);
|
|
} else if(fill->texture) {
|
|
renderer->SelectTexture(fill->texture);
|
|
} else {
|
|
renderer->SelectMask(Canvas::FillPattern::SOLID);
|
|
}
|
|
if(isShaded) {
|
|
renderer->meshRenderer.UseShaded(renderer->lighting);
|
|
} else {
|
|
renderer->meshRenderer.UseFilled(*fill);
|
|
}
|
|
renderer->meshRenderer.Draw(handle, /*useColors=*/fill->color.IsEmpty(), fill->color);
|
|
}
|
|
|
|
void Draw(OpenGl2Renderer *renderer) override {
|
|
glEnable(GL_POLYGON_OFFSET_FILL);
|
|
glEnable(GL_CULL_FACE);
|
|
|
|
if(hasFillBack)
|
|
DrawFace(renderer, GL_FRONT, &fillBack);
|
|
DrawFace(renderer, GL_BACK, &fillFront);
|
|
|
|
glDisable(GL_POLYGON_OFFSET_FILL);
|
|
glDisable(GL_CULL_FACE);
|
|
}
|
|
|
|
void Remove(OpenGl2Renderer *renderer) override {
|
|
renderer->meshRenderer.Remove(handle);
|
|
}
|
|
};
|
|
|
|
struct CompareDrawCall {
|
|
bool operator()(const std::shared_ptr<DrawCall> &a, const std::shared_ptr<DrawCall> &b) {
|
|
const Canvas::Layer stackup[] = {
|
|
Canvas::Layer::BACK,
|
|
Canvas::Layer::DEPTH_ONLY,
|
|
Canvas::Layer::NORMAL,
|
|
Canvas::Layer::OCCLUDED,
|
|
Canvas::Layer::FRONT
|
|
};
|
|
|
|
int aLayerIndex =
|
|
std::find(std::begin(stackup), std::end(stackup), a->GetLayer()) - std::begin(stackup);
|
|
int bLayerIndex =
|
|
std::find(std::begin(stackup), std::end(stackup), b->GetLayer()) - std::begin(stackup);
|
|
if(aLayerIndex == bLayerIndex) {
|
|
return a->GetZIndex() < b->GetZIndex();
|
|
} else {
|
|
return aLayerIndex < bLayerIndex;
|
|
}
|
|
}
|
|
};
|
|
|
|
class OpenGl2RendererBatch : public BatchCanvas {
|
|
public:
|
|
struct EdgeBuffer {
|
|
hStroke h;
|
|
SEdgeList edges;
|
|
|
|
void Clear() {
|
|
edges.Clear();
|
|
}
|
|
};
|
|
|
|
struct PointBuffer {
|
|
hStroke h;
|
|
SIndexedMesh points;
|
|
|
|
void Clear() {
|
|
points.Clear();
|
|
}
|
|
};
|
|
|
|
OpenGl2Renderer *renderer;
|
|
|
|
IdList<EdgeBuffer, hStroke> edgeBuffer;
|
|
IdList<PointBuffer, hStroke> pointBuffer;
|
|
|
|
std::multiset<std::shared_ptr<DrawCall>, CompareDrawCall> drawCalls;
|
|
|
|
OpenGl2RendererBatch() : renderer(), edgeBuffer(), pointBuffer() {}
|
|
|
|
void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override {
|
|
EdgeBuffer *eb = edgeBuffer.FindByIdNoOops(hcs);
|
|
if(!eb) {
|
|
EdgeBuffer neb = {};
|
|
neb.h = hcs;
|
|
edgeBuffer.Add(&neb);
|
|
eb = edgeBuffer.FindById(hcs);
|
|
}
|
|
|
|
eb->edges.AddEdge(a, b);
|
|
}
|
|
|
|
void DrawEdges(const SEdgeList &el, hStroke hcs) override {
|
|
EdgeBuffer *eb = edgeBuffer.FindByIdNoOops(hcs);
|
|
if(!eb) {
|
|
EdgeBuffer neb = {};
|
|
neb.h = hcs;
|
|
edgeBuffer.Add(&neb);
|
|
eb = edgeBuffer.FindById(hcs);
|
|
}
|
|
|
|
for(const SEdge &e : el.l) {
|
|
eb->edges.AddEdge(e.a, e.b);
|
|
}
|
|
}
|
|
|
|
bool DrawBeziers(const SBezierList &bl, hStroke hcs) override {
|
|
return false;
|
|
}
|
|
|
|
void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override {
|
|
drawCalls.emplace(OutlineDrawCall::Create(renderer, ol, strokes.FindById(hcs), drawAs));
|
|
}
|
|
|
|
void DrawVectorText(const std::string &text, double height,
|
|
const Vector &o, const Vector &u, const Vector &v,
|
|
hStroke hcs) override {
|
|
ssassert(false, "Not implemented");
|
|
}
|
|
|
|
void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
|
|
hFill hcf) override {
|
|
ssassert(false, "Not implemented");
|
|
}
|
|
|
|
void DrawPoint(const Vector &o, hStroke hcs) override {
|
|
PointBuffer *pb = pointBuffer.FindByIdNoOops(hcs);
|
|
if(!pb) {
|
|
PointBuffer npb = {};
|
|
npb.h = hcs;
|
|
pointBuffer.Add(&npb);
|
|
pb = pointBuffer.FindById(hcs);
|
|
}
|
|
|
|
pb->points.AddPoint(o);
|
|
}
|
|
|
|
void DrawPolygon(const SPolygon &p, hFill hcf) override {
|
|
SMesh m = {};
|
|
p.TriangulateInto(&m);
|
|
drawCalls.emplace(MeshDrawCall::Create(renderer, m, fills.FindById(hcf),
|
|
fills.FindById(hcf)));
|
|
m.Clear();
|
|
}
|
|
|
|
void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack = {}) override {
|
|
drawCalls.emplace(MeshDrawCall::Create(renderer, m, fills.FindById(hcfFront),
|
|
fills.FindByIdNoOops(hcfBack),
|
|
/*lighting=*/true));
|
|
}
|
|
|
|
void DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) override {
|
|
ssassert(false, "Not implemented");
|
|
}
|
|
|
|
void DrawPixmap(std::shared_ptr<const Pixmap> pm,
|
|
const Vector &o, const Vector &u, const Vector &v,
|
|
const Point2d &ta, const Point2d &tb, hFill hcf) override {
|
|
ssassert(false, "Not implemented");
|
|
}
|
|
|
|
void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override {
|
|
ssassert(false, "Not implemented");
|
|
}
|
|
|
|
void Finalize() override {
|
|
for(const EdgeBuffer &eb : edgeBuffer) {
|
|
drawCalls.emplace(EdgeDrawCall::Create(renderer, eb.edges, strokes.FindById(eb.h)));
|
|
}
|
|
edgeBuffer.Clear();
|
|
|
|
for(const PointBuffer &pb : pointBuffer) {
|
|
drawCalls.emplace(PointDrawCall::Create(renderer, pb.points, strokes.FindById(pb.h)));
|
|
}
|
|
pointBuffer.Clear();
|
|
}
|
|
|
|
void Draw() override {
|
|
renderer->current = {};
|
|
|
|
for(std::shared_ptr<DrawCall> dc : drawCalls) {
|
|
dc->Draw(renderer);
|
|
}
|
|
}
|
|
|
|
void Clear() override {
|
|
for(std::shared_ptr<DrawCall> dc : drawCalls) {
|
|
dc->Remove(renderer);
|
|
}
|
|
drawCalls.clear();
|
|
}
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Factory functions.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
std::shared_ptr<BatchCanvas> OpenGl2Renderer::CreateBatch() {
|
|
OpenGl2RendererBatch *batch = new OpenGl2RendererBatch();
|
|
batch->renderer = this;
|
|
return std::shared_ptr<BatchCanvas>(batch);
|
|
}
|
|
|
|
std::shared_ptr<ViewportCanvas> CreateRenderer() {
|
|
return std::shared_ptr<ViewportCanvas>(new OpenGl2Renderer());
|
|
}
|
|
|
|
}
|