SpriteGles.h#

Parent directory (OpenGLES)

Contains the Sprite classes and framework objects used by the UIRenderer (Sprite, Text, Image, Font, Group).

Includes#

  • PVRCore/math/AxisAlignedBox.h

  • PVRCore/math/Rectangle.h

  • PVRUtils/OpenGLES/BindingsGles.h

Included By#

Namespaces#

Classes#

Enums#

Typedefs#

Source Code#

#pragma once
#if SC_ENABLED
#include "PVRUtils/OpenGLSC/BindingsGlsc.h"
#else
#include "PVRUtils/OpenGLES/BindingsGles.h"
#endif
#include "PVRCore/math/Rectangle.h"
#include "PVRCore/math/AxisAlignedBox.h"

#define NUM_BITS_GROUP_ID 8

namespace pvr {
class Texture;

typedef Rectangle<int32_t> Rectanglei;

namespace ui {
class UIRenderer;
namespace impl {
class Font_;
class Text_;
class TextElement_;
class Image_;
class Group_;
class MatrixGroup_;
class PixelGroup_;
class Sprite_;
} // namespace impl

typedef std::shared_ptr<impl::Group_> Group;

typedef std::shared_ptr<impl::MatrixGroup_> MatrixGroup;

typedef std::shared_ptr<impl::PixelGroup_> PixelGroup;

typedef std::shared_ptr<impl::Sprite_> Sprite;

typedef std::weak_ptr<impl::Sprite_> SpriteWeakRef;

typedef std::shared_ptr<impl::Text_> Text;

typedef std::weak_ptr<impl::Text_> TextWeakRef;

typedef std::shared_ptr<impl::Font_> Font;

typedef std::weak_ptr<impl::Font_> FontWeakRef;

typedef std::shared_ptr<impl::TextElement_> TextElement;

typedef std::weak_ptr<impl::TextElement_> TextElementWeakRef;

typedef std::shared_ptr<impl::Image_> Image;

typedef std::weak_ptr<impl::Image_> ImageWeakRef;

enum class Anchor
{
    TopLeft,
    TopCenter,
    TopRight,
    CenterLeft,
    Center,
    CenterRight,
    BottomLeft,
    BottomCenter,
    BottomRight
};

namespace impl {
class Sprite_
{
private:
    friend class pvr::ui::UIRenderer;
    friend class pvr::ui::impl::Group_;
    friend class pvr::ui::impl::Font_;
    friend class pvr::ui::impl::PixelGroup_;
    friend class pvr::ui::impl::MatrixGroup_;
    friend class pvr::ui::impl::Image_;
    friend class pvr::ui::impl::TextElement_;
    friend class pvr::ui::impl::Text_;

    Sprite_(UIRenderer& uiRenderer);

    virtual void calculateMvp(uint64_t parentIds, glm::mat4 const& srt, const glm::mat4& viewProj, pvr::Rectanglei const& viewport) const = 0;

    /********************************************************************************************************
    \brief    Do not call directly. Render will call this function.
    If writing new sprites, implement this function to render a sprite with the provided
    transformation matrix. Necessary to support instanced rendering of the same sprite from
    different groups.
    ********************************************************************************************************/
    virtual void onRender(uint64_t parentId) const { (void)parentId; }

protected:
    void setUIRenderer(UIRenderer* uiRenderer) { _uiRenderer = uiRenderer; }

    mutable math::AxisAlignedBox _boundingRect;

    mutable glm::vec4 _color;

    mutable int32_t _alphaMode;

    UIRenderer* _uiRenderer;

    mutable glm::mat4 _cachedMatrix;

    glm::mat4 _viewProj;

public:
    virtual void commitUpdates() const;

    virtual ~Sprite_() {}

    glm::vec2 getDimensions() const { return glm::vec2(_boundingRect.getSize()); }

    void render() const;

    void setAlphaRenderingMode(bool isAlphaOnly) const { _alphaMode = isAlphaOnly; }

    void setColor(glm::vec4 color) const { _color = color; }

    void setColor(uint32_t r, uint32_t g, uint32_t b, uint32_t a) const
    {
        _color[0] = static_cast<float>(r / 255.f);
        _color[1] = static_cast<float>(g / 255.f);
        _color[2] = static_cast<float>(b / 255.f);
        _color[3] = static_cast<float>(a / 255.f);
    }

    void setColor(float r, float g, float b, float a) const
    {
        _color.r = r;
        _color.g = g;
        _color.b = b;
        _color.a = a;
    }

