src/gl/compiler.cpp

Wed, 25 May 2022 20:36:34 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Wed, 25 May 2022 20:36:34 +0300
changeset 199
6988973515d2
parent 193
b4beff48bb7a
child 200
ca23936b455b
permissions
-rw-r--r--

Fix pick() picking from weird places on the screen with high DPI scaling

glReadPixels reads data from the frame buffer, which contains data after
high DPI scaling, so any reads to that need to take this scaling into account

/*
 *  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 <QMessageBox>
#include "gl/compiler.h"
#include "documentmanager.h"
#include "invert.h"
#include "ring.h"

static const char* vertexShaderSource = R"(
#version 330 core

layout(location=0) in vec3 position;
layout(location=1) in vec4 color;
layout(location=2) in vec3 normal;
layout(location=3) in int id;
layout(location=4) in int selected;
out vec4 vColor;
out vec3 vFragPos;
out vec3 vNormal;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform int fragmentStyle;
uniform vec3 selectedColor;
uniform int highlighted;

const int FRAGSTYLE_Normal = 0;
const int FRAGSTYLE_BfcGreen = 1;
const int FRAGSTYLE_BfcRed = 2;
const int FRAGSTYLE_Random = 3;
const int FRAGSTYLE_Id = 4;
const int FRAGSTYLE_Black = 5;

void main()
{
	mat3 normalMatrix = transpose(inverse(mat3(modelMatrix)));
	vNormal = normalize(normalMatrix * normal);
	if (fragmentStyle == FRAGSTYLE_Id)
	{
		/* Calculate a color based from this index. This method caters for
		 * 16777216 objects. I don't think that will be exceeded anytime soon.
		 */
		int r = (id / 0x10000) % 0x100;
		int g = (id / 0x100) % 0x100;
		int b = id % 0x100;
		vColor = vec4(r / 255.0, g / 255.0, b / 255.0, 1.0);
	}
	else if (selected == 1)
	{
		vColor = vec4(selectedColor, 1.0);
	}
	else
	{
		if (fragmentStyle == FRAGSTYLE_BfcGreen)
		{
			vColor = vec4(0.2, 0.9, 0.2, 1.0);
		}
		else if (fragmentStyle == FRAGSTYLE_BfcRed)
		{
			vColor = vec4(0.9, 0.2, 0.2, 1.0);
		}
		else if (fragmentStyle == FRAGSTYLE_Black)
		{
			vColor = vec4(0.0, 0.0, 0.0, 1.0);
		}
		else
		{
			vColor = color;
		}
		if (highlighted == id)
		{
			vColor = (vColor + vec4(selectedColor, 1.0) * 0.6) / 1.6;
		}
	}

	vFragPos = vec3(modelMatrix * vec4(position, 1.0));
	gl_Position = projectionMatrix * viewMatrix * vec4(vFragPos, 1.0);
}
)";

static const char* fragmentShaderSource = R"(
#version 330 core

in vec4 vColor;
in vec3 vFragPos;
in vec3 vNormal;
out vec4 fColor;
const vec3 lightPos = vec3(0.5, 0.5, 0.5);
const vec4 lightColor = vec4(1.0, 1.0, 1.0, 1.0);
const float ambientStrength = 0.7;
uniform bool useLighting;

void main()
{
	if (useLighting)
	{
		vec4 ambient = ambientStrength * lightColor;
		vec3 lightDirection = normalize(lightPos - vFragPos);
		vec4 diffuse = max(dot(vNormal, lightDirection), 0.0) * lightColor;
		fColor = (ambient + diffuse) * vColor;
	}
	else
	{
		fColor = vColor;
	}
}
)";

