diff --git a/src/libvgcode/CMakeLists.txt b/src/libvgcode/CMakeLists.txt index e83a0b48c7..c452df0b0a 100644 --- a/src/libvgcode/CMakeLists.txt +++ b/src/libvgcode/CMakeLists.txt @@ -31,6 +31,7 @@ add_library(libvgcode STATIC src/Settings.hpp src/Settings.cpp src/Shaders.hpp + src/ShadersES.hpp src/ToolMarker.hpp src/ToolMarker.cpp src/Types.cpp diff --git a/src/libvgcode/include/Viewer.hpp b/src/libvgcode/include/Viewer.hpp index f8e5492c9d..6285512725 100644 --- a/src/libvgcode/include/Viewer.hpp +++ b/src/libvgcode/include/Viewer.hpp @@ -28,6 +28,11 @@ public: // Initialize the viewer. // This method must be called after a valid OpenGL context has been already created // and before calling any other method of the viewer. + // Throws an std::runtime_error exception if: + // * the method is called before creating an OpenGL context + // * the created OpenGL context does not support for OpenGL 3.2 or greater + // * when using OpenGL ES, the created OpenGL ES context does not support OpenGL ES 2.0 or greater + // * any of the shaders fails to compile // void init(); // diff --git a/src/libvgcode/src/OpenGLUtils.cpp b/src/libvgcode/src/OpenGLUtils.cpp index cdfdbd256a..0ff0102184 100644 --- a/src/libvgcode/src/OpenGLUtils.cpp +++ b/src/libvgcode/src/OpenGLUtils.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace libvgcode { @@ -32,24 +33,37 @@ void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char } #endif // HAS_GLSAFE -bool load_opengl() -{ - return gladLoadGL() != 0; -} +static const std::string OPENGL_ES_STR = "OpenGL ES"; -bool check_opengl_version() +bool OpenGLWrapper::s_detected = false; +bool OpenGLWrapper::s_valid = false; +bool OpenGLWrapper::s_es = false; + +bool OpenGLWrapper::load_opengl() { - bool ret = false; - const GLubyte* version = glGetString(GL_VERSION); - if (version != nullptr) { - const std::string version_str(reinterpret_cast(version)); - if (version_str.length() > 4 && isdigit(version_str[0]) && isdigit(version_str[2])) { - const int major = version_str[0] - '0'; - const int minor = version_str[2] - '0'; - ret = major > 3 || (major == 3 && minor >= 2); - } + if (gladLoadGL() == 0) + return false; + + s_detected = true; + + const char* version = reinterpret_cast(glGetString(GL_VERSION)); + if (version == nullptr) + return false; + + std::string version_str(version); + const size_t pos = version_str.find(OPENGL_ES_STR.c_str()); + if (pos == 0) { + s_es = true; + version_str = version_str.substr(OPENGL_ES_STR.length() + 1); } - return ret; + GLint major = 0; + GLint minor = 0; + const int res = sscanf(version_str.c_str(), "%d.%d", &major, &minor); + if (res != 2) + return false; + + s_valid = s_es ? major > 2 || (major == 2 && minor >= 0) : major > 3 || (major == 3 && minor >= 2); + return s_valid; } } // namespace libvgcode diff --git a/src/libvgcode/src/OpenGLUtils.hpp b/src/libvgcode/src/OpenGLUtils.hpp index 37b0dfc3a2..0ee0d7e954 100644 --- a/src/libvgcode/src/OpenGLUtils.hpp +++ b/src/libvgcode/src/OpenGLUtils.hpp @@ -24,8 +24,21 @@ inline void glAssertRecentCall() { } #define glcheck() #endif // HAS_GLSAFE -extern bool load_opengl(); -extern bool check_opengl_version(); +class OpenGLWrapper +{ +public: + OpenGLWrapper() = delete; + + static bool load_opengl(); + static bool is_detected() { return s_detected; } + static bool is_valid() { return s_valid; } + static bool is_es() { return s_es; } + +private: + static bool s_detected; + static bool s_valid; + static bool s_es; +}; } // namespace libvgcode diff --git a/src/libvgcode/src/ShadersES.hpp b/src/libvgcode/src/ShadersES.hpp new file mode 100644 index 0000000000..37825e2ee1 --- /dev/null +++ b/src/libvgcode/src/ShadersES.hpp @@ -0,0 +1,303 @@ +///|/ Copyright (c) Prusa Research 2023 Enrico Turri @enricoturri1966, Pavel Mikuš @Godrak +///|/ +///|/ libvgcode is released under the terms of the AGPLv3 or higher +///|/ +#ifndef VGCODE_SHADERSES_HPP +#define VGCODE_SHADERSES_HPP + +// needed for tech VGCODE_ENABLE_COG_AND_TOOL_MARKERS +#include "../include/Types.hpp" + +namespace libvgcode { + +static const char* Segments_Vertex_Shader_ES = +"#version 300 es\n" +"#define POINTY_CAPS\n" +"#define FIX_TWISTING\n" +"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n" +"const float light_top_diffuse = 0.6 * 0.8;\n" +"const float light_top_specular = 0.6 * 0.125;\n" +"const float light_top_shininess = 20.0;\n" +"const vec3 light_front_dir = vec3(0.6985074, 0.1397015, 0.6985074);\n" +"const float light_front_diffuse = 0.6 * 0.3;\n" +"const float ambient = 0.3;\n" +"const float emission = 0.15;\n" +"const vec3 UP = vec3(0, 0, 1);\n" +"uniform mat4 view_matrix;\n" +"uniform mat4 projection_matrix;\n" +"uniform vec3 camera_position;\n" +"uniform samplerBuffer positionsTex;\n" +"uniform samplerBuffer heightWidthAngleTex;\n" +"uniform samplerBuffer colorsTex;\n" +"uniform isamplerBuffer segmentIndexTex;\n" +"in int vertex_id;\n" +"out vec3 color;\n" +"vec3 decode_color(float color) {\n" +" int c = int(round(color));\n" +" int r = (c >> 16) & 0xFF;\n" +" int g = (c >> 8) & 0xFF;\n" +" int b = (c >> 0) & 0xFF;\n" +" float f = 1.0 / 255.0f;\n" +" return f * vec3(r, g, b);\n" +"}\n" +"float lighting(vec3 eye_position, vec3 eye_normal) {\n" +" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n" +" float front_diffuse = light_front_diffuse * max(dot(eye_normal, light_front_dir), 0.0);\n" +" float top_specular = light_top_specular * pow(max(dot(-normalize(eye_position), reflect(-light_top_dir, eye_normal)), 0.0), light_top_shininess);\n" +" return ambient + top_diffuse + front_diffuse + top_specular + emission;\n" +"}\n" +"void main() {\n" +" int id_a = texelFetch(segmentIndexTex, gl_InstanceID).r;\n" +" int id_b = id_a + 1;\n" +" vec3 pos_a = texelFetch(positionsTex, id_a).xyz;\n" +" vec3 pos_b = texelFetch(positionsTex, id_b).xyz;\n" +" vec3 line = pos_b - pos_a;\n" +" // directions of the line box in world space\n" +" float line_len = length(line);\n" +" vec3 line_dir;\n" +" if (line_len < 1e-4)\n" +" line_dir = vec3(1.0, 0.0, 0.0);\n" +" else\n" +" line_dir = line / line_len;\n" +" vec3 line_right_dir;\n" +" if (abs(dot(line_dir, UP)) > 0.9) {\n" +" // For vertical lines, the width and height should be same, there is no concept of up and down.\n" +" // For simplicity, the code will expand width in the x axis, and height in the y axis\n" +" line_right_dir = normalize(cross(vec3(1, 0, 0), line_dir));\n" +" }\n" +" else\n" +" line_right_dir = normalize(cross(line_dir, UP));\n" +" vec3 line_up_dir = normalize(cross(line_right_dir, line_dir));\n" +" const vec2 horizontal_vertical_view_signs_array[16] = vec2[](\n" +" //horizontal view (from right)\n" +" vec2(1.0, 0.0),\n" +" vec2(0.0, 1.0),\n" +" vec2(0.0, 0.0),\n" +" vec2(0.0, -1.0),\n" +" vec2(0.0, -1.0),\n" +" vec2(1.0, 0.0),\n" +" vec2(0.0, 1.0),\n" +" vec2(0.0, 0.0),\n" +" // vertical view (from top)\n" +" vec2(0.0, 1.0),\n" +" vec2(-1.0, 0.0),\n" +" vec2(0.0, 0.0),\n" +" vec2(1.0, 0.0),\n" +" vec2(1.0, 0.0),\n" +" vec2(0.0, 1.0),\n" +" vec2(-1.0, 0.0),\n" +" vec2(0.0, 0.0)\n" +" );\n" +" int id = vertex_id < 4 ? id_a : id_b;\n" +" vec3 endpoint_pos = vertex_id < 4 ? pos_a : pos_b;\n" +" vec3 height_width_angle = texelFetch(heightWidthAngleTex, id).xyz;\n" +"#ifdef FIX_TWISTING\n" +" int closer_id = (dot(camera_position - pos_a, camera_position - pos_a) < dot(camera_position - pos_b, camera_position - pos_b)) ? id_a : id_b;\n" +" vec3 closer_pos = (closer_id == id_a) ? pos_a : pos_b;\n" +" vec3 camera_view_dir = normalize(closer_pos - camera_position);\n" +" vec3 closer_height_width_angle = texelFetch(heightWidthAngleTex, closer_id).xyz;\n" +" vec3 diagonal_dir_border = normalize(closer_height_width_angle.x * line_up_dir + closer_height_width_angle.y * line_right_dir);\n" +"#else\n" +" vec3 camera_view_dir = normalize(endpoint_pos - camera_position);\n" +" vec3 diagonal_dir_border = normalize(height_width_angle.x * line_up_dir + height_width_angle.y * line_right_dir);\n" +"#endif\n" +" bool is_vertical_view = abs(dot(camera_view_dir, line_up_dir)) / abs(dot(diagonal_dir_border, line_up_dir)) >\n" +" abs(dot(camera_view_dir, line_right_dir)) / abs(dot(diagonal_dir_border, line_right_dir));\n" +" vec2 signs = horizontal_vertical_view_signs_array[vertex_id + 8 * int(is_vertical_view)];\n" +"#ifndef POINTY_CAPS\n" +" if (vertex_id == 2 || vertex_id == 7) signs = -horizontal_vertical_view_signs_array[(vertex_id - 2) + 8 * int(is_vertical_view)];\n" +"#endif\n" +" float view_right_sign = sign(dot(-camera_view_dir, line_right_dir));\n" +" float view_top_sign = sign(dot(-camera_view_dir, line_up_dir));\n" +" float half_height = 0.5 * height_width_angle.x;\n" +" float half_width = 0.5 * height_width_angle.y;\n" +" vec3 horizontal_dir = half_width * line_right_dir;\n" +" vec3 vertical_dir = half_height * line_up_dir;\n" +" float horizontal_sign = signs.x * view_right_sign;\n" +" float vertical_sign = signs.y * view_top_sign;\n" +" vec3 pos = endpoint_pos + horizontal_sign * horizontal_dir + vertical_sign * vertical_dir;\n" +" if (vertex_id == 2 || vertex_id == 7) {\n" +" float line_dir_sign = (vertex_id == 2) ? -1.0 : 1.0;\n" +" if (height_width_angle.z == 0.0) {\n" +"#ifdef POINTY_CAPS\n" +" // There I add a cap to lines that do not have a following line\n" +" // (or they have one, but perfectly aligned, so the cap is hidden inside the next line).\n" +" pos += line_dir_sign * line_dir * half_width;\n" +"#endif\n" +" }\n" +" else {\n" +" pos += line_dir_sign * line_dir * half_width * sin(abs(height_width_angle.z) * 0.5);\n" +" pos += sign(height_width_angle.z) * horizontal_dir * cos(abs(height_width_angle.z) * 0.5);\n" +" }\n" +" }\n" +" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n" +" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n" +" vec3 color_base = decode_color(texelFetch(colorsTex, id).x);\n" +" color = color_base * lighting(eye_position, eye_normal);\n" +" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n" +"}\n"; + +static const char* Segments_Fragment_Shader_ES = +"#version 300 es\n" +"precision highp float;\n" +"in vec3 color;\n" +"out vec4 fragmentColor;\n" +"void main() {\n" +" fragmentColor = vec4(color, 1.0);\n" +"}\n"; + +static const char* Options_Vertex_Shader_ES = +"#version 300 es\n" +"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n" +"const float light_top_diffuse = 0.6 * 0.8;\n" +"const float light_top_specular = 0.6 * 0.125;\n" +"const float light_top_shininess = 20.0;\n" +"const vec3 light_front_dir = vec3(0.6985074, 0.1397015, 0.6985074);\n" +"const float light_front_diffuse = 0.6 * 0.3;\n" +"const float ambient = 0.3;\n" +"const float emission = 0.25;\n" +"const float scaling_factor = 1.5;\n" +"uniform mat4 view_matrix;\n" +"uniform mat4 projection_matrix;\n" +"uniform samplerBuffer positionsTex;\n" +"uniform samplerBuffer heightWidthAngleTex;\n" +"uniform samplerBuffer colorsTex;\n" +"uniform isamplerBuffer segmentIndexTex;\n" +"in vec3 in_position;\n" +"in vec3 in_normal;\n" +"out vec3 color;\n" +"vec3 decode_color(float color) {\n" +" int c = int(round(color));\n" +" int r = (c >> 16) & 0xFF;\n" +" int g = (c >> 8) & 0xFF;\n" +" int b = (c >> 0) & 0xFF;\n" +" float f = 1.0 / 255.0f;\n" +" return f * vec3(r, g, b);\n" +"}\n" +"float lighting(vec3 eye_position, vec3 eye_normal) {\n" +" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n" +" float front_diffuse = light_front_diffuse * max(dot(eye_normal, light_front_dir), 0.0);\n" +" float top_specular = light_top_specular * pow(max(dot(-normalize(eye_position), reflect(-light_top_dir, eye_normal)), 0.0), light_top_shininess);\n" +" return ambient + top_diffuse + front_diffuse + top_specular + emission;\n" +"}\n" +"void main() {\n" +" int id = texelFetch(segmentIndexTex, gl_InstanceID).r;\n" +" vec2 height_width = texelFetch(heightWidthAngleTex, id).xy;\n" +" vec3 offset = texelFetch(positionsTex, id).xyz - vec3(0.0, 0.0, 0.5 * height_width.x);\n" +" height_width *= scaling_factor;\n" +" mat3 scale_matrix = mat3(\n" +" height_width.y, 0.0, 0.0,\n" +" 0.0, height_width.y, 0.0,\n" +" 0.0, 0.0, height_width.x);\n" +" vec3 eye_position = (view_matrix * vec4(scale_matrix * in_position + offset, 1.0)).xyz;\n" +" vec3 eye_normal = (view_matrix * vec4(in_normal, 0.0)).xyz;\n" +" vec3 color_base = decode_color(texelFetch(colorsTex, id).x);\n" +" color = color_base * lighting(eye_position, eye_normal);\n" +" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n" +"}\n"; + +static const char* Options_Fragment_Shader_ES = +"#version 300 es\n" +"precision highp float;\n" +"in vec3 color;\n" +"out vec4 fragmentColor;\n" +"void main() {\n" +" fragmentColor = vec4(color, 1.0);\n" +"}\n"; + +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS +static const char* Cog_Marker_Vertex_Shader_ES = +"#version 300 es\n" +"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n" +"const float light_top_diffuse = 0.6 * 0.8;\n" +"const float light_top_specular = 0.6 * 0.125;\n" +"const float light_top_shininess = 20.0;\n" +"const vec3 light_front_dir = vec3(0.6985074, 0.1397015, 0.6985074);\n" +"const float light_front_diffuse = 0.6 * 0.3;\n" +"const float ambient = 0.3;\n" +"const float emission = 0.25;\n" +"uniform vec3 world_center_position;\n" +"uniform float scale_factor;\n" +"uniform mat4 view_matrix;\n" +"uniform mat4 projection_matrix;\n" +"in vec3 in_position;\n" +"in vec3 in_normal;\n" +"out float intensity;\n" +"out vec3 world_position;\n" +"float lighting(vec3 eye_position, vec3 eye_normal) {\n" +" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n" +" float front_diffuse = light_front_diffuse * max(dot(eye_normal, light_front_dir), 0.0);\n" +" float top_specular = light_top_specular * pow(max(dot(-normalize(eye_position), reflect(-light_top_dir, eye_normal)), 0.0), light_top_shininess);\n" +" return ambient + top_diffuse + front_diffuse + top_specular + emission;\n" +"}\n" +"void main() {\n" +" world_position = scale_factor * in_position + world_center_position;\n" +" vec3 eye_position = (view_matrix * vec4(world_position, 1.0)).xyz;\n" +" vec3 eye_normal = (view_matrix * vec4(in_normal, 0.0)).xyz;\n" +" intensity = lighting(eye_position, eye_normal);\n" +" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n" +"}\n"; + +static const char* Cog_Marker_Fragment_Shader_ES = +"#version 300 es\n" +"precision highp float;\n" +"const vec3 BLACK = vec3(0.05);\n" +"const vec3 WHITE = vec3(0.95);\n" +"uniform vec3 world_center_position;\n" +"in float intensity;\n" +"in vec3 world_position;\n" +"out vec4 out_color;\n" +"void main()\n" +"{\n" +" vec3 delta = world_position - world_center_position;\n" +" vec3 color = delta.x * delta.y * delta.z > 0.0 ? BLACK : WHITE;\n" +" out_color = intensity * vec4(color, 1.0);\n" +"}\n"; + +static const char* Tool_Marker_Vertex_Shader_ES = +"#version 300 es\n" +"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n" +"const float light_top_diffuse = 0.6 * 0.8;\n" +"const float light_top_specular = 0.6 * 0.125;\n" +"const float light_top_shininess = 20.0;\n" +"const vec3 light_front_dir = vec3(0.6985074, 0.1397015, 0.6985074);\n" +"const float light_front_diffuse = 0.6 * 0.3;\n" +"const float ambient = 0.3;\n" +"const float emission = 0.25;\n" +"uniform vec3 world_origin;\n" +"uniform float scale_factor;\n" +"uniform mat4 view_matrix;\n" +"uniform mat4 projection_matrix;\n" +"uniform vec4 color_base;\n" +"in vec3 in_position;\n" +"in vec3 in_normal;\n" +"out vec4 color;\n" +"float lighting(vec3 eye_position, vec3 eye_normal) {\n" +" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n" +" float front_diffuse = light_front_diffuse * max(dot(eye_normal, light_front_dir), 0.0);\n" +" float top_specular = light_top_specular * pow(max(dot(-normalize(eye_position), reflect(-light_top_dir, eye_normal)), 0.0), light_top_shininess);\n" +" return ambient + top_diffuse + front_diffuse + top_specular + emission;\n" +"}\n" +"void main() {\n" +" vec3 world_position = scale_factor * in_position + world_origin;\n" +" vec3 eye_position = (view_matrix * vec4(world_position, 1.0)).xyz;\n" +" // no need of normal matrix as the scaling is uniform\n" +" vec3 eye_normal = (view_matrix * vec4(in_normal, 0.0)).xyz;\n" +" color = vec4(color_base.rgb * lighting(eye_position, eye_normal), color_base.a);\n" +" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n" +"}\n"; + +static const char* Tool_Marker_Fragment_Shader_ES = +"#version 300 es\n" +"precision highp float;\n" +"in vec4 color;\n" +"out vec4 fragment_color;\n" +"void main() {\n" +" fragment_color = color;\n" +"}\n"; +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + +} // namespace libvgcode + +#endif // VGCODE_SHADERSES_HPP + diff --git a/src/libvgcode/src/ViewerImpl.cpp b/src/libvgcode/src/ViewerImpl.cpp index 3ee6a6c63e..9ac6514cc2 100644 --- a/src/libvgcode/src/ViewerImpl.cpp +++ b/src/libvgcode/src/ViewerImpl.cpp @@ -5,6 +5,7 @@ #include "ViewerImpl.hpp" #include "../include/GCodeInputData.hpp" #include "Shaders.hpp" +#include "ShadersES.hpp" #include "OpenGLUtils.hpp" #include "Utils.hpp" @@ -322,14 +323,21 @@ void ViewerImpl::init() if (m_initialized) return; - if (!load_opengl()) - throw std::runtime_error("LibVGCode was unable to initialize OpenGL\n"); - - if (!check_opengl_version()) - throw std::runtime_error("LibVGCode requires an active OpenGL context based on OpenGL 3.2 or higher:\n"); + if (!OpenGLWrapper::load_opengl()) { + if (!OpenGLWrapper::is_detected()) + throw std::runtime_error("LibVGCode was unable to initialize OpenGL. A valid OpenGL context is missing.\n"); + else if (!OpenGLWrapper::is_valid()) { + if (OpenGLWrapper::is_es()) + throw std::runtime_error("LibVGCode requires an active OpenGL ES context based on OpenGL ES 2.0 or higher:\n"); + else + throw std::runtime_error("LibVGCode requires an active OpenGL context based on OpenGL 3.2 or higher:\n"); + } + } // segments shader - m_segments_shader_id = init_shader("segments", Segments_Vertex_Shader, Segments_Fragment_Shader); + m_segments_shader_id = OpenGLWrapper::is_es() ? + init_shader("segments", Segments_Vertex_Shader_ES, Segments_Fragment_Shader_ES) : + init_shader("segments", Segments_Vertex_Shader, Segments_Fragment_Shader); 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"); @@ -350,7 +358,9 @@ void ViewerImpl::init() m_segment_template.init(); // options shader - m_options_shader_id = init_shader("options", Options_Vertex_Shader, Options_Fragment_Shader); + m_options_shader_id = OpenGLWrapper::is_es() ? + init_shader("options", Options_Vertex_Shader_ES, Options_Fragment_Shader_ES) : + init_shader("options", Options_Vertex_Shader, Options_Fragment_Shader); 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"); @@ -370,7 +380,9 @@ void ViewerImpl::init() #if VGCODE_ENABLE_COG_AND_TOOL_MARKERS // cog marker shader - m_cog_marker_shader_id = init_shader("cog_marker", Cog_Marker_Vertex_Shader, Cog_Marker_Fragment_Shader); + m_cog_marker_shader_id = OpenGLWrapper::is_es() ? + init_shader("cog_marker", Cog_Marker_Vertex_Shader_ES, Cog_Marker_Fragment_Shader_ES) : + init_shader("cog_marker", Cog_Marker_Vertex_Shader, Cog_Marker_Fragment_Shader); 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"); @@ -385,7 +397,9 @@ void ViewerImpl::init() m_cog_marker.init(32, 1.0f); // tool marker shader - m_tool_marker_shader_id = init_shader("tool_marker", Tool_Marker_Vertex_Shader, Tool_Marker_Fragment_Shader); + m_tool_marker_shader_id = OpenGLWrapper::is_es() ? + init_shader("tool_marker", Tool_Marker_Vertex_Shader_ES, Tool_Marker_Fragment_Shader_ES) : + init_shader("tool_marker", Tool_Marker_Vertex_Shader, Tool_Marker_Fragment_Shader); 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");