1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-16 04:25:23 +00:00

Fixed animation glitches. Started work in animation priority. Fixed memory issues

This commit is contained in:
Tim Sarbin 2018-12-11 18:15:50 -05:00
parent c277cb2d89
commit 6646efd0c3
12 changed files with 140 additions and 103 deletions

View File

@ -6,7 +6,7 @@ using System.Runtime.CompilerServices;
namespace OpenDiablo2.Common.Models
{
public sealed class ImageFrame : IDisposable
public sealed class ImageFrame
{
public UInt32 Flip { get; internal set; }
public UInt32 Width { get; internal set; }
@ -18,7 +18,8 @@ namespace OpenDiablo2.Common.Models
public UInt32 Length { get; internal set; }
public Int16[] ImageData { get; internal set; }
public void Dispose()
~ImageFrame()
{
ImageData = null;
}
@ -31,7 +32,7 @@ namespace OpenDiablo2.Common.Models
}
}
public sealed class ImageSet : IDisposable
public sealed class ImageSet
{
private UInt32[] framePointers;
public ImageFrame[] Frames { get; private set; }
@ -117,8 +118,10 @@ namespace OpenDiablo2.Common.Models
return result;
}
public void Dispose()
~ImageSet()
{
framePointers = null;
Frames = Array.Empty<ImageFrame>();
}
}
}

View File

@ -57,6 +57,10 @@ namespace OpenDiablo2.Common.Models
public IEnumerable<COFLayer> Layers { get; private set; }
public IEnumerable<eAnimationFrame> AnimationFrames { get; private set; }
public byte[] Priority { get; private set; }
public int NumberOfDirections { get; internal set; }
public int FramesPerDirection { get; internal set; }
public int NumberOfLayers { get; internal set; }
public static MPQCOF Load(Stream stream, Dictionary<string, List<AnimationData>> animations, eHero hero, eWeaponClass weaponClass, eMobMode mobMode, string ShieldCode, string weaponCode)
{
@ -69,14 +73,14 @@ namespace OpenDiablo2.Common.Models
var br = new BinaryReader(stream);
var numLayers = br.ReadByte();
var framesPerDir = br.ReadByte();
br.ReadByte(); // Number of directions
result.NumberOfLayers = br.ReadByte();
result.FramesPerDirection = br.ReadByte();
result.NumberOfDirections = br.ReadByte(); // Number of directions
br.ReadBytes(25); // Skip 25 unknown bytes...
var layers = new List<COFLayer>();
for (var layerIdx = 0; layerIdx < numLayers; layerIdx++)
for (var layerIdx = 0; layerIdx < result.NumberOfLayers; layerIdx++)
{
var layer = new COFLayer
{
@ -93,7 +97,8 @@ namespace OpenDiablo2.Common.Models
layer.WeaponCode = weaponCode;
}
result.Layers = layers;
result.AnimationFrames = br.ReadBytes(framesPerDir).Select(x => (eAnimationFrame)x);
result.AnimationFrames = br.ReadBytes(result.FramesPerDirection).Select(x => (eAnimationFrame)x);
result.Priority = br.ReadBytes(result.FramesPerDirection * result.NumberOfLayers * result.NumberOfDirections);
var cofName = $"{hero.ToToken()}{mobMode.ToToken()}{weaponClass.ToToken()}".ToUpper();
result.Animations = animations[cofName];

View File

@ -112,6 +112,7 @@ namespace OpenDiablo2.Core
if (nextScene!= null)
{
currentScene.Dispose();
currentScene = nextScene;
nextScene = null;
continue;
@ -131,6 +132,13 @@ namespace OpenDiablo2.Core
}
public void ChangeScene(eSceneType sceneType)
=> nextScene = getScene(sceneType);
{
var loadingSprite = getRenderWindow().LoadSprite(ResourcePaths.LoadingScreen, Palettes.Loading, new Point(300, 400));
getRenderWindow().Clear();
getRenderWindow().Draw(loadingSprite);
getRenderWindow().Sync();
nextScene = getScene(sceneType);
}
}
}

View File

