diff --git a/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/GameData/ConformalDecals/Plugins/ConformalDecals.dll index 1ac867a..abddca6 100644 Binary files a/GameData/ConformalDecals/Plugins/ConformalDecals.dll and b/GameData/ConformalDecals/Plugins/ConformalDecals.dll differ diff --git a/Source/ConformalDecals/ConformalDecals.csproj b/Source/ConformalDecals/ConformalDecals.csproj index f4c8b7a..b6a10d0 100644 --- a/Source/ConformalDecals/ConformalDecals.csproj +++ b/Source/ConformalDecals/ConformalDecals.csproj @@ -90,6 +90,7 @@ + diff --git a/Source/ConformalDecals/ModuleConformalText.cs b/Source/ConformalDecals/ModuleConformalText.cs index a851536..6e3615a 100644 --- a/Source/ConformalDecals/ModuleConformalText.cs +++ b/Source/ConformalDecals/ModuleConformalText.cs @@ -27,8 +27,8 @@ namespace ConformalDecals { var decalText = new DecalText("Hello World!", _font, _style); - TextRenderer.Instance.RenderText(decalText, out var texture, out var window); - materialProperties.AddOrGetTextureProperty("_Decal", true).Texture = texture; + //TextRenderer.Instance.RenderText(decalText, out var texture, out var window); + //materialProperties.AddOrGetTextureProperty("_Decal", true).Texture = texture; UpdateMaterials(); UpdateScale(); } diff --git a/Source/ConformalDecals/Text/RenderedText.cs b/Source/ConformalDecals/Text/RenderedText.cs index d00c293..fc31ae4 100644 --- a/Source/ConformalDecals/Text/RenderedText.cs +++ b/Source/ConformalDecals/Text/RenderedText.cs @@ -1,11 +1,16 @@ using UnityEngine; namespace ConformalDecals.Text { - public class RenderedText : ScriptableObject { + public class RenderedText { public Texture2D Texture { get; private set; } public Rect Window { get; private set; } - public int UserCount { get; private set; } + public int UserCount { get; set; } + + public RenderedText(Texture2D texture, Rect window) { + Texture = texture; + Window = window; + } } } \ No newline at end of file diff --git a/Source/ConformalDecals/Text/TextRenderJob.cs b/Source/ConformalDecals/Text/TextRenderJob.cs new file mode 100644 index 0000000..0d17cbd --- /dev/null +++ b/Source/ConformalDecals/Text/TextRenderJob.cs @@ -0,0 +1,42 @@ +using System; +using UnityEngine.Events; + +namespace ConformalDecals.Text { + public class TextRenderJob { + public DecalText OldText { get; } + public DecalText NewText { get; } + public bool Needed { get; private set; } + public bool IsStarted { get; private set; } + public bool IsDone { get; private set; } + + public readonly TextRenderer.TextRenderEvent onRenderFinished = new TextRenderer.TextRenderEvent(); + + public TextRenderJob(DecalText oldText, DecalText newText, UnityAction renderFinishedCallback) { + OldText = oldText ?? throw new ArgumentNullException(nameof(oldText)); + NewText = newText ?? throw new ArgumentNullException(nameof(newText)); + Needed = true; + + if (renderFinishedCallback != null) onRenderFinished.AddListener(renderFinishedCallback); + } + + public TextRenderJob( DecalText newText, UnityAction renderFinishedCallback) { + NewText = newText ?? throw new ArgumentNullException(nameof(newText)); + Needed = true; + + if (renderFinishedCallback != null) onRenderFinished.AddListener(renderFinishedCallback); + } + + public void Cancel() { + Needed = false; + } + + public void Start() { + IsStarted = true; + } + + public void Finish(RenderedText output) { + IsDone = true; + onRenderFinished.Invoke(output); + } + } +} \ No newline at end of file diff --git a/Source/ConformalDecals/Text/TextRenderer.cs b/Source/ConformalDecals/Text/TextRenderer.cs index aee2b59..8cd57df 100644 --- a/Source/ConformalDecals/Text/TextRenderer.cs +++ b/Source/ConformalDecals/Text/TextRenderer.cs @@ -1,13 +1,15 @@ using System; -using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; -using UnityEngine.Rendering; +using UnityEngine.Events; namespace ConformalDecals.Text { [KSPAddon(KSPAddon.Startup.Instantly, true)] public class TextRenderer : MonoBehaviour { + public const TextureFormat TextTextureFormat = TextureFormat.RG16; + public const RenderTextureFormat TextRenderTextureFormat = RenderTextureFormat.R8; + public static TextRenderer Instance { get { if (!_instance._isSetup) { @@ -18,8 +20,8 @@ namespace ConformalDecals.Text { } } - public const TextureFormat TextTextureFormat = TextureFormat.RG16; - public const RenderTextureFormat TextRenderTextureFormat = RenderTextureFormat.R8; + [Serializable] + public class TextRenderEvent : UnityEvent { } private const string BlitShader = "ConformalDecals/Text Blit"; private const int MaxTextureSize = 4096; @@ -32,8 +34,30 @@ namespace ConformalDecals.Text { private TextMeshPro _tmp; private Material _blitMaterial; - private Dictionary _renderedTextures = new Dictionary(); - private Texture2D _lastTexture; // to reduce the number of Texture2D objects created and destroyed, keep the last one on hand + private readonly Dictionary _renderCache = new Dictionary(); + private readonly Queue _renderJobs = new Queue(); + + public TextRenderJob RenderText(DecalText text, UnityAction renderFinishedCallback) { + var job = new TextRenderJob(text, renderFinishedCallback); + _renderJobs.Enqueue(job); + return job; + } + + public TextRenderJob UpdateText(DecalText oldText, DecalText newText, UnityAction renderFinishedCallback) { + var job = new TextRenderJob(oldText, newText, renderFinishedCallback); + _renderJobs.Enqueue(job); + return job; + } + + public void UnregisterText(DecalText text) { + if (_renderCache.TryGetValue(text, out var renderedText)) { + renderedText.UserCount--; + if (renderedText.UserCount <= 0) { + _renderCache.Remove(text); + Destroy(renderedText.Texture); + } + } + } private void Start() { if (_instance != null) { @@ -45,6 +69,16 @@ namespace ConformalDecals.Text { DontDestroyOnLoad(gameObject); } + private void Update() { + TextRenderJob nextJob; + do { + if (_renderJobs.Count <= 0) return; + nextJob = _renderJobs.Dequeue(); + } while (nextJob.Needed); + + RunJob(nextJob); + } + private void Setup() { if (_isSetup) return; @@ -60,7 +94,47 @@ namespace ConformalDecals.Text { _isSetup = true; } - public void RenderText(DecalText text, out Texture2D texture, out Rect window) { + private void RunJob(TextRenderJob job) { + Debug.Log($"Starting Text Rendering Job. queue depth = {_renderJobs.Count}, cache size = {_renderCache.Count}"); + job.Start(); + + Texture2D texture = null; + if (job.OldText != null && _renderCache.TryGetValue(job.OldText, out var oldRender)) { + // old output still exists + + oldRender.UserCount--; + + if (oldRender.UserCount <= 0) { + // this is the only usage of this output, so we are free to re-render into the texture + Debug.Log("Render output is not shared with other users, so reusing texture and removing cache slot"); + + texture = oldRender.Texture; + _renderCache.Remove(job.OldText); + } + else { + // other things are using this render output, so decriment usercount, and we'll make a new entry instead + Debug.Log("Render output is shared with other users, so making new output"); + } + } + + // now that all old references are handled, begin rendering the new output + + if (_renderCache.TryGetValue(job.NewText, out var cachedRender)) { + Debug.Log("Using Cached Render Output"); + Debug.Log($"Finished Text Rendering Job. queue depth = {_renderJobs.Count}, cache size = {_renderCache.Count}"); + + cachedRender.UserCount++; + return; + } + + var output = RenderText(job.NewText, texture); + _renderCache.Add(job.NewText, output); + + job.Finish(output); + Debug.Log($"Finished Text Rendering Job. queue depth = {_renderJobs.Count}, cache size = {_renderCache.Count}"); + } + + public RenderedText RenderText(DecalText text, Texture2D texture) { // SETUP TMP OBJECT FOR RENDERING _tmp.text = text.FormattedText; _tmp.font = text.Font.FontAsset; @@ -108,20 +182,18 @@ namespace ConformalDecals.Text { float sizeRatio = Mathf.Min(textureSize.x / size.x, textureSize.y, size.y); // calculate where in the texture the used area actually is - window = new Rect { + var window = new Rect { size = size * sizeRatio, center = (Vector2) textureSize / 2 }; - // GET TEXTURE - if (_lastTexture != null) { - texture = _lastTexture; - texture.Resize(textureSize.x, textureSize.y, TextTextureFormat, false); - _lastTexture = null; - } - else { + // SETUP TEXTURE + if (texture == null) { texture = new Texture2D(textureSize.x, textureSize.y, TextTextureFormat, false); } + else if (texture.width != textureSize.x || texture.height != textureSize.y || texture.format != TextTextureFormat) { + texture.Resize(textureSize.x, textureSize.y, TextTextureFormat, false); + } // GENERATE PROJECTION MATRIX var halfSize = (Vector2) textureSize / PixelDensity / 2 / sizeRatio; @@ -147,6 +219,8 @@ namespace ConformalDecals.Text { // RELEASE RENDERTEX RenderTexture.ReleaseTemporary(renderTex); + + return new RenderedText(texture, window); } } } \ No newline at end of file