From 22cc31135e2989df28e0756eb3b03f85530d5555 Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Tue, 29 Sep 2009 18:15:22 -0700 Subject: Attempting to improve the robustness of texture decoding by always ignoring LayerInfo.End values and creating guessed default layer boundaries on failed decodes Changed a noisy J2K decode log message from Info to Debug Replacing openjpeg-dotnet decoding with managed CSJ2K decoding. Should be much more reliable, faster, and use less memory * Re-added openjpeg-dotnet files since they are used elsewhere in OpenSim * Updated prebuild.xml with a reference to CSJ2K --- .../Agent/TextureSender/J2KDecoderModule.cs | 236 ++++++--------------- 1 file changed, 68 insertions(+), 168 deletions(-) (limited to 'OpenSim/Region/CoreModules') diff --git a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs index 937f76b..aa29947 100644 --- a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs +++ b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs @@ -34,8 +34,8 @@ using System.Threading; using log4net; using Nini.Config; using OpenMetaverse; -using OpenMetaverse.Assets; using OpenMetaverse.Imaging; +using CSJ2K; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; @@ -195,174 +195,58 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender { int DecodeTime = 0; DecodeTime = Environment.TickCount; - OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[0]; // Dummy result for if it fails. Informs that there's only full quality + OpenJPEG.J2KLayerInfo[] layers = null; - if (!OpenJpegFail) + if (!fCache.TryLoadCacheForAsset(AssetId, out layers)) { - if (!fCache.TryLoadCacheForAsset(AssetId, out layers)) + try { - try + List layerStarts = CSJ2K.J2kImage.GetLayerBoundaries(new MemoryStream(j2kdata)); + + if (layerStarts != null && layerStarts.Count > 0) { + layers = new OpenJPEG.J2KLayerInfo[layerStarts.Count]; - AssetTexture texture = new AssetTexture(AssetId, j2kdata); - if (texture.DecodeLayerBoundaries()) + for (int i = 0; i < layerStarts.Count; i++) { - bool sane = true; - - // Sanity check all of the layers - for (int i = 0; i < texture.LayerInfo.Length; i++) - { - if (texture.LayerInfo[i].End > texture.AssetData.Length) - { - sane = false; - break; - } - } - - if (sane) - { - layers = texture.LayerInfo; - fCache.SaveFileCacheForAsset(AssetId, layers); - - - // Write out decode time - m_log.InfoFormat("[J2KDecoderModule]: {0} Decode Time: {1}", Environment.TickCount - DecodeTime, - AssetId); - - } + OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); + int start = layerStarts[i]; + + if (i == 0) + layer.Start = 0; else - { - m_log.WarnFormat( - "[J2KDecoderModule]: JPEG2000 texture decoding succeeded, but sanity check failed for {0}", - AssetId); - } - } + layer.Start = layerStarts[i]; - else - { - /* - Random rnd = new Random(); - // scramble ends for test - for (int i = 0; i < texture.LayerInfo.Length; i++) - { - texture.LayerInfo[i].End = rnd.Next(999999); - } - */ - - // Try to do some heuristics error correction! Yeah. - bool sane2Heuristics = true; - - - if (texture.Image == null) - sane2Heuristics = false; - - if (texture.LayerInfo == null) - sane2Heuristics = false; - - if (sane2Heuristics) - { - - - if (texture.LayerInfo.Length == 0) - sane2Heuristics = false; - } - - if (sane2Heuristics) - { - // Last layer start is less then the end of the file and last layer start is greater then 0 - if (texture.LayerInfo[texture.LayerInfo.Length - 1].Start < texture.AssetData.Length && texture.LayerInfo[texture.LayerInfo.Length - 1].Start > 0) - { - } - else - { - sane2Heuristics = false; - } - - } - - if (sane2Heuristics) - { - int start = 0; - - // try to fix it by using consistant data in the start field - for (int i = 0; i < texture.LayerInfo.Length; i++) - { - if (i == 0) - start = 0; - - if (i == texture.LayerInfo.Length - 1) - texture.LayerInfo[i].End = texture.AssetData.Length; - else - texture.LayerInfo[i].End = texture.LayerInfo[i + 1].Start - 1; - - // in this case, the end of the next packet is less then the start of the last packet - // after we've attempted to fix it which means the start of the last packet is borked - // there's no recovery from this - if (texture.LayerInfo[i].End < start) - { - sane2Heuristics = false; - break; - } - - if (texture.LayerInfo[i].End < 0 || texture.LayerInfo[i].End > texture.AssetData.Length) - { - sane2Heuristics = false; - break; - } - - if (texture.LayerInfo[i].Start < 0 || texture.LayerInfo[i].Start > texture.AssetData.Length) - { - sane2Heuristics = false; - break; - } - - start = texture.LayerInfo[i].Start; - } - } - - if (sane2Heuristics) - { - layers = texture.LayerInfo; - fCache.SaveFileCacheForAsset(AssetId, layers); - - - // Write out decode time - m_log.InfoFormat("[J2KDecoderModule]: HEURISTICS SUCCEEDED {0} Decode Time: {1}", Environment.TickCount - DecodeTime, - AssetId); - - } + if (i == layerStarts.Count - 1) + layer.End = j2kdata.Length; else - { - m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding failed for {0}. Is this a texture? is it J2K?", AssetId); - } + layer.End = layerStarts[i + 1] - 1; + + layers[i] = layer; } - texture = null; // dereference and dispose of ManagedImage - } - catch (DllNotFoundException) - { - m_log.Error( - "[J2KDecoderModule]: OpenJpeg is not installed properly. Decoding disabled! This will slow down texture performance! Often times this is because of an old version of GLIBC. You must have version 2.4 or above!"); - OpenJpegFail = true; - } - catch (Exception ex) - { - m_log.WarnFormat( - "[J2KDecoderModule]: JPEG2000 texture decoding threw an exception for {0}, {1}", - AssetId, ex); } } - - } - - // Cache Decoded layers - lock (m_cacheddecode) - { - if (m_cacheddecode.ContainsKey(AssetId)) - m_cacheddecode.Remove(AssetId); - m_cacheddecode.Add(AssetId, layers); + catch (Exception ex) + { + m_log.Warn("[J2KDecoderModule]: CSJ2K threw an exception decoding texture " + AssetId + ": " + ex.ToString()); + } - } + if (layers.Length == 0) + { + m_log.Warn("[J2KDecoderModule]: OpenJPEG failed to decode any layer data for texture " + AssetId + ", guessing sane defaults"); + // Layer decoding completely failed. Guess at sane defaults for the layer boundaries + layers = CreateDefaultLayers(j2kdata.Length); + } + // Cache Decoded layers + lock (m_cacheddecode) + { + if (m_cacheddecode.ContainsKey(AssetId)) + m_cacheddecode.Remove(AssetId); + m_cacheddecode.Add(AssetId, layers); + } + } + // Notify Interested Parties lock (m_notifyList) { @@ -377,6 +261,31 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender } } } + + private OpenJPEG.J2KLayerInfo[] CreateDefaultLayers(int j2kLength) + { + OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[5]; + layers[0] = new OpenJPEG.J2KLayerInfo(); + + for (int i = 0; i < layers.Length; i++) + { + OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); + + if (i == 0) + layer.Start = 0; + else + layer.Start = layers[i - 1].End + 1; + + // These default layer sizes are based on a small sampling of real-world texture data + // with extra padding thrown in for good measure. This is a worst case fallback plan + // and will probably not gracefully handle all real world data + layer.End = (int)(160d * Math.Exp(1.3d * (double)(i + 1))); + + layers[i] = layer; + } + + return layers; + } private void CleanCache() { @@ -418,10 +327,9 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender { m_cacheDecodeFolder = pFolder; m_cacheTimeout = timeout; + if (!Directory.Exists(pFolder)) - { Createj2KCacheFolder(pFolder); - } } /// @@ -447,14 +355,15 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].End - Layers[i].Start, strEnd); } + fsSWCache.Write(stringResult.ToString()); fsSWCache.Close(); fsSWCache.Dispose(); fsCache.Dispose(); + return true; } - return false; } @@ -475,11 +384,9 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender return false; if (!enabled) - { return false; - } - string readResult = string.Empty; + string readResult = String.Empty; try { @@ -493,7 +400,6 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender sr.Close(); sr.Dispose(); fsCachefile.Dispose(); - } catch (IOException ioe) { @@ -514,7 +420,6 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender "[J2KDecodeCache]: Cache Read failed. IO Exception."); } return false; - } catch (UnauthorizedAccessException) { @@ -541,7 +446,6 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender m_log.Error( "[J2KDecodeCache]: Cache Read failed, not supported. Cache disabled!"); enabled = false; - return false; } catch (Exception e) @@ -581,7 +485,6 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender Layers[i] = new OpenJPEG.J2KLayerInfo(); Layers[i].Start = element1; Layers[i].End = element2; - } else { @@ -592,9 +495,6 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender } } - - - return true; } -- cgit v1.1 From f908e32f620272a63c6a338fddfa3214ce95627b Mon Sep 17 00:00:00 2001 From: Melanie Date: Wed, 30 Sep 2009 19:26:26 +0100 Subject: Revert "Attempting to improve the robustness of texture decoding by always ignoring LayerInfo.End values and creating guessed default layer boundaries on failed decodes Changed a noisy J2K decode log message from Info to Debug" This reverts commit 22cc31135e2989df28e0756eb3b03f85530d5555. --- .../Agent/TextureSender/J2KDecoderModule.cs | 236 +++++++++++++++------ 1 file changed, 168 insertions(+), 68 deletions(-) (limited to 'OpenSim/Region/CoreModules') diff --git a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs index aa29947..937f76b 100644 --- a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs +++ b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs @@ -34,8 +34,8 @@ using System.Threading; using log4net; using Nini.Config; using OpenMetaverse; +using OpenMetaverse.Assets; using OpenMetaverse.Imaging; -using CSJ2K; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; @@ -195,58 +195,174 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender { int DecodeTime = 0; DecodeTime = Environment.TickCount; - OpenJPEG.J2KLayerInfo[] layers = null; + OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[0]; // Dummy result for if it fails. Informs that there's only full quality - if (!fCache.TryLoadCacheForAsset(AssetId, out layers)) + if (!OpenJpegFail) { - try + if (!fCache.TryLoadCacheForAsset(AssetId, out layers)) { - List layerStarts = CSJ2K.J2kImage.GetLayerBoundaries(new MemoryStream(j2kdata)); - - if (layerStarts != null && layerStarts.Count > 0) + try { - layers = new OpenJPEG.J2KLayerInfo[layerStarts.Count]; - for (int i = 0; i < layerStarts.Count; i++) + AssetTexture texture = new AssetTexture(AssetId, j2kdata); + if (texture.DecodeLayerBoundaries()) { - OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); - int start = layerStarts[i]; - - if (i == 0) - layer.Start = 0; + bool sane = true; + + // Sanity check all of the layers + for (int i = 0; i < texture.LayerInfo.Length; i++) + { + if (texture.LayerInfo[i].End > texture.AssetData.Length) + { + sane = false; + break; + } + } + + if (sane) + { + layers = texture.LayerInfo; + fCache.SaveFileCacheForAsset(AssetId, layers); + + + // Write out decode time + m_log.InfoFormat("[J2KDecoderModule]: {0} Decode Time: {1}", Environment.TickCount - DecodeTime, + AssetId); + + } else - layer.Start = layerStarts[i]; + { + m_log.WarnFormat( + "[J2KDecoderModule]: JPEG2000 texture decoding succeeded, but sanity check failed for {0}", + AssetId); + } + } - if (i == layerStarts.Count - 1) - layer.End = j2kdata.Length; + else + { + /* + Random rnd = new Random(); + // scramble ends for test + for (int i = 0; i < texture.LayerInfo.Length; i++) + { + texture.LayerInfo[i].End = rnd.Next(999999); + } + */ + + // Try to do some heuristics error correction! Yeah. + bool sane2Heuristics = true; + + + if (texture.Image == null) + sane2Heuristics = false; + + if (texture.LayerInfo == null) + sane2Heuristics = false; + + if (sane2Heuristics) + { + + + if (texture.LayerInfo.Length == 0) + sane2Heuristics = false; + } + + if (sane2Heuristics) + { + // Last layer start is less then the end of the file and last layer start is greater then 0 + if (texture.LayerInfo[texture.LayerInfo.Length - 1].Start < texture.AssetData.Length && texture.LayerInfo[texture.LayerInfo.Length - 1].Start > 0) + { + } + else + { + sane2Heuristics = false; + } + + } + + if (sane2Heuristics) + { + int start = 0; + + // try to fix it by using consistant data in the start field + for (int i = 0; i < texture.LayerInfo.Length; i++) + { + if (i == 0) + start = 0; + + if (i == texture.LayerInfo.Length - 1) + texture.LayerInfo[i].End = texture.AssetData.Length; + else + texture.LayerInfo[i].End = texture.LayerInfo[i + 1].Start - 1; + + // in this case, the end of the next packet is less then the start of the last packet + // after we've attempted to fix it which means the start of the last packet is borked + // there's no recovery from this + if (texture.LayerInfo[i].End < start) + { + sane2Heuristics = false; + break; + } + + if (texture.LayerInfo[i].End < 0 || texture.LayerInfo[i].End > texture.AssetData.Length) + { + sane2Heuristics = false; + break; + } + + if (texture.LayerInfo[i].Start < 0 || texture.LayerInfo[i].Start > texture.AssetData.Length) + { + sane2Heuristics = false; + break; + } + + start = texture.LayerInfo[i].Start; + } + } + + if (sane2Heuristics) + { + layers = texture.LayerInfo; + fCache.SaveFileCacheForAsset(AssetId, layers); + + + // Write out decode time + m_log.InfoFormat("[J2KDecoderModule]: HEURISTICS SUCCEEDED {0} Decode Time: {1}", Environment.TickCount - DecodeTime, + AssetId); + + } else - layer.End = layerStarts[i + 1] - 1; - - layers[i] = layer; + { + m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding failed for {0}. Is this a texture? is it J2K?", AssetId); + } } + texture = null; // dereference and dispose of ManagedImage + } + catch (DllNotFoundException) + { + m_log.Error( + "[J2KDecoderModule]: OpenJpeg is not installed properly. Decoding disabled! This will slow down texture performance! Often times this is because of an old version of GLIBC. You must have version 2.4 or above!"); + OpenJpegFail = true; + } + catch (Exception ex) + { + m_log.WarnFormat( + "[J2KDecoderModule]: JPEG2000 texture decoding threw an exception for {0}, {1}", + AssetId, ex); } } - catch (Exception ex) - { - m_log.Warn("[J2KDecoderModule]: CSJ2K threw an exception decoding texture " + AssetId + ": " + ex.ToString()); - } + + } - if (layers.Length == 0) - { - m_log.Warn("[J2KDecoderModule]: OpenJPEG failed to decode any layer data for texture " + AssetId + ", guessing sane defaults"); - // Layer decoding completely failed. Guess at sane defaults for the layer boundaries - layers = CreateDefaultLayers(j2kdata.Length); - } + // Cache Decoded layers + lock (m_cacheddecode) + { + if (m_cacheddecode.ContainsKey(AssetId)) + m_cacheddecode.Remove(AssetId); + m_cacheddecode.Add(AssetId, layers); + + } - // Cache Decoded layers - lock (m_cacheddecode) - { - if (m_cacheddecode.ContainsKey(AssetId)) - m_cacheddecode.Remove(AssetId); - m_cacheddecode.Add(AssetId, layers); - } - } - // Notify Interested Parties lock (m_notifyList) { @@ -261,31 +377,6 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender } } } - - private OpenJPEG.J2KLayerInfo[] CreateDefaultLayers(int j2kLength) - { - OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[5]; - layers[0] = new OpenJPEG.J2KLayerInfo(); - - for (int i = 0; i < layers.Length; i++) - { - OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); - - if (i == 0) - layer.Start = 0; - else - layer.Start = layers[i - 1].End + 1; - - // These default layer sizes are based on a small sampling of real-world texture data - // with extra padding thrown in for good measure. This is a worst case fallback plan - // and will probably not gracefully handle all real world data - layer.End = (int)(160d * Math.Exp(1.3d * (double)(i + 1))); - - layers[i] = layer; - } - - return layers; - } private void CleanCache() { @@ -327,9 +418,10 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender { m_cacheDecodeFolder = pFolder; m_cacheTimeout = timeout; - if (!Directory.Exists(pFolder)) + { Createj2KCacheFolder(pFolder); + } } /// @@ -355,15 +447,14 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].End - Layers[i].Start, strEnd); } - fsSWCache.Write(stringResult.ToString()); fsSWCache.Close(); fsSWCache.Dispose(); fsCache.Dispose(); - return true; } + return false; } @@ -384,9 +475,11 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender return false; if (!enabled) + { return false; + } - string readResult = String.Empty; + string readResult = string.Empty; try { @@ -400,6 +493,7 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender sr.Close(); sr.Dispose(); fsCachefile.Dispose(); + } catch (IOException ioe) { @@ -420,6 +514,7 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender "[J2KDecodeCache]: Cache Read failed. IO Exception."); } return false; + } catch (UnauthorizedAccessException) { @@ -446,6 +541,7 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender m_log.Error( "[J2KDecodeCache]: Cache Read failed, not supported. Cache disabled!"); enabled = false; + return false; } catch (Exception e) @@ -485,6 +581,7 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender Layers[i] = new OpenJPEG.J2KLayerInfo(); Layers[i].Start = element1; Layers[i].End = element2; + } else { @@ -495,6 +592,9 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender } } + + + return true; } -- cgit v1.1 From f56dc5fcda16d96a309120b2a75a623cf455a8e8 Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Wed, 30 Sep 2009 12:18:22 -0700 Subject: Attempting to improve the robustness of texture decoding by always ignoring LayerInfo.End values and creating guessed default layer boundaries on failed decodes Changed a noisy J2K decode log message from Info to Debug Replacing openjpeg-dotnet decoding with managed CSJ2K decoding. Should be much more reliable, faster, and use less memory * Re-added openjpeg-dotnet files since they are used elsewhere in OpenSim * Updated prebuild.xml with a reference to CSJ2K * Renamed IJ2KDecoder and J2KDecoder member names to follow standard naming conventions * Removed j2kDecodeCache cruft and replaced it with the OpenSim cache system * Rewrote the default layer boundary algorithm to use percentages instead of an exponent * Switched from an infinite in-memory cache to an expiring cache (10 minute timeout) * Slightly quieted logging errors for failed texture decodes --- .../Agent/TextureSender/J2KDecoderModule.cs | 688 +++++---------------- .../DynamicTexture/DynamicTextureModule.cs | 2 +- 2 files changed, 161 insertions(+), 529 deletions(-) (limited to 'OpenSim/Region/CoreModules') diff --git a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs index 937f76b..49f7f48 100644 --- a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs +++ b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs @@ -34,8 +34,8 @@ using System.Threading; using log4net; using Nini.Config; using OpenMetaverse; -using OpenMetaverse.Assets; using OpenMetaverse.Imaging; +using CSJ2K; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; @@ -43,31 +43,25 @@ using OpenSim.Services.Interfaces; namespace OpenSim.Region.CoreModules.Agent.TextureSender { - public delegate void J2KDecodeDelegate(UUID AssetId); + public delegate void J2KDecodeDelegate(UUID assetID); public class J2KDecoderModule : IRegionModule, IJ2KDecoder { - #region IRegionModule Members + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private static readonly ILog m_log - = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + /// Temporarily holds deserialized layer data information in memory + private readonly ExpiringCache m_decodedCache = new ExpiringCache(); + /// List of client methods to notify of results of decode + private readonly Dictionary> m_notifyList = new Dictionary>(); + /// Cache that will store decoded JPEG2000 layer boundary data + private IImprovedAssetCache m_cache; + /// Reference to a scene (doesn't matter which one as long as it can load the cache module) + private Scene m_scene; - /// - /// Cached Decoded Layers - /// - private readonly Dictionary m_cacheddecode = new Dictionary(); - private bool OpenJpegFail = false; - private string CacheFolder = Util.dataDir() + "/j2kDecodeCache"; - private int CacheTimeout = 720; - private J2KDecodeFileCache fCache = null; - private Thread CleanerThread = null; - private IAssetService AssetService = null; - private Scene m_Scene = null; + #region IRegionModule - /// - /// List of client methods to notify of results of decode - /// - private readonly Dictionary> m_notifyList = new Dictionary>(); + public string Name { get { return "J2KDecoderModule"; } } + public bool IsSharedModule { get { return true; } } public J2KDecoderModule() { @@ -75,630 +69,268 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender public void Initialise(Scene scene, IConfigSource source) { - if (m_Scene == null) - m_Scene = scene; - - IConfig j2kConfig = source.Configs["J2KDecoder"]; - if (j2kConfig != null) - { - CacheFolder = j2kConfig.GetString("CacheDir", CacheFolder); - CacheTimeout = j2kConfig.GetInt("CacheTimeout", CacheTimeout); - } - - if (fCache == null) - fCache = new J2KDecodeFileCache(CacheFolder, CacheTimeout); + if (m_scene == null) + m_scene = scene; scene.RegisterModuleInterface(this); - - if (CleanerThread == null && CacheTimeout != 0) - { - CleanerThread = new Thread(CleanCache); - CleanerThread.Name = "J2KCleanerThread"; - CleanerThread.IsBackground = true; - CleanerThread.Start(); - } } public void PostInitialise() { - AssetService = m_Scene.AssetService; + m_cache = m_scene.RequestModuleInterface(); } public void Close() { - - } - - public string Name - { - get { return "J2KDecoderModule"; } - } - - public bool IsSharedModule - { - get { return true; } } - #endregion + #endregion IRegionModule - #region IJ2KDecoder Members + #region IJ2KDecoder - - public void decode(UUID AssetId, byte[] assetData, DecodedCallback decodedReturn) + public void BeginDecode(UUID assetID, byte[] j2kData, DecodedCallback callback) { - // Dummy for if decoding fails. - OpenJPEG.J2KLayerInfo[] result = new OpenJPEG.J2KLayerInfo[0]; - - // Check if it's cached - bool cached = false; - lock (m_cacheddecode) - { - if (m_cacheddecode.ContainsKey(AssetId)) - { - cached = true; - result = m_cacheddecode[AssetId]; - } - } + OpenJPEG.J2KLayerInfo[] result; // If it's cached, return the cached results - if (cached) + if (m_decodedCache.TryGetValue(assetID, out result)) { - decodedReturn(AssetId, result); + callback(assetID, result); } else { - // not cached, so we need to decode it + // Not cached, we need to decode it. // Add to notify list and start decoding. // Next request for this asset while it's decoding will only be added to the notify list // once this is decoded, requests will be served from the cache and all clients in the notifylist will be updated bool decode = false; lock (m_notifyList) { - if (m_notifyList.ContainsKey(AssetId)) + if (m_notifyList.ContainsKey(assetID)) { - m_notifyList[AssetId].Add(decodedReturn); + m_notifyList[assetID].Add(callback); } else { List notifylist = new List(); - notifylist.Add(decodedReturn); - m_notifyList.Add(AssetId, notifylist); + notifylist.Add(callback); + m_notifyList.Add(assetID, notifylist); decode = true; } } + // Do Decode! if (decode) - { - doJ2kDecode(AssetId, assetData); - } + DoJ2KDecode(assetID, j2kData); } } /// /// Provides a synchronous decode so that caller can be assured that this executes before the next line /// - /// - /// - public void syncdecode(UUID AssetId, byte[] j2kdata) + /// + /// + public void Decode(UUID assetID, byte[] j2kData) { - doJ2kDecode(AssetId, j2kdata); + DoJ2KDecode(assetID, j2kData); } - #endregion + #endregion IJ2KDecoder /// /// Decode Jpeg2000 Asset Data /// - /// UUID of Asset - /// Byte Array Asset Data - private void doJ2kDecode(UUID AssetId, byte[] j2kdata) + /// UUID of Asset + /// JPEG2000 data + private void DoJ2KDecode(UUID assetID, byte[] j2kData) { int DecodeTime = 0; DecodeTime = Environment.TickCount; - OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[0]; // Dummy result for if it fails. Informs that there's only full quality + OpenJPEG.J2KLayerInfo[] layers; - if (!OpenJpegFail) + if (!TryLoadCacheForAsset(assetID, out layers)) { - if (!fCache.TryLoadCacheForAsset(AssetId, out layers)) + try { - try + List layerStarts = CSJ2K.J2kImage.GetLayerBoundaries(new MemoryStream(j2kData)); + + if (layerStarts != null && layerStarts.Count > 0) { + layers = new OpenJPEG.J2KLayerInfo[layerStarts.Count]; - AssetTexture texture = new AssetTexture(AssetId, j2kdata); - if (texture.DecodeLayerBoundaries()) + for (int i = 0; i < layerStarts.Count; i++) { - bool sane = true; - - // Sanity check all of the layers - for (int i = 0; i < texture.LayerInfo.Length; i++) - { - if (texture.LayerInfo[i].End > texture.AssetData.Length) - { - sane = false; - break; - } - } + OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); + int start = layerStarts[i]; - if (sane) - { - layers = texture.LayerInfo; - fCache.SaveFileCacheForAsset(AssetId, layers); - - - // Write out decode time - m_log.InfoFormat("[J2KDecoderModule]: {0} Decode Time: {1}", Environment.TickCount - DecodeTime, - AssetId); - - } + if (i == 0) + layer.Start = 0; else - { - m_log.WarnFormat( - "[J2KDecoderModule]: JPEG2000 texture decoding succeeded, but sanity check failed for {0}", - AssetId); - } - } + layer.Start = layerStarts[i]; - else - { - /* - Random rnd = new Random(); - // scramble ends for test - for (int i = 0; i < texture.LayerInfo.Length; i++) - { - texture.LayerInfo[i].End = rnd.Next(999999); - } - */ - - // Try to do some heuristics error correction! Yeah. - bool sane2Heuristics = true; - - - if (texture.Image == null) - sane2Heuristics = false; - - if (texture.LayerInfo == null) - sane2Heuristics = false; - - if (sane2Heuristics) - { - - - if (texture.LayerInfo.Length == 0) - sane2Heuristics = false; - } - - if (sane2Heuristics) - { - // Last layer start is less then the end of the file and last layer start is greater then 0 - if (texture.LayerInfo[texture.LayerInfo.Length - 1].Start < texture.AssetData.Length && texture.LayerInfo[texture.LayerInfo.Length - 1].Start > 0) - { - } - else - { - sane2Heuristics = false; - } - - } - - if (sane2Heuristics) - { - int start = 0; - - // try to fix it by using consistant data in the start field - for (int i = 0; i < texture.LayerInfo.Length; i++) - { - if (i == 0) - start = 0; - - if (i == texture.LayerInfo.Length - 1) - texture.LayerInfo[i].End = texture.AssetData.Length; - else - texture.LayerInfo[i].End = texture.LayerInfo[i + 1].Start - 1; - - // in this case, the end of the next packet is less then the start of the last packet - // after we've attempted to fix it which means the start of the last packet is borked - // there's no recovery from this - if (texture.LayerInfo[i].End < start) - { - sane2Heuristics = false; - break; - } - - if (texture.LayerInfo[i].End < 0 || texture.LayerInfo[i].End > texture.AssetData.Length) - { - sane2Heuristics = false; - break; - } - - if (texture.LayerInfo[i].Start < 0 || texture.LayerInfo[i].Start > texture.AssetData.Length) - { - sane2Heuristics = false; - break; - } - - start = texture.LayerInfo[i].Start; - } - } - - if (sane2Heuristics) - { - layers = texture.LayerInfo; - fCache.SaveFileCacheForAsset(AssetId, layers); - - - // Write out decode time - m_log.InfoFormat("[J2KDecoderModule]: HEURISTICS SUCCEEDED {0} Decode Time: {1}", Environment.TickCount - DecodeTime, - AssetId); - - } + if (i == layerStarts.Count - 1) + layer.End = j2kData.Length; else - { - m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding failed for {0}. Is this a texture? is it J2K?", AssetId); - } + layer.End = layerStarts[i + 1] - 1; + + layers[i] = layer; } - texture = null; // dereference and dispose of ManagedImage - } - catch (DllNotFoundException) - { - m_log.Error( - "[J2KDecoderModule]: OpenJpeg is not installed properly. Decoding disabled! This will slow down texture performance! Often times this is because of an old version of GLIBC. You must have version 2.4 or above!"); - OpenJpegFail = true; - } - catch (Exception ex) - { - m_log.WarnFormat( - "[J2KDecoderModule]: JPEG2000 texture decoding threw an exception for {0}, {1}", - AssetId, ex); } } - - } - - // Cache Decoded layers - lock (m_cacheddecode) - { - if (m_cacheddecode.ContainsKey(AssetId)) - m_cacheddecode.Remove(AssetId); - m_cacheddecode.Add(AssetId, layers); + catch (Exception ex) + { + m_log.Warn("[J2KDecoderModule]: CSJ2K threw an exception decoding texture " + assetID + ": " + ex.Message); + } - } + if (layers == null || layers.Length == 0) + { + m_log.Warn("[J2KDecoderModule]: Failed to decode layer data for texture " + assetID + ", guessing sane defaults"); + // Layer decoding completely failed. Guess at sane defaults for the layer boundaries + layers = CreateDefaultLayers(j2kData.Length); + } + // Cache Decoded layers + SaveFileCacheForAsset(assetID, layers); + } + // Notify Interested Parties lock (m_notifyList) { - if (m_notifyList.ContainsKey(AssetId)) + if (m_notifyList.ContainsKey(assetID)) { - foreach (DecodedCallback d in m_notifyList[AssetId]) + foreach (DecodedCallback d in m_notifyList[assetID]) { if (d != null) - d.DynamicInvoke(AssetId, layers); + d.DynamicInvoke(assetID, layers); } - m_notifyList.Remove(AssetId); + m_notifyList.Remove(assetID); } } } - - private void CleanCache() - { - m_log.Info("[J2KDecoderModule]: Cleaner thread started"); - - while (true) - { - if (AssetService != null) - fCache.ScanCacheFiles(RedecodeTexture); - System.Threading.Thread.Sleep(600000); - } - } - - private void RedecodeTexture(UUID assetID) + private OpenJPEG.J2KLayerInfo[] CreateDefaultLayers(int j2kLength) { - AssetBase texture = AssetService.Get(assetID.ToString()); - if (texture == null) - return; - - doJ2kDecode(assetID, texture.Data); + OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[5]; + + for (int i = 0; i < layers.Length; i++) + layers[i] = new OpenJPEG.J2KLayerInfo(); + + // These default layer sizes are based on a small sampling of real-world texture data + // with extra padding thrown in for good measure. This is a worst case fallback plan + // and may not gracefully handle all real world data + layers[0].Start = 0; + layers[1].Start = (int)((float)j2kLength * 0.02f); + layers[2].Start = (int)((float)j2kLength * 0.05f); + layers[3].Start = (int)((float)j2kLength * 0.20f); + layers[4].Start = (int)((float)j2kLength * 0.50f); + + layers[0].End = layers[1].Start - 1; + layers[1].End = layers[2].Start - 1; + layers[2].End = layers[3].Start - 1; + layers[3].End = layers[4].Start - 1; + layers[4].End = j2kLength; + + return layers; } - } - public class J2KDecodeFileCache - { - private readonly string m_cacheDecodeFolder; - private readonly int m_cacheTimeout; - private bool enabled = true; - - private static readonly ILog m_log - = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - /// - /// Creates a new instance of a file cache - /// - /// base folder for the cache. Will be created if it doesn't exist - public J2KDecodeFileCache(string pFolder, int timeout) + private void SaveFileCacheForAsset(UUID AssetId, OpenJPEG.J2KLayerInfo[] Layers) { - m_cacheDecodeFolder = pFolder; - m_cacheTimeout = timeout; - if (!Directory.Exists(pFolder)) - { - Createj2KCacheFolder(pFolder); - } - } + m_decodedCache.AddOrUpdate(AssetId, Layers, TimeSpan.FromMinutes(10)); - /// - /// Save Layers to Disk Cache - /// - /// Asset to Save the layers. Used int he file name by default - /// The Layer Data from OpenJpeg - /// - public bool SaveFileCacheForAsset(UUID AssetId, OpenJPEG.J2KLayerInfo[] Layers) - { - if (Layers.Length > 0 && enabled) + if (m_cache != null) { - FileStream fsCache = - new FileStream(String.Format("{0}/{1}", m_cacheDecodeFolder, FileNameFromAssetId(AssetId)), - FileMode.Create); - StreamWriter fsSWCache = new StreamWriter(fsCache); + AssetBase layerDecodeAsset = new AssetBase(); + layerDecodeAsset.ID = "j2kCache_" + AssetId.ToString(); + layerDecodeAsset.Local = true; + layerDecodeAsset.Name = layerDecodeAsset.ID; + layerDecodeAsset.Temporary = true; + layerDecodeAsset.Type = (sbyte)AssetType.Notecard; + + #region Serialize Layer Data + StringBuilder stringResult = new StringBuilder(); string strEnd = "\n"; for (int i = 0; i < Layers.Length; i++) { - if (i == (Layers.Length - 1)) - strEnd = ""; + if (i == Layers.Length - 1) + strEnd = String.Empty; stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].End - Layers[i].Start, strEnd); } - fsSWCache.Write(stringResult.ToString()); - fsSWCache.Close(); - fsSWCache.Dispose(); - fsCache.Dispose(); - return true; - } - - return false; - } + layerDecodeAsset.Data = Encoding.UTF8.GetBytes(stringResult.ToString()); - - /// - /// Loads the Layer data from the disk cache - /// Returns true if load succeeded - /// - /// AssetId that we're checking the cache for - /// out layers to save to - /// true if load succeeded - public bool TryLoadCacheForAsset(UUID AssetId, out OpenJPEG.J2KLayerInfo[] Layers) - { - string filename = String.Format("{0}/{1}", m_cacheDecodeFolder, FileNameFromAssetId(AssetId)); - Layers = new OpenJPEG.J2KLayerInfo[0]; + #endregion Serialize Layer Data - if (!File.Exists(filename)) - return false; - - if (!enabled) - { - return false; + m_cache.Cache(layerDecodeAsset); } + } - string readResult = string.Empty; - - try + bool TryLoadCacheForAsset(UUID AssetId, out OpenJPEG.J2KLayerInfo[] Layers) + { + if (m_decodedCache.TryGetValue(AssetId, out Layers)) { - FileStream fsCachefile = - new FileStream(filename, - FileMode.Open); - - StreamReader sr = new StreamReader(fsCachefile); - readResult = sr.ReadToEnd(); - - sr.Close(); - sr.Dispose(); - fsCachefile.Dispose(); - + return true; } - catch (IOException ioe) + else if (m_cache != null) { - if (ioe is PathTooLongException) - { - m_log.Error( - "[J2KDecodeCache]: Cache Read failed. Path is too long."); - } - else if (ioe is DirectoryNotFoundException) - { - m_log.Error( - "[J2KDecodeCache]: Cache Read failed. Cache Directory does not exist!"); - enabled = false; - } - else - { - m_log.Error( - "[J2KDecodeCache]: Cache Read failed. IO Exception."); - } - return false; + string assetName = "j2kCache_" + AssetId.ToString(); + AssetBase layerDecodeAsset = m_cache.Get(assetName); - } - catch (UnauthorizedAccessException) - { - m_log.Error( - "[J2KDecodeCache]: Cache Read failed. UnauthorizedAccessException Exception. Do you have the proper permissions on this file?"); - return false; - } - catch (ArgumentException ae) - { - if (ae is ArgumentNullException) - { - m_log.Error( - "[J2KDecodeCache]: Cache Read failed. No Filename provided"); - } - else + if (layerDecodeAsset != null) { - m_log.Error( - "[J2KDecodeCache]: Cache Read failed. Filname was invalid"); - } - return false; - } - catch (NotSupportedException) - { - m_log.Error( - "[J2KDecodeCache]: Cache Read failed, not supported. Cache disabled!"); - enabled = false; + #region Deserialize Layer Data - return false; - } - catch (Exception e) - { - m_log.ErrorFormat( - "[J2KDecodeCache]: Cache Read failed, unknown exception. Error: {0}", - e.ToString()); - return false; - } - - string[] lines = readResult.Split('\n'); + string readResult = Encoding.UTF8.GetString(layerDecodeAsset.Data); + string[] lines = readResult.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); - if (lines.Length <= 0) - return false; - - Layers = new OpenJPEG.J2KLayerInfo[lines.Length]; - - for (int i = 0; i < lines.Length; i++) - { - string[] elements = lines[i].Split('|'); - if (elements.Length == 3) - { - int element1, element2; - - try - { - element1 = Convert.ToInt32(elements[0]); - element2 = Convert.ToInt32(elements[1]); - } - catch (FormatException) + if (lines.Length == 0) { - m_log.WarnFormat("[J2KDecodeCache]: Cache Read failed with ErrorConvert for {0}", AssetId); - Layers = new OpenJPEG.J2KLayerInfo[0]; + m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (empty) " + assetName); + m_cache.Expire(assetName); return false; } - Layers[i] = new OpenJPEG.J2KLayerInfo(); - Layers[i].Start = element1; - Layers[i].End = element2; - - } - else - { - // reading failed - m_log.WarnFormat("[J2KDecodeCache]: Cache Read failed for {0}", AssetId); - Layers = new OpenJPEG.J2KLayerInfo[0]; - return false; - } - } - - - - - return true; - } + Layers = new OpenJPEG.J2KLayerInfo[lines.Length]; - /// - /// Routine which converts assetid to file name - /// - /// asset id of the image - /// string filename - public string FileNameFromAssetId(UUID AssetId) - { - return String.Format("j2kCache_{0}.cache", AssetId); - } + for (int i = 0; i < lines.Length; i++) + { + string[] elements = lines[i].Split('|'); + if (elements.Length == 3) + { + int element1, element2; - public UUID AssetIdFromFileName(string fileName) - { - string rawId = fileName.Replace("j2kCache_", "").Replace(".cache", ""); - UUID asset; - if (!UUID.TryParse(rawId, out asset)) - return UUID.Zero; + try + { + element1 = Convert.ToInt32(elements[0]); + element2 = Convert.ToInt32(elements[1]); + } + catch (FormatException) + { + m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (format) " + assetName); + m_cache.Expire(assetName); + return false; + } - return asset; - } + Layers[i] = new OpenJPEG.J2KLayerInfo(); + Layers[i].Start = element1; + Layers[i].End = element2; + } + else + { + m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (layout) " + assetName); + m_cache.Expire(assetName); + return false; + } + } - /// - /// Creates the Cache Folder - /// - /// Folder to Create - public void Createj2KCacheFolder(string pFolder) - { - try - { - Directory.CreateDirectory(pFolder); - } - catch (IOException ioe) - { - if (ioe is PathTooLongException) - { - m_log.Error( - "[J2KDecodeCache]: Cache Directory does not exist and create failed because the path to the cache folder is too long. Cache disabled!"); - } - else if (ioe is DirectoryNotFoundException) - { - m_log.Error( - "[J2KDecodeCache]: Cache Directory does not exist and create failed because the supplied base of the directory folder does not exist. Cache disabled!"); - } - else - { - m_log.Error( - "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an IO Exception. Cache disabled!"); - } - enabled = false; + #endregion Deserialize Layer Data - } - catch (UnauthorizedAccessException) - { - m_log.Error( - "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an UnauthorizedAccessException Exception. Cache disabled!"); - enabled = false; - } - catch (ArgumentException ae) - { - if (ae is ArgumentNullException) - { - m_log.Error( - "[J2KDecodeCache]: Cache Directory does not exist and create failed because the folder provided is invalid! Cache disabled!"); + return true; } - else - { - m_log.Error( - "[J2KDecodeCache]: Cache Directory does not exist and create failed because no cache folder was provided! Cache disabled!"); - } - enabled = false; } - catch (NotSupportedException) - { - m_log.Error( - "[J2KDecodeCache]: Cache Directory does not exist and create failed because it's not supported. Cache disabled!"); - enabled = false; - } - catch (Exception e) - { - m_log.ErrorFormat( - "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an unknown exception. Cache disabled! Error: {0}", - e.ToString()); - enabled = false; - } - } - - public void ScanCacheFiles(J2KDecodeDelegate decode) - { - DirectoryInfo dir = new DirectoryInfo(m_cacheDecodeFolder); - FileInfo[] files = dir.GetFiles("j2kCache_*.cache"); - foreach (FileInfo f in files) - { - TimeSpan fileAge = DateTime.Now - f.CreationTime; - - if (m_cacheTimeout != 0 && fileAge >= TimeSpan.FromMinutes(m_cacheTimeout)) - { - File.Delete(f.Name); - decode(AssetIdFromFileName(f.Name)); - System.Threading.Thread.Sleep(5000); - } - } + return false; } } } diff --git a/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs b/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs index 14eb9a2..9a6c49a 100644 --- a/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs @@ -325,7 +325,7 @@ namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture IJ2KDecoder cacheLayerDecode = scene.RequestModuleInterface(); if (cacheLayerDecode != null) { - cacheLayerDecode.syncdecode(asset.FullID, asset.Data); + cacheLayerDecode.Decode(asset.FullID, asset.Data); cacheLayerDecode = null; LastAssetID = asset.FullID; } -- cgit v1.1 From 6e0c79b8fe76c7d2c983cb7b258a75a3300e78f2 Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Thu, 1 Oct 2009 17:42:13 -0700 Subject: * Rewrote LLImageManager to use a real priority queue and hold minimal state * Rewrote the logic in J2KImage.RunUpdate() * Added a default avatar texture (I made it myself) --- OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs | 1 - 1 file changed, 1 deletion(-) (limited to 'OpenSim/Region/CoreModules') diff --git a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs index 49f7f48..a0f359b 100644 --- a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs +++ b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs @@ -161,7 +161,6 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender for (int i = 0; i < layerStarts.Count; i++) { OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); - int start = layerStarts[i]; if (i == 0) layer.Start = 0; -- cgit v1.1