@ -269,10 +269,8 @@ namespace OpenDiablo2.Core.GameState_
private IMapInfo GetMap(ref int cellX, ref int cellY)
{
var x = cellX;
var y = cellY;
var map = mapInfo.LastOrDefault(z => (x >= z.TileLocation.X) && (y >= z.TileLocation.Y)
&& (x < z.TileLocation.Right) && (y < z.TileLocation.Bottom));
var p = new Point(cellX, cellY);
var map = mapInfo.LastOrDefault(z => z.TileLocation.Contains(p));
if (map == null)
{
return null;

View File

@ -43,7 +43,8 @@ namespace OpenDiablo2.Core
}
public ImageSet GetImageSet(string resourcePath)
=> cache.AddOrGetExisting($"ImageSet::{resourcePath}", () => ImageSet.LoadFromStream(mpqProvider.GetStream(resourcePath)));
// => cache.AddOrGetExisting($"ImageSet::{resourcePath}", () => ImageSet.LoadFromStream(mpqProvider.GetStream(resourcePath)));
=> ImageSet.LoadFromStream(mpqProvider.GetStream(resourcePath));
public MPQFont GetMPQFont(string resourcePath)
=> cache.AddOrGetExisting($"Font::{resourcePath}", () => MPQFont.LoadFromStream(mpqProvider.GetStream($"{resourcePath}.DC6"), mpqProvider.GetStream($"{resourcePath}.tbl")));
@ -68,46 +69,29 @@ namespace OpenDiablo2.Core
{
var path = $"{ResourcePaths.PlayerAnimationBase}\\{hero.ToToken()}\\COF\\{hero.ToToken()}{mobMode.ToToken()}{weaponClass.ToToken()}.cof";
return MPQCOF.Load(mpqProvider.GetStream(path), Animations, hero, weaponClass, mobMode, shieldCode, weaponCode);
});
}, new System.Runtime.Caching.CacheItemPolicy { Priority = System.Runtime.Caching.CacheItemPriority.NotRemovable });
public MPQDCC GetPlayerDCC(MPQCOF.COFLayer cofLayer, eArmorType armorType, Palette palette)
{
// TODO: We need to cache this...
byte[] binaryData;
return cache.AddOrGetExisting($"PlayerDCC::{cofLayer.GetDCCPath(armorType)}", () =>
{
byte[] binaryData;
var streamPath = cofLayer.GetDCCPath(armorType);
using (var stream = mpqProvider.GetStream(streamPath))
{
if (stream == null)
{
log.Error($"Could not load Player DCC: {streamPath}");
return null;
}
var streamPath = cofLayer.GetDCCPath(armorType);
using (var stream = mpqProvider.GetStream(streamPath))
{
if (stream == null)
{
log.Error($"Could not load Player DCC: {streamPath}");
return null;
}
binaryData = new byte[stream.Length];
stream.Read(binaryData, 0, (int)stream.Length);
}
var result = new MPQDCC(binaryData, palette);
return result;
binaryData = new byte[stream.Length];
stream.Read(binaryData, 0, (int)stream.Length);
}
var result = new MPQDCC(binaryData, palette);
return result;
});
}
/*
=> cache.AddOrGetExisting($"DCC::{cofLayer}::{armorType}::{palette.Name}", () =>
{
byte[] binaryData;
using (var stream = mpqProvider.GetStream(cofLayer.GetDCCPath(armorType)))
{
if (stream == null)
return null;
binaryData = new byte[stream.Length];
stream.Read(binaryData, 0, (int)stream.Length);
}
var result = new MPQDCC(binaryData, palette);
return result;
});
/*
*/
}
}

View File

@ -13,7 +13,7 @@ namespace OpenDiablo2.SDL2_
log.Info("Configuring OpenDiablo2.Core service implementations.");
builder.RegisterType<SDL2RenderWindow>().AsImplementedInterfaces().SingleInstance();
builder.RegisterType<SDL2MusicProvider>().AsImplementedInterfaces().SingleInstance();
builder.RegisterType<SDL2SoundProvider>().AsImplementedInterfaces().SingleInstance();
}
}
}

View File

