Thu, 11 Jan 2018 15:30:30 +0200
renderer rework
/* * LDForge: LDraw parts authoring CAD * Copyright (C) 2013 - 2018 Teemu Piippo * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #define GL_GLEXT_PROTOTYPES #include <GL/glu.h> #include <GL/glext.h> #include "glCompiler.h" #include "ldObject.h" #include "colors.h" #include "ldDocument.h" #include "miscallenous.h" #include "glRenderer.h" #include "guiutilities.h" #include "documentmanager.h" static const QColor bfcFrontColor {64, 192, 80}; static const QColor bfcBackColor {208, 64, 64}; struct GLErrorInfo { GLenum value; QString text; }; static const GLErrorInfo g_GLErrors[] = { { GL_NO_ERROR, "No error" }, { GL_INVALID_ENUM, "Unacceptable enumerator passed" }, { GL_INVALID_VALUE, "Numeric argument out of range" }, { GL_INVALID_OPERATION, "The operation is not allowed to be done in this state" }, { GL_INVALID_FRAMEBUFFER_OPERATION, "Framebuffer object is not complete"}, { GL_OUT_OF_MEMORY, "Out of memory" }, { GL_STACK_UNDERFLOW, "The operation would have caused an underflow" }, { GL_STACK_OVERFLOW, "The operation would have caused an overflow" }, }; ConfigOption(QString SelectColorBlend = "#0080FF") void CheckGLErrorImpl(const char* file, int line) { QString errmsg; GLenum errnum = glGetError(); if (errnum == GL_NO_ERROR) return; for (const GLErrorInfo& err : g_GLErrors) { if (err.value == errnum) { errmsg = err.text; break; } } print("OpenGL ERROR: at %1:%2: %3", Basename(QString(file)), line, errmsg); } GLCompiler::GLCompiler(LDDocument* document, GLRenderer* renderer) : document {document}, renderer {renderer} {} void GLCompiler::initialize() { initializeOpenGLFunctions(); glGenBuffers(NumVbos, &vboIndices[0]); CHECK_GL_ERROR(); } GLCompiler::~GLCompiler() { glDeleteBuffers(NumVbos, &vboIndices[0]); CHECK_GL_ERROR(); } /* * Returns a color that represents this object index. There are 256³ possible * colors, so that many indices can be bijectively addressed with colorss. */ QColor GLCompiler::indexColorForID(int id) const { int red = (id / 0x10000) % 0x100; int green = (id / 0x100) % 0x100; int blue = id % 0x100; return {red, green, blue}; } QColor blend(QColor baseColor, QColor blendColor, double intensity) { double red = baseColor.red(); red += blendColor.red() * intensity; red /= (intensity + 1.0); double green = baseColor.green(); green += blendColor.green() * intensity; green /= (intensity + 1.0); double blue = baseColor.blue(); blue += blendColor.blue() * intensity; blue /= (intensity + 1.0); return {int(round(red)), int(round(green)), int(round(blue))}; } QColor GLCompiler::getColorForPolygon(LDPolygon& poly, LDObject* topobj, ComplementVboType complement) const { QColor color; switch(complement) { case SurfacesVboComplement: case NumVboComplements: return {}; case BfcFrontColorsVboComplement: color = bfcFrontColor; break; case BfcBackColorsVboComplement: color = bfcBackColor; break; case PickColorsVboComplement: return indexColorForID(topobj->id()); case RandomColorsVboComplement: color = topobj->randomColor(); break; case NormalColorsVboComplement: if (poly.color == MainColor) { if (topobj->color() == MainColor) color = mainColorRepresentation(); else color = topobj->color().faceColor(); } else if (poly.color == EdgeColor) { if (luma(config->backgroundColor()) > 40) color = Qt::black; else color = Qt::white; } else { LDColor colorInfo = poly.color; if (colorInfo.isValid()) color = colorInfo.faceColor(); } break; } if (not color.isValid()) { // The color was unknown. Use main color to make the polygon at least // not appear pitch-black. if (poly.num != 2 and poly.num != 5) color = mainColorRepresentation(); else color = Qt::black; // Warn about the unknown color, but only once. static QSet<int> warnedColors; if (not warnedColors.contains(poly.color)) { print("Unknown color %1!\n", poly.color); warnedColors.insert(poly.color); } return color; } double blendAlpha = 0.0; if (topobj->isSelected()) blendAlpha = 1.0; else if (topobj == renderer->objectAtCursor()) blendAlpha = 0.5; color = blend(color, config->selectColorBlend(), blendAlpha); return color; } void GLCompiler::needMerge() { for (bool& changed_flag : this->vboChanged) changed_flag = true; } void GLCompiler::stageForCompilation(LDObject* obj) { stagedObjects << obj; } void GLCompiler::unstage(LDObject* obj) { stagedObjects.remove(obj); } void GLCompiler::compileDocument() { for (LDObject* object : this->document->objects()) compileObject(object); } void GLCompiler::compileStaged() { for (QSetIterator<LDObject*> it(stagedObjects); it.hasNext();) compileObject(it.next()); stagedObjects.clear(); } void GLCompiler::prepareVBO(int vbonum) { // Compile anything that still awaits it compileStaged(); if (vboChanged[vbonum]) { QVector<GLfloat> vbodata; for (auto it = objectInfo.begin(); it != objectInfo.end();) { if (it.key() == nullptr) { it = objectInfo.erase(it); } else { if (not it.key()->isHidden()) vbodata += it->data[vbonum]; ++it; } } glBindBuffer(GL_ARRAY_BUFFER, vboIndices[vbonum]); glBufferData( GL_ARRAY_BUFFER, vbodata.size() * sizeof(GLfloat), vbodata.constData(), GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); CHECK_GL_ERROR(); vboChanged[vbonum] = false; vboSizes[vbonum] = vbodata.size(); } } void GLCompiler::dropObjectInfo(LDObject* obj) { if (objectInfo.contains(obj)) { objectInfo.remove(obj); needMerge(); } } void GLCompiler::compileObject(LDObject* obj) { if (obj == nullptr or obj->document() == nullptr or obj->document()->isCache()) return; ObjectVBOInfo info; dropObjectInfo(obj); switch(obj->type()) { // Note: We cannot split quads into triangles here, it would mess up the wireframe view. // Quads must go into separate vbos. case OBJ_Triangle: case OBJ_Quad: case OBJ_Line: case OBJ_CondLine: { LDPolygon* poly = obj->getPolygon(); poly->id = obj->id(); compilePolygon(*poly, obj, &info); delete poly; break; } case OBJ_SubfileReference: { LDSubfileReference* ref = static_cast<LDSubfileReference*>(obj); auto data = ref->inlinePolygons(); for (LDPolygon& poly : data) { poly.id = obj->id(); compilePolygon(poly, obj, &info); } break; } case OBJ_BezierCurve: { LDBezierCurve* curve = static_cast<LDBezierCurve*>(obj); for (LDPolygon& poly : curve->rasterizePolygons()) { poly.id = obj->id(); compilePolygon(poly, obj, &info); } } break; default: break; } objectInfo[obj] = info; needMerge(); } void GLCompiler::compilePolygon(LDPolygon& poly, LDObject* topobj, ObjectVBOInfo* objinfo) { SurfaceVboType surface; int numverts; switch(poly.num) { case 2: surface = LinesVbo; numverts = 2; break; case 3: surface = TrianglesVbo; numverts = 3; break; case 4: surface = QuadsVbo; numverts = 4; break; case 5: surface = ConditionalLinesVbo; numverts = 2; break; default: return; } for (ComplementVboType complement = FirstVboComplement; complement < NumVboComplements; ++complement) { const int vbonum = vboNumber(surface, complement); QVector<GLfloat>& vbodata = objinfo->data[vbonum]; const QColor color = getColorForPolygon(poly, topobj, complement); for (int vert = 0; vert < numverts; ++vert) { if (complement == SurfacesVboComplement) { // Write coordinates. Apparently Z must be flipped too? vbodata << poly.vertices[vert].x() << -poly.vertices[vert].y() << -poly.vertices[vert].z(); } else { vbodata <<((GLfloat) color.red()) / 255.0f <<((GLfloat) color.green()) / 255.0f <<((GLfloat) color.blue()) / 255.0f <<((GLfloat) color.alpha()) / 255.0f; } } } } int GLCompiler::vboNumber(SurfaceVboType surface, ComplementVboType complement) { return (surface * NumVboComplements) + complement; } GLuint GLCompiler::vbo(int vbonum) const { return vboIndices[vbonum]; } int GLCompiler::vboSize(int vbonum) const { return vboSizes[vbonum]; }