#include "pch.h" #include "log.h" #include "util.h" #include #include "app.h" #include "legacy_gl_framebuffer_dispatch.h" #include "legacy_gl_runtime_dispatch.h" #include "legacy_gl_sampler_dispatch.h" #include "legacy_gl_shader_dispatch.h" #include "legacy_ui_gl_dispatch.h" #include "renderer_gl/opengl_capabilities.h" template<> std::vector poly_remove_duplicate(const std::vector& v, const float tollerance) { std::vector ret; for (size_t i = 0; i < v.size(); i++) { if (glm::distance2(v[i].pos, v[(i + 1) % v.size()].pos) > tollerance) ret.push_back(v[i]); } return ret; } // params {x, y} and {origin, size} form bool point_in_rect(const glm::vec2& p, const glm::vec4& r) { return p.x > r.x && p.x < r.x+r.z && p.y > r.y && p.y < r.y+r.w; } // from {origin, size} to {min, max} glm::vec4 rect_to_box(const glm::vec4& r) { return { xy(r), xy(r) + zw(r) }; } // from {min, max} to {origin, size} glm::vec4 box_to_rect(const glm::vec4& b) { return { xy(b), zw(b) - xy(b) }; } // params and returns {origin, size} form glm::vec4 rect_intersection(glm::vec4 a, glm::vec4 b) { // convert from [x,y,w,h] to [x1,y1,x2,y1] a = glm::vec4(xy(a), xy(a) + zw(a)); b = glm::vec4(xy(b), xy(b) + zw(b)); // compute intersection auto o = glm::vec4(glm::max(xy(a), xy(b)), glm::min(zw(a), zw(b))); // back to rect form o = glm::vec4(xy(o), glm::max({ 0, 0 }, zw(o) - xy(o))); return o; } // params and returns {origin, size} form glm::vec4 rect_union(glm::vec4 a, glm::vec4 b) { // convert from rect [x,y,w,h] to bb [x1,y1,x2,y1] a = glm::vec4(xy(a), xy(a) + zw(a)); b = glm::vec4(xy(b), xy(b) + zw(b)); // compute union glm::vec4 o = { glm::min(xy(a), xy(b)), glm::max(zw(a), zw(b)) }; // back to rect form o = glm::vec4(xy(o), glm::max({ 0, 0 }, zw(o) - xy(o))); return o; } float box_area(glm::vec4 b) { return glm::compMul(box_size(b)); } glm::vec2 box_size(glm::vec4 b) { return zw(b) - xy(b); } // params and returns {min, max} form glm::vec4 box_union(glm::vec4 a, glm::vec4 b) { return { glm::min(xy(a), xy(b)), glm::max(zw(a), zw(b)) }; } // params and returns {min, max} form glm::vec4 box_intersection(glm::vec4 a, glm::vec4 b) { return { glm::max(xy(a), xy(b)), glm::min(zw(a), zw(b)) }; } bool ray_intersect(glm::vec3 ray_origin, glm::vec3 ray_dir, glm::vec3 plane_origin, glm::vec3 plane_normal, glm::vec3 plane_tangent, glm::vec3& out_hit, float& out_t) { float den = glm::dot(ray_dir, plane_normal); if (den == 0) return false; // no intersection float num = glm::dot(plane_origin - ray_origin, plane_normal); out_t = num / den; if (out_t > 0) out_hit = ray_origin + ray_dir * out_t; else // negative intersection return false; return true; }; // see: http://geomalgorithms.com/a07-_distance.html // dist3D_Line_to_Line(): get the 3D minimum distance between 2 lines // Input: two 3D lines L1 and L2 // Return: the shortest distance between L1 and L2 float lines_distance(const glm::vec3& p0a, const glm::vec3& p0b, const glm::vec3& p1a, const glm::vec3& p1b) { glm::vec3 u = p0b - p0a; glm::vec3 v = p1b - p1a; glm::vec3 w = p0a - p1a; float a = glm::dot(u,u); // always >= 0 float b = glm::dot(u,v); float c = glm::dot(v,v); // always >= 0 float d = glm::dot(u,w); float e = glm::dot(v,w); float D = a*c - b*b; // always >= 0 float sc, tc; // compute the line parameters of the two closest points if (D < 0.00001f) { // the lines are almost parallel sc = 0.0; tc = (b>c ? d/b : e/c); // use the largest denominator } else { sc = (b*e - c*d) / D; tc = (a*e - b*d) / D; } // get the difference of the two closest points glm::vec3 dP = w + (sc * u) - (tc * v); // = L1(sc) - L2(tc) return glm::length(dP); // return the closest distance } bool segments_intersect_3d(const glm::vec3& p0a, const glm::vec3& p0b, const glm::vec3& p1a, const glm::vec3& p1b, glm::vec3& out_pt, glm::vec2& out_hit_uv) { float denom = ((p1b.y - p1a.y)*(p0b.x - p0a.x)) - ((p1b.x - p1a.x)*(p0b.y - p0a.y)); float nume_a = ((p1b.x - p1a.x)*(p0a.y - p1a.y)) - ((p1b.y - p1a.y)*(p0b.x - p1a.x)); float nume_b = ((p0b.x - p0a.x)*(p0a.y - p1a.y)) - ((p0b.y - p0a.y)*(p0a.x - p1a.x)); if(denom == 0.0f) { if(nume_a == 0.0f && nume_b == 0.0f) { return 0;//COINCIDENT; } return 0;//PARALLEL; } float ua = nume_a / denom; float ub = nume_b / denom; if(ua >= 0.0f && ua <= 1.0f && ub >= 0.0f && ub <= 1.0f) { // Get the intersection point. out_pt.x = p0a.x + ua*(p0b.x - p0a.x); out_pt.y = p0a.y + ua*(p0b.y - p0a.y); return 1;//INTERESECTING; } return 0;//NOT_INTERESECTING; } // see: https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect bool segments_intersect(const glm::vec2& p0a, const glm::vec2& p0b, const glm::vec2& p1a, const glm::vec2& p1b, glm::vec2& out_pt, glm::vec2& out_hit_uv) { auto cross2d = [](const glm::vec2& v, const glm::vec2& w) { return (v.x * w.y) - (v.y * w.x); }; auto p = p0a; auto r = p0b - p0a; auto q = p1a; auto s = p1b - p1a; float den = cross2d(r, s); if (den == 0.f) { glm::vec4 is = rect_intersection({p, r}, {q, s}); out_pt = xy(is) + zw(is) * 0.5f; return glm::all(glm::greaterThan(zw(is), glm::vec2(0, 0))); } out_hit_uv.x = cross2d(q - p, s) / den; out_hit_uv.y = cross2d(q - p, r) / den; out_pt = p + out_hit_uv.x * r; if (out_hit_uv.x >= 0 && out_hit_uv.x <= 1 && out_hit_uv.y >= 0 && out_hit_uv.y <= 1) { return true; } return false; } // return true if the point p in the right halfspace of the ab line // computed using the 2d cross product bool point_side(glm::vec2 a, glm::vec2 b, glm::vec2 p) { return (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x) >= 0.f; } // intersect 2 closed polygons // a is a convex polygon // a and b are a list of non repeating points // returns the resulting intersection polygon points std::vector poly_intersect(const vertex_t* poly_begin, const vertex_t* poly_end, const std::vector& clip) { // implementing the Sutherland-Hodgman algorithm // see https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm std::vector ret(poly_begin, poly_end); for (int i = 0; i < clip.size(); i++) { std::vector tmp; glm::vec2 edge[2] = { clip[i], clip[(i + 1) % clip.size()] }; for (int j = 0; j < ret.size(); j++) { vertex_t s[2] = { ret[j], ret[(j + 1) % ret.size()] }; bool side0 = point_side(edge[0], edge[1], s[0].pos); bool side1 = point_side(edge[0], edge[1], s[1].pos); if (side0 != side1) // intersecting { glm::vec2 pt; glm::vec2 hit_uv; segments_intersect(edge[0], edge[1], s[0].pos, s[1].pos, pt, hit_uv); vertex_t v; v.pos = glm::lerp(s[0].pos, s[1].pos, hit_uv.y); v.uvs = glm::lerp(s[0].uvs, s[1].uvs, hit_uv.y); v.uvs2 = glm::lerp(s[0].uvs2, s[1].uvs2, hit_uv.y); if (side0) // outgoing { tmp.push_back(s[0]); tmp.push_back(v); } else // ingoing { tmp.push_back(v); } } else if (side0 && side1) { tmp.push_back(s[0]); } } ret = std::move(tmp); } return poly_remove_duplicate(ret); } std::vector poly_intersect(const std::vector& poly, const std::vector& clip) { // implementing the Sutherland-Hodgman algorithm // see https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm std::vector ret = poly; for (int i = 0; i < clip.size(); i++) { std::vector tmp; glm::vec2 edge[2] = { clip[i], clip[(i + 1) % clip.size()] }; for (int j = 0; j < ret.size(); j++) { glm::vec2 s[2] = { ret[j], ret[(j + 1) % ret.size()] }; bool side0 = point_side(edge[0], edge[1], s[0]); bool side1 = point_side(edge[0], edge[1], s[1]); if (side0 != side1) // intersecting { glm::vec2 pt; glm::vec2 hit_uv; segments_intersect(edge[0], edge[1], s[0], s[1], pt, hit_uv); if (side0) // outgoing { tmp.push_back(s[0]); tmp.push_back(pt); } else // ingoing { tmp.push_back(pt); } } else if (side0 && side1) { tmp.push_back(s[0]); } } ret = std::move(tmp); } return poly_remove_duplicate(ret); } // clip the polygon to the near clip plane // poly is the polygon in camera coordinates std::vector poly_clip_near(const std::vector& poly, float near_plane_distance) { // implementing the Sutherland-Hodgman algorithm in 3D // see https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm auto o = glm::vec3(0, 0, -near_plane_distance); auto n = glm::vec3(0, 0, -1); auto t = glm::vec3(0, 1, 0); std::vector ret; for (int j = 0; j < poly.size(); j++) { glm::vec3 s[2] = { poly[j], poly[(j + 1) % poly.size()] }; bool side0 = glm::dot(n, s[0] - o) >= 0.f; bool side1 = glm::dot(n, s[1] - o) >= 0.f; if (side0 != side1) // intersecting { glm::vec3 pt; float hit_t; if (!ray_intersect(s[0], glm::normalize(s[1] - s[0]), o, n, t, pt, hit_t)) { LOG("error ray_intersect"); } if (side0) // outgoing { ret.push_back(s[0]); ret.push_back(pt); } else // ingoing { ret.push_back(pt); } } else if (side0 && side1) { ret.push_back(s[0]); } } return poly_remove_duplicate(ret); } std::vector triangulate_simple(const std::vector& vertices) { std::vector ret; std::vector points(vertices.size()); std::vector points_ptr(vertices.size()); for (size_t i = 0; i < vertices.size(); i++) { points[i] = { vertices[i].pos.x, vertices[i].pos.y }; points_ptr[i] = &points[i]; } auto cdt = std::make_unique(points_ptr); cdt->Triangulate(); auto tr = cdt->GetTriangles(); for (auto t : tr) { vertex_t vertex; for (int i = 0; i < 3; i++) { auto index = std::distance(points.data(), t->GetPoint(i)); ret.push_back(vertices[index]); } } return ret; } std::vector triangulate(const std::vector& points) { std::vector tmp; for (auto pt : points) tmp.push_back(pt); return triangulate(tmp); } std::vector triangulate(const std::vector& points) { struct Segment { const vertex_t* a = nullptr; const vertex_t* b = nullptr; Segment* prev = nullptr; std::shared_ptr next = nullptr; bool end = false; }; std::vector> new_points; std::shared_ptr root = std::make_shared(); std::shared_ptr node = root; for (int i = 0; i < points.size(); i++) { node->a = &points[i]; if (i == points.size() - 1) { node->b = &points[0]; node->next = root; node->end = true; root->prev = node.get(); } else { node->b = &points[i + 1]; node->next = std::make_shared(); node->next->prev = node.get(); } node = node->next; } node = root; std::stack> todo; std::vector> polys; todo.push(root); while (!todo.empty()) { node = todo.top(); todo.pop(); polys.push_back(node); while (node) { std::shared_ptr other = node->next; while (other) { if (node->a->pos == other->a->pos || node->a->pos == other->b->pos || node->b->pos == other->a->pos || node->b->pos == other->b->pos) { other = other->end ? nullptr : other->next; continue; } glm::vec2 s0a(node->a->pos); glm::vec2 s0b(node->b->pos); glm::vec2 s1a(other->a->pos); glm::vec2 s1b(other->b->pos); glm::vec2 hit_uv; glm::vec2 is; if (segments_intersect(s0a, s0b, s1a, s1b, is, hit_uv)) { new_points.push_back(std::make_unique()); auto p = new_points.back().get(); p->pos = glm::lerp(node->a->pos, node->b->pos, hit_uv.x); p->uvs = glm::lerp(node->a->uvs, node->b->uvs, hit_uv.x); p->uvs2 = glm::lerp(node->a->uvs2, node->b->uvs2, hit_uv.x); auto poly_root = std::make_shared(); poly_root->a = p; poly_root->b = node->b; poly_root->next = node->next; todo.push(poly_root); other->a = p; node->b = p; auto poly_end = std::make_shared(); poly_end->a = other->prev->b; poly_end->b = p; poly_end->end = true; poly_end->prev = other->prev; other->prev->next = poly_end; other->prev = node.get(); node->next = other; break; } other = other->end ? nullptr : other->next; } node = node->end ? nullptr : node->next; } } std::vector ret; for (auto poly : polys) { std::vector outline; node = poly; while (node) { if (outline.empty() || // if empty insert right away (outline.back() != node->a && // insert only if different than the last post (outline.front() != node->a || !node->end))) // if is the end check against the first one { outline.push_back(node->a); } auto current = node; node = node->end ? nullptr : node->next; current->next = nullptr; } if (outline.size() > 2) { std::vector points(outline.size()); std::vector points_ptr(outline.size()); for (size_t i = 0; i < outline.size(); i++) { points[i] = { outline[i]->pos.x, outline[i]->pos.y }; points_ptr[i] = &points[i]; } p2t::CDT* cdt = new p2t::CDT(points_ptr); // TODO: remove duplicates cdt->Triangulate(); auto tr = cdt->GetTriangles(); for (auto t : tr) { for (int i = 0; i < 3; i++) { auto index = std::distance(points.data(), t->GetPoint(i)); ret.push_back(*outline[index]); } } } } return ret; } glm::vec4 rand_color() { float r = (rand() % 256) / 256.f; float g = (rand() % 256) / 256.f; float b = (rand() % 256) / 256.f; return { r, g, b, 1.f }; } glm::vec3 convert_long_rgb(uint32_t hex) { uint8_t b = (hex >> 0) & 0xFF; uint8_t g = (hex >> 8) & 0xFF; uint8_t r = (hex >> 16) & 0xFF; return glm::vec3(r, g, b) / 255.f; } uint32_t convert_rgb_long(glm::vec3 rgb) { uint8_t r = (uint8_t)(rgb.r * 255.f); uint8_t g = (uint8_t)(rgb.g * 255.f); uint8_t b = (uint8_t)(rgb.b * 255.f); return (r << 16) + (g << 8) + b; } glm::vec3 convert_hsv2rgb(const glm::vec3 c) { glm::vec4 K = glm::vec4(1.0f, 2.0f / 3.0f, 1.0f / 3.0f, 3.0f); glm::vec3 p = glm::abs(glm::fract(glm::vec3(c.x) + xyz(K)) * 6.0f - glm::vec3(K.w)); auto tmp = glm::clamp(p - glm::vec3(K.x), 0.0f, 1.0f); return c.z * glm::mix(glm::vec3(K.x), tmp, c.y); } glm::vec3 convert_rgb2hsv(const glm::vec3 c) { glm::vec4 K = glm::vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); //glm::vec4 p = mix(glm::vec4(c.bg, K.wz), glm::vec4(c.gb, K.xy), glm::step(c.b, c.g)); //glm::vec4 q = mix(glm::vec4(p.xyw, c.r), glm::vec4(c.r, p.yzx), glm::step(p.x, c.r)); glm::vec4 p = c.g < c.b ? glm::vec4(c.b, c.g, K.w, K.z) : glm::vec4(c.g, c.b, K.x, K.y); glm::vec4 q = c.r < p.x ? glm::vec4(p.x, p.y, p.w, c.r) : glm::vec4(c.r, p.y, p.z, p.x); float d = q.x - glm::min(q.w, q.y); float e = 1.0e-10f; return glm::vec3(fabs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); } std::string unescape(const std::string& s) { std::string res; std::string::const_iterator it = s.begin(); while (it != s.end()) { char c = *it++; if (c == '\\' && it != s.end()) { switch (*it++) { case '\\': c = '\\'; break; case 'n': c = '\n'; break; case 't': c = '\t'; break; // all other escapes default: // invalid escape sequence - skip it. alternatively you can copy it as is, throw an exception... continue; } } res += c; } return res; } std::wstring str2wstr(const std::string& str) { if (str.empty()) return {}; try { std::wstring_convert, wchar_t> converterX; return converterX.from_bytes(str); } catch (...) { LOG("str2wstr error: %s", str.c_str()); return {}; } } std::string wstr2str(const std::wstring & wstr) { if (wstr.empty()) return {}; try { std::wstring_convert, wchar_t> converterX; return converterX.to_bytes(wstr); } catch (...) { LOGW(L"wstr2str error: %s", wstr.c_str()); return {}; } } bool str_iequals(const std::string& a, const std::string& b) { size_t sz = a.size(); if (b.size() != sz) return false; for (size_t i = 0; i < sz; ++i) if (std::tolower(a[i]) != std::tolower(b[i])) return false; return true; } std::string str_replace(const std::string& string, const std::string& search, const std::string& replace) { std::string ret = string; // Get the first occurrence size_t pos = ret.find(search); // Repeat till end is reached while(pos != std::string::npos) { // Replace this occurrence of Sub String ret.replace(pos, search.size(), replace); // Get the next occurrence from the current position pos = ret.find(search, pos + replace.size()); } return ret; } static const char* gl2str(std::uint32_t err) { return pp::renderer::gl::opengl_error_name(err); } double now_seconds() { time_t timer; struct tm y2k = { 0 }; double seconds; y2k.tm_hour = 0; y2k.tm_min = 0; y2k.tm_sec = 0; y2k.tm_year = 100; y2k.tm_mon = 0; y2k.tm_mday = 1; time(&timer); /* get current time; same as: timer = time(NULL) */ seconds = difftime(timer, mktime(&y2k)); return seconds; } void check_OpenGLError(const char* stmt, const char* fname, int line) { std::uint32_t err = 0U; while ((err = pp::legacy::gl_runtime::query_opengl_error()) != pp::renderer::gl::no_error_code()) { LOG("OpenGL error %08x (%s), at %s:%i - for %s", err, gl2str(err), fname, line, stmt); } } bool copy_framebuffer_to_texture_target( uint32_t texture_target, int destination_x, int destination_y, int source_x, int source_y, int width, int height, int level) noexcept { const auto status = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d( pp::renderer::gl::OpenGlTexture2DFramebufferCopy { .texture_target = texture_target, .level = level, .destination_x = destination_x, .destination_y = destination_y, .source_x = source_x, .source_y = source_y, .width = width, .height = height, }, pp::legacy::gl_framebuffer::framebuffer_to_texture_copy_dispatch()); if (!status.ok()) { LOG("OpenGL framebuffer-to-texture copy failed: %s", status.message); return false; } return true; } bool copy_framebuffer_to_texture_2d( int destination_x, int destination_y, int source_x, int source_y, int width, int height, int level) noexcept { return copy_framebuffer_to_texture_target( pp::renderer::gl::texture_2d_target(), destination_x, destination_y, source_x, source_y, width, height, level); } size_t curl_data_handler(void *contents, size_t size, size_t nmemb, void *userp) { auto buffer = reinterpret_cast(userp); buffer->append((char*)contents, size * nmemb); return size * nmemb; } size_t curl_data_write(void *ptr, size_t size, size_t nmemb, FILE *stream) { size_t written = fwrite(ptr, size, nmemb, stream); return written; } /// @param[in] nb_elements : size of your for loop /// @param[in] functor(start, end) : /// your function processing a sub chunk of the for loop. /// "start" is the first index to process (included) until the index "end" /// (excluded) /// @code /// for(int i = start; i < end; ++i) /// computation(i); /// @endcode /// @param use_threads : enable / disable threads. /// /// void parallel_for(size_t nb_elements, std::function functor, bool use_threads) { // ------- size_t nb_threads_hint = std::thread::hardware_concurrency(); size_t nb_threads = nb_threads_hint == 0 ? 8 : (nb_threads_hint); size_t batch_size = nb_elements / nb_threads; size_t batch_remainder = nb_elements % nb_threads; std::vector< std::thread > my_threads(nb_threads); if (use_threads) { // Multithread execution for (size_t i = 0; i < nb_threads; ++i) { size_t start = i * batch_size; my_threads[i] = std::thread([functor, start, batch_size]() { BT_SetTerminate(); for (size_t j = start; j < start + batch_size; j++) functor(j); }); } } else { // Single thread execution (for easy debugging) for (size_t i = 0; i < nb_threads; ++i) { size_t start = i * batch_size; for (size_t j = start; j < start + batch_size; j++) functor(j); } } // Deform the elements left size_t start = nb_threads * batch_size; for (size_t j = start; j < start + batch_remainder; j++) functor(j); // Wait for the other thread to finish their task if (use_threads) std::for_each(my_threads.begin(), my_threads.end(), std::mem_fn(&std::thread::join)); } void gl_state::save() { assert(App::I->is_render_thread()); const auto snapshot = pp::renderer::gl::snapshot_opengl_state( pp::renderer::gl::OpenGlStateSnapshotDispatch { .is_enabled = pp::legacy::ui_gl::is_opengl_state_enabled, .get_integer = pp::legacy::gl_framebuffer::query_opengl_integer, .get_float = pp::legacy::ui_gl::get_opengl_float, .active_texture = pp::legacy::ui_gl::activate_opengl_texture, }); if (!snapshot.ok()) { LOG("OpenGL state snapshot failed: %s", snapshot.status().message); return; } const auto& state = snapshot.value(); blend = static_cast(state.blend_enabled); depth_test = static_cast(state.depth_test_enabled); scissor_test = static_cast(state.scissor_test_enabled); for (std::size_t i = 0; i < state.viewport.size(); ++i) { vp[i] = static_cast(state.viewport[i]); cc[i] = state.clear_color[i]; } for (std::size_t i = 0; i < state.texture_2d_bindings.size(); ++i) { tex[i] = static_cast(state.texture_2d_bindings[i]); sampler[i] = static_cast(state.sampler_bindings[i]); } cube = static_cast(state.cube_map_binding); program = static_cast(state.program); fbd = static_cast(state.draw_framebuffer); fbr = static_cast(state.read_framebuffer); active_tex = static_cast(state.active_texture); } void gl_state::restore() { assert(App::I->is_render_thread()); pp::renderer::gl::OpenGlSavedState state {}; state.blend_enabled = blend; state.depth_test_enabled = depth_test; state.scissor_test_enabled = scissor_test; for (std::size_t i = 0; i < state.viewport.size(); ++i) { state.viewport[i] = static_cast(vp[i]); state.clear_color[i] = cc[i]; } for (std::size_t i = 0; i < state.texture_2d_bindings.size(); ++i) { state.texture_2d_bindings[i] = static_cast(tex[i]); state.sampler_bindings[i] = static_cast(sampler[i]); } state.cube_map_binding = static_cast(cube); state.program = static_cast(program); state.draw_framebuffer = static_cast(fbd); state.read_framebuffer = static_cast(fbr); state.active_texture = static_cast(active_tex); const auto status = pp::renderer::gl::restore_opengl_state( state, pp::renderer::gl::OpenGlStateRestoreDispatch { .enable = pp::legacy::ui_gl::enable_opengl_state, .disable = pp::legacy::ui_gl::disable_opengl_state, .viewport = pp::legacy::ui_gl::set_opengl_viewport, .clear_color = pp::legacy::ui_gl::set_opengl_clear_color, .bind_framebuffer = pp::legacy::ui_gl::bind_opengl_framebuffer, .use_program = pp::legacy::gl_shader::use_opengl_program, .active_texture = pp::legacy::ui_gl::activate_opengl_texture, .bind_texture = pp::legacy::ui_gl::bind_opengl_texture, .bind_sampler = pp::legacy::gl_sampler::bind_opengl_sampler, }); if (!status.ok()) LOG("OpenGL state restore failed: %s", status.message); }