diff --git a/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/GameData/ConformalDecals/Plugins/ConformalDecals.dll index ede398e..f55108e 100644 Binary files a/GameData/ConformalDecals/Plugins/ConformalDecals.dll and b/GameData/ConformalDecals/Plugins/ConformalDecals.dll differ diff --git a/GameData/ConformalDecals/Versioning/ConformalDecals.version b/GameData/ConformalDecals/Versioning/ConformalDecals.version index 4dfbdd5..b68583a 100644 --- a/GameData/ConformalDecals/Versioning/ConformalDecals.version +++ b/GameData/ConformalDecals/Versioning/ConformalDecals.version @@ -6,14 +6,14 @@ { "MAJOR":0, "MINOR":2, - "PATCH":6, - "BUILD":1 + "PATCH":7, + "BUILD":0 }, "KSP_VERSION": { "MAJOR":1, - "MINOR":10, - "PATCH":1 + "MINOR":11, + "PATCH":0 }, "KSP_VERSION_MIN":{ "MAJOR":1, @@ -22,7 +22,7 @@ }, "KSP_VERSION_MAX":{ "MAJOR":1, - "MINOR":10, + "MINOR":11, "PATCH":99 } } diff --git a/README.md b/README.md index febd796..11dcb47 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# Conformal Decals v0.2.6 -[![Build Status](https://travis-ci.org/drewcassidy/KSP-Conformal-Decals.svg?branch=release)](https://travis-ci.org/drewcassidy/KSP-Conformal-Decals) [![Art: CC BY-SA 4.0](https://img.shields.io/badge/Art%20License-CC%20BY--SA%204.0-orange.svg)](https://creativecommons.org/licenses/by-sa/4.0/) [![Code: GPL v3](https://img.shields.io/badge/Code%20License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) +# Conformal Decals v0.2.7 +[![Build Status](https://travis-ci.com/drewcassidy/KSP-Conformal-Decals.svg?branch=release)](https://travis-ci.com/drewcassidy/KSP-Conformal-Decals) [![Art: CC BY-SA 4.0](https://img.shields.io/badge/Art%20License-CC%20BY--SA%204.0-orange.svg)](https://creativecommons.org/licenses/by-sa/4.0/) [![Code: GPL v3](https://img.shields.io/badge/Code%20License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) ![Screenshot](http://pileof.rocks/KSP/images/ConformalDecalsHeader.png) diff --git a/Source/ConformalDecals/ConformalDecals.csproj b/Source/ConformalDecals/ConformalDecals.csproj index 909c3a8..b511e08 100644 --- a/Source/ConformalDecals/ConformalDecals.csproj +++ b/Source/ConformalDecals/ConformalDecals.csproj @@ -5,6 +5,7 @@ false x64 1701;1702;CS0649;CS1591 + 0.2.7 diff --git a/Source/ConformalDecals/ModuleConformalText.cs b/Source/ConformalDecals/ModuleConformalText.cs index cb33a41..7aee17d 100644 --- a/Source/ConformalDecals/ModuleConformalText.cs +++ b/Source/ConformalDecals/ModuleConformalText.cs @@ -279,7 +279,7 @@ namespace ConformalDecals { protected override void UpdateTextures() { // Render text var newText = new DecalText(text, font, style, vertical, lineSpacing, charSpacing); - var output = TextRenderer.UpdateTextNow(_currentText, newText); + var output = TextRenderer.UpdateText(_currentText, newText); _currentText = newText; _decalTextureProperty.Texture = output.Texture; diff --git a/Source/ConformalDecals/Properties/AssemblyInfo.cs b/Source/ConformalDecals/Properties/AssemblyInfo.cs index f2a1459..e73f444 100644 --- a/Source/ConformalDecals/Properties/AssemblyInfo.cs +++ b/Source/ConformalDecals/Properties/AssemblyInfo.cs @@ -1 +1 @@ -[assembly: KSPAssembly("ConformalDecals", 0, 2, 6)] \ No newline at end of file +[assembly: KSPAssembly("ConformalDecals", 0, 2)] \ No newline at end of file diff --git a/Source/ConformalDecals/Text/DecalFont.cs b/Source/ConformalDecals/Text/DecalFont.cs index 95d7179..77dbd64 100644 --- a/Source/ConformalDecals/Text/DecalFont.cs +++ b/Source/ConformalDecals/Text/DecalFont.cs @@ -109,5 +109,9 @@ namespace ConformalDecals.Text { public void OnBeforeSerialize() { } public void OnAfterDeserialize() { } + + public override string ToString() { + return _title; + } } } \ No newline at end of file diff --git a/Source/ConformalDecals/Text/DecalText.cs b/Source/ConformalDecals/Text/DecalText.cs index d6af8d1..d02c23e 100644 --- a/Source/ConformalDecals/Text/DecalText.cs +++ b/Source/ConformalDecals/Text/DecalText.cs @@ -86,5 +86,9 @@ namespace ConformalDecals.Text { public static bool operator !=(DecalText left, DecalText right) { return !Equals(left, right); } + + public override string ToString() { + return $"{nameof(_text)}: {_text}, {nameof(_font)}: {_font}, {nameof(_style)}: {_style}, {nameof(_vertical)}: {_vertical}, {nameof(_lineSpacing)}: {_lineSpacing}, {nameof(_charSpacing)}: {_charSpacing}"; + } } } \ No newline at end of file diff --git a/Source/ConformalDecals/Text/TextRenderJob.cs b/Source/ConformalDecals/Text/TextRenderJob.cs deleted file mode 100644 index 475de9b..0000000 --- a/Source/ConformalDecals/Text/TextRenderJob.cs +++ /dev/null @@ -1,35 +0,0 @@ -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; - 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(TextRenderOutput output) { - IsDone = true; - onRenderFinished.Invoke(output); - } - } -} \ No newline at end of file diff --git a/Source/ConformalDecals/Text/TextRenderOutput.cs b/Source/ConformalDecals/Text/TextRenderOutput.cs index 210e14a..8dff52f 100644 --- a/Source/ConformalDecals/Text/TextRenderOutput.cs +++ b/Source/ConformalDecals/Text/TextRenderOutput.cs @@ -9,7 +9,7 @@ namespace ConformalDecals.Text { /// The rectangle that the rendered text takes up within the texture public Rect Window { get; private set; } - /// The number of users for this render output. If 0, it can be discarded from the cache and the texture reused + /// The number of users for this render output. If 0, it can be discarded from the cache public int UserCount { get; set; } public TextRenderOutput(Texture2D texture, Rect window) { diff --git a/Source/ConformalDecals/Text/TextRenderer.cs b/Source/ConformalDecals/Text/TextRenderer.cs index 6c1206a..457bcfe 100644 --- a/Source/ConformalDecals/Text/TextRenderer.cs +++ b/Source/ConformalDecals/Text/TextRenderer.cs @@ -3,16 +3,12 @@ using System.Collections.Generic; using ConformalDecals.Util; using TMPro; using UnityEngine; -using UnityEngine.Events; using UnityEngine.Rendering; +using Object = UnityEngine.Object; namespace ConformalDecals.Text { - // TODO: Testing shows the job system is unnecessary, so remove job system code. - /// Class handing text rendering. - /// Is a singleton referencing a single gameobject in the scene which contains the TextMeshPro component - [KSPAddon(KSPAddon.Startup.Instantly, true)] - public class TextRenderer : MonoBehaviour { + public static class TextRenderer { /// Texture format used for returned textures. /// Unfortunately due to how Unity textures work, this cannot be R8 or Alpha8, /// so theres always a superfluous green channel using memory @@ -22,163 +18,73 @@ namespace ConformalDecals.Text { /// Overriden below to be ARGB32 on DirectX because DirectX is dumb public static RenderTextureFormat textRenderTextureFormat = RenderTextureFormat.R8; - /// The text renderer object within the scene which contains the TextMeshPro component used for rendering. - public static TextRenderer Instance { - get { - if (!_instance._isSetup) { - _instance.Setup(); - } - - return _instance; - } - } - - /// Text Render unityevent, used with the job system to signal render completion - [Serializable] - public class TextRenderEvent : UnityEvent { } - private const string ShaderName = "ConformalDecals/Text Blit"; private const int MaxTextureSize = 4096; private const float FontSize = 100; private const float PixelDensity = 5; - private static TextRenderer _instance; - - private bool _isSetup; - private TextMeshPro _tmp; - private Shader _blitShader; + private static Shader _blitShader; + private static Texture2D _blankTexture; private static readonly Dictionary RenderCache = new Dictionary(); - private static readonly Queue RenderJobs = new Queue(); - /// Update text using the job queue - public static TextRenderJob UpdateText(DecalText oldText, DecalText newText, UnityAction renderFinishedCallback) { + /// Update text immediately without using job queue + public static TextRenderOutput UpdateText(DecalText oldText, DecalText newText) { if (newText == null) throw new ArgumentNullException(nameof(newText)); - var job = new TextRenderJob(oldText, newText, renderFinishedCallback); - RenderJobs.Enqueue(job); - return job; - } + if (!(oldText is null)) UnregisterText(oldText); - /// Update text immediately without using job queue - public static TextRenderOutput UpdateTextNow(DecalText oldText, DecalText newText) { - if (newText == null) throw new ArgumentNullException(nameof(newText)); + // now that all old references are handled, begin rendering the new output + if (!RenderCache.TryGetValue(newText, out var renderOutput)) { + renderOutput = RenderText(newText); + RenderCache.Add(newText, renderOutput); + } - return Instance.RunJob(new TextRenderJob(oldText, newText, null), out _); + renderOutput.UserCount++; + return renderOutput; } /// Unregister a user of a piece of text public static void UnregisterText(DecalText text) { + if (text == null) throw new ArgumentNullException(nameof(text)); + if (RenderCache.TryGetValue(text, out var renderedText)) { renderedText.UserCount--; if (renderedText.UserCount <= 0) { RenderCache.Remove(text); - Destroy(renderedText.Texture); + var texture = renderedText.Texture; + if (texture != _blankTexture) Object.Destroy(texture); } } } - private void Start() { - if (_instance != null) { - Logging.LogError("Duplicate TextRenderer created???"); - } - - Logging.Log("Creating TextRenderer Object"); - _instance = this; - DontDestroyOnLoad(gameObject); - if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11 || SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D12) { - textRenderTextureFormat = RenderTextureFormat.ARGB32; // DirectX is dumb - } - - if (!SystemInfo.SupportsTextureFormat(textTextureFormat)) { - Logging.LogError($"Text texture format {textTextureFormat} not supported on this platform."); - } - - if (!SystemInfo.SupportsRenderTextureFormat(textRenderTextureFormat)) { - Logging.LogError($"Text texture format {textRenderTextureFormat} not supported on this platform."); - } - } - - /// Setup this text renderer instance for rendering - private void Setup() { - if (_isSetup) return; - - Logging.Log("Setting Up TextRenderer Object"); - - _tmp = gameObject.AddComponent(); - _tmp.renderer.enabled = false; // dont automatically render - - _blitShader = Shabby.Shabby.FindShader(ShaderName); - if (_blitShader == null) Logging.LogError($"Could not find text blit shader named '{ShaderName}'"); - - _isSetup = true; - } - - /// Run a text render job - private TextRenderOutput RunJob(TextRenderJob job, out bool renderNeeded) { - if (!job.Needed) { - renderNeeded = false; - return null; - } - - 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 - - texture = oldRender.Texture; - RenderCache.Remove(job.OldText); - } - } - - // now that all old references are handled, begin rendering the new output - - if (RenderCache.TryGetValue(job.NewText, out var renderOutput)) { - renderNeeded = false; - } - else { - renderNeeded = true; - - renderOutput = RenderText(job.NewText, texture); - RenderCache.Add(job.NewText, renderOutput); - } - - renderOutput.UserCount++; - - job.Finish(renderOutput); - return renderOutput; - } /// Render a piece of text to a given texture - public TextRenderOutput RenderText(DecalText text, Texture2D texture) { + public static TextRenderOutput RenderText(DecalText text) { if (text == null) throw new ArgumentNullException(nameof(text)); - if (_tmp == null) throw new InvalidOperationException("TextMeshPro object not yet created."); + + var tmpObject = new GameObject("Text Mesh Pro renderer"); + var tmp = tmpObject.AddComponent(); // SETUP TMP OBJECT FOR RENDERING - _tmp.text = text.FormattedText; - _tmp.font = text.Font.FontAsset; - _tmp.fontStyle = text.Style | text.Font.FontStyle; - _tmp.lineSpacing = text.LineSpacing; - _tmp.characterSpacing = text.CharSpacing; - - _tmp.extraPadding = true; - _tmp.enableKerning = true; - _tmp.enableWordWrapping = false; - _tmp.overflowMode = TextOverflowModes.Overflow; - _tmp.alignment = TextAlignmentOptions.Center; - _tmp.fontSize = FontSize; + tmp.text = text.FormattedText; + tmp.font = text.Font.FontAsset; + tmp.fontStyle = text.Style | text.Font.FontStyle; + tmp.lineSpacing = text.LineSpacing; + tmp.characterSpacing = text.CharSpacing; + + tmp.extraPadding = true; + tmp.enableKerning = true; + tmp.enableWordWrapping = false; + tmp.overflowMode = TextOverflowModes.Overflow; + tmp.alignment = TextAlignmentOptions.Center; + tmp.fontSize = FontSize; // GENERATE MESH - _tmp.ClearMesh(false); - _tmp.ForceMeshUpdate(); + tmp.ClearMesh(false); + tmp.ForceMeshUpdate(); - var meshFilters = gameObject.GetComponentsInChildren(); + var meshFilters = tmpObject.GetComponentsInChildren(); var meshes = new Mesh[meshFilters.Length]; var materials = new Material[meshFilters.Length]; @@ -189,9 +95,9 @@ namespace ConformalDecals.Text { var renderer = meshFilters[i].gameObject.GetComponent(); meshes[i] = meshFilters[i].mesh; - if (i == 0) meshes[i] = _tmp.mesh; + if (i == 0) meshes[i] = tmp.mesh; - materials[i] = Instantiate(renderer.material); + materials[i] = Object.Instantiate(renderer.material); materials[i].shader = _blitShader; if (renderer == null) throw new FormatException($"Object {meshFilters[i].gameObject.name} has filter but no renderer"); @@ -209,15 +115,16 @@ namespace ConformalDecals.Text { var size = bounds.size * PixelDensity; size.x = Mathf.Max(size.x, 0.1f); size.y = Mathf.Max(size.y, 0.1f); - + var textureSize = new Vector2Int { x = Mathf.NextPowerOfTwo((int) size.x), y = Mathf.NextPowerOfTwo((int) size.y) }; if (textureSize.x == 0 || textureSize.y == 0) { - Logging.LogWarning("No text present or error in texture size calculation. Aborting."); - return new TextRenderOutput(Texture2D.blackTexture, Rect.zero); + Logging.LogError("No text present or error in texture size calculation. Aborting."); + Object.Destroy(tmpObject); + return new TextRenderOutput(_blankTexture, Rect.zero); } // make sure texture isnt too big, scale it down if it is @@ -242,12 +149,7 @@ namespace ConformalDecals.Text { }; // SETUP TEXTURE - if (texture == null) { - texture = new Texture2D(textureSize.x, textureSize.y, textTextureFormat, true); - } - else if (texture.width != textureSize.x || texture.height != textureSize.y || texture.format != textTextureFormat) { - texture.Resize(textureSize.x, textureSize.y, textTextureFormat, true); - } + var texture = new Texture2D(textureSize.x, textureSize.y, textTextureFormat, false); // GENERATE PROJECTION MATRIX var halfSize = (Vector2) textureSize / PixelDensity / 2 / sizeRatio; @@ -255,7 +157,7 @@ namespace ConformalDecals.Text { bounds.center.y - halfSize.y, bounds.center.y + halfSize.y, -1, 1); // GET RENDERTEX - var renderTex = new RenderTexture(textureSize.x, textureSize.y, 0, textRenderTextureFormat, RenderTextureReadWrite.Linear) {autoGenerateMips = false}; + var renderTex = RenderTexture.GetTemporary(textureSize.x, textureSize.y, 0, textRenderTextureFormat, RenderTextureReadWrite.Linear); // RENDER Graphics.SetRenderTarget(renderTex); @@ -271,28 +173,42 @@ namespace ConformalDecals.Text { } } - // COPY TEXTURE BACK INTO RAM + // COPY RENDERTEX INTO TEXTURE var prevRT = RenderTexture.active; RenderTexture.active = renderTex; - texture.ReadPixels(new Rect(0, 0, textureSize.x, textureSize.y), 0, 0, true); - texture.Apply(); + texture.ReadPixels(new Rect(0, 0, textureSize.x, textureSize.y), 0, 0, false); + texture.Apply(false, true); RenderTexture.active = prevRT; GL.PopMatrix(); - + // RELEASE RENDERTEX - renderTex.Release(); - RenderTexture.Destroy(renderTex); + RenderTexture.ReleaseTemporary(renderTex); - // CLEAR SUBMESHES - _tmp.text = ""; + // DESTROY THE RENDERER OBJECT + Object.Destroy(tmpObject); - for (int i = 0; i < transform.childCount; i++) { - var child = transform.GetChild(i); - Destroy(child.gameObject); + return new TextRenderOutput(texture, window); + } + + /// Setup shader and texture + public static void ModuleManagerPostLoad() { + if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11 || SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D12) { + textRenderTextureFormat = RenderTextureFormat.ARGB32; // DirectX is dumb } - return new TextRenderOutput(texture, window); + if (!SystemInfo.SupportsTextureFormat(textTextureFormat)) { + Logging.LogError($"Text texture format {textTextureFormat} not supported on this platform."); + } + + if (!SystemInfo.SupportsRenderTextureFormat(textRenderTextureFormat)) { + Logging.LogError($"Text texture format {textRenderTextureFormat} not supported on this platform."); + } + + _blankTexture = Texture2D.blackTexture; + _blitShader = Shabby.Shabby.FindShader(ShaderName); + if (_blitShader == null) Logging.LogError($"Could not find text blit shader named '{ShaderName}'"); } + } } \ No newline at end of file diff --git a/Source/ConformalDecals/UI/TextEntryController.cs b/Source/ConformalDecals/UI/TextEntryController.cs index 8a783b0..d763217 100644 --- a/Source/ConformalDecals/UI/TextEntryController.cs +++ b/Source/ConformalDecals/UI/TextEntryController.cs @@ -3,7 +3,6 @@ using ConformalDecals.Text; using ConformalDecals.Util; using TMPro; using UnityEngine; -using UnityEngine.Events; using UnityEngine.UI; namespace ConformalDecals.UI { @@ -35,14 +34,15 @@ namespace ConformalDecals.UI { private Vector2 _lineSpacingRange; private Vector2 _charSpacingRange; private TMP_InputField _textBoxTMP; + private FontMenuController _fontMenu; private TextUpdateDelegate _onValueChanged; - private FontMenuController _fontMenu; + private static int _lockCounter; - private bool _ignoreUpdates; private bool _isLocked; private string _lockString; - private static int _lockCounter; + private bool _ignoreUpdates; + private bool _textUpdated; public static TextEntryController Create( string text, DecalFont font, FontStyles style, bool vertical, float linespacing, float charspacing, @@ -74,7 +74,7 @@ namespace ConformalDecals.UI { public void SetControlLock(string value = null) { if (_isLocked) return; - InputLockManager.SetControlLock(_lockString); + InputLockManager.SetControlLock(ControlTypes.EDITOR_UI, _lockString); _isLocked = true; } @@ -86,8 +86,7 @@ namespace ConformalDecals.UI { public void OnTextUpdate(string newText) { this._text = newText; - - OnValueChanged(); + _textUpdated = true; } public void OnFontMenu() { @@ -105,7 +104,7 @@ namespace ConformalDecals.UI { _textBoxTMP.fontAsset = _font.FontAsset; UpdateStyleButtons(); - OnValueChanged(); + _textUpdated = true; } public void OnLineSpacingUpdate(float value) { @@ -114,7 +113,7 @@ namespace ConformalDecals.UI { _lineSpacing = Mathf.Lerp(_lineSpacingRange.x, _lineSpacingRange.y, value); UpdateLineSpacing(); - OnValueChanged(); + _textUpdated = true; } public void OnLineSpacingUpdate(string text) { @@ -128,7 +127,7 @@ namespace ConformalDecals.UI { } UpdateLineSpacing(); - OnValueChanged(); + _textUpdated = true; } public void OnCharSpacingUpdate(float value) { @@ -137,7 +136,7 @@ namespace ConformalDecals.UI { _charSpacing = Mathf.Lerp(_charSpacingRange.x, _charSpacingRange.y, value); UpdateCharSpacing(); - OnValueChanged(); + _textUpdated = true; } public void OnCharSpacingUpdate(string text) { @@ -151,7 +150,7 @@ namespace ConformalDecals.UI { } UpdateCharSpacing(); - OnValueChanged(); + _textUpdated = true; } public void OnBoldUpdate(bool state) { @@ -163,7 +162,7 @@ namespace ConformalDecals.UI { _style &= ~FontStyles.Bold; _textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask; - OnValueChanged(); + _textUpdated = true; } public void OnItalicUpdate(bool state) { @@ -175,7 +174,7 @@ namespace ConformalDecals.UI { _style &= ~FontStyles.Italic; _textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask; - OnValueChanged(); + _textUpdated = true; } public void OnUnderlineUpdate(bool state) { @@ -187,7 +186,7 @@ namespace ConformalDecals.UI { _style &= ~FontStyles.Underline; _textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask; - OnValueChanged(); + _textUpdated = true; } public void OnSmallCapsUpdate(bool state) { @@ -199,19 +198,19 @@ namespace ConformalDecals.UI { _style &= ~FontStyles.SmallCaps; _textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask; - OnValueChanged(); + _textUpdated = true; } public void OnVerticalUpdate(bool state) { if (_ignoreUpdates) return; _vertical = state; - OnValueChanged(); + _textUpdated = true; } private void Start() { _lockString = $"ConformalDecals_TextEditor_{_lockCounter++}"; - + _textBoxTMP = ((TMP_InputField) _textBox); _textBoxTMP.text = _text; _textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask; @@ -229,9 +228,12 @@ namespace ConformalDecals.UI { private void OnDestroy() { RemoveControlLock(); } - - private void OnValueChanged() { - _onValueChanged(_text, _font, _style, _vertical, _lineSpacing, _charSpacing); + + private void LateUpdate() { + if (_textUpdated) { + _onValueChanged(_text, _font, _style, _vertical, _lineSpacing, _charSpacing); + _textUpdated = false; + } } private void UpdateStyleButtons() { diff --git a/changelog.txt b/changelog.txt index dbaba71..a5c5f46 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,12 @@ +v0.2.7 +------ +- Supported KSP versions: 1.8.x to 1.11.x +- Notes: + - Attaching decal parts in flight using engineer kerbals is not supported. +- Fixes: + - Fixed certain non-ascii strings not rendering correctly under certain circumstances. + - Yet another attempted fix for the planet text glitch. + v0.2.6 ------ - Fixes: