// Copyright (c) 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.
///
[ActorToolbox("GUI")]
public class RenderToTextureControl : ContainerControl
{
private bool _invalid, _redrawRegistered, _isDuringTextureDraw;
private bool _autoSize = true;
private GPUTexture _texture;
private Float2 _textureSize;
private MaterialBase _drawMaterial;
private MaterialInstance _drawMaterialInstance;
private string _drawTextureParameterName = "Input";
///
/// 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)]
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(nameof(AutomaticTextureSize), true), Limit(0, 4096)]
public Float2 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).
///
[EditorOrder(30)]
public bool AutomaticInvalidate { get; set; } = true;
///
/// Gets or sets the GUI material used to draw the cached texture to the screen.
/// Can be used to post-process underlying GUI with a custom shader (eg. chromatic-aberration, blur or tint). If not set, simple texture copy is performed.
/// Materials has to be created with GUI domain and a GPUTexture parameter (default name is "Input") to be used as a source texture.
///
[EditorOrder(100)]
public MaterialBase DrawMaterial
{
get => _drawMaterial;
set
{
if (_drawMaterial != value)
{
_drawMaterial = value;
Invalidate();
}
}
}
///
/// Gets or sets the name of the GPUTexture parameter on the to use as a source texture.
///
[EditorOrder(110), VisibleIf("HasDrawMaterial")]
public string DrawTextureParameterName
{
get => _drawTextureParameterName;
set
{
if (_drawTextureParameterName != value)
{
_drawTextureParameterName = value;
Invalidate();
}
}
}
#if FLAX_EDITOR
private bool HasDrawMaterial => _drawMaterial != null;
#endif
///
/// 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 !BUILD_RELEASE
_texture.Name = nameof(RenderToTextureControl);
#endif
}
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;
}
}
if (!_texture || !_texture.IsAllocated)
return;
Profiler.BeginEventGPU("RenderToTextureControl");
var context = GPUDevice.Instance.MainContext;
_isDuringTextureDraw = true;
context.Clear(_texture.View(), Color.Transparent);
Render2D.Begin(context, _texture);
try
{
var scale = _textureSize / Size;
Matrix3x3.Scaling(scale.X, scale.Y, 1.0f, out var scaleMatrix);
Render2D.PushTransform(ref scaleMatrix);
Draw();
Render2D.PopTransform();
}
finally
{
Render2D.End();
_isDuringTextureDraw = false;
Profiler.EndEventGPU();
}
}
///
public override void Draw()
{
// Draw cached texture
if (_texture && !_invalid && !_isDuringTextureDraw)
{
var bounds = new Rectangle(Float2.Zero, Size);
// Background
var backgroundColor = BackgroundColor;
if (backgroundColor.A > 0.0f)
Render2D.FillRectangle(bounds, backgroundColor);
if (_drawMaterial && !_drawMaterial.WaitForLoaded())
{
// Blit with a custom material
if (!_drawMaterialInstance)
_drawMaterialInstance = Content.CreateVirtualAsset();
_drawMaterialInstance.BaseMaterial = _drawMaterial;
if (!_drawMaterial.IsGUI)
{
Debug.Logger.LogHandler.LogWrite(LogType.Error, $"Cannot draw RenderToTextureControl contents because material '{_drawMaterial}' isn't GUI domain");
return;
}
var textureParam = _drawMaterialInstance.GetParameter(_drawTextureParameterName);
if (!textureParam)
{
Debug.Logger.LogHandler.LogWrite(LogType.Error, $"Cannot draw RenderToTextureControl contents because material '{_drawMaterial}' doesn't have parameter '{_drawTextureParameterName}'");
return;
}
if (textureParam.ParameterType != MaterialParameterType.GPUTexture)
{
Debug.Logger.LogHandler.LogWrite(LogType.Error, $"Cannot draw RenderToTextureControl contents because material '{_drawMaterial}''s parameter '{_drawTextureParameterName}' is not a GPUTexture");
return;
}
textureParam.Value = _texture;
Render2D.DrawMaterial(_drawMaterialInstance, bounds);
}
else
{
// Simple texture draw
Render2D.DrawTexture(_texture, bounds);
}
return;
}
// Draw default UI directly
base.Draw();
}
///
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;
}
_drawMaterial = null;
Object.Destroy(ref _texture);
Object.Destroy(ref _drawMaterialInstance);
base.OnDestroy();
}
}
}