SpriteVk.h#

Parent directory (Vulkan)

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

Includes#

  • PVRCore/math/AxisAlignedBox.h

  • PVRCore/strings/StringFunctions.h

  • PVRCore/texture/Texture.h

  • PVRUtils/StructuredMemory.h

  • PVRVk/PVRVk.h

Included By#

Namespaces#

Classes#

Enums#

Source Code#

#pragma once
#include "PVRVk/PVRVk.h"
#include "PVRUtils/StructuredMemory.h"
#include "PVRCore/math/AxisAlignedBox.h"
#include "PVRCore/strings/StringFunctions.h"
#include "PVRCore/texture/Texture.h"

#define NUM_BITS_GROUP_ID 8

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

class UIRendererError : public std::runtime_error
{
public:
    explicit UIRendererError(const std::string& error) : std::runtime_error("UIRenderer Error | " + error) {}
};

class UIRendererInstanceMaxError : public UIRendererError
{
public:
    explicit UIRendererInstanceMaxError(const std::string& error) : UIRendererError("Maximum number of instances supported by this UIRenderer reached | " + error) {}
};

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_;

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

    virtual void onRender(pvrvk::CommandBufferBase& commands, uint64_t) { (void)commands; }

    virtual void onAddInstance(uint64_t parentId) = 0;

    virtual void onRemoveInstance(uint64_t parentId) { (void)parentId; }

    explicit Sprite_(UIRenderer& uiRenderer);

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;

    bool isSpriteNameDirty() const { return true; }

    std::string _spriteName;

public:
    virtual void commitUpdates() const;

    virtual ~Sprite_() {}

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

    void render();

    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; }

    virtual const std::string& getSpriteName()
    {
        if (isSpriteNameDirty()) { _spriteName = "Sprite"; }
        return _spriteName;
    }

    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 pvrvk::Rect2Df _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 pvrvk::Rect2Df& 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 pvrvk::ImageView& imageView, uint32_t width, uint32_t height, const pvrvk::Sampler& sampler)
    {
        return std::make_shared<Image_>(make_shared_enabler{}, uiRenderer, imageView, width, height, sampler);
    }

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

    void onRender(pvrvk::CommandBufferBase& commands, uint64_t parentId);

    void updateTextureDescriptorSet() const;

    void updateUbo(uint64_t parentIds) const;

    void onRemoveInstance(uint64_t parentid);

    void onAddInstance(uint64_t parentId);

protected:
    struct MvpUboData
    {
        glm::mat4 mvp;
        mutable int32_t bufferArrayId;

        MvpUboData() : bufferArrayId(-1) {}
    };

    struct MaterialUboData
    {
        glm::vec4 color;
        uint32_t isAlphaMode;

        mutable int32_t bufferArrayId;

        MaterialUboData() : bufferArrayId(-1) {}
    };

    MaterialUboData _materialData;

    mutable pvrvk::DescriptorSet _texDescSet;

    uint32_t _texW;

    uint32_t _texH;

    pvrvk::ImageView _imageView;

    pvrvk::Sampler _sampler;

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

    mutable bool _isTextureDirty;

public:
    Image_(make_shared_enabler, UIRenderer& uiRenderer, const pvrvk::ImageView& imageView, uint32_t width, uint32_t height, const pvrvk::Sampler& sampler);

    virtual ~Image_() { onRemoveInstance(0); }

    uint32_t getWidth() const { return _texW; }

    uint32_t getHeight() const { return _texH; }

    const pvrvk::ImageView& getImageView() const { return _imageView; }

    pvrvk::ImageView& getImageView() { return _imageView; }

    const pvrvk::Sampler& getSampler() const { return _sampler; }

    pvrvk::Sampler& getSampler() { return _sampler; }

    const pvrvk::DescriptorSet& getTexDescriptorSet() const
    {
        updateTextureDescriptorSet();
        return _texDescSet;
    }

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

    const std::string& getSpriteName()
    {
        if (isSpriteNameDirty()) { _spriteName = getImageView()->getObjectName(); }
        return _spriteName;
    }
};

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;

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

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

    static Font constructShared(UIRenderer& uiRenderer, const pvrvk::ImageView& tex2D, const TextureHeader& textureHeader, const pvrvk::Sampler& sampler)
    {
        return std::make_shared<Font_>(make_shared_enabler{}, uiRenderer, tex2D, textureHeader, sampler);
    }

    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<pvrvk::Rect2D> _rects;
    std::vector<int32_t> _yOffsets;
    pvrvk::ImageView _imageView;
    glm::uvec2 _dim;
    uint32_t _alphaRenderingMode;
    pvrvk::DescriptorSet _texDescSet;
    UIRenderer* _uiRenderer;

