If reusing dynamic textures, do not reuse small data length textures that fall below current viewer discard level 2 thresholds.

Viewer LL 3.3.4 and before sometimes fail to properly redisplay dynamic textures that have a small data length compared to pixel size when pulled from cache.
This appears to happen when the data length is smaller than the estimate discard level 2 size the viewer uses when making this GetTexture request.
This commit works around this by always regenerating dynamic textures that fall below this threshold rather than reusing them if ReuseDynamicTextures = true
This can be controlled by the [Textures] ReuseDynamicLowDataTextures config setting which defaults to false.
0.7.3-extended
Justin Clark-Casey (justincc) 2012-09-06 22:12:05 +01:00
parent e0a3a8d47b
commit a53f90cf7a
8 changed files with 188 additions and 46 deletions

View File

@ -42,7 +42,7 @@ namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture
{
public class DynamicTextureModule : IRegionModule, IDynamicTextureManager
{
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private const int ALL_SIDES = -1;
@ -54,6 +54,17 @@ namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture
/// </summary>
public bool ReuseTextures { get; set; }
/// <summary>
/// If false, then textures which have a low data size are not reused when ReuseTextures = true.
/// </summary>
/// <remarks>
/// LL viewers 3.3.4 and before appear to not fully render textures pulled from the viewer cache if those
/// textures have a relatively high pixel surface but a small data size. Typically, this appears to happen
/// if the data size is smaller than the viewer's discard level 2 size estimate. So if this is setting is
/// false, textures smaller than the calculation in IsSizeReuseable are always regenerated rather than reused
/// to work around this problem.</remarks>
public bool ReuseLowDataTextures { get; set; }
private Dictionary<UUID, Scene> RegisteredScenes = new Dictionary<UUID, Scene>();
private Dictionary<string, IDynamicTextureRender> RenderPlugins =
@ -83,18 +94,17 @@ namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture
/// <summary>
/// Called by code which actually renders the dynamic texture to supply texture data.
/// </summary>
/// <param name="id"></param>
/// <param name="data"></param>
/// <param name="isReuseable">True if the data generated can be reused for subsequent identical requests</param>
public void ReturnData(UUID id, byte[] data, bool isReuseable)
/// <param name="updaterId"></param>
/// <param name="texture"></param>
public void ReturnData(UUID updaterId, IDynamicTexture texture)
{
DynamicTextureUpdater updater = null;
lock (Updaters)
{
if (Updaters.ContainsKey(id))
if (Updaters.ContainsKey(updaterId))
{
updater = Updaters[id];
updater = Updaters[updaterId];
}
}
@ -103,11 +113,16 @@ namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture
if (RegisteredScenes.ContainsKey(updater.SimUUID))
{
Scene scene = RegisteredScenes[updater.SimUUID];
UUID newTextureID = updater.DataReceived(data, scene);
UUID newTextureID = updater.DataReceived(texture.Data, scene);
if (ReuseTextures && isReuseable && !updater.BlendWithOldTexture)
if (ReuseTextures
&& !updater.BlendWithOldTexture
&& texture.IsReuseable
&& (ReuseLowDataTextures || IsDataSizeReuseable(texture)))
{
m_reuseableDynamicTextures.Store(
GenerateReusableTextureKey(updater.BodyData, updater.Params), newTextureID);
GenerateReusableTextureKey(texture.InputCommands, texture.InputParams), newTextureID);
}
}
}
@ -123,6 +138,27 @@ namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture
}
}
/// <summary>
/// Determines whether the texture is reuseable based on its data size.
/// </summary>
/// <remarks>
/// This is a workaround for a viewer bug where very small data size textures relative to their pixel size
/// are not redisplayed properly when pulled from cache. The calculation here is based on the typical discard
/// level of 2, a 'rate' of 0.125 and 4 components (which makes for a factor of 0.5).
/// </remarks>
/// <returns></returns>
private bool IsDataSizeReuseable(IDynamicTexture texture)
{
// Console.WriteLine("{0} {1}", texture.Size.Width, texture.Size.Height);
int discardLevel2DataThreshold = (int)Math.Ceiling((texture.Size.Width >> 2) * (texture.Size.Height >> 2) * 0.5);
// m_log.DebugFormat(
// "[DYNAMIC TEXTURE MODULE]: Discard level 2 threshold {0}, texture data length {1}",
// discardLevel2DataThreshold, texture.Data.Length);
return discardLevel2DataThreshold < texture.Data.Length;
}
public UUID AddDynamicTextureURL(UUID simID, UUID primID, string contentType, string url,
string extraParams, int updateTimer)
{
@ -293,7 +329,10 @@ namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture
{
IConfig texturesConfig = config.Configs["Textures"];
if (texturesConfig != null)
{
ReuseTextures = texturesConfig.GetBoolean("ReuseDynamicTextures", false);
ReuseLowDataTextures = texturesConfig.GetBoolean("ReuseDynamicLowDataTextures", false);
}
if (!RegisteredScenes.ContainsKey(scene.RegionInfo.RegionID))
{

View File

@ -32,6 +32,7 @@ using System.Net;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.Imaging;
using OpenSim.Region.CoreModules.Scripting.DynamicTexture;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using log4net;
@ -73,12 +74,12 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
// return false;
// }
public byte[] ConvertUrl(string url, string extraParams)
public IDynamicTexture ConvertUrl(string url, string extraParams)
{
return null;
}
public byte[] ConvertData(string bodyData, string extraParams)
public IDynamicTexture ConvertData(string bodyData, string extraParams)
{
return null;
}
@ -171,11 +172,11 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
private void HttpRequestReturn(IAsyncResult result)
{
RequestState state = (RequestState) result.AsyncState;
WebRequest request = (WebRequest) state.Request;
Stream stream = null;
byte[] imageJ2000 = new byte[0];
Size newSize = new Size(0, 0);
try
{
@ -188,38 +189,44 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
try
{
Bitmap image = new Bitmap(stream);
Size newsize;
// TODO: make this a bit less hard coded
if ((image.Height < 64) && (image.Width < 64))
{
newsize = new Size(32, 32);
newSize.Width = 32;
newSize.Height = 32;
}
else if ((image.Height < 128) && (image.Width < 128))
{
newsize = new Size(64, 64);
newSize.Width = 64;
newSize.Height = 64;
}
else if ((image.Height < 256) && (image.Width < 256))
{
newsize = new Size(128, 128);
newSize.Width = 128;
newSize.Height = 128;
}
else if ((image.Height < 512 && image.Width < 512))
{
newsize = new Size(256, 256);
newSize.Width = 256;
newSize.Height = 256;
}
else if ((image.Height < 1024 && image.Width < 1024))
{
newsize = new Size(512, 512);
newSize.Width = 512;
newSize.Height = 512;
}
else
{
newsize = new Size(1024, 1024);
newSize.Width = 1024;
newSize.Height = 1024;
}
Bitmap resize = new Bitmap(image, newsize);
using (Bitmap resize = new Bitmap(image, newSize))
{
imageJ2000 = OpenJPEG.EncodeFromImage(resize, true);
}
}
catch (Exception)
{
m_log.Error("[LOADIMAGEURLMODULE]: OpenJpeg Conversion Failed. Empty byte data returned!");
@ -233,7 +240,6 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
}
catch (WebException)
{
}
finally
{
@ -243,10 +249,13 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
}
}
m_log.DebugFormat("[LOADIMAGEURLMODULE] Returning {0} bytes of image data for request {1}",
m_log.DebugFormat("[LOADIMAGEURLMODULE]: Returning {0} bytes of image data for request {1}",
imageJ2000.Length, state.RequestID);
m_textureManager.ReturnData(state.RequestID, imageJ2000, false);
m_textureManager.ReturnData(
state.RequestID,
new OpenSim.Region.CoreModules.Scripting.DynamicTexture.DynamicTexture(
request.RequestUri, null, imageJ2000, newSize, false));
}
#region Nested type: RequestState

View File

@ -57,6 +57,7 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender.Tests
m_dtm = new DynamicTextureModule();
m_dtm.ReuseTextures = reuseTextures;
// m_dtm.ReuseLowDataTextures = reuseTextures;
m_vrm = new VectorRenderModule();
@ -201,6 +202,7 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender.Tests
public void TestRepeatSameDrawReusingTexture()
{
TestHelpers.InMethod();
// TestHelpers.EnableLogging();
string dtText = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World;";
@ -228,6 +230,46 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender.Tests
Assert.That(firstDynamicTextureID, Is.EqualTo(so.RootPart.Shape.Textures.GetFace(0).TextureID));
}
/// <summary>
/// Test a low data dynamically generated texture such that it is treated as a low data texture that causes
/// problems for current viewers.
/// </summary>
/// <remarks>
/// As we do not set DynamicTextureModule.ReuseLowDataTextures = true in this test, it should not reuse the
/// texture
/// </remarks>
[Test]
public void TestRepeatSameDrawLowDataTexture()
{
TestHelpers.InMethod();
// TestHelpers.EnableLogging();
string dtText = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World;";
SetupScene(true);
SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene);
m_dtm.AddDynamicTextureData(
m_scene.RegionInfo.RegionID,
so.UUID,
m_vrm.GetContentType(),
dtText,
"1024",
0);
UUID firstDynamicTextureID = so.RootPart.Shape.Textures.GetFace(0).TextureID;
m_dtm.AddDynamicTextureData(
m_scene.RegionInfo.RegionID,
so.UUID,
m_vrm.GetContentType(),
dtText,
"1024",
0);
Assert.That(firstDynamicTextureID, Is.Not.EqualTo(so.RootPart.Shape.Textures.GetFace(0).TextureID));
}
[Test]
public void TestRepeatSameDrawDifferentExtraParamsReusingTexture()
{

View File

@ -35,6 +35,7 @@ using System.Net;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.Imaging;
using OpenSim.Region.CoreModules.Scripting.DynamicTexture;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using log4net;
@ -85,20 +86,14 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender
// return lines.Any((str, r) => str.StartsWith("Image"));
// }
public byte[] ConvertUrl(string url, string extraParams)
public IDynamicTexture ConvertUrl(string url, string extraParams)
{
return null;
}
public byte[] ConvertData(string bodyData, string extraParams)
public IDynamicTexture ConvertData(string bodyData, string extraParams)
{
bool reuseable;
return Draw(bodyData, extraParams, out reuseable);
}
private byte[] ConvertData(string bodyData, string extraParams, out bool reuseable)
{
return Draw(bodyData, extraParams, out reuseable);
return Draw(bodyData, extraParams);
}
public bool AsyncConvertUrl(UUID id, string url, string extraParams)
@ -109,10 +104,7 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender
public bool AsyncConvertData(UUID id, string bodyData, string extraParams)
{
// XXX: This isn't actually being done asynchronously!
bool reuseable;
byte[] data = ConvertData(bodyData, extraParams, out reuseable);
m_textureManager.ReturnData(id, data, reuseable);
m_textureManager.ReturnData(id, ConvertData(bodyData, extraParams));
return true;
}
@ -191,7 +183,7 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender
#endregion
private byte[] Draw(string data, string extraParams, out bool reuseable)
private IDynamicTexture Draw(string data, string extraParams)
{
// We need to cater for old scripts that didnt use extraParams neatly, they use either an integer size which represents both width and height, or setalpha
// we will now support multiple comma seperated params in the form width:256,height:512,alpha:255
@ -334,6 +326,7 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender
Bitmap bitmap = null;
Graphics graph = null;
bool reuseable = false;
try
{
@ -396,7 +389,8 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender
e.Message, e.StackTrace);
}
return imageJ2000;
return new OpenSim.Region.CoreModules.Scripting.DynamicTexture.DynamicTexture(
data, extraParams, imageJ2000, new Size(width, height), reuseable);
}
finally
{

View File

@ -43,7 +43,7 @@ using OpenSim.Tests.Common;
namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid.Tests
{
[TestFixture]
public class GridConnectorsTests
public class GridConnectorsTests : OpenSimTestCase
{
LocalGridServicesConnector m_LocalConnector;
private void SetUp()

View File

@ -51,7 +51,7 @@ using RegionSettings = OpenSim.Framework.RegionSettings;
namespace OpenSim.Region.CoreModules.World.Archiver.Tests
{
[TestFixture]
public class ArchiverTests
public class ArchiverTests : OpenSimTestCase
{
private Guid m_lastRequestId;
private string m_lastErrorMessage;

View File

@ -25,6 +25,8 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Drawing;
using System.IO;
using OpenMetaverse;
@ -33,7 +35,14 @@ namespace OpenSim.Region.Framework.Interfaces
public interface IDynamicTextureManager
{
void RegisterRender(string handleType, IDynamicTextureRender render);
void ReturnData(UUID id, byte[] data, bool isReuseable);
/// <summary>
/// Used by IDynamicTextureRender implementations to return renders
/// </summary>
/// <param name='id'></param>
/// <param name='data'></param>
/// <param name='isReuseable'></param>
void ReturnData(UUID id, IDynamicTexture texture);
UUID AddDynamicTextureURL(UUID simID, UUID primID, string contentType, string url, string extraParams,
int updateTimer);
@ -125,11 +134,53 @@ namespace OpenSim.Region.Framework.Interfaces
// /// <param name='extraParams'></param>
// bool AlwaysIdenticalConversion(string bodyData, string extraParams);
byte[] ConvertUrl(string url, string extraParams);
byte[] ConvertData(string bodyData, string extraParams);
IDynamicTexture ConvertUrl(string url, string extraParams);
IDynamicTexture ConvertData(string bodyData, string extraParams);
bool AsyncConvertUrl(UUID id, string url, string extraParams);
bool AsyncConvertData(UUID id, string bodyData, string extraParams);
void GetDrawStringSize(string text, string fontName, int fontSize,
out double xSize, out double ySize);
}
public interface IDynamicTexture
{
/// <summary>
/// Input commands used to generate this data.
/// </summary>
/// <remarks>
/// Null if input commands were not used.
/// </remarks>
string InputCommands { get; }
/// <summary>
/// Uri used to generate this data.
/// </summary>
/// <remarks>
/// Null if a uri was not used.
/// </remarks>
Uri InputUri { get; }
/// <summary>
/// Extra input params used to generate this data.
/// </summary>
string InputParams { get; }
/// <summary>
/// Texture data.
/// </summary>
byte[] Data { get; }
/// <summary>
/// Size of texture.
/// </summary>
Size Size { get; }
/// <summary>
/// Signal whether the texture is reuseable (i.e. whether the same input data will always generate the same
/// texture).
/// </summary>
bool IsReuseable { get; }
}
}

View File

@ -680,6 +680,13 @@
; Default is false.
ReuseDynamicTextures = false
; If true, then textures generated dynamically that have a low data size relative to their pixel size are not reused
; This is to workaround an apparent LL 3.3.4 and earlier viewer bug where such textures are not redisplayed properly when pulled from the viewer cache.
; Only set this to true if you are sure that all the viewers using your simulator will not suffer from this problem.
; This setting only has an affect is ReuseDynamicTextures = true
; Default is false
ReuseDynamicLowDataTextures = false
[ODEPhysicsSettings]
; ##