@ -38,7 +38,6 @@ namespace OpenDiablo2.SDL2_
public IntPtr[] SpriteTexture { get; set; }
public int FramesToAnimate { get; set; }
public int AnimationSpeed { get; set; }
public int RenderFrameIndex { get; set; }
}
static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
@ -57,6 +56,7 @@ namespace OpenDiablo2.SDL2_
private readonly List<DirectionCacheItem> directionCache = new List<DirectionCacheItem>();
DirectionCacheItem currentDirectionCache;
private float seconds;
private int renderFrameIndex = 0;
private readonly IResourceManager resourceManager;
private readonly IPaletteProvider paletteProvider;
@ -79,13 +79,13 @@ namespace OpenDiablo2.SDL2_
var destRect = new SDL.SDL_Rect
{
x = pixelOffsetX + currentDirectionCache.SpriteRect[currentDirectionCache.RenderFrameIndex].x,
y = pixelOffsetY + currentDirectionCache.SpriteRect[currentDirectionCache.RenderFrameIndex].y,
w = currentDirectionCache.SpriteRect[currentDirectionCache.RenderFrameIndex].w,
h = currentDirectionCache.SpriteRect[currentDirectionCache.RenderFrameIndex].h
x = pixelOffsetX + currentDirectionCache.SpriteRect[renderFrameIndex].x,
y = pixelOffsetY + currentDirectionCache.SpriteRect[renderFrameIndex].y,
w = currentDirectionCache.SpriteRect[renderFrameIndex].w,
h = currentDirectionCache.SpriteRect[renderFrameIndex].h
};
SDL.SDL_RenderCopy(renderer, currentDirectionCache.SpriteTexture[currentDirectionCache.RenderFrameIndex], IntPtr.Zero, ref destRect);
SDL.SDL_RenderCopy(renderer, currentDirectionCache.SpriteTexture[renderFrameIndex], IntPtr.Zero, ref destRect);
}
public void Update(long ms)
@ -98,9 +98,9 @@ namespace OpenDiablo2.SDL2_
while (seconds >= animationSeg)
{
seconds -= animationSeg;
currentDirectionCache.RenderFrameIndex++;
if (currentDirectionCache.RenderFrameIndex >= currentDirectionCache.FramesToAnimate)
currentDirectionCache.RenderFrameIndex = 0;
renderFrameIndex++;
while (renderFrameIndex >= currentDirectionCache.FramesToAnimate)
renderFrameIndex -= currentDirectionCache.FramesToAnimate;
}
}
@ -111,6 +111,7 @@ namespace OpenDiablo2.SDL2_
public void ResetAnimationData()
{
var lastMobMode = MobMode;
switch (LocationDetails.MovementType)
{
case eMovementType.Stopped:
@ -126,23 +127,22 @@ namespace OpenDiablo2.SDL2_
MobMode = eMobMode.PlayerNeutral;
break;
}
if (lastMobMode != MobMode)
renderFrameIndex = 0;
currentDirectionCache = directionCache.FirstOrDefault(x => x.MobMode == MobMode && x.Direction == directionConversion[LocationDetails.MovementDirection]);
if (currentDirectionCache != null)
{
currentDirectionCache.RenderFrameIndex = 0;
return;
}
animationData = resourceManager.GetPlayerAnimation(Hero, WeaponClass, MobMode, ShieldCode, WeaponCode);
if (animationData == null)
throw new OpenDiablo2Exception("Could not locate animation for the character!");
var palette = paletteProvider.PaletteTable["Units"];
CacheFrames(animationData.Layers.Select(layer => resourceManager.GetPlayerDCC(layer, ArmorType, palette)));
CacheFrames(animationData.Layers.Select(layer => resourceManager.GetPlayerDCC(layer, ArmorType, palette)).ToArray());
}
private unsafe void CacheFrames(IEnumerable<MPQDCC> layerData)
private unsafe void CacheFrames(MPQDCC[] layerData)
{
var directionCache = new DirectionCacheItem
{
@ -155,7 +155,6 @@ namespace OpenDiablo2.SDL2_
var dirAnimation = animationData.Animations[0];
directionCache.FramesToAnimate = dirAnimation.FramesPerDirection;
directionCache.AnimationSpeed = dirAnimation.AnimationSpeed;
directionCache.RenderFrameIndex = 0;
var minX = Int32.MaxValue;
var minY = Int32.MaxValue;
@ -163,7 +162,6 @@ namespace OpenDiablo2.SDL2_
var maxY = Int32.MinValue;
var layersIgnored = 0;
var layersToRender = new List<MPQDCC>();
foreach (var layer in layerData)
{
if (layer == null)
@ -172,7 +170,6 @@ namespace OpenDiablo2.SDL2_
continue;
}
layersToRender.Add(layer);
minX = Math.Min(minX, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Left);
minY = Math.Min(minY, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Top);
maxX = Math.Max(maxX, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Right);
@ -187,7 +184,7 @@ namespace OpenDiablo2.SDL2_
directionCache.SpriteTexture = new IntPtr[directionCache.FramesToAnimate];
directionCache.SpriteRect = new SDL.SDL_Rect[directionCache.FramesToAnimate];
for (var frameIndex = 0; frameIndex < directionCache.FramesToAnimate; frameIndex++)
{
var texture = SDL.SDL_CreateTexture(renderer, SDL.SDL_PIXELFORMAT_ARGB8888, (int)SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, frameW, frameH);
@ -195,8 +192,21 @@ namespace OpenDiablo2.SDL2_
SDL.SDL_LockTexture(texture, IntPtr.Zero, out IntPtr pixels, out int pitch);
UInt32* data = (UInt32*)pixels;
foreach (var layer in layersToRender)
var priorities = new int[animationData.NumberOfLayers];
Array.Copy(
animationData.Priority,
(directionConversion[LocationDetails.MovementDirection] * animationData.FramesPerDirection * animationData.NumberOfLayers)
+ (frameIndex * animationData.NumberOfLayers),
priorities,
0,
animationData.NumberOfLayers
);
for (var i = 0; i < layerData.Length; i++)
{
//var layer = layerData[priorities[i]];
var layer = layerData[i];
if (layer == null)
continue;
@ -221,18 +231,19 @@ namespace OpenDiablo2.SDL2_
}
}
}
SDL.SDL_UnlockTexture(texture);
SDL.SDL_SetTextureBlendMode(texture, SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND);
directionCache.SpriteTexture[frameIndex] = texture;
directionCache.SpriteRect[frameIndex] = new SDL.SDL_Rect { x = minX, y = minY, w = frameW, h = frameH };
this.directionCache.Add(directionCache);
currentDirectionCache = directionCache;
}
currentDirectionCache = directionCache;
}
}