void gl::buildShaders(
	QOpenGLShaderProgram* shaderProgram,
	const char* vertexShaderSource,
	const char* fragmentShaderSource)
{
	shaderProgram->create();
	const bool vertexShaderCompiled = shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
	QString log;
	if (not vertexShaderCompiled)
	{
		log += "\n" + QObject::tr("Vertex shader:") + "\n" + shaderProgram->log();
	}
	const bool fragmentShaderCompiled = shaderProgram->addShaderFromSourceCode(
		QOpenGLShader::Fragment,
		fragmentShaderSource);
	if (not fragmentShaderCompiled)
	{
		log += "\n" + QObject::tr("Fragment shader:") + "\n" + shaderProgram->log();
	}
	if (not vertexShaderCompiled or not fragmentShaderCompiled)
	{
		QMessageBox::critical(
			nullptr,
			QObject::tr("Shader compile error"),
			QObject::tr("Could not compile shaders.") + "\n" + log);
		std::exit(-1);
	}
	const bool linkSuccessful = shaderProgram->link();
	if (not linkSuccessful)
	{
		QMessageBox::critical(
			nullptr,
			QObject::tr("Shader link error"),
			QObject::tr("Could not link shaders: %1").arg(shaderProgram->log())
		);
	}
}

void gl::initializeModelShaders(gl::ModelShaders *modelShaders)
{
	if (not modelShaders->initialized)
	{
		for (auto& shader : modelShaders->shaderObjects)
		{
			shader.program = std::make_unique<QOpenGLShaderProgram>();
			gl::buildShaders(shader.program.get(), ::vertexShaderSource, ::fragmentShaderSource);
			shader.program->bind();
			shader.buffer.create();
			shader.buffer.bind();
			shader.buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
			shader.vertexArray.create();
			shader.vertexArray.bind();
			for (int k : {0, 1, 2, 3, 4})
			{
				shader.program->enableAttributeArray(k);
			}
			using Vertex = ModelShaders::Vertex;
			constexpr int stride = sizeof(Vertex);
			shader.program->setAttributeBuffer(0, GL_FLOAT, offsetof(Vertex, position), 3, stride);
			shader.program->setAttributeBuffer(1, GL_FLOAT, offsetof(Vertex, color), 4, stride);
			shader.program->setAttributeBuffer(2, GL_FLOAT, offsetof(Vertex, normal), 3, stride);
			glVertexAttribIPointer(3, 1, GL_INT, stride, reinterpret_cast<void*>(offsetof(Vertex, id)));
			glVertexAttribIPointer(4, 1, GL_INT, stride, reinterpret_cast<void*>(offsetof(Vertex, selected)));
			shader.vertexArray.release();
			shader.buffer.release();
			shader.program->release();
		}
		modelShaders->initialized = true;
	}
}

static gl::ArrayClass classifyPolygon(const gl::Polygon& polygon)
{
	switch (polygon.type)
	{
	case gl::Polygon::EdgeLine:
		return gl::ArrayClass::Lines;
	case gl::Polygon::Triangle:
		return gl::ArrayClass::Triangles;
	case gl::Polygon::Quadrilateral:
		return gl::ArrayClass::Quads;
	case gl::Polygon::ConditionalEdge:
		return gl::ArrayClass::ConditionalLines;
	}
	return gl::ArrayClass::Lines;
}

template<typename Fn>
void iterateModelPolygons(Model* model, DocumentManager* context, Fn&& fn)
{
	std::optional<ModelId> modelId = context->findIdForModel(model);
	if (modelId.has_value())
	{
		PolygonCache* cache = context->getPolygonCacheForModel(modelId.value());
		if (cache != nullptr)
		{
			for (const gl::Polygon& polygon : getCachedPolygons(cache, model, context))
			{
				fn(polygon);
			}
		}
	}
}

static QColor getColorForPolygon(
	const gl::Polygon& polygon,
	const gl::RenderPreferences& preferences,
	const ldraw::ColorTable& colorTable)
{
	QColor color;
	// For normal colors, use the polygon's color.
	if (polygon.color == ldraw::MAIN_COLOR)
	{
		color = preferences.mainColor;
	}
	else if (polygon.color == ldraw::EDGE_COLOR)
	{
		// Edge color is black, unless we have a dark background, in which case lines need to be bright.
		color = luma(preferences.backgroundColor) > (40.0 / 256.0) ? Qt::black : Qt::white;
	}
	else
	{
		// Not main or edge color, use the polygon's color as is.
		color = colorTable[polygon.color].faceColor;
	}
	return color;
}

/**
 * @brief Computes the minimum bounding box for a model
 */