    void setColor(uint32_t rgba) const
    {
        _color[0] = static_cast<float>(rgba & 0xFF) / 255.f;
        _color[1] = static_cast<float>(rgba >> 8 & 0xFF) / 255.f;
        _color[2] = static_cast<float>(rgba >> 16 & 0xFF) / 255.f;
        _color[3] = static_cast<float>(rgba >> 24 & 0xFF) / 255.f;
    }

    const glm::vec4& getColor() const { return _color; }

    bool getAlphaRenderingMode() const { return _alphaMode == 1; }

    const glm::mat4& getMatrix() const { return _cachedMatrix; }

    math::AxisAlignedBox const& getBoundingBox() const { return _boundingRect; }

    virtual glm::vec2 getScaledDimension() const = 0;
};

class I2dComponent
{
private:
    friend class ::pvr::ui::UIRenderer;
    friend class ::pvr::ui::impl::PixelGroup_;
    friend class ::pvr::ui::impl::Text_;
    friend class ::pvr::ui::impl::Image_;

    I2dComponent()
        : _anchor(Anchor::Center), _position(0.f, 0.f), _scale(1.f, 1.f), _rotation(0.f), _isPositioningDirty(true), _pixelOffset(0, 0), _uv(0.0f, 0.0f, 1.0, 1.0f), _isUVDirty(true)
    {}

protected:
    mutable Anchor _anchor;

    mutable glm::vec2 _position;

    mutable glm::vec2 _scale;

    mutable float _rotation;

    mutable bool _isPositioningDirty;

    mutable glm::vec2 _pixelOffset;

    mutable Rectanglef _uv;

    mutable bool _isUVDirty;

public:
    virtual ~I2dComponent() {}

    I2dComponent const* setAnchor(Anchor anchor, const glm::vec2& ndcPos)
    {
        setAnchor(anchor, ndcPos.x, ndcPos.y);
        return this;
    }

    I2dComponent const* setAnchor(Anchor anchor, float ndcPosX = -1.f, float ndcPosY = -1.f) const
    {
        _anchor = anchor;
        _position.x = ndcPosX;
        _position.y = ndcPosY;
        _isPositioningDirty = true;
        return this;
    }

    I2dComponent const* setPixelOffset(float offsetX, float offsetY) const
    {
        _pixelOffset.x = offsetX, _pixelOffset.y = offsetY;
        _isPositioningDirty = true;
        return this;
    }

    I2dComponent const* setPixelOffset(glm::vec2 offset) const { return setPixelOffset(offset.x, offset.y); }

    I2dComponent const* setScale(glm::vec2 const& scale) const
    {
        _scale = scale;
        _isPositioningDirty = true;
        return this;
    }

    I2dComponent const* setScale(float scaleX, float scaleY) const
    {
        _scale = glm::vec2(scaleX, scaleY);
        _isPositioningDirty = true;
        return this;
    }

    I2dComponent const* setRotation(float radians) const
    {
        _rotation = radians;
        _isPositioningDirty = true;
        return this;
    }

    I2dComponent const* setUV(const pvr::Rectanglef& uv) const
    {
        _uv = uv;
        _isUVDirty = true;
        return this;
    }
};

class Image_ : public Sprite_, public I2dComponent
{
private:
    friend class pvr::ui::UIRenderer;

    class make_shared_enabler
    {
    protected:
        make_shared_enabler() {}
        friend class Image_;
    };

    static Image constructShared(UIRenderer& uiRenderer, const GLuint& texture, uint32_t width, uint32_t height, bool useMipmapped, const GLuint& sampler)
    {
        return std::make_shared<Image_>(make_shared_enabler{}, uiRenderer, texture, width, height, useMipmapped, sampler);
    }

    void calculateMvp(uint64_t parentIds, glm::mat4 const& srt, const glm::mat4& viewProj, pvr::Rectanglei const& viewport) const;

    void onRender(uint64_t parentId) const;

protected:
    struct MvpData
    {
        glm::mat4 mvp;

        MvpData() {}
    };

    uint32_t _texW;

    uint32_t _texH;

    GLuint _texture;

    GLuint _sampler;

    mutable std::map<uint64_t, MvpData> _mvpData;

    mutable bool _isTextureDirty;

public:
    Image_(make_shared_enabler, UIRenderer& uiRenderer, const GLuint& texture, uint32_t width, uint32_t height, bool useMipmapped, const GLuint& sampler);

    virtual ~Image_() {}

    uint32_t getWidth() const { return _texW; }

    uint32_t getHeight() const { return _texH; }

    const GLuint& getTexture() const { return _texture; }

    GLuint& getTexture() { return _texture; }