View File

@ -22,14 +22,14 @@ using SDL2;
namespace OpenDiablo2.SDL2_
{
public sealed class SDL2MusicProvider : ISoundProvider
public sealed class SDL2SoundProvider : ISoundProvider
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private IntPtr music = IntPtr.Zero;
private int musicChannel;
private byte[] musicBytes; // Cannot be local or GC will destory it with great anger
public SDL2MusicProvider()
public SDL2SoundProvider()
{
if (SDL_mixer.Mix_OpenAudio(22050, SDL_mixer.MIX_DEFAULT_FORMAT, 2, 1024) < 0)
log.Error($"SDL_mixer could not initialize! SDL_mixer Error: {SDL.SDL_GetError()}");

View File

@ -26,7 +26,7 @@ namespace OpenDiablo2.SDL2_
{
internal sealed class SDL2Sprite : ISprite
{
internal readonly ImageSet source;
internal ImageSet source;
private readonly IntPtr renderer;
private readonly bool cacheFrames;
@ -96,7 +96,7 @@ namespace OpenDiablo2.SDL2_
SDL.SDL_SetTextureBlendMode(texture[i], blend ? SDL.SDL_BlendMode.SDL_BLENDMODE_ADD : SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND);
}
else
if (texture[TextureIndex] != IntPtr.Zero)
if (texture != null && texture[TextureIndex] != IntPtr.Zero)
SDL.SDL_SetTextureBlendMode(texture[TextureIndex], blend ? SDL.SDL_BlendMode.SDL_BLENDMODE_ADD : SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND);
}
@ -206,6 +206,23 @@ namespace OpenDiablo2.SDL2_
var framestoClear = cacheFrames ? TotalFrames : 1;
for (int i = 0; i < framestoClear; i++)
frameLoaded[i] = false;
DestroyTextures();
}
private void DestroyTextures()
{
var framestoClear = cacheFrames ? TotalFrames : 1;
for (var i = 0; i < framestoClear; i++)
{
if (!frameLoaded[i])
continue;
SDL.SDL_DestroyTexture(texture[i]);
texture[i] = IntPtr.Zero;
frameLoaded[i] = false;
}
texture = new IntPtr[TotalFrames];
}
public void Dispose()
@ -213,16 +230,11 @@ namespace OpenDiablo2.SDL2_
if (disposed)
return;
var framestoClear = cacheFrames ? TotalFrames : 1;
for (var i = 0; i < framestoClear; i++)
{
SDL.SDL_DestroyTexture(texture[i]);
texture[i] = IntPtr.Zero;
}
texture = Array.Empty<IntPtr>();
DestroyTextures();
source = null;
disposed = true;
}
}
}

