Files
OrcaSlicer/src/libvgcode/src/ViewerImpl.cpp
T
HanifKoh cda1588578 Draw the Toolpaths Top-Down When the Camera Looks Down on the Print (#15883)
The segments come in print order, bottom layer first, which seen from above is back to
front: every hidden fragment is shaded before the one that covers it, and on an integrated
GPU that overdraw is most of the frame. Drawing the instances last to first whenever the
camera looks down lets the depth test reject the hidden fragments instead. Side views and
views from below keep the print order, and the shadow-caster pass is unchanged.
2026-09-28 15:36:08 +08:00

2344 lines
94 KiB
C++

///|/ Copyright (c) Prusa Research 2023 Enrico Turri @enricoturri1966, Pavel Mikuš @Godrak, Vojtěch Bubník @bubnikv, Oleksandra Iushchenko @YuSanka
///|/
///|/ libvgcode is released under the terms of the AGPLv3 or higher
///|/
#include "ViewerImpl.hpp"
#include "../include/GCodeInputData.hpp"
#include "Shaders.hpp"
#include "ShadersES.hpp"
#include "OpenGLUtils.hpp"
#include "Utils.hpp"
#include <map>
#include <assert.h>
#include <stdexcept>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cmath>
#include <numeric>
namespace libvgcode {
template<class T, class O = T>
using IntegerOnly = std::enable_if_t<std::is_integral<T>::value, O>;
// Rounding up.
// 1.5 is rounded to 2
// 1.49 is rounded to 1
// 0.5 is rounded to 1,
// 0.49 is rounded to 0
// -0.5 is rounded to 0,
// -0.51 is rounded to -1,
// -1.5 is rounded to -1.
// -1.51 is rounded to -2.
// If input is not a valid float (it is infinity NaN or if it does not fit)
// the float to int conversion produces a max int on Intel and +-max int on ARM.
template<typename I>
inline IntegerOnly<I, I> fast_round_up(double a)
{
// Why does Java Math.round(0.49999999999999994) return 1?
// https://stackoverflow.com/questions/9902968/why-does-math-round0-49999999999999994-return-1
return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5));
}
// Round to a bin with minimum two digits resolution.
// Equivalent to conversion to string with sprintf(buf, "%.2g", value) and conversion back to float, but faster.
static float round_to_bin(const float value)
{
// assert(value >= 0);
constexpr float const scale[5] = { 100.f, 1000.f, 10000.f, 100000.f, 1000000.f };
constexpr float const invscale[5] = { 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f };
constexpr float const threshold[5] = { 0.095f, 0.0095f, 0.00095f, 0.000095f, 0.0000095f };
// Scaling factor, pointer to the tables above.
int i = 0;
// While the scaling factor is not yet large enough to get two integer digits after scaling and rounding:
for (; value < threshold[i] && i < 4; ++i);
// At least on MSVC std::round() calls a complex function, which is pretty expensive.
// our fast_round_up is much cheaper and it could be inlined.
// return std::round(value * scale[i]) * invscale[i];
double a = value * scale[i];
assert(std::abs(a) < double(std::numeric_limits<int64_t>::max()));
return fast_round_up<int64_t>(a) * invscale[i];
}
static Mat4x4 inverse(const Mat4x4& m)
{
// ref: https://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix
Mat4x4 inv;
inv[0] = m[5] * m[10] * m[15] -
m[5] * m[11] * m[14] -
m[9] * m[6] * m[15] +
m[9] * m[7] * m[14] +
m[13] * m[6] * m[11] -
m[13] * m[7] * m[10];
inv[4] = -m[4] * m[10] * m[15] +
m[4] * m[11] * m[14] +
m[8] * m[6] * m[15] -
m[8] * m[7] * m[14] -
m[12] * m[6] * m[11] +
m[12] * m[7] * m[10];
inv[8] = m[4] * m[9] * m[15] -
m[4] * m[11] * m[13] -
m[8] * m[5] * m[15] +
m[8] * m[7] * m[13] +
m[12] * m[5] * m[11] -
m[12] * m[7] * m[9];
inv[12] = -m[4] * m[9] * m[14] +
m[4] * m[10] * m[13] +
m[8] * m[5] * m[14] -
m[8] * m[6] * m[13] -
m[12] * m[5] * m[10] +
m[12] * m[6] * m[9];
inv[1] = -m[1] * m[10] * m[15] +
m[1] * m[11] * m[14] +
m[9] * m[2] * m[15] -
m[9] * m[3] * m[14] -
m[13] * m[2] * m[11] +
m[13] * m[3] * m[10];
inv[5] = m[0] * m[10] * m[15] -
m[0] * m[11] * m[14] -
m[8] * m[2] * m[15] +
m[8] * m[3] * m[14] +
m[12] * m[2] * m[11] -
m[12] * m[3] * m[10];
inv[9] = -m[0] * m[9] * m[15] +
m[0] * m[11] * m[13] +
m[8] * m[1] * m[15] -
m[8] * m[3] * m[13] -
m[12] * m[1] * m[11] +
m[12] * m[3] * m[9];
inv[13] = m[0] * m[9] * m[14] -
m[0] * m[10] * m[13] -
m[8] * m[1] * m[14] +
m[8] * m[2] * m[13] +
m[12] * m[1] * m[10] -
m[12] * m[2] * m[9];
inv[2] = m[1] * m[6] * m[15] -
m[1] * m[7] * m[14] -
m[5] * m[2] * m[15] +
m[5] * m[3] * m[14] +
m[13] * m[2] * m[7] -
m[13] * m[3] * m[6];
inv[6] = -m[0] * m[6] * m[15] +
m[0] * m[7] * m[14] +
m[4] * m[2] * m[15] -
m[4] * m[3] * m[14] -
m[12] * m[2] * m[7] +
m[12] * m[3] * m[6];
inv[10] = m[0] * m[5] * m[15] -
m[0] * m[7] * m[13] -
m[4] * m[1] * m[15] +
m[4] * m[3] * m[13] +
m[12] * m[1] * m[7] -
m[12] * m[3] * m[5];
inv[14] = -m[0] * m[5] * m[14] +
m[0] * m[6] * m[13] +
m[4] * m[1] * m[14] -
m[4] * m[2] * m[13] -
m[12] * m[1] * m[6] +
m[12] * m[2] * m[5];
inv[3] = -m[1] * m[6] * m[11] +
m[1] * m[7] * m[10] +
m[5] * m[2] * m[11] -
m[5] * m[3] * m[10] -
m[9] * m[2] * m[7] +
m[9] * m[3] * m[6];
inv[7] = m[0] * m[6] * m[11] -
m[0] * m[7] * m[10] -
m[4] * m[2] * m[11] +
m[4] * m[3] * m[10] +
m[8] * m[2] * m[7] -
m[8] * m[3] * m[6];
inv[11] = -m[0] * m[5] * m[11] +
m[0] * m[7] * m[9] +
m[4] * m[1] * m[11] -
m[4] * m[3] * m[9] -
m[8] * m[1] * m[7] +
m[8] * m[3] * m[5];
inv[15] = m[0] * m[5] * m[10] -
m[0] * m[6] * m[9] -
m[4] * m[1] * m[10] +
m[4] * m[2] * m[9] +
m[8] * m[1] * m[6] -
m[8] * m[2] * m[5];
float det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12];
assert(det != 0.0f);
det = 1.0f / det;
std::array<float, 16> ret = {};
for (int i = 0; i < 16; ++i) {
ret[i] = inv[i] * det;
}
return ret;
}
std::string check_shader(GLuint handle)
{
std::string ret;
GLint params;
glsafe(glGetShaderiv(handle, GL_COMPILE_STATUS, &params));
if (params == GL_FALSE) {
glsafe(glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &params));
ret.resize(params);
glsafe(glGetShaderInfoLog(handle, params, &params, ret.data()));
}
return ret;
}
std::string check_program(GLuint handle)
{
std::string ret;
GLint params;
glsafe(glGetProgramiv(handle, GL_LINK_STATUS, &params));
if (params == GL_FALSE) {
glsafe(glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &params));
ret.resize(params);
glsafe(glGetProgramInfoLog(handle, params, &params, ret.data()));
}
return ret;
}
unsigned int init_shader(const std::string& shader_name, const char* vertex_shader, const char* fragment_shader)
{
const GLuint vs_id = glCreateShader(GL_VERTEX_SHADER);
glcheck();
glsafe(glShaderSource(vs_id, 1, &vertex_shader, nullptr));
glsafe(glCompileShader(vs_id));
std::string res = check_shader(vs_id);
if (!res.empty()) {
glsafe(glDeleteShader(vs_id));
throw std::runtime_error("LibVGCode: Unable to compile vertex shader:\n" + shader_name + "\n" + res + "\n");
}
const GLuint fs_id = glCreateShader(GL_FRAGMENT_SHADER);
glcheck();
glsafe(glShaderSource(fs_id, 1, &fragment_shader, nullptr));
glsafe(glCompileShader(fs_id));
res = check_shader(fs_id);
if (!res.empty()) {
glsafe(glDeleteShader(vs_id));
glsafe(glDeleteShader(fs_id));
throw std::runtime_error("LibVGCode: Unable to compile fragment shader:\n" + shader_name + "\n" + res + "\n");
}
const GLuint shader_id = glCreateProgram();
glcheck();
glsafe(glAttachShader(shader_id, vs_id));
glsafe(glAttachShader(shader_id, fs_id));
glsafe(glLinkProgram(shader_id));
res = check_program(shader_id);
if (!res.empty()) {
glsafe(glDetachShader(shader_id, vs_id));
glsafe(glDetachShader(shader_id, fs_id));
glsafe(glDeleteShader(vs_id));
glsafe(glDeleteShader(fs_id));
glsafe(glDeleteProgram(shader_id));
throw std::runtime_error("LibVGCode: Unable to link shader program:\n" + shader_name + "\n" + res + "\n");
}
glsafe(glDetachShader(shader_id, vs_id));
glsafe(glDetachShader(shader_id, fs_id));
glsafe(glDeleteShader(vs_id));
glsafe(glDeleteShader(fs_id));
return shader_id;
}
static void delete_textures(unsigned int& id)
{
if (id != 0) {
glsafe(glDeleteTextures(1, &id));
id = 0;
}
}
static void delete_buffers(unsigned int& id)
{
if (id != 0) {
glsafe(glDeleteBuffers(1, &id));
id = 0;
}
}
static const std::array<Color, size_t(EGCodeExtrusionRole::COUNT)> DEFAULT_EXTRUSION_ROLES_COLORS = { {
{ 230, 179, 179 }, // None
{ 255, 230, 77 }, // Perimeter
{ 255, 125, 56 }, // ExternalPerimeter
{ 31, 31, 255 }, // OverhangPerimeter
{ 176, 48, 41 }, // InternalInfill
{ 150, 84, 204 }, // SolidInfill
{ 240, 64, 64 }, // TopSolidInfill
{ 255, 140, 105 }, // Ironing
{ 77, 128, 186 }, // BridgeInfill
{ 255, 255, 255 }, // GapFill
{ 0, 135, 110 }, // Skirt
{ 0, 255, 0 }, // SupportMaterial
{ 0, 128, 0 }, // SupportMaterialInterface
{ 179, 227, 171 }, // WipeTower
{ 94, 209, 148 }, // Custom
// ORCA
{ 102, 92, 199 }, // BottomSurface
{ 77, 128, 186 }, // InternalBridgeInfill
{ 0, 59, 110 }, // Brim
{ 0, 64, 0 }, // SupportTransition
{ 128, 128, 128 }, // Mixed
} };
static const std::array<Color, size_t(EOptionType::COUNT)> DEFAULT_OPTIONS_COLORS{ {
{ 56, 72, 155 }, // Travels
{ 255, 255, 0 }, // Wipes
{ 205, 34, 214 }, // Retractions
{ 73, 173, 207 }, // Unretractions
{ 230, 230, 230 }, // Seams
{ 193, 190, 99 }, // ToolChanges
{ 218, 148, 139 }, // ColorChanges
{ 82, 240, 131 }, // PausePrints
{ 226, 210, 67 } // CustomGCodes
} };
#ifdef ENABLE_OPENGL_ES
static std::pair<size_t, size_t> width_height(size_t count)
{
std::pair<size_t, size_t> ret;
ret.first = std::min(count, OpenGLWrapper::max_texture_size());
size_t rows_count = count / ret.first;
if (count > rows_count * ret.first)
++rows_count;
ret.second = std::min(rows_count, OpenGLWrapper::max_texture_size());
return ret;
}
void ViewerImpl::TextureData::init(size_t vertices_count)
{
if (vertices_count == 0)
return;
m_width = std::min(vertices_count, OpenGLWrapper::max_texture_size());
size_t rows_count = vertices_count / m_width;
if (vertices_count > rows_count * m_width)
++rows_count;
m_height = std::min(rows_count, OpenGLWrapper::max_texture_size());
m_count = rows_count / m_height;
if (rows_count > m_count * m_height)
++m_count;
const std::pair<size_t, size_t> test = width_height(vertices_count);
assert(test.first == m_width);
assert(test.second == m_height);
m_tex_ids = std::vector<TexIds>(m_count);
}
void ViewerImpl::TextureData::set_positions(const std::vector<Vec3>& positions)
{
if (m_count == 0)
return;
for (TexIds& ids : m_tex_ids) {
delete_textures(ids.positions.first);
ids.positions.second = 0;
}
m_positions_size = 0;
if (positions.empty())
return;
int curr_bound_texture = 0;
glsafe(glGetIntegerv(GL_TEXTURE_BINDING_2D, &curr_bound_texture));
int curr_unpack_alignment = 0;
glsafe(glGetIntegerv(GL_UNPACK_ALIGNMENT, &curr_unpack_alignment));
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
const size_t tex_capacity = max_texture_capacity();
size_t remaining = positions.size();
for (size_t i = 0; i < m_count; ++i) {
const auto [w, h] = width_height(std::min(remaining, tex_capacity));
const size_t offset = i * tex_capacity;
glsafe(glGenTextures(1, &m_tex_ids[i].positions.first));
glsafe(glBindTexture(GL_TEXTURE_2D, m_tex_ids[i].positions.first));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
if (remaining >= tex_capacity) {
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGB, GL_FLOAT, &positions[offset]));
m_tex_ids[i].positions.second = w * h;
}
else {
// the last row is only partially fitted with data, send it separately
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGB, GL_FLOAT, nullptr));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RGB, GL_FLOAT, &positions[offset]));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(remaining % w), 1, GL_RGB, GL_FLOAT, &positions[offset + w * (h - 1)]));
m_tex_ids[i].positions.second = w * (h - 1) + remaining % w;
}
m_positions_size += m_tex_ids[i].positions.second * sizeof(Vec3);
remaining = (remaining > tex_capacity) ? remaining - tex_capacity: 0;
}
glsafe(glBindTexture(GL_TEXTURE_2D, curr_bound_texture));
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, curr_unpack_alignment));
}
void ViewerImpl::TextureData::set_heights_widths_angles(const std::vector<Vec3>& heights_widths_angles)
{
if (m_count == 0)
return;
for (TexIds& ids : m_tex_ids) {
delete_textures(ids.heights_widths_angles.first);
ids.heights_widths_angles.second = 0;
}
m_height_width_angle_size = 0;
if (heights_widths_angles.empty())
return;
int curr_bound_texture = 0;
glsafe(glGetIntegerv(GL_TEXTURE_BINDING_2D, &curr_bound_texture));
int curr_unpack_alignment = 0;
glsafe(glGetIntegerv(GL_UNPACK_ALIGNMENT, &curr_unpack_alignment));
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
const size_t tex_capacity = max_texture_capacity();
size_t remaining = heights_widths_angles.size();
for (size_t i = 0; i < m_count; ++i) {
const auto [w, h] = width_height(std::min(remaining, tex_capacity));
const size_t offset = i * tex_capacity;
glsafe(glGenTextures(1, &m_tex_ids[i].heights_widths_angles.first));
glsafe(glBindTexture(GL_TEXTURE_2D, m_tex_ids[i].heights_widths_angles.first));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
if (remaining >= tex_capacity) {
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGB, GL_FLOAT, &heights_widths_angles[offset]));
m_tex_ids[i].heights_widths_angles.second = w * h;
}
else {
// the last row is only partially fitted with data, send it separately
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGB, GL_FLOAT, nullptr));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RGB, GL_FLOAT, &heights_widths_angles[offset]));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(remaining % w), 1, GL_RGB, GL_FLOAT, &heights_widths_angles[offset + w * (h - 1)]));
m_tex_ids[i].heights_widths_angles.second = w * (h - 1) + remaining % w;
}
m_height_width_angle_size += m_tex_ids[i].heights_widths_angles.second * sizeof(Vec3);
remaining = (remaining > tex_capacity) ? remaining - tex_capacity : 0;
}
glsafe(glBindTexture(GL_TEXTURE_2D, curr_bound_texture));
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, curr_unpack_alignment));
}
void ViewerImpl::TextureData::set_colors(const std::vector<float>& colors)
{
if (m_count == 0)
return;
for (TexIds& ids : m_tex_ids) {
delete_textures(ids.colors.first);
ids.colors.second = 0;
}
m_colors_size = 0;
if (colors.empty())
return;
int curr_bound_texture = 0;
glsafe(glGetIntegerv(GL_TEXTURE_BINDING_2D, &curr_bound_texture));
int curr_unpack_alignment = 0;
glsafe(glGetIntegerv(GL_UNPACK_ALIGNMENT, &curr_unpack_alignment));
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
const size_t tex_capacity = max_texture_capacity();
size_t remaining = colors.size();
for (size_t i = 0; i < m_count; ++i) {
const auto [w, h] = width_height(std::min(remaining, tex_capacity));
const size_t offset = i * tex_capacity;
glsafe(glGenTextures(1, &m_tex_ids[i].colors.first));
glsafe(glBindTexture(GL_TEXTURE_2D, m_tex_ids[i].colors.first));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
if (remaining >= tex_capacity) {
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RED, GL_FLOAT, &colors[offset]));
m_tex_ids[i].colors.second = w * h;
}
else {
// the last row is only partially fitted with data, send it separately
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RED, GL_FLOAT, nullptr));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RED, GL_FLOAT, &colors[offset]));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(remaining % w), 1, GL_RED, GL_FLOAT, &colors[offset + w * (h - 1)]));
m_tex_ids[i].colors.second = w * (h - 1) + remaining % w;
}
m_colors_size += m_tex_ids[i].colors.second * sizeof(float);
remaining = (remaining > tex_capacity) ? remaining - tex_capacity : 0;
}
glsafe(glBindTexture(GL_TEXTURE_2D, curr_bound_texture));
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, curr_unpack_alignment));
}
void ViewerImpl::TextureData::set_enabled_segments(const std::vector<uint32_t>& enabled_segments)
{
if (m_count == 0)
return;
for (TexIds& ids : m_tex_ids) {
delete_textures(ids.enabled_segments.first);
ids.enabled_segments.second = 0;
}
m_enabled_segments_size = 0;
if (enabled_segments.empty())
return;
int curr_bound_texture = 0;
glsafe(glGetIntegerv(GL_TEXTURE_BINDING_2D, &curr_bound_texture));
int curr_unpack_alignment = 0;
glsafe(glGetIntegerv(GL_UNPACK_ALIGNMENT, &curr_unpack_alignment));
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
const size_t tex_capacity = max_texture_capacity();
size_t curr_tex_id = 0;
std::vector<uint32_t> curr_segments;
for (size_t i = 0; i < enabled_segments.size(); ++i) {
uint32_t seg = enabled_segments[i];
const bool new_tex = static_cast<size_t>(seg) > (curr_tex_id + 1) * tex_capacity;
if (!new_tex)
curr_segments.push_back(seg - static_cast<uint32_t>(curr_tex_id * tex_capacity));
if (i + 1 == enabled_segments.size() || new_tex) {
const auto [w, h] = width_height(curr_segments.size());
glsafe(glGenTextures(1, &m_tex_ids[curr_tex_id].enabled_segments.first));
glsafe(glBindTexture(GL_TEXTURE_2D, m_tex_ids[curr_tex_id].enabled_segments.first));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
if (curr_segments.size() == tex_capacity) {
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_R32UI, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RED_INTEGER, GL_UNSIGNED_INT, curr_segments.data()));
m_tex_ids[curr_tex_id].enabled_segments.second = w * h;
}
else {
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_R32UI, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr));
if (h == 1) {
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_R32UI, static_cast<GLsizei>(w), 1, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, curr_segments.data()));
m_tex_ids[curr_tex_id].enabled_segments.second = w;
}
else {
// the last row is only partially fitted with data, send it separately
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RED_INTEGER, GL_UNSIGNED_INT, curr_segments.data()));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(curr_segments.size() % w), 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &curr_segments[w * (h - 1)]));
m_tex_ids[curr_tex_id].enabled_segments.second = w * (h - 1) + curr_segments.size() % w;
}
}
m_enabled_segments_size += m_tex_ids[curr_tex_id].enabled_segments.second * sizeof(uint32_t);
if (new_tex) {
curr_segments.clear();
++curr_tex_id;
curr_segments.push_back(seg - static_cast<uint32_t>(curr_tex_id * tex_capacity));
}
}
}
glsafe(glBindTexture(GL_TEXTURE_2D, curr_bound_texture));
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, curr_unpack_alignment));
}
void ViewerImpl::TextureData::set_enabled_options(const std::vector<uint32_t>& enabled_options)
{
if (m_count == 0)
return;
for (TexIds& ids : m_tex_ids) {
delete_textures(ids.enabled_options.first);
ids.enabled_options.second = 0;
}
m_enabled_options_size = 0;
if (enabled_options.empty())
return;
int curr_bound_texture = 0;
glsafe(glGetIntegerv(GL_TEXTURE_BINDING_2D, &curr_bound_texture));
int curr_unpack_alignment = 0;
glsafe(glGetIntegerv(GL_UNPACK_ALIGNMENT, &curr_unpack_alignment));
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
const size_t tex_capacity = max_texture_capacity();
size_t curr_tex_id = 0;
std::vector<uint32_t> curr_options;
for (size_t i = 0; i < enabled_options.size(); ++i) {
uint32_t opt = enabled_options[i];
const bool new_tex = static_cast<size_t>(opt) > (curr_tex_id + 1) * tex_capacity;
if (!new_tex)
curr_options.push_back(opt - static_cast<uint32_t>(curr_tex_id * tex_capacity));
if (i + 1 == enabled_options.size() || new_tex) {
const auto [w, h] = width_height(curr_options.size());
glsafe(glGenTextures(1, &m_tex_ids[curr_tex_id].enabled_options.first));
glsafe(glBindTexture(GL_TEXTURE_2D, m_tex_ids[curr_tex_id].enabled_options.first));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
if (curr_options.size() == tex_capacity) {
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_R32UI, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RED_INTEGER, GL_UNSIGNED_INT, curr_options.data()));
m_tex_ids[curr_tex_id].enabled_options.second = w * h;
}
else {
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_R32UI, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr));
if (h == 1) {
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_R32UI, static_cast<GLsizei>(w), 1, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, curr_options.data()));
m_tex_ids[curr_tex_id].enabled_options.second = w;
}
else {
// the last row is only partially fitted with data, send it separately
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RED_INTEGER, GL_UNSIGNED_INT, curr_options.data()));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(curr_options.size() % w), 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &curr_options[w * (h - 1)]));
m_tex_ids[curr_tex_id].enabled_options.second = w * (h - 1) + curr_options.size() % w;
}
}
m_enabled_options_size += m_tex_ids[curr_tex_id].enabled_options.second * sizeof(uint32_t);
if (new_tex) {
curr_options.clear();
++curr_tex_id;
curr_options.push_back(opt - static_cast<uint32_t>(curr_tex_id * tex_capacity));
}
}
}
glsafe(glBindTexture(GL_TEXTURE_2D, curr_bound_texture));
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, curr_unpack_alignment));
}
void ViewerImpl::TextureData::reset()
{
for (TexIds& ids : m_tex_ids) {
delete_textures(ids.enabled_options.first);
delete_textures(ids.enabled_segments.first);
delete_textures(ids.colors.first);
delete_textures(ids.heights_widths_angles.first);
delete_textures(ids.positions.first);
}
m_tex_ids.clear();
m_width = 0;
m_height = 0;
m_count = 0;
m_positions_size = 0;
m_height_width_angle_size = 0;
m_colors_size = 0;
m_enabled_segments_size = 0;
m_enabled_options_size = 0;
}
std::pair<unsigned int, size_t> ViewerImpl::TextureData::get_positions_tex_id(size_t id) const
{
assert(id < m_tex_ids.size());
return m_tex_ids[id].positions;
}
std::pair<unsigned int, size_t> ViewerImpl::TextureData::get_heights_widths_angles_tex_id(size_t id) const
{
assert(id < m_tex_ids.size());
return m_tex_ids[id].heights_widths_angles;
}
std::pair<unsigned int, size_t> ViewerImpl::TextureData::get_colors_tex_id(size_t id) const
{
assert(id < m_tex_ids.size());
return m_tex_ids[id].colors;
}
std::pair<unsigned int, size_t> ViewerImpl::TextureData::get_enabled_segments_tex_id(size_t id) const
{
assert(id < m_tex_ids.size());
return m_tex_ids[id].enabled_segments;
}
std::pair<unsigned int, size_t> ViewerImpl::TextureData::get_enabled_options_tex_id(size_t id) const
{
assert(id < m_tex_ids.size());
return m_tex_ids[id].enabled_options;
}
size_t ViewerImpl::TextureData::get_enabled_segments_count() const
{
size_t ret = 0;
for (size_t i = 0; i < m_count; ++i) {
ret += m_tex_ids[i].enabled_segments.second;
}
return ret;
}
size_t ViewerImpl::TextureData::get_enabled_options_count() const
{
size_t ret = 0;
for (size_t i = 0; i < m_count; ++i) {
ret += m_tex_ids[i].enabled_options.second;
}
return ret;
}
size_t ViewerImpl::TextureData::get_used_gpu_memory() const
{
size_t ret = 0;
ret += m_positions_size;
ret += m_height_width_angle_size;
ret += m_colors_size;
ret += m_enabled_segments_size;
ret += m_enabled_options_size;
return ret;
}
#endif // ENABLE_OPENGL_ES
ViewerImpl::ViewerImpl()
{
reset_default_extrusion_roles_colors();
reset_default_options_colors();
}
void ViewerImpl::init(const std::string& opengl_context_version)
{
if (m_initialized)
return;
if (!OpenGLWrapper::load_opengl(opengl_context_version)) {
if (OpenGLWrapper::is_valid_context())
throw std::runtime_error("LibVGCode was unable to initialize the GLAD library.\n");
else {
#ifdef ENABLE_OPENGL_ES
throw std::runtime_error("LibVGCode requires an OpenGL ES context based on OpenGL ES 3.0 or higher.\n");
#else
throw std::runtime_error("LibVGCode requires an OpenGL context based on OpenGL 3.2 or higher.\n");
#endif // ENABLE_OPENGL_ES
}
}
// segments shader
#ifdef ENABLE_OPENGL_ES
m_segments_shader_id = init_shader("segments", Segments_Vertex_Shader_ES, Segments_Fragment_Shader_ES);
#else
m_segments_shader_id = init_shader("segments", Segments_Vertex_Shader, Segments_Fragment_Shader);
#endif // ENABLE_OPENGL_ES
m_uni_segments_view_matrix_id = glGetUniformLocation(m_segments_shader_id, "view_matrix");
m_uni_segments_projection_matrix_id = glGetUniformLocation(m_segments_shader_id, "projection_matrix");
m_uni_segments_camera_position_id = glGetUniformLocation(m_segments_shader_id, "camera_position");
m_uni_segments_positions_tex_id = glGetUniformLocation(m_segments_shader_id, "position_tex");
m_uni_segments_height_width_angle_tex_id = glGetUniformLocation(m_segments_shader_id, "height_width_angle_tex");
m_uni_segments_colors_tex_id = glGetUniformLocation(m_segments_shader_id, "color_tex");
m_uni_segments_segment_index_tex_id = glGetUniformLocation(m_segments_shader_id, "segment_index_tex");
m_uni_segments_reverse_order_id = glGetUniformLocation(m_segments_shader_id, "reverse_order");
m_uni_segments_instance_count_id = glGetUniformLocation(m_segments_shader_id, "instance_count");
// ORCA: realistic view
m_uni_segments_shadow_map_id = glGetUniformLocation(m_segments_shader_id, "shadow_map");
m_uni_segments_shadow_light_vp_id = glGetUniformLocation(m_segments_shader_id, "shadow_light_vp");
m_uni_segments_shadow_intensity_id = glGetUniformLocation(m_segments_shader_id, "shadow_intensity");
m_uni_segments_shadow_map_texel_id = glGetUniformLocation(m_segments_shader_id, "shadow_map_texel");
m_uni_segments_exposure_id = glGetUniformLocation(m_segments_shader_id, "exposure");
m_uni_segments_saturation_id = glGetUniformLocation(m_segments_shader_id, "saturation");
m_uni_segments_bias_scale_id = glGetUniformLocation(m_segments_shader_id, "bias_scale");
glcheck();
assert(m_uni_segments_view_matrix_id != -1 &&
m_uni_segments_projection_matrix_id != -1 &&
m_uni_segments_camera_position_id != -1 &&
m_uni_segments_positions_tex_id != -1 &&
m_uni_segments_height_width_angle_tex_id != -1 &&
m_uni_segments_colors_tex_id != -1 &&
m_uni_segments_segment_index_tex_id != -1);
m_segment_template.init();
// options shader
#ifdef ENABLE_OPENGL_ES
m_options_shader_id = init_shader("options", Options_Vertex_Shader_ES, Options_Fragment_Shader_ES);
#else
m_options_shader_id = init_shader("options", Options_Vertex_Shader, Options_Fragment_Shader);
#endif // ENABLE_OPENGL_ES
m_uni_options_view_matrix_id = glGetUniformLocation(m_options_shader_id, "view_matrix");
m_uni_options_projection_matrix_id = glGetUniformLocation(m_options_shader_id, "projection_matrix");
m_uni_options_positions_tex_id = glGetUniformLocation(m_options_shader_id, "position_tex");
m_uni_options_height_width_angle_tex_id = glGetUniformLocation(m_options_shader_id, "height_width_angle_tex");
m_uni_options_colors_tex_id = glGetUniformLocation(m_options_shader_id, "color_tex");
m_uni_options_segment_index_tex_id = glGetUniformLocation(m_options_shader_id, "segment_index_tex");
glcheck();
assert(m_uni_options_view_matrix_id != -1 &&
m_uni_options_projection_matrix_id != -1 &&
m_uni_options_positions_tex_id != -1 &&
m_uni_options_height_width_angle_tex_id != -1 &&
m_uni_options_colors_tex_id != -1 &&
m_uni_options_segment_index_tex_id != -1);
m_option_template.init(16);
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
// cog marker shader
#ifdef ENABLE_OPENGL_ES
m_cog_marker_shader_id = init_shader("cog_marker", Cog_Marker_Vertex_Shader_ES, Cog_Marker_Fragment_Shader_ES);
#else
m_cog_marker_shader_id = init_shader("cog_marker", Cog_Marker_Vertex_Shader, Cog_Marker_Fragment_Shader);
#endif // ENABLE_OPENGL_ES
m_uni_cog_marker_world_center_position = glGetUniformLocation(m_cog_marker_shader_id, "world_center_position");
m_uni_cog_marker_scale_factor = glGetUniformLocation(m_cog_marker_shader_id, "scale_factor");
m_uni_cog_marker_view_matrix = glGetUniformLocation(m_cog_marker_shader_id, "view_matrix");
m_uni_cog_marker_projection_matrix = glGetUniformLocation(m_cog_marker_shader_id, "projection_matrix");
glcheck();
assert(m_uni_cog_marker_world_center_position != -1 &&
m_uni_cog_marker_scale_factor != -1 &&
m_uni_cog_marker_view_matrix != -1 &&
m_uni_cog_marker_projection_matrix != -1);
m_cog_marker.init(32, 1.0f);
// tool marker shader
#ifdef ENABLE_OPENGL_ES
m_tool_marker_shader_id = init_shader("tool_marker", Tool_Marker_Vertex_Shader_ES, Tool_Marker_Fragment_Shader_ES);
#else
m_tool_marker_shader_id = init_shader("tool_marker", Tool_Marker_Vertex_Shader, Tool_Marker_Fragment_Shader);
#endif // ENABLE_OPENGL_ES
m_uni_tool_marker_world_origin = glGetUniformLocation(m_tool_marker_shader_id, "world_origin");
m_uni_tool_marker_scale_factor = glGetUniformLocation(m_tool_marker_shader_id, "scale_factor");
m_uni_tool_marker_view_matrix = glGetUniformLocation(m_tool_marker_shader_id, "view_matrix");
m_uni_tool_marker_projection_matrix = glGetUniformLocation(m_tool_marker_shader_id, "projection_matrix");
m_uni_tool_marker_color_base = glGetUniformLocation(m_tool_marker_shader_id, "color_base");
glcheck();
assert(m_uni_tool_marker_world_origin != -1 &&
m_uni_tool_marker_scale_factor != -1 &&
m_uni_tool_marker_view_matrix != -1 &&
m_uni_tool_marker_projection_matrix != -1 &&
m_uni_tool_marker_color_base != -1);
m_tool_marker.init(32, 2.0f, 4.0f, 1.0f, 8.0f);
#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS
m_initialized = true;
}
void ViewerImpl::shutdown()
{
reset();
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
m_tool_marker.shutdown();
m_cog_marker.shutdown();
#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS
m_option_template.shutdown();
m_segment_template.shutdown();
if (m_options_shader_id != 0) {
glsafe(glDeleteProgram(m_options_shader_id));
m_options_shader_id = 0;
}
if (m_segments_shader_id != 0) {
glsafe(glDeleteProgram(m_segments_shader_id));
m_segments_shader_id = 0;
}
m_initialized = false;
OpenGLWrapper::unload_opengl();
}
void ViewerImpl::reset()
{
m_layers.reset();
m_view_range.reset();
m_extrusion_roles.reset();
m_options.clear();
m_used_extruders.clear();
m_total_time = { 0.0f, 0.0f };
m_travels_time = { 0.0f, 0.0f };
m_vertices.clear();
m_vertices_colors.clear();
// swap rather than clear: these are sized by the print, and a reset means the memory
// should go back, not sit reserved until the next load
for (std::vector<float>& times : m_layer_start_times)
std::vector<float>().swap(times);
std::vector<uint32_t>().swap(m_layer_first_vertex);
std::vector<float>().swap(m_colors_scratch);
m_valid_lines_bitset.clear();
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
m_cog_marker.reset();
#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS
#ifdef ENABLE_OPENGL_ES
m_texture_data.reset();
#else
m_enabled_segments_count = 0;
m_enabled_options_count = 0;
m_settings_used_for_ranges = std::nullopt;
delete_textures(m_enabled_options_tex_id);
delete_buffers(m_enabled_options_buf_id);
delete_textures(m_enabled_segments_tex_id);
delete_buffers(m_enabled_segments_buf_id);
delete_textures(m_colors_tex_id);
delete_buffers(m_colors_buf_id);
delete_textures(m_heights_widths_angles_tex_id);
delete_buffers(m_heights_widths_angles_buf_id);
delete_textures(m_positions_tex_id);
delete_buffers(m_positions_buf_id);
#endif // ENABLE_OPENGL_ES
}
// On some graphic cards texture buffers using GL_RGB32F format do not work, see:
// https://dev.prusa3d.com/browse/SPE-2411
// https://github.com/prusa3d/PrusaSlicer/issues/12908
// To let all drivers be happy, we use GL_RGBA32F format, so we need to add an extra (currently unused) float
// to position and heights_widths_angles vectors
using Vec4 = std::array<float, 4>;
static void extract_pos_and_or_hwa(const std::vector<PathVertex>& vertices, float travels_radius, float wipes_radius, BitSet<>& valid_lines_bitset,
std::vector<Vec4>* positions = nullptr, std::vector<Vec4>* heights_widths_angles = nullptr, bool update_bitset = false) {
static constexpr const Vec3 ZERO = { 0.0f, 0.0f, 0.0f };
if (positions == nullptr && heights_widths_angles == nullptr)
return;
if (vertices.empty())
return;
if (travels_radius <= 0.0f || wipes_radius <= 0.0f)
return;
if (positions != nullptr)
positions->reserve(vertices.size());
if (heights_widths_angles != nullptr)
heights_widths_angles->reserve(vertices.size());
for (size_t i = 0; i < vertices.size(); ++i) {
const PathVertex& v = vertices[i];
const EMoveType move_type = v.type;
const bool prev_line_valid = i > 0 && valid_lines_bitset[i - 1];
const Vec3 prev_line = prev_line_valid ? v.position - vertices[i - 1].position : ZERO;
const bool this_line_valid = i + 1 < vertices.size() &&
vertices[i + 1].position != v.position &&
vertices[i + 1].type == move_type &&
move_type != EMoveType::Seam;
const Vec3 this_line = this_line_valid ? vertices[i + 1].position - v.position : ZERO;
if (this_line_valid) {
// there is a valid path between point i and i+1.
}
else {
// the connection is invalid, there should be no line rendered, ever
if (update_bitset)
valid_lines_bitset.reset(i);
}
if (positions != nullptr) {
// the last component is a dummy float to comply with GL_RGBA32F format
Vec4 position = { v.position[0], v.position[1], v.position[2], 0.0f };
if (move_type == EMoveType::Extrude)
// push down extrusion vertices by half height to render them at the right z
position[2] -= 0.5f * v.height;
positions->emplace_back(position);
}
if (heights_widths_angles != nullptr) {
float height = 0.0f;
float width = 0.0f;
if (v.is_travel()) {
height = travels_radius;
width = travels_radius;
}
else if (v.is_wipe()) {
height = wipes_radius;
width = wipes_radius;
}
else {
height = v.height;
width = v.width;
}
// ORCA: Set bias for wipes and options to avoid z-fighting
float bias = 0.0f;
if (v.is_wipe())
bias = 0.05f;
else if (v.is_option())
bias = 0.1f;
// the last component is a dummy float to comply with GL_RGBA32F format
// ORCA: Pass bias to shader
heights_widths_angles->push_back({ height, width,
std::atan2(prev_line[0] * this_line[1] - prev_line[1] * this_line[0], dot(prev_line, this_line)), bias });
}
}
}
void ViewerImpl::load(GCodeInputData&& gcode_data)
{
if (!m_initialized)
return;
if (gcode_data.vertices.empty())
return;
reset();
m_vertices = std::move(gcode_data.vertices);
m_tool_colors = std::move(gcode_data.tools_colors);
m_color_print_colors = std::move(gcode_data.color_print_colors);
m_vertices_colors.resize(m_vertices.size());
m_settings.spiral_vase_mode = gcode_data.spiral_vase_mode;
for (size_t i = 0; i < m_vertices.size(); ++i) {
const PathVertex& v = m_vertices[i];
m_layers.update(v, static_cast<uint32_t>(i));
for (size_t j = 0; j < TIME_MODES_COUNT; ++j) {
m_total_time[j] += v.times[j];
if (v.type == EMoveType::Travel)
m_travels_time[j] += v.times[j];
}
const EOptionType option_type = move_type_to_option(v.type);
if (option_type != EOptionType::COUNT)
m_options.emplace_back(option_type);
if (v.type == EMoveType::Extrude) {
m_extrusion_roles.add(v.role, v.times);
auto estruder_it = m_used_extruders.find(v.extruder_id);
if (estruder_it == m_used_extruders.end())
estruder_it = m_used_extruders.insert({ v.extruder_id, std::vector<ColorPrint>() }).first;
if (estruder_it->second.empty() || estruder_it->second.back().color_id != v.color_id) {
const ColorPrint cp = { v.extruder_id, v.color_id, v.layer_id, m_total_time };
estruder_it->second.emplace_back(cp);
}
}
if (i > 0) {
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
// updates calculation for center of gravity
if (v.type == EMoveType::Extrude &&
v.role != EGCodeExtrusionRole::Skirt &&
v.role != EGCodeExtrusionRole::SupportMaterial &&
v.role != EGCodeExtrusionRole::SupportMaterialInterface &&
v.role != EGCodeExtrusionRole::WipeTower &&
v.role != EGCodeExtrusionRole::Custom &&
v.role != EGCodeExtrusionRole::Brim &&
v.role != EGCodeExtrusionRole::SupportTransition) {
m_cog_marker.update(0.5f * (v.position + m_vertices[i - 1].position), v.weight);
}
#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS
}
}
// Populate layer_duration for each vertex from the accumulated layer times
for (PathVertex& v : m_vertices) {
v.layer_duration = m_layers.get_layer_time(m_settings.time_mode, static_cast<size_t>(v.layer_id));
}
// Index of the first vertex of each layer, walked back to front so that a layer with no
// vertex of its own inherits the next layer's index and the array stays non-decreasing.
if (!m_layers.empty()) {
const uint32_t vertices_count = static_cast<uint32_t>(m_vertices.size());
m_layer_first_vertex.assign(m_layers.count(), vertices_count);
for (uint32_t i = vertices_count; i > 0; --i) {
const uint32_t layer_id = m_vertices[i - 1].layer_id;
if (layer_id < m_layer_first_vertex.size())
m_layer_first_vertex[layer_id] = i - 1;
}
for (size_t i = m_layer_first_vertex.size() - 1; i > 0; --i)
m_layer_first_vertex[i - 1] = std::min(m_layer_first_vertex[i - 1], m_layer_first_vertex[i]);
// the running time at each layer's first vertex, summed in vertex order so that
// get_estimated_time_at() matches a full accumulation exactly
std::array<float, TIME_MODES_COUNT> running{};
for (std::vector<float>& times : m_layer_start_times)
times.assign(m_layer_first_vertex.size(), 0.0f);
size_t layer = 0;
for (size_t i = 0; i <= m_vertices.size(); ++i) {
for (; layer < m_layer_first_vertex.size() && m_layer_first_vertex[layer] == i; ++layer) {
for (size_t j = 0; j < TIME_MODES_COUNT; ++j)
m_layer_start_times[j][layer] = running[j];
}
if (i < m_vertices.size()) {
for (size_t j = 0; j < TIME_MODES_COUNT; ++j)
running[j] += m_vertices[i].times[j];
}
}
}
if (!m_layers.empty())
m_layers.set_view_range(0, static_cast<uint32_t>(m_layers.count()) - 1);
std::sort(m_options.begin(), m_options.end());
m_options.erase(std::unique(m_options.begin(), m_options.end()), m_options.end());
m_options.shrink_to_fit();
// reset segments visibility bitset
m_valid_lines_bitset = BitSet<>(m_vertices.size());
m_valid_lines_bitset.setAll();
if (m_settings.time_mode != ETimeMode::Normal && m_total_time[static_cast<size_t>(m_settings.time_mode)] == 0.0f)
m_settings.time_mode = ETimeMode::Normal;
// buffers to send to gpu
// the last component is a dummy float to comply with GL_RGBA32F format
std::vector<Vec4> positions;
std::vector<Vec4> heights_widths_angles;
positions.reserve(m_vertices.size());
heights_widths_angles.reserve(m_vertices.size());
extract_pos_and_or_hwa(m_vertices, m_travels_radius, m_wipes_radius, m_valid_lines_bitset, &positions, &heights_widths_angles, true);
if (!positions.empty()) {
#ifdef ENABLE_OPENGL_ES
m_texture_data.init(positions.size());
// create and fill position textures
m_texture_data.set_positions(positions);
// create and fill height, width and angle textures
m_texture_data.set_heights_widths_angles(heights_widths_angles);
#else
m_positions_tex_size = positions.size() * sizeof(Vec3);
m_height_width_angle_tex_size = heights_widths_angles.size() * sizeof(Vec3);
int old_bound_texture = 0;
glsafe(glGetIntegerv(GL_TEXTURE_BINDING_BUFFER, &old_bound_texture));
// create and fill positions buffer
glsafe(glGenBuffers(1, &m_positions_buf_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_positions_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, positions.size() * sizeof(Vec4), positions.data(), GL_STATIC_DRAW));
glsafe(glGenTextures(1, &m_positions_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_positions_tex_id));
// create and fill height, width and angles buffer
glsafe(glGenBuffers(1, &m_heights_widths_angles_buf_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_heights_widths_angles_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, heights_widths_angles.size() * sizeof(Vec4), heights_widths_angles.data(), GL_DYNAMIC_DRAW));
glsafe(glGenTextures(1, &m_heights_widths_angles_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_heights_widths_angles_tex_id));
// create (but do not fill) colors buffer (data is set in update_colors())
glsafe(glGenBuffers(1, &m_colors_buf_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_colors_buf_id));
glsafe(glGenTextures(1, &m_colors_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
// create (but do not fill) enabled segments buffer (data is set in update_enabled_entities())
glsafe(glGenBuffers(1, &m_enabled_segments_buf_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_buf_id));
glsafe(glGenTextures(1, &m_enabled_segments_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_tex_id));
// create (but do not fill) enabled options buffer (data is set in update_enabled_entities())
glsafe(glGenBuffers(1, &m_enabled_options_buf_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_buf_id));
glsafe(glGenTextures(1, &m_enabled_options_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, old_bound_texture));
#endif // ENABLE_OPENGL_ES
}
update_view_full_range();
m_view_range.set_visible(m_view_range.get_enabled());
update_enabled_entities();
update_colors();
}
void ViewerImpl::update_enabled_entities()
{
if (m_vertices.empty())
return;
std::vector<uint32_t> enabled_segments;
std::vector<uint32_t> enabled_options;
Interval range = m_view_range.get_visible();
// when top layer only visualization is enabled, we need to render
// all the toolpaths in the other layers as grayed, so extend the range
// to contain them
if (m_settings.top_layer_only_view_range)
range[0] = m_view_range.get_full()[0];
// to show the options at the current tool marker position we need to extend the range by one extra step
if (m_vertices[range[1]].is_option() && range[1] < static_cast<uint32_t>(m_vertices.size()) - 1)
++range[1];
if (m_settings.spiral_vase_mode) {
// when spiral vase mode is enabled and only one layer is shown, extend the range by one step
const Interval& layers_range = m_layers.get_view_range();
if (layers_range[0] > 0 && layers_range[0] == layers_range[1])
--range[0];
}
for (size_t i = range[0]; i < range[1]; ++i) {
const PathVertex& v = m_vertices[i];
if (!m_valid_lines_bitset[i] && !v.is_option())
continue;
if (v.is_travel()) {
if (!m_settings.options_visibility[size_t(EOptionType::Travels)])
continue;
}
else if (v.is_wipe()) {
if (!m_settings.options_visibility[size_t(EOptionType::Wipes)])
continue;
}
else if (v.is_option()) {
if (!m_settings.options_visibility[size_t(move_type_to_option(v.type))])
continue;
}
else if (v.is_extrusion()) {
if (!m_settings.extrusion_roles_visibility[size_t(v.role)])
continue;
}
else
continue;
if (v.is_option())
enabled_options.push_back(static_cast<uint32_t>(i));
else
enabled_segments.push_back(static_cast<uint32_t>(i));
}
#ifdef ENABLE_OPENGL_ES
m_texture_data.set_enabled_segments(enabled_segments);
m_texture_data.set_enabled_options(enabled_options);
#else
m_enabled_segments_count = enabled_segments.size();
m_enabled_options_count = enabled_options.size();
m_enabled_segments_tex_size = enabled_segments.size() * sizeof(uint32_t);
m_enabled_options_tex_size = enabled_options.size() * sizeof(uint32_t);
// update gpu buffer for enabled segments
assert(m_enabled_segments_buf_id > 0);
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_buf_id));
if (!enabled_segments.empty())
glsafe(glBufferData(GL_TEXTURE_BUFFER, enabled_segments.size() * sizeof(uint32_t), enabled_segments.data(), GL_STATIC_DRAW));
else
glsafe(glBufferData(GL_TEXTURE_BUFFER, 0, nullptr, GL_STATIC_DRAW));
// update gpu buffer for enabled options
assert(m_enabled_options_buf_id > 0);
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_buf_id));
if (!enabled_options.empty())
glsafe(glBufferData(GL_TEXTURE_BUFFER, enabled_options.size() * sizeof(uint32_t), enabled_options.data(), GL_STATIC_DRAW));
else
glsafe(glBufferData(GL_TEXTURE_BUFFER, 0, nullptr, GL_STATIC_DRAW));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
#endif // ENABLE_OPENGL_ES
m_settings.update_enabled_entities = false;
}
static float encode_color(const Color& color) {
const int r = static_cast<int>(color[0]);
const int g = static_cast<int>(color[1]);
const int b = static_cast<int>(color[2]);
const int i_color = r << 16 | g << 8 | b;
return static_cast<float>(i_color);
}
// ORCA: returns the encoded color scaled towards black by 'brightness', preserving its hue.
// 1.0 = no change, 0.0 = black.
static float encode_color_dimmed(const Color& color, float brightness) {
const int r = static_cast<int>(color[0] * brightness);
const int g = static_cast<int>(color[1] * brightness);
const int b = static_cast<int>(color[2] * brightness);
const int i_color = r << 16 | g << 8 | b;
return static_cast<float>(i_color);
}
void ViewerImpl::update_colors_texture()
{
#if !defined(ENABLE_OPENGL_ES)
if (m_colors_buf_id == 0)
return;
#endif // ENABLE_OPENGL_ES
const size_t top_layer_id = m_settings.top_layer_only_view_range ? m_layers.get_view_range()[1] : 0;
const bool color_top_layer_only = m_view_range.get_full()[1] != m_view_range.get_visible()[1];
// ORCA: when dim_previous_layers is enabled, darken every layer (keeping its color) except the
// one(s) the layer slider is being scrubbed to, so that only those are shown at full brightness.
// A slider thumb marks a layer as inspected only once it is moved away from
// its end of the print: the upper one while it is below the last layer (or while the moves
// slider is not at the end of the layer), the lower one while it is above the first layer, so
// trimming the print from the bottom lights up the lowest visible layer and using the slider as
// a range lights up both ends. When neither thumb is moved the whole print is rendered normally.
// Gated on top-layer-only mode, which the greying path below also keys off of, so that the moves
// slider still animates normally across all layers when that mode is disabled.
const Interval& layers_range = m_layers.get_view_range();
const bool inspecting_top_layer = layers_range[1] + 1 < m_layers.count() || color_top_layer_only;
const bool inspecting_bottom_layer = layers_range[0] > 0;
const bool dim_previous_layers = m_settings.dim_previous_layers && m_settings.top_layer_only_view_range &&
!m_layers.empty() && (inspecting_top_layer || inspecting_bottom_layer);
// Based on current settings and slider position, we might want to render some
// vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache).
// Reused across calls: this runs on every slider tick, and the allocation alone is
// 4 bytes per vertex of the whole print each time.
std::vector<float>& colors = m_colors_scratch;
colors.resize(m_vertices_colors.size());
assert(colors.size() == m_vertices.size() && m_vertices_colors.size() == m_vertices.size());
for (size_t i=0; i<m_vertices.size(); ++i) {
const PathVertex& v = m_vertices[i];
const bool keep_spiral_seam = m_settings.spiral_vase_mode && i == m_view_range.get_enabled()[0];
// ORCA: layers kept at full brightness by the dimming above are excluded from the greying below too
const bool inspected_layer = dim_previous_layers &&
((inspecting_top_layer && v.layer_id == layers_range[1]) ||
(inspecting_bottom_layer && v.layer_id == layers_range[0]));
if (dim_previous_layers && !inspected_layer && !keep_spiral_seam)
colors[i] = encode_color_dimmed(get_vertex_color(v), m_settings.dim_previous_layers_brightness);
else if (!inspected_layer && color_top_layer_only && v.layer_id < top_layer_id && !keep_spiral_seam)
colors[i] = encode_color(DUMMY_COLOR);
else
colors[i] = m_vertices_colors[i];
}
#ifdef ENABLE_OPENGL_ES
if (!colors.empty())
// update gpu buffer for colors
m_texture_data.set_colors(colors);
#else
m_colors_tex_size = colors.size() * sizeof(float);
// update gpu buffer for colors
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_colors_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, colors.size() * sizeof(float), colors.data(), GL_STATIC_DRAW));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
#endif // ENABLE_OPENGL_ES
}
void ViewerImpl::update_colors()
{
if (!m_used_extruders.empty()) {
// ensure that the number of defined tool colors matches the max id of the used extruders
const size_t max_used_extruder_id = 1 + static_cast<size_t>(m_used_extruders.rbegin()->first);
const size_t tool_colors_size = m_tool_colors.size();
if (m_tool_colors.size() < max_used_extruder_id) {
for (size_t i = 0; i < max_used_extruder_id - tool_colors_size; ++i) {
m_tool_colors.emplace_back(DUMMY_COLOR);
}
}
}
update_color_ranges();
// Recalculate "normal" colors of all the vertices for current view settings.
// If some part of the preview should be rendered in dark grey, it is taken
// care of in update_colors_texture. That is to avoid the need to recalculate
// the "normal" color on every slider move.
for (size_t i = 0; i < m_vertices.size(); ++i)
m_vertices_colors[i] = encode_color(get_vertex_color(m_vertices[i]));
update_colors_texture();
m_settings.update_colors = false;
}
void ViewerImpl::apply_pending_updates()
{
if (m_settings.update_view_full_range)
update_view_full_range();
if (m_settings.update_enabled_entities)
update_enabled_entities();
if (m_settings.update_colors)
update_colors();
}
void ViewerImpl::render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix)
{
apply_pending_updates();
const Mat4x4 inv_view_matrix = inverse(view_matrix);
const Vec3 camera_position = { inv_view_matrix[12], inv_view_matrix[13], inv_view_matrix[14] };
render_segments(view_matrix, projection_matrix, camera_position);
render_options(view_matrix, projection_matrix);
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
if (m_settings.options_visibility[size_t(EOptionType::ToolMarker)])
render_tool_marker(view_matrix, projection_matrix);
if (m_settings.options_visibility[size_t(EOptionType::CenterOfGravity)])
render_cog_marker(view_matrix, projection_matrix);
#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS
}
void ViewerImpl::render_shadow_casters(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& light_position)
{
apply_pending_updates();
// Only the extrusions and travels cast: the option markers are indicators, not material.
m_rendering_shadow_casters = true;
render_segments(view_matrix, projection_matrix, light_position);
m_rendering_shadow_casters = false;
}
void ViewerImpl::set_shadow_map(int texture_unit, const Mat4x4& light_view_projection, float intensity, float texel_size)
{
m_shadow_map_texture_unit = texture_unit;
m_shadow_light_vp = light_view_projection;
m_shadow_intensity = intensity;
m_shadow_map_texel = texel_size;
}
void ViewerImpl::set_tone(float exposure, float saturation)
{
m_exposure = exposure;
m_saturation = saturation;
}
void ViewerImpl::set_view_type(EViewType type)
{
m_settings.view_type = type;
m_settings.update_colors = true;
}
void ViewerImpl::set_time_mode(ETimeMode mode)
{
m_settings.time_mode = mode;
m_settings.update_colors = true;
// Update layer_duration for all vertices based on the new time mode
for (PathVertex& v : m_vertices) {
v.layer_duration = m_layers.get_layer_time(mode, static_cast<size_t>(v.layer_id));
}
}
void ViewerImpl::set_layers_view_range(Interval::value_type min, Interval::value_type max)
{
min = std::clamp<Interval::value_type>(min, 0, m_layers.count() - 1);
max = std::clamp<Interval::value_type>(max, 0, m_layers.count() - 1);
m_layers.set_view_range(min, max);
// force immediate update of the full range
update_view_full_range();
m_view_range.set_visible(m_view_range.get_enabled());
m_settings.update_enabled_entities = true;
//m_settings.update_colors = true;
update_colors_texture();
}
void ViewerImpl::toggle_top_layer_only_view_range()
{
m_settings.top_layer_only_view_range = !m_settings.top_layer_only_view_range;
update_view_full_range();
m_view_range.set_visible(m_view_range.get_enabled());
m_settings.update_enabled_entities = true;
//m_settings.update_colors = true;
update_colors_texture();
}
// ORCA: enable/disable darkening of the layers the layer slider is not scrubbed to
void ViewerImpl::set_dim_previous_layers(bool value)
{
if (m_settings.dim_previous_layers == value)
return;
m_settings.dim_previous_layers = value;
// defer the actual color/texture rebuild to the next render(), when the GL context is current
// (this may be toggled from the Preferences dialog, outside the canvas context)
m_settings.update_colors = true;
}
// ORCA: set how bright the darkened layers are rendered, 1.0 = unchanged, 0.0 = black
void ViewerImpl::set_dim_previous_layers_brightness(float value)
{
value = std::clamp(value, 0.0f, 1.0f);
if (m_settings.dim_previous_layers_brightness == value)
return;
m_settings.dim_previous_layers_brightness = value;
m_settings.update_colors = true;
}
std::vector<ETimeMode> ViewerImpl::get_time_modes() const
{
std::vector<ETimeMode> ret;
for (size_t i = 0; i < TIME_MODES_COUNT; ++i) {
if (std::accumulate(m_vertices.begin(), m_vertices.end(), 0.0f,
[i](float a, const PathVertex& v) { return a + v.times[i]; }) > 0.0f)
ret.push_back(static_cast<ETimeMode>(i));
}
return ret;
}
std::vector<uint8_t> ViewerImpl::get_used_extruders_ids() const
{
std::vector<uint8_t> ret;
ret.reserve(m_used_extruders.size());
for (const auto& [id, colors] : m_used_extruders) {
ret.emplace_back(id);
}
return ret;
}
size_t ViewerImpl::get_color_prints_count(uint8_t extruder_id) const
{
const auto it = m_used_extruders.find(extruder_id);
return (it == m_used_extruders.end()) ? 0 : it->second.size();
}
std::vector<ColorPrint> ViewerImpl::get_color_prints(uint8_t extruder_id) const
{
const auto it = m_used_extruders.find(extruder_id);
return (it == m_used_extruders.end()) ? std::vector<ColorPrint>() : it->second;
}
AABox ViewerImpl::get_bounding_box(const std::vector<EMoveType>& types) const
{
Vec3 min = { FLT_MAX, FLT_MAX, FLT_MAX };
Vec3 max = { -FLT_MAX, -FLT_MAX, -FLT_MAX };
for (const PathVertex& v : m_vertices) {
if (std::find(types.begin(), types.end(), v.type) != types.end()) {
for (int j = 0; j < 3; ++j) {
min[j] = std::min(min[j], v.position[j]);
max[j] = std::max(max[j], v.position[j]);
}
}
}
return { min, max };
}
AABox ViewerImpl::get_extrusion_bounding_box(const std::vector<EGCodeExtrusionRole>& roles) const
{
Vec3 min = { FLT_MAX, FLT_MAX, FLT_MAX };
Vec3 max = { -FLT_MAX, -FLT_MAX, -FLT_MAX };
for (const PathVertex& v : m_vertices) {
if (v.is_extrusion() && std::find(roles.begin(), roles.end(), v.role) != roles.end()) {
for (int j = 0; j < 3; ++j) {
min[j] = std::min(min[j], v.position[j]);
max[j] = std::max(max[j], v.position[j]);
}
}
}
return { min, max };
}
bool ViewerImpl::is_option_visible(EOptionType type) const
{
return m_settings.options_visibility[size_t(type)];
}
void ViewerImpl::toggle_option_visibility(EOptionType type)
{
m_settings.options_visibility[size_t(type)] = ! m_settings.options_visibility[size_t(type)];
const Interval old_enabled_range = m_view_range.get_enabled();
update_view_full_range();
const Interval& new_enabled_range = m_view_range.get_enabled();
if (old_enabled_range != new_enabled_range) {
const Interval& visible_range = m_view_range.get_visible();
if (old_enabled_range == visible_range)
m_view_range.set_visible(new_enabled_range);
else if (m_settings.top_layer_only_view_range && new_enabled_range[0] < visible_range[0])
m_view_range.set_visible(new_enabled_range[0], visible_range[1]);
}
m_settings.update_enabled_entities = true;
m_settings.update_colors = true;
}
bool ViewerImpl::is_extrusion_role_visible(EGCodeExtrusionRole role) const
{
return m_settings.extrusion_roles_visibility[size_t(role)];
}
void ViewerImpl::toggle_extrusion_role_visibility(EGCodeExtrusionRole role)
{
m_settings.extrusion_roles_visibility[size_t(role)] = ! m_settings.extrusion_roles_visibility[size_t(role)];
update_view_full_range();
m_settings.update_enabled_entities = true;
m_settings.update_colors = true;
}
void ViewerImpl::set_view_visible_range(Interval::value_type min, Interval::value_type max)
{
// force update of the full range, to avoid clamping the visible range with full old values
// when calling m_view_range.set_visible()
update_view_full_range();
m_view_range.set_visible(min, max);
update_enabled_entities();
//m_settings.update_colors = true;
update_colors_texture();
}
float ViewerImpl::get_estimated_time_at(size_t id) const
{
const size_t mode = static_cast<size_t>(m_settings.time_mode);
if (mode >= TIME_MODES_COUNT || id >= m_vertices.size())
return 0.0f;
size_t first = 0;
float time = 0.0f;
const size_t layer = static_cast<size_t>(m_vertices[id].layer_id);
if (layer < m_layer_first_vertex.size() && m_layer_first_vertex[layer] <= id) {
first = m_layer_first_vertex[layer];
time = m_layer_start_times[mode][layer];
}
for (size_t i = first; i <= id; ++i)
time += m_vertices[i].times[mode];
return time;
}
Color ViewerImpl::get_vertex_color(const PathVertex& v) const
{
if (v.type == EMoveType::Noop)
return DUMMY_COLOR;
if ((v.is_wipe() && (m_settings.view_type != EViewType::Speed && m_settings.view_type != EViewType::ActualSpeed && m_settings.view_type != EViewType::Acceleration && m_settings.view_type != EViewType::Jerk)) || v.is_option())
return get_option_color(move_type_to_option(v.type));
switch (m_settings.view_type)
{
case EViewType::FeatureType:
{
return v.is_travel() ? get_option_color(move_type_to_option(v.type)) : get_extrusion_role_color(v.role);
}
case EViewType::Height:
{
return v.is_travel() ? get_option_color(move_type_to_option(v.type)) : m_height_range.get_color_at(v.height);
}
case EViewType::Width:
{
return v.is_travel() ? get_option_color(move_type_to_option(v.type)) : m_width_range.get_color_at(v.width);
}
case EViewType::Speed:
{
return m_speed_range.get_color_at(v.feedrate);
}
case EViewType::ActualSpeed:
{
return m_actual_speed_range.get_color_at(v.actual_feedrate);
}
case EViewType::FanSpeed:
{
return v.is_travel() ? get_option_color(move_type_to_option(v.type)) : m_fan_speed_range.get_color_at(v.fan_speed);
}
case EViewType::Temperature:
{
return v.is_travel() ? get_option_color(move_type_to_option(v.type)) : m_temperature_range.get_color_at(v.temperature);
}
// ORCA: Add Pressure Advance visualization support
case EViewType::PressureAdvance:
{
return v.is_travel() ? get_option_color(move_type_to_option(v.type)) : m_pressure_advance_range.get_color_at(v.pressure_advance);
}
// ORCA: Add Acceleration visualization support
case EViewType::Acceleration:
{
return m_acceleration_range.get_color_at(v.acceleration);
}
// ORCA: Add Jerk visualization support
case EViewType::Jerk:
{
return m_jerk_range.get_color_at(v.jerk);
}
case EViewType::VolumetricFlowRate:
{
return v.is_travel() ? get_option_color(move_type_to_option(v.type)) : m_volumetric_rate_range.get_color_at(v.volumetric_rate());
}
case EViewType::ActualVolumetricFlowRate:
{
return v.is_travel() ? get_option_color(move_type_to_option(v.type)) : m_actual_volumetric_rate_range.get_color_at(v.actual_volumetric_rate());
}
case EViewType::LayerTimeLinear:
{
return v.is_travel() ? get_option_color(move_type_to_option(v.type)) :
m_layer_time_range[0].get_color_at(m_layers.get_layer_time(m_settings.time_mode, static_cast<size_t>(v.layer_id)));
}
case EViewType::LayerTimeLogarithmic:
{
return v.is_travel() ? get_option_color(move_type_to_option(v.type)) :
m_layer_time_range[1].get_color_at(m_layers.get_layer_time(m_settings.time_mode, static_cast<size_t>(v.layer_id)));
}
case EViewType::Tool:
{
assert(static_cast<size_t>(v.extruder_id) < m_tool_colors.size());
return m_tool_colors[v.extruder_id];
}
case EViewType::Summary: // ORCA
case EViewType::ColorPrint:
{
return m_layers.layer_contains_colorprint_options(static_cast<size_t>(v.layer_id)) ? DUMMY_COLOR :
m_color_print_colors[static_cast<size_t>(v.color_id) % m_color_print_colors.size()];
}
default: { break; }
}
return DUMMY_COLOR;
}
void ViewerImpl::set_tool_colors(const Palette& colors)
{
m_tool_colors = colors;
m_settings.update_colors = true;
}
void ViewerImpl::set_color_print_colors(const Palette& colors)
{
m_color_print_colors = colors;
m_settings.update_colors = true;
}
const Color& ViewerImpl::get_extrusion_role_color(EGCodeExtrusionRole role) const
{
return m_extrusion_roles_colors[size_t(role)];
}
void ViewerImpl::set_extrusion_role_color(EGCodeExtrusionRole role, const Color& color)
{
m_extrusion_roles_colors[size_t(role)] = color;
m_settings.update_colors = true;
}
void ViewerImpl::reset_default_extrusion_roles_colors()
{
m_extrusion_roles_colors = DEFAULT_EXTRUSION_ROLES_COLORS;
}
const Color& ViewerImpl::get_option_color(EOptionType type) const
{
return m_options_colors[size_t(type)];
}
void ViewerImpl::set_option_color(EOptionType type, const Color& color)
{
m_options_colors[size_t(type)] = color;
m_settings.update_colors = true;
}
void ViewerImpl::reset_default_options_colors()
{
m_options_colors = DEFAULT_OPTIONS_COLORS;
}
const ColorRange& ViewerImpl::get_color_range(EViewType type) const
{
switch (type)
{
case EViewType::Height: { return m_height_range; }
case EViewType::Width: { return m_width_range; }
case EViewType::Speed: { return m_speed_range; }
case EViewType::ActualSpeed: { return m_actual_speed_range; }
case EViewType::FanSpeed: { return m_fan_speed_range; }
case EViewType::Temperature: { return m_temperature_range; }
// ORCA: Add Pressure Advance visualization support
case EViewType::PressureAdvance: { return m_pressure_advance_range; }
// ORCA: Add Acceleration visualization support
case EViewType::Acceleration: { return m_acceleration_range; }
// ORCA: Add Jerk visualization support
case EViewType::Jerk: { return m_jerk_range; }
case EViewType::VolumetricFlowRate: { return m_volumetric_rate_range; }
case EViewType::ActualVolumetricFlowRate: { return m_actual_volumetric_rate_range; }
case EViewType::LayerTimeLinear: { return m_layer_time_range[0]; }
case EViewType::LayerTimeLogarithmic: { return m_layer_time_range[1]; }
default: { return ColorRange::DUMMY_COLOR_RANGE; }
}
}
void ViewerImpl::set_color_range_palette(EViewType type, const Palette& palette)
{
switch (type)
{
case EViewType::Height: { m_height_range.set_palette(palette); break; }
case EViewType::Width: { m_width_range.set_palette(palette); break; }
case EViewType::Speed: { m_speed_range.set_palette(palette); break; }
case EViewType::ActualSpeed: { m_actual_speed_range.set_palette(palette); break; }
case EViewType::FanSpeed: { m_fan_speed_range.set_palette(palette); break; }
case EViewType::Temperature: { m_temperature_range.set_palette(palette); break; }
// ORCA: Add Pressure Advance visualization support
case EViewType::PressureAdvance: { m_pressure_advance_range.set_palette(palette); break; }
// ORCA: Add Acceleration visualization support
case EViewType::Acceleration: { m_acceleration_range.set_palette(palette); break; }
// ORCA: Add Jerk visualization support
case EViewType::Jerk: { m_jerk_range.set_palette(palette); break; }
case EViewType::VolumetricFlowRate: { m_volumetric_rate_range.set_palette(palette); break; }
case EViewType::ActualVolumetricFlowRate: { m_actual_volumetric_rate_range.set_palette(palette); break; }
case EViewType::LayerTimeLinear: { m_layer_time_range[0].set_palette(palette); break; }
case EViewType::LayerTimeLogarithmic: { m_layer_time_range[1].set_palette(palette); break; }
default: { break; }
}
m_settings.update_colors = true;
}
void ViewerImpl::set_travels_radius(float radius)
{
m_travels_radius = std::clamp(radius, MIN_TRAVELS_RADIUS_MM, MAX_TRAVELS_RADIUS_MM);
update_heights_widths();
}
void ViewerImpl::set_wipes_radius(float radius)
{
m_wipes_radius = std::clamp(radius, MIN_WIPES_RADIUS_MM, MAX_WIPES_RADIUS_MM);
update_heights_widths();
}
size_t ViewerImpl::get_used_cpu_memory() const
{
size_t ret = sizeof(*this);
ret += m_layers.size_in_bytes_cpu();
ret += STDVEC_MEMSIZE(m_options, EOptionType);
ret += m_used_extruders.size() * sizeof(std::map<uint8_t, ColorPrint>::value_type);
ret += sizeof(m_extrusion_roles_colors);
ret += sizeof(m_options_colors);
ret += STDVEC_MEMSIZE(m_vertices, PathVertex);
for (const std::vector<float>& times : m_layer_start_times)
ret += STDVEC_MEMSIZE(times, float);
ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t);
ret += STDVEC_MEMSIZE(m_colors_scratch, float);
ret += m_valid_lines_bitset.size_in_bytes_cpu();
ret += m_height_range.size_in_bytes_cpu();
ret += m_width_range.size_in_bytes_cpu();
ret += m_speed_range.size_in_bytes_cpu();
ret += m_actual_speed_range.size_in_bytes_cpu();
ret += m_fan_speed_range.size_in_bytes_cpu();
ret += m_temperature_range.size_in_bytes_cpu();
// ORCA: Add Pressure Advance visualization support
ret += m_pressure_advance_range.size_in_bytes_cpu();
// ORCA: Add Acceleration visualization support
ret += m_acceleration_range.size_in_bytes_cpu();
// ORCA: Add Jerk visualization support
ret += m_jerk_range.size_in_bytes_cpu();
ret += m_volumetric_rate_range.size_in_bytes_cpu();
ret += m_actual_volumetric_rate_range.size_in_bytes_cpu();
for (size_t i = 0; i < COLOR_RANGE_TYPES_COUNT; ++i) {
ret += m_layer_time_range[i].size_in_bytes_cpu();
}
ret += STDVEC_MEMSIZE(m_tool_colors, Color);
ret += STDVEC_MEMSIZE(m_color_print_colors, Color);
return ret;
}
size_t ViewerImpl::get_used_gpu_memory() const
{
size_t ret = 0;
ret += m_segment_template.size_in_bytes_gpu();
ret += m_option_template.size_in_bytes_gpu();
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
ret += m_tool_marker.size_in_bytes_gpu();
ret += m_cog_marker.size_in_bytes_gpu();
#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS
#ifdef ENABLE_OPENGL_ES
ret += m_texture_data.get_used_gpu_memory();
#else
ret += m_positions_tex_size;
ret += m_height_width_angle_tex_size;
ret += m_colors_tex_size;
ret += m_enabled_segments_tex_size;
ret += m_enabled_options_tex_size;
#endif // ENABLE_OPENGL_ES
return ret;
}
static bool is_visible(const PathVertex& v, const Settings& settings)
{
const EOptionType option_type = move_type_to_option(v.type);
try
{
return (option_type == EOptionType::COUNT) ?
(v.type == EMoveType::Extrude) ? settings.extrusion_roles_visibility[size_t(v.role)] : false :
settings.options_visibility[size_t(option_type)];
}
catch (...)
{
return false;
}
}
void ViewerImpl::update_view_full_range()
{
const Interval& layers_range = m_layers.get_view_range();
const bool travels_visible = m_settings.options_visibility[size_t(EOptionType::Travels)];
const bool wipes_visible = m_settings.options_visibility[size_t(EOptionType::Wipes)];
// every vertex before m_layer_first_vertex[layers_range[0]] has a smaller layer_id, so the loop
// below would skip all of them anyway
auto first_it = m_vertices.begin();
if (layers_range[0] < m_layer_first_vertex.size())
first_it += m_layer_first_vertex[layers_range[0]];
while (first_it != m_vertices.end() &&
(first_it->layer_id < layers_range[0] || !is_visible(*first_it, m_settings))) {
++first_it;
}
// If the first vertex is an extrusion, add an extra step to properly detect the first segment
if (first_it != m_vertices.begin() && first_it != m_vertices.end() && first_it->type == EMoveType::Extrude)
--first_it;
if (first_it == m_vertices.end())
m_view_range.set_full(Range());
else {
if (travels_visible || wipes_visible) {
// if the global range starts with a travel/wipe move, extend it to the travel/wipe start
while (first_it != m_vertices.begin() &&
((travels_visible && first_it->is_travel()) ||
(wipes_visible && first_it->is_wipe()))) {
--first_it;
}
}
auto last_it = first_it;
while (last_it != m_vertices.end() && last_it->layer_id <= layers_range[1]) {
++last_it;
}
if (last_it != first_it)
--last_it;
// remove disabled trailing options, if any
auto rev_first_it = std::make_reverse_iterator(first_it);
if (rev_first_it != m_vertices.rbegin())
--rev_first_it;
auto rev_last_it = std::make_reverse_iterator(last_it);
if (rev_last_it != m_vertices.rbegin())
--rev_last_it;
bool reduced = false;
while (rev_last_it != rev_first_it && !is_visible(*rev_last_it, m_settings)) {
++rev_last_it;
reduced = true;
}
if (reduced && rev_last_it != m_vertices.rend())
last_it = rev_last_it.base() - 1;
if (travels_visible || wipes_visible) {
// if the global range ends with a travel/wipe move, extend it to the travel/wipe end
while (last_it != m_vertices.end() && last_it + 1 != m_vertices.end() &&
((travels_visible && last_it->is_travel() && (last_it + 1)->is_travel()) ||
(wipes_visible && last_it->is_wipe() && (last_it + 1)->is_wipe()))) {
++last_it;
}
}
if (first_it != last_it)
m_view_range.set_full(std::distance(m_vertices.begin(), first_it), std::distance(m_vertices.begin(), last_it));
else
m_view_range.set_full(Range());
if (m_settings.top_layer_only_view_range) {
const Interval& full_range = m_view_range.get_full();
auto top_first_it = m_vertices.begin() + full_range[0];
bool shortened = false;
while (top_first_it != m_vertices.end() && (top_first_it->layer_id < layers_range[1] || !is_visible(*top_first_it, m_settings))) {
++top_first_it;
shortened = true;
}
if (shortened)
--top_first_it;
// when spiral vase mode is enabled and only one layer is shown, extend the range by one step
if (m_settings.spiral_vase_mode && layers_range[0] > 0 && layers_range[0] == layers_range[1])
--top_first_it;
m_view_range.set_enabled(std::distance(m_vertices.begin(), top_first_it), full_range[1]);
}
else
m_view_range.set_enabled(m_view_range.get_full());
}
m_settings.update_view_full_range = false;
}
void ViewerImpl::update_color_ranges()
{
// Color ranges do not need to be recalculated that often. If the following settings are the same
// as last time, the current ranges are still valid. The recalculation is quite expensive.
if (m_settings_used_for_ranges.has_value() &&
m_settings.extrusion_roles_visibility == m_settings_used_for_ranges->extrusion_roles_visibility &&
m_settings.options_visibility == m_settings_used_for_ranges->options_visibility)
return;
m_width_range.reset();
m_height_range.reset();
m_speed_range.reset();
m_actual_speed_range.reset();
m_fan_speed_range.reset();
m_temperature_range.reset();
// ORCA: Add Pressure Advance visualization support
m_pressure_advance_range.reset();
// ORCA: Add Acceleration visualization support
m_acceleration_range.reset();
// ORCA: Add Jerk visualization support
m_jerk_range.reset();
m_volumetric_rate_range.reset();
m_actual_volumetric_rate_range.reset();
m_layer_time_range[0].reset(); // ColorRange::EType::Linear
m_layer_time_range[1].reset(); // ColorRange::EType::Logarithmic
for (size_t i = 0; i < m_vertices.size(); i++) {
const PathVertex& v = m_vertices[i];
if (v.is_extrusion()) {
m_height_range.update(round_to_bin(v.height));
if (!v.is_custom_gcode() || m_settings.extrusion_roles_visibility[size_t(EGCodeExtrusionRole::Custom)]) {
m_width_range.update(round_to_bin(v.width));
m_volumetric_rate_range.update(round_to_bin(v.volumetric_rate()));
m_actual_volumetric_rate_range.update(round_to_bin(v.actual_volumetric_rate()));
}
m_fan_speed_range.update(round_to_bin(v.fan_speed));
m_temperature_range.update(round_to_bin(v.temperature));
// ORCA: Add Pressure Advance visualization support
if (v.pressure_advance >= 0.0f)
m_pressure_advance_range.update(v.pressure_advance);
}
if ((v.is_travel() && m_settings.options_visibility[size_t(EOptionType::Travels)]) ||
(v.is_wipe() && m_settings.options_visibility[size_t(EOptionType::Wipes)]) ||
v.is_extrusion()) {
m_speed_range.update(v.feedrate);
m_actual_speed_range.update(v.actual_feedrate);
// ORCA: Add Acceleration visualization support
m_acceleration_range.update(v.acceleration);
// ORCA: Add Jerk visualization support
m_jerk_range.update(v.jerk);
}
}
const std::vector<float> times = m_layers.get_times(m_settings.time_mode);
for (size_t i = 0; i < m_layer_time_range.size(); ++i) {
for (float t : times) {
m_layer_time_range[i].update(t);
}
}
m_settings_used_for_ranges = m_settings;
}
void ViewerImpl::update_heights_widths()
{
#ifdef ENABLE_OPENGL_ES
std::vector<Vec3> heights_widths_angles;
heights_widths_angles.reserve(m_vertices.size());
extract_pos_and_or_hwa(m_vertices, m_travels_radius, m_wipes_radius, m_valid_lines_bitset, nullptr, &heights_widths_angles);
m_texture_data.set_heights_widths_angles(heights_widths_angles);
#else
if (m_heights_widths_angles_buf_id == 0)
return;
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_heights_widths_angles_buf_id));
Vec3* buffer = static_cast<Vec3*>(glMapBuffer(GL_TEXTURE_BUFFER, GL_WRITE_ONLY));
glcheck();
for (size_t i = 0; i < m_vertices.size(); ++i) {
const PathVertex& v = m_vertices[i];
if (v.is_travel()) {
buffer[i][0] = m_travels_radius;
buffer[i][1] = m_travels_radius;
}
else if (v.is_wipe()) {
buffer[i][0] = m_wipes_radius;
buffer[i][1] = m_wipes_radius;
}
}
glsafe(glUnmapBuffer(GL_TEXTURE_BUFFER));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
#endif // ENABLE_OPENGL_ES
}
void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& camera_position)
{
if (m_segments_shader_id == 0)
return;
#ifdef ENABLE_OPENGL_ES
if (m_texture_data.get_enabled_segments_count() == 0)
#else
if (m_enabled_segments_count == 0)
#endif // ENABLE_OPENGL_ES
return;
int curr_active_texture = 0;
glsafe(glGetIntegerv(GL_ACTIVE_TEXTURE, &curr_active_texture));
int curr_shader;
glsafe(glGetIntegerv(GL_CURRENT_PROGRAM, &curr_shader));
const bool curr_cull_face = glIsEnabled(GL_CULL_FACE);
glcheck();
glsafe(glUseProgram(m_segments_shader_id));
glsafe(glUniform1i(m_uni_segments_positions_tex_id, 0));
glsafe(glUniform1i(m_uni_segments_height_width_angle_tex_id, 1));
glsafe(glUniform1i(m_uni_segments_colors_tex_id, 2));
glsafe(glUniform1i(m_uni_segments_segment_index_tex_id, 3));
glsafe(glUniformMatrix4fv(m_uni_segments_view_matrix_id, 1, GL_FALSE, view_matrix.data()));
glsafe(glUniformMatrix4fv(m_uni_segments_projection_matrix_id, 1, GL_FALSE, projection_matrix.data()));
glsafe(glUniform3fv(m_uni_segments_camera_position_id, 1, camera_position.data()));
// The segments come in print order, bottom layer first. Seen from above, that is back to front,
// and every hidden fragment is shaded before the one that covers it. Drawing them last to first
// lets the depth test reject the hidden ones instead. The camera looks down when the world's
// up axis points towards it, which is the view matrix's (2, 2) entry being positive.
const bool top_down = !m_rendering_shadow_casters && view_matrix[10] > 0.0f;
glsafe(glUniform1i(m_uni_segments_reverse_order_id, top_down ? 1 : 0));
#ifndef ENABLE_OPENGL_ES
glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast<int>(m_enabled_segments_count)));
#endif // ENABLE_OPENGL_ES
// ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades
// with the lookup off.
glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit));
glsafe(glUniformMatrix4fv(m_uni_segments_shadow_light_vp_id, 1, GL_FALSE, m_shadow_light_vp.data()));
glsafe(glUniform1f(m_uni_segments_shadow_intensity_id, m_rendering_shadow_casters ? 0.0f : m_shadow_intensity));
glsafe(glUniform1f(m_uni_segments_shadow_map_texel_id, m_shadow_map_texel));
glsafe(glUniform1f(m_uni_segments_exposure_id, m_exposure));
glsafe(glUniform1f(m_uni_segments_saturation_id, m_saturation));
glsafe(glUniform1f(m_uni_segments_bias_scale_id, m_rendering_shadow_casters ? 0.0f : 1.0f));
glsafe(glDisable(GL_CULL_FACE));
#ifdef ENABLE_OPENGL_ES
int curr_bound_texture = 0;
glsafe(glGetIntegerv(GL_TEXTURE_BINDING_2D, &curr_bound_texture));
for (size_t i = 0; i < m_texture_data.get_count(); ++i) {
const auto [id, count] = m_texture_data.get_enabled_segments_tex_id(i);
if (count == 0)
continue;
glsafe(glActiveTexture(GL_TEXTURE0));
glsafe(glBindTexture(GL_TEXTURE_2D, m_texture_data.get_positions_tex_id(i).first));
glsafe(glActiveTexture(GL_TEXTURE1));
glsafe(glBindTexture(GL_TEXTURE_2D, m_texture_data.get_heights_widths_angles_tex_id(i).first));
glsafe(glActiveTexture(GL_TEXTURE2));
glsafe(glBindTexture(GL_TEXTURE_2D, m_texture_data.get_colors_tex_id(i).first));
glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_2D, id));
m_segment_template.render(count);
}
#else
std::array<int, 4> curr_bound_texture = { 0, 0, 0, 0 };
for (int i = 0; i < curr_bound_texture.size(); ++i) {
glsafe(glActiveTexture(GL_TEXTURE0 + i));
glsafe(glGetIntegerv(GL_TEXTURE_BINDING_BUFFER, &curr_bound_texture[i]));
//assert(curr_bound_texture[i] == 0);
}
glsafe(glActiveTexture(GL_TEXTURE0));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_positions_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, m_positions_buf_id));
glsafe(glActiveTexture(GL_TEXTURE1));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_heights_widths_angles_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, m_heights_widths_angles_buf_id));
glsafe(glActiveTexture(GL_TEXTURE2));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id));
glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_segments_buf_id));
m_segment_template.render(m_enabled_segments_count);
#endif // ENABLE_OPENGL_ES
if (curr_cull_face)
glsafe(glEnable(GL_CULL_FACE));
glsafe(glUseProgram(curr_shader));
#ifdef ENABLE_OPENGL_ES
glsafe(glBindTexture(GL_TEXTURE_2D, curr_bound_texture));
#else
for (int i = 0; i < curr_bound_texture.size(); ++i) {
glsafe(glActiveTexture(GL_TEXTURE0 + i));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, curr_bound_texture[i]));
}
#endif // ENABLE_OPENGL_ES
glsafe(glActiveTexture(curr_active_texture));
}
void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& projection_matrix)
{
if (m_options_shader_id == 0)
return;
#ifdef ENABLE_OPENGL_ES
if (m_texture_data.get_enabled_options_count() == 0)
#else
if (m_enabled_options_count == 0)
#endif // ENABLE_OPENGL_ES
return;
int curr_active_texture = 0;
glsafe(glGetIntegerv(GL_ACTIVE_TEXTURE, &curr_active_texture));
int curr_shader;
glsafe(glGetIntegerv(GL_CURRENT_PROGRAM, &curr_shader));
const bool curr_cull_face = glIsEnabled(GL_CULL_FACE);
glcheck();
glsafe(glUseProgram(m_options_shader_id));
glsafe(glUniform1i(m_uni_options_positions_tex_id, 0));
glsafe(glUniform1i(m_uni_options_height_width_angle_tex_id, 1));
glsafe(glUniform1i(m_uni_options_colors_tex_id, 2));
glsafe(glUniform1i(m_uni_options_segment_index_tex_id, 3));
glsafe(glUniformMatrix4fv(m_uni_options_view_matrix_id, 1, GL_FALSE, view_matrix.data()));
glsafe(glUniformMatrix4fv(m_uni_options_projection_matrix_id, 1, GL_FALSE, projection_matrix.data()));
glsafe(glEnable(GL_CULL_FACE));
#ifdef ENABLE_OPENGL_ES
int curr_bound_texture = 0;
glsafe(glGetIntegerv(GL_TEXTURE_BINDING_2D, &curr_bound_texture));
for (size_t i = 0; i < m_texture_data.get_count(); ++i) {
const auto [id, count] = m_texture_data.get_enabled_options_tex_id(i);
if (count == 0)
continue;
glsafe(glActiveTexture(GL_TEXTURE0));
glsafe(glBindTexture(GL_TEXTURE_2D, m_texture_data.get_positions_tex_id(i).first));
glsafe(glActiveTexture(GL_TEXTURE1));
glsafe(glBindTexture(GL_TEXTURE_2D, m_texture_data.get_heights_widths_angles_tex_id(i).first));
glsafe(glActiveTexture(GL_TEXTURE2));
glsafe(glBindTexture(GL_TEXTURE_2D, m_texture_data.get_colors_tex_id(i).first));
glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_2D, id));
m_option_template.render(count);
}
#else
std::array<int, 4> curr_bound_texture = { 0, 0, 0, 0 };
for (int i = 0; i < curr_bound_texture.size(); ++i) {
glsafe(glActiveTexture(GL_TEXTURE0 + i));
glsafe(glGetIntegerv(GL_TEXTURE_BINDING_BUFFER, &curr_bound_texture[i]));
//assert(curr_bound_texture[i] == 0);
}
glsafe(glActiveTexture(GL_TEXTURE0));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_positions_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, m_positions_buf_id));
glsafe(glActiveTexture(GL_TEXTURE1));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_heights_widths_angles_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, m_heights_widths_angles_buf_id));
glsafe(glActiveTexture(GL_TEXTURE2));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id));
glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_options_buf_id));
m_option_template.render(m_enabled_options_count);
#endif // ENABLE_OPENGL_ES
if (!curr_cull_face)
glsafe(glDisable(GL_CULL_FACE));
glsafe(glUseProgram(curr_shader));
#ifdef ENABLE_OPENGL_ES
glsafe(glBindTexture(GL_TEXTURE_2D, curr_bound_texture));
#else
for (int i = 0; i < curr_bound_texture.size(); ++i) {
glsafe(glActiveTexture(GL_TEXTURE0 + i));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, curr_bound_texture[i]));
}
#endif // ENABLE_OPENGL_ES
glsafe(glActiveTexture(curr_active_texture));
}
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
void ViewerImpl::render_cog_marker(const Mat4x4& view_matrix, const Mat4x4& projection_matrix)
{
if (m_cog_marker_shader_id == 0)
return;
int curr_shader;
glsafe(glGetIntegerv(GL_CURRENT_PROGRAM, &curr_shader));
const bool curr_cull_face = glIsEnabled(GL_CULL_FACE);
const bool curr_depth_test = glIsEnabled(GL_DEPTH_TEST);
glcheck();
glsafe(glEnable(GL_CULL_FACE));
glsafe(glDisable(GL_DEPTH_TEST));
glsafe(glUseProgram(m_cog_marker_shader_id));
glsafe(glUniform3fv(m_uni_cog_marker_world_center_position, 1, m_cog_marker.get_position().data()));
glsafe(glUniform1f(m_uni_cog_marker_scale_factor, m_cog_marker_scale_factor));
glsafe(glUniformMatrix4fv(m_uni_cog_marker_view_matrix, 1, GL_FALSE, view_matrix.data()));
glsafe(glUniformMatrix4fv(m_uni_cog_marker_projection_matrix, 1, GL_FALSE, projection_matrix.data()));
m_cog_marker.render();
if (curr_depth_test)
glsafe(glEnable(GL_DEPTH_TEST));
if (!curr_cull_face)
glsafe(glDisable(GL_CULL_FACE));
glsafe(glUseProgram(curr_shader));
}
void ViewerImpl::render_tool_marker(const Mat4x4& view_matrix, const Mat4x4& projection_matrix)
{
if (m_tool_marker_shader_id == 0)
return;
if (m_view_range.get_visible()[1] == m_view_range.get_enabled()[1])
return;
m_tool_marker.set_position(get_current_vertex().position);
int curr_shader;
glsafe(glGetIntegerv(GL_CURRENT_PROGRAM, &curr_shader));
const bool curr_cull_face = glIsEnabled(GL_CULL_FACE);
GLboolean curr_depth_mask;
glsafe(glGetBooleanv(GL_DEPTH_WRITEMASK, &curr_depth_mask));
const bool curr_blend = glIsEnabled(GL_BLEND);
glcheck();
int curr_blend_func;
glsafe(glGetIntegerv(GL_BLEND_SRC_ALPHA, &curr_blend_func));
glsafe(glDisable(GL_CULL_FACE));
glsafe(glDepthMask(GL_FALSE));
glsafe(glEnable(GL_BLEND));
glsafe(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
glsafe(glUseProgram(m_tool_marker_shader_id));
const Vec3& origin = m_tool_marker.get_position();
const Vec3 offset = { 0.0f, 0.0f, m_tool_marker.get_offset_z() };
const Vec3 position = origin + offset;
glsafe(glUniform3fv(m_uni_tool_marker_world_origin, 1, position.data()));
glsafe(glUniform1f(m_uni_tool_marker_scale_factor, m_tool_marker_scale_factor));
glsafe(glUniformMatrix4fv(m_uni_tool_marker_view_matrix, 1, GL_FALSE, view_matrix.data()));
glsafe(glUniformMatrix4fv(m_uni_tool_marker_projection_matrix, 1, GL_FALSE, projection_matrix.data()));
const Color& color = m_tool_marker.get_color();
glsafe(glUniform4f(m_uni_tool_marker_color_base, color[0], color[1], color[2], m_tool_marker.get_alpha()));
m_tool_marker.render();
glsafe(glBlendFunc(GL_SRC_ALPHA, curr_blend_func));
if (!curr_blend)
glsafe(glDisable(GL_BLEND));
if (curr_depth_mask == GL_TRUE)
glsafe(glDepthMask(GL_TRUE));
if (curr_cull_face)
glsafe(glEnable(GL_CULL_FACE));
glsafe(glUseProgram(curr_shader));
}
#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS
} // namespace libvgcode