diff options
author | John Hurliman | 2009-10-01 17:42:13 -0700 |
---|---|---|
committer | John Hurliman | 2009-10-01 17:42:13 -0700 |
commit | 6e0c79b8fe76c7d2c983cb7b258a75a3300e78f2 (patch) | |
tree | 0de3c250460102fc59e9373e4bb33de2afc5ef3a /OpenSim/Region | |
parent | Fixing LLClientView memory leak (diff) | |
download | opensim-SC-6e0c79b8fe76c7d2c983cb7b258a75a3300e78f2.zip opensim-SC-6e0c79b8fe76c7d2c983cb7b258a75a3300e78f2.tar.gz opensim-SC-6e0c79b8fe76c7d2c983cb7b258a75a3300e78f2.tar.bz2 opensim-SC-6e0c79b8fe76c7d2c983cb7b258a75a3300e78f2.tar.xz |
* 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)
Diffstat (limited to '')
3 files changed, 331 insertions, 449 deletions
diff --git a/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs b/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs index d86b123..1448722 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs | |||
@@ -38,44 +38,37 @@ using System.Reflection; | |||
38 | namespace OpenSim.Region.ClientStack.LindenUDP | 38 | namespace OpenSim.Region.ClientStack.LindenUDP |
39 | { | 39 | { |
40 | /// <summary> | 40 | /// <summary> |
41 | /// We use this class to store image data and associated request data and attributes | 41 | /// Stores information about a current texture download and a reference to the texture asset |
42 | /// </summary> | 42 | /// </summary> |
43 | public class J2KImage | 43 | public class J2KImage |
44 | { | 44 | { |
45 | private const int IMAGE_PACKET_SIZE = 1000; | ||
46 | private const int FIRST_PACKET_SIZE = 600; | ||
47 | |||
45 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 48 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
46 | 49 | ||
47 | public double m_designatedPriorityKey; | 50 | public uint m_lastSequence; |
48 | public double m_requestedPriority = 0.0d; | 51 | public float m_requestedPriority; |
49 | public uint m_lastSequence = 0; | ||
50 | public uint m_requestedPacketNumber; | 52 | public uint m_requestedPacketNumber; |
51 | public sbyte m_requestedDiscardLevel; | 53 | public sbyte m_requestedDiscardLevel; |
52 | public UUID m_requestedUUID; | 54 | public UUID m_requestedUUID; |
53 | public IJ2KDecoder m_j2kDecodeModule; | 55 | public IJ2KDecoder m_j2kDecodeModule; |
54 | public IAssetService m_assetCache; | 56 | public IAssetService m_assetCache; |
55 | public OpenJPEG.J2KLayerInfo[] Layers = new OpenJPEG.J2KLayerInfo[0]; | 57 | public OpenJPEG.J2KLayerInfo[] m_layers; |
56 | public AssetBase m_MissingSubstitute = null; | 58 | public bool m_decoded; |
57 | public bool m_decoded = false; | 59 | public bool m_hasasset; |
58 | public bool m_completedSendAtCurrentDiscardLevel; | 60 | public C5.IPriorityQueueHandle<J2KImage> m_priorityQueueHandle; |
59 | 61 | ||
60 | private sbyte m_discardLevel=-1; | ||
61 | private uint m_packetNumber; | 62 | private uint m_packetNumber; |
62 | private bool m_decoderequested = false; | 63 | private bool m_decoderequested; |
63 | public bool m_hasasset = false; | 64 | private bool m_asset_requested; |
64 | private bool m_asset_requested = false; | 65 | private bool m_sentinfo; |
65 | private bool m_sentinfo = false; | 66 | private uint m_stopPacket; |
66 | private uint m_stopPacket = 0; | 67 | private AssetBase m_asset; |
67 | private const int cImagePacketSize = 1000; | 68 | private int m_assetDataLength; |
68 | private const int cFirstPacketSize = 600; | 69 | private LLImageManager m_imageManager; |
69 | |||
70 | private AssetBase m_asset = null; | ||
71 | private int m_assetDataLength = 0; | ||
72 | |||
73 | private LLImageManager m_image; | ||
74 | 70 | ||
75 | public J2KImage(LLImageManager image) | 71 | #region Properties |
76 | { | ||
77 | m_image = image; | ||
78 | } | ||
79 | 72 | ||
80 | public uint m_pPacketNumber | 73 | public uint m_pPacketNumber |
81 | { | 74 | { |
@@ -88,10 +81,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
88 | 81 | ||
89 | public byte[] Data | 82 | public byte[] Data |
90 | { | 83 | { |
91 | get | 84 | get |
92 | { | 85 | { |
93 | if (m_asset != null) | 86 | if (m_asset != null) |
94 | return m_asset.Data; | 87 | return m_asset.Data; |
95 | else | 88 | else |
96 | return null; | 89 | return null; |
97 | } | 90 | } |
@@ -101,9 +94,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
101 | { | 94 | { |
102 | if (!m_decoded) | 95 | if (!m_decoded) |
103 | return 0; | 96 | return 0; |
97 | |||
104 | try | 98 | try |
105 | { | 99 | { |
106 | return (ushort)(((m_assetDataLength - cFirstPacketSize + cImagePacketSize - 1) / cImagePacketSize) + 1); | 100 | return (ushort)(((m_assetDataLength - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1); |
107 | } | 101 | } |
108 | catch (Exception) | 102 | catch (Exception) |
109 | { | 103 | { |
@@ -114,127 +108,145 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
114 | } | 108 | } |
115 | } | 109 | } |
116 | 110 | ||
117 | public void DropAsset() | 111 | #endregion Properties |
118 | { | ||
119 | //m_log.WarnFormat("[LLIMAGE MANAGER]: Dropping texture asset {0}", m_requestedUUID); | ||
120 | m_asset = null; | ||
121 | m_hasasset = false; | ||
122 | m_asset_requested = false; | ||
123 | } | ||
124 | 112 | ||
125 | public void J2KDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers) | 113 | public J2KImage(LLImageManager imageManager) |
126 | { | 114 | { |
127 | m_image.m_outstandingtextures++; | 115 | m_imageManager = imageManager; |
128 | Layers = layers; | ||
129 | m_decoded = true; | ||
130 | RunUpdate(); | ||
131 | } | 116 | } |
132 | 117 | ||
133 | public void AssetDataCallback(UUID AssetID, AssetBase asset) | 118 | public bool SendPackets(LLClientView client, int maxpack) |
134 | { | 119 | { |
135 | m_hasasset = true; | 120 | if (m_packetNumber <= m_stopPacket) |
136 | if (asset == null || asset.Data == null) | ||
137 | { | ||
138 | m_asset = m_MissingSubstitute; | ||
139 | } | ||
140 | else | ||
141 | { | 121 | { |
142 | m_asset = asset; | 122 | bool SendMore = true; |
143 | } | 123 | if (!m_sentinfo || (m_packetNumber == 0)) |
144 | 124 | { | |
145 | m_assetDataLength = m_asset.Data.Length; | 125 | if (SendFirstPacket(client)) |
146 | 126 | { | |
147 | RunUpdate(); | 127 | SendMore = false; |
148 | } | 128 | } |
149 | 129 | m_sentinfo = true; | |
150 | protected void AssetReceived(string id, Object sender, AssetBase asset) | 130 | m_packetNumber++; |
151 | { | 131 | } |
152 | UUID assetID = UUID.Zero; | 132 | // bool ignoreStop = false; |
153 | if (asset != null) | 133 | if (m_packetNumber < 2) |
154 | assetID = asset.FullID; | 134 | { |
155 | 135 | m_packetNumber = 2; | |
156 | AssetDataCallback(assetID, asset); | 136 | } |
157 | |||
158 | } | ||
159 | 137 | ||
160 | private int GetPacketForBytePosition(int bytePosition) | 138 | int count = 0; |
161 | { | 139 | while (SendMore && count < maxpack && m_packetNumber <= m_stopPacket) |
162 | return ((bytePosition - cFirstPacketSize + cImagePacketSize - 1) / cImagePacketSize) + 1; | 140 | { |
163 | } | 141 | count++; |
142 | SendMore = SendPacket(client); | ||
143 | m_packetNumber++; | ||
144 | } | ||
164 | 145 | ||
165 | public int LastPacketSize() | 146 | if (m_packetNumber > m_stopPacket) |
166 | { | 147 | return true; |
167 | if (m_packetNumber == 1) | ||
168 | return m_assetDataLength; | ||
169 | int lastsize = (m_assetDataLength - cFirstPacketSize) % cImagePacketSize; | ||
170 | //If the last packet size is zero, it's really cImagePacketSize, it sits on the boundary | ||
171 | if (lastsize == 0) | ||
172 | { | ||
173 | lastsize = cImagePacketSize; | ||
174 | } | 148 | } |
175 | return lastsize; | ||
176 | } | ||
177 | |||
178 | public int CurrentBytePosition() | ||
179 | { | ||
180 | if (m_packetNumber == 0) | ||
181 | return 0; | ||
182 | if (m_packetNumber == 1) | ||
183 | return cFirstPacketSize; | ||
184 | 149 | ||
185 | int result = cFirstPacketSize + ((int)m_packetNumber - 2) * cImagePacketSize; | 150 | return false; |
186 | if (result < 0) | ||
187 | { | ||
188 | result = cFirstPacketSize; | ||
189 | } | ||
190 | return result; | ||
191 | } | 151 | } |
192 | 152 | ||
193 | public bool SendFirstPacket(LLClientView client) | 153 | public void RunUpdate() |
194 | { | 154 | { |
195 | // this means we don't have | 155 | //This is where we decide what we need to update |
196 | if (Data == null) | 156 | //and assign the real discardLevel and packetNumber |
197 | { | 157 | //assuming of course that the connected client might be bonkers |
198 | client.SendImageNotFound(m_requestedUUID); | 158 | |
199 | m_log.WarnFormat("[TEXTURE]: Got null Data element on a asset {0}.. and the missing image Data property is al", m_requestedUUID); | 159 | if (!m_hasasset) |
200 | return true; | ||
201 | } | ||
202 | // Do we have less then 1 packet's worth of data? | ||
203 | else if (m_assetDataLength <= cFirstPacketSize) | ||
204 | { | 160 | { |
205 | // Send only 1 packet | 161 | if (!m_asset_requested) |
206 | client.SendImageFirstPart(1, m_requestedUUID, (uint)m_assetDataLength, m_asset.Data, 2); | 162 | { |
207 | m_stopPacket = 0; | 163 | m_asset_requested = true; |
208 | return true; | 164 | m_assetCache.Get(m_requestedUUID.ToString(), this, AssetReceived); |
165 | } | ||
209 | } | 166 | } |
210 | else | 167 | else |
211 | { | 168 | { |
212 | byte[] firstImageData = new byte[cFirstPacketSize]; | 169 | if (!m_decoded) |
213 | try | 170 | { |
214 | { | 171 | //We need to decode the requested image first |
215 | Buffer.BlockCopy(m_asset.Data, 0, firstImageData, 0, (int)cFirstPacketSize); | 172 | if (!m_decoderequested) |
216 | client.SendImageFirstPart(TexturePacketCount(), m_requestedUUID, (uint)m_assetDataLength, firstImageData, 2); | 173 | { |
174 | //Request decode | ||
175 | m_decoderequested = true; | ||
176 | // Do we have a jpeg decoder? | ||
177 | if (m_j2kDecodeModule != null) | ||
178 | { | ||
179 | if (Data == null) | ||
180 | { | ||
181 | J2KDecodedCallback(m_requestedUUID, new OpenJPEG.J2KLayerInfo[0]); | ||
182 | } | ||
183 | else | ||
184 | { | ||
185 | // Send it off to the jpeg decoder | ||
186 | m_j2kDecodeModule.BeginDecode(m_requestedUUID, Data, J2KDecodedCallback); | ||
187 | } | ||
188 | |||
189 | } | ||
190 | else | ||
191 | { | ||
192 | J2KDecodedCallback(m_requestedUUID, new OpenJPEG.J2KLayerInfo[0]); | ||
193 | } | ||
194 | } | ||
217 | } | 195 | } |
218 | catch (Exception) | 196 | else |
219 | { | 197 | { |
220 | m_log.Error("Texture block copy failed. Possibly out of memory?"); | 198 | // Check for missing image asset data |
221 | return true; | 199 | if (m_asset == null || m_asset.Data == null) |
200 | { | ||
201 | // FIXME: | ||
202 | m_packetNumber = m_stopPacket; | ||
203 | return; | ||
204 | } | ||
205 | |||
206 | if (m_requestedDiscardLevel >= 0 || m_stopPacket == 0) | ||
207 | { | ||
208 | int maxDiscardLevel = Math.Max(0, m_layers.Length - 1); | ||
209 | |||
210 | // Treat initial texture downloads with a DiscardLevel of -1 a request for the highest DiscardLevel | ||
211 | if (m_requestedDiscardLevel < 0 && m_stopPacket == 0) | ||
212 | m_requestedDiscardLevel = (sbyte)maxDiscardLevel; | ||
213 | |||
214 | // Clamp at the highest discard level | ||
215 | m_requestedDiscardLevel = (sbyte)Math.Min(m_requestedDiscardLevel, maxDiscardLevel); | ||
216 | |||
217 | //Calculate the m_stopPacket | ||
218 | if (m_layers.Length > 0) | ||
219 | { | ||
220 | m_stopPacket = (uint)GetPacketForBytePosition(m_layers[(m_layers.Length - 1) - m_requestedDiscardLevel].End); | ||
221 | //I don't know why, but the viewer seems to expect the final packet if the file | ||
222 | //is just one packet bigger. | ||
223 | if (TexturePacketCount() == m_stopPacket + 1) | ||
224 | { | ||
225 | m_stopPacket = TexturePacketCount(); | ||
226 | } | ||
227 | } | ||
228 | else | ||
229 | { | ||
230 | m_stopPacket = TexturePacketCount(); | ||
231 | } | ||
232 | |||
233 | m_packetNumber = m_requestedPacketNumber; | ||
234 | } | ||
222 | } | 235 | } |
223 | } | 236 | } |
224 | return false; | ||
225 | } | 237 | } |
226 | 238 | ||
227 | private bool SendPacket(LLClientView client) | 239 | private bool SendPacket(LLClientView client) |
228 | { | 240 | { |
229 | bool complete = false; | 241 | bool complete = false; |
230 | int imagePacketSize = ((int)m_packetNumber == (TexturePacketCount())) ? LastPacketSize() : cImagePacketSize; | 242 | int imagePacketSize = ((int)m_packetNumber == (TexturePacketCount())) ? LastPacketSize() : IMAGE_PACKET_SIZE; |
231 | 243 | ||
232 | try | 244 | try |
233 | { | 245 | { |
234 | if ((CurrentBytePosition() + cImagePacketSize) > m_assetDataLength) | 246 | if ((CurrentBytePosition() + IMAGE_PACKET_SIZE) > m_assetDataLength) |
235 | { | 247 | { |
236 | imagePacketSize = LastPacketSize(); | 248 | imagePacketSize = LastPacketSize(); |
237 | complete=true; | 249 | complete = true; |
238 | if ((CurrentBytePosition() + imagePacketSize) > m_assetDataLength) | 250 | if ((CurrentBytePosition() + imagePacketSize) > m_assetDataLength) |
239 | { | 251 | { |
240 | imagePacketSize = m_assetDataLength - CurrentBytePosition(); | 252 | imagePacketSize = m_assetDataLength - CurrentBytePosition(); |
@@ -259,7 +271,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
259 | } | 271 | } |
260 | 272 | ||
261 | //Send the packet | 273 | //Send the packet |
262 | client.SendImageNextPart((ushort)(m_packetNumber-1), m_requestedUUID, imageData); | 274 | client.SendImageNextPart((ushort)(m_packetNumber - 1), m_requestedUUID, imageData); |
263 | } | 275 | } |
264 | if (complete) | 276 | if (complete) |
265 | { | 277 | { |
@@ -275,146 +287,107 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
275 | return false; | 287 | return false; |
276 | } | 288 | } |
277 | } | 289 | } |
278 | public bool SendPackets(LLClientView client, int maxpack) | 290 | |
291 | private int GetPacketForBytePosition(int bytePosition) | ||
279 | { | 292 | { |
293 | return ((bytePosition - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1; | ||
294 | } | ||
280 | 295 | ||
281 | if (!m_completedSendAtCurrentDiscardLevel) | 296 | private int LastPacketSize() |
297 | { | ||
298 | if (m_packetNumber == 1) | ||
299 | return m_assetDataLength; | ||
300 | int lastsize = (m_assetDataLength - FIRST_PACKET_SIZE) % IMAGE_PACKET_SIZE; | ||
301 | //If the last packet size is zero, it's really cImagePacketSize, it sits on the boundary | ||
302 | if (lastsize == 0) | ||
282 | { | 303 | { |
283 | if (m_packetNumber <= m_stopPacket) | 304 | lastsize = IMAGE_PACKET_SIZE; |
284 | { | ||
285 | bool SendMore = true; | ||
286 | if (!m_sentinfo || (m_packetNumber == 0)) | ||
287 | { | ||
288 | if (SendFirstPacket(client)) | ||
289 | { | ||
290 | SendMore = false; | ||
291 | } | ||
292 | m_sentinfo = true; | ||
293 | m_packetNumber++; | ||
294 | } | ||
295 | // bool ignoreStop = false; | ||
296 | if (m_packetNumber < 2) | ||
297 | { | ||
298 | m_packetNumber = 2; | ||
299 | } | ||
300 | |||
301 | int count = 0; | ||
302 | while (SendMore && count < maxpack && m_packetNumber <= m_stopPacket) | ||
303 | { | ||
304 | count++; | ||
305 | SendMore = SendPacket(client); | ||
306 | m_packetNumber++; | ||
307 | } | ||
308 | |||
309 | if (m_packetNumber > m_stopPacket) | ||
310 | { | ||
311 | return true; | ||
312 | } | ||
313 | } | ||
314 | } | 305 | } |
315 | return false; | 306 | return lastsize; |
316 | } | 307 | } |
317 | 308 | ||
318 | public void RunUpdate() | 309 | private int CurrentBytePosition() |
319 | { | 310 | { |
320 | //This is where we decide what we need to update | 311 | if (m_packetNumber == 0) |
321 | //and assign the real discardLevel and packetNumber | 312 | return 0; |
322 | //assuming of course that the connected client might be bonkers | 313 | if (m_packetNumber == 1) |
314 | return FIRST_PACKET_SIZE; | ||
323 | 315 | ||
324 | if (!m_hasasset) | 316 | int result = FIRST_PACKET_SIZE + ((int)m_packetNumber - 2) * IMAGE_PACKET_SIZE; |
317 | if (result < 0) | ||
325 | { | 318 | { |
319 | result = FIRST_PACKET_SIZE; | ||
320 | } | ||
321 | return result; | ||
322 | } | ||
326 | 323 | ||
327 | if (!m_asset_requested) | 324 | private bool SendFirstPacket(LLClientView client) |
325 | { | ||
326 | // this means we don't have | ||
327 | if (Data == null) | ||
328 | { | ||
329 | client.SendImageNotFound(m_requestedUUID); | ||
330 | m_log.WarnFormat("[TEXTURE]: Got null Data element on a asset {0}.. and the missing image Data property is al", m_requestedUUID); | ||
331 | return true; | ||
332 | } | ||
333 | // Do we have less then 1 packet's worth of data? | ||
334 | else if (m_assetDataLength <= FIRST_PACKET_SIZE) | ||
335 | { | ||
336 | // Send only 1 packet | ||
337 | client.SendImageFirstPart(1, m_requestedUUID, (uint)m_assetDataLength, m_asset.Data, 2); | ||
338 | m_stopPacket = 0; | ||
339 | return true; | ||
340 | } | ||
341 | else | ||
342 | { | ||
343 | byte[] firstImageData = new byte[FIRST_PACKET_SIZE]; | ||
344 | try | ||
328 | { | 345 | { |
329 | m_asset_requested = true; | 346 | Buffer.BlockCopy(m_asset.Data, 0, firstImageData, 0, (int)FIRST_PACKET_SIZE); |
330 | m_assetCache.Get(m_requestedUUID.ToString(), this, AssetReceived); | 347 | client.SendImageFirstPart(TexturePacketCount(), m_requestedUUID, (uint)m_assetDataLength, firstImageData, 2); |
331 | 348 | } | |
349 | catch (Exception) | ||
350 | { | ||
351 | m_log.Error("Texture block copy failed. Possibly out of memory?"); | ||
352 | return true; | ||
332 | } | 353 | } |
354 | } | ||
355 | return false; | ||
356 | } | ||
357 | |||
358 | private void J2KDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers) | ||
359 | { | ||
360 | m_layers = layers; | ||
361 | m_decoded = true; | ||
362 | RunUpdate(); | ||
363 | } | ||
364 | |||
365 | private void AssetDataCallback(UUID AssetID, AssetBase asset) | ||
366 | { | ||
367 | m_hasasset = true; | ||
333 | 368 | ||
369 | if (asset == null || asset.Data == null) | ||
370 | { | ||
371 | m_asset = null; | ||
372 | m_decoded = true; | ||
334 | } | 373 | } |
335 | else | 374 | else |
336 | { | 375 | { |
376 | m_asset = asset; | ||
377 | m_assetDataLength = m_asset.Data.Length; | ||
378 | } | ||
337 | 379 | ||
380 | RunUpdate(); | ||
381 | } | ||
338 | 382 | ||
339 | if (!m_decoded) | 383 | private void AssetReceived(string id, Object sender, AssetBase asset) |
340 | { | 384 | { |
341 | //We need to decode the requested image first | 385 | UUID assetID = UUID.Zero; |
342 | if (!m_decoderequested) | 386 | if (asset != null) |
343 | { | 387 | assetID = asset.FullID; |
344 | //Request decode | ||
345 | m_decoderequested = true; | ||
346 | // Do we have a jpeg decoder? | ||
347 | if (m_j2kDecodeModule != null) | ||
348 | { | ||
349 | if (Data == null) | ||
350 | { | ||
351 | J2KDecodedCallback(m_requestedUUID, new OpenJPEG.J2KLayerInfo[0]); | ||
352 | } | ||
353 | else | ||
354 | { | ||
355 | // Send it off to the jpeg decoder | ||
356 | m_j2kDecodeModule.BeginDecode(m_requestedUUID, Data, J2KDecodedCallback); | ||
357 | } | ||
358 | |||
359 | } | ||
360 | else | ||
361 | { | ||
362 | J2KDecodedCallback(m_requestedUUID, new OpenJPEG.J2KLayerInfo[0]); | ||
363 | } | ||
364 | } | ||
365 | |||
366 | } | ||
367 | else | ||
368 | { | ||
369 | //discardLevel of -1 means just update the priority | ||
370 | if (m_requestedDiscardLevel != -1) | ||
371 | { | ||
372 | //Evaluate the discard level | ||
373 | //First, is it positive? | ||
374 | if (m_requestedDiscardLevel >= 0) | ||
375 | { | ||
376 | if (m_requestedDiscardLevel > Layers.Length - 1) | ||
377 | { | ||
378 | m_discardLevel = (sbyte)(Layers.Length - 1); | ||
379 | } | ||
380 | else | ||
381 | { | ||
382 | m_discardLevel = m_requestedDiscardLevel; | ||
383 | } | ||
384 | 388 | ||
385 | //Calculate the m_stopPacket | 389 | AssetDataCallback(assetID, asset); |
386 | if (Layers.Length > 0) | ||
387 | { | ||
388 | m_stopPacket = (uint)GetPacketForBytePosition(Layers[(Layers.Length - 1) - m_discardLevel].End); | ||
389 | //I don't know why, but the viewer seems to expect the final packet if the file | ||
390 | //is just one packet bigger. | ||
391 | if (TexturePacketCount() == m_stopPacket + 1) | ||
392 | { | ||
393 | m_stopPacket = TexturePacketCount(); | ||
394 | } | ||
395 | } | ||
396 | else | ||
397 | { | ||
398 | m_stopPacket = TexturePacketCount(); | ||
399 | } | ||
400 | //Don't reset packet number unless we're waiting or it's ahead of us | ||
401 | if (m_completedSendAtCurrentDiscardLevel || m_requestedPacketNumber>m_packetNumber) | ||
402 | { | ||
403 | m_packetNumber = m_requestedPacketNumber; | ||
404 | } | ||
405 | 390 | ||
406 | if (m_packetNumber <= m_stopPacket) | ||
407 | { | ||
408 | m_completedSendAtCurrentDiscardLevel = false; | ||
409 | } | ||
410 | } | ||
411 | } | ||
412 | else | ||
413 | { | ||
414 | m_packetNumber = m_stopPacket; | ||
415 | } | ||
416 | } | ||
417 | } | ||
418 | } | 391 | } |
419 | } | 392 | } |
420 | } | 393 | } |
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs b/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs index b039049..a82eaae 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs | |||
@@ -27,26 +27,28 @@ | |||
27 | 27 | ||
28 | using System; | 28 | using System; |
29 | using System.Threading; | 29 | using System.Threading; |
30 | using System.Collections; | ||
30 | using System.Collections.Generic; | 31 | using System.Collections.Generic; |
32 | using System.Reflection; | ||
31 | using OpenMetaverse; | 33 | using OpenMetaverse; |
32 | using OpenMetaverse.Imaging; | 34 | using OpenMetaverse.Imaging; |
33 | using OpenSim.Framework; | 35 | using OpenSim.Framework; |
34 | using OpenSim.Region.Framework.Interfaces; | 36 | using OpenSim.Region.Framework.Interfaces; |
35 | using OpenSim.Services.Interfaces; | 37 | using OpenSim.Services.Interfaces; |
36 | using log4net; | 38 | using log4net; |
37 | using System.Reflection; | ||
38 | 39 | ||
39 | namespace OpenSim.Region.ClientStack.LindenUDP | 40 | namespace OpenSim.Region.ClientStack.LindenUDP |
40 | { | 41 | { |
41 | |||
42 | public class LLImageManager | 42 | public class LLImageManager |
43 | { | 43 | { |
44 | 44 | private sealed class J2KImageComparer : IComparer<J2KImage> | |
45 | //Public interfaces: | 45 | { |
46 | //Constructor - (LLClientView, IAssetCache, IJ2KDecoder); | 46 | public int Compare(J2KImage x, J2KImage y) |
47 | //void EnqueueReq - (TextureRequestArgs) | 47 | { |
48 | //ProcessImageQueue | 48 | return x.m_requestedPriority.CompareTo(y.m_requestedPriority); |
49 | //Close | 49 | } |
50 | } | ||
51 | |||
50 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 52 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
51 | private bool m_shuttingdown = false; | 53 | private bool m_shuttingdown = false; |
52 | private long m_lastloopprocessed = 0; | 54 | private long m_lastloopprocessed = 0; |
@@ -54,28 +56,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
54 | private LLClientView m_client; //Client we're assigned to | 56 | private LLClientView m_client; //Client we're assigned to |
55 | private IAssetService m_assetCache; //Asset Cache | 57 | private IAssetService m_assetCache; //Asset Cache |
56 | private IJ2KDecoder m_j2kDecodeModule; //Our J2K module | 58 | private IJ2KDecoder m_j2kDecodeModule; //Our J2K module |
59 | private C5.IntervalHeap<J2KImage> m_priorityQueue = new C5.IntervalHeap<J2KImage>(10, new J2KImageComparer()); | ||
57 | 60 | ||
58 | private readonly AssetBase m_missingsubstitute; //Sustitute for bad decodes | ||
59 | private Dictionary<UUID,J2KImage> m_imagestore; // Our main image storage dictionary | ||
60 | private SortedList<double,UUID> m_priorities; // For fast image lookup based on priority | ||
61 | private Dictionary<int, int> m_priorityresolver; //Enabling super fast assignment of images with the same priorities | ||
62 | |||
63 | private const double doubleMinimum = .0000001; | ||
64 | |||
65 | public int m_outstandingtextures = 0; | ||
66 | //Constructor | ||
67 | public LLImageManager(LLClientView client, IAssetService pAssetCache, IJ2KDecoder pJ2kDecodeModule) | 61 | public LLImageManager(LLClientView client, IAssetService pAssetCache, IJ2KDecoder pJ2kDecodeModule) |
68 | { | 62 | { |
69 | |||
70 | m_imagestore = new Dictionary<UUID,J2KImage>(); | ||
71 | m_priorities = new SortedList<double,UUID>(); | ||
72 | m_priorityresolver = new Dictionary<int, int>(); | ||
73 | m_client = client; | 63 | m_client = client; |
74 | m_assetCache = pAssetCache; | 64 | m_assetCache = pAssetCache; |
75 | if (pAssetCache != null) | ||
76 | m_missingsubstitute = pAssetCache.Get("5748decc-f629-461c-9a36-a35a221fe21f"); | ||
77 | else | ||
78 | m_log.Error("[ClientView] - couldn't set missing image, all manner of things will probably break"); | ||
79 | m_j2kDecodeModule = pJ2kDecodeModule; | 65 | m_j2kDecodeModule = pJ2kDecodeModule; |
80 | } | 66 | } |
81 | 67 | ||
@@ -88,174 +74,147 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
88 | //Make sure we're not shutting down.. | 74 | //Make sure we're not shutting down.. |
89 | if (!m_shuttingdown) | 75 | if (!m_shuttingdown) |
90 | { | 76 | { |
77 | J2KImage imgrequest; | ||
91 | 78 | ||
92 | //Do we already know about this UUID? | 79 | // Do a linear search for this texture download |
93 | if (m_imagestore.ContainsKey(newRequest.RequestedAssetID)) | 80 | m_priorityQueue.Find(delegate(J2KImage img) { return img.m_requestedUUID == newRequest.RequestedAssetID; }, out imgrequest); |
94 | { | ||
95 | //Check the packet sequence to make sure this isn't older than | ||
96 | //one we've already received | ||
97 | |||
98 | J2KImage imgrequest = m_imagestore[newRequest.RequestedAssetID]; | ||
99 | 81 | ||
100 | // This is the inbound request sequence number. We can ignore | 82 | if (imgrequest != null) |
101 | // "old" ones. | 83 | { |
102 | 84 | if (newRequest.DiscardLevel == -1 && newRequest.Priority == 0f) | |
103 | if (newRequest.requestSequence > imgrequest.m_lastSequence) | ||
104 | { | 85 | { |
86 | m_log.Debug("[JPEG2000]: (CAN) ID=" + newRequest.RequestedAssetID); | ||
105 | 87 | ||
106 | imgrequest.m_lastSequence = newRequest.requestSequence; | 88 | try { m_priorityQueue.Delete(imgrequest.m_priorityQueueHandle); } |
107 | 89 | catch (Exception) { } | |
108 | //Check the priority | 90 | } |
91 | else | ||
92 | { | ||
93 | m_log.DebugFormat("[JPEG2000]: (UPD) ID={0}: D={1}, S={2}, P={3}", | ||
94 | newRequest.RequestedAssetID, newRequest.DiscardLevel, newRequest.PacketNumber, newRequest.Priority); | ||
109 | 95 | ||
110 | double priority = imgrequest.m_requestedPriority; | 96 | //Check the packet sequence to make sure this isn't older than |
111 | if (priority != newRequest.Priority) | 97 | //one we've already received |
98 | if (newRequest.requestSequence > imgrequest.m_lastSequence) | ||
112 | { | 99 | { |
113 | //Remove the old priority | 100 | //Update the sequence number of the last RequestImage packet |
114 | m_priorities.Remove(imgrequest.m_designatedPriorityKey); | 101 | imgrequest.m_lastSequence = newRequest.requestSequence; |
115 | //Assign a new unique priority | ||
116 | imgrequest.m_requestedPriority = newRequest.Priority; | ||
117 | imgrequest.m_designatedPriorityKey = AssignPriority(newRequest.RequestedAssetID, newRequest.Priority); | ||
118 | } | ||
119 | 102 | ||
120 | //Update the requested discard level | 103 | //Update the requested discard level |
121 | imgrequest.m_requestedDiscardLevel = newRequest.DiscardLevel; | 104 | imgrequest.m_requestedDiscardLevel = newRequest.DiscardLevel; |
122 | 105 | ||
123 | //Update the requested packet number | 106 | //Update the requested packet number |
124 | imgrequest.m_requestedPacketNumber = newRequest.PacketNumber; | 107 | imgrequest.m_requestedPacketNumber = newRequest.PacketNumber; |
125 | 108 | ||
126 | //Check if this will create an outstanding texture request | 109 | //Update the requested priority |
127 | bool activated = imgrequest.m_completedSendAtCurrentDiscardLevel; | 110 | imgrequest.m_requestedPriority = newRequest.Priority; |
128 | //Run an update | 111 | try { m_priorityQueue.Replace(imgrequest.m_priorityQueueHandle, imgrequest); } |
129 | 112 | catch (Exception) { imgrequest.m_priorityQueueHandle = null; m_priorityQueue.Add(ref imgrequest.m_priorityQueueHandle, imgrequest); } | |
130 | imgrequest.RunUpdate(); | ||
131 | 113 | ||
132 | if (activated && !imgrequest.m_completedSendAtCurrentDiscardLevel && imgrequest.m_decoded) | 114 | //Run an update |
133 | { | 115 | imgrequest.RunUpdate(); |
134 | Interlocked.Increment(ref m_outstandingtextures); | ||
135 | } | 116 | } |
136 | } | 117 | } |
137 | } | 118 | } |
138 | else | 119 | else |
139 | { | 120 | { |
140 | J2KImage imgrequest = new J2KImage(this); | 121 | if (newRequest.DiscardLevel == -1 && newRequest.Priority == 0f) |
141 | 122 | { | |
142 | //Assign our missing substitute | 123 | m_log.DebugFormat("[JPEG2000]: (IGN) ID={0}: D={1}, S={2}, P={3}", |
143 | imgrequest.m_MissingSubstitute = m_missingsubstitute; | 124 | newRequest.RequestedAssetID, newRequest.DiscardLevel, newRequest.PacketNumber, newRequest.Priority); |
125 | } | ||
126 | else | ||
127 | { | ||
128 | m_log.DebugFormat("[JPEG2000]: (NEW) ID={0}: D={1}, S={2}, P={3}", | ||
129 | newRequest.RequestedAssetID, newRequest.DiscardLevel, newRequest.PacketNumber, newRequest.Priority); | ||
144 | 130 | ||
145 | //Assign our decoder module | 131 | imgrequest = new J2KImage(this); |
146 | imgrequest.m_j2kDecodeModule = m_j2kDecodeModule; | ||
147 | 132 | ||
148 | //Assign our asset cache module | 133 | //Assign our decoder module |
149 | imgrequest.m_assetCache = m_assetCache; | 134 | imgrequest.m_j2kDecodeModule = m_j2kDecodeModule; |
150 | 135 | ||
151 | //Assign a priority based on our request | 136 | //Assign our asset cache module |
152 | imgrequest.m_designatedPriorityKey = AssignPriority(newRequest.RequestedAssetID, newRequest.Priority); | 137 | imgrequest.m_assetCache = m_assetCache; |
153 | 138 | ||
154 | //Assign the requested discard level | 139 | //Assign the requested discard level |
155 | imgrequest.m_requestedDiscardLevel = newRequest.DiscardLevel; | 140 | imgrequest.m_requestedDiscardLevel = newRequest.DiscardLevel; |
156 | 141 | ||
157 | //Assign the requested packet number | 142 | //Assign the requested packet number |
158 | imgrequest.m_requestedPacketNumber = newRequest.PacketNumber; | 143 | imgrequest.m_requestedPacketNumber = newRequest.PacketNumber; |
159 | 144 | ||
160 | //Assign the requested priority | 145 | //Assign the requested priority |
161 | imgrequest.m_requestedPriority = newRequest.Priority; | 146 | imgrequest.m_requestedPriority = newRequest.Priority; |
162 | 147 | ||
163 | //Assign the asset uuid | 148 | //Assign the asset uuid |
164 | imgrequest.m_requestedUUID = newRequest.RequestedAssetID; | 149 | imgrequest.m_requestedUUID = newRequest.RequestedAssetID; |
165 | 150 | ||
166 | m_imagestore.Add(imgrequest.m_requestedUUID, imgrequest); | 151 | //Assign the requested priority |
152 | imgrequest.m_requestedPriority = newRequest.Priority; | ||
167 | 153 | ||
168 | //Run an update | 154 | //Add this download to the priority queue |
169 | imgrequest.RunUpdate(); | 155 | m_priorityQueue.Add(ref imgrequest.m_priorityQueueHandle, imgrequest); |
170 | 156 | ||
157 | //Run an update | ||
158 | imgrequest.RunUpdate(); | ||
159 | } | ||
171 | } | 160 | } |
172 | } | 161 | } |
173 | } | 162 | } |
174 | 163 | ||
175 | private double AssignPriority(UUID pAssetID, double pPriority) | 164 | public bool ProcessImageQueue(int count, int maxpack) |
176 | { | 165 | { |
177 | 166 | //count is the number of textures we want to process in one go. | |
178 | //First, find out if we can just assign directly | 167 | //As part of this class re-write, that number will probably rise |
179 | if (m_priorityresolver.ContainsKey((int)pPriority) == false) | 168 | //since we're processing in a more efficient manner. |
169 | |||
170 | // this can happen during Close() | ||
171 | if (m_client == null) | ||
172 | return false; | ||
173 | |||
174 | int numCollected = 0; | ||
175 | |||
176 | //Calculate our threshold | ||
177 | int threshold; | ||
178 | if (m_lastloopprocessed == 0) | ||
180 | { | 179 | { |
181 | m_priorities.Add((double)((int)pPriority), pAssetID); | 180 | if (m_client.PacketHandler == null || m_client.PacketHandler.PacketQueue == null || m_client.PacketHandler.PacketQueue.TextureThrottle == null) |
182 | m_priorityresolver.Add((int)pPriority, 0); | 181 | return false; |
183 | return (double)((int)pPriority); | 182 | //This is decent for a semi fast machine, but we'll calculate it more accurately based on time below |
183 | threshold = m_client.PacketHandler.PacketQueue.TextureThrottle.Current / 6300; | ||
184 | m_lastloopprocessed = DateTime.Now.Ticks; | ||
184 | } | 185 | } |
185 | else | 186 | else |
186 | { | 187 | { |
187 | //Use the hash lookup goodness of a secondary dictionary to find a free slot | 188 | double throttleseconds = ((double)DateTime.Now.Ticks - (double)m_lastloopprocessed) / (double)TimeSpan.TicksPerSecond; |
188 | double mFreePriority = ((int)pPriority) + (doubleMinimum * (m_priorityresolver[(int)pPriority] + 1)); | 189 | throttleseconds = throttleseconds * m_client.PacketHandler.PacketQueue.TextureThrottle.Current; |
189 | m_priorities[mFreePriority] = pAssetID; | ||
190 | m_priorityresolver[(int)pPriority]++; | ||
191 | return mFreePriority; | ||
192 | } | ||
193 | 190 | ||
191 | //Average of 1000 bytes per packet | ||
192 | throttleseconds = throttleseconds / 1000; | ||
194 | 193 | ||
194 | //Safe-zone multiplier of 2.0 | ||
195 | threshold = (int)(throttleseconds * 2.0); | ||
196 | m_lastloopprocessed = DateTime.Now.Ticks; | ||
195 | 197 | ||
196 | } | 198 | } |
197 | 199 | ||
198 | public bool ProcessImageQueue(int count, int maxpack) | 200 | if (m_client.PacketHandler == null) |
199 | { | 201 | return false; |
200 | 202 | ||
201 | // this can happen during Close() | 203 | if (m_client.PacketHandler.PacketQueue == null) |
202 | if (m_client == null) | ||
203 | return false; | 204 | return false; |
204 | |||
205 | //Count is the number of textures we want to process in one go. | ||
206 | //As part of this class re-write, that number will probably rise | ||
207 | //since we're processing in a more efficient manner. | ||
208 | |||
209 | int numCollected = 0; | ||
210 | 205 | ||
211 | //Calculate our threshold | 206 | if (threshold < 10) |
212 | int threshold; | 207 | threshold = 10; |
213 | if (m_lastloopprocessed == 0) | ||
214 | { | ||
215 | if (m_client.PacketHandler == null || m_client.PacketHandler.PacketQueue == null || m_client.PacketHandler.PacketQueue.TextureThrottle == null) | ||
216 | return false; | ||
217 | //This is decent for a semi fast machine, but we'll calculate it more accurately based on time below | ||
218 | threshold = m_client.PacketHandler.PacketQueue.TextureThrottle.Current / 6300; | ||
219 | m_lastloopprocessed = DateTime.Now.Ticks; | ||
220 | } | ||
221 | else | ||
222 | { | ||
223 | double throttleseconds = ((double)DateTime.Now.Ticks - (double)m_lastloopprocessed) / (double)TimeSpan.TicksPerSecond; | ||
224 | throttleseconds = throttleseconds * m_client.PacketHandler.PacketQueue.TextureThrottle.Current; | ||
225 | |||
226 | //Average of 1000 bytes per packet | ||
227 | throttleseconds = throttleseconds / 1000; | ||
228 | |||
229 | //Safe-zone multiplier of 2.0 | ||
230 | threshold = (int)(throttleseconds * 2.0); | ||
231 | m_lastloopprocessed = DateTime.Now.Ticks; | ||
232 | |||
233 | } | ||
234 | |||
235 | if (threshold < 10) | ||
236 | { | ||
237 | threshold = 10; | ||
238 | } | ||
239 | |||
240 | if (m_client.PacketHandler == null) | ||
241 | return false; | ||
242 | |||
243 | if (m_client.PacketHandler.PacketQueue == null) | ||
244 | return false; | ||
245 | |||
246 | //First of all make sure our packet queue isn't above our threshold | ||
247 | 208 | ||
248 | //Uncomment this to see what the texture stack is doing | 209 | //Uncomment this to see what the texture stack is doing |
249 | //m_log.Debug("Queue: " + m_client.PacketHandler.PacketQueue.TextureOutgoingPacketQueueCount.ToString() + " Threshold: " + threshold.ToString() + " outstanding: " + m_outstandingtextures.ToString()); | 210 | //m_log.Debug("Queue: " + m_client.PacketHandler.PacketQueue.TextureOutgoingPacketQueueCount.ToString() + " Threshold: " + threshold.ToString() + " outstanding: " + m_outstandingtextures.ToString()); |
250 | if (m_client.PacketHandler.PacketQueue.TextureOutgoingPacketQueueCount < threshold && m_outstandingtextures > 0) | 211 | if (m_client.PacketHandler.PacketQueue.TextureOutgoingPacketQueueCount < threshold) |
251 | { | 212 | { |
252 | bool justreset = false; | 213 | while (m_priorityQueue.Count > 0) |
253 | |||
254 | for (int x = m_priorities.Count - 1; x > -1; x--) | ||
255 | { | 214 | { |
256 | 215 | J2KImage imagereq = m_priorityQueue.FindMax(); | |
257 | J2KImage imagereq = m_imagestore[m_priorities.Values[x]]; | 216 | |
258 | if (imagereq.m_decoded == true && !imagereq.m_completedSendAtCurrentDiscardLevel) | 217 | if (imagereq.m_decoded == true) |
259 | { | 218 | { |
260 | // we need to test this here now that we are dropping assets | 219 | // we need to test this here now that we are dropping assets |
261 | if (!imagereq.m_hasasset) | 220 | if (!imagereq.m_hasasset) |
@@ -265,78 +224,29 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
265 | continue; | 224 | continue; |
266 | } | 225 | } |
267 | 226 | ||
268 | numCollected++; | 227 | ++numCollected; |
228 | |||
269 | //SendPackets will send up to ten packets per cycle | 229 | //SendPackets will send up to ten packets per cycle |
270 | if (imagereq.SendPackets(m_client, maxpack)) | 230 | if (imagereq.SendPackets(m_client, maxpack)) |
271 | { | 231 | { |
272 | //Send complete | 232 | // Send complete. Destroy any knowledge of this transfer |
273 | if (!imagereq.m_completedSendAtCurrentDiscardLevel) | 233 | try { m_priorityQueue.Delete(imagereq.m_priorityQueueHandle); } |
274 | { | 234 | catch (Exception) { } |
275 | // I think this field imagereq.m_completedSendAtCurrentDiscardLevel | ||
276 | // is completely redundant | ||
277 | //imagereq.m_completedSendAtCurrentDiscardLevel = true; | ||
278 | |||
279 | Interlocked.Decrement(ref m_outstandingtextures); | ||
280 | |||
281 | // First and foremost, drop the reference to the asset | ||
282 | // so that the asset doesn't stay in memory forever. | ||
283 | // We'll Get it again from the asset service (usually cache) | ||
284 | // if/when the client requests it again. | ||
285 | // In order not to mess much with the current implementation | ||
286 | // of this management code, we drop only the asset reference | ||
287 | // but keep the image request itself. | ||
288 | imagereq.DropAsset(); | ||
289 | |||
290 | //Re-assign priority to bottom | ||
291 | //Remove the old priority | ||
292 | m_priorities.Remove(imagereq.m_designatedPriorityKey); | ||
293 | int lowest; | ||
294 | if (m_priorities.Count > 0) | ||
295 | { | ||
296 | lowest = (int)m_priorities.Keys[0]; | ||
297 | lowest--; | ||
298 | } | ||
299 | else | ||
300 | { | ||
301 | lowest = -10000; | ||
302 | } | ||
303 | m_priorities.Add((double)lowest, imagereq.m_requestedUUID); | ||
304 | imagereq.m_designatedPriorityKey = (double)lowest; | ||
305 | if (m_priorityresolver.ContainsKey((int)lowest)) | ||
306 | { | ||
307 | m_priorityresolver[(int)lowest]++; | ||
308 | } | ||
309 | else | ||
310 | { | ||
311 | m_priorityresolver.Add((int)lowest, 0); | ||
312 | } | ||
313 | } | ||
314 | } | ||
315 | if (numCollected == count) | ||
316 | { | ||
317 | break; | ||
318 | } | 235 | } |
319 | } | 236 | } |
320 | if (numCollected == count || m_outstandingtextures == 0) | ||
321 | break; | ||
322 | if (numCollected % m_outstandingtextures == 0 && !justreset) | ||
323 | { | ||
324 | //We've gotten as much as we can from the stack, | ||
325 | //reset to the top so that we can send MOAR DATA (nomnomnom)! | ||
326 | x = m_priorities.Count - 1; | ||
327 | 237 | ||
328 | justreset = true; //prevents us from getting stuck in a loop | 238 | if (numCollected == count) |
329 | } | 239 | break; |
330 | } | 240 | } |
331 | } | 241 | } |
332 | 242 | ||
333 | return m_outstandingtextures != 0; | 243 | return m_priorityQueue.Count > 0; |
334 | } | 244 | } |
335 | 245 | ||
336 | //Faux destructor | 246 | //Faux destructor |
337 | public void Close() | 247 | public void Close() |
338 | { | 248 | { |
339 | 249 | ||
340 | m_shuttingdown = true; | 250 | m_shuttingdown = true; |
341 | m_j2kDecodeModule = null; | 251 | m_j2kDecodeModule = null; |
342 | m_assetCache = null; | 252 | m_assetCache = null; |
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 | |||
161 | for (int i = 0; i < layerStarts.Count; i++) | 161 | for (int i = 0; i < layerStarts.Count; i++) |
162 | { | 162 | { |
163 | OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); | 163 | OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); |
164 | int start = layerStarts[i]; | ||
165 | 164 | ||
166 | if (i == 0) | 165 | if (i == 0) |
167 | layer.Start = 0; | 166 | layer.Start = 0; |