public static void RunEtcTool(Bitmap bitmap, AssetBundle bundle, string path, AssetAttributes attributes, bool mipMaps, bool highQualityCompression, byte[] CookingRulesSHA1) { var hasAlpha = bitmap.HasAlpha; var bledBitmap = hasAlpha ? TextureConverterUtils.BleedAlpha(bitmap) : null; var ktxPath = Toolbox.GetTempFilePathWithExtension(".ktx"); var pngPath = Path.ChangeExtension(ktxPath, ".png"); try { (bledBitmap ?? bitmap).SaveTo(pngPath); var etcTool = GetToolPath("EtcTool"); var args = $"{pngPath} -format " + (hasAlpha ? "RGBA8" : "ETC1") + " -jobs 8 " + " -effort " + (highQualityCompression ? "60" : "40") + (mipMaps ? " -mipmaps 4" : "") + $" -output {ktxPath}"; if (Process.Start(etcTool, args) != 0) { throw new Lime.Exception($"ETCTool error\nCommand line: {etcTool} {args}\""); } bundle.ImportFile(ktxPath, path, 0, "", attributes, CookingRulesSHA1); } finally { bledBitmap?.Dispose(); DeletePossibleLockedFile(pngPath); DeletePossibleLockedFile(ktxPath); } }
public static void RunEtcTool(Bitmap bitmap, AssetBundle bundle, string path, AssetAttributes attributes, bool mipMaps, bool highQualityCompression, byte[] CookingRulesSHA1, DateTime time) { var hasAlpha = bitmap.HasAlpha; var bledBitmap = hasAlpha ? TextureConverterUtils.BleedAlpha(bitmap) : null; var args = "{0} -format " + (hasAlpha ? "RGBA8" : "RGB8") + " -jobs {1} " + " -effort " + (highQualityCompression ? "60" : "40") + (mipMaps ? " -mipmaps 4" : "") + " -output {2}"; var hashString = GetTextureHashString(bledBitmap ?? bitmap, ".etc", args); var cachePath = AssetCache.Instance.Load(hashString); if (cachePath != null) { bundle.ImportFile(cachePath, path, 0, "", attributes, time, CookingRulesSHA1); return; } var ktxPath = Toolbox.GetTempFilePathWithExtension(".ktx"); var pngPath = Path.ChangeExtension(ktxPath, ".png"); var etcTool = GetToolPath("EtcTool"); try { (bledBitmap ?? bitmap).SaveTo(pngPath); if (Process.Start(etcTool, args.Format(pngPath, 8, ktxPath)) != 0) { throw new Lime.Exception($"ETCTool error\nCommand line: {etcTool} {args}\""); } bundle.ImportFile(ktxPath, path, 0, "", attributes, time, CookingRulesSHA1); AssetCache.Instance.Save(ktxPath, hashString); } finally { bledBitmap?.Dispose(); DeletePossibleLockedFile(pngPath); DeletePossibleLockedFile(ktxPath); } }
public static void ImportTexture(string path, Bitmap texture, ICookingRules rules, byte[] CookingRulesSHA1) { var textureParamsPath = Path.ChangeExtension(path, ".texture"); if (!AreTextureParamsDefault(rules)) { UpscaleTextureIfNeeded(ref texture, rules, false); var textureParams = new TextureParams { WrapMode = rules.WrapMode, MinFilter = rules.MinFilter, MagFilter = rules.MagFilter, }; Serialization.WriteObjectToBundle(AssetBundle, textureParamsPath, textureParams, Serialization.Format.Binary, ".texture", AssetAttributes.None, null); } else { if (AssetBundle.FileExists(textureParamsPath)) { DeleteFileFromBundle(textureParamsPath); } } if (ShouldGenerateOpacityMasks()) { 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 (Platform) { case TargetPlatform.Android: 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); } else { TextureConverter.RunEtcTool(texture, AssetBundle, path, attributes, rules.MipMaps, rules.HighQualityCompression, CookingRulesSHA1); } break; case TargetPlatform.iOS: TextureConverter.RunPVRTexTool(texture, AssetBundle, path, attributes, rules.MipMaps, rules.HighQualityCompression, rules.PVRFormat, CookingRulesSHA1); break; case TargetPlatform.Win: case TargetPlatform.Mac: TextureConverter.RunNVCompress(texture, AssetBundle, path, attributes, rules.DDSFormat, rules.MipMaps, CookingRulesSHA1); break; default: throw new Lime.Exception(); } }
public static void RunNVCompress(Bitmap bitmap, AssetBundle bundle, string path, AssetAttributes attributes, DDSFormat format, bool mipMaps, byte[] CookingRulesSHA1, DateTime time) { bool compressed = format == DDSFormat.DXTi; Bitmap bledBitmap = null; if (bitmap.HasAlpha) { bledBitmap = TextureConverterUtils.BleedAlpha(bitmap); } string mipsFlag = mipMaps ? string.Empty : "-nomips"; string compressionMethod; if (compressed) { compressionMethod = bitmap.HasAlpha ? "-bc3" : "-bc1"; } else { #if WIN compressionMethod = "-rgb"; #else compressionMethod = "-rgb -rgbfmt bgra8"; #endif } var nvcompress = GetToolPath("nvcompress"); string args = "{0} {1}".Format(mipsFlag, compressionMethod); var hashString = GetTextureHashString(bledBitmap ?? bitmap, ".dds", args); var cachePath = AssetCache.Instance.Load(hashString); if (cachePath != null) { bundle.ImportFile(cachePath, path, 0, "", attributes, time, CookingRulesSHA1); return; } var ddsPath = Toolbox.GetTempFilePathWithExtension(".dds"); var tgaPath = Path.ChangeExtension(ddsPath, ".tga"); var srcPath = Path.Combine(Directory.GetCurrentDirectory(), tgaPath); var dstPath = Path.Combine(Directory.GetCurrentDirectory(), ddsPath); try { TextureConverterUtils.SaveToTGA(bledBitmap ?? bitmap, tgaPath, swapRedAndBlue: compressed); if (bledBitmap != null && bledBitmap != bitmap) { bledBitmap.Dispose(); } args += $" \"{srcPath}\" \"{dstPath}\""; if (Process.Start(nvcompress, args.Format(mipsFlag, compressionMethod, srcPath, dstPath), options: Process.Options.RedirectErrors) != 0) { throw new Lime.Exception($"NVCompress error\nCommand line: {nvcompress} {args}\""); } bundle.ImportFile(ddsPath, path, 0, "", attributes, time, CookingRulesSHA1); AssetCache.Instance.Save(ddsPath, hashString); } finally { DeletePossibleLockedFile(ddsPath); DeletePossibleLockedFile(tgaPath); } }
private Size GetTextureSize(string srcPath) { var fontPngFile = Path.ChangeExtension(srcPath, ".png"); Size size; bool hasAlpha; if (!TextureConverterUtils.GetPngFileInfo(fontPngFile, out size.Width, out size.Height, out hasAlpha, isTangerine)) { throw new Lime.Exception("Font doesn't have an appropriate png texture file"); } return(size); }
public static BitmapInfo FromFile(AssetBundle bundle, string path) { if (TextureConverterUtils.GetPngFileInfo(bundle, path, out var width, out var height, out var hasAlpha)) { return(new BitmapInfo { Width = width, Height = height, HasAlpha = hasAlpha }); } Debug.Write($"Failed to read image info {path}"); return(null); }
public static BitmapInfo FromFile(string file) { int width; int height; bool hasAlpha; if (TextureConverterUtils.GetPngFileInfo(file, out width, out height, out hasAlpha, false)) { return(new BitmapInfo() { Width = width, Height = height, HasAlpha = hasAlpha }); } Debug.Write("Failed to read image info {0}", file); return(null); }
public static void RunNVCompress(Bitmap bitmap, AssetBundle bundle, string path, AssetAttributes attributes, DDSFormat format, bool mipMaps, byte[] CookingRulesSHA1) { bool compressed = format == DDSFormat.DXTi; Bitmap bledBitmap = null; if (bitmap.HasAlpha) { bledBitmap = TextureConverterUtils.BleedAlpha(bitmap); } var ddsPath = Toolbox.GetTempFilePathWithExtension(".dds"); var tgaPath = Path.ChangeExtension(ddsPath, ".tga"); try { TextureConverterUtils.SaveToTGA(bledBitmap ?? bitmap, tgaPath, swapRedAndBlue: compressed); if (bledBitmap != null && bledBitmap != bitmap) { bledBitmap.Dispose(); } RunNVCompressHelper(tgaPath, ddsPath, bitmap.HasAlpha, compressed, mipMaps); bundle.ImportFile(ddsPath, path, 0, "", attributes, CookingRulesSHA1); } finally { DeletePossibleLockedFile(ddsPath); DeletePossibleLockedFile(tgaPath); } }
public static void UpscaleTextureIfNeeded(ref Bitmap texture, ICookingRules rules, bool square) { if (rules.WrapMode == TextureWrapMode.Clamp) { return; } if (TextureConverterUtils.IsPowerOf2(texture.Width) && TextureConverterUtils.IsPowerOf2(texture.Height)) { return; } int newWidth = CalcUpperPowerOfTwo(texture.Width); int newHeight = CalcUpperPowerOfTwo(texture.Height); if (square) { newHeight = newWidth = Math.Max(newWidth, newHeight); } var newTexture = texture.Rescale(newWidth, newHeight); texture.Dispose(); texture = newTexture; }
public static void RunPVRTexTool(Bitmap bitmap, AssetBundle bundle, string path, AssetAttributes attributes, bool mipMaps, bool highQualityCompression, PVRFormat pvrFormat, byte[] CookingRulesSHA1) { int width = bitmap.Width; int height = bitmap.Height; bool hasAlpha = bitmap.HasAlpha; int potWidth = TextureConverterUtils.GetNearestPowerOf2(width, 8, 2048); int potHeight = TextureConverterUtils.GetNearestPowerOf2(height, 8, 2048); var args = new StringBuilder(); switch (pvrFormat) { case PVRFormat.PVRTC4: if (!hasAlpha) { args.Append(" -f PVRTC1_2"); } else { args.Append(" -f PVRTC1_4"); } width = height = Math.Max(potWidth, potHeight); break; case PVRFormat.PVRTC4_Forced: args.Append(" -f PVRTC1_4"); width = height = Math.Max(potWidth, potHeight); break; case PVRFormat.PVRTC2: args.Append(" -f PVRTC1_2"); width = height = Math.Max(potWidth, potHeight); break; case PVRFormat.ETC2: args.Append(" -f ETC1 -q etcfast"); break; case PVRFormat.RGB565: if (hasAlpha) { Console.WriteLine("WARNING: texture has alpha channel. " + "Used 'RGBA4444' format instead of 'RGB565'."); args.Append(" -f r4g4b4a4 -dither"); } else { args.Append(" -f r5g6b5"); } break; case PVRFormat.RGBA4: args.Append(" -f r4g4b4a4 -dither"); break; case PVRFormat.ARGB8: args.Append(" -f r8g8b8a8"); break; } if (highQualityCompression && (new [] { PVRFormat.PVRTC2, PVRFormat.PVRTC4, PVRFormat.PVRTC4_Forced }.Contains (pvrFormat))) { args.Append(" -q pvrtcbest"); } var pvrPath = Toolbox.GetTempFilePathWithExtension(".pvr"); var tgaPath = Path.ChangeExtension(pvrPath, ".tga"); try { if (hasAlpha) { bitmap = TextureConverterUtils.BleedAlpha(bitmap); } TextureConverterUtils.SaveToTGA(bitmap, tgaPath, swapRedAndBlue: true); if (mipMaps) { args.Append(" -m"); } args.AppendFormat(" -i \"{0}\" -o \"{1}\" -r {2},{3} -shh", tgaPath, pvrPath, width, height); #if MAC var pvrTexTool = GetToolPath("PVRTexTool"); #else var pvrTexTool = GetToolPath("PVRTexToolCli"); #endif if (Process.Start(pvrTexTool, args.ToString()) != 0) { throw new Lime.Exception($"PVRTextTool error\nCommand line: {pvrTexTool} {args}\""); } bundle.ImportFile(pvrPath, path, 0, "", attributes, CookingRulesSHA1); } finally { DeletePossibleLockedFile(tgaPath); DeletePossibleLockedFile(pvrPath); } }
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(); } }
public static void AnalyzeResourcesAction() { var target = The.UI.GetActiveTarget(); requestedPaths = new List <PathRequestRecord>(); var crossRefReport = new List <Tuple <string, List <string> > >(); var missingResourcesReport = new List <string>(); var suspiciousTexturesReport = new List <string>(); var bundles = new HashSet <string>(); var cookingRulesMap = CookingRulesBuilder.Build(AssetBundle.Current, target); AssetBundle.Current = new PackedAssetBundle(The.Workspace.GetBundlePath(target.Platform, CookingRulesBuilder.MainBundleName)); foreach (var i in cookingRulesMap) { if (i.Key.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) { if (i.Value.TextureAtlas == null && i.Value.PVRFormat != PVRFormat.PVRTC4 && i.Value.PVRFormat != PVRFormat.PVRTC4_Forced) { suspiciousTexturesReport.Add(string.Format("{0}: {1}, atlas: none", i.Key, i.Value.PVRFormat)); } if (i.Value.PVRFormat != PVRFormat.PVRTC4 && i.Value.PVRFormat != PVRFormat.PVRTC4_Forced && i.Value.PVRFormat != PVRFormat.PVRTC2) { int w; int h; bool hasAlpha; TextureConverterUtils.GetPngFileInfo(AssetBundle.Current, i.Key, out w, out h, out hasAlpha); if (w >= 1024 || h >= 1024) { suspiciousTexturesReport.Add(string.Format("{3}: {0}, {1}, {2}, {4}, atlas: {5}", w, h, hasAlpha, i.Key, i.Value.PVRFormat, i.Value.TextureAtlas)); } } } foreach (var bundle in i.Value.Bundles) { if (bundle != CookingRulesBuilder.MainBundleName) { bundles.Add(bundle); } } } var savedAssetBundle = AssetBundle.Current; try { var aggregateBundle = new AggregateAssetBundle( bundles.Select( i => new PackedAssetBundle(The.Workspace.GetBundlePath(target.Platform, i))).ToArray()); AssetBundle.Current = new CustomSetAssetBundle(aggregateBundle, aggregateBundle.EnumerateFileInfos().Where(i => { if (cookingRulesMap.TryGetValue(i.Path, out CookingRules rules)) { if (rules.Ignore) { return(false); } } return(true); })); var usedImages = new HashSet <string>(); var usedSounds = new HashSet <string>(); foreach (var srcPath in AssetBundle.Current.EnumerateFiles(null, ".tan")) { using (var scene = (Frame)Node.CreateFromAssetBundle(srcPath)) { foreach (var j in scene.Descendants) { var checkTexture = new Action <SerializableTexture>((Lime.SerializableTexture t) => { if (t == null) { return; } string texPath; try { texPath = t.SerializationPath; } catch { return; } if (string.IsNullOrEmpty(texPath)) { return; } if (texPath.Length == 2 && texPath[0] == '#') { switch (texPath[1]) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': return; default: suspiciousTexturesReport.Add(string.Format("wrong render target: {0}, {1}", texPath, j.ToString())); return; } } string[] possiblePaths = new string[] { texPath + ".atlasPart", texPath + ".pvr", texPath + ".jpg", texPath + ".png", texPath + ".dds", texPath + ".jpg", texPath + ".png", }; foreach (var tpp in possiblePaths) { if (Lime.AssetBundle.Current.FileExists(tpp)) { Lime.AssetBundle.Current.OpenFile(tpp); usedImages.Add(texPath.Replace('\\', '/')); return; } } missingResourcesReport.Add(string.Format("texture missing:\n\ttexture path: {0}\n\tscene path: {1}\n", t.SerializationPath, j.ToString())); }); var checkAnimators = new Action <Node>((Node n) => { if (n.Animators.TryFind <SerializableTexture>("Texture", out var ta)) { foreach (var key in ta.ReadonlyKeys) { checkTexture(key.Value); } } }); if (j is Widget) { var w = j as Lime.Widget; var serializableTexture = w.Texture as SerializableTexture; if (serializableTexture != null) { checkTexture(serializableTexture); } checkAnimators(w); } else if (j is ParticleModifier) { var pm = j as Lime.ParticleModifier; var serializableTexture = pm.Texture as SerializableTexture; if (serializableTexture != null) { checkTexture(serializableTexture); } checkAnimators(pm); } else if (j is Lime.Audio) { var au = j as Lime.Audio; var path = au.Sample.SerializationPath + ".sound"; usedSounds.Add(au.Sample.SerializationPath.Replace('\\', '/')); if (!Lime.AssetBundle.Current.FileExists(path)) { missingResourcesReport.Add(string.Format("audio missing:\n\taudio path: {0}\n\tscene path: {1}\n", path, j.ToString())); } else { using (var tempStream = Lime.AssetBundle.Current.OpenFile(path)) { } } // FIXME: should we check for audio:Sample animators too? } } } var reportList = new List <string>(); foreach (var rpr in requestedPaths) { string pattern = String.Format(@".*[/\\](.*)\.{0}", target.Platform.ToString()); string bundle = ""; foreach (Match m in Regex.Matches(rpr.bundle, pattern, RegexOptions.IgnoreCase)) { bundle = m.Groups[1].Value; } int index = Array.IndexOf(cookingRulesMap[srcPath].Bundles, bundle); if (index == -1) { reportList.Add(string.Format("\t[{0}]=>[{2}]: {1}", string.Join(", ", cookingRulesMap[srcPath].Bundles), rpr.path, bundle)); } } requestedPaths.Clear(); if (reportList.Count > 0) { crossRefReport.Add(new Tuple <string, List <string> >(srcPath, reportList)); } Lime.Application.FreeScheduledActions(); } var allImages = new Dictionary <string, bool>(); foreach (var img in AssetBundle.Current.EnumerateFiles(null, ".png")) { var key = Path.Combine(Path.GetDirectoryName(img), Path.GetFileNameWithoutExtension(img)).Replace('\\', '/'); if (!key.StartsWith("Fonts")) { allImages[key] = false; } } foreach (var img in usedImages) { allImages[img] = true; } var unusedImages = allImages.Where(kv => !kv.Value).Select(kv => kv.Key).ToList(); var allSounds = new Dictionary <string, bool>(); foreach (var sound in AssetBundle.Current.EnumerateFiles(null, ".ogg")) { var key = Path.Combine(Path.GetDirectoryName(sound), Path.GetFileNameWithoutExtension(sound)) .Replace('\\', '/'); allSounds[key] = false; } foreach (var sound in usedSounds) { allSounds[sound] = true; } var unusedSounds = allSounds.Where(kv => !kv.Value).Select(kv => kv.Key).ToList(); Action <string> writeHeader = (s) => { int n0 = (80 - s.Length) / 2; int n1 = (80 - s.Length) % 2 == 0 ? n0 : n0 - 1; Console.WriteLine("\n" + new String('=', n0) + " " + s + " " + new String('=', n1)); }; writeHeader("Cross Bundle Dependencies"); foreach (var scenePath in crossRefReport) { Console.WriteLine("\n" + scenePath.Item1); foreach (var refStr in scenePath.Item2) { Console.WriteLine(refStr); } } writeHeader("Missing Resources"); foreach (var s in missingResourcesReport) { Console.WriteLine(s); } writeHeader("Unused Images"); foreach (var s in unusedImages) { Console.WriteLine(s); } writeHeader("Unused Sounds"); foreach (var s in unusedSounds) { Console.WriteLine(s); } writeHeader("Suspicious Textures"); foreach (var s in suspiciousTexturesReport) { Console.WriteLine(s); } } finally { AssetBundle.Current = savedAssetBundle; } }