    const GLuint& getSampler() const { return _sampler; }

    GLuint& getSampler() { return _sampler; }

    glm::vec2 getScaledDimension() const { return getDimensions() * _scale; }
};

class Font_
{
public:
    struct CharacterUV
    {
        float ul;
        float vt;
        float ur;
        float vb;
    };

    struct CharMetrics
    {
        int16_t xOff;
        uint16_t characterWidth;
    };

    enum
    {
        InvalidChar = 0xFDFDFDFD,
        FontHeader = 0xFCFC0050,
        FontCharList = 0xFCFC0051,
        FontRects = 0xFCFC0052,
        FontMetrics = 0xFCFC0053,
        FontYoffset = 0xFCFC0054,
        FontKerning = 0xFCFC0055,
        MaxRenderableLetters = 0xFFFF >> 2,
        FontElement = MaxRenderableLetters * 6,
    };

private:
    friend class ::pvr::ui::UIRenderer;

    class make_shared_enabler
    {
    protected:
        make_shared_enabler() {}
        friend class Font_;
    };

    static Font constructShared(UIRenderer& uiRenderer, const GLuint& tex2D, const Texture& tex, const GLuint& sampler)
    {
        return std::make_shared<Font_>(make_shared_enabler{}, uiRenderer, tex2D, tex, sampler);
    }

    static int32_t characterCompFunc(const void* a, const void* b);
    static int32_t kerningCompFunc(const void* a, const void* b);

    void setUIRenderer(UIRenderer* uiRenderer) { _uiRenderer = uiRenderer; }

    struct Header // 12 bytes
    {
        uint8_t version;
        uint8_t spaceWidth;
        int16_t numCharacters;
        int16_t numKerningPairs;
        int16_t ascent;
        int16_t lineSpace;
        int16_t borderWidth;
    } _header;
#pragma pack(push, 4) // force 4byte alignment
    struct KerningPair
    {
        uint64_t pair;
        int32_t offset;
    };
#pragma pack(pop)
    std::vector<uint32_t> _characters;
    std::vector<KerningPair> _kerningPairs;
    std::vector<CharMetrics> _charMetrics;
    std::vector<CharacterUV> _characterUVs;
    std::vector<Rectanglei> _rects;
    std::vector<int32_t> _yOffsets;
    GLuint _texture;
    GLuint _sampler;
    glm::uvec2 _dim;
    uint32_t _alphaRenderingMode;
    UIRenderer* _uiRenderer;

public:
    Font_(make_shared_enabler, UIRenderer& uiRenderer, const GLuint& tex2D, const Texture& tex, const GLuint& sampler);

    void loadFontData(const Texture& texture);

    uint32_t findCharacter(uint32_t character) const;

    void applyKerning(uint32_t charA, uint32_t charB, float& offset);

    const CharMetrics& getCharMetrics(uint32_t index) const { return _charMetrics[index]; }

    const CharacterUV& getCharacterUV(uint32_t index) const { return _characterUVs[index]; }

    const Rectanglei& getRectangle(uint32_t index) const { return _rects[index]; }

    int16_t getFontLineSpacing() const { return _header.lineSpace; }

    int16_t getAscent() const { return _header.ascent; }

    uint8_t getSpaceWidth() const { return _header.spaceWidth; }

    int32_t getYOffset(uint32_t index) const { return _yOffsets[index]; }

    bool isAlphaRendering() const { return _alphaRenderingMode != 0; }

    GLuint getSampler() const { return _sampler; }

    GLuint getTexture() const { return _texture; }
};

struct Vertex
{
    float x;
    float y;
    float z;
    float rhw;

    float tu;
    float tv;

    void setData(float inX, float inY, float inZ, float inRhw, float inU, float inV)
    {
        this->x = inX;
        this->y = inY;
        this->z = inZ;
        this->rhw = inRhw;
        this->tu = inU;
        this->tv = inV;
    }
};

class TextElement_
{
private:
    friend class ::pvr::ui::impl::Text_;
    friend class ::pvr::ui::UIRenderer;

    class make_shared_enabler
    {
    protected:
        make_shared_enabler() {}
        friend class TextElement_;
    };

    static TextElement constructShared(UIRenderer& uiRenderer, const Font& font) { return std::make_shared<TextElement_>(make_shared_enabler{}, uiRenderer, font); }

    static TextElement constructShared(UIRenderer& uiRenderer, const std::wstring& str, const Font& font)
    {
        return std::make_shared<TextElement_>(make_shared_enabler{}, uiRenderer, str, font);
    }

