private void CopyAllocatedItemsToAtlas(List <TextureTools.AtlasItem> items, string atlasChain, int atlasId, Size size) { var atlasPath = AssetCooker.GetAtlasPath(atlasChain, atlasId); var atlasPixels = new Color4[size.Width * size.Height]; foreach (var item in items.Where(i => i.Allocated)) { var atlasRect = item.AtlasRect; using (var bitmap = TextureTools.OpenAtlasItemBitmapAndRescaleIfNeeded(AssetCooker.Platform, item)) { CopyPixels(bitmap, atlasPixels, atlasRect.A.X, atlasRect.A.Y, size.Width, size.Height); } var atlasPart = new TextureAtlasElement.Params { AtlasRect = atlasRect, AtlasPath = Path.ChangeExtension(atlasPath, null) }; var srcPath = Path.ChangeExtension(item.Path, item.SourceExtension); InternalPersistence.Instance.WriteObjectToBundle(AssetCooker.OutputBundle, item.Path, atlasPart, Persistence.Format.Binary, item.SourceExtension, AssetCooker.InputBundle.GetFileLastWriteTime(srcPath), AssetAttributes.None, item.CookingRules.SHA1); // Delete non-atlased texture since now its useless var texturePath = Path.ChangeExtension(item.Path, AssetCooker.GetPlatformTextureExtension()); if (AssetCooker.OutputBundle.FileExists(texturePath)) { AssetCooker.DeleteFileFromBundle(texturePath); } UserInterface.Instance.IncreaseProgressBar(); } Console.WriteLine("+ " + atlasPath); var firstItem = items.First(i => i.Allocated); using (var atlas = new Bitmap(atlasPixels, size.Width, size.Height)) { AssetCooker.ImportTexture(atlasPath, atlas, firstItem.CookingRules, AssetCooker.InputBundle.GetFileLastWriteTime(atlasPath), CookingRulesSHA1: null); } }
private bool Converter(string srcPath, string dstPath) { var rules = AssetCooker.CookingRulesMap[Path.ChangeExtension(dstPath, originalTextureExtension)]; if (rules.TextureAtlas != null) { // No need to cache this texture since it is a part of texture atlas. return(false); } using (var stream = File.OpenRead(srcPath)) { var bitmap = new Bitmap(stream); if (TextureTools.ShouldDownscale(AssetCooker.Platform, bitmap, rules)) { var scaledBitmap = TextureTools.DownscaleTexture(AssetCooker.Platform, bitmap, srcPath, rules); bitmap.Dispose(); bitmap = scaledBitmap; } AssetCooker.ImportTexture(dstPath, bitmap, rules, File.GetLastWriteTime(srcPath), rules.SHA1); bitmap.Dispose(); } return(true); }
private IEnumerable <Size> EnumerateAtlasSizes(bool squareAtlas, int minSize) { if (squareAtlas) { for (var i = minSize; i <= TextureTools.GetMaxAtlasSize().Width; i *= 2) { yield return(new Size(i, i)); } } else { for (var i = minSize; i <= TextureTools.GetMaxAtlasSize().Width / 2; i *= 2) { yield return(new Size(i, i)); yield return(new Size(i * 2, i)); yield return(new Size(i, i * 2)); } yield return(TextureTools.GetMaxAtlasSize()); } }
public void ImportTexture(string path, Bitmap texture, ICookingRules rules, DateTime time, byte[] CookingRulesSHA1) { var textureParamsPath = Path.ChangeExtension(path, ".texture"); var textureParams = new TextureParams { WrapMode = rules.WrapMode, MinFilter = rules.MinFilter, MagFilter = rules.MagFilter, }; if (!AreTextureParamsDefault(rules)) { TextureTools.UpscaleTextureIfNeeded(ref texture, rules, false); var isNeedToRewriteTexParams = true; if (AssetBundle.FileExists(textureParamsPath)) { var oldTexParams = InternalPersistence.Instance.ReadObject <TextureParams>(textureParamsPath, AssetBundle.OpenFile(textureParamsPath)); isNeedToRewriteTexParams = !oldTexParams.Equals(textureParams); } if (isNeedToRewriteTexParams) { InternalPersistence.Instance.WriteObjectToBundle(AssetBundle, textureParamsPath, textureParams, Persistence.Format.Binary, ".texture", File.GetLastWriteTime(textureParamsPath), AssetAttributes.None, null); } } else { if (AssetBundle.FileExists(textureParamsPath)) { DeleteFileFromBundle(textureParamsPath); } } if (rules.GenerateOpacityMask) { var maskPath = Path.ChangeExtension(path, ".mask"); OpacityMaskCreator.CreateMask(AssetBundle, texture, maskPath); } var attributes = AssetAttributes.ZippedDeflate; if (!TextureConverterUtils.IsPowerOf2(texture.Width) || !TextureConverterUtils.IsPowerOf2(texture.Height)) { attributes |= AssetAttributes.NonPowerOf2Texture; } switch (Target.Platform) { case TargetPlatform.Android: //case TargetPlatform.iOS: var f = rules.PVRFormat; if (f == PVRFormat.ARGB8 || f == PVRFormat.RGB565 || f == PVRFormat.RGBA4) { TextureConverter.RunPVRTexTool(texture, AssetBundle, path, attributes, rules.MipMaps, rules.HighQualityCompression, rules.PVRFormat, CookingRulesSHA1, time); } else { TextureConverter.RunEtcTool(texture, AssetBundle, path, attributes, rules.MipMaps, rules.HighQualityCompression, CookingRulesSHA1, time); } break; case TargetPlatform.iOS: TextureConverter.RunPVRTexTool(texture, AssetBundle, path, attributes, rules.MipMaps, rules.HighQualityCompression, rules.PVRFormat, CookingRulesSHA1, time); break; case TargetPlatform.Win: case TargetPlatform.Mac: TextureConverter.RunNVCompress(texture, AssetBundle, path, attributes, rules.DDSFormat, rules.MipMaps, CookingRulesSHA1, time); break; default: throw new Lime.Exception(); } }
private int PackItemsToAtlas(string atlasChain, List <TextureTools.AtlasItem> items, AtlasOptimization atlasOptimization, int initialAtlasId, bool squareAtlas) { // Sort images in descending size order items.Sort((x, y) => { var a = Math.Max(x.BitmapInfo.Width, x.BitmapInfo.Height); var b = Math.Max(y.BitmapInfo.Width, y.BitmapInfo.Height); return(b - a); }); var atlasId = initialAtlasId; while (items.Count > 0) { if (atlasId >= AssetCooker.MaxAtlasChainLength) { throw new Lime.Exception("Too many textures in the atlas chain {0}", atlasChain); } var bestSize = new Size(0, 0); double bestPackRate = 0; int minItemsLeft = Int32.MaxValue; // TODO: Fix for non-square atlases var maxTextureSize = items.Max(item => Math.Max(item.BitmapInfo.Height, item.BitmapInfo.Width)); var minAtlasSize = Math.Max(64, TextureTools.CalcUpperPowerOfTwo(maxTextureSize)); foreach (var size in EnumerateAtlasSizes(squareAtlas: squareAtlas, minSize: minAtlasSize)) { double packRate; var prevAllocated = items.Where(i => i.Allocated).ToList(); PackItemsToAtlas(items, size, out packRate); switch (atlasOptimization) { case AtlasOptimization.Memory: if (packRate * 0.95f > bestPackRate) { bestPackRate = packRate; bestSize = size; } break; case AtlasOptimization.DrawCalls: { var notAllocatedCount = items.Count(item => !item.Allocated); if (notAllocatedCount < minItemsLeft) { minItemsLeft = notAllocatedCount; bestSize = size; } else if (notAllocatedCount == minItemsLeft) { if (items.Where(i => i.Allocated).SequenceEqual(prevAllocated)) { continue; } else { minItemsLeft = notAllocatedCount; bestSize = size; } } if (notAllocatedCount == 0) { goto end; } break; } } } end: if (atlasOptimization == AtlasOptimization.Memory && bestPackRate == 0) { throw new Lime.Exception("Failed to create atlas '{0}'", atlasChain); } PackItemsToAtlas(items, bestSize, out bestPackRate); CopyAllocatedItemsToAtlas(items, atlasChain, atlasId, bestSize); items.RemoveAll(x => x.Allocated); atlasId++; } return(atlasId); }
private void BuildAtlasChain(string atlasChain) { for (var i = 0; i < AssetCooker.MaxAtlasChainLength; i++) { var atlasPath = AssetCooker.GetAtlasPath(atlasChain, i); if (AssetCooker.OutputBundle.FileExists(atlasPath)) { AssetCooker.DeleteFileFromBundle(atlasPath); } else { break; } } var pluginItems = new Dictionary <string, List <TextureTools.AtlasItem> >(); var items = new Dictionary <AtlasOptimization, List <TextureTools.AtlasItem> >(); items[AtlasOptimization.Memory] = new List <TextureTools.AtlasItem>(); items[AtlasOptimization.DrawCalls] = new List <TextureTools.AtlasItem>(); foreach (var fileInfo in AssetBundle.Current.EnumerateFileInfos(null, textureExtension)) { var cookingRules = AssetCooker.CookingRulesMap[fileInfo.Path]; if (cookingRules.TextureAtlas == atlasChain) { var item = new TextureTools.AtlasItem { Path = Path.ChangeExtension(fileInfo.Path, atlasPartExtension), CookingRules = cookingRules, SourceExtension = Path.GetExtension(fileInfo.Path) }; var bitmapInfo = TextureTools.BitmapInfo.FromFile(AssetCooker.InputBundle, fileInfo.Path); if (bitmapInfo == null) { using (var bitmap = TextureTools.OpenAtlasItemBitmapAndRescaleIfNeeded(AssetCooker.Platform, item)) { item.BitmapInfo = TextureTools.BitmapInfo.FromBitmap(bitmap); } } else { var srcTexturePath = AssetPath.Combine(The.Workspace.AssetsDirectory, Path.ChangeExtension(item.Path, item.SourceExtension)); if (TextureTools.ShouldDownscale(AssetCooker.Platform, bitmapInfo, item.CookingRules)) { TextureTools.DownscaleTextureInfo(AssetCooker.Platform, bitmapInfo, srcTexturePath, item.CookingRules); } // Ensure that no image exceeded maxAtlasSize limit TextureTools.DownscaleTextureToFitAtlas(bitmapInfo, srcTexturePath); item.BitmapInfo = bitmapInfo; } var k = cookingRules.AtlasPacker; if (!string.IsNullOrEmpty(k) && k != "Default") { List <TextureTools.AtlasItem> l; if (!pluginItems.TryGetValue(k, out l)) { pluginItems.Add(k, l = new List <TextureTools.AtlasItem>()); } l.Add(item); } else { items[cookingRules.AtlasOptimization].Add(item); } } } var initialAtlasId = 0; foreach (var kv in items) { if (kv.Value.Any()) { if (AssetCooker.Platform == TargetPlatform.iOS) { Predicate <PVRFormat> isRequireSquare = (format) => { return (format == PVRFormat.PVRTC2 || format == PVRFormat.PVRTC4 || format == PVRFormat.PVRTC4_Forced); }; var square = kv.Value.Where(item => isRequireSquare(item.CookingRules.PVRFormat)).ToList(); var nonSquare = kv.Value.Where(item => !isRequireSquare(item.CookingRules.PVRFormat)).ToList(); initialAtlasId = PackItemsToAtlas(atlasChain, square, kv.Key, initialAtlasId, true); initialAtlasId = PackItemsToAtlas(atlasChain, nonSquare, kv.Key, initialAtlasId, false); } else { initialAtlasId = PackItemsToAtlas(atlasChain, kv.Value, kv.Key, initialAtlasId, false); } } } var packers = PluginLoader.CurrentPlugin.AtlasPackers.ToDictionary(i => i.Metadata.Id, i => i.Value); foreach (var kv in pluginItems) { if (!packers.ContainsKey(kv.Key)) { throw new InvalidOperationException($"Packer {kv.Key} not found"); } initialAtlasId = packers[kv.Key](atlasChain, kv.Value, initialAtlasId); } }