clockwerk-opensim-stable – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | vero | 1 | /* |
2 | * Copyright (c) Contributors, http://opensimulator.org/ |
||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. |
||
4 | * |
||
5 | * Redistribution and use in source and binary forms, with or without |
||
6 | * modification, are permitted provided that the following conditions are met: |
||
7 | * * Redistributions of source code must retain the above copyright |
||
8 | * notice, this list of conditions and the following disclaimer. |
||
9 | * * Redistributions in binary form must reproduce the above copyright |
||
10 | * notice, this list of conditions and the following disclaimer in the |
||
11 | * documentation and/or other materials provided with the distribution. |
||
12 | * * Neither the name of the OpenSimulator Project nor the |
||
13 | * names of its contributors may be used to endorse or promote products |
||
14 | * derived from this software without specific prior written permission. |
||
15 | * |
||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY |
||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY |
||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
26 | */ |
||
27 | |||
28 | using System; |
||
29 | using System.Collections.Generic; |
||
30 | using System.IO; |
||
31 | using System.Reflection; |
||
32 | using System.Text; |
||
33 | using System.Threading; |
||
34 | using log4net; |
||
35 | using Mono.Addins; |
||
36 | using Nini.Config; |
||
37 | using OpenMetaverse; |
||
38 | using OpenMetaverse.Imaging; |
||
39 | using CSJ2K; |
||
40 | using OpenSim.Framework; |
||
41 | using OpenSim.Region.Framework.Interfaces; |
||
42 | using OpenSim.Region.Framework.Scenes; |
||
43 | using OpenSim.Services.Interfaces; |
||
44 | |||
45 | namespace OpenSim.Region.CoreModules.Agent.TextureSender |
||
46 | { |
||
47 | public delegate void J2KDecodeDelegate(UUID assetID); |
||
48 | |||
49 | [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "J2KDecoderModule")] |
||
50 | public class J2KDecoderModule : ISharedRegionModule, IJ2KDecoder |
||
51 | { |
||
52 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
||
53 | |||
54 | /// <summary>Temporarily holds deserialized layer data information in memory</summary> |
||
55 | private readonly ExpiringCache<UUID, OpenJPEG.J2KLayerInfo[]> m_decodedCache = new ExpiringCache<UUID,OpenJPEG.J2KLayerInfo[]>(); |
||
56 | /// <summary>List of client methods to notify of results of decode</summary> |
||
57 | private readonly Dictionary<UUID, List<DecodedCallback>> m_notifyList = new Dictionary<UUID, List<DecodedCallback>>(); |
||
58 | /// <summary>Cache that will store decoded JPEG2000 layer boundary data</summary> |
||
59 | private IImprovedAssetCache m_cache; |
||
60 | private IImprovedAssetCache Cache |
||
61 | { |
||
62 | get |
||
63 | { |
||
64 | if (m_cache == null) |
||
65 | m_cache = m_scene.RequestModuleInterface<IImprovedAssetCache>(); |
||
66 | |||
67 | return m_cache; |
||
68 | } |
||
69 | } |
||
70 | /// <summary>Reference to a scene (doesn't matter which one as long as it can load the cache module)</summary> |
||
71 | private UUID m_CreatorID = UUID.Zero; |
||
72 | private Scene m_scene; |
||
73 | |||
74 | #region ISharedRegionModule |
||
75 | |||
76 | private bool m_useCSJ2K = true; |
||
77 | |||
78 | public string Name { get { return "J2KDecoderModule"; } } |
||
79 | |||
80 | public J2KDecoderModule() |
||
81 | { |
||
82 | } |
||
83 | |||
84 | public void Initialise(IConfigSource source) |
||
85 | { |
||
86 | IConfig startupConfig = source.Configs["Startup"]; |
||
87 | if (startupConfig != null) |
||
88 | { |
||
89 | m_useCSJ2K = startupConfig.GetBoolean("UseCSJ2K", m_useCSJ2K); |
||
90 | } |
||
91 | } |
||
92 | |||
93 | public void AddRegion(Scene scene) |
||
94 | { |
||
95 | if (m_scene == null) |
||
96 | { |
||
97 | m_scene = scene; |
||
98 | m_CreatorID = scene.RegionInfo.RegionID; |
||
99 | } |
||
100 | |||
101 | scene.RegisterModuleInterface<IJ2KDecoder>(this); |
||
102 | |||
103 | } |
||
104 | |||
105 | public void RemoveRegion(Scene scene) |
||
106 | { |
||
107 | if (m_scene == scene) |
||
108 | m_scene = null; |
||
109 | } |
||
110 | |||
111 | public void PostInitialise() |
||
112 | { |
||
113 | } |
||
114 | |||
115 | public void Close() |
||
116 | { |
||
117 | } |
||
118 | |||
119 | public void RegionLoaded(Scene scene) |
||
120 | { |
||
121 | } |
||
122 | |||
123 | public Type ReplaceableInterface |
||
124 | { |
||
125 | get { return null; } |
||
126 | } |
||
127 | |||
128 | #endregion Region Module interface |
||
129 | |||
130 | #region IJ2KDecoder |
||
131 | |||
132 | public void BeginDecode(UUID assetID, byte[] j2kData, DecodedCallback callback) |
||
133 | { |
||
134 | OpenJPEG.J2KLayerInfo[] result; |
||
135 | |||
136 | // If it's cached, return the cached results |
||
137 | if (m_decodedCache.TryGetValue(assetID, out result)) |
||
138 | { |
||
139 | // m_log.DebugFormat( |
||
140 | // "[J2KDecoderModule]: Returning existing cached {0} layers j2k decode for {1}", |
||
141 | // result.Length, assetID); |
||
142 | |||
143 | callback(assetID, result); |
||
144 | } |
||
145 | else |
||
146 | { |
||
147 | // Not cached, we need to decode it. |
||
148 | // Add to notify list and start decoding. |
||
149 | // Next request for this asset while it's decoding will only be added to the notify list |
||
150 | // once this is decoded, requests will be served from the cache and all clients in the notifylist will be updated |
||
151 | bool decode = false; |
||
152 | lock (m_notifyList) |
||
153 | { |
||
154 | if (m_notifyList.ContainsKey(assetID)) |
||
155 | { |
||
156 | m_notifyList[assetID].Add(callback); |
||
157 | } |
||
158 | else |
||
159 | { |
||
160 | List<DecodedCallback> notifylist = new List<DecodedCallback>(); |
||
161 | notifylist.Add(callback); |
||
162 | m_notifyList.Add(assetID, notifylist); |
||
163 | decode = true; |
||
164 | } |
||
165 | } |
||
166 | |||
167 | // Do Decode! |
||
168 | if (decode) |
||
169 | Util.FireAndForget(delegate { Decode(assetID, j2kData); }); |
||
170 | } |
||
171 | } |
||
172 | |||
173 | public bool Decode(UUID assetID, byte[] j2kData) |
||
174 | { |
||
175 | OpenJPEG.J2KLayerInfo[] layers; |
||
176 | int components; |
||
177 | return Decode(assetID, j2kData, out layers, out components); |
||
178 | } |
||
179 | |||
180 | public bool Decode(UUID assetID, byte[] j2kData, out OpenJPEG.J2KLayerInfo[] layers, out int components) |
||
181 | { |
||
182 | return DoJ2KDecode(assetID, j2kData, out layers, out components); |
||
183 | } |
||
184 | |||
185 | #endregion IJ2KDecoder |
||
186 | |||
187 | /// <summary> |
||
188 | /// Decode Jpeg2000 Asset Data |
||
189 | /// </summary> |
||
190 | /// <param name="assetID">UUID of Asset</param> |
||
191 | /// <param name="j2kData">JPEG2000 data</param> |
||
192 | /// <param name="layers">layer data</param> |
||
193 | /// <param name="components">number of components</param> |
||
194 | /// <returns>true if decode was successful. false otherwise.</returns> |
||
195 | private bool DoJ2KDecode(UUID assetID, byte[] j2kData, out OpenJPEG.J2KLayerInfo[] layers, out int components) |
||
196 | { |
||
197 | // m_log.DebugFormat( |
||
198 | // "[J2KDecoderModule]: Doing J2K decoding of {0} bytes for asset {1}", j2kData.Length, assetID); |
||
199 | |||
200 | bool decodedSuccessfully = true; |
||
201 | |||
202 | //int DecodeTime = 0; |
||
203 | //DecodeTime = Environment.TickCount; |
||
204 | |||
205 | // We don't get this from CSJ2K. Is it relevant? |
||
206 | components = 0; |
||
207 | |||
208 | if (!TryLoadCacheForAsset(assetID, out layers)) |
||
209 | { |
||
210 | if (m_useCSJ2K) |
||
211 | { |
||
212 | try |
||
213 | { |
||
214 | List<int> layerStarts = CSJ2K.J2kImage.GetLayerBoundaries(new MemoryStream(j2kData)); |
||
215 | |||
216 | if (layerStarts != null && layerStarts.Count > 0) |
||
217 | { |
||
218 | layers = new OpenJPEG.J2KLayerInfo[layerStarts.Count]; |
||
219 | |||
220 | for (int i = 0; i < layerStarts.Count; i++) |
||
221 | { |
||
222 | OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); |
||
223 | |||
224 | if (i == 0) |
||
225 | layer.Start = 0; |
||
226 | else |
||
227 | layer.Start = layerStarts[i]; |
||
228 | |||
229 | if (i == layerStarts.Count - 1) |
||
230 | layer.End = j2kData.Length; |
||
231 | else |
||
232 | layer.End = layerStarts[i + 1] - 1; |
||
233 | |||
234 | layers[i] = layer; |
||
235 | } |
||
236 | } |
||
237 | } |
||
238 | catch (Exception ex) |
||
239 | { |
||
240 | m_log.Warn("[J2KDecoderModule]: CSJ2K threw an exception decoding texture " + assetID + ": " + ex.Message); |
||
241 | decodedSuccessfully = false; |
||
242 | } |
||
243 | } |
||
244 | else |
||
245 | { |
||
246 | if (!OpenJPEG.DecodeLayerBoundaries(j2kData, out layers, out components)) |
||
247 | { |
||
248 | m_log.Warn("[J2KDecoderModule]: OpenJPEG failed to decode texture " + assetID); |
||
249 | decodedSuccessfully = false; |
||
250 | } |
||
251 | } |
||
252 | |||
253 | if (layers == null || layers.Length == 0) |
||
254 | { |
||
255 | m_log.Warn("[J2KDecoderModule]: Failed to decode layer data for texture " + assetID + ", guessing sane defaults"); |
||
256 | // Layer decoding completely failed. Guess at sane defaults for the layer boundaries |
||
257 | layers = CreateDefaultLayers(j2kData.Length); |
||
258 | decodedSuccessfully = false; |
||
259 | } |
||
260 | |||
261 | // Cache Decoded layers |
||
262 | SaveFileCacheForAsset(assetID, layers); |
||
263 | } |
||
264 | |||
265 | // Notify Interested Parties |
||
266 | lock (m_notifyList) |
||
267 | { |
||
268 | if (m_notifyList.ContainsKey(assetID)) |
||
269 | { |
||
270 | foreach (DecodedCallback d in m_notifyList[assetID]) |
||
271 | { |
||
272 | if (d != null) |
||
273 | d.DynamicInvoke(assetID, layers); |
||
274 | } |
||
275 | m_notifyList.Remove(assetID); |
||
276 | } |
||
277 | } |
||
278 | |||
279 | return decodedSuccessfully; |
||
280 | } |
||
281 | |||
282 | private OpenJPEG.J2KLayerInfo[] CreateDefaultLayers(int j2kLength) |
||
283 | { |
||
284 | OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[5]; |
||
285 | |||
286 | for (int i = 0; i < layers.Length; i++) |
||
287 | layers[i] = new OpenJPEG.J2KLayerInfo(); |
||
288 | |||
289 | // These default layer sizes are based on a small sampling of real-world texture data |
||
290 | // with extra padding thrown in for good measure. This is a worst case fallback plan |
||
291 | // and may not gracefully handle all real world data |
||
292 | layers[0].Start = 0; |
||
293 | layers[1].Start = (int)((float)j2kLength * 0.02f); |
||
294 | layers[2].Start = (int)((float)j2kLength * 0.05f); |
||
295 | layers[3].Start = (int)((float)j2kLength * 0.20f); |
||
296 | layers[4].Start = (int)((float)j2kLength * 0.50f); |
||
297 | |||
298 | layers[0].End = layers[1].Start - 1; |
||
299 | layers[1].End = layers[2].Start - 1; |
||
300 | layers[2].End = layers[3].Start - 1; |
||
301 | layers[3].End = layers[4].Start - 1; |
||
302 | layers[4].End = j2kLength; |
||
303 | |||
304 | return layers; |
||
305 | } |
||
306 | |||
307 | private void SaveFileCacheForAsset(UUID AssetId, OpenJPEG.J2KLayerInfo[] Layers) |
||
308 | { |
||
309 | m_decodedCache.AddOrUpdate(AssetId, Layers, TimeSpan.FromMinutes(10)); |
||
310 | |||
311 | if (Cache != null) |
||
312 | { |
||
313 | string assetID = "j2kCache_" + AssetId.ToString(); |
||
314 | |||
315 | AssetBase layerDecodeAsset = new AssetBase(assetID, assetID, (sbyte)AssetType.Notecard, m_CreatorID.ToString()); |
||
316 | layerDecodeAsset.Local = true; |
||
317 | layerDecodeAsset.Temporary = true; |
||
318 | |||
319 | #region Serialize Layer Data |
||
320 | |||
321 | StringBuilder stringResult = new StringBuilder(); |
||
322 | string strEnd = "\n"; |
||
323 | for (int i = 0; i < Layers.Length; i++) |
||
324 | { |
||
325 | if (i == Layers.Length - 1) |
||
326 | strEnd = String.Empty; |
||
327 | |||
328 | stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].End - Layers[i].Start, strEnd); |
||
329 | } |
||
330 | |||
331 | layerDecodeAsset.Data = Util.UTF8.GetBytes(stringResult.ToString()); |
||
332 | |||
333 | #endregion Serialize Layer Data |
||
334 | |||
335 | Cache.Cache(layerDecodeAsset); |
||
336 | } |
||
337 | } |
||
338 | |||
339 | bool TryLoadCacheForAsset(UUID AssetId, out OpenJPEG.J2KLayerInfo[] Layers) |
||
340 | { |
||
341 | if (m_decodedCache.TryGetValue(AssetId, out Layers)) |
||
342 | { |
||
343 | return true; |
||
344 | } |
||
345 | else if (Cache != null) |
||
346 | { |
||
347 | string assetName = "j2kCache_" + AssetId.ToString(); |
||
348 | AssetBase layerDecodeAsset = Cache.Get(assetName); |
||
349 | |||
350 | if (layerDecodeAsset != null) |
||
351 | { |
||
352 | #region Deserialize Layer Data |
||
353 | |||
354 | string readResult = Util.UTF8.GetString(layerDecodeAsset.Data); |
||
355 | string[] lines = readResult.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); |
||
356 | |||
357 | if (lines.Length == 0) |
||
358 | { |
||
359 | m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (empty) " + assetName); |
||
360 | Cache.Expire(assetName); |
||
361 | return false; |
||
362 | } |
||
363 | |||
364 | Layers = new OpenJPEG.J2KLayerInfo[lines.Length]; |
||
365 | |||
366 | for (int i = 0; i < lines.Length; i++) |
||
367 | { |
||
368 | string[] elements = lines[i].Split('|'); |
||
369 | if (elements.Length == 3) |
||
370 | { |
||
371 | int element1, element2; |
||
372 | |||
373 | try |
||
374 | { |
||
375 | element1 = Convert.ToInt32(elements[0]); |
||
376 | element2 = Convert.ToInt32(elements[1]); |
||
377 | } |
||
378 | catch (FormatException) |
||
379 | { |
||
380 | m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (format) " + assetName); |
||
381 | Cache.Expire(assetName); |
||
382 | return false; |
||
383 | } |
||
384 | |||
385 | Layers[i] = new OpenJPEG.J2KLayerInfo(); |
||
386 | Layers[i].Start = element1; |
||
387 | Layers[i].End = element2; |
||
388 | } |
||
389 | else |
||
390 | { |
||
391 | m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (layout) " + assetName); |
||
392 | Cache.Expire(assetName); |
||
393 | return false; |
||
394 | } |
||
395 | } |
||
396 | |||
397 | #endregion Deserialize Layer Data |
||
398 | |||
399 | return true; |
||
400 | } |
||
401 | } |
||
402 | |||
403 | return false; |
||
404 | } |
||
405 | } |
||
406 | } |