View File

@ -77,18 +77,6 @@ namespace OpenDiablo2.Scenes
urlLabel = renderWindow.CreateLabel(labelFont, new Point(50, 569), "https://github.com/essial/OpenDiablo2/");
urlLabel.TextColor = Color.Magenta;
var loadingSprite = renderWindow.LoadSprite(ResourcePaths.LoadingScreen, Palettes.Loading, new Point(300, 400));
// Pre-load all the scenes for now until we fix the sdl load problem
var scenesToLoad = new eSceneType[] { eSceneType.SelectHeroClass };
for (int i = 0; i < scenesToLoad.Count(); i++)
{
renderWindow.Clear();
renderWindow.Draw(loadingSprite, (int)(loadingSprite.TotalFrames * (i / (float)scenesToLoad.Count())));
renderWindow.Sync();
getScene(scenesToLoad[i]);
}
soundProvider.LoadSong(mpqProvider.GetStream(ResourcePaths.BGMTitle));
soundProvider.PlaySong();
}

View File

@ -20,13 +20,25 @@ namespace OpenDiablo2.Scenes
Retreating
}
class HeroRenderInfo
class HeroRenderInfo : IDisposable
{
public ISprite IdleSprite, IdleSelectedSprite, ForwardWalkSprite, ForwardWalkSpriteOverlay, SelectedSprite, SelectedSpriteOverlay, BackWalkSprite, BackWalkSpriteOverlay;
public eHeroStance Stance;
public long ForwardWalkTimeMs, BackWalkTimeMs;
public long SpecialFrameTime;
public Rectangle SelectionBounds = new Rectangle();
public void Dispose()
{
IdleSprite?.Dispose();
IdleSelectedSprite?.Dispose();
ForwardWalkSprite?.Dispose();
ForwardWalkSpriteOverlay?.Dispose();
SelectedSprite?.Dispose();
SelectedSpriteOverlay?.Dispose();
BackWalkSprite?.Dispose();
BackWalkSpriteOverlay?.Dispose();
}
}
[Scene(eSceneType.SelectHeroClass)]
@ -267,7 +279,7 @@ namespace OpenDiablo2.Scenes
StopSfx();
var heros = Enum.GetValues(typeof(eHero)).Cast<eHero>();
foreach(var hero in heros)
foreach (var hero in heros)
{
heroRenderInfo[hero].SpecialFrameTime = 0;
heroRenderInfo[hero].Stance = eHeroStance.Idle;
@ -460,7 +472,7 @@ namespace OpenDiablo2.Scenes
renderInfo.SpecialFrameTime = 0;
foreach(var ri in heroRenderInfo)
foreach (var ri in heroRenderInfo)
{
if (ri.Value.Stance != eHeroStance.Selected)
continue;
@ -611,6 +623,12 @@ namespace OpenDiablo2.Scenes
headingFont.Dispose();
headingLabel.Dispose();
sfxDictionary.Clear();
foreach (var hri in heroRenderInfo)
hri.Value.Dispose();
heroRenderInfo.Clear();
}
}
}

View File

@ -1,8 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
<system.runtime.caching>
<memoryCache>
<namedCaches>
<add name="OpenDiablo2.Cache"
cacheMemoryLimitMegabytes="0"
physicalMemoryLimitPercentage="0"
pollingInterval="00:00:05" />
</namedCaches>
</memoryCache>
</system.runtime.caching>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>