Implement an OpenGL 2 renderer.

There are two main reasons to desire an OpenGL 2 renderer:
 1. Compatibility. The compatibility profile, ironically, does not
    offer a lot of compatibility, and our OpenGL 1 renderer will not
    run on Android, iOS, or WebGL.
 2. Performance. The immediate mode does not scale, and in fact
    becomes very slow with only a moderate amount of lines on screen,
    and only a somewhat large amount of triangles.

This commit implements a basic OpenGL 2 renderer that uses only
features from the (OpenGL 3.2) core profile. It is not yet faster
than the OpenGL 1 renderer, primarily because it uses a lot of small
draw calls.

This commit uses OpenGL 2 on Linux and Mac OS X directly (i.e. links
to the GL symbols from version 2+); on Windows this is impossible
with the default drivers, so for now OpenGL 1 is still used there.
single-window
EvilSpirit 2016-06-30 21:54:35 +06:00 committed by whitequark
parent f8824e1fb2
commit 6d2c2aecff
25 changed files with 2366 additions and 8 deletions

View File

@ -196,6 +196,20 @@ add_resources(
fonts/private/6-stipple-dash.png
fonts/private/7-stipple-zigzag.png
fonts/unicode.lff.gz
shaders/imesh.frag
shaders/imesh.vert
shaders/imesh_point.frag
shaders/imesh_point.vert
shaders/imesh_tex.frag
shaders/imesh_texa.frag
shaders/imesh_tex.vert
shaders/mesh.frag
shaders/mesh.vert
shaders/mesh_fill.frag
shaders/mesh_fill.vert
shaders/edge.frag
shaders/edge.vert
shaders/outline.vert
threejs/three-r76.js.gz
threejs/hammer-2.0.8.js.gz
threejs/SolveSpaceControls.js)

37
res/shaders/edge.frag Normal file
View File

@ -0,0 +1,37 @@
//-----------------------------------------------------------------------------
// SolveSpace Edge rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
const float feather = 0.5;
uniform vec4 color;
uniform float pixel;
uniform float width;
uniform float patternLen;
uniform float patternScale;
uniform sampler2D pattern;
varying vec3 fragLoc;
void main() {
// lookup distance texture
vec4 v = texture2D(pattern, vec2(fragLoc.z / patternScale, 0.0));
// decode distance value
float val = dot(v, vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 160581375.0));
// calculate cap
float dist = length(vec2(val * patternScale / (patternLen * width) + abs(fragLoc.x), fragLoc.y));
// perform antialising
float k = smoothstep(1.0 - 2.0 * feather * pixel / (width + feather * pixel), 1.0, abs(dist));
// perfrom alpha-test
if(k == 1.0) discard;
// write resulting color
gl_FragColor = vec4(color.rgb, color.a * (1.0 - k));
}

43
res/shaders/edge.vert Normal file
View File

@ -0,0 +1,43 @@
//-----------------------------------------------------------------------------
// SolveSpace Edge rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
const float feather = 0.5;
attribute vec3 pos;
attribute vec3 loc;
attribute vec3 tan;
uniform mat4 modelview;
uniform mat4 projection;
uniform float width;
uniform float pixel;
varying vec3 fragLoc;
void main() {
// get camera direction from modelview matrix
vec3 dir = vec3(modelview[0].z, modelview[1].z, modelview[2].z);
// calculate line contour extension basis for constant width and caps
vec3 norm = normalize(cross(tan, dir));
norm = normalize(norm - dir * dot(dir, norm));
vec3 perp = normalize(cross(dir, norm));
// calculate line extension width considering antialiasing
float ext = width + feather * pixel;
// extend line contour
vec3 vertex = pos;
vertex += ext * loc.x * normalize(perp);
vertex += ext * loc.y * normalize(norm);
// write fragment location for calculating caps and antialiasing
fragLoc = loc;
// transform resulting vertex with modelview and projection matrices
gl_Position = projection * modelview * vec4(vertex, 1.0);
}

12
res/shaders/imesh.frag Normal file
View File

@ -0,0 +1,12 @@
//-----------------------------------------------------------------------------
// SolveSpace Indexed Mesh rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
uniform vec4 color;
void main() {
gl_FragColor = color;
}

