solvespace/src/render/gl3shader.cpp
whitequark e74137dc67 Fix misuse of glTexImage2D (again).
This was originally changed in 74aa80b6, but the fix broke stipping
because it incorrectly changed the logic. Revert that, and just make
the textures smaller instead.
2019-11-23 15:56:57 +00:00

1072 lines
36 KiB
C++

//-----------------------------------------------------------------------------
// OpenGL ES 2.0 and OpenGL 3.0 shader interface.
//
// Copyright 2016 Aleksey Egorov
//-----------------------------------------------------------------------------
#include "solvespace.h"
#include "gl3shader.h"
namespace SolveSpace {
//-----------------------------------------------------------------------------
// Floating point data structures
//-----------------------------------------------------------------------------
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 *resData = (const char *)Platform::LoadResource(res, &size);
// Sigh, here we go... We want to deploy to four platforms: Linux, Windows, OS X, mobile+web.
// These platforms are basically disjunctive in the OpenGL versions and profiles that they
// support: mobile+web support GLES2, Windows can only be guaranteed to support GL1 without
// vendor's drivers installed but supports D3D9+ natively, Linux supports GL3.2+ and/or
// GLES2+ depending on whether we run on X11 or Wayland, and OS X supports either a legacy
// profile or a GL3.2 core profile or (on 10.9+) a GL4.1 core profile.
// The platforms barely have a common subset of features:
// * mobile+web and Windows (D3D9 through ANGLE) are strictly GLES2/GLSL1.0;
// * OS X legacy compatibility profile has GLSL1.2 only shaders, and GL3.2 core profile
// that has GLSL1.0 shaders compatible with GLES2 makes mandatory the use of vertex array
// objects, which cannot be used in GLES2 at all; similarly GL3.2 core has GL_RED but not
// GL_ALPHA whereas GLES2 has GL_ALPHA but not GL_RED.
// * GTK does not work on anything prior to GL3.0/GLES2.0; it does not permit explicitly
// asking for a compatibility profile, i.e. you can only ask for 3.2+; and it does not
// permit asking for a GLES profile prior to GTK 3.22, which will get into Ubuntu
// no earlier than late 2017. This is despite the fact that if only GTK defaulted
// to the compatibility profile, everything would have just worked as Mesa is
// very permissive.
// While we're at it, let's remember that GLES2 has *only* glDepthRangef, GL3.2 has *only*
// glDepthRange, and GL4.1+ has both glDepthRangef and glDepthRange. Also, that GLSL1.0
// makes `precision highp float;` mandatory in fragment shaders, and GLSL1.2 removes
// the `precision` keyword entirely, because that's clearly how minor versions work.
// Christ, what a trash fire.
const char *prelude;
#if defined(HAVE_GLES)
prelude = R"(
#version 100
#define TEX_ALPHA a
precision highp float;
)";
#else
prelude = R"(
#version 120
#define TEX_ALPHA r
)";
#endif
std::string src(resData, size);
src = prelude + src;
GLuint shader = glCreateShader(type);
ssassert(shader != 0, "glCreateShader failed");
const GLint glSize[] = { (int)src.length() };
const GLchar* glSource[] = { src.c_str() };
glShaderSource(shader, 1, glSource, 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);
if(!compiled) {
dbp("Failed to compile shader:\n"
"----8<----8<----8<----8<----8<----\n"
"%s\n"
"----8<----8<----8<----8<----8<----\n",
src.c_str());
}
ssassert(compiled, "Cannot compile shader");
return shader;
}
void Shader::Init(const std::string &vertexRes, const std::string &fragmentRes,
const std::vector<std::pair<GLuint, std::string> > &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, const 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[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) {
if(handle.size == 0) return;
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<double> &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);
size /= 2;
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.empty()) {
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, "tgt" }
}
);
}
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[i];
const SEdge &next = edges.l[(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) {
if(handle.size == 0) return;
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(const double *matrix) {
shader.SetUniformMatrix("modelview", matrix);
}
void EdgeRenderer::SetProjection(const 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, "tgt" },
{ 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[i];
const SOutline &next = outlines.l[(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) {
if(handle.size == 0) return;
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(const double *matrix) {
shader.SetUniformMatrix("modelview", matrix);
}
void OutlineRenderer::SetProjection(const 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" }
}
);
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 &handle) {
glDeleteBuffers(1, &handle.vertexBuffer);
glDeleteBuffers(1, &handle.indexBuffer);
}
void IndexedMeshRenderer::Draw(const IndexedMeshRenderer::Handle &handle) {
if(handle.size == 0) return;
selectedShader->Enable();
glBindBuffer(GL_ARRAY_BUFFER, handle.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, handle.indexBuffer);
glDrawElements(GL_TRIANGLES, handle.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(const double *matrix) {
colShader.SetUniformMatrix("modelview", matrix);
texShader.SetUniformMatrix("modelview", matrix);
texaShader.SetUniformMatrix("modelview", matrix);
pointShader.SetUniformMatrix("modelview", matrix);
}
void IndexedMeshRenderer::SetProjection(const 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;
}
}