@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
733 lines (732 loc) • 48 kB
JavaScript
;
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TexturePerformanceTierCount = exports.TextureImageInfoGeneratorTest = void 0;
const ProjectInfoItem_1 = __importDefault(require("./ProjectInfoItem"));
const IProjectItemData_1 = require("../app/IProjectItemData");
const IInfoItemData_1 = require("./IInfoItemData");
const Database_1 = __importDefault(require("../minecraft/Database"));
const StorageUtilities_1 = __importDefault(require("../storage/StorageUtilities"));
const ProjectItemUtilities_1 = __importDefault(require("../app/ProjectItemUtilities"));
const TextureDefinition_1 = __importDefault(require("../minecraft/TextureDefinition"));
const ProjectUtilities_1 = __importStar(require("../app/ProjectUtilities"));
const Utilities_1 = __importDefault(require("../core/Utilities"));
const Pack_1 = require("../minecraft/Pack");
var TextureImageInfoGeneratorTest;
(function (TextureImageInfoGeneratorTest) {
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["textureImages"] = 101] = "textureImages";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["textureImagesTier0"] = 200] = "textureImagesTier0";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["textureImagesTier1"] = 201] = "textureImagesTier1";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["textureImagesTier2"] = 202] = "textureImagesTier2";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["textureImagesTier3"] = 203] = "textureImagesTier3";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["textureImagesTier4"] = 204] = "textureImagesTier4";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["textureImagesTier5"] = 205] = "textureImagesTier5";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["pngJpgImageProcessingError"] = 401] = "pngJpgImageProcessingError";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["individualTextureMemoryExceedsBudget"] = 402] = "individualTextureMemoryExceedsBudget";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["totalTextureMemoryExceedsBudget"] = 403] = "totalTextureMemoryExceedsBudget";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["tgaImageProcessingError"] = 404] = "tgaImageProcessingError";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["individualAtlasTextureMemoryExceedsBudget"] = 405] = "individualAtlasTextureMemoryExceedsBudget";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["totalAtlasTextureMemoryExceedsBudgetWarn"] = 406] = "totalAtlasTextureMemoryExceedsBudgetWarn";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["totalAtlasTextureMemoryExceedsBudgetError"] = 407] = "totalAtlasTextureMemoryExceedsBudgetError";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["pngJpgImageProcessingNoResults"] = 408] = "pngJpgImageProcessingNoResults";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["invalidTieringConfiguration"] = 409] = "invalidTieringConfiguration";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["invalidTieringForVibrantVisuals"] = 410] = "invalidTieringForVibrantVisuals";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["totalTextureMemoryExceedsBudgetErrorBase"] = 420] = "totalTextureMemoryExceedsBudgetErrorBase";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["totalTextureMemoryExceedsBudgetWarningBase"] = 440] = "totalTextureMemoryExceedsBudgetWarningBase";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["texturePackDoesntOverrideVanillaGameTexture"] = 460] = "texturePackDoesntOverrideVanillaGameTexture";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["texturePackDoesntOverrideMostTextures"] = 461] = "texturePackDoesntOverrideMostTextures";
TextureImageInfoGeneratorTest[TextureImageInfoGeneratorTest["mashupPackDoesntOverrideMostTextures"] = 462] = "mashupPackDoesntOverrideMostTextures";
})(TextureImageInfoGeneratorTest || (exports.TextureImageInfoGeneratorTest = TextureImageInfoGeneratorTest = {}));
exports.TexturePerformanceTierCount = 6;
const TextureTiersBase = 200;
const MashupResourcePackThresholdErrorPercent = 0.6; // for a global resource pack in content that also has a world (i.e., a mashup), return an error if it doesn't override at least 60% of vanilla game textures.
const TextureOverrideThresholdPercent = 0.7; // if you override at least 70% of vanilla game textures, we assume you're trying to create a "texture pack" and should warn when you're not "covering" a vanilla texture.
const TextureOverrideThresholdErrorPercent = 0.95; // if you override at least 95% of vanilla game textures, we assume you're trying to create a "texture pack" and should error if you don't have 95% coverage.
const ExemptVanillaOverridePaths = [
"/entity/npc/",
"/entity/banner/",
"/entity/horse/",
"/entity/horse/armor/",
"/entity/zombie_villager/",
"/entity/villager/",
"/entity/zombie_villager2/professions/",
"/colormap/",
"/particle/",
"/misc/",
"/persona_thumbnails/",
"/ui/",
"/gui/",
"/entity/shield_patterns/",
"/textures/trims/",
// Blocks
"/blocks/glowing_obsidian",
"/blocks/missing_tile",
"/blocks/camera_back",
"/blocks/camera_front",
"/blocks/camera_side",
"/blocks/camera_top",
"/blocks/reactor_core_stage_0",
"/blocks/reactor_core_stage_1",
"/blocks/reactor_core_stage_2",
"/blocks/bed_feet_end",
"/blocks/bed_feet_side",
"/blocks/bed_feet_top",
"/blocks/bed_head_end",
"/blocks/bed_head_side",
"/blocks/bed_head_top",
"/blocks/flower_rose_blue",
"/blocks/flower_paeonia",
"/blocks/llama",
"/blocks/border",
"/blocks/build_allow",
"/blocks/build_deny",
"/blocks/smithing_table_top",
"/blocks/end_gateway",
"/blocks/end_portal",
"/blocks/water_flow",
"/blocks/water_still",
"/blocks/carrots_stage3",
"/blocks/bell_side",
"/blocks/bell_top",
// Entity
"/entity/agent",
"/entity/alex",
"/entity/camera_tripod",
"/entity/char",
"/entity/dummy",
"/entity/screenshot_frame",
"/entity/enchanting_table_book_shadow",
"/entity/loyalty_rope",
"/entity/egg_null",
"/entity/egg_template",
"/entity/dragon_exploding",
"/entity/dragon_eyes",
"/entity/llama",
"/entity/pigzombie",
"/entity/steve",
"/entity/cape_invisible",
// Entity subcategories
"/entity/cat/blackcat",
"/entity/cat/graytabby_tame",
"/entity/cat/red",
"/entity/cat/siamese",
"/entity/fish/clownfish",
"/entity/fish/fish",
"/entity/villager2/professions/unskilled",
"/entity/horse2/horse_markings_none",
"/entity/horse2/armor/horse_armor_none",
"/entity/llama/decor/decor_none",
"/entity/llama/spit",
"/entity/iron_golem/cracked_none",
"/entity/wolf/wolf_armor_cracked_none",
// Items
"/items/camera",
"/items/chalkboard_large",
"/items/chalkboard_medium",
"/items/chalkboard_small",
"/items/egg_agent",
"/items/egg_npc",
"/items/quiver",
"/items/ruby",
"/items/spawn_egg_overlay",
"/items/book_portfolio",
"/items/boat",
"/items/tipped_arrow_base",
"/items/tipped_arrow_head",
"/items/tipped_arrow_luck",
"/items/potion_bottle_saturation",
"/items/potion_overlay",
"/items/egg_template",
"/items/egg_mask",
"/items/spawn_egg",
"/items/hoglin_meat_cooked",
"/items/hoglin_meat_raw",
"/items/egg_fish",
"/items/boat_dark_oak",
"/items/light_block_0",
"/items/light_block_1",
"/items/light_block_10",
"/items/light_block_11",
"/items/light_block_12",
"/items/light_block_13",
"/items/light_block_14",
"/items/light_block_15",
"/items/light_block_2",
"/items/light_block_3",
"/items/light_block_4",
"/items/light_block_5",
"/items/light_block_6",
"/items/light_block_7",
"/items/light_block_8",
"/items/light_block_9",
// Models
"models/armor/cloth_1",
"models/armor/cloth_2",
// Map
"map/player_icon_background",
"map/jungle_temple",
"map/swamp_hut",
"map/village_desert",
"map/village_plains",
"map/village_savanna",
"map/village_snowy",
"map/village_taiga",
"map/trial_chambers",
// Root level
"forcefield_atlas",
];
/*
export enum ProjectMetaCategory {
mix = 0,
worldTemplate = 1,
texturePack = 2,
addOn = 3,
skinPack = 4,
persona = 5,
}*/
const TextureMemoryLimitsByTier = {
0 /*mix*/: { 0: 750, 1: 750, 2: 1000, 3: 1500, 4: 3000, 5: 4000 },
1 /*world template*/: { 0: 750, 1: 750, 2: 1000, 3: 1500, 4: 3000, 5: 4000 },
2 /*texture pack*/: { 0: 350, 1: 350, 2: 500, 3: 650, 4: 1250, 5: 1650 }, // added 50mb as a "discount" assuming texture packs override vanilla textures, and therefore save the 50mb of vanilla texture overhead
3 /*add-on*/: { 0: 150, 1: 150, 2: 225, 3: 300, 4: 600, 5: 800 },
4 /*skin pack*/: { 0: 150, 1: 150, 2: 225, 3: 300, 4: 600, 5: 800 },
5 /*persona*/: { 0: 150, 1: 150, 2: 225, 3: 300, 4: 600, 5: 800 },
};
/**
* Validates texture images for size, format, and memory budget compliance.
*
* @see {@link ../../public/data/forms/mctoolsval/textureimage.form.json} for topic definitions
*/
class TextureImageInfoGenerator {
id = "TEXTUREIMAGE";
title = "Texture Image Validation";
performAddOnValidations = false;
_vanillaPathCache = new Map();
async _matchesVanillaPathCached(path) {
const cached = this._vanillaPathCache.get(path);
if (cached !== undefined) {
return cached;
}
const result = await Database_1.default.matchesVanillaPath(path);
this._vanillaPathCache.set(path, result);
return result;
}
summarize(info, infoSet) {
info.textureCount = infoSet.getSummedFeatureValue(this.id, TextureImageInfoGeneratorTest.textureImages, "texels", "instanceCount");
info.textureTexelSize = infoSet.getSummedFeatureValue(this.id, TextureImageInfoGeneratorTest.textureImages, "nonVanillaTexels", "total");
info.vanillaGameTextureCoverage = infoSet.getAverageFeatureValue(this.id, TextureImageInfoGeneratorTest.textureImages, "vanillaGameTextureCoverage", "percent");
info.vanillaGameTextureCount = infoSet.getAverageFeatureValue(this.id, TextureImageInfoGeneratorTest.textureImages, "vanillaGameTextureCoverage", "overrideCount");
let minimumSupportablePerformanceTier = 0;
for (let i = 0; i < exports.TexturePerformanceTierCount; i++) {
const overBudgetTierErrors = infoSet.getCount(this.id, TextureImageInfoGeneratorTest.totalTextureMemoryExceedsBudgetWarningBase + i);
if (overBudgetTierErrors > 0) {
minimumSupportablePerformanceTier = i + 1;
}
}
// If the project uses subpacks, the minimum tier is at least the lowest tier
// any subpack targets — Minecraft never loads a resource pack with subpacks
// without one of them applied.
const minSubpackTier = infoSet.getSummedFeatureValue(this.id, TextureImageInfoGeneratorTest.textureImages, "subpackTiering", "minimumEffectiveTier");
if (minSubpackTier > 0) {
minimumSupportablePerformanceTier = Math.max(minimumSupportablePerformanceTier, minSubpackTier);
}
info.minimumSupportablePerformanceTier = minimumSupportablePerformanceTier;
}
static isGameTexturePath(path) {
path = path.toLowerCase();
let result = path.startsWith("/resource_pack/textures/") &&
(path.endsWith(".png") || path.endsWith(".tga") || path.indexOf(".") < 0) &&
path.indexOf("_mers.") < 0 &&
path.indexOf("_mer.") < 0 &&
path.indexOf("_normal.") < 0 &&
path.indexOf("_mipmap.") < 0 &&
(path.indexOf("/textures/blocks") >= 0 ||
path.indexOf("/textures/entity") >= 0 ||
path.indexOf("/textures/items") >= 0);
if (!result) {
return false;
}
for (let exemptPath of ExemptVanillaOverridePaths) {
if (path.toLowerCase().indexOf(exemptPath) >= 0) {
return false;
}
}
return true;
}
async generate(project, contentIndex) {
const items = [];
const textureImagePi = new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.featureAggregate, this.id, TextureImageInfoGeneratorTest.textureImages, "Texture Images");
items.push(textureImagePi);
const vanillaPathList = await Database_1.default.getVanillaPathList();
const nonVanillaTextureMemoryByTier = [];
const totalTextureMemoryByTier = [];
const itemAtlasTextureMemoryByTier = [];
const blockAtlasTextureMemoryByTier = [];
const textureTierImagePi = [];
const hasSupportForTier = [];
const tierTotalMemorySizes = [];
let isExplicitlyTargetingTiers = false;
const vanillaTexturePathNonMers = {};
let vanillaTexturePathNonMersCount = 0;
if (vanillaPathList) {
for (const path of vanillaPathList) {
if (TextureImageInfoGenerator.isGameTexturePath(path)) {
vanillaTexturePathNonMersCount++;
let extensionlessPath = StorageUtilities_1.default.stripExtension(path);
vanillaTexturePathNonMers[extensionlessPath] = false;
}
}
}
for (let i = 0; i < exports.TexturePerformanceTierCount; i++) {
hasSupportForTier[i] = false;
nonVanillaTextureMemoryByTier.push({});
totalTextureMemoryByTier.push({});
itemAtlasTextureMemoryByTier.push({});
blockAtlasTextureMemoryByTier.push({});
textureTierImagePi[i] = new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.featureAggregate, this.id, TextureTiersBase + i, "Texture Images Tier " + i);
items.push(textureTierImagePi[i]);
}
for (const projectVariant in project.variants) {
const variant = project.variants[projectVariant];
if (!variant.isDefault && variant.effectiveUnifiedTier !== undefined) {
hasSupportForTier[variant.effectiveUnifiedTier] = true;
isExplicitlyTargetingTiers = true;
}
}
// When a resource pack uses subpacks, Minecraft never loads the pack without
// one of the subpacks applied. The lowest tier among all subpacks is therefore
// the de-facto minimum device tier the creator supports, regardless of texture
// budgets. Record it so that summarize() can incorporate it into
// minimumSupportablePerformanceTier.
if (isExplicitlyTargetingTiers) {
let minSubpackTier = exports.TexturePerformanceTierCount; // start above max, will be reduced
for (const projectVariant in project.variants) {
const variant = project.variants[projectVariant];
if (!variant.isDefault && variant.effectiveUnifiedTier !== undefined) {
minSubpackTier = Math.min(minSubpackTier, variant.effectiveUnifiedTier);
}
}
if (minSubpackTier < exports.TexturePerformanceTierCount) {
textureImagePi.setFeature("Subpack Tiering", "Minimum Effective Tier", minSubpackTier);
}
}
const textureItems = project.getItemsByType(IProjectItemData_1.ProjectItemType.texture);
for (const projectItem of textureItems) {
if (projectItem.projectPath &&
!projectItem.projectPath.endsWith(".hdr") // ignore HDR files
) {
if (!projectItem.isContentLoaded) {
await projectItem.loadContent();
}
const variantList = projectItem.getVariantList();
// we assume that the default (base) variant, with a label of "", must go
// first.
variantList.sort((a, b) => {
return a.label.localeCompare(b.label);
});
for (const variant of variantList) {
const variantFile = variant.file;
if (variantFile) {
let nonVanillaTextureMemoryByTierToUpdate = [];
let totalTextureMemoryByTierToUpdate = [];
let itemAtlasTextureMemoryByTierToUpdate = [];
let blockAtlasTextureMemoryByTierToUpdate = [];
let textureTierItem = undefined;
if (variant.projectVariant) {
if (variant.projectVariant.isDefault) {
// if this is the default, set texture sizes by path for all tiers
nonVanillaTextureMemoryByTierToUpdate = nonVanillaTextureMemoryByTier;
totalTextureMemoryByTierToUpdate = totalTextureMemoryByTier;
itemAtlasTextureMemoryByTierToUpdate = itemAtlasTextureMemoryByTier;
blockAtlasTextureMemoryByTierToUpdate = blockAtlasTextureMemoryByTier;
}
else if (variant.projectVariant.effectiveUnifiedTier !== undefined) {
// otherwise, set the texture sizes by path for the effective tier
textureTierItem = textureTierImagePi[variant.projectVariant.effectiveUnifiedTier];
nonVanillaTextureMemoryByTierToUpdate.push(nonVanillaTextureMemoryByTier[variant.projectVariant.effectiveUnifiedTier]);
totalTextureMemoryByTierToUpdate.push(totalTextureMemoryByTier[variant.projectVariant.effectiveUnifiedTier]);
itemAtlasTextureMemoryByTierToUpdate.push(itemAtlasTextureMemoryByTier[variant.projectVariant.effectiveUnifiedTier]);
blockAtlasTextureMemoryByTierToUpdate.push(blockAtlasTextureMemoryByTier[variant.projectVariant.effectiveUnifiedTier]);
}
}
let pathInRp = await projectItem.getPackRelativePath();
let isVanilla = false;
// Log.assertDefined(pathInRp, "TIIGGP");
if (pathInRp) {
pathInRp = StorageUtilities_1.default.getBaseFromName(StorageUtilities_1.default.ensureNotStartsWithDelimiter(pathInRp));
const vanillaRpPath = "/resource_pack/" + pathInRp;
if (vanillaTexturePathNonMers[vanillaRpPath] === false) {
vanillaTexturePathNonMers[vanillaRpPath] = true;
}
if (await this._matchesVanillaPathCached(pathInRp)) {
textureImagePi.incrementFeature("Vanilla Override Texture");
if (textureTierItem) {
textureTierItem.incrementFeature("Vanilla Override Texture");
}
isVanilla = true;
}
else {
textureImagePi.incrementFeature("Custom Texture");
if (textureTierItem) {
textureTierItem.incrementFeature("Custom Texture");
}
}
}
else {
textureImagePi.incrementFeature("Custom Texture");
}
let imageWidth = -1;
let imageHeight = -1;
if (variantFile.content && variantFile.content instanceof Uint8Array) {
const textureDefinition = await TextureDefinition_1.default.ensureOnFile(variantFile);
if (textureDefinition) {
await textureDefinition.processContent();
if (textureDefinition.errorProcessing) {
if (variantFile.type === "tga") {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.internalProcessingError, this.id, TextureImageInfoGeneratorTest.tgaImageProcessingError, `Error processing TGA image`, projectItem, textureDefinition.errorMessage));
}
else {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.warning, this.id, TextureImageInfoGeneratorTest.pngJpgImageProcessingNoResults, `Could not extract metadata from PNG/JPG/TIF/HEIC image`, projectItem, textureDefinition.errorMessage));
}
}
else if (textureDefinition.width !== undefined && textureDefinition.height !== undefined) {
imageWidth = textureDefinition.width;
imageHeight = textureDefinition.height;
const textureMem = imageWidth * imageHeight * 4;
if (variantFile.type === "tga") {
textureImagePi.spectrumIntFeature("TGA Width", imageWidth);
textureImagePi.spectrumIntFeature("TGA Height", imageHeight);
textureImagePi.spectrumIntFeature("TGA Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("TGA Texture Memory", textureMem);
}
else {
textureImagePi.spectrumIntFeature("PNGJPG Width", imageWidth);
textureImagePi.spectrumIntFeature("PNGJPG Height", imageHeight);
textureImagePi.spectrumIntFeature("PNGJPG Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("PNGJPG Texture Memory", textureMem);
}
}
}
}
if (imageWidth >= 0 && imageHeight >= 0) {
const textureMem = imageWidth * imageHeight * 4;
let isAtlasTexture = false;
textureImagePi.spectrumIntFeature("Width", imageWidth);
textureImagePi.spectrumIntFeature("Height", imageHeight);
textureImagePi.spectrumIntFeature("Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Texture Memory", textureMem);
if (pathInRp) {
for (const totalTextureMemoryByTier of totalTextureMemoryByTierToUpdate) {
totalTextureMemoryByTier[pathInRp] = textureMem;
}
}
if (!isVanilla) {
textureImagePi.spectrumIntFeature("Non-Vanilla Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Non-Vanilla Texture Memory", textureMem);
if (pathInRp) {
for (const nonVanillaTextureMemoryByTier of nonVanillaTextureMemoryByTierToUpdate) {
nonVanillaTextureMemoryByTier[pathInRp] = textureMem;
}
}
}
let relations = 0;
if (ProjectItemUtilities_1.default.isUIRelated(projectItem)) {
textureImagePi.spectrumIntFeature("UI Width", imageWidth);
textureImagePi.spectrumIntFeature("UI Height", imageHeight);
textureImagePi.spectrumIntFeature("UI Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("UI Texture Memory", textureMem);
if (!isVanilla) {
textureImagePi.spectrumIntFeature("UI Non-Vanilla Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("UI Non-Vanilla Texture Memory", textureMem);
}
relations++;
}
if (ProjectItemUtilities_1.default.isBlockRelated(projectItem)) {
textureImagePi.spectrumIntFeature("Block Width", imageWidth);
textureImagePi.spectrumIntFeature("Block Height", imageHeight);
textureImagePi.spectrumIntFeature("Block Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Block Texture Memory", textureMem);
if (!isVanilla) {
textureImagePi.spectrumIntFeature("Block Non-Vanilla Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Block Non-Vanilla Texture Memory", textureMem);
if (pathInRp) {
for (const blockAtlasMemoryByTier of blockAtlasTextureMemoryByTierToUpdate) {
blockAtlasMemoryByTier[pathInRp] = textureMem;
}
}
}
relations++;
isAtlasTexture = true;
}
if (ProjectItemUtilities_1.default.isEntityRelated(projectItem)) {
textureImagePi.spectrumIntFeature("Entity Width", imageWidth);
textureImagePi.spectrumIntFeature("Entity Height", imageHeight);
textureImagePi.spectrumIntFeature("Entity Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Entity Texture Memory", textureMem);
if (!isVanilla) {
textureImagePi.spectrumIntFeature("Entity Non-Vanilla Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Entity Non-Vanilla Texture Memory", textureMem);
}
relations++;
}
if (ProjectItemUtilities_1.default.isItemRelated(projectItem)) {
textureImagePi.spectrumIntFeature("Item Width", imageWidth);
textureImagePi.spectrumIntFeature("Item Height", imageHeight);
textureImagePi.spectrumIntFeature("Item Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Item Texture Memory", textureMem);
if (!isVanilla) {
textureImagePi.spectrumIntFeature("Item Non-Vanilla Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Item Non-Vanilla Texture Memory", textureMem);
if (pathInRp) {
for (const itemAtlasMemoryByTier of itemAtlasTextureMemoryByTierToUpdate) {
itemAtlasMemoryByTier[pathInRp] = textureMem;
}
}
}
relations++;
isAtlasTexture = true;
}
if (ProjectItemUtilities_1.default.isParticleRelated(projectItem)) {
textureImagePi.spectrumIntFeature("Particle Width", imageWidth);
textureImagePi.spectrumIntFeature("Particle Height", imageHeight);
textureImagePi.spectrumIntFeature("Particle Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Particle Texture Memory", textureMem);
if (!isVanilla) {
textureImagePi.spectrumIntFeature("Particle Non-Vanilla Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Particle Non-Vanilla Texture Memory", textureMem);
}
relations++;
}
if (relations === 0 && projectItem.projectPath) {
const texturePath = TextureDefinition_1.default.getTexturePath(projectItem.projectPath);
if (texturePath) {
const isVanillaEx = await this._matchesVanillaPathCached(texturePath);
if (isVanillaEx) {
textureImagePi.spectrumIntFeature("Vanilla Override Width", imageWidth);
textureImagePi.spectrumIntFeature("Vanilla Override Height", imageHeight);
textureImagePi.spectrumIntFeature("Vanilla Override Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Vanilla Override Texture Memory", textureMem);
}
else {
textureImagePi.spectrumIntFeature("Non-tied Width", imageWidth);
textureImagePi.spectrumIntFeature("Non-tied Height", imageHeight);
textureImagePi.spectrumIntFeature("Non-tied Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Non-tied Texture Memory", textureMem);
if (!isVanilla) {
textureImagePi.spectrumIntFeature("Non-tied Non-Vanilla Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Non-tied Non-Vanilla Texture Memory", textureMem);
}
}
}
}
else if (relations > 1) {
textureImagePi.spectrumIntFeature("Multi-tied Width", imageWidth);
textureImagePi.spectrumIntFeature("Multi-tied Height", imageHeight);
textureImagePi.spectrumIntFeature("Multi-tied Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Multi-tied Texture Memory", textureMem);
if (!isVanilla) {
textureImagePi.spectrumIntFeature("Multi-tied Non-Vanilla Texels", imageWidth * imageHeight);
textureImagePi.spectrumIntFeature("Multi-tied Non-Vanilla Texture Memory", textureMem);
}
}
let individualMemoryBudget = 2048 * 2048 * 4;
if (isAtlasTexture) {
individualMemoryBudget = 256 * 256 * 4;
if (textureMem > individualMemoryBudget) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.warning, this.id, TextureImageInfoGeneratorTest.individualAtlasTextureMemoryExceedsBudget, `Individual atlassed texture memory exceeds budget of ${individualMemoryBudget} bytes. Memory used`, projectItem, textureMem));
}
}
else {
if (textureMem > individualMemoryBudget) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.warning, this.id, TextureImageInfoGeneratorTest.individualTextureMemoryExceedsBudget, `Individual loose texture memory exceeds budget of ${individualMemoryBudget} bytes. Memory used`, projectItem, textureMem));
}
}
}
}
}
}
}
let maxTotalTextureMemory = 0;
let maxNonVanillaTextureMemory = 0;
let maxBlockAtlasTextureMemory = 0;
let maxItemAtlasTextureMemory = 0;
const category = await ProjectUtilities_1.default.getMetaCategory(project);
for (let curTier = 0; curTier < exports.TexturePerformanceTierCount; curTier++) {
let tierNonVanillaTextureMemory = 0;
let tierTotalTextureMemory = 0;
let tierTotalBlockAtlasTextureMemory = 0;
let tierTotalItemAtlasTextureMemory = 0;
for (const texturePath in nonVanillaTextureMemoryByTier[curTier]) {
tierNonVanillaTextureMemory += nonVanillaTextureMemoryByTier[curTier][texturePath];
}
for (const texturePath in totalTextureMemoryByTier[curTier]) {
tierTotalTextureMemory += totalTextureMemoryByTier[curTier][texturePath];
}
for (const texturePath in blockAtlasTextureMemoryByTier[curTier]) {
tierTotalBlockAtlasTextureMemory += blockAtlasTextureMemoryByTier[curTier][texturePath];
}
for (const texturePath in itemAtlasTextureMemoryByTier[curTier]) {
tierTotalItemAtlasTextureMemory += itemAtlasTextureMemoryByTier[curTier][texturePath];
}
if (curTier > 0 && !hasSupportForTier[curTier]) {
textureTierImagePi[curTier].spectrumIntFeature("Non-Vanilla Texture Memory Tier", maxNonVanillaTextureMemory);
textureTierImagePi[curTier].spectrumIntFeature("Total Texture Memory Tier", maxTotalTextureMemory);
}
else {
maxItemAtlasTextureMemory = Math.max(maxItemAtlasTextureMemory, tierTotalItemAtlasTextureMemory);
maxBlockAtlasTextureMemory = Math.max(maxBlockAtlasTextureMemory, tierTotalBlockAtlasTextureMemory);
maxNonVanillaTextureMemory = Math.max(maxNonVanillaTextureMemory, tierNonVanillaTextureMemory);
maxTotalTextureMemory = Math.max(maxTotalTextureMemory, tierTotalTextureMemory);
textureTierImagePi[curTier].spectrumIntFeature("Non-Vanilla Texture Memory Tier", tierNonVanillaTextureMemory);
textureTierImagePi[curTier].spectrumIntFeature("Total Texture Memory Tier", tierTotalTextureMemory);
}
// Expand atlases to power of 2
tierTotalBlockAtlasTextureMemory = Math.pow(2, Math.ceil(Math.log2(tierTotalBlockAtlasTextureMemory)));
tierTotalItemAtlasTextureMemory = Math.pow(2, Math.ceil(Math.log2(tierTotalItemAtlasTextureMemory)));
textureTierImagePi[curTier].spectrumIntFeature("Block Atlas Texture Memory Tier", tierTotalBlockAtlasTextureMemory);
textureTierImagePi[curTier].spectrumIntFeature("Item Atlas Texture Memory Tier", tierTotalItemAtlasTextureMemory);
// 4K limit for warn, 8K as hard limit
const totalAtlasTextureMemoryBudgetWarn = 4096 * 4096 * 4;
const totalAtlasTextureMemoryBudgetError = totalAtlasTextureMemoryBudgetWarn * 4;
if (tierTotalBlockAtlasTextureMemory > totalAtlasTextureMemoryBudgetError) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.error, this.id, TextureImageInfoGeneratorTest.totalAtlasTextureMemoryExceedsBudgetError, `Texture memory of block atlas exceeds hard limit of ${Utilities_1.default.addCommasToNumber(totalAtlasTextureMemoryBudgetError)} bytes. Total memory used`, undefined, tierTotalBlockAtlasTextureMemory));
}
else if (tierTotalBlockAtlasTextureMemory > totalAtlasTextureMemoryBudgetWarn) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.warning, this.id, TextureImageInfoGeneratorTest.totalAtlasTextureMemoryExceedsBudgetWarn, `Texture memory of block atlas exceeds budget of ${Utilities_1.default.addCommasToNumber(totalAtlasTextureMemoryBudgetWarn)} bytes. Total memory used`, undefined, tierTotalBlockAtlasTextureMemory));
}
if (tierTotalItemAtlasTextureMemory > totalAtlasTextureMemoryBudgetError) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.error, this.id, TextureImageInfoGeneratorTest.totalAtlasTextureMemoryExceedsBudgetError, `Texture memory of item atlas exceeds hard limit of ${Utilities_1.default.addCommasToNumber(totalAtlasTextureMemoryBudgetError)} bytes. Total memory used`, undefined, tierTotalItemAtlasTextureMemory));
}
else if (tierTotalItemAtlasTextureMemory > totalAtlasTextureMemoryBudgetWarn) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.warning, this.id, TextureImageInfoGeneratorTest.totalAtlasTextureMemoryExceedsBudgetWarn, `Texture memory of item atlas exceeds budget of ${Utilities_1.default.addCommasToNumber(totalAtlasTextureMemoryBudgetWarn)} bytes. Total memory used`, undefined, tierTotalItemAtlasTextureMemory));
}
tierNonVanillaTextureMemory += tierTotalBlockAtlasTextureMemory + tierTotalItemAtlasTextureMemory;
tierTotalTextureMemory += tierTotalBlockAtlasTextureMemory + tierTotalItemAtlasTextureMemory;
let curMemoryUsedInTier = tierTotalTextureMemory;
if (curMemoryUsedInTier <= 0) {
curMemoryUsedInTier = maxTotalTextureMemory;
}
if (curTier > 0 && !hasSupportForTier[curTier]) {
curMemoryUsedInTier = maxTotalTextureMemory;
}
tierTotalMemorySizes[curTier] = curMemoryUsedInTier;
// check for non linear tier scaling (e.g., tier 2 occupies more memory than tier 3)
if (hasSupportForTier[curTier]) {
for (let prevTier = 0; prevTier < curTier; prevTier++) {
if (tierTotalMemorySizes[prevTier] && tierTotalMemorySizes[prevTier] > curMemoryUsedInTier) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.error, this.id, TextureImageInfoGeneratorTest.invalidTieringConfiguration, `Lower tier ${prevTier} has a higher memory requirement (${Utilities_1.default.addCommasToNumber(tierTotalMemorySizes[prevTier])}) than tier ${curTier}`, undefined, curMemoryUsedInTier));
}
}
}
const tierTotalTextureMemoryBudget = 1024 * 1024 * TextureMemoryLimitsByTier[category][curTier];
if (curMemoryUsedInTier > tierTotalTextureMemoryBudget) {
let warningMessage = `Total texture memory exceeds budget of ${Utilities_1.default.addCommasToNumber(tierTotalTextureMemoryBudget)} bytes for items of type ${ProjectUtilities_1.default.getMetaCategoryDescription(category)} at tier ${curTier}.`;
warningMessage += " Total texture memory used";
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.warning, this.id, TextureImageInfoGeneratorTest.totalTextureMemoryExceedsBudgetWarningBase + curTier, warningMessage, undefined, curMemoryUsedInTier));
for (const pvLabel in project.variants) {
const pv = project.variants[pvLabel];
if (pv && pv.effectiveUnifiedTier === curTier) {
warningMessage += " Because this project specifically targets this tier, it is an error.";
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.error, this.id, TextureImageInfoGeneratorTest.totalTextureMemoryExceedsBudgetErrorBase + curTier, warningMessage, undefined, curMemoryUsedInTier));
break;
}
}
// vibrant visuals content must support tier 2 memory limits.
if (curTier === 2) {
const isVibrantVisualsCompatible = await ProjectUtilities_1.default.isVibrantVisualsCompatible(project);
if (isVibrantVisualsCompatible) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.error, this.id, TextureImageInfoGeneratorTest.invalidTieringForVibrantVisuals, `Project is marked to support vibrant visuals, but does not support texture limits at tiers ${curTier}. Texture memory`, undefined, curMemoryUsedInTier));
}
}
}
}
// if add-on content is not managing its texture memory across tiers and is exceeding a base limit, throw an error
if (!isExplicitlyTargetingTiers &&
category === ProjectUtilities_1.ProjectMetaCategory.addOn &&
maxTotalTextureMemory > TextureMemoryLimitsByTier[category][0] * 1024 * 1024) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.error, this.id, TextureImageInfoGeneratorTest.totalTextureMemoryExceedsBudget, `Total texture memory exceeds base budget of ${Utilities_1.default.addCommasToNumber(TextureMemoryLimitsByTier[category][0] * 1024 * 1024)} bytes and is not using subpacks to target specific tiers. Total memory used`, undefined, maxTotalTextureMemory));
}
const totalTextureMemoryBudget = 1024 * 1024 * TextureMemoryLimitsByTier[category][5];
// if content exceeds absolute limits, throw an error
if (maxTotalTextureMemory > totalTextureMemoryBudget) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.error, this.id, TextureImageInfoGeneratorTest.totalTextureMemoryExceedsBudget, `Total texture memory exceeds absolute budget of ${Utilities_1.default.addCommasToNumber(totalTextureMemoryBudget)} bytes. Total memory used`, undefined, maxTotalTextureMemory));
}
if (vanillaTexturePathNonMersCount > 0) {
let vanillaOverrideTextureCount = 0;
for (const path in vanillaTexturePathNonMers) {
if (vanillaTexturePathNonMers[path] === true) {
vanillaOverrideTextureCount++;
}
}
textureImagePi.setFeature("Vanilla Game Texture Coverage", "Override Count", vanillaOverrideTextureCount);
textureImagePi.setFeature("Vanilla Game Texture Coverage", "Vanilla Texture Count", vanillaTexturePathNonMersCount);
textureImagePi.setFeature("Vanilla Game Texture Coverage", "Percent", vanillaOverrideTextureCount / vanillaTexturePathNonMersCount);
const actualOverridePercent = vanillaOverrideTextureCount / vanillaTexturePathNonMersCount;
if (actualOverridePercent < MashupResourcePackThresholdErrorPercent) {
let hasGlobalResourcePack = false;
for (const pack of project.packs) {
if (pack.packType === Pack_1.PackType.resource && !pack.isInWorld) {
hasGlobalResourcePack = true;
}
}
const worldCount = project.getItemsByType(IProjectItemData_1.ProjectItemType.worldFolder).length;
if (hasGlobalResourcePack && worldCount > 0) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.error, this.id, TextureImageInfoGeneratorTest.mashupPackDoesntOverrideMostTextures, `Content seems like a mashup pack, but the resource pack does not override >60% of vanilla textures.`, undefined, actualOverridePercent));
}
}
if (actualOverridePercent >= TextureOverrideThresholdPercent) {
if (actualOverridePercent < TextureOverrideThresholdErrorPercent) {
const nonRpItemCount = project.getItemsByType(IProjectItemData_1.ProjectItemType.behaviorPackManifestJson).length +
project.getItemsByType(IProjectItemData_1.ProjectItemType.worldFolder).length;
if (nonRpItemCount === 0) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.error, this.id, TextureImageInfoGeneratorTest.texturePackDoesntOverrideMostTextures, `Content seems like a texture pack (overrides >70% of textures), but does not override the vast majority of textures. This pack should override at least 95% of vanilla textures.`, undefined, actualOverridePercent));
}
}
for (const path in vanillaTexturePathNonMers) {
if (vanillaTexturePathNonMers[path] === false) {
items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.warning, this.id, TextureImageInfoGeneratorTest.texturePackDoesntOverrideVanillaGameTexture, `Content seems like a texture pack, but does not override vanilla texture`, undefined, path));
}
}
}
}
return items;
}
}
exports.default = TextureImageInfoGenerator;