15
res/shaders/imesh.vert Normal file
View File

@ -0,0 +1,15 @@
//-----------------------------------------------------------------------------
// SolveSpace Indexed Mesh rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
attribute vec3 pos;
uniform mat4 modelview;
uniform mat4 projection;
void main() {
gl_Position = projection * modelview * vec4(pos, 1.0);
}

View File

@ -0,0 +1,19 @@
//-----------------------------------------------------------------------------
// SolveSpace Point rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
const float feather = 0.5;
uniform vec4 color;
uniform float pixel;
uniform float width;
varying vec2 fragLoc;
void main() {
// Rectangular points
gl_FragColor = color;
}

View File

@ -0,0 +1,40 @@
//-----------------------------------------------------------------------------
// SolveSpace Point rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
const float feather = 0.5;
attribute vec3 pos;
attribute vec2 loc;
uniform mat4 modelview;
uniform mat4 projection;
uniform float width;
uniform float pixel;
varying vec2 fragLoc;
void main() {
// get camera vectors from modelview matrix
vec3 u = vec3(modelview[0].x, modelview[1].x, modelview[2].x);
vec3 v = vec3(modelview[0].y, modelview[1].y, modelview[2].y);
// calculate point contour extension basis for constant width and caps
// calculate point extension width considering antialiasing
float ext = width + feather * pixel;
// extend point contour
vec3 vertex = pos;
vertex += ext * loc.x * normalize(u);
vertex += ext * loc.y * normalize(v);
// write fragment location for calculating caps and antialiasing
fragLoc = loc;
// transform resulting vertex with modelview and projection matrices
gl_Position = projection * modelview * vec4(vertex, 1.0);
}

View File

@ -0,0 +1,15 @@
//-----------------------------------------------------------------------------
// SolveSpace Indexed Mesh rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
uniform vec4 color;
uniform sampler2D texture;
varying vec2 fragTex;
void main() {
gl_FragColor = texture2D(texture, fragTex) * color;
}

View File

@ -0,0 +1,19 @@
//-----------------------------------------------------------------------------
// SolveSpace Indexed Mesh rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
attribute vec3 pos;
attribute vec2 tex;
uniform mat4 modelview;
uniform mat4 projection;
varying vec2 fragTex;
void main() {
fragTex = tex;
gl_Position = projection * modelview * vec4(pos, 1.0);
}

View File

@ -0,0 +1,15 @@
//-----------------------------------------------------------------------------
// SolveSpace Indexed Mesh rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
uniform vec4 color;
uniform sampler2D texture;
varying vec2 fragTex;
void main() {
gl_FragColor = vec4(color.rgb, color.a * texture2D(texture, fragTex).a);
}

28
res/shaders/mesh.frag Normal file
View File

@ -0,0 +1,28 @@
//-----------------------------------------------------------------------------
// SolveSpace Mesh rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
uniform vec3 lightDir0;
uniform vec3 lightDir1;
uniform float lightInt0;
uniform float lightInt1;
uniform float ambient;
varying vec3 fragNormal;
varying vec4 fragColor;
void main() {
vec3 result = fragColor.xyz * ambient;
vec3 normal = normalize(fragNormal);
float light0 = clamp(dot(lightDir0, normal), 0.0, 1.0) * lightInt0 * (1.0 - ambient);
result += fragColor.rgb * light0;
float light1 = clamp(dot(lightDir1, normal), 0.0, 1.0) * lightInt1 * (1.0 - ambient);
result += fragColor.rgb * light1;
gl_FragColor = vec4(result, fragColor.a);
}

23
res/shaders/mesh.vert Normal file
View File

@ -0,0 +1,23 @@
//-----------------------------------------------------------------------------
// SolveSpace Mesh rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
attribute vec3 pos;
attribute vec3 nor;
attribute vec4 col;
uniform mat4 modelview;
uniform mat4 projection;
varying vec3 fragNormal;
varying vec4 fragColor;
void main() {
fragNormal = vec3(modelview * vec4(nor, 0.0));
fragColor = col;
gl_Position = projection * modelview * vec4(pos, 1.0);
}

View File