public:
    Font_(make_shared_enabler, UIRenderer& uiRenderer, const pvrvk::ImageView& tex2D, const TextureHeader& textureHeader, const pvrvk::Sampler& 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 pvrvk::Rect2D& 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; }

    const pvrvk::DescriptorSet& getTexDescriptorSet() const { return _texDescSet; }

    const pvrvk::ImageView& getImageView() const { return _imageView; }
};

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, uint32_t maxTextLength = 255)
    {
        return std::make_shared<TextElement_>(make_shared_enabler{}, uiRenderer, font, maxTextLength);
    }

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

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

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

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

    void createBuffers();
    void regenerateText() const;
    void updateVbo() const;
    void onRender(pvrvk::CommandBufferBase& commands);

    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 pvrvk::Buffer _vbo;
    mutable pvrvk::Buffer _drawIndirectBuffer;
    mutable uint32_t _maxLength;
    mutable std::string _textStr;
    mutable std::wstring _textWStr;
    mutable std::vector<uint32_t> _utf32;
    mutable std::vector<Vertex> _vertices;
    mutable int32_t _numCachedVerts;
    mutable math::AxisAlignedBox _boundingRect; //< Bounding rectangle of the sprite
    UIRenderer* _uiRenderer;

public:
    TextElement_(make_shared_enabler, UIRenderer& uiRenderer, const Font& font, uint32_t maxTextLength)
        : _isTextDirty(true), _font(font), _maxLength(maxTextLength), _uiRenderer(&uiRenderer)
    {
        _maxLength = _maxLength ? _maxLength : 255;
        createBuffers();
        updateText();
    }

    TextElement_(make_shared_enabler, UIRenderer& uiRenderer, const std::string& str, const Font& font, uint32_t maxTextLength = 0)
        : _isTextDirty(true), _font(font), _maxLength(std::max<uint32_t>(static_cast<uint32_t>(str.length()), maxTextLength)), _uiRenderer(&uiRenderer)
    {
        _maxLength = _maxLength ? _maxLength : 255;
        createBuffers();
        setText(str);
        updateText();
    }

    TextElement_(make_shared_enabler, UIRenderer& uiRenderer, const std::wstring& str, const Font& font, uint32_t maxTextLength = 0)
        : _isTextDirty(true), _font(font), _maxLength(std::max<uint32_t>(static_cast<uint32_t>(str.length()), maxTextLength)), _uiRenderer(&uiRenderer)
    {
        _maxLength = _maxLength ? _maxLength : 255;
        createBuffers();
        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); }

    void onRemoveInstance(uint64_t parentId);

    struct MvpUboData
    {
        glm::mat4 mvp; // model-view-projection
        mutable int32_t bufferArrayId;
        MvpUboData() : bufferArrayId(-1) {}
    };

    struct MaterialUboData
    {
        glm::vec4 color;
        uint32_t isAlphaMode;
        mutable int32_t bufferArrayId;
        MaterialUboData() : bufferArrayId(-1) {}
    } _materialData;

    const pvrvk::DescriptorSet& getTexDescriptorSet() const { return getFont()->getTexDescriptorSet(); }
    void onAddInstance(uint64_t parentId);

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

    void onRender(pvrvk::CommandBufferBase& commands, uint64_t parentId);

    void updateUbo(uint64_t parentId) const;
    mutable TextElement _textElement;
    mutable std::map<uint64_t, MvpUboData> _mvpData;

    std::string _imageViewObjectName;
    std::string _vboObjectName;
    std::string _drawIndirectBufferObjectName;

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

    virtual ~Text_() { onRemoveInstance(0); }

    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;
    }

    bool isSpriteNameDirty() const
    {
        return !(_textElement->getFont()->getImageView()->getObjectName() == _imageViewObjectName && _textElement->_vbo->getObjectName() == _vboObjectName);
    }

    const std::string& getSpriteName()
    {
        if (isSpriteNameDirty())
        {
            _imageViewObjectName = _textElement->getFont()->getImageView()->getObjectName();
            _vboObjectName = _textElement->_vbo->getObjectName();
            _drawIndirectBufferObjectName = _textElement->_drawIndirectBuffer->getObjectName();
            _spriteName = "ImageView: " + _imageViewObjectName;
            _spriteName += ", Vbo: " + _vboObjectName;
            _spriteName += ", Indirect Buffer: " + _drawIndirectBufferObjectName;
        }
        return _spriteName;
    }
};

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& viewProj, pvrvk::Rect2D 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); }
    }

    virtual void onRender(pvrvk::CommandBufferBase& commandBuffer, uint64_t parentId)
    {
        for (ChildContainer::iterator it = _children.begin(); it != _children.end(); ++it) { (*it)->onRender(commandBuffer, 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; }
    };

    void onRemoveInstance(uint64_t parentId)
    {
        for (uint32_t i = 0; i < _children.size(); ++i) { _children[i]->onRemoveInstance(packId(parentId, _id)); }
    }

    void onAddInstance(uint64_t parentId)
    {
        for (ChildContainer::iterator it = _children.begin(); it != _children.end(); ++it) { (*it)->onAddInstance(packId(parentId, _id)); }
    }
    uint64_t packId(uint64_t parentIds, uint64_t id) const
    {
        uint64_t packed = parentIds << NUM_BITS_GROUP_ID;
        return packed | id;
    }

protected:
    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())
        {
            (*it)->onRemoveInstance(_id);
            _children.erase(it);
        }
    }

    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;

    glm::mat4 _viewProj;

    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); }

    void calculateMvp(uint64_t parentIds, glm::mat4 const& srt, const glm::mat4& viewProj, pvrvk::Rect2D 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, pvrvk::Rect2D 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