
This commit performs two main changes: * Alters the shaders to use only strictly conformant GLSL 2.0. * Alters the Windows UI to use ANGLE via GL ES 2.0 and EGL 1.4. This commit also drops official support for Windows XP, since ANGLE requires a non-XP toolset to build. It is still possible to build SolveSpace for Windows XP using: cmake -T v120_xp -DOPENGL=1
1043 lines
35 KiB
C++
1043 lines
35 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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 *resData = (const char *)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:
|
|
// * Linux Mesa/NVidia accept basically everything thrown at it;
|
|
// * 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.
|
|
// 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.
|
|
|
|
std::string src(resData, size);
|
|
#ifdef __APPLE__
|
|
src = "#version 120\n" + src;
|
|
#else
|
|
src = "#version 100\nprecision highp float;\n" + src;
|
|
#endif
|
|
|
|
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);
|
|
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, 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<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);
|
|
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;
|
|
}
|
|
|
|
}
|