From c0082b4c3f1c27898724a4964c3a4648b963bfb7 Mon Sep 17 00:00:00 2001 From: UbitUmarov Date: Fri, 26 Jan 2024 18:39:03 +0000 Subject: [PATCH] a few changes to map grid service --- .../World/Warp3DMap/TerrainSplat.cs | 15 +- .../MapImage/MapImageServicesConnector.cs | 2 +- .../MapImageService/MapImageService.cs | 350 +++++++++--------- 3 files changed, 196 insertions(+), 171 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs b/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs index e962189610..69426032ba 100644 --- a/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs +++ b/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs @@ -29,6 +29,7 @@ using System; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; +using System.IO; using log4net; using OpenMetaverse; using OpenSim.Framework; @@ -119,7 +120,7 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap { try { - using(System.IO.MemoryStream stream = new System.IO.MemoryStream(asset.Data)) + using(MemoryStream stream = new MemoryStream(asset.Data)) detailTexture[i] = (Bitmap)Image.FromStream(stream); if(detailTexture[i].PixelFormat != PixelFormat.Format24bppRgb || @@ -153,14 +154,18 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap if(detailTexture[i] != null) { - if(detailTexture[i].PixelFormat != PixelFormat.Format24bppRgb || + //detailTexture[i].Save("terrOri" + i.ToString() + ".png"); + + if (detailTexture[i].PixelFormat != PixelFormat.Format24bppRgb || detailTexture[i].Width != 16 || detailTexture[i].Height != 16) using(Bitmap origBitmap = detailTexture[i]) detailTexture[i] = Util.ResizeImageSolid(origBitmap, 16, 16); + //detailTexture[i].Save("terr" + i.ToString() + ".png"); + // Save the decoded and resized texture to the cache byte[] data; - using(System.IO.MemoryStream stream = new System.IO.MemoryStream()) + using(MemoryStream stream = new MemoryStream()) { detailTexture[i].Save(stream, ImageFormat.Png); data = stream.ToArray(); @@ -235,9 +240,9 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap else { // Fill in any missing textures with a solid color - for(int i = 0; i < 4; i++) + for (int i = 0; i < 4; i++) { - if(detailTexture[i] == null) + if (detailTexture[i] == null) { m_log.DebugFormat("{0} Missing terrain texture for layer {1}. Filling with solid default color", LogHeader, i); diff --git a/OpenSim/Services/Connectors/MapImage/MapImageServicesConnector.cs b/OpenSim/Services/Connectors/MapImage/MapImageServicesConnector.cs index f61679e988..1079e101dd 100644 --- a/OpenSim/Services/Connectors/MapImage/MapImageServicesConnector.cs +++ b/OpenSim/Services/Connectors/MapImage/MapImageServicesConnector.cs @@ -224,7 +224,7 @@ namespace OpenSim.Services.Connectors { // This just dumps a warning for any operation that takes more than 100 ms int tickdiff = Util.EnvironmentTickCountSubtract(tickstart); - m_log.DebugFormat("[MAP IMAGE CONNECTOR]: AddMapTile time {0}ms", tickdiff); + m_log.DebugFormat("[MAP IMAGE CONNECTOR]: AddMapTile {1} Bytes in {0}ms", tickdiff, jpgData.Length); } return false; } diff --git a/OpenSim/Services/MapImageService/MapImageService.cs b/OpenSim/Services/MapImageService/MapImageService.cs index 28ece6d0c3..dc10168781 100644 --- a/OpenSim/Services/MapImageService/MapImageService.cs +++ b/OpenSim/Services/MapImageService/MapImageService.cs @@ -29,31 +29,25 @@ * https://github.com/openmetaversefoundation/simiangrid/ */ +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Services.Interfaces; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; -using System.Net; using System.Reflection; using System.Threading; -using Nini.Config; -using log4net; -using OpenMetaverse; - -using OpenSim.Framework; -using OpenSim.Framework.Console; -using OpenSim.Services.Interfaces; - namespace OpenSim.Services.MapImageService { public class MapImageService : IMapImageService { - private static readonly ILog m_log = - LogManager.GetLogger( - MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType); #pragma warning disable 414 private string LogHeader = "[MAP IMAGE SERVICE]"; #pragma warning restore 414 @@ -67,43 +61,31 @@ namespace OpenSim.Services.MapImageService private static object m_Sync = new object(); private static bool m_Initialized = false; - private static string m_WaterTileFile = string.Empty; - private static Color m_Watercolor = Color.FromArgb(29, 71, 95); + private static Color m_Watercolor = Color.FromArgb(29, 72, 96); private static Bitmap m_WaterBitmap = null; - private static byte[] m_WaterBytes = null; + private static byte[] m_WaterJPEGBytes = null; public MapImageService(IConfigSource config) { - if (!m_Initialized) + lock (m_Sync) { - m_Initialized = true; - m_log.Debug("[MAP IMAGE SERVICE]: Starting MapImage service"); - - IConfig serviceConfig = config.Configs["MapImageService"]; - if (serviceConfig != null) + if (!m_Initialized) { - m_TilesStoragePath = serviceConfig.GetString("TilesStoragePath", m_TilesStoragePath); - if (!Directory.Exists(m_TilesStoragePath)) - Directory.CreateDirectory(m_TilesStoragePath); + m_Initialized = true; + m_log.Debug("[MAP IMAGE SERVICE]: Starting MapImage service"); - - m_WaterTileFile = Path.Combine(m_TilesStoragePath, "water.jpg"); - if (!File.Exists(m_WaterTileFile)) + IConfig serviceConfig = config.Configs["MapImageService"]; + if (serviceConfig is not null) { - Bitmap waterTile = new Bitmap(IMAGE_WIDTH, IMAGE_WIDTH); - FillImage(waterTile, m_Watercolor); - waterTile.Save(m_WaterTileFile, ImageFormat.Jpeg); - m_WaterBitmap = waterTile; - } - - if (File.Exists(m_WaterTileFile)) - { - m_WaterBitmap = new Bitmap(m_WaterTileFile); + m_TilesStoragePath = serviceConfig.GetString("TilesStoragePath", m_TilesStoragePath); + //memory cache JPEG tile with just water. + m_WaterBitmap = new Bitmap(IMAGE_WIDTH, IMAGE_WIDTH, PixelFormat.Format24bppRgb); + FillImage(m_WaterBitmap, m_Watercolor); using (MemoryStream ms = new MemoryStream()) { - m_WaterBitmap.Save(ms,ImageFormat.Jpeg); + m_WaterBitmap.Save(ms, ImageFormat.Jpeg); ms.Seek(0, SeekOrigin.Begin); - m_WaterBytes = ms.ToArray(); + m_WaterJPEGBytes = ms.ToArray(); } } } @@ -115,14 +97,13 @@ namespace OpenSim.Services.MapImageService public bool AddMapTile(int x, int y, byte[] imageData, UUID scopeID, out string reason) { reason = string.Empty; - string fileName = GetFileName(1, x, y, scopeID); - + ReadOnlySpan path = GetFolder(scopeID); + string fileName = GetFileName(1, x, y, path); lock (m_Sync) { try { - using (FileStream f = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.Write)) - f.Write(imageData, 0, imageData.Length); + File.WriteAllBytes(fileName, imageData); } catch (Exception e) { @@ -132,7 +113,7 @@ namespace OpenSim.Services.MapImageService } } - return UpdateMultiResolutionFiles(x, y, scopeID, out reason); + return UpdateMultiResolutionFiles(x, y, scopeID); } public bool RemoveMapTile(int x, int y, UUID scopeID, out string reason) @@ -148,43 +129,41 @@ namespace OpenSim.Services.MapImageService } catch (Exception e) { - m_log.WarnFormat("[MAP IMAGE SERVICE]: Unable to save delete file {0}: {1}", fileName, e); + m_log.Warn($"[MAP IMAGE SERVICE]: Unable to save delete file {fileName}: {e.Message}"); reason = e.Message; return false; } } - return UpdateMultiResolutionFiles(x, y, scopeID, out reason); + return UpdateMultiResolutionFiles(x, y, scopeID); } // When large varregions start up, they can send piles of new map tiles. This causes // this multi-resolution routine to be called a zillion times an causes much CPU // time to be spent creating multi-resolution tiles that will be replaced when // the next maptile arrives. - private class mapToMultiRez + private readonly struct MapToMultiRez { - public int xx; - public int yy; - public UUID scopeID; + public readonly int x; + public readonly int y; + public readonly UUID scopeID; public mapToMultiRez(int pX, int pY, UUID pscopeID) { - xx = pX; - yy = pY; + x = pX; + y = pY; scopeID = pscopeID; } }; - private Queue multiRezToBuild = new Queue(); - private bool UpdateMultiResolutionFiles(int x, int y, UUID scopeID, out string reason) + private readonly Queue m_MultiRezToBuild = new Queue(); + + private bool UpdateMultiResolutionFiles(int x, int y, UUID scopeID) { - reason = String.Empty; - - lock (multiRezToBuild) + lock (m_MultiRezToBuild) { // m_log.DebugFormat("{0} UpdateMultiResolutionFilesAsync: scheduling update for <{1},{2}>", LogHeader, x, y); - multiRezToBuild.Enqueue(new mapToMultiRez(x, y, scopeID)); - if (multiRezToBuild.Count == 1) - Util.FireAndForget( - DoUpdateMultiResolutionFilesAsync); + m_MultiRezToBuild.Enqueue(new MapToMultiRez(x, y, scopeID)); + if (m_MultiRezToBuild.Count == 1) + Util.FireAndForget(DoUpdateMultiResolutionFilesAsync); } return true; @@ -195,44 +174,26 @@ namespace OpenSim.Services.MapImageService // let acumulate large region tiles Thread.Sleep(60 * 1000); // large regions take time to upload tiles - while (multiRezToBuild.Count > 0) + while (true) { - mapToMultiRez toMultiRez = null; - lock (multiRezToBuild) + MapToMultiRez toMultiRez; + lock (m_MultiRezToBuild) { - if (multiRezToBuild.Count > 0) - toMultiRez = multiRezToBuild.Dequeue(); + if(!m_MultiRezToBuild.TryDequeue(out toMultiRez)) + return; } - if (toMultiRez != null) + + ReadOnlySpan path = GetFolder(toMultiRez.scopeID); + for (int zoomLevel = 2; zoomLevel <= ZOOM_LEVELS; zoomLevel++) { - int x = toMultiRez.xx; - int y = toMultiRez.yy; - UUID scopeID = toMultiRez.scopeID; - // m_log.DebugFormat("{0} DoUpdateMultiResolutionFilesAsync: doing build for <{1},{2}>", LogHeader, x, y); - - int width = 1; - - // Stitch seven more aggregate tiles together - for (uint zoomLevel = 2; zoomLevel <= ZOOM_LEVELS; zoomLevel++) + if (!CreateTile(zoomLevel, toMultiRez.x, toMultiRez.y, path)) { - // Calculate the width (in full resolution tiles) and bottom-left - // corner of the current zoom level - width *= 2; - int x1 = x - (x % width); - int y1 = y - (y % width); - - lock (m_Sync) // must lock the reading and writing of the maptile files - { - if (!CreateTile(zoomLevel, x1, y1, scopeID)) - { - m_log.WarnFormat("[MAP IMAGE SERVICE]: Unable to create tile for {0},{1} at zoom level {1}", x, y, zoomLevel); - return; - } - } + m_log.WarnFormat("[MAP IMAGE SERVICE]: Unable to create tile for {0},{1} at zoom level {1}", toMultiRez.x, toMultiRez.y, zoomLevel); + return; } } + Thread.Sleep(50); // slow things a bit } - return; } public byte[] GetMapTile(string fileName, UUID scopeID, out string format) @@ -242,44 +203,61 @@ namespace OpenSim.Services.MapImageService fullName = Path.Combine(fullName, fileName); try { - format = Path.GetExtension(fileName).ToLower(); - //m_log.DebugFormat("[MAP IMAGE SERVICE]: Found file {0}, extension {1}", fileName, format); - return File.ReadAllBytes(fullName); + lock (m_Sync) + { + format = Path.GetExtension(fileName).ToLower(); + //m_log.DebugFormat("[MAP IMAGE SERVICE]: Found file {0}, extension {1}", fileName, format); + return File.ReadAllBytes(fullName); + } } catch { format = ".jpg"; - if (m_WaterBytes != null) - return (byte[])m_WaterBytes.Clone(); - else - { - //m_log.DebugFormat("[MAP IMAGE SERVICE]: unable to get file {0}", fileName); - return Array.Empty(); - } + return m_WaterJPEGBytes is null ? Array.Empty() : m_WaterJPEGBytes; } } #endregion - - private string GetFileName(uint zoomLevel, int x, int y, UUID scopeID) + private string GetFileName(int zoomLevel, int x, int y, UUID scopeID) + { + string path = Path.Combine(m_TilesStoragePath, scopeID.ToString()); + return Path.Combine(path, string.Format("map-{0}-{1}-{2}-objects.{3}", zoomLevel, x, y, "jpg")); + } + private string GetFileName(int zoomLevel, int x, int y, ReadOnlySpan path) + { + return Path.Combine(path.ToString(), string.Format("map-{0}-{1}-{2}-objects.{3}", zoomLevel, x, y, "jpg")); + } + + private string GetFolder(UUID scopeID) { - string extension = "jpg"; string path = Path.Combine(m_TilesStoragePath, scopeID.ToString()); Directory.CreateDirectory(path); - return Path.Combine(path, string.Format("map-{0}-{1}-{2}-objects.{3}", zoomLevel, x, y, extension)); + return path; } private Bitmap GetInputTileImage(string fileName) { try { - if (File.Exists(fileName)) - return new Bitmap(fileName); + lock(m_Sync) + { + if (File.Exists(fileName)) + { + Bitmap bm = new Bitmap(fileName); + if (bm.Width != IMAGE_WIDTH || bm.Height != IMAGE_WIDTH || bm.PixelFormat != PixelFormat.Format24bppRgb) + { + m_log.Error($"[MAP IMAGE SERVICE]: invalid map tile {fileName}: {bm.Width} , {bm.Height}, {bm.PixelFormat}"); + bm.Dispose(); + return null; + } + return bm; + } + } } catch (Exception e) { - m_log.WarnFormat("[MAP IMAGE SERVICE]: Unable to read image data from {0}: {1}", fileName, e); + m_log.Warn($"[MAP IMAGE SERVICE]: Unable to read image data from {fileName}: {e.Message}"); } return null; @@ -289,15 +267,9 @@ namespace OpenSim.Services.MapImageService { try { - if (File.Exists(fileName)) - return new Bitmap(fileName); - - else + lock(m_Sync) { - // Create a new output tile with a transparent background - Bitmap bm = new Bitmap(IMAGE_WIDTH, IMAGE_WIDTH, PixelFormat.Format24bppRgb); - //bm.MakeTransparent(); // 24bpp does not have transparency, this would make it 32bpp - return bm; + return File.Exists(fileName) ? new Bitmap(fileName) : new Bitmap(IMAGE_WIDTH, IMAGE_WIDTH, PixelFormat.Format24bppRgb); } } catch (Exception e) @@ -308,67 +280,62 @@ namespace OpenSim.Services.MapImageService return null; } - private bool CreateTile(uint zoomLevel, int x, int y, UUID scopeID) + private bool CreateTile(int zoomLevel, int inx, int iny, ReadOnlySpan path) { - // m_log.DebugFormat("[MAP IMAGE SERVICE]: Create tile for {0} {1}, zoom {2}", x, y, zoomLevel); - zoomLevel--; - int thisWidth = 1 << ((int)zoomLevel); - int prevWidth = thisWidth >> 1; + int previusLevel = zoomLevel - 1; + int prevStep = 1 << previusLevel - 1; - // Convert x and y to the bottom left tile for this zoom level - int xIn = x - (x % prevWidth); - int yIn = y - (y % prevWidth); + int mask = unchecked((int)0xffffffff) << previusLevel; - // Convert x and y to the bottom left tile for the next zoom level - int xOut = x - (x % thisWidth); - int yOut = y - (y % thisWidth); - - // Try to open the four input tiles from the previous zoom level - Bitmap inputBL = GetInputTileImage(GetFileName(zoomLevel, xIn, yIn, scopeID)); - Bitmap inputBR = GetInputTileImage(GetFileName(zoomLevel, xIn + prevWidth, yIn, scopeID)); - Bitmap inputTL = GetInputTileImage(GetFileName(zoomLevel, xIn, yIn + prevWidth, scopeID)); - Bitmap inputTR = GetInputTileImage(GetFileName(zoomLevel, xIn + prevWidth, yIn + prevWidth, scopeID)); - - // Open the output tile (current zoom level) - string outputFile = GetFileName(++zoomLevel, xOut, yOut, scopeID); + // Convert x and y to the bottom left of current tile + int x = inx & mask; + int y = iny & mask; int ntiles = 0; Bitmap output = (Bitmap)m_WaterBitmap.Clone(); - if (inputBL != null) + Bitmap input = GetInputTileImage(GetFileName(previusLevel, x, y, path)); + if (input is not null) { - ImageCopyResampled(output, inputBL, 0, HALF_WIDTH, 0, 0); - inputBL.Dispose(); + ImageCopyResampled(output, input, 0, HALF_WIDTH); + input.Dispose(); ntiles++; } - if (inputBR != null) + input = GetInputTileImage(GetFileName(previusLevel, x + prevStep, y, path)); + if (input is not null) { - ImageCopyResampled(output, inputBR, HALF_WIDTH, HALF_WIDTH, 0, 0); - inputBR.Dispose(); + ImageCopyResampled(output, input, HALF_WIDTH, HALF_WIDTH); + input.Dispose(); ntiles++; } - if (inputTL != null) + input = GetInputTileImage(GetFileName(previusLevel, x, y + prevStep, path)); + if (input is not null) { - ImageCopyResampled(output, inputTL, 0, 0, 0, 0); - inputTL.Dispose(); + ImageCopyResampled(output, input, 0, 0); + input.Dispose(); ntiles++; } - if (inputTR != null) + input = GetInputTileImage(GetFileName(previusLevel, x + prevStep, y + prevStep, path)); + if (input is not null) { - ImageCopyResampled(output, inputTR, HALF_WIDTH, 0, 0, 0); - inputTR.Dispose(); + ImageCopyResampled(output, input, HALF_WIDTH, 0); + input.Dispose(); ntiles++; } + string outputFile = GetFileName(zoomLevel, x, y, path); try { - File.Delete(outputFile); - if (ntiles > 0) - output.Save(outputFile, ImageFormat.Jpeg); + lock(m_Sync) + { + File.Delete(outputFile); + if (ntiles > 0) + output.Save(outputFile, ImageFormat.Jpeg); + } } catch (Exception e) { - m_log.WarnFormat("[MAP IMAGE SERVICE]: Oops on saving {0} {1}", outputFile, e); + m_log.Warn($"[MAP IMAGE SERVICE]: Oops on saving {outputFile} {e.Message}"); } output.Dispose(); @@ -379,22 +346,75 @@ namespace OpenSim.Services.MapImageService private void FillImage(Bitmap bm, Color c) { - for (int x = 0; x < bm.Width; x++) - for (int y = 0; y < bm.Height; y++) - bm.SetPixel(x, y, c); - } - - private void ImageCopyResampled(Bitmap output, Bitmap input, int destX, int destY, int srcX, int srcY) - { - int resamplingRateX = 2; // (input.Width - srcX) / (output.Width - destX); - int resamplingRateY = 2; // (input.Height - srcY) / (output.Height - destY); - - for (int x = destX; x < destX + HALF_WIDTH; x++) - for (int y = destY; y < destY + HALF_WIDTH; y++) + BitmapData srcData = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); + byte r = c.R; + byte g = c.G; + byte b = c.B; + unsafe + { + byte* ptr = (byte*)srcData.Scan0; + for(int y = 0; y < bm.Height; y++) { - Color p = input.GetPixel(srcX + (x - destX) * resamplingRateX, srcY + (y - destY) * resamplingRateY); - output.SetPixel(x, y, p); + for(int x = 0; x < bm.Width; x++) + { + *ptr++ = b; + *ptr++ = g; + *ptr++ = r; + } } + } + bm.UnlockBits(srcData); + } + + private void ImageCopyResampled(Bitmap output, Bitmap input, int destX, int destY) + { + try + { + BitmapData srcData = input.LockBits(new Rectangle(0, 0, input.Width, input.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); + BitmapData dstData = output.LockBits(new Rectangle(0, 0, output.Width, output.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); + unsafe + { + byte* srcPointer = (byte*)srcData.Scan0; + byte* srcPointer2 = (byte*)srcData.Scan0 + srcData.Stride; + byte* dstPointer = (byte*)dstData.Scan0 + destY * dstData.Stride + 3 * destX; + for (int y = 0; y < HALF_WIDTH; y++) + { + byte* dxptr = dstPointer; + for (int i = 0; i < HALF_WIDTH; i++) + { + // Blue + int t = srcPointer[0] + srcPointer[3] + srcPointer2[0] + srcPointer2[3]; + dxptr[0] = (byte)(t >> 2); + // Green + t = srcPointer[1] + srcPointer[4] + srcPointer2[1] + srcPointer2[4]; + dxptr[1] = (byte)(t >> 2); + + // Red + t = srcPointer[2] + srcPointer[5] + srcPointer2[2] + srcPointer2[5]; + dxptr[2] = (byte)(t >> 2); + + /* + dxptr[0] = srcPointer[0]; // Blue + dxptr[1] = srcPointer[1]; // Green + dxptr[2] = srcPointer[2]; // Red + */ + srcPointer += 6; // skip one point + srcPointer2 += 6; // skip one point + dxptr += 3; + } + srcPointer += srcData.Stride; // skip extra line + srcPointer2 += srcData.Stride; + dstPointer += dstData.Stride; + } + } + + output.UnlockBits(dstData); + input.UnlockBits(srcData); + } + catch (InvalidOperationException e) + { + + } } #endregion