    static TextElement constructShared(UIRenderer& uiRenderer, const std::string& str, const Font& font)
    {
        return std::make_shared<TextElement_>(make_shared_enabler{}, uiRenderer, str, font);
    }

    bool updateText() const
    {
        if (_isTextDirty)
        {
            regenerateText();
            updateVbo();
            _isTextDirty = false;
            return true;
        }
        return false;
    }

    void setUIRenderer(UIRenderer* uiRenderer) { _uiRenderer = uiRenderer; }

    void regenerateText() const;
    void updateVbo() const;
    void onRender() const;

    uint32_t updateVertices(float fZPos, float xPos, float yPos, const std::vector<uint32_t>& text, Vertex* const pVertices) const;

    bool _isUtf8;
    mutable bool _isTextDirty;
    mutable Font _font;
    mutable GLuint _vbo;
    mutable bool _vboCreated;
    mutable uint32_t _vboSize;
    mutable std::string _textStr;
    mutable std::wstring _textWStr;
    mutable std::vector<uint32_t> _utf32;
    mutable std::vector<Vertex> _vertices;
    mutable int32_t _numCachedVerts;
    UIRenderer* _uiRenderer;
    mutable math::AxisAlignedBox _boundingRect; //< Bounding rectangle of the sprite
public:
    TextElement_(make_shared_enabler, UIRenderer& uiRenderer, const Font& font)
        : _isTextDirty(false), _font(font), _vbo(static_cast<GLuint>(-1)), _vboCreated(false), _uiRenderer(&uiRenderer), _isUtf8(false)
    {}

    TextElement_(make_shared_enabler, UIRenderer& uiRenderer, const std::string& str, const Font& font)
        : _font(font), _vbo(static_cast<GLuint>(-1)), _vboCreated(false), _uiRenderer(&uiRenderer)
    {
        setText(str);
        updateText();
    }

    TextElement_(make_shared_enabler, UIRenderer& uiRenderer, const std::wstring& str, const Font& font)
        : _font(font), _vbo(static_cast<uint32_t>(-1)), _vboCreated(false), _uiRenderer(&uiRenderer)
    {
        setText(str);
        updateText();
    }

    enum
    {
        MaxLetters = 5120
    };

    glm::vec2 getDimensions() const { return glm::vec2(_boundingRect.getSize()); }

    math::AxisAlignedBox const& getBoundingBox() const { return _boundingRect; }

    TextElement_& setText(const std::string& str);

    TextElement_& setText(std::string&& str);

    TextElement_& setText(const std::wstring& str);

    TextElement_& setText(std::wstring&& str);

    const std::string& getString() const { return _textStr; }

    const std::wstring& getWString() const { return _textWStr; }

    const Font& getFont() const { return _font; }
};

class Text_ : public Sprite_, public I2dComponent
{
private:
    friend class pvr::ui::UIRenderer;

    class make_shared_enabler
    {
    protected:
        make_shared_enabler() {}
        friend class Text_;
    };

    static Text constructShared(UIRenderer& uiRenderer, const TextElement& textElement) { return std::make_shared<Text_>(make_shared_enabler{}, uiRenderer, textElement); }

    struct MvpData
    {
        glm::mat4 mvp;
    };

    void calculateMvp(uint64_t parentIds, glm::mat4 const& srt, const glm::mat4& viewProj, pvr::Rectanglei const& viewport) const;

    void onRender(uint64_t parentId) const;

    mutable TextElement _textElement;
    mutable std::map<uint64_t, MvpData> _mvpData;

public:
    Text_(make_shared_enabler, UIRenderer& uiRenderer, const TextElement& textElement);

    virtual ~Text_() {}

    const Font getFont() const { return getTextElement()->getFont(); }

    TextElement getTextElement() { return _textElement; }

    const TextElement getTextElement() const { return _textElement; }

    glm::vec2 getScaledDimension() const { return getDimensions() * _scale; }

    Text_& setText(const std::string& str)
    {
        getTextElement()->setText(str);
        return *this;
    }

    Text_& setText(std::string&& str)
    {
        getTextElement()->setText(std::forward<std::string>(str));
        return *this;
    }

    Text_& setText(const std::wstring& str)
    {
        getTextElement()->setText(str);
        return *this;
    }

    Text_& setText(std::wstring&& str)
    {
        getTextElement()->setText(std::forward<std::wstring>(str));
        return *this;
    }
};

class Group_ : public Sprite_
{
private:
    friend class ::pvr::ui::impl::PixelGroup_;
    friend class ::pvr::ui::impl::MatrixGroup_;