@ -0,0 +1,14 @@
//-----------------------------------------------------------------------------
// SolveSpace Mesh rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
uniform vec4 color;
uniform sampler2D texture;
void main() {
if(texture2D(texture, gl_FragCoord.xy / 32.0f).a < 0.5) discard;
gl_FragColor = color;
}

View File

@ -0,0 +1,15 @@
//-----------------------------------------------------------------------------
// SolveSpace Mesh rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
attribute vec3 pos;
uniform mat4 modelview;
uniform mat4 projection;
void main() {
gl_Position = projection * modelview * vec4(pos, 1.0);
}

62
res/shaders/outline.vert Normal file
View File

@ -0,0 +1,62 @@
//-----------------------------------------------------------------------------
// SolveSpace Outline rendering shader
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#version 120
const int EMPHASIZED_AND_CONTOUR = 0;
const int EMPHASIZED_WITHOUT_CONTOUR = 1;
const int CONTOUR_ONLY = 2;
const float feather = 0.5;
attribute vec3 pos;
attribute vec4 loc;
attribute vec3 tan;
attribute vec3 nol;
attribute vec3 nor;
uniform mat4 modelview;
uniform mat4 projection;
uniform float width;
uniform float pixel;
uniform int mode;
varying vec3 fragLoc;
void main() {
// get camera direction from modelview matrix
vec3 dir = vec3(modelview[0].z, modelview[1].z, modelview[2].z);
// perform outline visibility test
float ldot = dot(nol, dir);
float rdot = dot(nor, dir);
bool isOutline = (ldot > -1e-6) == (rdot < 1e-6) ||
(rdot > -1e-6) == (ldot < 1e-6);
bool isTagged = loc.w > 0.5;
float visible = float((mode == CONTOUR_ONLY && isOutline) ||
(mode == EMPHASIZED_AND_CONTOUR && (isOutline || isTagged)) ||
(mode == EMPHASIZED_WITHOUT_CONTOUR && isTagged && !isOutline));
// calculate line contour extension basis for constant width and caps
vec3 norm = normalize(cross(tan, dir));
norm = normalize(norm - dir * dot(dir, norm));
vec3 perp = normalize(cross(dir, norm));
// calculate line extension width considering antialiasing
float ext = (width + feather * pixel) * visible;
// extend line contour
vec3 vertex = pos;
vertex += ext * loc.x * normalize(perp);
vertex += ext * loc.y * normalize(norm);
// write fragment location for calculating caps and antialiasing
fragLoc = vec3(loc);
// transform resulting vertex with modelview and projection matrices
gl_Position = projection * modelview * vec4(vertex, 1.0);
}

View File

@ -70,10 +70,19 @@ if(SPACEWARE_FOUND)
${SPACEWARE_INCLUDE_DIR})
endif()
if(WIN32)
set(gl_SOURCES
render/rendergl1.cpp)
else()
set(gl_SOURCES
render/gl2shader.cpp
render/rendergl2.cpp)
endif()
if(WIN32)
set(platform_SOURCES
platform/w32main.cpp
render/rendergl1.cpp)
${gl_SOURCES})
set(platform_LIBRARIES
comctl32
@ -85,7 +94,7 @@ elseif(APPLE)
set(platform_SOURCES
platform/cocoamain.mm
render/rendergl.cpp
render/rendergl1.cpp)
${gl_SOURCES})
set(platform_BUNDLED_LIBS
${PNG_LIBRARIES}
@ -97,7 +106,7 @@ elseif(HAVE_GTK)
set(platform_SOURCES
platform/gtkmain.cpp
render/rendergl.cpp
render/rendergl1.cpp)
${gl_SOURCES})
set(platform_LIBRARIES
${SPACEWARE_LIBRARIES})
@ -120,6 +129,7 @@ set(solvespace_cad_HEADERS
solvespace.h
ui.h
render/render.h
render/gl2shader.h
srf/surface.h)
set(solvespace_cad_SOURCES

View File

@ -772,6 +772,7 @@ void GraphicsWindow::Paint() {
(int)orig.mouse.y, (int)begin.y,
/*fillColor=*/Style::Color(Style::HOVERED).WithAlpha(25),
/*outlineColor=*/Style::Color(Style::HOVERED));
canvas->EndFrame();
}
// And finally the toolbar.

