initital commit
This commit is contained in:
commit
6661f7eb0f
19 changed files with 2400 additions and 0 deletions
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
# don't push project files
|
||||
*.cbp
|
||||
*.sln
|
||||
*.vcx*
|
||||
*.workspace*
|
||||
config.make
|
||||
Makefile
|
||||
*.xcodeproj
|
||||
*.plist
|
||||
*.xcconfig
|
||||
*.suo
|
||||
|
||||
*.depend
|
||||
*.layout
|
||||
*.mode*v3
|
||||
*.pbxuser
|
||||
*.app*
|
||||
*.DS_*
|
||||
*.xcworkspacedata
|
||||
xcuserdata/
|
||||
project.xcworkspace
|
||||
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.suo
|
||||
*.ipch
|
||||
|
||||
.svn/
|
||||
obj/
|
||||
bin/
|
||||
build/
|
||||
!data/
|
||||
|
||||
.cache
|
||||
.ccls-cache
|
||||
|
||||
# don't push backup files/directories
|
||||
*.bk
|
||||
|
||||
# vim
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# qtcreator
|
||||
*.qbs
|
||||
*.qbs.user
|
||||
build-*
|
||||
|
||||
compile_commands.json
|
||||
compile_commands*
|
||||
.vroot
|
||||
.ycm_extra_conf.py
|
||||
*.nohup
|
||||
|
||||
# prevent accidental leaks
|
||||
celines-fonts
|
65
addon_config.mk
Normal file
65
addon_config.mk
Normal file
|
@ -0,0 +1,65 @@
|
|||
# All variables and this file are optional, if they are not present the PG and the
|
||||
# makefiles will try to parse the correct values from the file system.
|
||||
#
|
||||
# Variables that specify exclusions can use % as a wildcard to specify that anything in
|
||||
# that position will match. A partial path can also be specified to, for example, exclude
|
||||
# a whole folder from the parsed paths from the file system
|
||||
#
|
||||
# Variables can be specified using = or +=
|
||||
# = will clear the contents of that variable both specified from the file or the ones parsed
|
||||
# from the file system
|
||||
# += will add the values to the previous ones in the file or the ones parsed from the file
|
||||
# system
|
||||
#
|
||||
# The PG can be used to detect errors in this file, just create a new project with this addon
|
||||
# and the PG will write to the console the kind of error and in which line it is
|
||||
|
||||
meta:
|
||||
ADDON_NAME = ofxGPUFont
|
||||
ADDON_DESCRIPTION = draw font shapes with gpu
|
||||
ADDON_AUTHOR = Jakob Schloetter
|
||||
ADDON_TAGS = "typography font"
|
||||
ADDON_URL = https://gitlab.com/pointerstudio/utils/ofxGPUFont
|
||||
|
||||
common:
|
||||
# dependencies with other addons, a list of them separated by spaces
|
||||
# or use += in several lines
|
||||
# ADDON_DEPENDENCIES =
|
||||
|
||||
# include search paths, this will be usually parsed from the file system
|
||||
# but if the addon or addon libraries need special search paths they can be
|
||||
# specified here separated by spaces or one per line using +=
|
||||
# ADDON_INCLUDES =
|
||||
|
||||
# any special flag that should be passed to the compiler when using this
|
||||
# addon
|
||||
# ADDON_CFLAGS =
|
||||
|
||||
# any special flag that should be passed to the linker when using this
|
||||
# addon, also used for system libraries with -lname
|
||||
# ADDON_LDFLAGS =
|
||||
|
||||
# linux only, any library that should be included in the project using
|
||||
# pkg-config
|
||||
# ADDON_PKG_CONFIG_LIBRARIES =
|
||||
|
||||
# osx/iOS only, any framework that should be included in the project
|
||||
# ADDON_FRAMEWORKS =
|
||||
|
||||
# source files, these will be usually parsed from the file system looking
|
||||
# in the src folders in libs and the root of the addon. if your addon needs
|
||||
# to include files in different places or a different set of files per platform
|
||||
# they can be specified here
|
||||
# ADDON_SOURCES =
|
||||
|
||||
# some addons need resources to be copied to the bin/data folder of the project
|
||||
# specify here any files that need to be copied, you can use wildcards like * and ?
|
||||
# ADDON_DATA =
|
||||
ADDON_DATA = data/ofxGPUFont
|
||||
|
||||
# when parsing the file system looking for libraries exclude this for all or
|
||||
# a specific platform
|
||||
# ADDON_LIBS_EXCLUDE =
|
||||
|
||||
emscripten:
|
||||
linux64:
|
12
data/ofxGPUFont/shaders/GL3/background.frag
Normal file
12
data/ofxGPUFont/shaders/GL3/background.frag
Normal file
|
@ -0,0 +1,12 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 position;
|
||||
|
||||
out vec3 color;
|
||||
|
||||
void main() {
|
||||
float t = (position.y + 1.0) / 2.0;
|
||||
vec3 bottom = vec3(75.0, 55.0, 201.0) / 255.0;
|
||||
vec3 top = vec3(0.0, 12.0, 0.0) / 255.0;
|
||||
color = mix(bottom, top, t);
|
||||
}
|
15
data/ofxGPUFont/shaders/GL3/background.vert
Normal file
15
data/ofxGPUFont/shaders/GL3/background.vert
Normal file
|
@ -0,0 +1,15 @@
|
|||
#version 330 core
|
||||
|
||||
const vec2 vertices[4] = vec2[4](
|
||||
vec2(-1.0, -1.0),
|
||||
vec2( 1.0, -1.0),
|
||||
vec2(-1.0, 1.0),
|
||||
vec2( 1.0, 1.0)
|
||||
);
|
||||
|
||||
out vec2 position;
|
||||
|
||||
void main() {
|
||||
position = vertices[gl_VertexID];
|
||||
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
|
||||
}
|
156
data/ofxGPUFont/shaders/GL3/font.frag
Normal file
156
data/ofxGPUFont/shaders/GL3/font.frag
Normal file
|
@ -0,0 +1,156 @@
|
|||
#version 330 core
|
||||
|
||||
// Based on: http://wdobbie.com/post/gpu-text-rendering-with-vector-textures/
|
||||
|
||||
struct Glyph {
|
||||
int start, count;
|
||||
};
|
||||
|
||||
struct Curve {
|
||||
vec2 p0, p1, p2;
|
||||
};
|
||||
|
||||
uniform isamplerBuffer glyphs;
|
||||
uniform samplerBuffer curves;
|
||||
uniform vec4 color;
|
||||
|
||||
|
||||
// Controls for debugging and exploring:
|
||||
|
||||
// Size of the window (in pixels) used for 1-dimensional anti-aliasing along each rays.
|
||||
// 0 - no anti-aliasing
|
||||
// 1 - normal anti-aliasing
|
||||
// >=2 - exaggerated effect
|
||||
uniform float antiAliasingWindowSize = 1.0;
|
||||
|
||||
// Enable a second ray along the y-axis to achieve 2-dimensional anti-aliasing.
|
||||
uniform bool enableSuperSamplingAntiAliasing = true;
|
||||
|
||||
// Draw control points for debugging (green - on curve, magenta - off curve).
|
||||
uniform bool enableControlPointsVisualization = false;
|
||||
|
||||
|
||||
in vec2 uv;
|
||||
flat in int bufferIndex;
|
||||
|
||||
out vec4 result;
|
||||
|
||||
Glyph loadGlyph(int index) {
|
||||
Glyph result;
|
||||
ivec2 data = texelFetch(glyphs, index).xy;
|
||||
result.start = data.x;
|
||||
result.count = data.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
Curve loadCurve(int index) {
|
||||
Curve result;
|
||||
result.p0 = texelFetch(curves, 3*index+0).xy;
|
||||
result.p1 = texelFetch(curves, 3*index+1).xy;
|
||||
result.p2 = texelFetch(curves, 3*index+2).xy;
|
||||
return result;
|
||||
}
|
||||
|
||||
float computeCoverage(float inverseDiameter, vec2 p0, vec2 p1, vec2 p2) {
|
||||
if (p0.y > 0 && p1.y > 0 && p2.y > 0) return 0.0;
|
||||
if (p0.y < 0 && p1.y < 0 && p2.y < 0) return 0.0;
|
||||
|
||||
// Note: Simplified from abc formula by extracting a factor of (-2) from b.
|
||||
vec2 a = p0 - 2*p1 + p2;
|
||||
vec2 b = p0 - p1;
|
||||
vec2 c = p0;
|
||||
|
||||
float t0, t1;
|
||||
if (abs(a.y) >= 1e-5) {
|
||||
// Quadratic segment, solve abc formula to find roots.
|
||||
float radicand = b.y*b.y - a.y*c.y;
|
||||
if (radicand <= 0) return 0.0;
|
||||
|
||||
float s = sqrt(radicand);
|
||||
t0 = (b.y - s) / a.y;
|
||||
t1 = (b.y + s) / a.y;
|
||||
} else {
|
||||
// Linear segment, avoid division by a.y, which is near zero.
|
||||
// There is only one root, so we have to decide which variable to
|
||||
// assign it to based on the direction of the segment, to ensure that
|
||||
// the ray always exits the shape at t0 and enters at t1. For a
|
||||
// quadratic segment this works 'automatically', see readme.
|
||||
float t = p0.y / (p0.y - p2.y);
|
||||
if (p0.y < p2.y) {
|
||||
t0 = -1.0;
|
||||
t1 = t;
|
||||
} else {
|
||||
t0 = t;
|
||||
t1 = -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
float alpha = 0;
|
||||
|
||||
if (t0 >= 0 && t0 < 1) {
|
||||
float x = (a.x*t0 - 2.0*b.x)*t0 + c.x;
|
||||
alpha += clamp(x * inverseDiameter + 0.5, 0, 1);
|
||||
}
|
||||
|
||||
if (t1 >= 0 && t1 < 1) {
|
||||
float x = (a.x*t1 - 2.0*b.x)*t1 + c.x;
|
||||
alpha -= clamp(x * inverseDiameter + 0.5, 0, 1);
|
||||
}
|
||||
|
||||
return alpha;
|
||||
}
|
||||
|
||||
vec2 rotate(vec2 v) {
|
||||
return vec2(v.y, -v.x);
|
||||
}
|
||||
|
||||
void main() {
|
||||
float alpha = 0;
|
||||
|
||||
// Inverse of the diameter of a pixel in uv units for anti-aliasing.
|
||||
vec2 inverseDiameter = 1.0 / (antiAliasingWindowSize * fwidth(uv));
|
||||
|
||||
Glyph glyph = loadGlyph(bufferIndex);
|
||||
for (int i = 0; i < glyph.count; i++) {
|
||||
Curve curve = loadCurve(glyph.start + i);
|
||||
|
||||
vec2 p0 = curve.p0 - uv;
|
||||
vec2 p1 = curve.p1 - uv;
|
||||
vec2 p2 = curve.p2 - uv;
|
||||
|
||||
alpha += computeCoverage(inverseDiameter.x, p0, p1, p2);
|
||||
if (enableSuperSamplingAntiAliasing) {
|
||||
alpha += computeCoverage(inverseDiameter.y, rotate(p0), rotate(p1), rotate(p2));
|
||||
}
|
||||
}
|
||||
|
||||
if (enableSuperSamplingAntiAliasing) {
|
||||
alpha *= 0.5;
|
||||
}
|
||||
|
||||
alpha = clamp(alpha, 0.0, 1.0);
|
||||
result = color * alpha;
|
||||
|
||||
if (enableControlPointsVisualization) {
|
||||
// Visualize control points.
|
||||
vec2 fw = fwidth(uv);
|
||||
float r = 4.0 * 0.5 * (fw.x + fw.y);
|
||||
for (int i = 0; i < glyph.count; i++) {
|
||||
Curve curve = loadCurve(glyph.start + i);
|
||||
|
||||
vec2 p0 = curve.p0 - uv;
|
||||
vec2 p1 = curve.p1 - uv;
|
||||
vec2 p2 = curve.p2 - uv;
|
||||
|
||||
if (dot(p0, p0) < r*r || dot(p2, p2) < r*r) {
|
||||
result = vec4(0, 1, 0, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dot(p1, p1) < r*r) {
|
||||
result = vec4(1, 0, 1, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
data/ofxGPUFont/shaders/GL3/font.vert
Normal file
19
data/ofxGPUFont/shaders/GL3/font.vert
Normal file
|
@ -0,0 +1,19 @@
|
|||
#version 330 core
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat4 view;
|
||||
uniform mat4 model;
|
||||
uniform float z;
|
||||
|
||||
layout (location = 0) in vec2 vertexPosition;
|
||||
layout (location = 1) in vec2 vertexUV;
|
||||
layout (location = 2) in int vertexIndex;
|
||||
|
||||
out vec2 uv;
|
||||
flat out int bufferIndex;
|
||||
|
||||
void main() {
|
||||
gl_Position = projection * view * model * vec4(vertexPosition, z, 1);
|
||||
uv = vertexUV;
|
||||
bufferIndex = vertexIndex;
|
||||
}
|
1
example/addons.make
Normal file
1
example/addons.make
Normal file
|
@ -0,0 +1 @@
|
|||
ofxGPUFont
|
14
example/clean.sh
Executable file
14
example/clean.sh
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
PREVIOUS_DIR=$(pwd)
|
||||
|
||||
cd $DIR
|
||||
|
||||
emmake make clean && make clean && rm -rf obj && rm -rf ../../obj
|
||||
rm -rf ../../../libs/openFrameworksCompiled/lib/linux64/obj
|
||||
rm -rf ../../../libs/openFrameworksCompiled/lib/linux64/libopenFrameworks.a
|
||||
rm -rf ../../../libs/openFrameworksCompiled/lib/emscripten/obj
|
||||
rm -rf ../../../libs/openFrameworksCompiled/lib/emscripten/libopenFrameworks.bc
|
||||
|
||||
cd $PREVIOUS_DIR
|
9
example/src/defer.hpp
Normal file
9
example/src/defer.hpp
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Source: https://stackoverflow.com/a/42060129
|
||||
#ifndef defer
|
||||
struct defer_dummy {};
|
||||
template <class F> struct deferrer { F f; ~deferrer() { f(); } };
|
||||
template <class F> deferrer<F> operator*(defer_dummy, F f) { return {f}; }
|
||||
#define DEFER_(LINE) zz_defer##LINE
|
||||
#define DEFER(LINE) DEFER_(LINE)
|
||||
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]()
|
||||
#endif // defer
|
767
example/src/gpufont/font.hpp
Normal file
767
example/src/gpufont/font.hpp
Normal file
|
@ -0,0 +1,767 @@
|
|||
// Note: See "main.cpp" for headers.
|
||||
// This file was extracted to improve the organization of the code,
|
||||
// but it is still compiled in the "main.cpp" translation unit,
|
||||
// because both files have mostly the same dependencies (OpenGL, GLM, FreeType).
|
||||
#include "ofMain.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_MULTIPLE_MASTERS_H
|
||||
|
||||
#define F26DOT6_TO_DOUBLE(x) (1 / 64. * double(x))
|
||||
#define F16DOT16_TO_DOUBLE(x) (1 / 65536. * double(x))
|
||||
#define DOUBLE_TO_F16DOT16(x) FT_Fixed(65536. * x)
|
||||
|
||||
class Font {
|
||||
struct Glyph {
|
||||
FT_UInt index;
|
||||
int32_t bufferIndex;
|
||||
|
||||
int32_t curveCount;
|
||||
|
||||
// Important glyph metrics in font units.
|
||||
FT_Pos width, height;
|
||||
FT_Pos bearingX;
|
||||
FT_Pos bearingY;
|
||||
FT_Pos advance;
|
||||
};
|
||||
|
||||
struct BufferGlyph {
|
||||
int32_t start, count; // range of bezier curves belonging to this glyph
|
||||
};
|
||||
|
||||
struct BufferCurve {
|
||||
float x0, y0, x1, y1, x2, y2;
|
||||
};
|
||||
|
||||
struct BufferVertex {
|
||||
float x, y, u, v;
|
||||
int32_t bufferIndex;
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
static bool setFontVariationAxis(FT_Library & library, FT_Face & face, const char * name, double coordinate){
|
||||
bool success = false;
|
||||
if(face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS){
|
||||
FT_MM_Var * master = NULL;
|
||||
if(FT_Get_MM_Var(face, &master)){
|
||||
return false;
|
||||
}
|
||||
if(master && master->num_axis){
|
||||
std::vector <FT_Fixed> coords(master->num_axis);
|
||||
if(!FT_Get_Var_Design_Coordinates(face, FT_UInt(coords.size()), &coords[0])){
|
||||
for(FT_UInt i = 0; i < master->num_axis; ++i){
|
||||
if(!strcmp(name, master->axis[i].name)){
|
||||
coords[i] = DOUBLE_TO_F16DOT16(coordinate);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(FT_Set_Var_Design_Coordinates(face, FT_UInt(coords.size()), &coords[0])){
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
FT_Done_MM_Var(library, master);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
//bool listFontVariationAxes(std::vector<FontVariationAxis> &axes, FreetypeHandle *library, FontHandle *font) {
|
||||
//if (font->face->face_flags&FT_FACE_FLAG_MULTIPLE_MASTERS) {
|
||||
//FT_MM_Var *master = NULL;
|
||||
//if (FT_Get_MM_Var(font->face, &master))
|
||||
//return false;
|
||||
//axes.resize(master->num_axis);
|
||||
//for (FT_UInt i = 0; i < master->num_axis; i++) {
|
||||
//FontVariationAxis &axis = axes[i];
|
||||
//axis.name = master->axis[i].name;
|
||||
//axis.minValue = master->axis[i].minimum;
|
||||
//axis.maxValue = master->axis[i].maximum;
|
||||
//axis.defaultValue = master->axis[i].def;
|
||||
//}
|
||||
//FT_Done_MM_Var(library->library, master);
|
||||
//return true;
|
||||
//}
|
||||
//return false;
|
||||
//}
|
||||
static FT_Face loadFace(FT_Library library, const std::string & filename, std::string & error){
|
||||
FT_Face face = NULL;
|
||||
|
||||
string pwd = ofSystem("pwd");
|
||||
cout << "pwd: " << pwd << endl;
|
||||
pwd.erase(std::remove_if(pwd.begin(), pwd.end(), ::isspace), pwd.end());
|
||||
cout << "fontPAth: " << pwd << "/" << filename << endl;
|
||||
string fontPath = pwd + "/" + filename;
|
||||
|
||||
FT_Error ftError = FT_New_Face(library, fontPath.c_str(), 0, &face);
|
||||
if(ftError){
|
||||
const char * ftErrorStr = FT_Error_String(ftError);
|
||||
if(ftErrorStr){
|
||||
error = std::string(ftErrorStr);
|
||||
}else{
|
||||
// Fallback in case FT_Error_String returns NULL (e.g. if there
|
||||
// was an error or FT was compiled without error strings).
|
||||
std::stringstream stream;
|
||||
stream << "Error " << ftError;
|
||||
error = stream.str();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(!(face->face_flags & FT_FACE_FLAG_SCALABLE)){
|
||||
error = "non-scalable fonts are not supported";
|
||||
FT_Done_Face(face);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return face;
|
||||
}
|
||||
|
||||
// If hinting is enabled, worldSize must be an integer and defines the font size in pixels used for hinting.
|
||||
// Otherwise, worldSize can be an arbitrary floating-point value.
|
||||
Font(FT_Face face, float worldSize = 1.0f, bool hinting = false) : face(face), worldSize(worldSize), hinting(hinting){
|
||||
|
||||
if(hinting){
|
||||
loadFlags = FT_LOAD_NO_BITMAP;
|
||||
kerningMode = FT_KERNING_DEFAULT;
|
||||
|
||||
emSize = worldSize * 64;
|
||||
FT_Error error = FT_Set_Pixel_Sizes(face, 0, static_cast <FT_UInt>(std::ceil(worldSize)));
|
||||
if(error){
|
||||
std::cerr << "[font] error while setting pixel size: " << error << std::endl;
|
||||
}
|
||||
|
||||
}else{
|
||||
loadFlags = FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP;
|
||||
kerningMode = FT_KERNING_UNSCALED;
|
||||
emSize = face->units_per_EM;
|
||||
}
|
||||
|
||||
glGenVertexArrays(1, &vao);
|
||||
|
||||
glGenBuffers(1, &vbo);
|
||||
glGenBuffers(1, &ebo);
|
||||
|
||||
glGenTextures(1, &glyphTexture);
|
||||
glGenTextures(1, &curveTexture);
|
||||
|
||||
glGenBuffers(1, &glyphBuffer);
|
||||
glGenBuffers(1, &curveBuffer);
|
||||
|
||||
glBindVertexArray(vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof(BufferVertex), (void *)offsetof(BufferVertex, x));
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(BufferVertex), (void *)offsetof(BufferVertex, u));
|
||||
glEnableVertexAttribArray(2);
|
||||
glVertexAttribIPointer(2, 1, GL_INT, sizeof(BufferVertex), (void *)offsetof(BufferVertex, bufferIndex));
|
||||
glBindVertexArray(0);
|
||||
|
||||
{
|
||||
uint32_t charcode = 0;
|
||||
FT_UInt glyphIndex = 0;
|
||||
FT_Error error = FT_Load_Glyph(face, glyphIndex, loadFlags);
|
||||
if(error){
|
||||
std::cerr << "[font] error while loading undefined glyph: " << error << std::endl;
|
||||
// Continue, because we always want an entry for the undefined glyph in our glyphs map!
|
||||
}
|
||||
|
||||
buildGlyph(charcode, glyphIndex);
|
||||
}
|
||||
|
||||
for(uint32_t charcode = 32; charcode < 128; charcode++){
|
||||
FT_UInt glyphIndex = FT_Get_Char_Index(face, charcode);
|
||||
if(!glyphIndex){
|
||||
continue;
|
||||
}
|
||||
|
||||
FT_Error error = FT_Load_Glyph(face, glyphIndex, loadFlags);
|
||||
if(error){
|
||||
std::cerr << "[font] error while loading glyph for character " << charcode << ": " << error << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
buildGlyph(charcode, glyphIndex);
|
||||
}
|
||||
|
||||
uploadBuffers();
|
||||
|
||||
glBindTexture(GL_TEXTURE_BUFFER, glyphTexture);
|
||||
glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32I, glyphBuffer);
|
||||
glBindTexture(GL_TEXTURE_BUFFER, 0);
|
||||
|
||||
glBindTexture(GL_TEXTURE_BUFFER, curveTexture);
|
||||
glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, curveBuffer);
|
||||
glBindTexture(GL_TEXTURE_BUFFER, 0);
|
||||
}
|
||||
|
||||
~Font(){
|
||||
glDeleteVertexArrays(1, &vao);
|
||||
|
||||
glDeleteBuffers(1, &vbo);
|
||||
glDeleteBuffers(1, &ebo);
|
||||
|
||||
glDeleteTextures(1, &glyphTexture);
|
||||
glDeleteTextures(1, &curveTexture);
|
||||
|
||||
glDeleteBuffers(1, &glyphBuffer);
|
||||
glDeleteBuffers(1, &curveBuffer);
|
||||
|
||||
FT_Done_Face(face);
|
||||
}
|
||||
|
||||
public:
|
||||
void setWorldSize(float worldSize){
|
||||
if(worldSize == this->worldSize){
|
||||
return;
|
||||
}
|
||||
this->worldSize = worldSize;
|
||||
|
||||
if(!hinting){
|
||||
return;
|
||||
}
|
||||
|
||||
// We have to rebuild our buffers, because the outline coordinates can
|
||||
// change because of hinting.
|
||||
|
||||
emSize = worldSize * 64;
|
||||
FT_Error error = FT_Set_Pixel_Sizes(face, 0, static_cast <FT_UInt>(std::ceil(worldSize)));
|
||||
if(error){
|
||||
std::cerr << "[font] error while setting pixel size: " << error << std::endl;
|
||||
}
|
||||
|
||||
bufferGlyphs.clear();
|
||||
bufferCurves.clear();
|
||||
|
||||
for(auto it = glyphs.begin(); it != glyphs.end();){
|
||||
uint32_t charcode = it->first;
|
||||
FT_UInt glyphIndex = it->second.index;
|
||||
|
||||
FT_Error error = FT_Load_Glyph(face, glyphIndex, loadFlags);
|
||||
if(error){
|
||||
std::cerr << "[font] error while loading glyph for character " << charcode << ": " << error << std::endl;
|
||||
it = glyphs.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
// This call will overwrite the glyph in the glyphs map. However, it
|
||||
// cannot invalidate the iterator because the glyph is already in
|
||||
// the map if we are here.
|
||||
buildGlyph(charcode, glyphIndex);
|
||||
it++;
|
||||
}
|
||||
|
||||
uploadBuffers();
|
||||
}
|
||||
|
||||
void prepareGlyphsForText(const std::string & text){
|
||||
bool changed = false;
|
||||
|
||||
for(const char * textIt = text.c_str(); *textIt != '\0';){
|
||||
uint32_t charcode = decodeCharcode(&textIt);
|
||||
|
||||
if(charcode == '\r' || charcode == '\n'){
|
||||
continue;
|
||||
}
|
||||
if(glyphs.count(charcode) != 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
FT_UInt glyphIndex = FT_Get_Char_Index(face, charcode);
|
||||
if(!glyphIndex){
|
||||
continue;
|
||||
}
|
||||
|
||||
FT_Error error = FT_Load_Glyph(face, glyphIndex, loadFlags);
|
||||
if(error){
|
||||
std::cerr << "[font] error while loading glyph for character " << charcode << ": " << error << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
buildGlyph(charcode, glyphIndex);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if(changed){
|
||||
// Reupload the full buffer contents. To make this even more
|
||||
// dynamic, the buffers could be overallocated and only the added
|
||||
// data could be uploaded.
|
||||
uploadBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void uploadBuffers(){
|
||||
glBindBuffer(GL_TEXTURE_BUFFER, glyphBuffer);
|
||||
glBufferData(GL_TEXTURE_BUFFER, sizeof(BufferGlyph) * bufferGlyphs.size(), bufferGlyphs.data(), GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_TEXTURE_BUFFER, 0);
|
||||
|
||||
glBindBuffer(GL_TEXTURE_BUFFER, curveBuffer);
|
||||
glBufferData(GL_TEXTURE_BUFFER, sizeof(BufferCurve) * bufferCurves.size(), bufferCurves.data(), GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_TEXTURE_BUFFER, 0);
|
||||
}
|
||||
|
||||
void buildGlyph(uint32_t charcode, FT_UInt glyphIndex){
|
||||
BufferGlyph bufferGlyph;
|
||||
bufferGlyph.start = static_cast <int32_t>(bufferCurves.size());
|
||||
|
||||
short start = 0;
|
||||
for(int i = 0; i < face->glyph->outline.n_contours; i++){
|
||||
// Note: The end indices in face->glyph->outline.contours are inclusive.
|
||||
convertContour(bufferCurves, &face->glyph->outline, start, face->glyph->outline.contours[i], emSize);
|
||||
start = face->glyph->outline.contours[i] + 1;
|
||||
}
|
||||
|
||||
bufferGlyph.count = static_cast <int32_t>(bufferCurves.size()) - bufferGlyph.start;
|
||||
|
||||
int32_t bufferIndex = static_cast <int32_t>(bufferGlyphs.size());
|
||||
bufferGlyphs.push_back(bufferGlyph);
|
||||
|
||||
Glyph glyph;
|
||||
glyph.index = glyphIndex;
|
||||
glyph.bufferIndex = bufferIndex;
|
||||
glyph.curveCount = bufferGlyph.count;
|
||||
glyph.width = face->glyph->metrics.width;
|
||||
glyph.height = face->glyph->metrics.height;
|
||||
glyph.bearingX = face->glyph->metrics.horiBearingX;
|
||||
glyph.bearingY = face->glyph->metrics.horiBearingY;
|
||||
glyph.advance = face->glyph->metrics.horiAdvance;
|
||||
glyphs[charcode] = glyph;
|
||||
}
|
||||
|
||||
// This function takes a single contour (defined by firstIndex and
|
||||
// lastIndex, both inclusive) from outline and converts it into individual
|
||||
// quadratic bezier curves, which are added to the curves vector.
|
||||
void convertContour(std::vector <BufferCurve> & curves, const FT_Outline * outline, short firstIndex, short lastIndex, float emSize){
|
||||
// See https://freetype.org/freetype2/docs/glyphs/glyphs-6.html
|
||||
// for a detailed description of the outline format.
|
||||
//
|
||||
// In short, a contour is a list of points describing line segments
|
||||
// and quadratic or cubic bezier curves that form a closed shape.
|
||||
//
|
||||
// TrueType fonts only contain quadratic bezier curves. OpenType fonts
|
||||
// may contain outline data in TrueType format or in Compact Font
|
||||
// Format, which also allows cubic beziers. However, in FreeType it is
|
||||
// (theoretically) possible to mix the two types of bezier curves, so
|
||||
// we handle both at the same time.
|
||||
//
|
||||
// Each point in the contour has a tag specifying its type
|
||||
// (FT_CURVE_TAG_ON, FT_CURVE_TAG_CONIC or FT_CURVE_TAG_CUBIC).
|
||||
// FT_CURVE_TAG_ON points sit exactly on the outline, whereas the
|
||||
// other types are control points for quadratic/conic bezier curves,
|
||||
// which in general do not sit exactly on the outline and are also
|
||||
// called off points.
|
||||
//
|
||||
// Some examples of the basic segments:
|
||||
// ON - ON ... line segment
|
||||
// ON - CONIC - ON ... quadratic bezier curve
|
||||
// ON - CUBIC - CUBIC - ON ... cubic bezier curve
|
||||
//
|
||||
// Cubic bezier curves must always be described by two CUBIC points
|
||||
// inbetween two ON points. For the points used in the TrueType format
|
||||
// (ON, CONIC) there is a special rule, that two consecutive points of
|
||||
// the same type imply a virtual point of the opposite type at their
|
||||
// exact midpoint.
|
||||
//
|
||||
// For example the sequence ON - CONIC - CONIC - ON describes two
|
||||
// quadratic bezier curves where the virtual point forms the joining
|
||||
// end point of the two curves: ON - CONIC - [ON] - CONIC - ON.
|
||||
//
|
||||
// Similarly the sequence ON - ON can be thought of as a line segment
|
||||
// or a quadratic bezier curve (ON - [CONIC] - ON). Because the
|
||||
// virtual point is at the exact middle of the two endpoints, the
|
||||
// bezier curve is identical to the line segment.
|
||||
//
|
||||
// The font shader only supports quadratic bezier curves, so we use
|
||||
// this virtual point rule to represent line segments as quadratic
|
||||
// bezier curves.
|
||||
//
|
||||
// Cubic bezier curves are slightly more difficult, since they have a
|
||||
// higher degree than the shader supports. Each cubic curve is
|
||||
// approximated by two quadratic curves according to the following
|
||||
// paper. This preserves C1-continuity (location of and tangents at
|
||||
// the end points of the cubic curve) and the paper even proves that
|
||||
// splitting at the parametric center minimizes the error due to the
|
||||
// degree reduction. One could also analyze the approximation error
|
||||
// and split the cubic curve, if the error is too large. However,
|
||||
// almost all fonts use "nice" cubic curves, resulting in very small
|
||||
// errors already (see also the section on Font Design in the paper).
|
||||
//
|
||||
// Quadratic Approximation of Cubic Curves
|
||||
// Nghia Truong, Cem Yuksel, Larry Seiler
|
||||
// https://ttnghia.github.io/pdf/QuadraticApproximation.pdf
|
||||
// https://doi.org/10.1145/3406178
|
||||
|
||||
if(firstIndex == lastIndex){
|
||||
return;
|
||||
}
|
||||
|
||||
short dIndex = 1;
|
||||
if(outline->flags & FT_OUTLINE_REVERSE_FILL){
|
||||
short tmpIndex = lastIndex;
|
||||
lastIndex = firstIndex;
|
||||
firstIndex = tmpIndex;
|
||||
dIndex = -1;
|
||||
}
|
||||
|
||||
auto convert = [emSize](const FT_Vector & v){
|
||||
return glm::vec2(
|
||||
(float)v.x / emSize,
|
||||
(float)v.y / emSize
|
||||
);
|
||||
};
|
||||
|
||||
auto makeMidpoint = [](const glm::vec2 & a, const glm::vec2 & b){
|
||||
return 0.5f * (a + b);
|
||||
};
|
||||
|
||||
auto makeCurve = [](const glm::vec2 & p0, const glm::vec2 & p1, const glm::vec2 & p2){
|
||||
BufferCurve result;
|
||||
result.x0 = p0.x;
|
||||
result.y0 = p0.y;
|
||||
result.x1 = p1.x;
|
||||
result.y1 = p1.y;
|
||||
result.x2 = p2.x;
|
||||
result.y2 = p2.y;
|
||||
return result;
|
||||
};
|
||||
|
||||
// Find a point that is on the curve and remove it from the list.
|
||||
glm::vec2 first;
|
||||
bool firstOnCurve = (outline->tags[firstIndex] & FT_CURVE_TAG_ON);
|
||||
if(firstOnCurve){
|
||||
first = convert(outline->points[firstIndex]);
|
||||
firstIndex += dIndex;
|
||||
}else{
|
||||
bool lastOnCurve = (outline->tags[lastIndex] & FT_CURVE_TAG_ON);
|
||||
if(lastOnCurve){
|
||||
first = convert(outline->points[lastIndex]);
|
||||
lastIndex -= dIndex;
|
||||
}else{
|
||||
first = makeMidpoint(convert(outline->points[firstIndex]), convert(outline->points[lastIndex]));
|
||||
// This is a virtual point, so we don't have to remove it.
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec2 start = first;
|
||||
glm::vec2 control = first;
|
||||
glm::vec2 previous = first;
|
||||
char previousTag = FT_CURVE_TAG_ON;
|
||||
for(short index = firstIndex; index != lastIndex + dIndex; index += dIndex){
|
||||
glm::vec2 current = convert(outline->points[index]);
|
||||
char currentTag = FT_CURVE_TAG(outline->tags[index]);
|
||||
if(currentTag == FT_CURVE_TAG_CUBIC){
|
||||
// No-op, wait for more points.
|
||||
control = previous;
|
||||
}else if(currentTag == FT_CURVE_TAG_ON){
|
||||
if(previousTag == FT_CURVE_TAG_CUBIC){
|
||||
glm::vec2 & b0 = start;
|
||||
glm::vec2 & b1 = control;
|
||||
glm::vec2 & b2 = previous;
|
||||
glm::vec2 & b3 = current;
|
||||
|
||||
glm::vec2 c0 = b0 + 0.75f * (b1 - b0);
|
||||
glm::vec2 c1 = b3 + 0.75f * (b2 - b3);
|
||||
|
||||
glm::vec2 d = makeMidpoint(c0, c1);
|
||||
|
||||
curves.push_back(makeCurve(b0, c0, d));
|
||||
curves.push_back(makeCurve(d, c1, b3));
|
||||
}else if(previousTag == FT_CURVE_TAG_ON){
|
||||
// Linear segment.
|
||||
curves.push_back(makeCurve(previous, makeMidpoint(previous, current), current));
|
||||
}else{
|
||||
// Regular bezier curve.
|
||||
curves.push_back(makeCurve(start, previous, current));
|
||||
}
|
||||
start = current;
|
||||
control = current;
|
||||
}else{ /* currentTag == FT_CURVE_TAG_CONIC */
|
||||
if(previousTag == FT_CURVE_TAG_ON){
|
||||
// No-op, wait for third point.
|
||||
}else{
|
||||
// Create virtual on point.
|
||||
glm::vec2 mid = makeMidpoint(previous, current);
|
||||
curves.push_back(makeCurve(start, previous, mid));
|
||||
start = mid;
|
||||
control = mid;
|
||||
}
|
||||
}
|
||||
previous = current;
|
||||
previousTag = currentTag;
|
||||
}
|
||||
|
||||
// Close the contour.
|
||||
if(previousTag == FT_CURVE_TAG_CUBIC){
|
||||
glm::vec2 & b0 = start;
|
||||
glm::vec2 & b1 = control;
|
||||
glm::vec2 & b2 = previous;
|
||||
glm::vec2 & b3 = first;
|
||||
|
||||
glm::vec2 c0 = b0 + 0.75f * (b1 - b0);
|
||||
glm::vec2 c1 = b3 + 0.75f * (b2 - b3);
|
||||
|
||||
glm::vec2 d = makeMidpoint(c0, c1);
|
||||
|
||||
curves.push_back(makeCurve(b0, c0, d));
|
||||
curves.push_back(makeCurve(d, c1, b3));
|
||||
|
||||
}else if(previousTag == FT_CURVE_TAG_ON){
|
||||
// Linear segment.
|
||||
curves.push_back(makeCurve(previous, makeMidpoint(previous, first), first));
|
||||
}else{
|
||||
curves.push_back(makeCurve(start, previous, first));
|
||||
}
|
||||
}
|
||||
|
||||
// Decodes the first Unicode code point from the null-terminated UTF-8 string *text and advances *text to point at the next code point.
|
||||
// If the encoding is invalid, advances *text by one byte and returns 0.
|
||||
// *text should not be empty, because it will be advanced past the null terminator.
|
||||
uint32_t decodeCharcode(const char * * text){
|
||||
uint8_t first = static_cast <uint8_t>((*text)[0]);
|
||||
|
||||
// Fast-path for ASCII.
|
||||
if(first < 128){
|
||||
(*text)++;
|
||||
return static_cast <uint32_t>(first);
|
||||
}
|
||||
|
||||
// This could probably be optimized a bit.
|
||||
uint32_t result;
|
||||
int size;
|
||||
if((first & 0xE0) == 0xC0){ // 110xxxxx
|
||||
result = first & 0x1F;
|
||||
size = 2;
|
||||
}else if((first & 0xF0) == 0xE0){ // 1110xxxx
|
||||
result = first & 0x0F;
|
||||
size = 3;
|
||||
}else if((first & 0xF8) == 0xF0){ // 11110xxx
|
||||
result = first & 0x07;
|
||||
size = 4;
|
||||
}else{
|
||||
// Invalid encoding.
|
||||
(*text)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for(int i = 1; i < size; i++){
|
||||
uint8_t value = static_cast <uint8_t>((*text)[i]);
|
||||
// Invalid encoding (also catches a null terminator in the middle of a code point).
|
||||
if((value & 0xC0) != 0x80){ // 10xxxxxx
|
||||
(*text)++;
|
||||
return 0;
|
||||
}
|
||||
result = (result << 6) | (value & 0x3F);
|
||||
}
|
||||
|
||||
(*text) += size;
|
||||
return result;
|
||||
}
|
||||
|
||||
public:
|
||||
void drawSetup(){
|
||||
GLint location;
|
||||
|
||||
location = glGetUniformLocation(program, "glyphs");
|
||||
glUniform1i(location, 0);
|
||||
location = glGetUniformLocation(program, "curves");
|
||||
glUniform1i(location, 1);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_BUFFER, glyphTexture);
|
||||
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_BUFFER, curveTexture);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
void draw(float x, float y, float z, const std::string & text){
|
||||
float originalX = x;
|
||||
|
||||
glBindVertexArray(vao);
|
||||
|
||||
std::vector <BufferVertex> vertices;
|
||||
std::vector <int32_t> indices;
|
||||
|
||||
FT_UInt previous = 0;
|
||||
for(const char * textIt = text.c_str(); *textIt != '\0';){
|
||||
uint32_t charcode = decodeCharcode(&textIt);
|
||||
|
||||
if(charcode == '\r'){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(charcode == '\n'){
|
||||
x = originalX;
|
||||
y -= (float)face->height / (float)face->units_per_EM * worldSize;
|
||||
if(hinting){
|
||||
y = std::round(y);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto glyphIt = glyphs.find(charcode);
|
||||
Glyph & glyph = (glyphIt == glyphs.end()) ? glyphs[0] : glyphIt->second;
|
||||
|
||||
if(previous != 0 && glyph.index != 0){
|
||||
FT_Vector kerning;
|
||||
FT_Error error = FT_Get_Kerning(face, previous, glyph.index, kerningMode, &kerning);
|
||||
if(!error){
|
||||
x += (float)kerning.x / emSize * worldSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Do not emit quad for empty glyphs (whitespace).
|
||||
if(glyph.curveCount){
|
||||
FT_Pos d = (FT_Pos)(emSize * dilation);
|
||||
|
||||
float u0 = (float)(glyph.bearingX - d) / emSize;
|
||||
float v0 = (float)(glyph.bearingY - glyph.height - d) / emSize;
|
||||
float u1 = (float)(glyph.bearingX + glyph.width + d) / emSize;
|
||||
float v1 = (float)(glyph.bearingY + d) / emSize;
|
||||
|
||||
float x0 = x + u0 * worldSize;
|
||||
float y0 = y + v0 * worldSize;
|
||||
float x1 = x + u1 * worldSize;
|
||||
float y1 = y + v1 * worldSize;
|
||||
|
||||
int32_t base = static_cast <int32_t>(vertices.size());
|
||||
vertices.push_back(BufferVertex{x0, y0, u0, v0, glyph.bufferIndex});
|
||||
vertices.push_back(BufferVertex{x1, y0, u1, v0, glyph.bufferIndex});
|
||||
vertices.push_back(BufferVertex{x1, y1, u1, v1, glyph.bufferIndex});
|
||||
vertices.push_back(BufferVertex{x0, y1, u0, v1, glyph.bufferIndex});
|
||||
indices.insert(indices.end(), {base, base + 1, base + 2, base + 2, base + 3, base});
|
||||
}
|
||||
|
||||
x += (float)glyph.advance / emSize * worldSize;
|
||||
previous = glyph.index;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(BufferVertex) * vertices.size(), vertices.data(), GL_STREAM_DRAW);
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int32_t) * indices.size(), indices.data(), GL_STREAM_DRAW);
|
||||
|
||||
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
struct BoundingBox {
|
||||
float minX, minY, maxX, maxY;
|
||||
};
|
||||
|
||||
BoundingBox measure(float x, float y, const std::string & text){
|
||||
BoundingBox bb;
|
||||
bb.minX = +std::numeric_limits <float>::infinity();
|
||||
bb.minY = +std::numeric_limits <float>::infinity();
|
||||
bb.maxX = -std::numeric_limits <float>::infinity();
|
||||
bb.maxY = -std::numeric_limits <float>::infinity();
|
||||
|
||||
float originalX = x;
|
||||
|
||||
FT_UInt previous = 0;
|
||||
for(const char * textIt = text.c_str(); *textIt != '\0';){
|
||||
uint32_t charcode = decodeCharcode(&textIt);
|
||||
|
||||
if(charcode == '\r'){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(charcode == '\n'){
|
||||
x = originalX;
|
||||
y -= (float)face->height / (float)face->units_per_EM * worldSize;
|
||||
if(hinting){
|
||||
y = std::round(y);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto glyphIt = glyphs.find(charcode);
|
||||
Glyph & glyph = (glyphIt == glyphs.end()) ? glyphs[0] : glyphIt->second;
|
||||
|
||||
if(previous != 0 && glyph.index != 0){
|
||||
FT_Vector kerning;
|
||||
FT_Error error = FT_Get_Kerning(face, previous, glyph.index, kerningMode, &kerning);
|
||||
if(!error){
|
||||
x += (float)kerning.x / emSize * worldSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Do not apply dilation here, we want to calculate exact bounds.
|
||||
float u0 = (float)(glyph.bearingX) / emSize;
|
||||
float v0 = (float)(glyph.bearingY - glyph.height) / emSize;
|
||||
float u1 = (float)(glyph.bearingX + glyph.width) / emSize;
|
||||
float v1 = (float)(glyph.bearingY) / emSize;
|
||||
|
||||
float x0 = x + u0 * worldSize;
|
||||
float y0 = y + v0 * worldSize;
|
||||
float x1 = x + u1 * worldSize;
|
||||
float y1 = y + v1 * worldSize;
|
||||
|
||||
if(x0 < bb.minX){
|
||||
bb.minX = x0;
|
||||
}
|
||||
if(y0 < bb.minY){
|
||||
bb.minY = y0;
|
||||
}
|
||||
if(x1 > bb.maxX){
|
||||
bb.maxX = x1;
|
||||
}
|
||||
if(y1 > bb.maxY){
|
||||
bb.maxY = y1;
|
||||
}
|
||||
|
||||
x += (float)glyph.advance / emSize * worldSize;
|
||||
previous = glyph.index;
|
||||
}
|
||||
|
||||
return bb;
|
||||
}
|
||||
|
||||
private:
|
||||
FT_Face face;
|
||||
|
||||
// Whether hinting is enabled for this instance.
|
||||
// Note that hinting changes how we operate FreeType:
|
||||
// If hinting is not enabled, we scale all coordinates ourselves (see comment for emSize).
|
||||
// If hinting is enabled, we must let FreeType scale the outlines for the hinting to work properly.
|
||||
// The variables loadFlags and kerningMode are set in the constructor and control this scaling behavior.
|
||||
bool hinting;
|
||||
FT_Int32 loadFlags;
|
||||
FT_Kerning_Mode kerningMode;
|
||||
|
||||
// Size of the em square used to convert metrics into em-relative values,
|
||||
// which can then be scaled to the worldSize. We do the scaling ourselves in
|
||||
// floating point to support arbitrary world sizes (whereas the fixed-point
|
||||
// numbers used by FreeType do not have enough resolution if the world size
|
||||
// is small).
|
||||
// Following the FreeType convention, if hinting (and therefore scaling) is enabled,
|
||||
// this value is in 1/64th of a pixel (compatible with 26.6 fixed point numbers).
|
||||
// If hinting/scaling is not enabled, this value is in font units.
|
||||
float emSize;
|
||||
|
||||
float worldSize;
|
||||
|
||||
GLuint vao, vbo, ebo;
|
||||
GLuint glyphTexture, curveTexture;
|
||||
GLuint glyphBuffer, curveBuffer;
|
||||
|
||||
std::vector <BufferGlyph> bufferGlyphs;
|
||||
std::vector <BufferCurve> bufferCurves;
|
||||
std::unordered_map <uint32_t, Glyph> glyphs;
|
||||
|
||||
public:
|
||||
// ID of the shader program to use.
|
||||
GLuint program = 0;
|
||||
|
||||
// The glyph quads are expanded by this amount to enable proper
|
||||
// anti-aliasing. Value is relative to emSize.
|
||||
float dilation = 0;
|
||||
};
|
18
example/src/gpufont/glm.hpp
Normal file
18
example/src/gpufont/glm.hpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/trigonometric.hpp>
|
||||
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include <glm/gtx/hash.hpp>
|
||||
#include <glm/gtx/string_cast.hpp>
|
||||
#include <glm/gtx/transform.hpp>
|
717
example/src/gpufont/maingpufont.hpp
Normal file
717
example/src/gpufont/maingpufont.hpp
Normal file
|
@ -0,0 +1,717 @@
|
|||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <defer.hpp>
|
||||
|
||||
#include "ofMain.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_MULTIPLE_MASTERS_H
|
||||
|
||||
#include "glm.hpp"
|
||||
|
||||
#include "shader_catalog.hpp"
|
||||
|
||||
#define F26DOT6_TO_DOUBLE(x) (1 / 64. * double(x))
|
||||
#define F16DOT16_TO_DOUBLE(x) (1 / 65536. * double(x))
|
||||
#define DOUBLE_TO_F16DOT16(x) FT_Fixed(65536. * x)
|
||||
|
||||
#include "font.hpp"
|
||||
|
||||
float wght = 700.0;
|
||||
float wghtStep = 10.0;
|
||||
std::string currentFontPath = "";
|
||||
|
||||
struct Transform {
|
||||
float fovy = glm::radians(60.0f);
|
||||
float distance = 0.42f;
|
||||
glm::mat3 rotation = glm::mat3(1.0f);
|
||||
glm::vec3 position = glm::vec3(0.0f);
|
||||
|
||||
glm::mat4 getProjectionMatrix(float aspect){
|
||||
return glm::perspective( /* fovy = */ glm::radians(60.0f), aspect, 0.002f, 12.000f);
|
||||
}
|
||||
|
||||
glm::mat4 getViewMatrix(){
|
||||
auto translation = glm::translate(position);
|
||||
return glm::lookAt(glm::vec3(0, 0, distance), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0)) * glm::mat4(rotation) * translation;
|
||||
}
|
||||
};
|
||||
|
||||
struct DragController {
|
||||
enum class Action {
|
||||
NONE,
|
||||
TRANSLATE,
|
||||
ROTATE_TURNTABLE,
|
||||
ROTATE_TRACKBALL
|
||||
};
|
||||
|
||||
Transform * transform = nullptr;
|
||||
int activeButton = -1;
|
||||
Action activeAction = Action::NONE;
|
||||
|
||||
double dragX, dragY;
|
||||
double wrapX, wrapY;
|
||||
double virtualX, virtualY;
|
||||
glm::vec3 dragTarget;
|
||||
|
||||
void reset(){
|
||||
// Reset transform.
|
||||
*transform = Transform{};
|
||||
|
||||
// Cancel active action, if any.
|
||||
activeButton = -1;
|
||||
activeAction = Action::NONE;
|
||||
}
|
||||
|
||||
bool unprojectMousePositionToXYPlane(GLFWwindow * window, double x, double y, glm::vec3 & result){
|
||||
int iwidth = 0, iheight = 0;
|
||||
glfwGetWindowSize(window, &iwidth, &iheight);
|
||||
|
||||
double width = iwidth;
|
||||
double height = iheight;
|
||||
|
||||
glm::mat4 projection = transform->getProjectionMatrix(float(width / height));
|
||||
glm::mat4 view = transform->getViewMatrix();
|
||||
|
||||
double relX = x / width * 2.0 - 1.0;
|
||||
double relY = y / height * 2.0 - 1.0;
|
||||
|
||||
glm::vec4 clipPos = glm::vec4(float(relX), -float(relY), 0.5f, 1.0f);
|
||||
glm::vec4 worldPos = glm::inverse(projection * view) * clipPos;
|
||||
worldPos *= 1.0f / worldPos.w;
|
||||
|
||||
glm::vec3 pos = glm::vec3(glm::column(glm::inverse(view), 3));
|
||||
glm::vec3 dir = glm::normalize(glm::vec3(worldPos) - pos);
|
||||
float t = -pos.z / dir.z;
|
||||
|
||||
result = pos + t * dir;
|
||||
return t > 0.0f;
|
||||
}
|
||||
|
||||
void onMouseButton(GLFWwindow * window, int button, int action, int mods){
|
||||
if(action == GLFW_PRESS && activeButton == -1){
|
||||
activeButton = button;
|
||||
|
||||
if(mods & GLFW_MOD_CONTROL){
|
||||
activeAction = Action::TRANSLATE;
|
||||
}else{
|
||||
if(activeButton == GLFW_MOUSE_BUTTON_2){
|
||||
activeAction = Action::TRANSLATE;
|
||||
}else if(activeButton == GLFW_MOUSE_BUTTON_3){
|
||||
activeAction = Action::ROTATE_TURNTABLE;
|
||||
}else{
|
||||
activeAction = Action::ROTATE_TRACKBALL;
|
||||
}
|
||||
}
|
||||
|
||||
glfwGetCursorPos(window, &dragX, &dragY);
|
||||
wrapX = std::numeric_limits <double>::quiet_NaN();
|
||||
wrapY = std::numeric_limits <double>::quiet_NaN();
|
||||
virtualX = dragX;
|
||||
virtualY = dragY;
|
||||
|
||||
glm::vec3 target;
|
||||
bool ok = unprojectMousePositionToXYPlane(window, dragX, dragY, target);
|
||||
dragTarget = ok ? target : glm::vec3();
|
||||
}else if(action == GLFW_RELEASE && activeButton == button){
|
||||
activeButton = -1;
|
||||
activeAction = Action::NONE;
|
||||
dragX = 0.0;
|
||||
dragY = 0.0;
|
||||
wrapX = std::numeric_limits <double>::quiet_NaN();
|
||||
wrapY = std::numeric_limits <double>::quiet_NaN();
|
||||
virtualX = 0.0;
|
||||
virtualY = 0.0;
|
||||
dragTarget = glm::vec3();
|
||||
}
|
||||
}
|
||||
|
||||
void onCursorPos(GLFWwindow * window, double x, double y){
|
||||
if(activeAction == Action::NONE){
|
||||
return;
|
||||
}
|
||||
|
||||
int iwidth = 0, iheight = 0;
|
||||
glfwGetWindowSize(window, &iwidth, &iheight);
|
||||
|
||||
double width = iwidth;
|
||||
double height = iheight;
|
||||
|
||||
double deltaX = x - dragX;
|
||||
double deltaY = y - dragY;
|
||||
|
||||
if(!std::isnan(wrapX) && !std::isnan(wrapY)){
|
||||
double wrapDeltaX = x - wrapX;
|
||||
double wrapDeltaY = y - wrapY;
|
||||
if(wrapDeltaX * wrapDeltaX + wrapDeltaY * wrapDeltaY < deltaX * deltaX + deltaY * deltaY){
|
||||
deltaX = wrapDeltaX;
|
||||
deltaY = wrapDeltaY;
|
||||
wrapX = std::numeric_limits <double>::quiet_NaN();
|
||||
wrapY = std::numeric_limits <double>::quiet_NaN();
|
||||
}
|
||||
}
|
||||
|
||||
dragX = x;
|
||||
dragY = y;
|
||||
|
||||
double targetX = x;
|
||||
double targetY = y;
|
||||
bool changed = false;
|
||||
if(targetX < 0){
|
||||
targetX += width - 1;
|
||||
changed = true;
|
||||
}else if(targetX >= width){
|
||||
targetX -= width - 1;
|
||||
changed = true;
|
||||
}
|
||||
if(targetY < 0){
|
||||
targetY += height - 1;
|
||||
changed = true;
|
||||
}else if(targetY >= height){
|
||||
targetY -= height - 1;
|
||||
changed = true;
|
||||
}
|
||||
if(changed){
|
||||
glfwSetCursorPos(window, targetX, targetY);
|
||||
wrapX = targetX;
|
||||
wrapY = targetY;
|
||||
}
|
||||
|
||||
if(activeAction == Action::TRANSLATE){
|
||||
virtualX += deltaX;
|
||||
virtualY += deltaY;
|
||||
|
||||
glm::vec3 target;
|
||||
bool ok = unprojectMousePositionToXYPlane(window, virtualX, virtualY, target);
|
||||
if(ok){
|
||||
float x = transform->position.x;
|
||||
float y = transform->position.y;
|
||||
glm::vec3 delta = target - dragTarget;
|
||||
transform->position.x = glm::clamp(x + delta.x, -4.0f, 4.0f);
|
||||
transform->position.y = glm::clamp(y + delta.y, -4.0f, 4.0f);
|
||||
}
|
||||
}else if(activeAction == Action::ROTATE_TURNTABLE){
|
||||
double size = glm::min(width, height);
|
||||
glm::mat3 rx = glm::rotate(float(deltaX / size * glm::pi <double>()), glm::vec3(0, 0, 1));
|
||||
glm::mat3 ry = glm::rotate(float(deltaY / size * glm::pi <double>()), glm::vec3(1, 0, 0));
|
||||
transform->rotation = ry * transform->rotation * rx;
|
||||
}else if(activeAction == Action::ROTATE_TRACKBALL){
|
||||
double size = glm::min(width, height);
|
||||
glm::mat3 rx = glm::rotate(float(deltaX / size * glm::pi <double>()), glm::vec3(0, 1, 0));
|
||||
glm::mat3 ry = glm::rotate(float(deltaY / size * glm::pi <double>()), glm::vec3(1, 0, 0));
|
||||
transform->rotation = ry * rx * transform->rotation;
|
||||
}
|
||||
}
|
||||
|
||||
void onScroll(GLFWwindow * window, double xOffset, double yOffset){
|
||||
float factor = glm::clamp(1.0 - float(yOffset) / 10.0, 0.1, 1.9);
|
||||
transform->distance = glm::clamp(transform->distance * factor, 0.010f, 10.000f);
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
FT_Library library;
|
||||
|
||||
Transform transform;
|
||||
DragController dragController;
|
||||
|
||||
// Empty VAO used when the vertex shader has no input and only uses gl_VertexID,
|
||||
// because OpenGL still requires a non-zero VAO to be bound for the draw call.
|
||||
GLuint emptyVAO;
|
||||
|
||||
std::unique_ptr <ShaderCatalog> shaderCatalog;
|
||||
std::shared_ptr <ShaderCatalog::Entry> backgroundShader;
|
||||
std::shared_ptr <ShaderCatalog::Entry> fontShader;
|
||||
|
||||
std::unique_ptr <Font> mainFont;
|
||||
std::unique_ptr <Font> helpFont;
|
||||
std::vector <std::unique_ptr <Font> > otherFonts;
|
||||
std::vector <float> otherWghts;
|
||||
std::vector <float> otherSteps;
|
||||
static const int N_OTHER_FONTS = 5;
|
||||
|
||||
constexpr float helpFontBaseSize = 20.0f;
|
||||
|
||||
int antiAliasingWindowSize = 1;
|
||||
bool enableSuperSamplingAntiAliasing = true;
|
||||
bool enableControlPointsVisualization = false;
|
||||
|
||||
bool showHelp = true;
|
||||
|
||||
Font::BoundingBox bb;
|
||||
std::string mainTexta =
|
||||
R"DONE(AVM AAIn the center of Fedora, that gray stone metropolis, stands a metal building
|
||||
[from Invisible Cities by Italo Calvino])DONE";
|
||||
std::string mainText =
|
||||
R"DONE(In the center of Fedora, that gray stone metropolis, stands a metal building
|
||||
with a crystal globe in every room. Looking into each globe, you see a blue
|
||||
city, the model of a different Fedora. These are the forms the city could have
|
||||
taken if, for one reason or another, it had not become what we see today. In
|
||||
every age someone, looking at Fedora as it was, imagined a way of making it the
|
||||
ideal city, but while he constructed his miniature model, Fedora was already no
|
||||
longer the same as before, and what had been until yesterday a possible future
|
||||
became only a toy in a glass globe.
|
||||
|
||||
The building with the globes is now Fedora's museum: every inhabitant visits it,
|
||||
chooses the city that corresponds to his desires, contemplates it, imagining his
|
||||
reflection in the medusa pond that would have collected the waters of the canal
|
||||
(if it had not been dried up), the view from the high canopied box along the
|
||||
avenue reserved for elephants (now banished from the city), the fun of sliding
|
||||
down the spiral, twisting minaret (which never found a pedestal from which to
|
||||
rise).
|
||||
|
||||
On the map of your empire, O Great Khan, there must be room both for the big,
|
||||
stone Fedora and the little Fedoras in glass globes. Not because they are all
|
||||
equally real, but because they are only assumptions. The one contains what is
|
||||
accepted as necessary when it is not yet so; the others, what is imagined as
|
||||
possible and, a moment later, is possible no longer.
|
||||
|
||||
On the map of your empire, O Great Khan, there must be room both for the big,
|
||||
stone Fedora and the little Fedoras in glass globes. Not because they are all
|
||||
equally real, but because they are only assumptions. The one contains what is
|
||||
accepted as necessary when it is not yet so; the others, what is imagined as
|
||||
possible and, a moment later, is possible no longer.
|
||||
|
||||
On the map of your empire, O Great Khan, there must be room both for the big,
|
||||
stone Fedora and the little Fedoras in glass globes. Not because they are all
|
||||
equally real, but because they are only assumptions. The one contains what is
|
||||
accepted as necessary when it is not yet so; the others, what is imagined as
|
||||
possible and, a moment later, is possible no longer.
|
||||
|
||||
On the map of your empire, O Great Khan, there must be room both for the big,
|
||||
stone Fedora and the little Fedoras in glass globes. Not because they are all
|
||||
equally real, but because they are only assumptions. The one contains what is
|
||||
accepted as necessary when it is not yet so; the others, what is imagined as
|
||||
possible and, a moment later, is possible no longer.
|
||||
|
||||
The building with the globes is now Fedora's museum: every inhabitant visits it,
|
||||
chooses the city that corresponds to his desires, contemplates it, imagining his
|
||||
reflection in the medusa pond that would have collected the waters of the canal
|
||||
(if it had not been dried up), the view from the high canopied box along the
|
||||
avenue reserved for elephants (now banished from the city), the fun of sliding
|
||||
down the spiral, twisting minaret (which never found a pedestal from which to
|
||||
rise).
|
||||
|
||||
The building with the globes is now Fedora's museum: every inhabitant visits it,
|
||||
chooses the city that corresponds to his desires, contemplates it, imagining his
|
||||
reflection in the medusa pond that would have collected the waters of the canal
|
||||
(if it had not been dried up), the view from the high canopied box along the
|
||||
avenue reserved for elephants (now banished from the city), the fun of sliding
|
||||
down the spiral, twisting minaret (which never found a pedestal from which to
|
||||
rise).
|
||||
|
||||
On the map of your empire, O Great Khan, there must be room both for the big,
|
||||
stone Fedora and the little Fedoras in glass globes. Not because they are all
|
||||
equally real, but because they are only assumptions. The one contains what is
|
||||
accepted as necessary when it is not yet so; the others, what is imagined as
|
||||
possible and, a moment later, is possible no longer.
|
||||
|
||||
On the map of your empire, O Great Khan, there must be room both for the big,
|
||||
stone Fedora and the little Fedoras in glass globes. Not because they are all
|
||||
equally real, but because they are only assumptions. The one contains what is
|
||||
accepted as necessary when it is not yet so; the others, what is imagined as
|
||||
possible and, a moment later, is possible no longer.
|
||||
|
||||
On the map of your empire, O Great Khan, there must be room both for the big,
|
||||
stone Fedora and the little Fedoras in glass globes. Not because they are all
|
||||
equally real, but because they are only assumptions. The one contains what is
|
||||
accepted as necessary when it is not yet so; the others, what is imagined as
|
||||
possible and, a moment later, is possible no longer.
|
||||
|
||||
On the map of your empire, O Great Khan, there must be room both for the big,
|
||||
stone Fedora and the little Fedoras in glass globes. Not because they are all
|
||||
equally real, but because they are only assumptions. The one contains what is
|
||||
accepted as necessary when it is not yet so; the others, what is imagined as
|
||||
possible and, a moment later, is possible no longer.
|
||||
[from Invisible Cities by Italo Calvino])DONE";
|
||||
|
||||
}
|
||||
|
||||
void updateWght(float & value, float & step){
|
||||
value += step;
|
||||
|
||||
if(value >= 700.0){
|
||||
if(step > 0){
|
||||
step *= -1;
|
||||
}
|
||||
value += step;
|
||||
}
|
||||
if(value <= 100.0){
|
||||
if(step < 0){
|
||||
step *= -1;
|
||||
}
|
||||
value += step;
|
||||
}
|
||||
}
|
||||
|
||||
int currentIndexx = -1;
|
||||
|
||||
static std::unique_ptr <Font> loadFont(const std::string & filename, float worldSize = 1.0f, bool hinting = false){
|
||||
std::string error;
|
||||
FT_Face face = Font::loadFace(library, filename, error);
|
||||
float & wghtValue = currentIndexx < 0 ? wght : otherWghts[currentIndexx];
|
||||
bool success = Font::setFontVariationAxis(library, face, "Weight", wghtValue);
|
||||
//if(success){
|
||||
//std::cout << "lol, success?" << wght << std::endl;
|
||||
//}else{
|
||||
//std::cout << "godverdomme" << wght << std::endl;
|
||||
//}
|
||||
if(error != ""){
|
||||
std::cerr << "[font] failed to load " << filename << ": " << error << std::endl;
|
||||
return std::unique_ptr <Font>{};
|
||||
}
|
||||
|
||||
return std::make_unique <Font>(face, worldSize, hinting);
|
||||
}
|
||||
|
||||
static void tryUpdateMainFont(const std::string & filename){
|
||||
{
|
||||
currentIndexx = -1;
|
||||
auto font = loadFont(filename, 0.05f);
|
||||
if(!font){
|
||||
return;
|
||||
}
|
||||
|
||||
font->dilation = 0.1f;
|
||||
|
||||
font->prepareGlyphsForText(mainText);
|
||||
|
||||
mainFont = std::move(font);
|
||||
bb = mainFont->measure(0, 0, mainText);
|
||||
}
|
||||
updateWght(wght, wghtStep);
|
||||
|
||||
for(int i = 0; i < N_OTHER_FONTS; ++i){
|
||||
if(otherWghts.size() == i){
|
||||
int w = i * 213456;
|
||||
while(w > 700){
|
||||
w -= 700;
|
||||
}
|
||||
otherWghts.push_back(float(w));
|
||||
otherSteps.push_back(float(wghtStep));
|
||||
}
|
||||
currentIndexx = i;
|
||||
auto font = loadFont(filename, 0.05f);
|
||||
if(!font){
|
||||
return;
|
||||
}
|
||||
updateWght(otherWghts[i], otherSteps[i]);
|
||||
//std::cout << "updateWght(" << otherWghts[i] << "," << otherSteps[i] << ");" << std::endl;
|
||||
|
||||
font->dilation = 0.1f;
|
||||
|
||||
font->prepareGlyphsForText(mainText);
|
||||
|
||||
if(otherFonts.size() == i){
|
||||
otherFonts.push_back(std::move(font));
|
||||
}else{
|
||||
otherFonts[i] = std::move(font);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mouseButtonCallback(GLFWwindow * window, int button, int action, int mods){
|
||||
dragController.onMouseButton(window, button, action, mods);
|
||||
}
|
||||
|
||||
static void cursorPosCallback(GLFWwindow * window, double x, double y){
|
||||
dragController.onCursorPos(window, x, y);
|
||||
}
|
||||
|
||||
static void scrollCallback(GLFWwindow * window, double xOffset, double yOffset){
|
||||
dragController.onScroll(window, xOffset, yOffset);
|
||||
}
|
||||
|
||||
static void keyCallback(GLFWwindow * window, int key, int scancode, int action, int mods){
|
||||
if(action != GLFW_PRESS){
|
||||
return;
|
||||
}
|
||||
switch(key){
|
||||
case GLFW_KEY_R:
|
||||
dragController.reset();
|
||||
break;
|
||||
|
||||
case GLFW_KEY_C:
|
||||
enableControlPointsVisualization = !enableControlPointsVisualization;
|
||||
break;
|
||||
|
||||
case GLFW_KEY_A:
|
||||
enableSuperSamplingAntiAliasing = !enableSuperSamplingAntiAliasing;
|
||||
break;
|
||||
|
||||
case GLFW_KEY_0:
|
||||
antiAliasingWindowSize = 0;
|
||||
break;
|
||||
|
||||
case GLFW_KEY_1:
|
||||
antiAliasingWindowSize = 1;
|
||||
break;
|
||||
|
||||
case GLFW_KEY_2:
|
||||
antiAliasingWindowSize = 20;
|
||||
break;
|
||||
|
||||
case GLFW_KEY_3:
|
||||
antiAliasingWindowSize = 40;
|
||||
break;
|
||||
|
||||
case GLFW_KEY_S:
|
||||
antiAliasingWindowSize = 1;
|
||||
enableSuperSamplingAntiAliasing = true;
|
||||
break;
|
||||
|
||||
case GLFW_KEY_H:
|
||||
showHelp = !showHelp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void dropCallback(GLFWwindow * window, int pathCount, const char * paths[]){
|
||||
if(pathCount == 0){
|
||||
return;
|
||||
}
|
||||
currentFontPath = paths[0];
|
||||
tryUpdateMainFont(paths[0]);
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[]){
|
||||
if(!glfwInit()){
|
||||
std::cerr << "ERROR: failed to initialize GLFW" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
|
||||
glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE);
|
||||
|
||||
GLFWwindow * window = glfwCreateWindow(1600, 900, "GPU Font Rendering Demo", nullptr, nullptr);
|
||||
if(!window){
|
||||
std::cerr << "ERROR: failed to create GLFW window" << std::endl;
|
||||
glfwTerminate();
|
||||
return 1;
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
|
||||
if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){
|
||||
std::cerr << "ERROR: failed to initialize OpenGL context" << std::endl;
|
||||
glfwTerminate();
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
FT_Error error = FT_Init_FreeType(&library);
|
||||
if(error){
|
||||
std::cerr << "ERROR: failed to initialize FreeType" << std::endl;
|
||||
glfwTerminate();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
dragController.transform = &transform;
|
||||
glfwSetMouseButtonCallback(window, mouseButtonCallback);
|
||||
glfwSetCursorPosCallback(window, cursorPosCallback);
|
||||
glfwSetScrollCallback(window, scrollCallback);
|
||||
glfwSetKeyCallback(window, keyCallback);
|
||||
glfwSetDropCallback(window, dropCallback);
|
||||
|
||||
glGenVertexArrays(1, &emptyVAO);
|
||||
|
||||
shaderCatalog = std::make_unique <ShaderCatalog>("shaders");
|
||||
backgroundShader = shaderCatalog->get("background");
|
||||
fontShader = shaderCatalog->get("font");
|
||||
|
||||
currentFontPath = "fonts/SourceSerifPro-Regular.otf";
|
||||
tryUpdateMainFont(currentFontPath);
|
||||
|
||||
{
|
||||
float xscale, yscale;
|
||||
glfwGetWindowContentScale(window, &xscale, &yscale);
|
||||
float worldSize = std::ceil(helpFontBaseSize * yscale);
|
||||
helpFont = loadFont("fonts/SourceSansPro-Semibold.otf", worldSize, true);
|
||||
}
|
||||
|
||||
while(!glfwWindowShouldClose(window)){
|
||||
tryUpdateMainFont(currentFontPath);
|
||||
shaderCatalog->update();
|
||||
|
||||
glfwPollEvents();
|
||||
|
||||
int width, height;
|
||||
glfwGetFramebufferSize(window, &width, &height);
|
||||
glViewport(0, 0, width, height);
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
GLuint location;
|
||||
|
||||
glm::mat4 projection = transform.getProjectionMatrix((float)width / height);
|
||||
glm::mat4 view = transform.getViewMatrix();
|
||||
glm::mat4 model = glm::mat4(1.0f);
|
||||
|
||||
{ // Draw background.
|
||||
GLuint program = backgroundShader->program;
|
||||
glUseProgram(program);
|
||||
glBindVertexArray(emptyVAO);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
// Uses premultiplied-alpha.
|
||||
glEnable(GL_BLEND);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
for(int i = N_OTHER_FONTS - 1; i >= 0; i--){
|
||||
if(otherFonts.size() > i){
|
||||
//std::cout << "drawing " << i << " with " << otherWghts[i] << std::endl;
|
||||
auto & otherFont = otherFonts[i];
|
||||
GLuint program = fontShader->program;
|
||||
glUseProgram(program);
|
||||
|
||||
otherFont->program = program;
|
||||
otherFont->drawSetup();
|
||||
|
||||
location = glGetUniformLocation(program, "projection");
|
||||
glUniformMatrix4fv(location, 1, false, glm::value_ptr(projection));
|
||||
location = glGetUniformLocation(program, "view");
|
||||
glUniformMatrix4fv(location, 1, false, glm::value_ptr(view));
|
||||
location = glGetUniformLocation(program, "model");
|
||||
glUniformMatrix4fv(location, 1, false, glm::value_ptr(model));
|
||||
|
||||
location = glGetUniformLocation(program, "color");
|
||||
float r = float(i) / N_OTHER_FONTS;
|
||||
float g = sin(42.143 * float(i) + otherWghts[i] * 0.0001) * 0.5 + 0.5;
|
||||
float b = cos(3.143 * float(i) + otherWghts[i] * 0.0005) * 0.1 + 0.1;
|
||||
glUniform4f(location, r, 1.0f - r, 1.0f - r, 1.0f);
|
||||
float z = (i + 1) * -0.1;
|
||||
location = glGetUniformLocation(program, "z");
|
||||
glUniform1f(location, z);
|
||||
|
||||
location = glGetUniformLocation(program, "antiAliasingWindowSize");
|
||||
glUniform1f(location, (float)antiAliasingWindowSize);
|
||||
location = glGetUniformLocation(program, "enableSuperSamplingAntiAliasing");
|
||||
glUniform1i(location, enableSuperSamplingAntiAliasing);
|
||||
location = glGetUniformLocation(program, "enableControlPointsVisualization");
|
||||
glUniform1i(location, enableControlPointsVisualization);
|
||||
|
||||
float cx = 0.5f * (bb.minX + bb.maxX);
|
||||
float cy = 0.5f * (bb.minY + bb.maxY);
|
||||
otherFont->draw(-cx, -cy, 0, mainText);
|
||||
glUseProgram(0);
|
||||
}
|
||||
}
|
||||
if(mainFont){
|
||||
GLuint program = fontShader->program;
|
||||
glUseProgram(program);
|
||||
|
||||
mainFont->program = program;
|
||||
mainFont->drawSetup();
|
||||
|
||||
location = glGetUniformLocation(program, "projection");
|
||||
glUniformMatrix4fv(location, 1, false, glm::value_ptr(projection));
|
||||
location = glGetUniformLocation(program, "view");
|
||||
glUniformMatrix4fv(location, 1, false, glm::value_ptr(view));
|
||||
location = glGetUniformLocation(program, "model");
|
||||
glUniformMatrix4fv(location, 1, false, glm::value_ptr(model));
|
||||
float z = 0;
|
||||
location = glGetUniformLocation(program, "z");
|
||||
glUniform1f(location, z);
|
||||
|
||||
location = glGetUniformLocation(program, "color");
|
||||
glUniform4f(location, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
||||
location = glGetUniformLocation(program, "antiAliasingWindowSize");
|
||||
glUniform1f(location, (float)antiAliasingWindowSize);
|
||||
location = glGetUniformLocation(program, "enableSuperSamplingAntiAliasing");
|
||||
glUniform1i(location, enableSuperSamplingAntiAliasing);
|
||||
location = glGetUniformLocation(program, "enableControlPointsVisualization");
|
||||
glUniform1i(location, enableControlPointsVisualization);
|
||||
|
||||
float cx = 0.5f * (bb.minX + bb.maxX);
|
||||
float cy = 0.5f * (bb.minY + bb.maxY);
|
||||
mainFont->draw(-cx, -cy, 0, mainText);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
if(helpFont && showHelp){
|
||||
GLuint program = fontShader->program;
|
||||
glUseProgram(program);
|
||||
|
||||
helpFont->program = program;
|
||||
helpFont->drawSetup();
|
||||
|
||||
glm::mat4 projection = glm::ortho(0.0f, (float)width, 0.0f, (float)height, -1.0f, 1.0f);
|
||||
glm::mat4 view = glm::mat4(1.0f);
|
||||
glm::mat4 model = glm::mat4(1.0f);
|
||||
|
||||
location = glGetUniformLocation(program, "projection");
|
||||
glUniformMatrix4fv(location, 1, false, glm::value_ptr(projection));
|
||||
location = glGetUniformLocation(program, "view");
|
||||
glUniformMatrix4fv(location, 1, false, glm::value_ptr(view));
|
||||
location = glGetUniformLocation(program, "model");
|
||||
glUniformMatrix4fv(location, 1, false, glm::value_ptr(model));
|
||||
|
||||
location = glGetUniformLocation(program, "color");
|
||||
float r = 200, g = 35, b = 220, a = 0.8;
|
||||
glUniform4f(location, r * a / 255.0f, g * a / 255.0f, b * a / 255.0f, a);
|
||||
|
||||
location = glGetUniformLocation(program, "antiAliasingWindowSize");
|
||||
glUniform1f(location, 1.0f);
|
||||
location = glGetUniformLocation(program, "enableSuperSamplingAntiAliasing");
|
||||
glUniform1i(location, true);
|
||||
location = glGetUniformLocation(program, "enableControlPointsVisualization");
|
||||
glUniform1i(location, false);
|
||||
|
||||
std::stringstream stream;
|
||||
stream << "Drag and drop a .ttf or .otf file to change the font\n";
|
||||
stream << "\n";
|
||||
stream << "right drag (or CTRL drag) - move\n";
|
||||
stream << "left drag - trackball rotate\n";
|
||||
stream << "middle drag - turntable rotate\n";
|
||||
stream << "scroll wheel - zoom\n";
|
||||
stream << "\n";
|
||||
stream << "0, 1, 2, 3 - change anti-aliasing window size: " << antiAliasingWindowSize << " pixel" << ((antiAliasingWindowSize != 1) ? "s" : "") << "\n";
|
||||
stream << glfwGetKeyName(GLFW_KEY_A, 0) << " - " << (enableSuperSamplingAntiAliasing ? "disable" : "enable") << " 2D anti-aliasing\n";
|
||||
stream << "(using another ray along the y-axis)\n";
|
||||
stream << glfwGetKeyName(GLFW_KEY_S, 0) << " - reset anti-aliasing settings\n";
|
||||
stream << glfwGetKeyName(GLFW_KEY_C, 0) << " - " << (enableControlPointsVisualization ? "disable" : "enable") << " control points\n";
|
||||
stream << glfwGetKeyName(GLFW_KEY_R, 0) << " - reset view\n";
|
||||
stream << glfwGetKeyName(GLFW_KEY_H, 0) << " - toggle help\n";
|
||||
|
||||
std::string helpText = stream.str();
|
||||
helpFont->prepareGlyphsForText(helpText);
|
||||
|
||||
float xscale, yscale;
|
||||
glfwGetWindowContentScale(window, &xscale, &yscale);
|
||||
helpFont->setWorldSize(std::ceil(helpFontBaseSize * yscale));
|
||||
|
||||
auto bb = helpFont->measure(0, 0, helpText);
|
||||
helpFont->draw(10 - bb.minX, height - 10 - bb.maxY, 0, helpText);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
// Clean up OpenGL resources before termination.
|
||||
mainFont = nullptr;
|
||||
helpFont = nullptr;
|
||||
|
||||
glfwTerminate();
|
||||
return 0;
|
||||
}
|
191
example/src/gpufont/shader_catalog.cpp
Normal file
191
example/src/gpufont/shader_catalog.cpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
#include "shader_catalog.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <defer.hpp>
|
||||
|
||||
//#include <glad/glad.h>
|
||||
|
||||
// UpdateList keeps track of which entries need to be updated.
|
||||
// The actual update is slightly delayed to avoid reading a partially written file.
|
||||
// It is threadsafe to allow safe communication with the asynchronous file watcher callback.
|
||||
class UpdateList {
|
||||
std::mutex mutex;
|
||||
std::unordered_map <std::string, std::chrono::steady_clock::time_point> updates;
|
||||
|
||||
public:
|
||||
void requestUpdate(const std::string & name){
|
||||
using namespace std::chrono_literals;
|
||||
std::lock_guard <std::mutex> guard(mutex);
|
||||
updates[name] = std::chrono::steady_clock::now() + 50ms;
|
||||
}
|
||||
|
||||
std::vector <std::string> collectDueUpdates(){
|
||||
std::lock_guard <std::mutex> guard(mutex);
|
||||
std::vector <std::string> result;
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
for(auto it = updates.begin(); it != updates.end();){
|
||||
if(it->second < now){
|
||||
result.push_back(it->first);
|
||||
it = updates.erase(it);
|
||||
}else{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class ShaderCatalog::Impl {
|
||||
private:
|
||||
std::string dir;
|
||||
std::unordered_map <std::string, std::shared_ptr <Entry> > entries;
|
||||
|
||||
UpdateList list;
|
||||
|
||||
public:
|
||||
Impl(const std::string & dir) : dir(dir){
|
||||
}
|
||||
|
||||
private:
|
||||
std::string readFile(const std::string & filename, std::string & error){
|
||||
std::ifstream stream(filename, std::ios::binary);
|
||||
if(!stream){
|
||||
error = "failed to open: " + filename;
|
||||
return "";
|
||||
}
|
||||
|
||||
stream.seekg(0, std::istream::end);
|
||||
size_t size = stream.tellg();
|
||||
stream.seekg(0, std::istream::beg);
|
||||
|
||||
std::string result = std::string(size, 0);
|
||||
stream.read(&result[0], size);
|
||||
if(!stream){
|
||||
error = "failed to read: " + filename;
|
||||
return "";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GLuint compile(const std::string & name, std::string & error){
|
||||
std::string vertexData = readFile(dir + "/" + name + ".vert", error);
|
||||
if(error != ""){
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string fragmentData = readFile(dir + "/" + name + ".frag", error);
|
||||
if(error != ""){
|
||||
return 0;
|
||||
}
|
||||
|
||||
GLint success = 0;
|
||||
|
||||
const char * vertexSource = vertexData.c_str();
|
||||
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||
defer {glDeleteShader(vertexShader);
|
||||
};
|
||||
glShaderSource(vertexShader, 1, &vertexSource, nullptr);
|
||||
glCompileShader(vertexShader);
|
||||
|
||||
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
|
||||
if(!success){
|
||||
char log[1024];
|
||||
GLsizei length = 0;
|
||||
glGetShaderInfoLog(vertexShader, sizeof(log), &length, log);
|
||||
error = "failed to compile vertex shader " + name + ":\n\n" + log;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char * fragmentSource = fragmentData.c_str();
|
||||
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
defer {glDeleteShader(fragmentShader);
|
||||
};
|
||||
glShaderSource(fragmentShader, 1, &fragmentSource, nullptr);
|
||||
glCompileShader(fragmentShader);
|
||||
|
||||
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
|
||||
if(!success){
|
||||
char log[1024];
|
||||
GLsizei length = 0;
|
||||
glGetShaderInfoLog(fragmentShader, sizeof(log), &length, log);
|
||||
error = "failed to compile fragment shader " + name + ":\n\n" + log;
|
||||
return 0;
|
||||
}
|
||||
|
||||
GLuint program = glCreateProgram();
|
||||
glAttachShader(program, vertexShader);
|
||||
glAttachShader(program, fragmentShader);
|
||||
glLinkProgram(program);
|
||||
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &success);
|
||||
if(!success){
|
||||
char log[1024];
|
||||
GLsizei length = 0;
|
||||
glGetProgramInfoLog(program, sizeof(log), &length, log);
|
||||
glDeleteProgram(program);
|
||||
error = "failed to compile program " + name + ":\n\n" + log;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
public:
|
||||
std::shared_ptr <Entry> get(const std::string & name){
|
||||
auto it = entries.find(name);
|
||||
if(it != entries.end()){
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::string error;
|
||||
GLuint program = compile(name, error);
|
||||
if(error != ""){
|
||||
std::cerr << "[shader] " << error << std::endl;
|
||||
}
|
||||
|
||||
auto entry = std::make_shared <Entry>(program);
|
||||
entries[name] = entry;
|
||||
return entry;
|
||||
}
|
||||
|
||||
void update(){
|
||||
std::vector <std::string> updates = list.collectDueUpdates();
|
||||
for(const std::string & name : updates){
|
||||
auto it = entries.find(name);
|
||||
if(it == entries.end()){
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string error;
|
||||
GLuint program = compile(name, error);
|
||||
if(error != ""){
|
||||
std::cerr << "[shader] " << error << std::endl;
|
||||
}else{
|
||||
std::cerr << "[shader] reloaded " << name << std::endl;
|
||||
glDeleteProgram(it->second->program);
|
||||
it->second->program = program;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ShaderCatalog::ShaderCatalog(const std::string & dir) : impl(std::make_unique <Impl>(dir)){
|
||||
}
|
||||
|
||||
ShaderCatalog::~ShaderCatalog(){
|
||||
}
|
||||
|
||||
std::shared_ptr <ShaderCatalog::Entry> ShaderCatalog::get(const std::string & name){
|
||||
return impl->get(name);
|
||||
}
|
||||
|
||||
void ShaderCatalog::update(){
|
||||
impl->update();
|
||||
}
|
32
example/src/gpufont/shader_catalog.hpp
Normal file
32
example/src/gpufont/shader_catalog.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "ofMain.h"
|
||||
|
||||
// A shader catalog loads and compiles shaders from a directory. Vertex and
|
||||
// fragment shaders are matched based on their filename (e.g. example.vert and
|
||||
// example.frag are loaded and linked together to form the "example" program).
|
||||
// Whenever a shader file changes on disk, the corresponding program is
|
||||
// recompiled and relinked.
|
||||
class ShaderCatalog {
|
||||
public:
|
||||
struct Entry {
|
||||
unsigned int program;
|
||||
|
||||
Entry() : program(0){
|
||||
}
|
||||
Entry(unsigned int program) : program(program){
|
||||
}
|
||||
};
|
||||
|
||||
ShaderCatalog(const std::string & dir);
|
||||
~ShaderCatalog();
|
||||
|
||||
std::shared_ptr <Entry> get(const std::string & name);
|
||||
void update();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr <Impl> impl;
|
||||
};
|
21
example/src/main.cpp
Normal file
21
example/src/main.cpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include "ofMain.h"
|
||||
#include "ofApp.h"
|
||||
|
||||
//========================================================================
|
||||
int main(){
|
||||
#ifdef OF_TARGET_OPENGLES
|
||||
ofGLESWindowSettings settings;
|
||||
//settings.setSize(1920, 1080);
|
||||
settings.glesVersion = 3;
|
||||
#else
|
||||
ofGLWindowSettings settings;
|
||||
settings.setSize(1920, 1080);
|
||||
settings.setGLVersion(3, 3);
|
||||
#endif
|
||||
ofCreateWindow(settings);
|
||||
|
||||
// this kicks off the running of my app
|
||||
// can be OF_WINDOW or OF_FULLSCREEN
|
||||
// pass in width and height too:
|
||||
ofRunApp(new ofApp());
|
||||
}
|
191
example/src/ofApp.cpp
Normal file
191
example/src/ofApp.cpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
#include "ofApp.h"
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::setup(){
|
||||
|
||||
mainText = R"DONE(Some things are hard to write about. Take soil,
|
||||
for instance. Soil, Oxford dictionary reads, “is the
|
||||
upper layer of earth in which plants grow, a black or
|
||||
dark brown material typically consisting of a mixture
|
||||
of organic remains, clay, and rock particles”.
|
||||
I wonder why I have chosen to write
|
||||
about soil, as I don’t seem to have had many
|
||||
encounters with it. Perhaps because of that?
|
||||
Intuiting its importance, but never minding it?
|
||||
So far it has appeared rather distant. Absent
|
||||
from my thoughts and words. But now, when
|
||||
I think of it, I see a charismatic substance.
|
||||
Soil, according to Merriam-Webster, is
|
||||
“1. firm land: earth;
|
||||
2. a) the upper layer of earth that may be dug
|
||||
or plowed and in which plants grow; and
|
||||
b) the superficial unconsolidated and
|
||||
usually weathered part of the mantle of
|
||||
a planet and especially of the earth”.
|
||||
|
||||
The Soil Science Society of America defines soil
|
||||
as “a mixture of minerals, dead and living
|
||||
organisms (organic materials), air, and water.”
|
||||
The “soil” entry in Encyclopaedia Britannica starts
|
||||
with “soil is the biologically active,
|
||||
porous medium […] serving as a reservoir of
|
||||
water and nutrients, as a medium for the filtration
|
||||
and breakdown of injurious wastes, and as
|
||||
a participant in the cycling of carbon and other
|
||||
elements through the global ecosystem.”
|
||||
Such nebulous definitions, though, aren’t
|
||||
most definitions unsettled? (all definitions are
|
||||
blasphemy!)… like “soil is (a mix of) all and nothing,
|
||||
doing anything and everything, everywhere”. How
|
||||
one defines or translates soil by posing a question
|
||||
always affects the answer. Soil can mean earth,
|
||||
ground, dirt, clay, turf, humus, silt, loam, land,
|
||||
clod, terra, territory, landscape, country, a political
|
||||
power base, an aspect of divinity, a terrain
|
||||
to cultivate, or a resource to be exploited… This
|
||||
is going to be difficult. I don’t know yet where to
|
||||
start. I hope to capture something essential.)DONE";
|
||||
|
||||
shaderCatalog = std::make_unique <ShaderCatalog>("data/ofxGPUFont/shaders/GL3");
|
||||
backgroundShader = shaderCatalog->get("background");
|
||||
fontShader = shaderCatalog->get("font");
|
||||
|
||||
{
|
||||
FT_Error error = FT_Init_FreeType(&library);
|
||||
if(error){
|
||||
std::cerr << "ERROR: failed to initialize FreeType" << std::endl;
|
||||
ofExit();
|
||||
}
|
||||
}
|
||||
|
||||
currentFontPath = "data/celines-fonts/Version-1-var.ttf";
|
||||
tryUpdateMainFont(library,
|
||||
currentFontPath,
|
||||
mainText,
|
||||
font,
|
||||
bb);
|
||||
|
||||
transform = Transform();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::update(){
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::draw(){
|
||||
GLuint location;
|
||||
|
||||
int width = ofGetWidth();
|
||||
int height = ofGetHeight();
|
||||
|
||||
glm::mat4 projection = transform.getProjectionMatrix((float)width / height);
|
||||
glm::mat4 view = transform.getViewMatrix();
|
||||
glm::mat4 model = glm::mat4(1.0f);
|
||||
|
||||
{ // Draw background.
|
||||
GLuint program = backgroundShader->program;
|
||||
glUseProgram(program);
|
||||
glBindVertexArray(emptyVAO);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
// Uses premultiplied-alpha.
|
||||
glEnable(GL_BLEND);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
if(font){
|
||||
GLuint program = fontShader->program;
|
||||
glUseProgram(program);
|
||||
|
||||
font->program = program;
|
||||
font->drawSetup();
|
||||
|
||||
location = glGetUniformLocation(program, "projection");
|
||||
glUniformMatrix4fv(location, 1, false, glm::value_ptr(projection));
|
||||
location = glGetUniformLocation(program, "view");
|
||||
glUniformMatrix4fv(location, 1, false, glm::value_ptr(view));
|
||||
location = glGetUniformLocation(program, "model");
|
||||
glUniformMatrix4fv(location, 1, false, glm::value_ptr(model));
|
||||
float z = 0;
|
||||
location = glGetUniformLocation(program, "z");
|
||||
glUniform1f(location, z);
|
||||
|
||||
location = glGetUniformLocation(program, "color");
|
||||
glUniform4f(location, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
||||
location = glGetUniformLocation(program, "antiAliasingWindowSize");
|
||||
glUniform1f(location, (float)antiAliasingWindowSize);
|
||||
location = glGetUniformLocation(program, "enableSuperSamplingAntiAliasing");
|
||||
glUniform1i(location, enableSuperSamplingAntiAliasing);
|
||||
location = glGetUniformLocation(program, "enableControlPointsVisualization");
|
||||
glUniform1i(location, enableControlPointsVisualization);
|
||||
|
||||
float cx = 0.5f * (bb.minX + bb.maxX);
|
||||
float cy = 0.5f * (bb.minY + bb.maxY);
|
||||
font->draw(-cx, -cy, 0, mainText);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::keyPressed(int key){
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::keyReleased(int key){
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::mouseMoved(int x, int y){
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::mouseDragged(int x, int y, int button){
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::mousePressed(int x, int y, int button){
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::mouseReleased(int x, int y, int button){
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::mouseEntered(int x, int y){
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::mouseExited(int x, int y){
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::windowResized(int w, int h){
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::gotMessage(ofMessage msg){
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::dragEvent(ofDragInfo dragInfo){
|
||||
|
||||
}
|
97
example/src/ofApp.h
Normal file
97
example/src/ofApp.h
Normal file
|
@ -0,0 +1,97 @@
|
|||
#pragma once
|
||||
|
||||
#include "ofMain.h"
|
||||
#include "ofxGPUFont.h"
|
||||
#include "gpufont/font.hpp"
|
||||
#include "gpufont/shader_catalog.hpp"
|
||||
|
||||
static std::unique_ptr <Font> loadFont(FT_Library & library, const std::string & filename, float worldSize = 1.0f, bool hinting = false){
|
||||
std::string error;
|
||||
FT_Face face = Font::loadFace(library, filename, error);
|
||||
if(error != ""){
|
||||
std::cerr << "[font] failed to load " << filename << ": " << error << std::endl;
|
||||
return std::unique_ptr <Font>{};
|
||||
}
|
||||
|
||||
return std::make_unique <Font>(face, worldSize, hinting);
|
||||
}
|
||||
|
||||
static void tryUpdateMainFont(FT_Library & library,
|
||||
const std::string & filename,
|
||||
const string & mainText,
|
||||
unique_ptr <Font> & mainFont,
|
||||
Font::BoundingBox & bb){
|
||||
{
|
||||
auto font = loadFont(library, filename, 0.05f);
|
||||
if(!font){
|
||||
return;
|
||||
}
|
||||
|
||||
font->dilation = 0.1f;
|
||||
|
||||
font->prepareGlyphsForText(mainText);
|
||||
|
||||
mainFont = std::move(font);
|
||||
bb = mainFont->measure(0, 0, mainText);
|
||||
}
|
||||
}
|
||||
class ofApp : public ofBaseApp {
|
||||
|
||||
public:
|
||||
struct Transform {
|
||||
float fovy = glm::radians(60.0f);
|
||||
float distance = 0.42f;
|
||||
glm::mat3 rotation = glm::mat3(1.0f);
|
||||
glm::vec3 position = glm::vec3(0.0f);
|
||||
|
||||
glm::mat4 getProjectionMatrix(float aspect){
|
||||
return glm::perspective( /* fovy = */ glm::radians(60.0f), aspect, 0.002f, 12.000f);
|
||||
}
|
||||
|
||||
glm::mat4 getViewMatrix(){
|
||||
auto translation = glm::translate(position);
|
||||
return glm::lookAt(glm::vec3(0, 0, distance), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0)) * glm::mat4(rotation) * translation;
|
||||
}
|
||||
};
|
||||
void setup();
|
||||
void update();
|
||||
void draw();
|
||||
|
||||
void keyPressed(int key);
|
||||
void keyReleased(int key);
|
||||
void mouseMoved(int x, int y);
|
||||
void mouseDragged(int x, int y, int button);
|
||||
void mousePressed(int x, int y, int button);
|
||||
void mouseReleased(int x, int y, int button);
|
||||
void mouseEntered(int x, int y);
|
||||
void mouseExited(int x, int y);
|
||||
void windowResized(int w, int h);
|
||||
void dragEvent(ofDragInfo dragInfo);
|
||||
void gotMessage(ofMessage msg);
|
||||
|
||||
unique_ptr <Font> font;
|
||||
FT_Library library;
|
||||
|
||||
// Empty VAO used when the vertex shader has no input and only uses gl_VertexID,
|
||||
// because OpenGL still requires a non-zero VAO to be bound for the draw call.
|
||||
GLuint emptyVAO;
|
||||
|
||||
std::unique_ptr <ShaderCatalog> shaderCatalog;
|
||||
std::shared_ptr <ShaderCatalog::Entry> backgroundShader;
|
||||
std::shared_ptr <ShaderCatalog::Entry> fontShader;
|
||||
|
||||
Font::BoundingBox bb;
|
||||
|
||||
Transform transform;
|
||||
|
||||
string currentFontPath;
|
||||
string mainText;
|
||||
|
||||
float helpFontBaseSize = 20.0f;
|
||||
|
||||
int antiAliasingWindowSize = 1;
|
||||
bool enableSuperSamplingAntiAliasing = true;
|
||||
bool enableControlPointsVisualization = false;
|
||||
|
||||
bool showHelp = true;
|
||||
};
|
18
src/glm.hpp
Normal file
18
src/glm.hpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/trigonometric.hpp>
|
||||
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include <glm/gtx/hash.hpp>
|
||||
#include <glm/gtx/string_cast.hpp>
|
||||
#include <glm/gtx/transform.hpp>
|
1
src/ofxGPUFont.h
Normal file
1
src/ofxGPUFont.h
Normal file
|
@ -0,0 +1 @@
|
|||
#pragma one
|
Loading…
Reference in a new issue