File mesh_layer_renderer.cpp
File List > gui > mesh_layer_renderer.cpp
Go to the documentation of this file
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <mutex>
#include "gui/gl_check.hpp"
#include "gui/layer.hpp"
#include "gui/layer_renderer.hpp"
#include "gui/raster_data.hpp"
#include "gui/shaders.hpp"
namespace {
struct MeshVertex {
float position[3];
float color[3];
float normal[3];
};
struct TexturedMeshVertex {
float position[3];
float texcoord[2];
float normal[3];
};
std::vector<uint8_t> build_texture_rgb(const Geo<MultiBand<FlexGrid>>& texture) {
const size_t width = texture.width();
const size_t height = texture.height();
const size_t rgb_bands = std::min<size_t>(texture.size(), 3);
std::vector<uint8_t> rgb(width * height * 3);
for (size_t y = 0; y < height; ++y) {
const size_t dest_y = height - 1 - y;
for (size_t x = 0; x < width; ++x) {
const size_t i = (dest_y * width + x) * 3;
const double r = flex_grid_value(texture[0], x, y);
const double g = rgb_bands > 1 ? flex_grid_value(texture[1], x, y) : r;
const double b = rgb_bands > 2 ? flex_grid_value(texture[2], x, y) : r;
rgb[i] = static_cast<uint8_t>(std::clamp(r, 0.0, 255.0));
rgb[i + 1] = static_cast<uint8_t>(std::clamp(g, 0.0, 255.0));
rgb[i + 2] = static_cast<uint8_t>(std::clamp(b, 0.0, 255.0));
}
}
return rgb;
}
} // namespace
MeshLayerRenderer::MeshLayerRenderer(std::shared_ptr<Layer> layer,
std::function<const AsyncRasterData*()> data_accessor,
const Coordinate3D<double>& /*offset*/, bool gpu_texture)
: m_layer(std::move(layer)),
m_data_accessor(std::move(data_accessor)),
m_gpu_texture(gpu_texture) {}
void MeshLayerRenderer::upload_texture(const Geo<MultiBand<FlexGrid>>& texture) {
const size_t width = texture.width();
const size_t height = texture.height();
if (width == 0 || height == 0) {
return;
}
const std::vector<uint8_t> rgb = build_texture_rgb(texture);
QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
GLint unpack_alignment = 4;
CHECK_GL(f->glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_alignment));
CHECK_GL(f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
m_texture = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target2D);
m_texture->setSize(static_cast<int>(width), static_cast<int>(height));
m_texture->setFormat(QOpenGLTexture::RGB8_UNorm);
m_texture->allocateStorage(QOpenGLTexture::RGB, QOpenGLTexture::UInt8);
m_texture->setData(QOpenGLTexture::RGB, QOpenGLTexture::UInt8, rgb.data());
CHECK_GL_AFTER();
CHECK_GL(f->glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_alignment));
m_texture->setMinificationFilter(QOpenGLTexture::Linear);
m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);
m_texture_uploaded = true;
}
void MeshLayerRenderer::upload_mesh(const DemMeshData& mesh, const Coordinate3D<double>& offset) {
if (mesh.vertices.empty() || mesh.indices.empty()) {
return;
}
const size_t vertex_count = mesh.vertices.size() / 3;
const bool use_texture = m_gpu_texture && mesh.has_texture;
if (!use_texture && mesh.colors.empty()) {
return;
}
if (!use_texture && mesh.colors.size() != mesh.vertices.size()) {
std::cerr << "Mesh color/vertex size mismatch" << std::endl;
return;
}
if (use_texture && mesh.texcoords.size() != vertex_count * 2) {
std::cerr << "Mesh texcoord/vertex size mismatch" << std::endl;
return;
}
for (unsigned int index : mesh.indices) {
if (index >= vertex_count) {
std::cerr << "Mesh index out of range: " << index << " >= " << vertex_count << std::endl;
return;
}
}
if (!m_shader) {
m_shader = std::make_unique<QOpenGLShaderProgram>();
if (use_texture) {
const QString vertex_src = get_textured_mesh_vertex_shader();
const QString fragment_src = get_textured_mesh_fragment_shader();
if (vertex_src.isEmpty() || fragment_src.isEmpty()) {
std::cout << "Failed to load textured mesh shaders from resources" << std::endl;
m_shader.reset();
return;
}
if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertex_src)) {
std::cout << "Mesh textured vertex shader compile error: " << m_shader->log().toStdString()
<< std::endl;
m_shader.reset();
return;
}
if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragment_src)) {
std::cout << "Mesh textured fragment shader compile error: "
<< m_shader->log().toStdString() << std::endl;
m_shader.reset();
return;
}
m_shader->bindAttributeLocation("position", 0);
m_shader->bindAttributeLocation("texcoord", 1);
m_shader->bindAttributeLocation("normal", 2);
} else {
const QString vertex_src = get_mesh_vertex_shader();
const QString fragment_src = get_mesh_fragment_shader();
if (vertex_src.isEmpty() || fragment_src.isEmpty()) {
std::cout << "Failed to load mesh shaders from resources" << std::endl;
m_shader.reset();
return;
}
if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertex_src)) {
std::cout << "Mesh vertex shader compile error: " << m_shader->log().toStdString()
<< std::endl;
m_shader.reset();
return;
}
if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragment_src)) {
std::cout << "Mesh fragment shader compile error: " << m_shader->log().toStdString()
<< std::endl;
m_shader.reset();
return;
}
m_shader->bindAttributeLocation("position", 0);
m_shader->bindAttributeLocation("color", 1);
m_shader->bindAttributeLocation("normal", 2);
}
if (!m_shader->link()) {
std::cout << "Mesh shader link error: " << m_shader->log().toStdString() << std::endl;
m_shader.reset();
return;
}
}
m_proj_matrix_loc = m_shader->uniformLocation("proj_matrix");
m_light_direction_loc = m_shader->uniformLocation("light_direction");
m_camera_position_loc = m_shader->uniformLocation("camera_position");
m_ambient_light_loc = m_shader->uniformLocation("ambient_light");
m_diffuse_light_loc = m_shader->uniformLocation("diffuse_light");
if (use_texture) {
m_texture_sampler_loc = m_shader->uniformLocation("dem_texture");
}
m_layer_alpha_loc = m_shader->uniformLocation("layer_alpha");
m_vertical_offset_loc = m_shader->uniformLocation("vertical_offset");
if (m_vao.isCreated()) {
m_vao.destroy();
}
if (m_vbo.isCreated()) {
m_vbo.destroy();
}
if (m_ibo.isCreated()) {
m_ibo.destroy();
}
m_vao.create();
{
QOpenGLVertexArrayObject::Binder vao_binder(&m_vao);
m_vbo.create();
m_vbo.bind();
m_ibo = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
m_ibo.create();
m_ibo.bind();
m_ibo.allocate(mesh.indices.data(),
static_cast<int>(mesh.indices.size() * sizeof(unsigned int)));
CHECK_GL_AFTER();
DemMeshData mesh_with_normals = mesh;
if (mesh_with_normals.normals.size() != mesh_with_normals.vertices.size()) {
compute_mesh_normals(mesh_with_normals);
}
if (!bind_shader(m_shader.get())) {
return;
}
if (use_texture) {
std::vector<TexturedMeshVertex> interleaved;
interleaved.reserve(vertex_count);
for (size_t i = 0; i < mesh.vertices.size(); i += 3) {
TexturedMeshVertex vertex;
vertex.position[0] = static_cast<float>(mesh.vertices[i] - offset.x());
vertex.position[1] = static_cast<float>(mesh.vertices[i + 1] - offset.y());
vertex.position[2] = static_cast<float>(mesh.vertices[i + 2] - offset.z());
const size_t tex_index = (i / 3) * 2;
vertex.texcoord[0] = mesh.texcoords[tex_index];
vertex.texcoord[1] = mesh.texcoords[tex_index + 1];
vertex.normal[0] = mesh_with_normals.normals[i];
vertex.normal[1] = mesh_with_normals.normals[i + 1];
vertex.normal[2] = mesh_with_normals.normals[i + 2];
for (float component : vertex.position) {
if (!std::isfinite(component)) {
return;
}
}
interleaved.push_back(vertex);
}
m_vbo.allocate(interleaved.data(),
static_cast<int>(interleaved.size() * sizeof(TexturedMeshVertex)));
const int stride = static_cast<int>(sizeof(TexturedMeshVertex));
m_shader->enableAttributeArray(0);
m_shader->setAttributeBuffer(0, GL_FLOAT, offsetof(TexturedMeshVertex, position), 3, stride);
m_shader->enableAttributeArray(1);
m_shader->setAttributeBuffer(1, GL_FLOAT, offsetof(TexturedMeshVertex, texcoord), 2, stride);
m_shader->enableAttributeArray(2);
m_shader->setAttributeBuffer(2, GL_FLOAT, offsetof(TexturedMeshVertex, normal), 3, stride);
} else {
std::vector<MeshVertex> interleaved;
interleaved.reserve(vertex_count);
for (size_t i = 0; i < mesh.vertices.size(); i += 3) {
MeshVertex vertex;
vertex.position[0] = static_cast<float>(mesh.vertices[i] - offset.x());
vertex.position[1] = static_cast<float>(mesh.vertices[i + 1] - offset.y());
vertex.position[2] = static_cast<float>(mesh.vertices[i + 2] - offset.z());
vertex.color[0] = mesh.colors[i];
vertex.color[1] = mesh.colors[i + 1];
vertex.color[2] = mesh.colors[i + 2];
vertex.normal[0] = mesh_with_normals.normals[i];
vertex.normal[1] = mesh_with_normals.normals[i + 1];
vertex.normal[2] = mesh_with_normals.normals[i + 2];
for (float component : vertex.position) {
if (!std::isfinite(component)) {
return;
}
}
interleaved.push_back(vertex);
}
m_vbo.allocate(interleaved.data(), static_cast<int>(interleaved.size() * sizeof(MeshVertex)));
const int stride = static_cast<int>(sizeof(MeshVertex));
m_shader->enableAttributeArray(0);
m_shader->setAttributeBuffer(0, GL_FLOAT, offsetof(MeshVertex, position), 3, stride);
m_shader->enableAttributeArray(1);
m_shader->setAttributeBuffer(1, GL_FLOAT, offsetof(MeshVertex, color), 3, stride);
m_shader->enableAttributeArray(2);
m_shader->setAttributeBuffer(2, GL_FLOAT, offsetof(MeshVertex, normal), 3, stride);
}
m_shader->release();
CHECK_GL_AFTER();
}
m_index_count = mesh.indices.size();
m_mesh_uploaded = true;
}
void MeshLayerRenderer::render(const Camera& camera, const RenderContext& ctx) {
if (ctx.incremental_points) {
return;
}
if (!m_visible) {
return;
}
const AsyncRasterData* data = m_data_accessor();
if (!data) {
return;
}
if (!data->ready()) {
emit repaint_required();
return;
}
if (m_data_update_required) {
m_mesh_uploaded = false;
m_texture_uploaded = false;
m_texture.reset();
m_shader.reset();
m_data_update_required = false;
}
if (!m_mesh_uploaded || (m_gpu_texture && !m_texture_uploaded)) {
DemMeshData mesh_copy;
const Geo<MultiBand<FlexGrid>>* texture_grid = nullptr;
{
std::unique_lock<std::mutex> lock(data->mutex(), std::try_to_lock);
if (!lock.owns_lock() || !data->ready()) {
emit repaint_required();
return;
}
mesh_copy = data->mesh();
if (m_gpu_texture && mesh_copy.has_texture) {
texture_grid = &data->texture_grid();
}
}
if (!m_mesh_uploaded) {
upload_mesh(mesh_copy, camera.world_offset());
}
if (m_gpu_texture && !m_texture_uploaded && texture_grid != nullptr) {
upload_texture(*texture_grid);
}
}
if (!m_shader || !m_mesh_uploaded || m_index_count == 0) {
return;
}
if (m_gpu_texture && !m_texture_uploaded) {
return;
}
QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
if (!f) {
return;
}
CHECK_GL(f->glEnable(GL_DEPTH_TEST));
CHECK_GL(f->glEnable(GL_BLEND));
CHECK_GL(f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
if (!bind_shader(m_shader.get())) {
return;
}
m_shader->setUniformValue(m_proj_matrix_loc, camera.proj_matrix());
if (m_light_direction_loc >= 0) {
m_shader->setUniformValue(m_light_direction_loc, ctx.light_direction_world);
}
if (m_camera_position_loc >= 0) {
m_shader->setUniformValue(m_camera_position_loc, camera.position());
}
if (m_ambient_light_loc >= 0) {
m_shader->setUniformValue(m_ambient_light_loc, ctx.ambient_light);
}
if (m_diffuse_light_loc >= 0) {
m_shader->setUniformValue(m_diffuse_light_loc, ctx.diffuse_light);
}
float layer_alpha = 1.0f;
float vertical_offset = 0.0f;
if (auto layer = m_layer.lock()) {
layer_alpha = layer->opacity();
vertical_offset = layer->vertical_offset();
}
if (m_layer_alpha_loc >= 0) {
m_shader->setUniformValue(m_layer_alpha_loc, layer_alpha);
}
if (m_vertical_offset_loc >= 0) {
m_shader->setUniformValue(m_vertical_offset_loc, vertical_offset);
}
// Keep depth writes enabled for partial opacity so later layers depth-test against
// this surface instead of painting over it in list order.
CHECK_GL(f->glDepthMask(GL_TRUE));
if (m_gpu_texture && m_texture) {
m_texture->bind(0);
CHECK_GL_AFTER();
m_shader->setUniformValue(m_texture_sampler_loc, 0);
}
QOpenGLVertexArrayObject::Binder vao_binder(&m_vao);
if (!m_vao.isCreated() || !m_vbo.isCreated() || !m_ibo.isCreated()) {
if (m_gpu_texture && m_texture) {
m_texture->release();
CHECK_GL_AFTER();
}
m_shader->release();
CHECK_GL_AFTER();
return;
}
m_vbo.bind();
m_ibo.bind();
CHECK_GL_AFTER();
if (m_gpu_texture) {
const int stride = static_cast<int>(sizeof(TexturedMeshVertex));
m_shader->enableAttributeArray(0);
m_shader->setAttributeBuffer(0, GL_FLOAT, offsetof(TexturedMeshVertex, position), 3, stride);
m_shader->enableAttributeArray(1);
m_shader->setAttributeBuffer(1, GL_FLOAT, offsetof(TexturedMeshVertex, texcoord), 2, stride);
m_shader->enableAttributeArray(2);
m_shader->setAttributeBuffer(2, GL_FLOAT, offsetof(TexturedMeshVertex, normal), 3, stride);
} else {
const int stride = static_cast<int>(sizeof(MeshVertex));
m_shader->enableAttributeArray(0);
m_shader->setAttributeBuffer(0, GL_FLOAT, offsetof(MeshVertex, position), 3, stride);
m_shader->enableAttributeArray(1);
m_shader->setAttributeBuffer(1, GL_FLOAT, offsetof(MeshVertex, color), 3, stride);
m_shader->enableAttributeArray(2);
m_shader->setAttributeBuffer(2, GL_FLOAT, offsetof(MeshVertex, normal), 3, stride);
}
CHECK_GL_AFTER();
CHECK_GL(f->glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_index_count), GL_UNSIGNED_INT,
nullptr));
if (m_gpu_texture && m_texture) {
m_texture->release();
CHECK_GL_AFTER();
}
CHECK_GL(f->glDepthMask(GL_TRUE));
CHECK_GL(f->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
m_shader->release();
CHECK_GL_AFTER();
}