From 6d2c2aecff72183328f3f0a14a24b8be79edbf7e Mon Sep 17 00:00:00 2001 From: EvilSpirit Date: Thu, 30 Jun 2016 21:54:35 +0600 Subject: [PATCH] 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. --- res/CMakeLists.txt | 14 + res/shaders/edge.frag | 37 ++ res/shaders/edge.vert | 43 ++ res/shaders/imesh.frag | 12 + res/shaders/imesh.vert | 15 + res/shaders/imesh_point.frag | 19 + res/shaders/imesh_point.vert | 40 ++ res/shaders/imesh_tex.frag | 15 + res/shaders/imesh_tex.vert | 19 + res/shaders/imesh_texa.frag | 15 + res/shaders/mesh.frag | 28 + res/shaders/mesh.vert | 23 + res/shaders/mesh_fill.frag | 14 + res/shaders/mesh_fill.vert | 15 + res/shaders/outline.vert | 62 +++ src/CMakeLists.txt | 16 +- src/draw.cpp | 1 + src/groupmesh.cpp | 5 +- src/render/gl2shader.cpp | 1015 ++++++++++++++++++++++++++++++++++ src/render/gl2shader.h | 248 +++++++++ src/render/render.cpp | 3 +- src/render/render.h | 7 +- src/render/rendergl2.cpp | 694 +++++++++++++++++++++++ src/solvespace.h | 2 + src/util.cpp | 12 + 25 files changed, 2366 insertions(+), 8 deletions(-) create mode 100644 res/shaders/edge.frag create mode 100644 res/shaders/edge.vert create mode 100644 res/shaders/imesh.frag create mode 100644 res/shaders/imesh.vert create mode 100644 res/shaders/imesh_point.frag create mode 100644 res/shaders/imesh_point.vert create mode 100644 res/shaders/imesh_tex.frag create mode 100644 res/shaders/imesh_tex.vert create mode 100644 res/shaders/imesh_texa.frag create mode 100644 res/shaders/mesh.frag create mode 100644 res/shaders/mesh.vert create mode 100644 res/shaders/mesh_fill.frag create mode 100644 res/shaders/mesh_fill.vert create mode 100644 res/shaders/outline.vert create mode 100644 src/render/gl2shader.cpp create mode 100644 src/render/gl2shader.h create mode 100644 src/render/rendergl2.cpp diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt index dad6bf54..c87a4921 100644 --- a/res/CMakeLists.txt +++ b/res/CMakeLists.txt @@ -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) diff --git a/res/shaders/edge.frag b/res/shaders/edge.frag new file mode 100644 index 00000000..37281d74 --- /dev/null +++ b/res/shaders/edge.frag @@ -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)); +} diff --git a/res/shaders/edge.vert b/res/shaders/edge.vert new file mode 100644 index 00000000..3dfb9b1b --- /dev/null +++ b/res/shaders/edge.vert @@ -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); +} diff --git a/res/shaders/imesh.frag b/res/shaders/imesh.frag new file mode 100644 index 00000000..c3dbff1e --- /dev/null +++ b/res/shaders/imesh.frag @@ -0,0 +1,12 @@ +//----------------------------------------------------------------------------- +// SolveSpace Indexed Mesh rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +#version 120 + +uniform vec4 color; + +void main() { + gl_FragColor = color; +} diff --git a/res/shaders/imesh.vert b/res/shaders/imesh.vert new file mode 100644 index 00000000..c32dcfee --- /dev/null +++ b/res/shaders/imesh.vert @@ -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); +} diff --git a/res/shaders/imesh_point.frag b/res/shaders/imesh_point.frag new file mode 100644 index 00000000..edb31206 --- /dev/null +++ b/res/shaders/imesh_point.frag @@ -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; +} diff --git a/res/shaders/imesh_point.vert b/res/shaders/imesh_point.vert new file mode 100644 index 00000000..3a3eb774 --- /dev/null +++ b/res/shaders/imesh_point.vert @@ -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); +} diff --git a/res/shaders/imesh_tex.frag b/res/shaders/imesh_tex.frag new file mode 100644 index 00000000..4113f7ad --- /dev/null +++ b/res/shaders/imesh_tex.frag @@ -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; +} diff --git a/res/shaders/imesh_tex.vert b/res/shaders/imesh_tex.vert new file mode 100644 index 00000000..1ec88d14 --- /dev/null +++ b/res/shaders/imesh_tex.vert @@ -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); +} diff --git a/res/shaders/imesh_texa.frag b/res/shaders/imesh_texa.frag new file mode 100644 index 00000000..4cfa2a4f --- /dev/null +++ b/res/shaders/imesh_texa.frag @@ -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); +} diff --git a/res/shaders/mesh.frag b/res/shaders/mesh.frag new file mode 100644 index 00000000..18de9b74 --- /dev/null +++ b/res/shaders/mesh.frag @@ -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); +} diff --git a/res/shaders/mesh.vert b/res/shaders/mesh.vert new file mode 100644 index 00000000..c06375ae --- /dev/null +++ b/res/shaders/mesh.vert @@ -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); +} diff --git a/res/shaders/mesh_fill.frag b/res/shaders/mesh_fill.frag new file mode 100644 index 00000000..09658621 --- /dev/null +++ b/res/shaders/mesh_fill.frag @@ -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; +} diff --git a/res/shaders/mesh_fill.vert b/res/shaders/mesh_fill.vert new file mode 100644 index 00000000..83abcbd8 --- /dev/null +++ b/res/shaders/mesh_fill.vert @@ -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); +} diff --git a/res/shaders/outline.vert b/res/shaders/outline.vert new file mode 100644 index 00000000..36141d2c --- /dev/null +++ b/res/shaders/outline.vert @@ -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); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 346d5259..ff782ee7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/draw.cpp b/src/draw.cpp index dc41f31d..4f080643 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -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. diff --git a/src/groupmesh.cpp b/src/groupmesh.cpp index 420478af..365423fa 100644 --- a/src/groupmesh.cpp +++ b/src/groupmesh.cpp @@ -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); diff --git a/src/render/gl2shader.cpp b/src/render/gl2shader.cpp new file mode 100644 index 00000000..912ed504 --- /dev/null +++ b/src/render/gl2shader.cpp @@ -0,0 +1,1015 @@ +//----------------------------------------------------------------------------- +// OpenGL 2 shader interface. +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +#include "solvespace.h" +#include "gl2shader.h" + +namespace SolveSpace { + +//----------------------------------------------------------------------------- +// Floating point data sturctures +//----------------------------------------------------------------------------- + +Vector2f Vector2f::From(float x, float y) { + return { x, y }; +} + +Vector2f Vector2f::From(double x, double y) { + return { (float)x, (float)y }; +} + +Vector2f Vector2f::FromInt(uint32_t x, uint32_t y) { + return { (float)x, (float)y }; +} + +Vector3f Vector3f::From(float x, float y, float z) { + return { x, y, z }; +} + +Vector3f Vector3f::From(const Vector &v) { + return { (float)v.x, (float)v.y, (float)v.z }; +} + +Vector3f Vector3f::From(const RgbaColor &c) { + return { c.redF(), c.greenF(), c.blueF() }; +} + +Vector4f Vector4f::From(float x, float y, float z, float w) { + return { x, y, z, w }; +} + +Vector4f Vector4f::From(const Vector &v, float w) { + return { (float)v.x, (float)v.y, (float)v.z, w }; +} + +Vector4f Vector4f::FromInt(uint32_t x, uint32_t y, uint32_t z, uint32_t w) { + return { (float)x, (float)y, (float)z, (float)w }; +} + +Vector4f Vector4f::From(const RgbaColor &c) { + return { c.redF(), c.greenF(), c.blueF(), c.alphaF() }; +} + +//----------------------------------------------------------------------------- +// Shader manipulation +//----------------------------------------------------------------------------- + +static GLuint CompileShader(const std::string &res, GLenum type) { + size_t size; + const char *src = (const char *)LoadResource(res, &size); + + GLuint shader = glCreateShader(type); + ssassert(shader != 0, "glCreateShader failed"); + + GLint glSize = size; + glShaderSource(shader, 1, &src, &glSize); + glCompileShader(shader); + + GLint infoLen; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + if(infoLen > 1) { + std::string infoStr(infoLen, '\0'); + glGetShaderInfoLog(shader, infoLen, NULL, &infoStr[0]); + dbp(infoStr.c_str()); + } + + GLint compiled; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + ssassert(compiled, "Cannot compile shader"); + + return shader; +} + +void Shader::Init(const std::string &vertexRes, const std::string &fragmentRes, + const std::vector > &locations) { + GLuint vert = CompileShader(vertexRes, GL_VERTEX_SHADER); + GLuint frag = CompileShader(fragmentRes, GL_FRAGMENT_SHADER); + + program = glCreateProgram(); + ssassert(program != 0, "glCreateProgram failed"); + + glAttachShader(program, vert); + glAttachShader(program, frag); + for(const auto &l : locations) { + glBindAttribLocation(program, l.first, l.second.c_str()); + } + glLinkProgram(program); + + GLint infoLen; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen); + if(infoLen > 1) { + std::string infoStr(infoLen, '\0'); + glGetProgramInfoLog(program, infoLen, NULL, &infoStr[0]); + dbp(infoStr.c_str()); + } + + GLint linked; + glGetProgramiv(program, GL_LINK_STATUS, &linked); + ssassert(linked, "Cannot link shader"); +} + +void Shader::Clear() { + glDeleteProgram(program); +} + +void Shader::SetUniformMatrix(const char *name, double *md) { + Enable(); + float mf[16]; + for(int i = 0; i < 16; i++) mf[i] = (float)md[i]; + glUniformMatrix4fv(glGetUniformLocation(program, name), 1, false, mf); +} + +void Shader::SetUniformVector(const char *name, const Vector &v) { + Enable(); + glUniform3f(glGetUniformLocation(program, name), (float)v.x, (float)v.y, (float)v.z); +} + +void Shader::SetUniformVector(const char *name, const Vector4f &v) { + Enable(); + glUniform4f(glGetUniformLocation(program, name), v.x, v.y, v.z, v.w); +} + +void Shader::SetUniformColor(const char *name, RgbaColor c) { + Enable(); + glUniform4f(glGetUniformLocation(program, name), c.redF(), c.greenF(), c.blueF(), c.alphaF()); +} + +void Shader::SetUniformFloat(const char *name, float v) { + Enable(); + glUniform1f(glGetUniformLocation(program, name), v); +} + +void Shader::SetUniformInt(const char *name, GLint v) { + Enable(); + glUniform1i(glGetUniformLocation(program, name), v); +} + +void Shader::SetUniformTextureUnit(const char *name, GLint index) { + Enable(); + glUniform1i(glGetUniformLocation(program, name), index); +} + +void Shader::Enable() const { + glUseProgram(program); +} + +void Shader::Disable() const { + glUseProgram(0); +} + +//----------------------------------------------------------------------------- +// Mesh rendering +//----------------------------------------------------------------------------- + +void MeshRenderer::Init() { + lightShader.Init( + "shaders/mesh.vert", "shaders/mesh.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_NOR, "nor" }, + { ATTRIB_COL, "col" }, + } + ); + + fillShader.Init( + "shaders/mesh_fill.vert", "shaders/mesh_fill.frag", + { + { ATTRIB_POS, "pos" }, + } + ); + fillShader.SetUniformTextureUnit("texture", 0); + + selectedShader = &lightShader; +} + +void MeshRenderer::Clear() { + lightShader.Clear(); + fillShader.Clear(); +} + +MeshRenderer::Handle MeshRenderer::Add(const SMesh &m, bool dynamic) { + Handle handle; + glGenBuffers(1, &handle.vertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + + MeshVertex *vertices = new MeshVertex[m.l.n * 3]; + for(int i = 0; i < m.l.n; i++) { + const STriangle &t = m.l.elem[i]; + vertices[i * 3 + 0].pos = Vector3f::From(t.a); + vertices[i * 3 + 1].pos = Vector3f::From(t.b); + vertices[i * 3 + 2].pos = Vector3f::From(t.c); + + if(t.an.EqualsExactly(Vector::From(0, 0, 0))) { + Vector3f normal = Vector3f::From(t.Normal()); + vertices[i * 3 + 0].nor = normal; + vertices[i * 3 + 1].nor = normal; + vertices[i * 3 + 2].nor = normal; + } else { + vertices[i * 3 + 0].nor = Vector3f::From(t.an); + vertices[i * 3 + 1].nor = Vector3f::From(t.bn); + vertices[i * 3 + 2].nor = Vector3f::From(t.cn); + } + + for(int j = 0; j < 3; j++) { + vertices[i * 3 + j].col = Vector4f::From(t.meta.color); + } + + } + glBufferData(GL_ARRAY_BUFFER, m.l.n * 3 * sizeof(MeshVertex), + vertices, dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); + handle.size = m.l.n * 3; + delete []vertices; + + return handle; +} + +void MeshRenderer::Remove(const MeshRenderer::Handle &handle) { + glDeleteBuffers(1, &handle.vertexBuffer); +} + +void MeshRenderer::Draw(const MeshRenderer::Handle &handle, + bool useColors, RgbaColor overrideColor) { + selectedShader->Enable(); + + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + + glEnableVertexAttribArray(ATTRIB_POS); + glVertexAttribPointer(ATTRIB_POS, 3, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), + (void *)offsetof(MeshVertex, pos)); + + if(selectedShader == &lightShader) { + glEnableVertexAttribArray(ATTRIB_NOR); + glVertexAttribPointer(ATTRIB_NOR, 3, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), + (void *)offsetof(MeshVertex, nor)); + if(useColors) { + glEnableVertexAttribArray(ATTRIB_COL); + glVertexAttribPointer(ATTRIB_COL, 4, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), + (void *)offsetof(MeshVertex, col)); + } else { + glVertexAttrib4f(ATTRIB_COL, overrideColor.redF(), overrideColor.greenF(), overrideColor.blueF(), overrideColor.alphaF()); + } + } + + glDrawArrays(GL_TRIANGLES, 0, handle.size); + + glDisableVertexAttribArray(ATTRIB_POS); + if(selectedShader == &lightShader) { + glDisableVertexAttribArray(ATTRIB_NOR); + if(useColors) glDisableVertexAttribArray(ATTRIB_COL); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + + selectedShader->Disable(); +} + +void MeshRenderer::Draw(const SMesh &mesh, bool useColors, RgbaColor overrideColor) { + Handle handle = Add(mesh, /*dynamic=*/true); + Draw(handle, useColors, overrideColor); + Remove(handle); +} + +void MeshRenderer::SetModelview(double *matrix) { + lightShader.SetUniformMatrix("modelview", matrix); + fillShader.SetUniformMatrix("modelview", matrix); +} + +void MeshRenderer::SetProjection(double *matrix) { + lightShader.SetUniformMatrix("projection", matrix); + fillShader.SetUniformMatrix("projection", matrix); +} + +void MeshRenderer::UseShaded(const Lighting &lighting) { + Vector dir0 = lighting.lightDirection[0]; + Vector dir1 = lighting.lightDirection[1]; + dir0.z = -dir0.z; + dir1.z = -dir1.z; + + lightShader.SetUniformVector("lightDir0", dir0); + lightShader.SetUniformFloat("lightInt0", (float)lighting.lightIntensity[0]); + lightShader.SetUniformVector("lightDir1", dir1); + lightShader.SetUniformFloat("lightInt1", (float)lighting.lightIntensity[1]); + lightShader.SetUniformFloat("ambient", (float)lighting.ambientIntensity); + selectedShader = &lightShader; +} + +void MeshRenderer::UseFilled(const Canvas::Fill &fill) { + fillShader.SetUniformColor("color", fill.color); + selectedShader = &fillShader; +} + +//----------------------------------------------------------------------------- +// Arrangement of stipple patterns into textures +//----------------------------------------------------------------------------- + +static double Frac(double x) { + return x - floor(x); +} + +static RgbaColor EncodeLengthAsFloat(double v) { + v = max(0.0, min(1.0, v)); + double er = v; + double eg = Frac(255.0 * v); + double eb = Frac(65025.0 * v); + double ea = Frac(160581375.0 * v); + + double r = er - eg / 255.0; + double g = eg - eb / 255.0; + double b = eb - ea / 255.0; + return RgbaColor::From((int)floor( r * 255.0 + 0.5), + (int)floor( g * 255.0 + 0.5), + (int)floor( b * 255.0 + 0.5), + (int)floor(ea * 255.0 + 0.5)); +} + +GLuint Generate(const std::vector &pattern) { + double patternLen = 0.0; + for(double s : pattern) { + patternLen += s; + } + + GLuint texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + + GLint size; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &size); + RgbaColor *textureData = new RgbaColor[size]; + + int mipCount = (int)log2(size) + 1; + for(int mip = 0; mip < mipCount; mip++) { + int dashI = 0; + double dashT = 0.0; + for(int i = 0; i < size; i++) { + if(pattern.size() == 0) { + textureData[i] = EncodeLengthAsFloat(0.0); + continue; + } + + double t = (double)i / (double)(size - 1); + while(t - LENGTH_EPS > dashT + pattern[dashI] / patternLen) { + dashT += pattern[dashI] / patternLen; + dashI++; + } + double dashW = pattern[dashI] / patternLen; + if(dashI % 2 == 0) { + textureData[i] = EncodeLengthAsFloat(0.0); + } else { + double value; + if(t - dashT < pattern[dashI] / patternLen / 2.0) { + value = t - dashT; + } else { + value = dashT + dashW - t; + } + value = value * patternLen; + textureData[i] = EncodeLengthAsFloat(value); + } + } + glTexImage2D(GL_TEXTURE_2D, mip, GL_RGBA, size, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, + textureData); + size /= 2; + } + + delete []textureData; + return texture; +} + +void StippleAtlas::Init() { + for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) { + patterns.push_back(Generate(StipplePatternDashes((StipplePattern)i))); + } +} + +void StippleAtlas::Clear() { + for(GLuint p : patterns) { + glDeleteTextures(1, &p); + } +} + +GLint StippleAtlas::GetTexture(StipplePattern pattern) const { + return patterns[(uint32_t)pattern]; +} + +double StippleAtlas::GetLength(StipplePattern pattern) const { + if(pattern == StipplePattern::CONTINUOUS) { + return 1.0; + } + return StipplePatternLength(pattern); +} + +//----------------------------------------------------------------------------- +// Edge rendering +//----------------------------------------------------------------------------- + +void EdgeRenderer::Init(const StippleAtlas *a) { + atlas = a; + shader.Init( + "shaders/edge.vert", "shaders/edge.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_LOC, "loc" }, + { ATTRIB_TAN, "tan" } + } + ); +} + +void EdgeRenderer::Clear() { + shader.Clear(); +} + +EdgeRenderer::Handle EdgeRenderer::Add(const SEdgeList &edges, bool dynamic) { + Handle handle; + glGenBuffers(1, &handle.vertexBuffer); + glGenBuffers(1, &handle.indexBuffer); + + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle.indexBuffer); + + EdgeVertex *vertices = new EdgeVertex[edges.l.n * 8]; + uint32_t *indices = new uint32_t[edges.l.n * 6 * 3]; + double phase = 0.0; + uint32_t curVertex = 0; + uint32_t curIndex = 0; + for(int i = 0; i < edges.l.n; i++) { + const SEdge &curr = edges.l.elem[i]; + const SEdge &next = edges.l.elem[(i + 1) % edges.l.n]; + + // 3d positions + Vector3f a = Vector3f::From(curr.a); + Vector3f b = Vector3f::From(curr.b); + + // tangent + Vector3f tan = Vector3f::From(curr.b.Minus(curr.a)); + + // length + double len = curr.b.Minus(curr.a).Magnitude(); + + // make line start cap + for(int j = 0; j < 2; j++) { + vertices[curVertex + j].pos = a; + vertices[curVertex + j].tan = tan; + } + vertices[curVertex + 0].loc = Vector3f::From(-1.0f, -1.0f, float(phase)); + vertices[curVertex + 1].loc = Vector3f::From(-1.0f, +1.0f, float(phase)); + + indices[curIndex++] = curVertex + 0; + indices[curIndex++] = curVertex + 1; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 1; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 3; + + curVertex += 2; + + // make line body + vertices[curVertex + 0].pos = a; + vertices[curVertex + 1].pos = a; + vertices[curVertex + 2].pos = b; + vertices[curVertex + 3].pos = b; + + for(int j = 0; j < 4; j++) { + vertices[curVertex + j].tan = tan; + } + + vertices[curVertex + 0].loc = Vector3f::From( 0.0f, -1.0f, float(phase)); + vertices[curVertex + 1].loc = Vector3f::From( 0.0f, +1.0f, float(phase)); + vertices[curVertex + 2].loc = Vector3f::From( 0.0f, +1.0f, float(phase + len)); + vertices[curVertex + 3].loc = Vector3f::From( 0.0f, -1.0f, float(phase + len)); + + indices[curIndex++] = curVertex + 0; + indices[curIndex++] = curVertex + 1; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 0; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 3; + + curVertex += 4; + + // make line end cap + for(int j = 0; j < 2; j++) { + vertices[curVertex + j].pos = b; + vertices[curVertex + j].tan = tan; + } + + vertices[curVertex + 0].loc = Vector3f::From(+1.0, +1.0, float(phase + len)); + vertices[curVertex + 1].loc = Vector3f::From(+1.0, -1.0, float(phase + len)); + + indices[curIndex++] = curVertex - 2; + indices[curIndex++] = curVertex - 1; + indices[curIndex++] = curVertex; + indices[curIndex++] = curVertex - 1; + indices[curIndex++] = curVertex; + indices[curIndex++] = curVertex + 1; + + curVertex += 2; + + // phase stitching + if(curr.a.EqualsExactly(next.a) || + curr.a.EqualsExactly(next.b) || + curr.b.EqualsExactly(next.a) || + curr.b.EqualsExactly(next.b)) + { + phase += len; + } else { + phase = 0.0; + } + } + handle.size = curIndex; + GLenum mode = dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW; + glBufferData(GL_ARRAY_BUFFER, curVertex * sizeof(EdgeVertex), vertices, mode); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, curIndex * sizeof(uint32_t), indices, mode); + delete []vertices; + delete []indices; + + return handle; +} + +void EdgeRenderer::Remove(const EdgeRenderer::Handle &handle) { + glDeleteBuffers(1, &handle.vertexBuffer); + glDeleteBuffers(1, &handle.indexBuffer); +} + +void EdgeRenderer::Draw(const EdgeRenderer::Handle &handle) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, atlas->GetTexture(pattern)); + shader.SetUniformTextureUnit("pattern", 1); + shader.SetUniformFloat("patternLen", (float)atlas->GetLength(pattern)); + + shader.Enable(); + + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle.indexBuffer); + + glEnableVertexAttribArray(ATTRIB_POS); + glEnableVertexAttribArray(ATTRIB_LOC); + glEnableVertexAttribArray(ATTRIB_TAN); + + glVertexAttribPointer(ATTRIB_POS, 3, GL_FLOAT, GL_FALSE, sizeof(EdgeVertex), (void *)offsetof(EdgeVertex, pos)); + glVertexAttribPointer(ATTRIB_LOC, 3, GL_FLOAT, GL_FALSE, sizeof(EdgeVertex), (void *)offsetof(EdgeVertex, loc)); + glVertexAttribPointer(ATTRIB_TAN, 3, GL_FLOAT, GL_FALSE, sizeof(EdgeVertex), (void *)offsetof(EdgeVertex, tan)); + glDrawElements(GL_TRIANGLES, handle.size, GL_UNSIGNED_INT, NULL); + + glDisableVertexAttribArray(ATTRIB_POS); + glDisableVertexAttribArray(ATTRIB_LOC); + glDisableVertexAttribArray(ATTRIB_TAN); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + shader.Disable(); +} + +void EdgeRenderer::Draw(const SEdgeList &edges) { + Handle handle = Add(edges, /*dynamic=*/true); + Draw(handle); + Remove(handle); +} + +void EdgeRenderer::SetModelview(double *matrix) { + shader.SetUniformMatrix("modelview", matrix); +} + +void EdgeRenderer::SetProjection(double *matrix) { + shader.SetUniformMatrix("projection", matrix); +} + +void EdgeRenderer::SetStroke(const Canvas::Stroke &stroke, double pixel) { + double unitScale = stroke.unit == Canvas::Unit::PX ? pixel : 1.0; + shader.SetUniformFloat("width", float(stroke.width * unitScale / 2.0)); + shader.SetUniformColor("color", stroke.color); + shader.SetUniformFloat("patternScale", float(stroke.stippleScale * unitScale * 2.0)); + shader.SetUniformFloat("pixel", (float)pixel); + pattern = stroke.stipplePattern; +} + +//----------------------------------------------------------------------------- +// Outline rendering +//----------------------------------------------------------------------------- + +void OutlineRenderer::Init(const StippleAtlas *a) { + atlas = a; + shader.Init( + "shaders/outline.vert", "shaders/edge.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_LOC, "loc" }, + { ATTRIB_TAN, "tan" }, + { ATTRIB_NOL, "nol" }, + { ATTRIB_NOR, "nor" } + } + ); +} + +void OutlineRenderer::Clear() { + shader.Clear(); +} + +OutlineRenderer::Handle OutlineRenderer::Add(const SOutlineList &outlines, bool dynamic) { + Handle handle; + glGenBuffers(1, &handle.vertexBuffer); + glGenBuffers(1, &handle.indexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle.indexBuffer); + + OutlineVertex *vertices = new OutlineVertex[outlines.l.n * 8]; + uint32_t *indices = new uint32_t[outlines.l.n * 6 * 3]; + double phase = 0.0; + uint32_t curVertex = 0; + uint32_t curIndex = 0; + + for(int i = 0; i < outlines.l.n; i++) { + const SOutline &curr = outlines.l.elem[i]; + const SOutline &next = outlines.l.elem[(i + 1) % outlines.l.n]; + + // 3d positions + Vector3f a = Vector3f::From(curr.a); + Vector3f b = Vector3f::From(curr.b); + Vector3f nl = Vector3f::From(curr.nl); + Vector3f nr = Vector3f::From(curr.nr); + + // tangent + Vector3f tan = Vector3f::From(curr.b.Minus(curr.a)); + + // length + double len = curr.b.Minus(curr.a).Magnitude(); + float tag = (float)curr.tag; + + // make line start cap + for(int j = 0; j < 2; j++) { + vertices[curVertex + j].pos = a; + vertices[curVertex + j].nol = nl; + vertices[curVertex + j].nor = nr; + vertices[curVertex + j].tan = tan; + } + + vertices[curVertex + 0].loc = Vector4f::From(-1.0f, -1.0f, float(phase), (float)tag); + vertices[curVertex + 1].loc = Vector4f::From(-1.0f, +1.0f, float(phase), (float)tag); + + indices[curIndex++] = curVertex; + indices[curIndex++] = curVertex + 1; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 1; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 3; + + curVertex += 2; + + // make line body + vertices[curVertex + 0].pos = a; + vertices[curVertex + 1].pos = a; + vertices[curVertex + 2].pos = b; + vertices[curVertex + 3].pos = b; + + for(int j = 0; j < 4; j++) { + vertices[curVertex + j].nol = nl; + vertices[curVertex + j].nor = nr; + vertices[curVertex + j].tan = tan; + } + + vertices[curVertex + 0].loc = Vector4f::From( 0.0f, -1.0f, float(phase), (float)tag); + vertices[curVertex + 1].loc = Vector4f::From( 0.0f, +1.0f, float(phase), (float)tag); + vertices[curVertex + 2].loc = Vector4f::From( 0.0f, +1.0f, + float(phase + len), (float)tag); + vertices[curVertex + 3].loc = Vector4f::From( 0.0f, -1.0f, + float(phase + len), (float)tag); + + indices[curIndex++] = curVertex + 0; + indices[curIndex++] = curVertex + 1; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 0; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 3; + + curVertex += 4; + + // make line end cap + for(int j = 0; j < 2; j++) { + vertices[curVertex + j].pos = b; + vertices[curVertex + j].nol = nl; + vertices[curVertex + j].nor = nr; + vertices[curVertex + j].tan = tan; + } + + vertices[curVertex + 0].loc = Vector4f::From(+1.0f, +1.0f, float(phase + len), (float)tag); + vertices[curVertex + 1].loc = Vector4f::From(+1.0f, -1.0f, float(phase + len), (float)tag); + + indices[curIndex++] = curVertex - 2; + indices[curIndex++] = curVertex - 1; + indices[curIndex++] = curVertex; + indices[curIndex++] = curVertex - 1; + indices[curIndex++] = curVertex; + indices[curIndex++] = curVertex + 1; + + curVertex += 2; + + // phase stitching + if(curr.a.EqualsExactly(next.a) || + curr.a.EqualsExactly(next.b) || + curr.b.EqualsExactly(next.a) || + curr.b.EqualsExactly(next.b)) + { + phase += len; + } else { + phase = 0.0; + } + } + handle.size = curIndex; + GLenum mode = dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW; + glBufferData(GL_ARRAY_BUFFER, curVertex * sizeof(OutlineVertex), vertices, mode); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, curIndex * sizeof(uint32_t), indices, mode); + + delete []vertices; + delete []indices; + return handle; +} + +void OutlineRenderer::Remove(const OutlineRenderer::Handle &handle) { + glDeleteBuffers(1, &handle.vertexBuffer); + glDeleteBuffers(1, &handle.indexBuffer); +} + +void OutlineRenderer::Draw(const OutlineRenderer::Handle &handle, Canvas::DrawOutlinesAs mode) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, atlas->GetTexture(pattern)); + shader.SetUniformTextureUnit("pattern", 1); + shader.SetUniformFloat("patternLen", (float)atlas->GetLength(pattern)); + shader.SetUniformInt("mode", (GLint)mode); + + shader.Enable(); + + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle.indexBuffer); + + glEnableVertexAttribArray(ATTRIB_POS); + glEnableVertexAttribArray(ATTRIB_LOC); + glEnableVertexAttribArray(ATTRIB_TAN); + glEnableVertexAttribArray(ATTRIB_NOL); + glEnableVertexAttribArray(ATTRIB_NOR); + + glVertexAttribPointer(ATTRIB_POS, 3, GL_FLOAT, GL_FALSE, sizeof(OutlineVertex), + (void *)offsetof(OutlineVertex, pos)); + glVertexAttribPointer(ATTRIB_LOC, 4, GL_FLOAT, GL_FALSE, sizeof(OutlineVertex), + (void *)offsetof(OutlineVertex, loc)); + glVertexAttribPointer(ATTRIB_TAN, 3, GL_FLOAT, GL_FALSE, sizeof(OutlineVertex), + (void *)offsetof(OutlineVertex, tan)); + glVertexAttribPointer(ATTRIB_NOL, 3, GL_FLOAT, GL_FALSE, sizeof(OutlineVertex), + (void *)offsetof(OutlineVertex, nol)); + glVertexAttribPointer(ATTRIB_NOR, 3, GL_FLOAT, GL_FALSE, sizeof(OutlineVertex), + (void *)offsetof(OutlineVertex, nor)); + glDrawElements(GL_TRIANGLES, handle.size, GL_UNSIGNED_INT, NULL); + + glDisableVertexAttribArray(ATTRIB_POS); + glDisableVertexAttribArray(ATTRIB_LOC); + glDisableVertexAttribArray(ATTRIB_TAN); + glDisableVertexAttribArray(ATTRIB_NOL); + glDisableVertexAttribArray(ATTRIB_NOR); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + shader.Disable(); +} + +void OutlineRenderer::Draw(const SOutlineList &outlines, Canvas::DrawOutlinesAs drawAs) { + Handle handle = Add(outlines, /*dynamic=*/true); + Draw(handle, drawAs); + Remove(handle); +} + +void OutlineRenderer::SetModelview(double *matrix) { + shader.SetUniformMatrix("modelview", matrix); +} + +void OutlineRenderer::SetProjection(double *matrix) { + shader.SetUniformMatrix("projection", matrix); +} + +void OutlineRenderer::SetStroke(const Canvas::Stroke &stroke, double pixel) { + double unitScale = (stroke.unit == Canvas::Unit::PX) ? pixel : 1.0; + shader.SetUniformFloat("width", (float)(stroke.width * unitScale / 2.0)); + shader.SetUniformColor("color", stroke.color); + shader.SetUniformFloat("patternScale", (float)(stroke.stippleScale * unitScale * 2.0)); + shader.SetUniformFloat("pixel", (float)pixel); + pattern = stroke.stipplePattern; +} + +//----------------------------------------------------------------------------- +// Indexed mesh storage +//----------------------------------------------------------------------------- + +void SIndexedMesh::AddPoint(const Vector &p) { + uint32_t vstart = vertices.size(); + vertices.resize(vertices.size() + 4); + + vertices[vstart + 0].pos = Vector3f::From(p); + vertices[vstart + 0].tex = Vector2f::From(-1.0f, -1.0f); + vertices[vstart + 1].pos = Vector3f::From(p); + vertices[vstart + 1].tex = Vector2f::From(+1.0f, -1.0f); + vertices[vstart + 2].pos = Vector3f::From(p); + vertices[vstart + 2].tex = Vector2f::From(+1.0f, +1.0f); + vertices[vstart + 3].pos = Vector3f::From(p); + vertices[vstart + 3].tex = Vector2f::From(-1.0f, +1.0f); + + size_t istart = indices.size(); + indices.resize(indices.size() + 6); + + indices[istart + 0] = vstart + 0; + indices[istart + 1] = vstart + 1; + indices[istart + 2] = vstart + 2; + indices[istart + 3] = vstart + 0; + indices[istart + 4] = vstart + 2; + indices[istart + 5] = vstart + 3; +} + +void SIndexedMesh::AddQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d) { + uint32_t vstart = vertices.size(); + vertices.resize(vertices.size() + 4); + + vertices[vstart + 0].pos = Vector3f::From(a); + vertices[vstart + 1].pos = Vector3f::From(b); + vertices[vstart + 2].pos = Vector3f::From(c); + vertices[vstart + 3].pos = Vector3f::From(d); + + size_t istart = indices.size(); + indices.resize(indices.size() + 6); + + indices[istart + 0] = vstart + 0; + indices[istart + 1] = vstart + 1; + indices[istart + 2] = vstart + 2; + indices[istart + 3] = vstart + 0; + indices[istart + 4] = vstart + 2; + indices[istart + 5] = vstart + 3; +} + +void SIndexedMesh::AddPixmap(const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb) { + uint32_t vstart = vertices.size(); + vertices.resize(vertices.size() + 4); + + vertices[vstart + 0].pos = Vector3f::From(o); + vertices[vstart + 0].tex = Vector2f::From(ta.x, ta.y); + + vertices[vstart + 1].pos = Vector3f::From(o.Plus(v)); + vertices[vstart + 1].tex = Vector2f::From(ta.x, tb.y); + + vertices[vstart + 2].pos = Vector3f::From(o.Plus(u).Plus(v)); + vertices[vstart + 2].tex = Vector2f::From(tb.x, tb.y); + + vertices[vstart + 3].pos = Vector3f::From(o.Plus(u)); + vertices[vstart + 3].tex = Vector2f::From(tb.x, ta.y); + + size_t istart = indices.size(); + indices.resize(indices.size() + 6); + + indices[istart + 0] = vstart + 0; + indices[istart + 1] = vstart + 1; + indices[istart + 2] = vstart + 2; + indices[istart + 3] = vstart + 0; + indices[istart + 4] = vstart + 2; + indices[istart + 5] = vstart + 3; +} + +void SIndexedMesh::Clear() { + vertices.clear(); + indices.clear(); +} + +//----------------------------------------------------------------------------- +// Indexed mesh rendering +//----------------------------------------------------------------------------- + +void IndexedMeshRenderer::Init() { + colShader.Init( + "shaders/imesh.vert", "shaders/imesh.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_TEX, "tex" } + } + ); + texShader.Init( + "shaders/imesh_tex.vert", "shaders/imesh_tex.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_TEX, "tex" } + } + ); + texaShader.Init( + "shaders/imesh_tex.vert", "shaders/imesh_texa.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_TEX, "tex" } + } + ); + pointShader.Init( + "shaders/imesh_point.vert", "shaders/imesh_point.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_TEX, "loc" } + } + ); + + texShader.SetUniformTextureUnit("texture", 0); + texaShader.SetUniformTextureUnit("texture", 0); + selectedShader = &colShader; +} + +void IndexedMeshRenderer::Clear() { + texShader.Clear(); + texaShader.Clear(); + colShader.Clear(); + pointShader.Clear(); +} + +IndexedMeshRenderer::Handle IndexedMeshRenderer::Add(const SIndexedMesh &m, bool dynamic) { + Handle handle; + glGenBuffers(1, &handle.vertexBuffer); + glGenBuffers(1, &handle.indexBuffer); + + GLenum mode = dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW; + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + glBufferData(GL_ARRAY_BUFFER, m.vertices.size() * sizeof(SIndexedMesh::Vertex), + m.vertices.data(), mode); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle.indexBuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, m.indices.size() * sizeof(uint32_t), + m.indices.data(), mode); + handle.size = m.indices.size(); + return handle; +} + +void IndexedMeshRenderer::Remove(const IndexedMeshRenderer::Handle &m) { + glDeleteBuffers(1, &m.vertexBuffer); + glDeleteBuffers(1, &m.indexBuffer); +} + +void IndexedMeshRenderer::Draw(const IndexedMeshRenderer::Handle &m) { + selectedShader->Enable(); + + glBindBuffer(GL_ARRAY_BUFFER, m.vertexBuffer); + glEnableVertexAttribArray(ATTRIB_POS); + glVertexAttribPointer(ATTRIB_POS, 3, GL_FLOAT, GL_FALSE, sizeof(SIndexedMesh::Vertex), + (void *)offsetof(SIndexedMesh::Vertex, pos)); + if(NeedsTexture()) { + glEnableVertexAttribArray(ATTRIB_TEX); + glVertexAttribPointer(ATTRIB_TEX, 2, GL_FLOAT, GL_FALSE, sizeof(SIndexedMesh::Vertex), + (void *)offsetof(SIndexedMesh::Vertex, tex)); + } + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m.indexBuffer); + glDrawElements(GL_TRIANGLES, m.size, GL_UNSIGNED_INT, NULL); + + glDisableVertexAttribArray(ATTRIB_POS); + if(NeedsTexture()) glDisableVertexAttribArray(ATTRIB_TEX); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + selectedShader->Disable(); +} + +void IndexedMeshRenderer::Draw(const SIndexedMesh &mesh) { + Handle handle = Add(mesh, /*dynamic=*/true) ; + Draw(handle); + Remove(handle); +} + +bool IndexedMeshRenderer::NeedsTexture() const { + return selectedShader == &texShader || + selectedShader == &texaShader || + selectedShader == &pointShader; +} + +void IndexedMeshRenderer::SetModelview(double *matrix) { + colShader.SetUniformMatrix("modelview", matrix); + texShader.SetUniformMatrix("modelview", matrix); + texaShader.SetUniformMatrix("modelview", matrix); + pointShader.SetUniformMatrix("modelview", matrix); +} + +void IndexedMeshRenderer::SetProjection(double *matrix) { + colShader.SetUniformMatrix("projection", matrix); + texShader.SetUniformMatrix("projection", matrix); + texaShader.SetUniformMatrix("projection", matrix); + pointShader.SetUniformMatrix("projection", matrix); +} + +void IndexedMeshRenderer::UseFilled(const Canvas::Fill &fill) { + if(fill.texture) { + selectedShader = (fill.texture->format == Pixmap::Format::A) ? &texaShader : &texShader; + } else { + selectedShader = &colShader; + } + selectedShader->SetUniformColor("color", fill.color); +} + +void IndexedMeshRenderer::UsePoint(const Canvas::Stroke &stroke, double pixel) { + pointShader.SetUniformColor("color", stroke.color); + pointShader.SetUniformFloat("width", (float)(stroke.width * pixel / 2.0)); + pointShader.SetUniformFloat("pixel", (float)pixel); + selectedShader = &pointShader; +} + +} diff --git a/src/render/gl2shader.h b/src/render/gl2shader.h new file mode 100644 index 00000000..f52d0c45 --- /dev/null +++ b/src/render/gl2shader.h @@ -0,0 +1,248 @@ +//----------------------------------------------------------------------------- +// OpenGL 2 shader interface. +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +#ifndef SOLVESPACE_GL2UTILS_H +#define SOLVESPACE_GL2UTILS_H + +#ifdef WIN32 +# include // required by GL headers +#endif +#ifdef __APPLE__ +# include +#else +# define GL_GLEXT_PROTOTYPES +# include +# include +#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> &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 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 vertices; + std::vector 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 diff --git a/src/render/render.cpp b/src/render/render.cpp index e82aa5ff..a8ae6355 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -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() { diff --git a/src/render/render.h b/src/render/render.h index 4f8fcf9a..a521be41 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -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 texture; void Clear() { *this = {}; } bool Equals(const Fill &other) const; diff --git a/src/render/rendergl2.cpp b/src/render/rendergl2.cpp new file mode 100644 index 00000000..967282d7 --- /dev/null +++ b/src/render/rendergl2.cpp @@ -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, GLuint, + std::owner_less>> items; + + bool Lookup(std::shared_ptr 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 lines; + IdList meshes; + IdList points; + + TextureCache pixmapCache; + std::shared_ptr 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 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 &faces, hFill hcf) override; + void DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) override; + void InvalidatePixmap(std::shared_ptr pm) override; + + Stroke *SelectStroke(hStroke hcs); + Fill *SelectFill(hFill hcf); + void SelectMask(FillPattern pattern); + void SelectTexture(std::shared_ptr 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 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 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 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 &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 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 OpenGl2Renderer::ReadFrame() { + std::shared_ptr 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 CreateRenderer() { + return std::shared_ptr(new OpenGl2Renderer()); +} + +} diff --git a/src/solvespace.h b/src/solvespace.h index 3c1e30be..faa9bd80 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -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); diff --git a/src/util.cpp b/src/util.cpp index ecff190f..89f93c8c 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -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.