diff options
author | John Hurliman | 2009-09-29 18:15:22 -0700 |
---|---|---|
committer | Melanie | 2009-09-30 18:42:45 +0100 |
commit | 22cc31135e2989df28e0756eb3b03f85530d5555 (patch) | |
tree | dc24b568ce040a674a830d598994f8efa8866f7d /OpenSim | |
parent | This releases the texture assets from LLImageManager cache, and re-requests t... (diff) | |
download | opensim-SC_OLD-22cc31135e2989df28e0756eb3b03f85530d5555.zip opensim-SC_OLD-22cc31135e2989df28e0756eb3b03f85530d5555.tar.gz opensim-SC_OLD-22cc31135e2989df28e0756eb3b03f85530d5555.tar.bz2 opensim-SC_OLD-22cc31135e2989df28e0756eb3b03f85530d5555.tar.xz |
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
Diffstat (limited to 'OpenSim')
-rw-r--r-- | OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs | 236 |
1 files changed, 68 insertions, 168 deletions
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; | |||
34 | using log4net; | 34 | using log4net; |
35 | using Nini.Config; | 35 | using Nini.Config; |
36 | using OpenMetaverse; | 36 | using OpenMetaverse; |
37 | using OpenMetaverse.Assets; | ||
38 | using OpenMetaverse.Imaging; | 37 | using OpenMetaverse.Imaging; |
38 | using CSJ2K; | ||
39 | using OpenSim.Framework; | 39 | using OpenSim.Framework; |
40 | using OpenSim.Region.Framework.Interfaces; | 40 | using OpenSim.Region.Framework.Interfaces; |
41 | using OpenSim.Region.Framework.Scenes; | 41 | using OpenSim.Region.Framework.Scenes; |
@@ -195,174 +195,58 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender | |||
195 | { | 195 | { |
196 | int DecodeTime = 0; | 196 | int DecodeTime = 0; |
197 | DecodeTime = Environment.TickCount; | 197 | DecodeTime = Environment.TickCount; |
198 | OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[0]; // Dummy result for if it fails. Informs that there's only full quality | 198 | OpenJPEG.J2KLayerInfo[] layers = null; |
199 | 199 | ||
200 | if (!OpenJpegFail) | 200 | if (!fCache.TryLoadCacheForAsset(AssetId, out layers)) |
201 | { | 201 | { |
202 | if (!fCache.TryLoadCacheForAsset(AssetId, out layers)) | 202 | try |
203 | { | 203 | { |
204 | try | 204 | List<int> layerStarts = CSJ2K.J2kImage.GetLayerBoundaries(new MemoryStream(j2kdata)); |
205 | |||
206 | if (layerStarts != null && layerStarts.Count > 0) | ||
205 | { | 207 | { |
208 | layers = new OpenJPEG.J2KLayerInfo[layerStarts.Count]; | ||
206 | 209 | ||
207 | AssetTexture texture = new AssetTexture(AssetId, j2kdata); | 210 | for (int i = 0; i < layerStarts.Count; i++) |
208 | if (texture.DecodeLayerBoundaries()) | ||
209 | { | 211 | { |
210 | bool sane = true; | 212 | OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); |
211 | 213 | int start = layerStarts[i]; | |
212 | // Sanity check all of the layers | 214 | |
213 | for (int i = 0; i < texture.LayerInfo.Length; i++) | 215 | if (i == 0) |
214 | { | 216 | layer.Start = 0; |
215 | if (texture.LayerInfo[i].End > texture.AssetData.Length) | ||
216 | { | ||
217 | sane = false; | ||
218 | break; | ||
219 | } | ||
220 | } | ||
221 | |||
222 | if (sane) | ||
223 | { | ||
224 | layers = texture.LayerInfo; | ||
225 | fCache.SaveFileCacheForAsset(AssetId, layers); | ||
226 | |||
227 | |||
228 | // Write out decode time | ||
229 | m_log.InfoFormat("[J2KDecoderModule]: {0} Decode Time: {1}", Environment.TickCount - DecodeTime, | ||
230 | AssetId); | ||
231 | |||
232 | } | ||
233 | else | 217 | else |
234 | { | 218 | layer.Start = layerStarts[i]; |
235 | m_log.WarnFormat( | ||
236 | "[J2KDecoderModule]: JPEG2000 texture decoding succeeded, but sanity check failed for {0}", | ||
237 | AssetId); | ||
238 | } | ||
239 | } | ||
240 | 219 | ||
241 | else | 220 | if (i == layerStarts.Count - 1) |
242 | { | 221 | layer.End = j2kdata.Length; |
243 | /* | ||
244 | Random rnd = new Random(); | ||
245 | // scramble ends for test | ||
246 | for (int i = 0; i < texture.LayerInfo.Length; i++) | ||
247 | { | ||
248 | texture.LayerInfo[i].End = rnd.Next(999999); | ||
249 | } | ||
250 | */ | ||
251 | |||
252 | // Try to do some heuristics error correction! Yeah. | ||
253 | bool sane2Heuristics = true; | ||
254 | |||
255 | |||
256 | if (texture.Image == null) | ||
257 | sane2Heuristics = false; | ||
258 | |||
259 | if (texture.LayerInfo == null) | ||
260 | sane2Heuristics = false; | ||
261 | |||
262 | if (sane2Heuristics) | ||
263 | { | ||
264 | |||
265 | |||
266 | if (texture.LayerInfo.Length == 0) | ||
267 | sane2Heuristics = false; | ||
268 | } | ||
269 | |||
270 | if (sane2Heuristics) | ||
271 | { | ||
272 | // Last layer start is less then the end of the file and last layer start is greater then 0 | ||
273 | if (texture.LayerInfo[texture.LayerInfo.Length - 1].Start < texture.AssetData.Length && texture.LayerInfo[texture.LayerInfo.Length - 1].Start > 0) | ||
274 | { | ||
275 | } | ||
276 | else | ||
277 | { | ||
278 | sane2Heuristics = false; | ||
279 | } | ||
280 | |||
281 | } | ||
282 | |||
283 | if (sane2Heuristics) | ||
284 | { | ||
285 | int start = 0; | ||
286 | |||
287 | // try to fix it by using consistant data in the start field | ||
288 | for (int i = 0; i < texture.LayerInfo.Length; i++) | ||
289 | { | ||
290 | if (i == 0) | ||
291 | start = 0; | ||
292 | |||
293 | if (i == texture.LayerInfo.Length - 1) | ||
294 | texture.LayerInfo[i].End = texture.AssetData.Length; | ||
295 | else | ||
296 | texture.LayerInfo[i].End = texture.LayerInfo[i + 1].Start - 1; | ||
297 | |||
298 | // in this case, the end of the next packet is less then the start of the last packet | ||
299 | // after we've attempted to fix it which means the start of the last packet is borked | ||
300 | // there's no recovery from this | ||
301 | if (texture.LayerInfo[i].End < start) | ||
302 | { | ||
303 | sane2Heuristics = false; | ||
304 | break; | ||
305 | } | ||
306 | |||
307 | if (texture.LayerInfo[i].End < 0 || texture.LayerInfo[i].End > texture.AssetData.Length) | ||
308 | { | ||
309 | sane2Heuristics = false; | ||
310 | break; | ||
311 | } | ||
312 | |||
313 | if (texture.LayerInfo[i].Start < 0 || texture.LayerInfo[i].Start > texture.AssetData.Length) | ||
314 | { | ||
315 | sane2Heuristics = false; | ||
316 | break; | ||
317 | } | ||
318 | |||
319 | start = texture.LayerInfo[i].Start; | ||
320 | } | ||
321 | } | ||
322 | |||
323 | if (sane2Heuristics) | ||
324 | { | ||
325 | layers = texture.LayerInfo; | ||
326 | fCache.SaveFileCacheForAsset(AssetId, layers); | ||
327 | |||
328 | |||
329 | // Write out decode time | ||
330 | m_log.InfoFormat("[J2KDecoderModule]: HEURISTICS SUCCEEDED {0} Decode Time: {1}", Environment.TickCount - DecodeTime, | ||
331 | AssetId); | ||
332 | |||
333 | } | ||
334 | else | 222 | else |
335 | { | 223 | layer.End = layerStarts[i + 1] - 1; |
336 | m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding failed for {0}. Is this a texture? is it J2K?", AssetId); | 224 | |
337 | } | 225 | layers[i] = layer; |
338 | } | 226 | } |
339 | texture = null; // dereference and dispose of ManagedImage | ||
340 | } | ||
341 | catch (DllNotFoundException) | ||
342 | { | ||
343 | m_log.Error( | ||
344 | "[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!"); | ||
345 | OpenJpegFail = true; | ||
346 | } | ||
347 | catch (Exception ex) | ||
348 | { | ||
349 | m_log.WarnFormat( | ||
350 | "[J2KDecoderModule]: JPEG2000 texture decoding threw an exception for {0}, {1}", | ||
351 | AssetId, ex); | ||
352 | } | 227 | } |
353 | } | 228 | } |
354 | 229 | catch (Exception ex) | |
355 | } | 230 | { |
356 | 231 | m_log.Warn("[J2KDecoderModule]: CSJ2K threw an exception decoding texture " + AssetId + ": " + ex.ToString()); | |
357 | // Cache Decoded layers | 232 | } |
358 | lock (m_cacheddecode) | ||
359 | { | ||
360 | if (m_cacheddecode.ContainsKey(AssetId)) | ||
361 | m_cacheddecode.Remove(AssetId); | ||
362 | m_cacheddecode.Add(AssetId, layers); | ||
363 | 233 | ||
364 | } | 234 | if (layers.Length == 0) |
235 | { | ||
236 | m_log.Warn("[J2KDecoderModule]: OpenJPEG failed to decode any layer data for texture " + AssetId + ", guessing sane defaults"); | ||
237 | // Layer decoding completely failed. Guess at sane defaults for the layer boundaries | ||
238 | layers = CreateDefaultLayers(j2kdata.Length); | ||
239 | } | ||
365 | 240 | ||
241 | // Cache Decoded layers | ||
242 | lock (m_cacheddecode) | ||
243 | { | ||
244 | if (m_cacheddecode.ContainsKey(AssetId)) | ||
245 | m_cacheddecode.Remove(AssetId); | ||
246 | m_cacheddecode.Add(AssetId, layers); | ||
247 | } | ||
248 | } | ||
249 | |||
366 | // Notify Interested Parties | 250 | // Notify Interested Parties |
367 | lock (m_notifyList) | 251 | lock (m_notifyList) |
368 | { | 252 | { |
@@ -377,6 +261,31 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender | |||
377 | } | 261 | } |
378 | } | 262 | } |
379 | } | 263 | } |
264 | |||
265 | private OpenJPEG.J2KLayerInfo[] CreateDefaultLayers(int j2kLength) | ||
266 | { | ||
267 | OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[5]; | ||
268 | layers[0] = new OpenJPEG.J2KLayerInfo(); | ||
269 | |||
270 | for (int i = 0; i < layers.Length; i++) | ||
271 | { | ||
272 | OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); | ||
273 | |||
274 | if (i == 0) | ||
275 | layer.Start = 0; | ||
276 | else | ||
277 | layer.Start = layers[i - 1].End + 1; | ||
278 | |||
279 | // These default layer sizes are based on a small sampling of real-world texture data | ||
280 | // with extra padding thrown in for good measure. This is a worst case fallback plan | ||
281 | // and will probably not gracefully handle all real world data | ||
282 | layer.End = (int)(160d * Math.Exp(1.3d * (double)(i + 1))); | ||
283 | |||
284 | layers[i] = layer; | ||
285 | } | ||
286 | |||
287 | return layers; | ||
288 | } | ||
380 | 289 | ||
381 | private void CleanCache() | 290 | private void CleanCache() |
382 | { | 291 | { |
@@ -418,10 +327,9 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender | |||
418 | { | 327 | { |
419 | m_cacheDecodeFolder = pFolder; | 328 | m_cacheDecodeFolder = pFolder; |
420 | m_cacheTimeout = timeout; | 329 | m_cacheTimeout = timeout; |
330 | |||
421 | if (!Directory.Exists(pFolder)) | 331 | if (!Directory.Exists(pFolder)) |
422 | { | ||
423 | Createj2KCacheFolder(pFolder); | 332 | Createj2KCacheFolder(pFolder); |
424 | } | ||
425 | } | 333 | } |
426 | 334 | ||
427 | /// <summary> | 335 | /// <summary> |
@@ -447,14 +355,15 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender | |||
447 | 355 | ||
448 | stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].End - Layers[i].Start, strEnd); | 356 | stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].End - Layers[i].Start, strEnd); |
449 | } | 357 | } |
358 | |||
450 | fsSWCache.Write(stringResult.ToString()); | 359 | fsSWCache.Write(stringResult.ToString()); |
451 | fsSWCache.Close(); | 360 | fsSWCache.Close(); |
452 | fsSWCache.Dispose(); | 361 | fsSWCache.Dispose(); |
453 | fsCache.Dispose(); | 362 | fsCache.Dispose(); |
363 | |||
454 | return true; | 364 | return true; |
455 | } | 365 | } |
456 | 366 | ||
457 | |||
458 | return false; | 367 | return false; |
459 | } | 368 | } |
460 | 369 | ||
@@ -475,11 +384,9 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender | |||
475 | return false; | 384 | return false; |
476 | 385 | ||
477 | if (!enabled) | 386 | if (!enabled) |
478 | { | ||
479 | return false; | 387 | return false; |
480 | } | ||
481 | 388 | ||
482 | string readResult = string.Empty; | 389 | string readResult = String.Empty; |
483 | 390 | ||
484 | try | 391 | try |
485 | { | 392 | { |
@@ -493,7 +400,6 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender | |||
493 | sr.Close(); | 400 | sr.Close(); |
494 | sr.Dispose(); | 401 | sr.Dispose(); |
495 | fsCachefile.Dispose(); | 402 | fsCachefile.Dispose(); |
496 | |||
497 | } | 403 | } |
498 | catch (IOException ioe) | 404 | catch (IOException ioe) |
499 | { | 405 | { |
@@ -514,7 +420,6 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender | |||
514 | "[J2KDecodeCache]: Cache Read failed. IO Exception."); | 420 | "[J2KDecodeCache]: Cache Read failed. IO Exception."); |
515 | } | 421 | } |
516 | return false; | 422 | return false; |
517 | |||
518 | } | 423 | } |
519 | catch (UnauthorizedAccessException) | 424 | catch (UnauthorizedAccessException) |
520 | { | 425 | { |
@@ -541,7 +446,6 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender | |||
541 | m_log.Error( | 446 | m_log.Error( |
542 | "[J2KDecodeCache]: Cache Read failed, not supported. Cache disabled!"); | 447 | "[J2KDecodeCache]: Cache Read failed, not supported. Cache disabled!"); |
543 | enabled = false; | 448 | enabled = false; |
544 | |||
545 | return false; | 449 | return false; |
546 | } | 450 | } |
547 | catch (Exception e) | 451 | catch (Exception e) |
@@ -581,7 +485,6 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender | |||
581 | Layers[i] = new OpenJPEG.J2KLayerInfo(); | 485 | Layers[i] = new OpenJPEG.J2KLayerInfo(); |
582 | Layers[i].Start = element1; | 486 | Layers[i].Start = element1; |
583 | Layers[i].End = element2; | 487 | Layers[i].End = element2; |
584 | |||
585 | } | 488 | } |
586 | else | 489 | else |
587 | { | 490 | { |
@@ -592,9 +495,6 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender | |||
592 | } | 495 | } |
593 | } | 496 | } |
594 | 497 | ||
595 | |||
596 | |||
597 | |||
598 | return true; | 498 | return true; |
599 | } | 499 | } |
600 | 500 | ||