Explorar o código

Merge pull request #2185 from rokups/update-3D-UI

3D UI cleanup and update
Eugene Kozlov %!s(int64=8) %!d(string=hai) anos
pai
achega
2ae6c6b85f

+ 58 - 39
Source/Urho3D/UI/UI.cpp

@@ -441,32 +441,36 @@ void UI::RenderUpdate()
     }
 
     // Get batches for UI elements rendered into textures. Each element rendered into texture is treated as root element.
-    for (Vector<WeakPtr<UIComponent> >::Iterator it = renderToTexture_.Begin(); it != renderToTexture_.End();)
+    for (auto it = renderToTexture_.Begin(); it != renderToTexture_.End();)
     {
-        WeakPtr<UIComponent> component = *it;
-        if (component.Null() || !component->IsEnabled())
+        RenderToTextureData& data = it->second_;
+        if (data.rootElement_.Expired())
+        {
             it = renderToTexture_.Erase(it);
-        else if (component->IsEnabled())
+            continue;
+        }
+
+        if (data.rootElement_->IsEnabled())
         {
-            component->batches_.Clear();
-            component->vertexData_.Clear();
-            UIElement* element = component->GetRoot();
+            data.batches_.Clear();
+            data.vertexData_.Clear();
+            UIElement* element = data.rootElement_;
             const IntVector2& size = element->GetSize();
             const IntVector2& pos = element->GetPosition();
             // Note: the scissors operate on unscaled coordinates. Scissor scaling is only performed during render
             IntRect scissor = IntRect(pos.x_, pos.y_, pos.x_ + size.x_, pos.y_ + size.y_);
-            GetBatches(component->batches_, component->vertexData_, element, scissor);
+            GetBatches(data.batches_, data.vertexData_, element, scissor);
 
             // UIElement does not have anything to show. Insert dummy batch that will clear the texture.
-            if (component->batches_.Empty())
+            if (data.batches_.Empty())
             {
-                UIBatch batch(element, BLEND_REPLACE, scissor, nullptr, &component->vertexData_);
+                UIBatch batch(element, BLEND_REPLACE, scissor, nullptr, &data.vertexData_);
                 batch.SetColor(Color::BLACK);
                 batch.AddQuad(scissor.left_, scissor.top_, scissor.right_, scissor.bottom_, 0, 0);
-                component->batches_.Push(batch);
+                data.batches_.Push(batch);
             }
-            ++it;
         }
+        ++it;
     }
 }
 
