// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. namespace FlaxEngine.GUI { /// /// UI container control that can render children to texture and display pre-cached texture instead of drawing children every frame. It can be also used to render part of UI to texture and use it in material or shader. /// public class RenderToTextureControl : ContainerControl { private bool _invalid, _redrawRegistered, _isDuringTextureDraw; private bool _autoSize = true; private GPUTexture _texture; private Vector2 _textureSize; /// /// Gets the texture with cached children controls. /// public GPUTexture Texture => _texture; /// /// Gets or sets a value indicating whether automatically update size of texture when control dimensions gets changed. /// [EditorOrder(10), Tooltip("If checked, size of the texture will be automatically updated when control dimensions gets changed.")] public bool AutomaticTextureSize { get => _autoSize; set { if (_autoSize == value) return; _autoSize = value; if (_autoSize) TextureSize = Size; } } /// /// Gets or sets the size of the texture (in pixels). /// [EditorOrder(20), VisibleIf("CanEditTextureSize"), Limit(0, 4096), Tooltip("The size of the texture (in pixels).")] public Vector2 TextureSize { get => _textureSize; set { if (_textureSize == value) return; _textureSize = value; Invalidate(); } } /// /// Gets or sets the value whether cached texture data should be invalidated automatically (eg. when child control changes). /// public bool AutomaticInvalidate { get; set; } = true; private bool CanEditTextureSize => !_autoSize; /// /// Invalidates the cached image of children controls and invokes the redraw to the texture. /// [Tooltip("Invalidates the cached image of children controls and invokes the redraw to the texture.")] public void Invalidate() { _invalid = true; if (!_redrawRegistered) { _redrawRegistered = true; Scripting.Draw += OnDraw; } } private void OnDraw() { if (_redrawRegistered) { _redrawRegistered = false; Scripting.Draw -= OnDraw; } if (!_invalid) return; _invalid = false; if (!_texture) _texture = new GPUTexture(); if (_texture.Size != _textureSize) { var desc = GPUTextureDescription.New2D((int)_textureSize.X, (int)_textureSize.Y, PixelFormat.R8G8B8A8_UNorm); if (_texture.Init(ref desc)) { Debug.Logger.LogHandler.LogWrite(LogType.Error, "Failed to allocate texture for RenderToTextureControl"); return; } } Profiler.BeginEventGPU("RenderToTextureControl"); var context = GPUDevice.Instance.MainContext; _isDuringTextureDraw = true; context.Clear(_texture.View(), Color.Transparent); Render2D.CallDrawing(this, context, _texture); _isDuringTextureDraw = false; Profiler.EndEventGPU(); } /// public override void DrawSelf() { // Draw cached texture if (_texture && !_invalid && !_isDuringTextureDraw) { var bounds = new Rectangle(Vector2.Zero, Size); var backgroundColor = BackgroundColor; if (backgroundColor.A > 0.0f) Render2D.FillRectangle(bounds, backgroundColor); Render2D.DrawTexture(_texture, bounds); return; } // Draw default UI directly base.DrawSelf(); } /// protected override void OnSizeChanged() { base.OnSizeChanged(); if (_autoSize) TextureSize = Size; } /// public override void OnChildResized(Control control) { base.OnChildResized(control); if (AutomaticInvalidate) Invalidate(); } /// public override void OnChildrenChanged() { base.OnChildrenChanged(); if (AutomaticInvalidate) Invalidate(); } /// protected override void PerformLayoutBeforeChildren() { base.PerformLayoutBeforeChildren(); if (AutomaticInvalidate) Invalidate(); } /// public override void OnDestroy() { _invalid = false; if (_redrawRegistered) { _redrawRegistered = false; Scripting.Draw -= OnDraw; } Object.Destroy(ref _texture); base.OnDestroy(); } } }