    void calculateMvp(uint64_t parentIds, glm::mat4 const& srt, const glm::mat4& viewProjection, pvr::Rectanglei const& viewport) const
    {
        glm::mat4 tmpMatrix = srt * _cachedMatrix;
        // My cached matrix should always be up-to-date unless overridden. No effect.
        for (ChildContainer::iterator it = _children.begin(); it != _children.end(); ++it) { (*it)->calculateMvp(packId(parentIds, _id), tmpMatrix, viewProjection, viewport); }
    }

    virtual void onRender(uint64_t parentId) const
    {
        for (ChildContainer::iterator it = _children.begin(); it != _children.end(); ++it) { (*it)->onRender(packId(parentId, _id)); }
    }

    Group_(UIRenderer& uiRenderer, uint64_t groupid) : Sprite_(uiRenderer), _id(groupid) {}

    struct SpriteEntryEquals
    {
        Sprite sprite;
        SpriteEntryEquals(const Sprite& sprite) : sprite(sprite) {}
        bool operator()(const Sprite& rhs) { return sprite == rhs; }
    };

protected:
    uint64_t packId(uint64_t parentIds, uint64_t id) const
    {
        uint64_t packed = parentIds << NUM_BITS_GROUP_ID;
        return packed | id;
    }

    typedef std::vector<Sprite> ChildContainer;

    mutable ChildContainer _children;

    uint64_t _id;

public:
    Group_* add(const Sprite& sprite);

    void add(const Sprite* sprites, uint32_t numSprites)
    {
        std::for_each(sprites, sprites + numSprites, [&](const Sprite& sprite) { add(sprite); });
    }

    void remove(const Sprite& sprite)
    {
        ChildContainer::iterator it = std::find_if(_children.begin(), _children.end(), SpriteEntryEquals(sprite));
        if (it != _children.end()) { _children.erase(it); }

        // reconstruct the bounding box
        _boundingRect.clear();
        for (Sprite& currentSprite : _children) { _boundingRect.add(currentSprite->getBoundingBox()); }
    }

    void removeAll()
    {
        _children.erase(_children.begin(), _children.end());
        _boundingRect.clear();
    }

    glm::vec2 getScaledDimension() const
    {
        glm::vec2 dim(0);
        for (uint32_t i = 0; i < _children.size(); ++i) { dim += _children[i]->getScaledDimension(); }
        return dim;
    }
};

class MatrixGroup_ : public Group_
{
private:
    friend class pvr::ui::UIRenderer;

    class make_shared_enabler
    {
    protected:
        make_shared_enabler() {}
        friend class MatrixGroup_;
    };

    static MatrixGroup constructShared(UIRenderer& uiRenderer, uint64_t id) { return std::make_shared<MatrixGroup_>(make_shared_enabler{}, uiRenderer, id); }

    glm::mat4 _viewProj;

    void calculateMvp(uint64_t parentIds, glm::mat4 const& srt, const glm::mat4& viewProj, pvr::Rectanglei const& viewport) const
    {
        glm::mat4 tmpMatrix = srt * _cachedMatrix;
        // My cached matrix should always be up-to-date unless overridden. No effect.
        for (ChildContainer::iterator it = _children.begin(); it != _children.end(); ++it) { (*it)->calculateMvp(packId(parentIds, _id), tmpMatrix, viewProj, viewport); }
    }

public:
    MatrixGroup_(make_shared_enabler, UIRenderer& uiRenderer, uint64_t id);

    void setScaleRotateTranslate(const glm::mat4& srt) { _cachedMatrix = srt; }
    void setViewProjection(const glm::mat4& viewProj) { _viewProj = viewProj; }

    void commitUpdates() const;
};

class PixelGroup_ : public Group_, public I2dComponent
{
private:
    friend class pvr::ui::UIRenderer;

    class make_shared_enabler
    {
    protected:
        make_shared_enabler() {}
        friend class PixelGroup_;
    };

    static PixelGroup constructShared(UIRenderer& uiRenderer, uint64_t id) { return std::make_shared<PixelGroup_>(make_shared_enabler{}, uiRenderer, id); }

    void calculateMvp(uint64_t parentIds, glm::mat4 const& srt, const glm::mat4& viewProj, pvr::Rectanglei const& viewport) const;

public:
    PixelGroup_(make_shared_enabler, UIRenderer& uiRenderer, uint64_t id) : Group_(uiRenderer, id) {}

    PixelGroup_* setSize(glm::vec2 const& size)
    {
        _boundingRect.setMinMax(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(size.x, size.y, 0.0f));
        return this;
    }
};
} // namespace impl
} // namespace ui
} // namespace pvr