@@ -501,23 +505,23 @@ void UI::Render(bool renderUICommand)
     // Render to UIComponent textures. This is skipped when called from the RENDERUI command
     if (!renderUICommand)
     {
-        for (Vector<WeakPtr<UIComponent> >::ConstIterator it = renderToTexture_.Begin(); it != renderToTexture_.End(); it++)
+        for (auto& item : renderToTexture_)
         {
-            WeakPtr<UIComponent> component = *it;
-            if (component->IsEnabled())
+            RenderToTextureData& data = item.second_;
+            if (data.rootElement_->IsEnabled())
             {
-                SetVertexData(component->vertexBuffer_, component->vertexData_);
-                SetVertexData(component->debugVertexBuffer_, component->debugVertexData_);
+                SetVertexData(data.vertexBuffer_, data.vertexData_);
+                SetVertexData(data.debugVertexBuffer_, data.debugVertexData_);
 
-                RenderSurface* surface = component->GetTexture()->GetRenderSurface();
+                RenderSurface* surface = data.texture_->GetRenderSurface();
                 graphics_->SetRenderTarget(0, surface);
                 graphics_->SetViewport(IntRect(0, 0, surface->GetWidth(), surface->GetHeight()));
                 graphics_->Clear(Urho3D::CLEAR_COLOR);
 
-                Render(component->vertexBuffer_, component->batches_, 0, component->batches_.Size());
-                Render(component->debugVertexBuffer_, component->debugDrawBatches_, 0, component->debugDrawBatches_.Size());
-                component->debugDrawBatches_.Clear();
-                component->debugVertexData_.Clear();
+                Render(data.vertexBuffer_, data.batches_, 0, data.batches_.Size());
+                Render(data.debugVertexBuffer_, data.debugDrawBatches_, 0, data.debugDrawBatches_.Size());
+                data.debugDrawBatches_.Clear();
+                data.debugVertexData_.Clear();
             }
         }
 
@@ -546,12 +550,12 @@ void UI::DebugDraw(UIElement* element)
             element->GetDebugDrawBatches(debugDrawBatches_, debugVertexData_, scissor);
         else
         {
-            for (Vector<WeakPtr<UIComponent> >::Iterator it = renderToTexture_.Begin(); it != renderToTexture_.End(); it++)
+            for (auto& item : renderToTexture_)
             {
-                WeakPtr<UIComponent> component = *it;
-                if (component.NotNull() && component->GetRoot() == root && component->IsEnabled())
+                RenderToTextureData& data = item.second_;
+                if (!data.rootElement_.Expired() && data.rootElement_ == root && data.rootElement_->IsEnabled())
                 {
-                    element->GetDebugDrawBatches(component->debugDrawBatches_, component->debugVertexData_, scissor);
+                    element->GetDebugDrawBatches(data.debugDrawBatches_, data.debugVertexData_, scissor);
                     break;
                 }
             }
@@ -768,16 +772,16 @@ UIElement* UI::GetElementAt(const IntVector2& position, bool enabledOnly, IntVec
     // Mouse was not hovering UI element. Check elements rendered on 3D objects.
     if (!result && renderToTexture_.Size())
     {
-        for (Vector<WeakPtr<UIComponent> >::Iterator it = renderToTexture_.Begin(); it != renderToTexture_.End(); it++)
+        for (auto& item : renderToTexture_)
         {
-            WeakPtr<UIComponent> component = *it;
-            if (component.Null() || !component->IsEnabled())
+            RenderToTextureData& data = item.second_;
+            if (data.rootElement_.Expired() || !data.rootElement_->IsEnabled())
                 continue;
 
-            IntVector2 screenPosition;
-            if (component->ScreenToUIPosition(position, screenPosition))
+            IntVector2 screenPosition = data.rootElement_->ScreenToElement(position);
+            if (data.rootElement_->GetCombinedScreenRect().IsInside(screenPosition) == INSIDE)
             {
-                result = GetElementAt(component->GetRoot(), screenPosition, enabledOnly);
+                result = GetElementAt(data.rootElement_, screenPosition, enabledOnly);
                 if (result)
                 {
                     if (elementScreenPosition)
@@ -2076,16 +2080,31 @@ IntVector2 UI::GetEffectiveRootElementSize(bool applyScale) const
     return size;
 }
 
-void UI::SetRenderToTexture(UIComponent* component, bool enable)
+void UI::SetElementRenderTexture(UIElement* element, Texture2D* texture)
 {
-    WeakPtr<UIComponent> weak(component);
-    if (enable)
+    if (element == nullptr)
     {
-        if (!renderToTexture_.Contains(weak))
-            renderToTexture_.Push(weak);
+        URHO3D_LOGERROR("UI::SetElementRenderTexture called with null element.");
+        return;
+    }
+
+    auto it = renderToTexture_.Find(element);
+    if (texture && it == renderToTexture_.End())
+    {
+        RenderToTextureData data;
+        data.texture_ = texture;
+        data.rootElement_ = element;
+        data.vertexBuffer_ = new VertexBuffer(context_);
+        data.debugVertexBuffer_ = new VertexBuffer(context_);
+        renderToTexture_[element] = data;
+    }
+    else if (it != renderToTexture_.End())
+    {
+        if (texture == nullptr)
+            renderToTexture_.Erase(it);
+        else
+            it->second_.texture_ = texture;
     }
-    else
-        renderToTexture_.Remove(weak);
 }
 
 void RegisterUILibrary(Context* context)

+ 25 - 6
Source/Urho3D/UI/UI.h

@@ -23,6 +23,7 @@
 #pragma once
 
 #include "../Core/Object.h"
+#include "../Graphics/VertexBuffer.h"
 #include "../UI/Cursor.h"
 #include "../UI/UIBatch.h"
 
@@ -48,7 +49,6 @@ class ResourceCache;
 class Timer;
 class UIBatch;
 class UIElement;
-class VertexBuffer;
 class XMLElement;
 class XMLFile;
 class RenderSurface;
@@ -212,8 +212,8 @@ public:
     /// Return root element custom size. Returns 0,0 when custom size is not being used and automatic resizing according to window size is in use instead (default.)
     const IntVector2& GetCustomSize() const { return customSize_; }
 
-    /// Register UIElement for being rendered into a texture.
-    void SetRenderToTexture(UIComponent* component, bool enable);
+    /// Set texture to which element will be rendered.
+    void SetElementRenderTexture(UIElement* element, Texture2D* texture);
 
     /// Data structure used to represent the drag data associated to a UIElement.
     struct DragData
@@ -233,6 +233,27 @@ public:
     };
 
 private:
+    /// Data structured used to hold data of UI elements that are rendered to texture.
+    struct RenderToTextureData
+    {
+        /// UIElement to be rendered into texture.
+        WeakPtr<UIElement> rootElement_;
+        /// Texture that UIElement will be rendered into.
+        SharedPtr<Texture2D> texture_;
+        /// UI rendering batches.
+        PODVector<UIBatch> batches_;
+        /// UI rendering vertex data.
+        PODVector<float> vertexData_;
+        /// UI vertex buffer.
+        SharedPtr<VertexBuffer> vertexBuffer_;
+        /// UI rendering batches for debug draw.
+        PODVector<UIBatch> debugDrawBatches_;
+        /// UI rendering vertex data for debug draw.
+        PODVector<float> debugVertexData_;
+        /// UI debug geometry vertex buffer.
+        SharedPtr<VertexBuffer> debugVertexBuffer_;
+    };
+
     /// Initialize when screen mode initially set.
     void Initialize();
     /// Update UI element logic recursively.
@@ -398,9 +419,7 @@ private:
     /// Root element custom size. 0,0 for automatic resizing (default.)
     IntVector2 customSize_;
     /// Elements that should be rendered to textures.
-    Vector<WeakPtr<UIComponent> > renderToTexture_;
-
-    friend class UIComponent;
+    HashMap<UIElement*, RenderToTextureData> renderToTexture_;
 };
 
 /// Register UI library objects.

+ 129 - 84
Source/Urho3D/UI/UIComponent.cpp

@@ -19,7 +19,6 @@
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 // THE SOFTWARE.
 //
-#include "UIComponent.h"
 #include "../Core/Context.h"
 #include "../Graphics/BillboardSet.h"
 #include "../Graphics/Graphics.h"
@@ -32,11 +31,14 @@
 #include "../Graphics/Camera.h"
 #include "../Graphics/VertexBuffer.h"
 #include "../Scene/Scene.h"
+#include "../Scene/SceneEvents.h"
 #include "../Resource/ResourceCache.h"
 #include "../IO/Log.h"
 #include "../UI/UI.h"
+#include "../UI/UIComponent.h"
 #include "../UI/UIEvents.h"
 
+#include "../DebugNew.h"
 
 namespace Urho3D
 {
@@ -45,16 +47,116 @@ static int const UICOMPONENT_DEFAULT_TEXTURE_SIZE = 512;
 static int const UICOMPONENT_MIN_TEXTURE_SIZE = 64;
 static int const UICOMPONENT_MAX_TEXTURE_SIZE = 4096;
 
-UIComponent::UIComponent(Context* context) : 
-    Component(context),
-    isStaticModelOwned_(false)
+class UIElement3D : public UIElement
+{
+    URHO3D_OBJECT(UIElement3D, UIElement);
+public:
+    /// Construct.
+    UIElement3D(Context* context) : UIElement(context) { }
+    /// Destruct.
+    virtual ~UIElement3D() override = default;
+    /// Set UIComponent which is using this element as root element.
+    void SetNode(Node* node) { node_ = node; }
+    /// Set active viewport through which this element is rendered. If viewport is not set, it defaults to first viewport.
+    void SetViewport(Viewport* viewport) { viewport_ = viewport; }
+    /// Convert element coordinates to screen coordinates.
+    IntVector2 ElementToScreen(const IntVector2& position) override
+    {
+        URHO3D_LOGERROR("UIElement3D::ElementToScreen is not implemented.");
+        return {-1, -1};
+    }
+    /// Convert screen coordinates to element coordinates.
+    IntVector2 ScreenToElement(const IntVector2& screenPos) override
+    {
+        IntVector2 result(-1, -1);
+
+        if (node_.Expired())
+            return result;
+
+        Scene* scene = node_->GetScene();
+        StaticModel* model = node_->GetComponent<StaticModel>();
+        if (scene == nullptr || model == nullptr)
+            return result;
+
+        Renderer* renderer = GetSubsystem<Renderer>();
+        if (renderer == nullptr)
+            return result;
+
+        // \todo Always uses the first viewport, in case there are multiple
+        Octree* octree = scene->GetComponent<Octree>();
+        if (viewport_ == nullptr)
+            viewport_ = renderer->GetViewportForScene(scene, 0);
+
+        if (viewport_.Expired() || octree == nullptr)
+            return result;
+
+        if (viewport_->GetScene() != scene)
+        {
+            URHO3D_LOGERROR("UIComponent and Viewport set to component's root element belong to different scenes.");
+            return result;
+        }
+
+        Camera* camera = viewport_->GetCamera();
+
+        if (camera == nullptr)
+            return result;
+
+        IntRect rect = viewport_->GetRect();
+        if (rect == IntRect::ZERO)
+        {
+            Graphics* graphics = GetSubsystem<Graphics>();
+            rect.right_ = graphics->GetWidth();
+            rect.bottom_ = graphics->GetHeight();
+        }
+
+        Ray ray(camera->GetScreenRay((float)screenPos.x_ / rect.Width(), (float)screenPos.y_ / rect.Height()));
+        PODVector<RayQueryResult> queryResultVector;
+        RayOctreeQuery query(queryResultVector, ray, RAY_TRIANGLE_UV, M_INFINITY, DRAWABLE_GEOMETRY, DEFAULT_VIEWMASK);
+
+        octree->Raycast(query);
+
+        if (queryResultVector.Empty())
+            return result;
+
+        for (unsigned i = 0; i < queryResultVector.Size(); i++)
+        {
+            RayQueryResult& queryResult = queryResultVector[i];
+            if (queryResult.drawable_ != model)
+            {
+                // ignore billboard sets by default
+                if (queryResult.drawable_->GetTypeInfo()->IsTypeOf(BillboardSet::GetTypeStatic()))
+                    continue;
+                return result;
+            }
+
+            Vector2& uv = queryResult.textureUV_;
+            result = IntVector2(static_cast<int>(uv.x_ * GetWidth()),
+                static_cast<int>(uv.y_ * GetHeight()));
+            return result;
+        }
+        return result;
+    }
+
+protected:
+    /// A UIComponent which owns this element.
+    WeakPtr<Node> node_;
+    /// Viewport which renders this element.
+    WeakPtr<Viewport> viewport_;
+};
+
+UIComponent::UIComponent(Context* context)
+    : Component(context)
+    , viewportIndex_(0)
 {
-    vertexBuffer_ = new VertexBuffer(context_);
-    debugVertexBuffer_ = new VertexBuffer(context_);
     texture_ = context_->CreateObject<Texture2D>();
+    texture_->SetFilterMode(FILTER_BILINEAR);
+    texture_->SetAddressMode(COORD_U, ADDRESS_CLAMP);
+    texture_->SetAddressMode(COORD_V, ADDRESS_CLAMP);
+    texture_->SetNumLevels(1);                                        // No mipmaps
 
-    rootElement_ = context_->CreateObject<UIElement>();
+    rootElement_ = context_->CreateObject<UIElement3D>();
     rootElement_->SetTraversalMode(TM_BREADTH_FIRST);
+    rootElement_->SetEnabled(true);
 
     material_ = context_->CreateObject<Material>();
     material_->SetTechnique(0, GetSubsystem<ResourceCache>()->GetResource<Technique>("Techniques/Diff.xml"));
@@ -63,6 +165,7 @@ UIComponent::UIComponent(Context* context) :
     SubscribeToEvent(rootElement_, E_RESIZED, URHO3D_HANDLER(UIComponent, OnElementResized));
 
     // Triggers resizing of texture.
+    rootElement_->SetRenderTexture(texture_);
     rootElement_->SetSize(UICOMPONENT_DEFAULT_TEXTURE_SIZE, UICOMPONENT_DEFAULT_TEXTURE_SIZE);
 }
 
@@ -73,6 +176,7 @@ UIComponent::~UIComponent()
 void UIComponent::RegisterObject(Context* context)
 {
     context->RegisterFactory<UIComponent>();
+    context->RegisterFactory<UIElement3D>();
 }
 
 UIElement* UIComponent::GetRoot() const
@@ -90,34 +194,28 @@ Texture2D* UIComponent::GetTexture() const
     return texture_;
 }
 
-
 void UIComponent::OnNodeSet(Node* node)
 {
+    rootElement_->SetNode(node);
     if (node)
     {
-        model_ = node->GetComponent<StaticModel>();
-        if (model_.Null())
-        {
-            isStaticModelOwned_ = true;
-            model_ = node->CreateComponent<StaticModel>();
-        }
-        model_->SetMaterial(material_);
+        Renderer* renderer = GetSubsystem<Renderer>();
+        StaticModel* model = node->GetComponent<StaticModel>();
+        rootElement_->SetViewport(renderer->GetViewportForScene(GetScene(), viewportIndex_));
+        if (model == nullptr)
+            model_ = model = node->CreateComponent<StaticModel>();
+        model->SetMaterial(material_);
+        rootElement_->SetRenderTexture(texture_);
     }
     else
     {
-        model_->SetMaterial(nullptr);
-        if (isStaticModelOwned_)
+        rootElement_->SetRenderTexture(nullptr);
+        if (model_.NotNull())
         {
-            model_->GetNode()->RemoveComponent<StaticModel>();
-            isStaticModelOwned_ = false;
+            model_->Remove();
+            model_ = nullptr;
         }
-        model_ = nullptr;
     }
-
-    UI* ui = GetSubsystem<UI>();
-    // May be null on shutdown
-    if (ui)
-        ui->SetRenderToTexture(this, node != nullptr);
 }
 
 void UIComponent::OnElementResized(StringHash eventType, VariantMap& args)
@@ -134,73 +232,20 @@ void UIComponent::OnElementResized(StringHash eventType, VariantMap& args)
     }
 
     if (texture_->SetSize(width, height, GetSubsystem<Graphics>()->GetRGBAFormat(), TEXTURE_RENDERTARGET))
-    {
-        texture_->SetFilterMode(FILTER_BILINEAR);
-        texture_->SetAddressMode(COORD_U, ADDRESS_CLAMP);
-        texture_->SetAddressMode(COORD_V, ADDRESS_CLAMP);
-        texture_->SetNumLevels(1);                                                // No mipmaps
         texture_->GetRenderSurface()->SetUpdateMode(SURFACE_MANUALUPDATE);
-    }
     else
         URHO3D_LOGERROR("UIComponent: resizing texture failed.");
 }
 
-bool UIComponent::ScreenToUIPosition(IntVector2 screenPos, IntVector2& result)
+void UIComponent::SetViewportIndex(unsigned int index)
 {
-    Scene* scene = GetScene();
-    if (!scene)
-        return false;
-
-    Renderer* renderer = GetSubsystem<Renderer>();
-    if (!renderer)
-        return false;
-
-    // \todo Always uses the first viewport, in case there are multiple
-    Viewport* viewport = renderer->GetViewportForScene(scene, 0);
-    Octree* octree = scene->GetComponent<Octree>();
-
-    if (!viewport || !octree)
-        return false;
-
-    Camera* camera = viewport->GetCamera();
-
-    if (!camera)
-        return false;
-
-    IntRect rect = viewport->GetRect();
-    if (rect == IntRect::ZERO)
+    viewportIndex_ = index;
+    if (Scene* scene = GetScene())
     {
-        Graphics* graphics = GetSubsystem<Graphics>();
-        rect.right_ = graphics->GetWidth();
-        rect.bottom_ = graphics->GetHeight();
-    }
-
-    Ray ray(camera->GetScreenRay((float)screenPos.x_ / rect.Width(), (float)screenPos.y_ / rect.Height()));
-    PODVector<RayQueryResult> queryResultVector;
-    RayOctreeQuery query(queryResultVector, ray, RAY_TRIANGLE_UV, M_INFINITY, DRAWABLE_GEOMETRY, DEFAULT_VIEWMASK);
-
-    octree->Raycast(query);
-
-    if (queryResultVector.Empty())
-        return false;
-
-    for (unsigned i = 0; i < queryResultVector.Size(); i++)
-    {
-        RayQueryResult& queryResult = queryResultVector[i];
-        if (queryResult.drawable_ != model_)
-        {
-            // ignore billboard sets by default
-            if (queryResult.drawable_->GetTypeInfo()->IsTypeOf(BillboardSet::GetTypeStatic()))
-                continue;
-            return false;
-        }
-
-        Vector2& uv = queryResult.textureUV_;
-        result = IntVector2(static_cast<int>(uv.x_ * rootElement_->GetWidth()),
-                            static_cast<int>(uv.y_ * rootElement_->GetHeight()));
-        return true;
+        Renderer* renderer = GetSubsystem<Renderer>();
+        Viewport* viewport = renderer->GetViewportForScene(scene, index);
+        rootElement_->SetViewport(viewport);
     }
-    return false;
 }
 
 }

+ 14 - 25
Source/Urho3D/UI/UIComponent.h

@@ -22,6 +22,7 @@
 #pragma once
 
 #include "../Scene/Component.h"
+#include "../UI/UIElement.h"
 
 namespace Urho3D
 {
@@ -33,6 +34,7 @@ class Viewport;
 class UIElement;
 class UIBatch;
 class VertexBuffer;
+class UIElement3D;
 
 class URHO3D_API UIComponent : public Component
 {
@@ -52,38 +54,25 @@ public:
     Material* GetMaterial() const;
     /// Return texture which will be used for rendering UI to.
     Texture2D* GetTexture() const;
+    /// Set index of viewport to be used for screen coordinate translation.
+    void SetViewportIndex(unsigned int index);
 
 protected:
+    /// Handle component being added to Node or removed from it.
+    virtual void OnNodeSet(Node* node) override;
+    /// Handle resizing of element. Setting size of element will automatically resize texture. UIElement size matches size of texture.
+    void OnElementResized(StringHash eventType, VariantMap& args);
+
     /// Material that is set to the model.
     SharedPtr<Material> material_;
     /// Texture that UIElement will be rendered into.
     SharedPtr<Texture2D> texture_;
-    /// Model that texture will be applied to.
+    /// Model created by this component. If node already has StaticModel then this will be null.
     SharedPtr<StaticModel> model_;
-    /// UIElement to be rendered into texture.
-    SharedPtr<UIElement> rootElement_;
-    /// UI rendering batches.
-    PODVector<UIBatch> batches_;
-    /// UI rendering vertex data.
-    PODVector<float> vertexData_;
-    /// UI vertex buffer.
-    SharedPtr<VertexBuffer> vertexBuffer_;
-    /// UI rendering batches for debug draw.
-    PODVector<UIBatch> debugDrawBatches_;
-    /// UI rendering vertex data for debug draw.
-    PODVector<float> debugVertexData_;
-    /// UI debug geometry vertex buffer.
-    SharedPtr<VertexBuffer> debugVertexBuffer_;
-    /// Is StaticModel component created by this component.
-    bool isStaticModelOwned_;
-
-    virtual void OnNodeSet(Node* node) override;
-    /// Handle resizing of element. Setting size of element will automatically resize texture. UIElement size matches size of texture.
-    void OnElementResized(StringHash eventType, VariantMap& args);
-    /// Convert screen position to position on UIElement.
-    bool ScreenToUIPosition(IntVector2 screenPos, IntVector2& result);
-
-    friend class UI;
+    /// UIElement to be rendered into texture. It also handles screen to UI coordinate translation.
+    SharedPtr<UIElement3D> rootElement_;
+    /// Viewport index to be set when component is added to a node.
+    unsigned viewportIndex_;
 };
 
 }

+ 6 - 0
Source/Urho3D/UI/UIElement.cpp

@@ -2277,4 +2277,10 @@ void UIElement::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
     UpdateAttributeAnimations(eventData[P_TIMESTEP].GetFloat());
 }
 
+void UIElement::SetRenderTexture(Texture2D* texture)
+{
+    if (UI* ui = GetSubsystem<UI>())
+        ui->SetElementRenderTexture(this, texture);
+}
+
 }

+ 4 - 0
Source/Urho3D/UI/UIElement.h

@@ -109,6 +109,7 @@ static const unsigned DD_SOURCE_AND_TARGET = 0x3;
 
 class Cursor;
 class ResourceCache;
+class Texture2D;
 
 /// Base class for %UI elements.
 class URHO3D_API UIElement : public Animatable
@@ -640,6 +641,9 @@ public:
     /// Return effective minimum size, also considering layout. Used internally.
     IntVector2 GetEffectiveMinSize() const;
 
+    /// Set texture to which element will be rendered.
+    void SetRenderTexture(Texture2D* texture);
+
 protected:
     /// Handle attribute animation added.
     virtual void OnAttributeAnimationAdded() override;