Skip to content

Commit

Permalink
[unity] Implemented first experimental preview package version of del…
Browse files Browse the repository at this point in the history
…ayed on-demand loading of Atlas assets. See #1890.
  • Loading branch information
HaraldCsaszar committed Sep 8, 2023
1 parent ec0ff67 commit 76e8538
Show file tree
Hide file tree
Showing 40 changed files with 1,471 additions and 4 deletions.
Expand Up @@ -42,7 +42,7 @@ namespace Spine.Unity.Editor {

[CustomEditor(typeof(SpineAtlasAsset)), CanEditMultipleObjects]
public class SpineAtlasAssetInspector : UnityEditor.Editor {
SerializedProperty atlasFile, materials;
SerializedProperty atlasFile, materials, textureLoadingMode, onDemandTextureLoader;
SpineAtlasAsset atlasAsset;

GUIContent spriteSlicesLabel;
Expand Down Expand Up @@ -70,6 +70,8 @@ public class SpineAtlasAssetInspector : UnityEditor.Editor {
SpineEditorUtilities.ConfirmInitialization();
atlasFile = serializedObject.FindProperty("atlasFile");
materials = serializedObject.FindProperty("materials");
textureLoadingMode = serializedObject.FindProperty("textureLoadingMode");
onDemandTextureLoader = serializedObject.FindProperty("onDemandTextureLoader");
materials.isExpanded = true;
atlasAsset = (SpineAtlasAsset)target;
#if REGION_BAKING_MESH
Expand Down Expand Up @@ -132,6 +134,12 @@ public class SpineAtlasAssetInspector : UnityEditor.Editor {
}
}

if (textureLoadingMode != null) {
EditorGUILayout.Space();
EditorGUILayout.PropertyField(textureLoadingMode);
EditorGUILayout.PropertyField(onDemandTextureLoader);
}

EditorGUILayout.Space();
if (SpineInspectorUtility.LargeCenteredButton(SpineInspectorUtility.TempContent("Set Mipmap Bias to " + SpinePreferences.DEFAULT_MIPMAPBIAS, tooltip: "This may help textures with mipmaps be less blurry when used for 2D sprites."))) {
foreach (Material m in atlasAsset.materials) {
Expand Down
Expand Up @@ -46,6 +46,8 @@
#define HAS_SAVE_ASSET_IF_DIRTY
#endif

#define SPINE_OPTIONAL_ON_DEMAND_LOADING

using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Build;
Expand All @@ -60,6 +62,9 @@ public class SpineBuildProcessor {

#if HAS_ON_POSTPROCESS_PREFAB
static List<string> prefabsToRestore = new List<string>();
#endif
#if SPINE_OPTIONAL_ON_DEMAND_LOADING
static List<string> textureLoadersToRestore = new List<string>();
#endif
static Dictionary<string, string> spriteAtlasTexturesToRestore = new Dictionary<string, string>();

Expand All @@ -68,6 +73,9 @@ public class SpineBuildProcessor {
#if HAS_ON_POSTPROCESS_PREFAB
if (SpineEditorUtilities.Preferences.removePrefabPreviewMeshes)
PreprocessSpinePrefabMeshes();
#endif
#if SPINE_OPTIONAL_ON_DEMAND_LOADING
PreprocessOnDemandTextureLoaders();
#endif
PreprocessSpriteAtlases();
}
Expand All @@ -77,6 +85,9 @@ public class SpineBuildProcessor {
#if HAS_ON_POSTPROCESS_PREFAB
if (SpineEditorUtilities.Preferences.removePrefabPreviewMeshes)
PostprocessSpinePrefabMeshes();
#endif
#if SPINE_OPTIONAL_ON_DEMAND_LOADING
PostprocessOnDemandTextureLoaders();
#endif
PostprocessSpriteAtlases();
}
Expand Down Expand Up @@ -132,6 +143,66 @@ public class SpineBuildProcessor {
}
}
#endif

#if SPINE_OPTIONAL_ON_DEMAND_LOADING
internal static void PreprocessOnDemandTextureLoaders () {
BuildUtilities.IsInSkeletonAssetBuildPreProcessing = true;
try {
AssetDatabase.StartAssetEditing();
textureLoadersToRestore.Clear();
string[] loaderAssets = AssetDatabase.FindAssets("t:OnDemandTextureLoader");
foreach (string loaderAsset in loaderAssets) {
string assetPath = AssetDatabase.GUIDToAssetPath(loaderAsset);
OnDemandTextureLoader loader = AssetDatabase.LoadAssetAtPath<OnDemandTextureLoader>(assetPath);
bool isLoaderUsed = loader.atlasAsset && loader.atlasAsset.OnDemandTextureLoader == loader &&
loader.atlasAsset.TextureLoadingMode == AtlasAssetBase.LoadingMode.OnDemand;
if (isLoaderUsed) {
IEnumerable<Material> modifiedMaterials;
textureLoadersToRestore.Add(assetPath);
loader.AssignPlaceholderTextures(out modifiedMaterials);

#if HAS_SAVE_ASSET_IF_DIRTY
foreach (Material material in modifiedMaterials) {
AssetDatabase.SaveAssetIfDirty(material);
}
#endif
}
}
EditorUtility.UnloadUnusedAssetsImmediate();
AssetDatabase.StopAssetEditing();
#if !HAS_SAVE_ASSET_IF_DIRTY
if (textureLoadersToRestore.Length > 0)
AssetDatabase.SaveAssets();
#endif
} finally {
BuildUtilities.IsInSkeletonAssetBuildPreProcessing = false;
}
}

internal static void PostprocessOnDemandTextureLoaders () {
BuildUtilities.IsInSkeletonAssetBuildPostProcessing = true;
try {
foreach (string assetPath in textureLoadersToRestore) {
OnDemandTextureLoader loader = AssetDatabase.LoadAssetAtPath<OnDemandTextureLoader>(assetPath);
IEnumerable<Material> modifiedMaterials;
loader.AssignTargetTextures(out modifiedMaterials);
#if HAS_SAVE_ASSET_IF_DIRTY
foreach (Material material in modifiedMaterials) {
AssetDatabase.SaveAssetIfDirty(material);
}
#endif
}
#if !HAS_SAVE_ASSET_IF_DIRTY
if (textureLoadersToRestore.Count > 0)
AssetDatabase.SaveAssets();
#endif
textureLoadersToRestore.Clear();

} finally {
BuildUtilities.IsInSkeletonAssetBuildPostProcessing = false;
}
}
#endif
internal static void PreprocessSpriteAtlases () {
BuildUtilities.IsInSpriteAtlasBuildPreProcessing = true;
try {
Expand Down
Expand Up @@ -238,7 +238,7 @@ public partial class SpineEditorUtilities : AssetPostprocessor {
}

public static void ConfirmInitialization () {
if (!initialized || Icons.skeleton == null)
if (!initialized)
Initialize();
}

Expand Down
Expand Up @@ -27,6 +27,8 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/

#define SPINE_OPTIONAL_ON_DEMAND_LOADING

using System.Collections.Generic;
using UnityEngine;

Expand All @@ -39,5 +41,38 @@ public abstract class AtlasAssetBase : ScriptableObject {
public abstract bool IsLoaded { get; }
public abstract void Clear ();
public abstract Atlas GetAtlas (bool onlyMetaData = false);

#if SPINE_OPTIONAL_ON_DEMAND_LOADING
public enum LoadingMode {
Normal = 0,
OnDemand
}
public virtual LoadingMode TextureLoadingMode {
get { return textureLoadingMode; }
set { textureLoadingMode = value; }
}
public OnDemandTextureLoader OnDemandTextureLoader {
get { return onDemandTextureLoader; }
set { onDemandTextureLoader = value; }
}

public virtual void BeginCustomTextureLoading () {
if (onDemandTextureLoader)
onDemandTextureLoader.BeginCustomTextureLoading();
}

public virtual void EndCustomTextureLoading () {
if (onDemandTextureLoader)
onDemandTextureLoader.EndCustomTextureLoading();
}

public virtual void RequireTexturesLoaded (Material material, ref Material overrideMaterial) {
if (onDemandTextureLoader)
onDemandTextureLoader.RequestLoadMaterialTextures(material, ref overrideMaterial);
}

[SerializeField] protected LoadingMode textureLoadingMode = LoadingMode.Normal;
[SerializeField] protected OnDemandTextureLoader onDemandTextureLoader = null;
#endif
}
}
@@ -0,0 +1,90 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/

#define SPINE_OPTIONAL_ON_DEMAND_LOADING

using System.Collections.Generic;
using UnityEngine;

#if SPINE_OPTIONAL_ON_DEMAND_LOADING
namespace Spine.Unity {
public abstract class OnDemandTextureLoader : ScriptableObject {
public AtlasAssetBase atlasAsset;

/// <param name="originalTextureName">Original texture name without extension.</param>
/// <returns>The placeholder texture's name for a given original target texture name.</returns>
public abstract string GetPlaceholderTextureName (string originalTextureName);
/// <summary>
/// Assigns previously setup placeholder textures at each Material of the associated AtlasAssetBase.</summary>
/// <returns>True on success, false if the placeholder texture could not be assigned at any of the
/// AtlasAssetBase's materials.</returns>
public abstract bool AssignPlaceholderTextures (out IEnumerable<Material> modifiedMaterials);
/// <summary>
/// Returns whether any placeholder textures are assigned at the Material of the associated AtlasAssetBase.
/// </summary>
/// <param name="placeholderMaterials">A newly created list of materials which has a placeholder texture assigned.</param>
/// <returns>True, if any placeholder texture is assigned at a Material of the associated AtlasAssetBase.</returns>
public abstract bool HasPlaceholderTexturesAssigned (out List<Material> placeholderMaterials);
/// <summary>
/// Assigns previously setup target textures at each Material where placeholder textures are setup.</summary>
/// <returns>True on success, false if the target texture could not be assigned at any of the
/// AtlasAssetBase's materials.</returns>
public abstract bool AssignTargetTextures (out IEnumerable<Material> modifiedMaterials);
public abstract void BeginCustomTextureLoading ();
public abstract void EndCustomTextureLoading ();
public abstract bool HasPlaceholderAssigned (Material material);
public abstract void RequestLoadMaterialTextures (Material material, ref Material overrideMaterial);
public abstract void Clear (bool clearAtlasAsset = false);

#region Event delegates
public delegate void TextureLoadDelegate (OnDemandTextureLoader loader, Material material, int textureIndex);
protected event TextureLoadDelegate onTextureLoaded;
protected event TextureLoadDelegate onTextureUnloaded;

public event TextureLoadDelegate TextureLoaded {
add { onTextureLoaded += value; }
remove { onTextureLoaded -= value; }
}
public event TextureLoadDelegate TextureUnloaded {
add { onTextureUnloaded += value; }
remove { onTextureUnloaded -= value; }
}

protected void OnTextureLoaded (Material material, int textureIndex) {
if (onTextureLoaded != null)
onTextureLoaded(this, material, textureIndex);
}
protected void OnTextureUnloaded (Material material, int textureIndex) {
if (onTextureUnloaded != null)
onTextureUnloaded(this, material, textureIndex);
}
#endregion
}
}
#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Expand Up @@ -263,7 +263,10 @@ public class MaterialsTextureLoader : TextureLoader {
Debug.LogError("Material is missing texture: " + other.name, other);
return;
}
if (other.mainTexture.name == name) {
string textureName = other.mainTexture.name;
if (textureName == name ||
(atlasAsset.OnDemandTextureLoader != null &&
textureName == atlasAsset.OnDemandTextureLoader.GetPlaceholderTextureName(name))) {
material = other;
break;
}
Expand Down
Expand Up @@ -49,6 +49,7 @@

#define SPINE_OPTIONAL_RENDEROVERRIDE
#define SPINE_OPTIONAL_MATERIALOVERRIDE
#define SPINE_OPTIONAL_ON_DEMAND_LOADING

using System.Collections.Generic;
using UnityEngine;
Expand Down Expand Up @@ -565,6 +566,10 @@ public class SpriteMaskInteractionMaterials {
AssignSpriteMaskMaterials();
}
#endif
#if SPINE_OPTIONAL_ON_DEMAND_LOADING
if (Application.isPlaying)
HandleOnDemandLoading();
#endif

#if PER_MATERIAL_PROPERTY_BLOCKS
if (fixDrawOrder && meshRenderer.sharedMaterials.Length > 2) {
Expand Down Expand Up @@ -743,6 +748,23 @@ public class SpriteMaskInteractionMaterials {

#endif //#if BUILT_IN_SPRITE_MASK_COMPONENT

#if SPINE_OPTIONAL_ON_DEMAND_LOADING
void HandleOnDemandLoading () {
foreach (AtlasAssetBase atlasAsset in skeletonDataAsset.atlasAssets) {
if (atlasAsset.TextureLoadingMode != AtlasAssetBase.LoadingMode.Normal) {
atlasAsset.BeginCustomTextureLoading();
for (int i = 0, count = meshRenderer.sharedMaterials.Length; i < count; ++i) {
Material overrideMaterial = null;
atlasAsset.RequireTexturesLoaded(meshRenderer.sharedMaterials[i], ref overrideMaterial);
if (overrideMaterial != null)
meshRenderer.sharedMaterials[i] = overrideMaterial;
}
atlasAsset.EndCustomTextureLoading();
}
}
}
#endif

#if PER_MATERIAL_PROPERTY_BLOCKS
private MaterialPropertyBlock reusedPropertyBlock;
public static readonly int SUBMESH_DUMMY_PARAM_ID = Shader.PropertyToID("_Submesh");
Expand Down
2 changes: 1 addition & 1 deletion spine-unity/Assets/Spine/package.json
Expand Up @@ -2,7 +2,7 @@
"name": "com.esotericsoftware.spine.spine-unity",
"displayName": "spine-unity Runtime",
"description": "This plugin provides the spine-unity runtime core.",
"version": "4.1.20",
"version": "4.1.21",
"unity": "2018.3",
"author": {
"name": "Esoteric Software",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -0,0 +1,7 @@
## Spine Addressables Extensions [Experimental]

This experimental plugin provides integration of Addressables on-demand texture loading for the spine-unity runtime. Please be sure to test this package first and create backups of your project before using.

### Usage

First declare your target Material textures as addressable. Then select the SpineAtlasAsset, right-click the SpineAtlasAsset Inspector heading and select 'Add Addressables Loader'. This generates an 'AddressableTextureLoader' asset providing configuration parameters and sets up low-resolution placeholder textures which are automatically assigned in a pre-build step when building your game executable.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 76e8538

Please sign in to comment.