CGI/exercise2/src/Viewer.cpp

576 lines
16 KiB
C++
Raw Normal View History

2018-11-26 08:59:16 +00:00
// This source code is property of the Computer Graphics and Visualization
// chair of the TU Dresden. Do not distribute!
2018-11-13 08:35:18 +00:00
// Copyright (C) CGV TU Dresden - All Rights Reserved
#include "Viewer.h"
#include <nanogui/window.h>
#include <nanogui/layout.h>
#include <nanogui/checkbox.h>
#include <gui/SliderHelper.h>
#include <stb_image.h>
2018-11-26 08:59:16 +00:00
#include "glsl.h"
2018-11-13 08:35:18 +00:00
#include "textures.h"
2018-11-16 11:47:55 +00:00
#include <fstream>
2018-11-27 19:22:35 +00:00
#include <iostream>
2018-11-26 16:58:30 +00:00
#include <sstream>
2018-11-27 19:22:35 +00:00
#include <cmath>
//using vec4 = Eigen::Vector4f;
/*
#define Eigen::Vector4f vec4
#define Eigen::Vector3f vec3
#define Eigen::Vector2f vec2
*/
2018-11-16 11:47:55 +00:00
2018-11-26 16:58:30 +00:00
constexpr bool REFERENCE = true;
constexpr uint32_t PATCH_SIZE = 256; //number of vertices along one side of the terrain patch
2018-11-13 08:35:18 +00:00
Viewer::Viewer()
: AbstractViewer("CG1 Exercise 2"),
2018-11-26 08:59:16 +00:00
terrainPositions(nse::gui::VertexBuffer), terrainIndices(nse::gui::IndexBuffer),
2018-11-26 16:58:30 +00:00
offsetBuffer(nse::gui::VertexBuffer),
referenceVB(nse::gui::VertexBuffer), referenceIB(nse::gui::IndexBuffer)
2018-11-26 08:59:16 +00:00
{
2018-11-13 08:35:18 +00:00
LoadShaders();
CreateGeometry();
2018-11-26 08:59:16 +00:00
2018-11-26 12:11:05 +00:00
grass_texture_location = terrainShader.uniform("grass");
2018-11-26 16:58:30 +00:00
rock_texture_location = terrainShader.uniform("rock");
alpha_texture_location = terrainShader.uniform("alpha");
road_texture_location = terrainShader.uniform("road");
road_specular_location = terrainShader.uniform("specular_road");
2018-11-26 12:11:05 +00:00
2018-11-13 08:35:18 +00:00
//Create a texture and framebuffer for the background
2018-11-26 08:59:16 +00:00
glGenFramebuffers(1, &backgroundFBO);
glGenTextures(1, &backgroundTexture);
2018-11-13 08:35:18 +00:00
//Align camera to view a reasonable part of the terrain
camera().SetSceneExtent(nse::math::BoundingBox<float, 3>(Eigen::Vector3f(0, 0, 0), Eigen::Vector3f(PATCH_SIZE - 1, 0, PATCH_SIZE - 1)));
2018-11-26 08:59:16 +00:00
camera().FocusOnPoint(0.5f * Eigen::Vector3f(PATCH_SIZE - 1, 15, PATCH_SIZE - 1));
2018-11-13 08:35:18 +00:00
camera().Zoom(-30);
camera().RotateAroundFocusPointLocal(Eigen::AngleAxisf(-0.5f, Eigen::Vector3f::UnitY()) * Eigen::AngleAxisf(-0.05f, Eigen::Vector3f::UnitX()));
camera().FixClippingPlanes(0.1, 1000);
}
2018-11-26 08:59:16 +00:00
bool Viewer::resizeEvent(const Eigen::Vector2i &)
2018-11-13 08:35:18 +00:00
{
//Re-generate the texture and FBO for the background
glBindFramebuffer(GL_FRAMEBUFFER, backgroundFBO);
glBindTexture(GL_TEXTURE_2D, backgroundTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width(), height(), 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, backgroundTexture, 0);
auto fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (fboStatus != GL_FRAMEBUFFER_COMPLETE)
std::cout << "Warning: Background framebuffer is not complete: " << fboStatus << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return false;
}
2018-11-16 11:47:55 +00:00
inline std::string loadShaderText(const char *path)
{
std::ifstream is(path);
std::string s_save;
if (is.is_open())
{
s_save.assign(std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>());
}
else
{
std::cout << "could not open " << path << std::endl;
}
is.close();
return s_save;
}
2018-11-13 08:35:18 +00:00
void Viewer::LoadShaders()
{
2018-11-16 11:47:55 +00:00
std::string sky_vs = loadShaderText("exercise2/glsl/sky.vert");
std::string sky_fs = loadShaderText("exercise2/glsl/sky.frag");
std::string terrain_vs = loadShaderText("exercise2/glsl/terrain.vert");
std::string terrain_fs = loadShaderText("exercise2/glsl/terrain.frag");
skyShader.init("Sky Shader", sky_vs, sky_fs);
terrainShader.init("Terrain Shader", terrain_vs, terrain_fs);
2018-11-13 08:35:18 +00:00
}
2018-11-26 08:59:16 +00:00
GLuint CreateTexture(const unsigned char *fileData, size_t fileLength, bool repeat = true)
2018-11-13 08:35:18 +00:00
{
GLuint textureName;
int textureWidth, textureHeight, textureChannels;
auto pixelData = stbi_load_from_memory(fileData, fileLength, &textureWidth, &textureHeight, &textureChannels, 3);
2018-11-26 12:11:05 +00:00
GLenum provided_format;
if (textureChannels == 1)
{
provided_format = GL_R;
}
else if (textureChannels == 2)
{
provided_format = GL_RG;
}
else if (textureChannels == 3)
{
provided_format = GL_RGB;
}
else if (textureChannels == 4)
{
provided_format = GL_RGBA;
}
glGenTextures(1, &textureName);
glBindTexture(GL_TEXTURE_2D, textureName);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureWidth, textureHeight, 0, provided_format, GL_UNSIGNED_BYTE, pixelData);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
if (repeat)
{
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
else
{
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
2018-11-13 08:35:18 +00:00
stbi_image_free(pixelData);
return textureName;
}
2018-11-26 16:58:30 +00:00
std::string vec4_to_str(Eigen::Vector4f &v)
{
std::stringstream ss;
ss << "(" << v.x() << ", "
<< v.y() << ", "
<< v.z() << ", "
<< v.w() << ")";
2018-11-27 19:22:35 +00:00
return ss.str();
}
std::string vec3_to_str(Eigen::Vector3f& v) {
std::stringstream ss;
ss << "(" << v.x() << ", "
<< v.y() << ", "
<< v.z() << ")";
2018-11-26 16:58:30 +00:00
return ss.str();
}
2018-11-27 19:22:35 +00:00
float length(Eigen::Vector2f v) {
return sqrt(v.x() * v.x() + v.y() * v.y());
}
float dot(Eigen::Vector2f v1, Eigen::Vector2f v2) {
return (v1.x() * v2.x() + v1.y() * v2.y()) / length(v1) * length(v2);
}
float mix(float f1, float f2, float value) {
float difference = f1 - f2;
return f1 + difference * value;
}
//source: https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
float rand(Eigen::Vector2f c)
{
double dummy;
return 2 * modf(sin(dot(c, Eigen::Vector2f(12.9898f, 78.233f))) * 43758.5453, &dummy) - 1;
}
Eigen::Vector2f mul(Eigen::Vector2f v1, Eigen::Vector2f v2) {
return Eigen::Vector2f(v1.x() * v2.x(), v1.y() * v2.y());
}
float perlinNoise(Eigen::Vector2f p)
{
Eigen::Vector2f ij = Eigen::Vector2f(floor(p.x()), floor(p.y()));
Eigen::Vector2f xy = p - ij;
xy = (3.0f * mul(xy, xy)) - (2.0f * mul(mul(xy, xy), xy));
//xy = 0.5f * (1.0f - cos(3.1415926 * xy));
float a = rand((ij + Eigen::Vector2f(0.0f, 0.0f)));
float b = rand((ij + Eigen::Vector2f(1.0f, 0.0f)));
float c = rand((ij + Eigen::Vector2f(0.0f, 1.0f)));
float d = rand((ij + Eigen::Vector2f(1.0f, 1.0f)));
float x1 = mix(a, b, xy.x());
float x2 = mix(c, d, xy.x());
return mix(x1, x2, xy.y());
}
//based on https://www.seedofandromeda.com/blogs/58-procedural-heightmap-terrain-generation
float getTerrainHeight(Eigen::Vector2f p)
{
float total = 0.0;
float maxAmplitude = 0.0;
float amplitude = 1.0;
float frequency = 0.02;
for (int i = 0; i < 11; i++)
{
total += ((1.0 - abs(perlinNoise(p * frequency))) * 2.0 - 1.0) * amplitude;
frequency *= 2.0;
maxAmplitude += amplitude;
amplitude *= 0.45;
}
return 15 * total / maxAmplitude;
}
Eigen::Vector3f vec4_to_vec3(Eigen::Vector4f v) {
return Eigen::Vector3f(v.x(), v.y(), v.z());
}
Eigen::Vector3f calculate_gpu_normal(Eigen::Vector4f pos, int gl_VertexID) {
const Eigen::Vector2f offsets[6] = {
Eigen::Vector2f(0.0, 0.0),
Eigen::Vector2f(0.0, 1.0),
Eigen::Vector2f(1.0, 0.0),
Eigen::Vector2f(0.0, 1.0),
Eigen::Vector2f(1.0, 0.0),
Eigen::Vector2f(1.0, 1.0)
};
int offset_index = gl_VertexID % 6;
Eigen::Vector4f base_vertex = Eigen::Vector4f(pos.x() - offsets[offset_index].x(), pos.y(), pos.z() - offsets[offset_index].y(), pos.w());
// the other 2 points from the triangle
int points_index = 0;
Eigen::Vector4f points[2];
int iterator_offset = 0;
// second triangle offset
if (offset_index > 2) {
iterator_offset = 3;
}
for (int i = iterator_offset; i < (3 + iterator_offset); i++) {
// skip for current vertex
if (i == offset_index) {
continue;
}
Eigen::Vector2f new_pos = Eigen::Vector2f(base_vertex.x(), base_vertex.z()) + offsets[i];
float y = getTerrainHeight(new_pos);
points[points_index] = Eigen::Vector4f(new_pos.x(), y, new_pos.y(), base_vertex.w());
points_index++;
}
// create connection vectors
Eigen::Vector3f v1 = vec4_to_vec3(points[0] - pos);
Eigen::Vector3f v2 = vec4_to_vec3(points[1] - pos);
v1.normalize();
v2.normalize();
Eigen::Vector3f normal = v1.cross(v2).normalized();
return normal;
}
Eigen::Vector3f calculate_reference_normal(Eigen::Vector4f p1, Eigen::Vector4f p2, Eigen::Vector4f p3) {
Eigen::Vector3f v1 = vec4_to_vec3(p1 - p2);
Eigen::Vector3f v2 = vec4_to_vec3(p1 - p3);
v1.normalize();
v2.normalize();
Eigen::Vector3f normal = v1.cross(v2).normalized();
return normal;
}
2018-11-13 08:35:18 +00:00
void Viewer::CreateGeometry()
{
//empty VAO for sky
emptyVAO.generate();
2018-11-26 08:59:16 +00:00
//terrain VAO
2018-11-13 08:35:18 +00:00
terrainVAO.generate();
terrainVAO.bind();
2018-11-26 08:59:16 +00:00
2018-11-13 08:35:18 +00:00
std::vector<Eigen::Vector4f> positions;
std::vector<uint32_t> indices;
2018-11-26 08:59:16 +00:00
2018-11-13 08:35:18 +00:00
/*Generate positions and indices for a terrain patch with a
single triangle strip */
2018-11-26 08:59:16 +00:00
for (int i = 0; i < PATCH_SIZE; i++)
{
for (int j = 0; j < PATCH_SIZE; j++)
{
2018-11-26 16:58:30 +00:00
positions.emplace_back((float)j, 0.0f, (float)i, 1.0f);
2018-11-16 08:27:11 +00:00
}
}
// full x direction, we skip x = 255 inside the loop
// decrease y direction by 1
2018-11-26 08:59:16 +00:00
int count = (PATCH_SIZE) * (PATCH_SIZE - 1);
2018-11-16 08:27:11 +00:00
2018-11-26 08:59:16 +00:00
for (int k = 0; k < count; k++)
{
2018-11-16 08:27:11 +00:00
// skip creating triangles on last x in row
2018-11-26 16:58:30 +00:00
/*
int end_of_x = k % (PATCH_SIZE);
2018-11-16 08:27:11 +00:00
2018-11-26 08:59:16 +00:00
if (end_of_x == 0)
{
2018-11-16 08:27:11 +00:00
continue;
}
2018-11-26 16:58:30 +00:00
*/
2018-11-16 08:27:11 +00:00
indices.emplace_back(k);
indices.emplace_back(k + PATCH_SIZE);
2018-11-16 11:47:55 +00:00
//indices.emplace_back(k + 1);
//indices.emplace_back(k + 1 + PATCH_SIZE);
2018-11-16 08:27:11 +00:00
}
2018-11-13 08:35:18 +00:00
terrainShader.bind();
terrainPositions.uploadData(positions).bindToAttribute("position");
terrainIndices.uploadData(indices.size() * sizeof(uint32_t), indices.data());
2018-11-26 16:58:30 +00:00
referenceVAO.generate();
referenceVAO.bind();
std::vector<Eigen::Vector4f> ref_pos;
std::vector<uint32_t> ref_ind;
for (int i = 0; i < PATCH_SIZE; i++)
{
for (int j = 0; j < PATCH_SIZE; j++)
{
2018-11-27 19:22:35 +00:00
ref_pos.emplace_back((float)j, getTerrainHeight(Eigen::Vector2f((float)j, (float)i)), (float)i, 1.0f);
2018-11-26 16:58:30 +00:00
}
}
int index_width = PATCH_SIZE - 1;
for (int i = 0; i < index_width; i++)
{
for (int j = 0; j < index_width; j++)
{
int p1 = i + (PATCH_SIZE * j);
int p2 = i + (PATCH_SIZE * j) + 1;
int p3 = i + (PATCH_SIZE * (j + 1));
int p4 = i + (PATCH_SIZE * (j + 1)) + 1;
ref_ind.emplace_back(p1);
ref_ind.emplace_back(p3);
ref_ind.emplace_back(p2);
ref_ind.emplace_back(p3);
ref_ind.emplace_back(p2);
ref_ind.emplace_back(p4);
}
}
2018-11-27 19:22:35 +00:00
// --------------------------------------------------
// ---------------- Debugging Area ------------------
// --------------------------------------------------
/*
for (int i = 0; i < ref_ind.size(); i+=6) {
struct Point {
Eigen::Vector4f position;
Eigen::Vector3f normal;
};
std::vector<Point> gpu_points;
std::vector<Point> ref_points;
for (int j = i; j < i+6; j++) {
// gpu
{
auto position = ref_pos[j];
auto normal = calculate_gpu_normal(position, j);
gpu_points.emplace_back(Point { position, normal });
}
// reference
{
int index = j % 6;
auto position = ref_pos[j];
Eigen::Vector3f normal;
if (index < 3) {
normal = calculate_reference_normal(ref_pos[i], ref_pos[i+1], ref_pos[i+2]);
} else {
normal = calculate_reference_normal(ref_pos[i+3], ref_pos[i+4], ref_pos[i+5]);
}
ref_points.emplace_back(Point { position, normal });
}
}
std::string gpu_output = "gpu_points: \n";
std::string ref_output = "ref_points: \n";
for (Point& point : gpu_points) {
gpu_output += "\tpos: " + vec4_to_str(point.position) + "\tnormal: " + vec3_to_str(point.normal) + "\n";
}
for (Point& point : ref_points) {
ref_output += "\tpos: " + vec4_to_str(point.position) + "\tnormal: " + vec3_to_str(point.normal) + "\n";
}
std::cout << gpu_output << std::endl;
std::cout << ref_output << std::endl;
abort();
}
*/
// --------------------------------------------------
// -------------- Debugging Area End ----------------
// --------------------------------------------------
2018-11-26 16:58:30 +00:00
referenceVB.uploadData(ref_pos)
.bindToAttribute("position");
referenceIB.uploadData(ref_ind.size() * sizeof(uint32_t), ref_ind.data());
2018-11-13 08:35:18 +00:00
//textures
2018-11-26 08:59:16 +00:00
grassTexture = CreateTexture((unsigned char *)grass_jpg, grass_jpg_size);
rockTexture = CreateTexture((unsigned char *)rock_jpg, rock_jpg_size);
roadColorTexture = CreateTexture((unsigned char *)roadcolor_jpg, roadcolor_jpg_size);
roadNormalMap = CreateTexture((unsigned char *)roadnormals_jpg, roadnormals_jpg_size);
roadSpecularMap = CreateTexture((unsigned char *)roadspecular_jpg, roadspecular_jpg_size);
alphaMap = CreateTexture((unsigned char *)alpha_jpg, alpha_jpg_size, false);
2018-11-13 08:35:18 +00:00
}
void Viewer::RenderSky()
{
Eigen::Matrix4f skyView = view;
for (int i = 0; i < 3; ++i)
skyView.col(i).normalize();
skyView.col(3).head<3>().setZero();
Eigen::Matrix4f skyMvp = proj * skyView;
glDepthMask(GL_FALSE);
glEnable(GL_DEPTH_CLAMP);
emptyVAO.bind();
skyShader.bind();
skyShader.setUniform("mvp", skyMvp);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 6);
glDisable(GL_DEPTH_CLAMP);
glDepthMask(GL_TRUE);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, backgroundFBO);
glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
2018-11-26 08:59:16 +00:00
void CalculateViewFrustum(const Eigen::Matrix4f &mvp, Eigen::Vector4f *frustumPlanes, nse::math::BoundingBox<float, 3> &bbox)
2018-11-13 08:35:18 +00:00
{
frustumPlanes[0] = (mvp.row(3) + mvp.row(0)).transpose();
frustumPlanes[1] = (mvp.row(3) - mvp.row(0)).transpose();
frustumPlanes[2] = (mvp.row(3) + mvp.row(1)).transpose();
frustumPlanes[3] = (mvp.row(3) - mvp.row(1)).transpose();
frustumPlanes[4] = (mvp.row(3) + mvp.row(2)).transpose();
frustumPlanes[5] = (mvp.row(3) - mvp.row(2)).transpose();
Eigen::Matrix4f invMvp = mvp.inverse();
bbox.reset();
2018-11-26 08:59:16 +00:00
for (int x = -1; x <= 1; x += 2)
for (int y = -1; y <= 1; y += 2)
2018-11-13 08:35:18 +00:00
for (int z = -1; z <= 1; z += 2)
2018-11-26 08:59:16 +00:00
{
Eigen::Vector4f corner = invMvp * Eigen::Vector4f(x, y, z, 1);
corner /= corner.w();
bbox.expand(corner.head<3>());
}
2018-11-13 08:35:18 +00:00
}
2018-11-26 08:59:16 +00:00
bool IsBoxCompletelyBehindPlane(const Eigen::Vector3f &boxMin, const Eigen::Vector3f &boxMax, const Eigen::Vector4f &plane)
2018-11-13 08:35:18 +00:00
{
2018-11-26 08:59:16 +00:00
return plane.dot(Eigen::Vector4f(boxMin.x(), boxMin.y(), boxMin.z(), 1)) < 0 &&
plane.dot(Eigen::Vector4f(boxMin.x(), boxMin.y(), boxMax.z(), 1)) < 0 &&
plane.dot(Eigen::Vector4f(boxMin.x(), boxMax.y(), boxMin.z(), 1)) < 0 &&
plane.dot(Eigen::Vector4f(boxMin.x(), boxMax.y(), boxMin.z(), 1)) < 0 &&
plane.dot(Eigen::Vector4f(boxMax.x(), boxMin.y(), boxMin.z(), 1)) < 0 &&
plane.dot(Eigen::Vector4f(boxMax.x(), boxMin.y(), boxMax.z(), 1)) < 0 &&
plane.dot(Eigen::Vector4f(boxMax.x(), boxMax.y(), boxMin.z(), 1)) < 0 &&
plane.dot(Eigen::Vector4f(boxMax.x(), boxMax.y(), boxMin.z(), 1)) < 0;
2018-11-13 08:35:18 +00:00
}
void Viewer::drawContents()
{
camera().ComputeCameraMatrices(view, proj);
Eigen::Matrix4f mvp = proj * view;
Eigen::Vector3f cameraPosition = view.inverse().col(3).head<3>();
int visiblePatches = 0;
RenderSky();
2018-11-26 08:59:16 +00:00
2018-11-13 08:35:18 +00:00
//render terrain
glEnable(GL_DEPTH_TEST);
2018-11-26 16:58:30 +00:00
if (REFERENCE)
{
referenceVAO.bind();
}
else
{
terrainVAO.bind();
}
2018-11-26 08:59:16 +00:00
terrainShader.bind();
2018-11-13 08:35:18 +00:00
terrainShader.setUniform("screenSize", Eigen::Vector2f(width(), height()), false);
terrainShader.setUniform("mvp", mvp);
terrainShader.setUniform("cameraPos", cameraPosition, false);
2018-11-26 12:11:05 +00:00
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, grassTexture);
glUniform1i(grass_texture_location, 0);
2018-11-26 16:58:30 +00:00
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, rockTexture);
glUniform1i(rock_texture_location, 1);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, alphaMap);
glUniform1i(alpha_texture_location, 2);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, roadColorTexture);
glUniform1i(road_texture_location, 3);
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, roadSpecularMap);
glUniform1i(road_specular_location, 4);
if (REFERENCE)
{
glDrawElements(GL_TRIANGLES, (PATCH_SIZE - 1) * (PATCH_SIZE - 1) * 6, GL_UNSIGNED_INT, 0);
}
else
{
glPrimitiveRestartIndex(PATCH_SIZE * 2);
glDrawElements(GL_TRIANGLE_STRIP, (PATCH_SIZE - 1) * PATCH_SIZE * 2, GL_UNSIGNED_INT, 0);
}
2018-11-26 08:59:16 +00:00
2018-11-13 08:35:18 +00:00
//Render text
nvgBeginFrame(mNVGContext, width(), height(), mPixelRatio);
std::string text = "Patches visible: " + std::to_string(visiblePatches);
nvgText(mNVGContext, 10, 20, text.c_str(), nullptr);
nvgEndFrame(mNVGContext);
}