View File

@ -554,8 +554,11 @@ void Group::Draw(Canvas *canvas) {
? Canvas::DrawOutlinesAs::EMPHASIZED_WITHOUT_CONTOUR
: Canvas::DrawOutlinesAs::EMPHASIZED_AND_CONTOUR);
if(SS.GW.drawOccludedAs == GraphicsWindow::DrawOccludedAs::STIPPLED) {
if(SS.GW.drawOccludedAs != GraphicsWindow::DrawOccludedAs::INVISIBLE) {
Canvas::Stroke strokeHidden = Style::Stroke(Style::HIDDEN_EDGE);
if(SS.GW.drawOccludedAs == GraphicsWindow::DrawOccludedAs::VISIBLE) {
strokeHidden.stipplePattern = StipplePattern::CONTINUOUS;
}
strokeHidden.layer = Canvas::Layer::OCCLUDED;
Canvas::hStroke hcsHidden = canvas->GetStroke(strokeHidden);

1015
src/render/gl2shader.cpp Normal file

File diff suppressed because it is too large Load Diff

248
src/render/gl2shader.h Normal file
View File

@ -0,0 +1,248 @@
//-----------------------------------------------------------------------------
// OpenGL 2 shader interface.
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#ifndef SOLVESPACE_GL2UTILS_H
#define SOLVESPACE_GL2UTILS_H
#ifdef WIN32
# include <windows.h> // required by GL headers
#endif
#ifdef __APPLE__
# include <OpenGL/gl.h>
#else
# define GL_GLEXT_PROTOTYPES
# include <GL/gl.h>
# include <GL/glext.h>
#endif
namespace SolveSpace {
class Vector2f {
public:
float x, y;
static Vector2f From(float x, float y);
static Vector2f From(double x, double y);
static Vector2f FromInt(uint32_t x, uint32_t y);
};
class Vector3f {
public:
float x, y, z;
static Vector3f From(float x, float y, float z);
static Vector3f From(const Vector &v);
static Vector3f From(const RgbaColor &c);
};
class Vector4f {
public:
float w, x, y, z;
static Vector4f From(float x, float y, float z, float w);
static Vector4f From(const Vector &v, float w);
static Vector4f FromInt(uint32_t x, uint32_t y, uint32_t z, uint32_t w);
static Vector4f From(const RgbaColor &c);
};
class Shader {
public:
GLuint program;
void Init(const std::string &vertexRes,
const std::string &fragmentRes,
const std::vector<std::pair<GLuint, std::string>> &locations = {});
void Clear();
void SetUniformMatrix(const char *name, double *md);
void SetUniformVector(const char *name, const Vector &v);
void SetUniformVector(const char *name, const Vector4f &v);
void SetUniformColor(const char *name, RgbaColor c);
void SetUniformFloat(const char *name, float v);
void SetUniformInt(const char *name, GLint v);
void SetUniformTextureUnit(const char *name, GLint index);
void Enable() const;
void Disable() const;
};
class MeshRenderer {
public:
const GLint ATTRIB_POS = 0;
const GLint ATTRIB_NOR = 1;
const GLint ATTRIB_COL = 2;
struct MeshVertex {
Vector3f pos;
Vector3f nor;
Vector4f col;
};
struct Handle {
GLuint vertexBuffer;
GLsizei size;
};
Shader lightShader;
Shader fillShader;
Shader *selectedShader;
void Init();
void Clear();
Handle Add(const SMesh &m, bool dynamic = false);
void Remove(const Handle &handle);
void Draw(const Handle &handle, bool useColors = true, RgbaColor overrideColor = {});
void Draw(const SMesh &mesh, bool useColors = true, RgbaColor overrideColor = {});
void SetModelview(double *matrix);
void SetProjection(double *matrix);
void UseShaded(const Lighting &lighting);
void UseFilled(const Canvas::Fill &fill);
};
class StippleAtlas {
public:
std::vector<GLint> patterns;
void Init();
void Clear();
GLint GetTexture(StipplePattern pattern) const;
double GetLength(StipplePattern pattern) const;
};
class EdgeRenderer {
public:
const GLint ATTRIB_POS = 0;
const GLint ATTRIB_LOC = 1;
const GLint ATTRIB_TAN = 2;
struct EdgeVertex {
Vector3f pos;
Vector3f loc;
Vector3f tan;
};
struct Handle {
GLuint vertexBuffer;
GLuint indexBuffer;
GLsizei size;
};
Shader shader;
const StippleAtlas *atlas;
StipplePattern pattern;
void Init(const StippleAtlas *atlas);
void Clear();
Handle Add(const SEdgeList &edges, bool dynamic = false);
void Remove(const Handle &handle);
void Draw(const Handle &handle);
void Draw(const SEdgeList &edges);
void SetModelview(double *matrix);
void SetProjection(double *matrix);
void SetStroke(const Canvas::Stroke &stroke, double pixel);
};
class OutlineRenderer {
public:
const GLint ATTRIB_POS = 0;
const GLint ATTRIB_LOC = 1;
const GLint ATTRIB_TAN = 2;
const GLint ATTRIB_NOL = 3;
const GLint ATTRIB_NOR = 4;
struct OutlineVertex {
Vector3f pos;
Vector4f loc;
Vector3f tan;
Vector3f nol;
Vector3f nor;
};
struct Handle {
GLuint vertexBuffer;
GLuint indexBuffer;
GLsizei size;
};
Shader shader;
const StippleAtlas *atlas;
StipplePattern pattern;
void Init(const StippleAtlas *atlas);
void Clear();
Handle Add(const SOutlineList &outlines, bool dynamic = false);
void Remove(const Handle &handle);
void Draw(const Handle &handle, Canvas::DrawOutlinesAs mode);
void Draw(const SOutlineList &outlines, Canvas::DrawOutlinesAs mode);
void SetModelview(double *matrix);
void SetProjection(double *matrix);
void SetStroke(const Canvas::Stroke &stroke, double pixel);
};
class SIndexedMesh {
public:
struct Vertex {
Vector3f pos;
Vector2f tex;
};
std::vector<Vertex> vertices;
std::vector<uint32_t> indices;
void AddPoint(const Vector &p);
void AddQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d);
void AddPixmap(const Vector &o, const Vector &u, const Vector &v,
const Point2d &ta, const Point2d &tb);
void Clear();
};
class IndexedMeshRenderer {
public:
const GLint ATTRIB_POS = 0;
const GLint ATTRIB_TEX = 1;
struct Handle {
GLuint vertexBuffer;
GLuint indexBuffer;
GLsizei size;
};
Shader texShader;
Shader texaShader;
Shader colShader;
Shader pointShader;
Shader *selectedShader;
void Init();
void Clear();
Handle Add(const SIndexedMesh &m, bool dynamic = false);
void Remove(const Handle &handle);
void Draw(const Handle &handle);
void Draw(const SIndexedMesh &mesh);
void SetModelview(double *matrix);
void SetProjection(double *matrix);
bool NeedsTexture() const;
void UseFilled(const Canvas::Fill &fill);
void UsePoint(const Canvas::Stroke &stroke, double pixel);
};
}
#endif

View File

@ -186,7 +186,8 @@ bool Canvas::Fill::Equals(const Fill &other) const {
return (layer == other.layer &&
zIndex == other.zIndex &&
color.Equals(other.color) &&
pattern == other.pattern);
pattern == other.pattern &&
texture == other.texture);
}
void Canvas::Clear() {

View File

@ -82,9 +82,9 @@ public:
// Outlines can also be classified as contour or not; contour outlines indicate the boundary
// of the filled mesh. Whether an outline is a part of contour or not depends on point of view.
enum class DrawOutlinesAs {
EMPHASIZED_AND_CONTOUR, // Both emphasized and contour outlines
EMPHASIZED_WITHOUT_CONTOUR, // Emphasized outlines except those also belonging to contour
CONTOUR_ONLY // Contour outlines only
EMPHASIZED_AND_CONTOUR = 0, // Both emphasized and contour outlines
EMPHASIZED_WITHOUT_CONTOUR = 1, // Emphasized outlines except those also belonging to contour
CONTOUR_ONLY = 2 // Contour outlines only
};
// Stroke widths, etc, can be scale-invariant (in pixels) or scale-dependent (in millimeters).
@ -126,6 +126,7 @@ public:
int zIndex;
RgbaColor color;
FillPattern pattern;
std::shared_ptr<const Pixmap> texture;
void Clear() { *this = {}; }
bool Equals(const Fill &other) const;

694
src/render/rendergl2.cpp Normal file
View File

@ -0,0 +1,694 @@
//-----------------------------------------------------------------------------
// OpenGL 2 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;
void DrawEdgesInternal(const SEdgeList &el, hStroke hcs);
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;
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(bool flip = FLIP_FRAMEBUFFER);
void SetCamera(const Camera &c, bool flip) override;
void SetLighting(const Lighting &l) override;
void BeginFrame() override;
void EndFrame() 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:
glDepthRange(0.0, 0.0);
break;
case Canvas::Layer::BACK:
glDepthRange(1.0, 1.0);
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;
glDepthRange(0.1 - offset, 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;
case Pixmap::Format::A: format = GL_ALPHA; break;
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();
}
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::DrawEdgesInternal(const SEdgeList &el, hStroke hcs) {
if(el.l.n == 0) return;
Stroke *stroke = SelectStroke(hcs);
if(stroke->stipplePattern == StipplePattern::ZIGZAG ||
stroke->stipplePattern == StipplePattern::FREEHAND)
{
for(const SEdge *e = el.l.First(); e; e = el.l.NextAfter(e)) {
DoStippledLine(e->a, e->b, hcs);
}
return;
}
edgeRenderer.SetStroke(*stroke, 1.0 / camera.scale);
edgeRenderer.Draw(el);
}
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) {
SEdgeList el = {};
auto traceEdge = [&](Vector a, Vector b) { el.AddEdge(a, b); };
VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera);
DrawEdgesInternal(el, hcs);
el.Clear();
}
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) {
SelectFill(hcfFront);
glEnable(GL_POLYGON_OFFSET_FILL);
meshRenderer.UseShaded(lighting);
meshRenderer.Draw(m);
glDisable(GL_POLYGON_OFFSET_FILL);
}
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(bool flip) {
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;
double fy = flip ? -1.0 : 1.0;
MakeMatrix(mat2,
1, 0, 0, 0,
0, fy, 0, 0,
0, 0, 1, 0,
0, 0, clp, 1
);
// If we flip the framebuffer, then we also flip the handedness
// of the coordinate system, and so the face winding order.
glFrontFace(flip ? GL_CW : GL_CCW);
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);
}
void OpenGl2Renderer::BeginFrame() {
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());
glClearDepth(1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPolygonOffset(2.0, 1.0);
}
void OpenGl2Renderer::EndFrame() {
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) {
DrawEdgesInternal(eli.lines, eli.h);
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, bool flip) {
camera = c;
UpdateProjection(flip);
}
void OpenGl2Renderer::SetLighting(const Lighting &l) {
lighting = l;
}
std::shared_ptr<ViewportCanvas> CreateRenderer() {
return std::shared_ptr<ViewportCanvas>(new OpenGl2Renderer());
}
}

View File

@ -365,6 +365,8 @@ void MakeMatrix(double *mat, double a11, double a12, double a13, double a14,
double a21, double a22, double a23, double a24,
double a31, double a32, double a33, double a34,
double a41, double a42, double a43, double a44);
void MultMatrix(double *mata, double *matb, double *matr);
std::string MakeAcceleratorLabel(int accel);
bool FilenameHasExtension(const std::string &str, const char *ext);
std::string Extension(const std::string &filename);

View File

@ -155,6 +155,18 @@ void SolveSpace::MakeMatrix(double *mat,
mat[15] = a44;
}
void SolveSpace::MultMatrix(double *mata, double *matb, double *matr) {
for(int i = 0; i < 4; i++) {
for(int j = 0; j < 4; j++) {
double s = 0.0;
for(int k = 0; k < 4; k++) {
s += mata[k * 4 + j] * matb[i * 4 + k];
}
matr[i * 4 + j] = s;
}
}
}
//-----------------------------------------------------------------------------
// Word-wrap the string for our message box appropriately, and then display
// that string.