Files
BABA_YAGA/Packages/app.rive.rive-unity/Runtime/RenderQueue.cs
2026-05-19 17:39:03 +07:00

625 lines
19 KiB
C#

using System;
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.Rendering;
using System.Numerics;
namespace Rive
{
public class RiveCommandBuffer : CommandBuffer
{
private readonly Renderer m_renderer;
public RiveCommandBuffer(Renderer renderer)
{
m_renderer = renderer;
}
}
internal class CoroutineRunner : MonoBehaviour {}
public class Renderer : IRenderer
{
protected RenderQueue m_renderQueue;
private IntPtr m_nativeRenderQueue = IntPtr.Zero;
private uint m_index;
internal Renderer(RenderQueue queue)
{
m_renderQueue = queue;
m_nativeRenderQueue = queue.m_nativeRenderQueue;
m_index = getNextCommandBufferIndex(m_nativeRenderQueue);
}
internal RenderQueue RenderQueue => m_renderQueue;
void Release()
{
releaseCommandBuffer(m_nativeRenderQueue, m_index);
}
/// <summary>
/// Clear the commands in the render queue.
/// </summary>
public void Clear()
{
clearCommandBuffer(m_nativeRenderQueue, m_index);
}
/// <summary>
/// Enables/disables checking whether any artboards referenced by this render queue have dirt (didChange)
/// to decide if a render should occur.
/// This is a render-queue level setting: if any artboard in the queue has dirt, the entire queue can redraw.
/// </summary>
internal void SetArtboardDirtCheckEnabled(bool enabled)
{
setRenderQueueArtboardDirtCheckEnabled(m_nativeRenderQueue, enabled);
}
/// <summary>
/// Draw the given artboard to the render queue.
/// </summary>
public void Draw(Artboard artboard)
{
if (artboard == null)
{
throw new ArgumentException("A non null artboard must be provided.");
}
renderQueueDrawArtboard(m_nativeRenderQueue, m_index, artboard.NativeArtboard);
}
/// <summary>
/// Draw the given path and paint to the render queue.
/// </summary>
public void Draw(Path path, Paint paint)
{
renderQueueDrawPath(m_nativeRenderQueue, m_index, path.NativePath, paint.NativePaint);
}
/// <summary>
/// Clip the render queue to the given path.
/// </summary>
public void Clip(Path path)
{
renderQueueClipPath(m_nativeRenderQueue, m_index, path.NativePath);
}
/// <summary>
/// Save the current render queue state.
/// </summary>
public void Save()
{
renderQueueSave(m_nativeRenderQueue, m_index);
}
/// <summary>
/// Restore the last saved render queue state.
/// </summary>
public void Restore()
{
renderQueueRestore(m_nativeRenderQueue, m_index);
}
/// <summary>
/// Transform the render queue by the given translation.
/// </summary>
public void Translate(System.Numerics.Vector2 translation)
{
renderQueueTransform(
m_nativeRenderQueue,
m_index,
1.0f,
0.0f,
0.0f,
1.0f,
translation.X,
translation.Y
);
}
/// <summary>
/// Transform the render queue by the given translation.
/// </summary>
public void Translate(float x, float y)
{
renderQueueTransform(m_nativeRenderQueue, m_index, 1.0f, 0.0f, 0.0f, 1.0f, x, y);
}
/// <summary>
/// Transform the render queue by the given matrix.
/// </summary>
public void Transform(Matrix3x2 matrix)
{
renderQueueTransform(
m_nativeRenderQueue,
m_index,
matrix.M11,
matrix.M12,
matrix.M21,
matrix.M22,
matrix.M31,
matrix.M32
);
}
/// <summary>
/// Align the artboard to the given fit and alignment.
/// </summary>
public void Align(Fit fit, Alignment alignment, Artboard artboard, float scaleFactor = 1.0f)
{
if (artboard == null)
{
throw new ArgumentException("A non null artboard must be provided.");
}
renderQueueAlign(
m_nativeRenderQueue,
m_index,
(byte)fit,
alignment.X,
alignment.Y,
artboard.NativeArtboard,
scaleFactor
);
}
/// <summary>
/// Align the artboard to the given fit and alignment, with the given frame.
/// </summary>
public void Align(Fit fit, Alignment alignment, Artboard artboard, AABB frame, float scaleFactor = 1.0f)
{
renderQueueAlignWithFrame(
m_nativeRenderQueue,
m_index,
(byte)fit,
alignment.X,
alignment.Y,
artboard.NativeArtboard,
frame.minX,
frame.minY,
frame.maxX,
frame.maxY,
scaleFactor
);
}
public void Submit()
{
var commandBuffer = new RiveCommandBuffer(this);
if (m_renderQueue.Texture != null)
{
commandBuffer.SetRenderTarget(m_renderQueue.Texture);
}
m_renderQueue.UpdateDelayedRenderTexture();
commandBuffer.IssuePluginEventAndData(
getRenderCommandBufferCallback(),
(int)m_index,
m_nativeRenderQueue
);
Graphics.ExecuteCommandBuffer(commandBuffer);
}
public void SubmitAndRelease()
{
var commandBuffer = new RiveCommandBuffer(this);
if (m_renderQueue.Texture != null)
{
commandBuffer.SetRenderTarget(m_renderQueue.Texture);
}
m_renderQueue.UpdateDelayedRenderTexture();
commandBuffer.IssuePluginEventAndData(
getRenderAndReleaseCommandBufferCallback(),
(int)m_index,
m_nativeRenderQueue
);
Graphics.ExecuteCommandBuffer(commandBuffer);
}
public CommandBuffer ToCommandBuffer()
{
var commandBuffer = new RiveCommandBuffer(this);
AddToCommandBuffer(commandBuffer);
return commandBuffer;
}
public void AddToCommandBuffer(CommandBuffer commandBuffer, bool release = false)
{
if (
UnityEngine.SystemInfo.graphicsDeviceType
== UnityEngine.Rendering.GraphicsDeviceType.Metal
)
{
// Unity seems to have the wrong texture bound when querying the
// exposed CurrentRenderPassDescriptor's colorAttachment. This
// forces the Metal backend to catch up.
commandBuffer.DrawMesh(
GetResetMesh(),
new UnityEngine.Matrix4x4(),
GetResetMaterial()
);
}
m_renderQueue.UpdateDelayedRenderTexture();
commandBuffer.IssuePluginEventAndData(
release
? getRenderAndReleaseCommandBufferCallback()
: getRenderCommandBufferCallback(),
(int)m_index,
m_nativeRenderQueue
);
commandBuffer.IssuePluginEvent(getInvalidateState(), 0);
}
#if UNITY_2023_1_OR_NEWER && RIVE_USING_URP
public void AddToCommandBuffer(UnsafeCommandBuffer commandBuffer, bool release = false)
{
if (
UnityEngine.SystemInfo.graphicsDeviceType
== UnityEngine.Rendering.GraphicsDeviceType.Metal
)
{
// Unity seems to have the wrong texture bound when querying the
// exposed CurrentRenderPassDescriptor's colorAttachment. This
// forces the Metal backend to catch up.
commandBuffer.DrawMesh(
GetResetMesh(),
new UnityEngine.Matrix4x4(),
GetResetMaterial()
);
}
m_renderQueue.UpdateDelayedRenderTexture();
commandBuffer.IssuePluginEventAndData(
release
? getRenderAndReleaseCommandBufferCallback()
: getRenderCommandBufferCallback(),
(int)m_index,
m_nativeRenderQueue
);
commandBuffer.IssuePluginEvent(getInvalidateState(), 0);
}
#endif
private static Material m_resetMaterial;
private static Mesh m_resetMesh;
private static Material GetResetMaterial()
{
if (m_resetMaterial == null)
{
m_resetMaterial = new Material(UnityEngine.Shader.Find("UI/Default"));
}
return m_resetMaterial;
}
private static Mesh GetResetMesh()
{
if (m_resetMesh == null)
{
m_resetMesh = new Mesh();
}
return m_resetMesh;
}
#region Native Methods
[DllImport(NativeLibrary.name)]
protected static extern uint getNextCommandBufferIndex(IntPtr renderQueue);
[DllImport(NativeLibrary.name)]
internal static extern IntPtr getRenderCommandBufferCallback();
[DllImport(NativeLibrary.name)]
internal static extern void releaseCommandBuffer(
IntPtr renderQueue,
uint commandBufferIndex
);
[DllImport(NativeLibrary.name)]
internal static extern void clearCommandBuffer(
IntPtr renderQueue,
uint commandBufferIndex
);
[DllImport(NativeLibrary.name)]
internal static extern void setRenderQueueArtboardDirtCheckEnabled(
IntPtr renderQueue,
bool enabled
);
[DllImport(NativeLibrary.name)]
internal static extern void renderQueueDrawArtboard(
IntPtr renderQueue,
uint commandBufferIndex,
IntPtr artboard
);
[DllImport(NativeLibrary.name)]
internal static extern void renderQueueDrawPath(
IntPtr renderQueue,
uint commandBufferIndex,
IntPtr path,
IntPtr Paint
);
[DllImport(NativeLibrary.name)]
internal static extern void renderQueueClipPath(
IntPtr renderQueue,
uint commandBufferIndex,
IntPtr path
);
[DllImport(NativeLibrary.name)]
internal static extern void renderQueueSave(IntPtr renderQueue, uint commandBufferIndex);
[DllImport(NativeLibrary.name)]
internal static extern void renderQueueRestore(IntPtr renderQueue, uint commandBufferIndex);
[DllImport(NativeLibrary.name)]
internal static extern void renderQueueTransform(
IntPtr renderQueue,
uint commandBufferIndex,
float xx,
float xy,
float yx,
float yy,
float tx,
float ty
);
[DllImport(NativeLibrary.name)]
internal static extern void renderQueueAlign(
IntPtr renderQueue,
uint commandBufferIndex,
byte fit,
float alignX,
float alignY,
IntPtr artboard,
float scaleFactor
);
[DllImport(NativeLibrary.name)]
internal static extern void renderQueueAlignWithFrame(
IntPtr renderQueue,
uint commandBufferIndex,
byte fit,
float alignX,
float alignY,
IntPtr artboard,
float minX,
float minY,
float maxX,
float maxY,
float scaleFactor
);
[DllImport(NativeLibrary.name)]
internal static extern IntPtr getRenderAndReleaseCommandBufferCallback();
[DllImport(NativeLibrary.name)]
internal static extern IntPtr getInvalidateState();
#endregion
}
public class RenderQueue : IDisposable
{
private bool m_disposed = false;
// When using Vulkan we need to pass the RenderTexture.colorBuffer's
// native pointer. This colorBuffer's native pointer sometimes returns 0
// (null) even after calling RenderTexture.Create. In these rare cases
// we hold onto it to try to re-acquire it prior to the next submission.
private bool m_delayed = false;
public RenderTexture Texture { get; private set; }
private void Initialize(RenderTexture texture, bool clear)
{
m_updateDelayedCallback = UpdateDelayedRenderTexture;
Texture = texture;
ValidateRenderTexture(texture, true);
if (texture != null)
{
texture.Create();
}
m_nativeRenderQueue = makeRenderQueue(
texture == null ? IntPtr.Zero :
GetNativeTexturePointer(texture),
(uint)(texture?.width ?? 0),
(uint)(texture?.height ?? 0),
clear
);
}
public RenderQueue(RenderTexture texture = null, bool clear = true)
{
Initialize(texture, clear);
}
internal RenderQueue(RenderTexture texture, bool clear, MonoBehaviour coroutineHelper)
{
CoroutineHelper = coroutineHelper;
Initialize(texture, clear);
}
public Renderer Renderer()
{
return new Renderer(this);
}
~RenderQueue()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!m_disposed)
{
if (m_nativeRenderQueue != IntPtr.Zero)
{
unrefRenderQueue(m_nativeRenderQueue);
m_nativeRenderQueue = IntPtr.Zero;
}
Texture = null;
m_disposed = true;
}
}
static void ValidateRenderTexture(RenderTexture texture, bool allowNull = false)
{
if (allowNull && texture == null)
{
return;
}
if (texture == null)
{
throw new ArgumentException("A non null RenderTexture must be provided.");
}
if (
UnityEngine.SystemInfo.graphicsDeviceType
== UnityEngine.Rendering.GraphicsDeviceType.Direct3D11
&& !texture.enableRandomWrite
)
{
throw new ArgumentException(
"RenderTexture must have enableRandomWrite set to true for D3D11."
);
}
}
static WaitForEndOfFrame s_waitForEndOfFrame = new WaitForEndOfFrame();
private CallbackEventHandler m_updateDelayedCallback;
private static MonoBehaviour s_coroutineHelper;
/// <summary>
/// Used to run coroutines for the render queue. Needs to be a dontdestroyonload object so that it can be used across scenes.
/// </summary>
internal MonoBehaviour CoroutineHelper { get { return s_coroutineHelper; } set { s_coroutineHelper = value; } }
private delegate void CallbackEventHandler();
private static IEnumerator CallCallback(CallbackEventHandler callback)
{
yield return s_waitForEndOfFrame;
callback();
}
private static void EndOfFrame(CallbackEventHandler callback)
{
if (Application.isPlaying)
{
if (s_coroutineHelper == null) {
s_coroutineHelper = new GameObject("[Rive] RenderQueue Coroutine Helper").AddComponent<CoroutineRunner>();;
UnityEngine.Object.DontDestroyOnLoad(s_coroutineHelper.gameObject);
}
s_coroutineHelper.StartCoroutine(CallCallback(callback));
}
}
private IntPtr GetNativeTexturePointer(RenderTexture texture)
{
if(UnityEngine.SystemInfo.graphicsDeviceType
== UnityEngine.Rendering.GraphicsDeviceType.Vulkan)
{
IntPtr pointer = texture.colorBuffer.GetNativeRenderBufferPtr();
if(pointer == IntPtr.Zero)
{
texture.Create();
pointer = texture.colorBuffer.GetNativeRenderBufferPtr();
if(pointer == IntPtr.Zero)
{
m_delayed = true;
EndOfFrame(m_updateDelayedCallback);
}
}
return pointer;
}
return texture.GetNativeTexturePtr();
}
internal void UpdateDelayedRenderTexture()
{
if(!m_delayed || Texture == null)
{
return;
}
IntPtr pointer = GetNativeTexturePointer(Texture);
// Make sure null pointers are set too or the native side could be
// left with an invalid/discarded texture.
renderQueueUpdateRenderTexture(
m_nativeRenderQueue,
pointer,
(uint)Texture.width,
(uint)Texture.height
);
m_delayed = false;
}
/// <summary>
/// Update the render queue's target texture.
/// </summary>
public void UpdateTexture(RenderTexture texture)
{
ValidateRenderTexture(texture);
IntPtr pointer = GetNativeTexturePointer(texture);
renderQueueUpdateRenderTexture(
m_nativeRenderQueue,
pointer,
(uint)texture.width,
(uint)texture.height
);
Texture = texture;
}
internal IntPtr m_nativeRenderQueue = IntPtr.Zero;
#region Native Methods
[DllImport(NativeLibrary.name)]
protected static extern IntPtr makeRenderQueue(
IntPtr renderTexture,
uint width,
uint height,
bool clear
);
[DllImport(NativeLibrary.name)]
internal static extern void unrefRenderQueue(IntPtr renderQueue);
[DllImport(NativeLibrary.name)]
internal static extern void renderQueueUpdateRenderTexture(
IntPtr renderQueue,
IntPtr texture,
uint width,
uint heigh
);
[DllImport(NativeLibrary.name)]
public static extern bool supportsDrawingToScreen();
#endregion
}
}