From 70eb4f97f4ab2fb3de1addc1b7ccd627870f97d9 Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Thu, 13 Apr 2023 17:15:35 +0200 Subject: [PATCH] GPUFont with layers --- src/AtlasLayerCombo.h | 7 +- src/GPUFontAtlasLayerCombo.cpp | 150 +++++++++++++++++++-------------- src/GPUFontAtlasLayerCombo.h | 16 ++-- src/LayerComposition.cpp | 89 ++++++++++++------- src/LayerComposition.h | 13 ++- src/MsdfAtlasLayerCombo.cpp | 22 ++++- src/MsdfAtlasLayerCombo.h | 10 ++- src/Utils.h | 38 +++++++++ 8 files changed, 236 insertions(+), 109 deletions(-) diff --git a/src/AtlasLayerCombo.h b/src/AtlasLayerCombo.h index cfcebd0..3003d34 100644 --- a/src/AtlasLayerCombo.h +++ b/src/AtlasLayerCombo.h @@ -19,6 +19,9 @@ namespace ofxVariableLab { +struct AtlasLayerComboSettings { +}; + struct ComboIdentifier { std::string fontPath = "data/celines-fonts/Version-2-var.ttf"; Layer::Type type = Layer::MSDFGEN; @@ -31,9 +34,11 @@ struct ComboIdentifier { class AtlasLayerCombo { public: - virtual void setup(const ComboIdentifier & identifier) = 0; + virtual void setup(const ComboIdentifier & identifier, + AtlasLayerComboSettings settings) = 0; virtual void update() = 0; virtual void careForChild(shared_ptr layer) = 0; + virtual void abandonChild(shared_ptr layer) = 0; virtual const ComboIdentifier & getIdentifier() const = 0; virtual void setVFlip(VFlipState vFlipState) = 0; diff --git a/src/GPUFontAtlasLayerCombo.cpp b/src/GPUFontAtlasLayerCombo.cpp index 87ce86b..ace80a3 100644 --- a/src/GPUFontAtlasLayerCombo.cpp +++ b/src/GPUFontAtlasLayerCombo.cpp @@ -1,5 +1,6 @@ #include "GPUFontAtlasLayerCombo.h" #include "Utils.h" +#include "font.hpp" #include "ofColor.h" #include "ofEvents.h" #include "ofGraphics.h" @@ -7,8 +8,10 @@ namespace ofxVariableLab { -void GPUFontAtlasLayerCombo::setup(const ComboIdentifier & identifier){ +void GPUFontAtlasLayerCombo::setup(const ComboIdentifier & identifier, + AtlasLayerComboSettings settings){ this->identifier = identifier; + this->settings = static_cast (settings); #ifdef TARGET_EMSCRIPTEN string shaderDir = "data/ofxGPUFont/shaders/DEBUG_ES3"; #else @@ -25,16 +28,14 @@ void GPUFontAtlasLayerCombo::setup(const ComboIdentifier & identifier){ ofExit(); } } - currentFontPath = "data/celines-fonts/Version-2-var.ttf"; - mainText = "whatever"; ofxGPUFont::tryUpdateMainFont(library, - currentFontPath, + this->identifier.fontPath, mainText, font, - bb); + this->settings.gpuTextureOffset); font->listFontVariationAxes(fontVariationAxesParameters, library); - cout << currentFontPath << " : fontVariationAxes :" << endl; + cout << this->identifier.fontPath << " : fontVariationAxes :" << endl; for(const auto & axis : fontVariationAxesParameters){ cout << std::fixed << std::setprecision(10) << axis.name << " :\n" << "\tmin: " << std::to_string(axis.minValue) @@ -49,29 +50,76 @@ void GPUFontAtlasLayerCombo::setup(const ComboIdentifier & identifier){ cout << "Render type " << r->getType() << endl; } void GPUFontAtlasLayerCombo::update(){ - //#ifndef TARGET_OPENGLES - //if(ofGetFrameNum() % 10 == 0){ - //shaderCatalog->requestUpdate("font"); - //}else if(ofGetFrameNum() % 10 == 5){ - //shaderCatalog->update(); + //float animationSpeed = 1.0; + //float threshold = 1.0; + //for(int i = 0; i < fontVariationAxes.size(); i++){ + //ofxGPUFont::Font::FontVariationAxis & axis = fontVariationAxes[i]; + //ofxGPUFont::Font::FontVariationAxisParameters & axisParams = fontVariationAxesParameters[i]; + //double newValue = ofMap(sin(ofGetElapsedTimef() * animationSpeed), + //-1, 1, + //axisParams.minValue, axisParams.maxValue); + //if(abs(newValue - axis.value) > threshold){ + //font->setFontVariationAxis(library, axis.name, newValue); + //axis.value = newValue; + //font->prepareGlyphsForText(mainText, true); //} - //#endif - float animationSpeed = 1.0; - float threshold = 1.0; - for(int i = 0; i < fontVariationAxes.size(); i++){ - ofxGPUFont::Font::FontVariationAxis & axis = fontVariationAxes[i]; - ofxGPUFont::Font::FontVariationAxisParameters & axisParams = fontVariationAxesParameters[i]; - double newValue = ofMap(sin(ofGetElapsedTimef() * animationSpeed), - -1, 1, - axisParams.minValue, axisParams.maxValue); - if(abs(newValue - axis.value) > threshold){ - font->setFontVariationAxis(library, axis.name, newValue); - axis.value = newValue; - font->prepareGlyphsForText(mainText, true); - } + //} + if(isDirty){ + //ofxGPUFont::tryUpdateMainFont(library, + //this->identifier.fontPath, + //mainText, + //font, + //this->settings.gpuTextureOffset); + font->prepareGlyphsForText(mainText, true); + isDirty = false; } } void GPUFontAtlasLayerCombo::careForChild(shared_ptr layer){ + if(layer->getType() == Layer::GPUFONT){ + shared_ptr gpuFontLayer = static_pointer_cast (layer); + layers.push_back(gpuFontLayer); + std::string oldText = mainText; + std::string layerText = gpuFontLayer->getProps().text; + std::string text = oldText + layerText; + totalCharacters += layerText.length(); + removeDuplicateCharacters(text); + // check if we added any characters + // TODO: check for variation variations + if(text.length() != oldText.length()){ + mainText = text; + isDirty = true; + } + }else{ + ofLogError("GPUFontAtlasLayerCombo::careForChild()") << __LINE__ << ": child is not recognized as Layer::GPUFONT"; + } +} +void GPUFontAtlasLayerCombo::abandonChild(shared_ptr layer){ + // TODO: handle removing child text from buffer + shared_ptr gpuFontLayer = static_pointer_cast (layer); + layers.erase( + std::remove_if( + layers.begin(), + layers.end(), + [gpuFontLayer](shared_ptr const & l) + { + return l == gpuFontLayer; + }), layers.end()); + + // update the text + // this doesn't happen very often, so let's just + // iterate over all layers and calculate fresh + std::string text = ""; + totalCharacters = 0; + for(const auto & layer : layers){ + std::string layerText = layer->getProps().text; + totalCharacters += layerText.length(); + text += layerText; + } + removeDuplicateCharacters(text); + mainText = text; + // NOTE: we do not want to update buffers now, + // as it's probably more costly than just keeping + // the old glyphs around... } const ComboIdentifier & GPUFontAtlasLayerCombo::getIdentifier() const { return identifier; @@ -89,13 +137,6 @@ void GPUFontAtlasLayerCombo::draw(){ int height = ofGetHeight(); glm::vec2 mouse = glm::vec2(ofGetMouseX(), ofGetMouseY()); - float targetFontSize = 128; - - float width_unit = 1; - float height_unit = (float)height / width; - - //ofClear(125, 255, 125, 255); - //glm::mat4 projection = transform.getProjectionMatrix((float)width / height); glm::mat4 projection = transform.getOrthoProjectionMatrix(width, height, @@ -158,37 +199,20 @@ void GPUFontAtlasLayerCombo::draw(){ //mouse.y = 0; std::vector vertices; std::vector indices; - string text = "whatever"; - int n_texts = 240; - vertices.resize(text.length() * n_texts * 4); - indices.resize(text.length() * n_texts * 6); - cx = 0.5f * (bb.minX + bb.maxX); - cy = 0.5f * (bb.minY + bb.maxY); - for(int i = 0; i < n_texts - 1; i++){ - float pipi = ((PI * 2 * i) / (n_texts - 1)); - float shift = (400 * cos(ofGetElapsedTimef() * 0.125 + pipi)); - float shift_x = (600 * sin(ofGetElapsedTimef() * 0.125 + pipi)); - float shift_o = (100 * sin(ofGetElapsedTimef() * 0.4 + pipi)); - font->collectVerticesAndIndices( - glm::vec3(mouse.x + shift_x, - mouse.y + shift + shift_o, - 0), - text, - vertices, - indices, - true, - int(ofMap(sin(ofGetElapsedTimef() + pipi), -1, 1, 42, 96))); + for(const auto & layer : layers){ + font->collectVerticesAndIndices(glm::vec3(layer->getProps().x, + layer->getProps().y, + 0), + layer->getProps().text, + vertices, indices, true, + layer->getProps().fontSize_px); } - font->collectVerticesAndIndices( - glm::vec3(mouse.x, - mouse.y, - 0), - text, - vertices, - indices, - true, - ofMap(sin(ofGetElapsedTimef() * 0.25f), -1.0f, 1.0f, 42.0f, 96.0f)); + vertices.resize(totalCharacters * 4); + indices.resize(totalCharacters * 6); + //cx = 0.5f * (bb.minX + bb.maxX); + //cy = 0.5f * (bb.minY + bb.maxY); + font->draw(vertices, indices); //ofRectangle rectangle(0, 0, width, height); //ofDrawRectangle(rectangle); @@ -208,9 +232,7 @@ void GPUFontAtlasLayerCombo::draw(){ ofDrawBitmapStringHighlight( "fps: " + ofToString(ofGetFrameRate()) + "\n" - + "font: " + currentFontPath + "\n" - + "cx: " + ofToString(cx) + "\n" - + "cy: " + ofToString(cy) + "\n" + + "font: " + this->identifier.fontPath + "\n" , 20, 20); } diff --git a/src/GPUFontAtlasLayerCombo.h b/src/GPUFontAtlasLayerCombo.h index 62d15cd..2bcb974 100644 --- a/src/GPUFontAtlasLayerCombo.h +++ b/src/GPUFontAtlasLayerCombo.h @@ -10,6 +10,9 @@ #endif namespace ofxVariableLab { +struct GPUFontAtlasLayerComboSettings : public AtlasLayerComboSettings { + int gpuTextureOffset = 0; +}; class GPUFontAtlasLayerCombo : public AtlasLayerCombo { struct Transform { enum Origin { @@ -53,9 +56,11 @@ class GPUFontAtlasLayerCombo : public AtlasLayerCombo { } }; public: - void setup(const ComboIdentifier & identifier) override; + void setup(const ComboIdentifier & identifier, + AtlasLayerComboSettings settings = GPUFontAtlasLayerComboSettings()) override; void update() override; void careForChild(shared_ptr layer) override; + void abandonChild(shared_ptr layer) override; const ComboIdentifier & getIdentifier() const override; void setVFlip(VFlipState vFlipState) override; @@ -63,8 +68,11 @@ class GPUFontAtlasLayerCombo : public AtlasLayerCombo { void draw(); shared_ptr font; + string mainText = ""; + bool isDirty = false; private: + GPUFontAtlasLayerComboSettings settings; FT_Library library; vector > layers; ComboIdentifier identifier; @@ -76,12 +84,8 @@ class GPUFontAtlasLayerCombo : public AtlasLayerCombo { //std::shared_ptr backgroundShader; std::shared_ptr fontShader; - ofxGPUFont::Font::BoundingBox bb; - Transform transform; - string currentFontPath; - string mainText; // Size of the window (in pixels) used for 1-dimensional anti-aliasing along each rays. // 0 - no anti-aliasing // 1 - normal anti-aliasing @@ -97,7 +101,7 @@ class GPUFontAtlasLayerCombo : public AtlasLayerCombo { std::vector fontVariationAxes; VFlipState vFlip; - bool isDirty = false; + int totalCharacters = 0; }; } diff --git a/src/LayerComposition.cpp b/src/LayerComposition.cpp index 5116bb2..db8be95 100644 --- a/src/LayerComposition.cpp +++ b/src/LayerComposition.cpp @@ -1,5 +1,6 @@ #include "LayerComposition.h" #include "Atlas.h" +#include "GPUFontAtlasLayerCombo.h" #include "MsdfLayer.h" #include "Utils.h" #include "ofUtils.h" @@ -8,10 +9,10 @@ namespace ofxVariableLab { void LayerComposition::setup(){ - auto combo = make_shared (); - ComboIdentifier comboIdentifier = {"lol", Layer::GPUFONT}; - combo->setup(comboIdentifier); - atlasLayerCombos[comboIdentifier] = combo; + //auto combo = make_shared (); + //ComboIdentifier comboIdentifier = {"lol", Layer::GPUFONT}; + //combo->setup(comboIdentifier); + //atlasLayerCombos[comboIdentifier] = combo; } void LayerComposition::update(){ @@ -35,40 +36,66 @@ LayerID LayerComposition::addLayer(const ComboIdentifier & identifier, const Layer::Props & props, const std::vector & variations){ string layerID = ""; - if(atlasLayerCombos.count(identifier) <= 0){ - switch(identifier.type){ - case Layer::GPUFONT: { - ofLogError("LayerComposition::addLayer") - << "GPUFont not implemented" << endl; - break; + switch(identifier.type){ + case Layer::GPUFONT: { + shared_ptr combo; + auto comboIterator = atlasLayerCombos.find(identifier); + if(comboIterator == atlasLayerCombos.end()){ + // we don't have one yet + // so let's create it + combo = make_shared (); + GPUFontAtlasLayerComboSettings settings; + settings.gpuTextureOffset = nextGpuTextureOffset; + combo->setup(identifier, settings); + nextGpuTextureOffset++; + atlasLayerCombos[identifier] = combo; + }else{ + // use existing combo + combo = dynamic_pointer_cast (comboIterator->second); } - default: - case Layer::MSDFGEN: { - // TODO: put most stuff in combo setup - auto combo = make_shared (); - combo->setup(identifier); // TODO: add here text and variations - auto layer = make_shared (); - layer->vFlip = vFlipState; - layer->setProps(props); - layer->setup(); - std::vector msdfVariations; - for(const auto & v : variations){ - msdfVariations.push_back({v.name, v.value}); - } - // TODO: do not add Variation to atlas, but to combo, - // so we know it's dirty - combo->atlas->addVariations(msdfVariations); - combo->careForChild(layer); - layers[layer->getId()] = layer; - atlasLayerCombos[identifier] = std::move(combo); - layerID = layer->getId(); + auto layer = make_shared (); + layer->setProps(props); + combo->careForChild(layer); + layerID = layer->getId(); + layers[layerID] = layer; + + break; + } + + default: + case Layer::MSDFGEN: { + // TODO: put most stuff in combo setup + auto combo = make_shared (); + combo->setup(identifier); // TODO: add here text and variations + auto layer = make_shared (); + layer->vFlip = vFlipState; + layer->setProps(props); + layer->setup(); + std::vector msdfVariations; + for(const auto & v : variations){ + msdfVariations.push_back({v.name, v.value}); } - } + // TODO: do not add Variation to atlas, but to combo, + // so we know it's dirty + combo->atlas->addVariations(msdfVariations); + combo->careForChild(layer); + layers[layer->getId()] = layer; + atlasLayerCombos[identifier] = std::move(combo); + layerID = layer->getId(); + } } return layerID; } +void LayerComposition::removeLayer(const LayerID & id){ + auto layer = getLayer(id); + for(auto & it : atlasLayerCombos){ + it.second->abandonChild(layer); + } + layers.erase(id); +} + shared_ptr LayerComposition::getLayer(const LayerID & layerID){ return layers[layerID]; } diff --git a/src/LayerComposition.h b/src/LayerComposition.h index 6c7a489..61672de 100644 --- a/src/LayerComposition.h +++ b/src/LayerComposition.h @@ -6,6 +6,7 @@ #include "MsdfLayer.h" #include "GPUFontAtlasLayerCombo.h" #include "GPUFontLayer.h" +#include "Utils.h" namespace ofxVariableLab { @@ -20,14 +21,22 @@ class LayerComposition { LayerID addLayer(const ComboIdentifier & identifier, const Layer::Props & props, const std::vector & variations); - shared_ptr getLayer(const LayerID & layerID); + void removeLayer(const LayerID & id); // TODO: make bool, to catch nonexisting + shared_ptr getLayer(const LayerID & layerID); const unordered_map > & getAtlasLayerCombos() const; void setVFlip(bool vFlip); private: VFlipState vFlipState = V_FLIP_UNKNOWN; - unordered_map > layers; + unordered_map > layers; unordered_map > atlasLayerCombos; + + /** + * @brief lastGpuTextureOffset GL_TEXTURE0 + offset + * we want to different textures for different fonts + * this value is incremented/decremented when adding/removing GPUFont layers + */ + int nextGpuTextureOffset = 0; //unordered_map > layers; }; diff --git a/src/MsdfAtlasLayerCombo.cpp b/src/MsdfAtlasLayerCombo.cpp index 0fd33db..a0ac914 100644 --- a/src/MsdfAtlasLayerCombo.cpp +++ b/src/MsdfAtlasLayerCombo.cpp @@ -1,16 +1,19 @@ #include "MsdfAtlasLayerCombo.h" +#include "AtlasLayerCombo.h" #include "Utils.h" namespace ofxVariableLab { -void MsdfAtlasLayerCombo::setup(const ComboIdentifier & layerIdentifier){ +void MsdfAtlasLayerCombo::setup(const ComboIdentifier & layerIdentifier, + AtlasLayerComboSettings settings){ this->identifier = layerIdentifier; + this->settings = static_cast (settings); ofxMsdfgen::AtlasSettings atlasSettings; //settings.characters = "ABCDEFGHIJKL"; - atlasSettings.scale = 64; - atlasSettings.minimumScale = 64; + atlasSettings.scale = this->settings.scale; + atlasSettings.minimumScale = this->settings.minimumScale; atlasSettings.characters = ""; - atlasSettings.maxInterpolationStepSize = 50.0; + atlasSettings.maxInterpolationStepSize = this->settings.maxInterpolationStepSize; atlas = make_shared (); @@ -45,6 +48,17 @@ void MsdfAtlasLayerCombo::careForChild(shared_ptr layer){ } layers.push_back(msdfLayer); } +void MsdfAtlasLayerCombo::abandonChild(shared_ptr layer){ + shared_ptr msdfLayer = dynamic_pointer_cast (layer); + layers.erase( + std::remove_if( + layers.begin(), + layers.end(), + [msdfLayer](shared_ptr const & l) + { + return l == msdfLayer; + }), layers.end()); +} const ComboIdentifier & MsdfAtlasLayerCombo::getIdentifier() const { return identifier; diff --git a/src/MsdfAtlasLayerCombo.h b/src/MsdfAtlasLayerCombo.h index 6615eff..8b5b11d 100644 --- a/src/MsdfAtlasLayerCombo.h +++ b/src/MsdfAtlasLayerCombo.h @@ -5,11 +5,18 @@ #include "Utils.h" namespace ofxVariableLab { +struct MsdfAtlasLayerComboSettings : public AtlasLayerComboSettings { + float scale = 64; + float minimumScale = 64; + float maxInterpolationStepSize = 50.0; +}; class MsdfAtlasLayerCombo : public AtlasLayerCombo { public: - void setup(const ComboIdentifier & identifier) override; + void setup(const ComboIdentifier & identifier, + AtlasLayerComboSettings settings = MsdfAtlasLayerComboSettings()) override; void update() override; void careForChild(shared_ptr layer) override; + void abandonChild(shared_ptr layer) override; const ComboIdentifier & getIdentifier() const override; void setVFlip(VFlipState vFlipState) override; const ofImage & getAtlasImage(); @@ -18,6 +25,7 @@ class MsdfAtlasLayerCombo : public AtlasLayerCombo { shared_ptr atlas; private: + MsdfAtlasLayerComboSettings settings; vector > layers; vector glyphGeometries; shared_ptr msdfShader; diff --git a/src/Utils.h b/src/Utils.h index fbaff93..765d374 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include namespace ofxVariableLab { @@ -22,6 +24,42 @@ inline void hash_combine(std::size_t & seed, const T & v){ std::hash hasher; seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } +//template +//ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPredicate p){ +//first = std::find_if(first, last, p); +//if(first != last){ +//for(ForwardIt i = first; ++i != last;){ +//if(!p(*i)){ +//*first++ = std::move(*i); +//} +//} +//} +//return first; +//} + +inline void removeDuplicateCharacters(std::string & str){ + std::set chars; + + str.erase( + std::remove_if( + str.begin(), + str.end(), + [&chars](char i){ + // If encountered character, remove this one. + if(chars.count(i)){ + return true; + } + + // Otherwise, mark this character encountered and don't remove. + chars.insert(i); + return false; + } + ), + str.end() + ); + +} + enum VFlipState { V_FLIP_UNKNOWN = 0, V_FLIP_ON,