BoundingBox gl::boundingBoxForModel(Model* model, DocumentManager* context)
{
	BoundingBox result = emptyBoundingBox;
	iterateModelPolygons(model, context, [&](const gl::Polygon& polygon)
	{
		for (unsigned int i = 0; i < polygon.numPolygonVertices(); i += 1)
		{
			addPointToBox(result, polygon.vertices[i]);
		}
	});
	return result;
}

/**
 * @brief gl::build Creates GL vertices for objects in the model and buffers them to shaders.
 */
void gl::build(
	gl::ModelShaders* shaders,
	Model* model,
	const ldraw::ColorTable& colorTable,
	DocumentManager* context,
	const gl::RenderPreferences& preferences)
{
	for (gl::ModelShaders::ShaderObject& shader : shaders->shaderObjects) {
		shader.cachedData.clear();
	}
	iterateModelPolygons(model, context, [&](const Polygon& polygon)
	{
		const int index = static_cast<int>(classifyPolygon(polygon));
		std::vector<gl::ModelShaders::Vertex>& vertexBuffer = shaders->shaderObjects[index].cachedData;
		auto vertexRing = iter::ring(polygon.vertices, polygon.numPolygonVertices());
		reserveMore(vertexBuffer, polygon.numPolygonVertices());
		const QColor color = getColorForPolygon(polygon, preferences, colorTable);
		for (unsigned int i = 0; i < polygon.numPolygonVertices(); i += 1)
		{
			const glm::vec3& v1 = vertexRing[i - 1];
			const glm::vec3& v2 = vertexRing[i];
			const glm::vec3& v3 = vertexRing[i + 1];
			gl::ModelShaders::Vertex& vertex = vertexBuffer.emplace_back();
			vertex.position = polygon.vertices[i];
			vertex.normal = glm::normalize(glm::cross(v1 - v2, v3 - v2));
			vertex.color = glm::vec4{color.redF(), color.greenF(), color.blueF(), color.alphaF()};
			vertex.id = polygon.id.value;
		}
	});
	for (gl::ModelShaders::ShaderObject& shader : shaders->shaderObjects)
	{
		shader.vertexCount = shader.cachedData.size();
		shader.buffer.bind();
		const int bytes = static_cast<int>(shader.cachedData.size() * sizeof shader.cachedData[0]);
		shader.buffer.allocate(shader.cachedData.data(), bytes);
		shader.buffer.release();
	}
}

ldraw::id_t gl::idFromColor(const std::array<GLubyte, 3>& data)
{
	return {data[0] * std::int32_t{0x10000} + data[1] * std::int32_t{0x100} + data[2]};
}

void gl::bindModelShaderVertexArray(gl::ModelShaders* shaders, gl::ArrayClass arrayClass)
{
	ModelShaders::ShaderObject& shaderObject = shaders->shaderObjects[static_cast<int>(arrayClass)];
	shaderObject.vertexArray.bind();
	shaderObject.program->bind();
}

void gl::releaseModelShaderVertexArray(gl::ModelShaders* shaders, gl::ArrayClass arrayClass)
{
	ModelShaders::ShaderObject& shaderObject = shaders->shaderObjects[static_cast<int>(arrayClass)];
	shaderObject.program->release();
	shaderObject.vertexArray.release();
}

void gl::setModelShaderSelectedObjects(gl::ModelShaders* shaders, const QSet<ldraw::id_t>& ids)
{
	for (ModelShaders::ShaderObject& object : shaders->shaderObjects)
	{
		std::vector<ModelShaders::Vertex>& vector = object.cachedData;
		for (ModelShaders::Vertex& vertex : vector)
		{
			vertex.selected = (ids.contains({vertex.id})) ? 1 : 0;
		}
		const GLsizeiptr size = static_cast<int>(vector.size() * sizeof vector[0]);
		object.buffer.bind();
		glBufferSubData(GL_ARRAY_BUFFER, 0, size, vector.data());
		object.buffer.release();
	}
}

std::size_t gl::vertexCount(const gl::ModelShaders* shaders, const gl::ArrayClass arrayClass)
{
	return shaders->shaderObjects[static_cast<int>(arrayClass)].vertexCount;
}

mercurial