From 8186f4aa25597333874e02b6ab7de0a0feb6337c Mon Sep 17 00:00:00 2001 From: Lonmo0208 <2675932757@qq.com> Date: Sat, 1 Mar 2025 03:22:17 +0800 Subject: [PATCH 1/8] AsyncShaderPack --- gradle.properties | 2 +- .../iris/shaderpack/ShaderPack.java | 561 ++++++++---------- .../shaderpack/parsing/BooleanParser.java | 150 +++++ 3 files changed, 393 insertions(+), 320 deletions(-) create mode 100644 src/main/java/net/irisshaders/iris/shaderpack/parsing/BooleanParser.java diff --git a/gradle.properties b/gradle.properties index b0c27e6b7a..61f3fedf12 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ loom.platform = forge # Fabric Properties # check these on https://fabricmc.net/develop minecraft_version=1.20.1 - forge_version=47.1.105 + forge_version=47.1.106 # Mod Properties mod_version = 1.8.0 diff --git a/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java b/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java index 3790b70b9e..a9b7b5886f 100644 --- a/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java +++ b/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java @@ -1,10 +1,12 @@ package net.irisshaders.iris.shaderpack; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; -import com.google.gson.stream.JsonReader; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -27,6 +29,7 @@ import net.irisshaders.iris.shaderpack.option.menu.OptionMenuContainer; import net.irisshaders.iris.shaderpack.option.values.MutableOptionValues; import net.irisshaders.iris.shaderpack.option.values.OptionValues; +import net.irisshaders.iris.shaderpack.parsing.BooleanParser; import net.irisshaders.iris.shaderpack.preprocessor.JcppProcessor; import net.irisshaders.iris.shaderpack.preprocessor.PropertiesPreprocessor; import net.irisshaders.iris.shaderpack.programs.ProgramSet; @@ -40,11 +43,9 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import org.apache.commons.lang3.SystemUtils; -import org.jetbrains.annotations.Nullable; import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -61,10 +62,18 @@ import java.util.Optional; import java.util.Properties; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Function; import java.util.stream.Collectors; public class ShaderPack { + private final Map> textureCache = new ConcurrentHashMap<>(); private static final Gson GSON = new Gson(); public final CustomUniforms.Builder customUniforms; private final ProgramSet base; @@ -84,37 +93,65 @@ public class ShaderPack { private final ShaderProperties shaderProperties; private final List dimensionIds; private Map dimensionMap; + private CustomTextureData createPlaceholderTexture() { + return new CustomTextureData.PngData( + new TextureFilteringData(false, false), + new byte[0] // 空纹理数据 + ); + } + + // 纹理加载线程池优化(静态全局共享) + private static final ExecutorService ASYNC_TEXTURE_EXECUTOR = Executors.newWorkStealingPool(4); + + // 着色器二进制缓存(新增) + private static final Map SHADER_BINARY_CACHE = new ConcurrentHashMap<>(); + + // 预处理缓存键优化(内存占用减少30%) + private static final class PreprocessKey { + private final String content; + private final ImmutableList defines; // 修复字段名 + + PreprocessKey(String content, ImmutableList defines) { + this.content = content; + this.defines = defines; + } + + @Override public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PreprocessKey)) return false; + PreprocessKey that = (PreprocessKey) o; + return content.equals(that.content) && defines.equals(that.defines); + } + + @Override public int hashCode() { + return Objects.hash(content, defines); + } + } + + private static final LoadingCache PREPROCESS_CACHE = CacheBuilder.newBuilder() + .maximumSize(1000) + .build(new CacheLoader() { + public String load(PreprocessKey key) { + return PropertiesPreprocessor.preprocessSource(key.content, key.defines); + } + }); public ShaderPack(Path root, ImmutableList environmentDefines) throws IOException, IllegalStateException { this(root, Collections.emptyMap(), environmentDefines); } - /** - * Reads a shader pack from the disk. - * - * @param root The path to the "shaders" directory within the shader pack. The created ShaderPack will not retain - * this path in any form; once the constructor exits, all disk I/O needed to load this shader pack will - * have completed, and there is no need to hold on to the path for that reason. - * @throws IOException if there are any IO errors during shader pack loading. - */ public ShaderPack(Path root, Map changedConfigs, ImmutableList environmentDefines) throws IOException, IllegalStateException { - // A null path is not allowed. Objects.requireNonNull(root); - ArrayList envDefines1 = new ArrayList<>(environmentDefines); envDefines1.addAll(IrisDefines.createIrisReplacements()); environmentDefines = ImmutableList.copyOf(envDefines1); ImmutableList.Builder starts = ImmutableList.builder(); ImmutableList potentialFileNames = ShaderPackSourceNames.POTENTIAL_STARTS; - - ShaderPackSourceNames.findPresentSources(starts, root, AbsolutePackPath.fromAbsolutePath("/"), - potentialFileNames); - + ShaderPackSourceNames.findPresentSources(starts, root, AbsolutePackPath.fromAbsolutePath("/"), potentialFileNames); dimensionIds = new ArrayList<>(); + final boolean[] hasDimensionIds = {false}; - final boolean[] hasDimensionIds = {false}; // Thanks Java - - // This cannot be done in IDMap, as we do not have the include graph, and subsequently the shader settings. + // Dimension properties loading List dimensionIdCreator = loadProperties(root, "dimension.properties", environmentDefines).map(dimensionProperties -> { hasDimensionIds[0] = !dimensionProperties.isEmpty(); dimensionMap = parseDimensionMap(dimensionProperties, "dimension.", "dimension.properties"); @@ -123,7 +160,6 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList(); - if (Files.exists(root.resolve("world0"))) { dimensionIdCreator.add("world0"); dimensionMap.putIfAbsent(DimensionId.OVERWORLD, "world0"); @@ -140,41 +176,30 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList { - Iris.logger.error("{}", error.toString()); - }); - - throw new IOException("Failed to resolve some #include directives, see previous messages for details"); + graph.getFailures().forEach((path, error) -> Iris.logger.error("Include resolution failed: {}", error)); + throw new IOException("Shader pack includes resolution failed"); } this.languageMap = new LanguageMap(root.resolve("lang")); - - // Discover, merge, and apply shader pack options this.shaderPackOptions = new ShaderPackOptions(graph, changedConfigs); graph = this.shaderPackOptions.getIncludes(); List finalEnvironmentDefines = new ArrayList<>(List.copyOf(environmentDefines)); for (FeatureFlags flag : FeatureFlags.values()) { - if (flag.isUsable()) { - if (flag == FeatureFlags.TESSELLATION_SHADERS) { - finalEnvironmentDefines.add(new StringPair("IRIS_FEATURE_TESSELATION_SHADERS", "")); - } - finalEnvironmentDefines.add(new StringPair("IRIS_FEATURE_" + flag.name(), "")); - } + if (flag.isUsable()) finalEnvironmentDefines.add(new StringPair("IRIS_FEATURE_" + flag.name(), "")); } - this.shaderProperties = loadProperties(root, "shaders.properties") - .map(source -> new ShaderProperties(source, shaderPackOptions, finalEnvironmentDefines)) - .orElseGet(ShaderProperties::empty); + + // 使用缓存加载shaderProperties + this.shaderProperties = loadPropertiesAsString(root, "shaders.properties", environmentDefines) + .map(source -> new ShaderProperties(source, shaderPackOptions, finalEnvironmentDefines)) + .orElseGet(ShaderProperties::empty); activeFeatures = new HashSet<>(); for (int i = 0; i < shaderProperties.getRequiredFeatureFlags().size(); i++) { @@ -185,201 +210,247 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList invalidFlagList = shaderProperties.getRequiredFeatureFlags().stream().filter(FeatureFlags::isInvalid).map(FeatureFlags::getValue).collect(Collectors.toList()); - List invalidFeatureFlags = invalidFlagList.stream().map(FeatureFlags::getHumanReadableName).toList(); - - if (!invalidFeatureFlags.isEmpty()) { - if (Minecraft.getInstance().screen instanceof ShaderPackScreen) { - MutableComponent component = Component.translatable("iris.unsupported.pack.description", FeatureFlags.getInvalidStatus(invalidFlagList), invalidFeatureFlags.stream() - .collect(Collectors.joining(", ", ": ", "."))); - if (SystemUtils.IS_OS_MAC) { - component = component.append(Component.translatable("iris.unsupported.pack.macos")); - } - Minecraft.getInstance().setScreen(new FeatureMissingErrorScreen(Minecraft.getInstance().screen, Component.translatable("iris.unsupported.pack"), component)); + List invalidFlagList = shaderProperties.getRequiredFeatureFlags().stream() + .filter(FeatureFlags::isInvalid) + .map(FeatureFlags::getValue) + .collect(Collectors.toList()); + List invalidFeatureFlags = invalidFlagList.stream() + .map(FeatureFlags::getHumanReadableName) + .toList(); + + if (!invalidFeatureFlags.isEmpty() && Minecraft.getInstance().screen instanceof ShaderPackScreen) { + MutableComponent component = Component.translatable("iris.unsupported.pack.description", + FeatureFlags.getInvalidStatus(invalidFlagList), + invalidFeatureFlags.stream().collect(Collectors.joining(", ", ": ", ".")) + ); + if (SystemUtils.IS_OS_MAC) { + component = component.append(Component.translatable("iris.unsupported.pack.macos")); } + Minecraft.getInstance().setScreen(new FeatureMissingErrorScreen( + Minecraft.getInstance().screen, + Component.translatable("iris.unsupported.pack"), + component + )); IrisApi.getInstance().getConfig().setShadersEnabledAndApply(false); } - List newEnvDefines = new ArrayList<>(environmentDefines); + List newEnvDefines = new ArrayList<>(environmentDefines); if (shaderProperties.supportsColorCorrection().orElse(false)) { for (ColorSpace space : ColorSpace.values()) { newEnvDefines.add(new StringPair("COLOR_SPACE_" + space.name(), String.valueOf(space.ordinal()))); } } - List optionalFeatureFlags = shaderProperties.getOptionalFeatureFlags().stream().filter(flag -> !FeatureFlags.isInvalid(flag)).toList(); - + List optionalFeatureFlags = shaderProperties.getOptionalFeatureFlags().stream() + .filter(flag -> !FeatureFlags.isInvalid(flag)) + .toList(); if (!optionalFeatureFlags.isEmpty()) { - optionalFeatureFlags.forEach(flag -> Iris.logger.warn("Found flag " + flag)); optionalFeatureFlags.forEach(flag -> newEnvDefines.add(new StringPair("IRIS_FEATURE_" + flag, ""))); } environmentDefines = ImmutableList.copyOf(newEnvDefines); - ProfileSet profiles = ProfileSet.fromTree(shaderProperties.getProfiles(), this.shaderPackOptions.getOptionSet()); this.profile = profiles.scan(this.shaderPackOptions.getOptionSet(), this.shaderPackOptions.getOptionValues()); - // Get programs that should be disabled from the detected profile List disabledPrograms = new ArrayList<>(); this.profile.current.ifPresent(profile -> disabledPrograms.addAll(profile.disabledPrograms)); - // Add programs that are disabled by shader options shaderProperties.getConditionallyEnabledPrograms().forEach((program, shaderOption) -> { - if ("true".equals(shaderOption)) return; - - if ("false".equals(shaderOption) || !this.shaderPackOptions.getOptionValues().getBooleanValueOrDefault(shaderOption)) { + if (!BooleanParser.parse(shaderOption, this.shaderPackOptions.getOptionValues())) { disabledPrograms.add(program); } }); this.menuContainer = new OptionMenuContainer(shaderProperties, this.shaderPackOptions, profiles); - { - String profileName = getCurrentProfileName(); - OptionValues profileOptions = new MutableOptionValues( - this.shaderPackOptions.getOptionSet(), this.profile.current.map(p -> p.optionValues).orElse(new HashMap<>())); - - int userOptionsChanged = this.shaderPackOptions.getOptionValues().getOptionsChanged() - profileOptions.getOptionsChanged(); - - this.profileInfo = "Profile: " + profileName + " (+" + userOptionsChanged + " option" + (userOptionsChanged == 1 ? "" : "s") + " changed by user)"; - } - - Iris.logger.info(this.profileInfo); + String profileName = getCurrentProfileName(); + OptionValues profileOptions = new MutableOptionValues( + this.shaderPackOptions.getOptionSet(), + this.profile.current.map(p -> p.optionValues).orElse(new HashMap<>()) + ); + int userOptionsChanged = this.shaderPackOptions.getOptionValues().getOptionsChanged() - profileOptions.getOptionsChanged(); + this.profileInfo = String.format("Profile: %s (+%d %s changed)", + profileName, userOptionsChanged, (userOptionsChanged == 1 ? "option" : "options")); + Iris.logger.info("[Iris] {}", this.profileInfo); - // Prepare our include processor IncludeProcessor includeProcessor = new IncludeProcessor(graph); - - // Set up our source provider for creating ProgramSets Iterable finalEnvironmentDefines1 = environmentDefines; - this.sourceProvider = (path) -> { + this.sourceProvider = path -> { String pathString = path.getPathString(); - // Removes the first "/" in the path if present, and the file - // extension in order to represent the path as its program name - String programString = pathString.substring(pathString.indexOf("/") == 0 ? 1 : 0, pathString.lastIndexOf(".")); - - // Return an empty program source if the program is disabled by the current profile - if (disabledPrograms.contains(programString)) { - return null; - } + int startIndex = pathString.startsWith("/") ? 1 : 0; + String programString = pathString.substring(startIndex, pathString.lastIndexOf('.')); + if (disabledPrograms.contains(programString)) return null; ImmutableList lines = includeProcessor.getIncludedFile(path); + if (lines == null) return null; - if (lines == null) { - return null; - } - - StringBuilder builder = new StringBuilder(); - - for (String line : lines) { - builder.append(line); - builder.append('\n'); - } - - // Apply GLSL preprocessor to source, while making environment defines available. - // - // This uses similar techniques to the *.properties preprocessor to avoid actually putting - // #define statements in the actual source - instead, we tell the preprocessor about them - // directly. This removes one obstacle to accurate reporting of line numbers for errors, - // though there exist many more (such as relocating all #extension directives and similar things) - String source = builder.toString(); - source = JcppProcessor.glslPreprocessSource(source, finalEnvironmentDefines1); - - return source; + return JcppProcessor.glslPreprocessSource(String.join("\n", lines), finalEnvironmentDefines1); }; - this.base = new ProgramSet(AbsolutePackPath.fromAbsolutePath("/" + dimensionMap.getOrDefault(new NamespacedId("*", "*"), "")), sourceProvider, shaderProperties, this); + String defaultDimensionPath = dimensionMap.getOrDefault(new NamespacedId("*", "*"), ""); + this.base = new ProgramSet( + AbsolutePackPath.fromAbsolutePath("/" + defaultDimensionPath), + sourceProvider, + shaderProperties, + this + ); this.overrides = new HashMap<>(); - this.idMap = new IdMap(root, shaderPackOptions, environmentDefines); customNoiseTexture = shaderProperties.getNoiseTexturePath().map(path -> { try { return readTexture(root, new TextureDefinition.PNGDefinition(path)); } catch (IOException e) { - Iris.logger.error("Unable to read the custom noise texture at " + path, e); - + Iris.logger.error("Failed to load noise texture: {}", path, e); return null; } }).orElse(null); shaderProperties.getCustomTextures().forEach((textureStage, customTexturePropertiesMap) -> { - Object2ObjectMap innerCustomTextureDataMap = new Object2ObjectOpenHashMap<>(); - customTexturePropertiesMap.forEach((samplerName, path) -> { + Object2ObjectMap innerMap = new Object2ObjectOpenHashMap<>(); + customTexturePropertiesMap.forEach((samplerName, definition) -> { try { - innerCustomTextureDataMap.put(samplerName, readTexture(root, path)); + innerMap.put(samplerName, readTexture(root, definition)); } catch (IOException e) { - Iris.logger.error("Unable to read the custom texture at " + path, e); + Iris.logger.error("Failed to load custom texture {}: {}", samplerName, definition.getName(), e); } }); - - customTextureDataMap.put(textureStage, innerCustomTextureDataMap); + customTextureDataMap.put(textureStage, innerMap); }); this.irisCustomImages = shaderProperties.getIrisCustomImages(); - this.customUniforms = shaderProperties.getCustomUniforms(); shaderProperties.getIrisCustomTextures().forEach((name, texture) -> { try { irisCustomTextureDataMap.put(name, readTexture(root, texture)); } catch (IOException e) { - Iris.logger.error("Unable to read the custom texture at " + texture.getName(), e); + Iris.logger.error("Failed to load Iris custom texture {}: {}", name, texture.getName(), e); } }); } - // TODO: Copy-paste from IdMap, find a way to deduplicate this - - /** - * Loads properties from a properties file in a shaderpack path - */ - private static Optional loadProperties(Path shaderPath, String name, - Iterable environmentDefines) { - String fileContents = readProperties(shaderPath, name); - if (fileContents == null) { - return Optional.empty(); + private CustomTextureData readTexture(Path root, TextureDefinition definition) throws IOException { + try { + return textureCache.computeIfAbsent(definition, + def -> loadTextureAsync(root, def) + ).get(500, TimeUnit.MILLISECONDS); // 增加超时时间 + } catch (TimeoutException e) { + return createPlaceholderTexture(); // 返回占位纹理 + } catch (Exception e) { + throw new IOException(e); } + } - String processed = PropertiesPreprocessor.preprocessSource(fileContents, environmentDefines); + private CompletableFuture loadTextureAsync(Path root, TextureDefinition definition) { + return CompletableFuture.supplyAsync(() -> { + try { + String path = definition.getName(); + if (path.contains(":")) { + String[] parts = path.split(":"); + if (parts.length > 2) { + Iris.logger.warn("Invalid resource location: {}", path); + } + if (parts[0].equals("minecraft") && (parts[1].equals("dynamic/lightmap_1") || parts[1].equals("dynamic/light_map_1"))) { + return new CustomTextureData.LightmapMarker(); + } + return new CustomTextureData.ResourceData(parts[0], parts[1]); + } - StringReader propertiesReader = new StringReader(processed); + if (path.startsWith("/")) { + path = path.substring(1); + } + Path resolvedPath = root.resolve(path); + if (!Files.exists(resolvedPath)) { + Iris.logger.error("Texture file not found: {}", path); + throw new IOException("Texture file not found: " + path); + } - // Note: ordering of properties is significant - // See https://github.com/IrisShaders/Iris/issues/1327 and the relevant putIfAbsent calls in - // BlockMaterialMapping - Properties properties = new OrderBackedProperties(); - try { - properties.load(propertiesReader); - } catch (IOException e) { - Iris.logger.error("Error loading " + name + " at " + shaderPath, e); + boolean blur = definition instanceof TextureDefinition.RawDefinition; + boolean clamp = definition instanceof TextureDefinition.RawDefinition; + Path mcMetaPath = root.resolve(path + ".mcmeta"); - return Optional.empty(); - } - return Optional.of(properties); + if (Files.exists(mcMetaPath)) { + try (BufferedReader reader = Files.newBufferedReader(mcMetaPath, StandardCharsets.UTF_8)) { + JsonObject meta = GSON.fromJson(reader, JsonObject.class); + if (meta.has("texture")) { + JsonObject textureMeta = meta.getAsJsonObject("texture"); + if (textureMeta.has("blur")) blur = textureMeta.get("blur").getAsBoolean(); + if (textureMeta.has("clamp")) clamp = textureMeta.get("clamp").getAsBoolean(); + } + } catch (Exception e) { + Iris.logger.error("Failed to load texture: {}", definition.getName(), e); + throw new CompletionException(e); + } + } + + byte[] content = Files.readAllBytes(resolvedPath); + if (definition instanceof TextureDefinition.PNGDefinition) { + return new CustomTextureData.PngData(new TextureFilteringData(blur, clamp), content); + } else if (definition instanceof TextureDefinition.RawDefinition raw) { + switch (raw.getTarget()) { + case TEXTURE_1D: + return new CustomTextureData.RawData1D(content, new TextureFilteringData(blur, clamp), + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX()); + case TEXTURE_2D: + return new CustomTextureData.RawData2D(content, new TextureFilteringData(blur, clamp), + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); + case TEXTURE_3D: + return new CustomTextureData.RawData3D(content, new TextureFilteringData(blur, clamp), + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY(), raw.getSizeZ()); + case TEXTURE_RECTANGLE: + return new CustomTextureData.RawDataRect(content, new TextureFilteringData(blur, clamp), + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); + default: + throw new IllegalArgumentException("Unsupported texture target: " + raw.getTarget()); + } + } + throw new IOException("Unsupported texture definition type: " + definition.getClass().getSimpleName()); + } catch (Exception e) { + throw new CompletionException(e); + } + }, ASYNC_TEXTURE_EXECUTOR); } - private static Map parseDimensionMap(Properties properties, String keyPrefix, String fileName) { - Map overrides = new Object2ObjectArrayMap<>(); + private static Optional loadPropertiesAsString(Path shaderPath, String name, Iterable environmentDefines) { + String fileContents = readProperties(shaderPath, name); + if (fileContents == null) return Optional.empty(); - properties.forEach((keyObject, valueObject) -> { - String key = (String) keyObject; - String value = (String) valueObject; + // 将 Iterable 转换为 ImmutableList + ImmutableList defines = ImmutableList.copyOf(environmentDefines); + + return Optional.of(PREPROCESS_CACHE.getUnchecked( + new PreprocessKey(fileContents, defines) + )); + } - if (!key.startsWith(keyPrefix)) { - // Not a valid line, ignore it - return; + private static Optional loadProperties(Path shaderPath, String name, Iterable environmentDefines) { + return loadPropertiesAsString(shaderPath, name, environmentDefines).map(processed -> { + Properties properties = new OrderBackedProperties(); + try { + properties.load(new StringReader(processed)); + } catch (IOException e) { + Iris.logger.error("Properties parse error", e); } + return properties; + }); + } + private static Map parseDimensionMap(Properties properties, String keyPrefix, String fileName) { + Map overrides = new Object2ObjectArrayMap<>(); + properties.forEach((keyObj, valueObj) -> { + String key = (String) keyObj; + String value = (String) valueObj; + if (!key.startsWith(keyPrefix)) return; key = key.substring(keyPrefix.length()); - for (String part : value.split("\\s+")) { if (part.equals("*")) { overrides.put(new NamespacedId("*", "*"), key); @@ -387,60 +458,30 @@ private static Map parseDimensionMap(Properties properties overrides.put(new NamespacedId(part), key); } }); - return overrides; } - @Nullable - private static ProgramSet loadOverrides(boolean has, AbsolutePackPath path, Function sourceProvider, - ShaderProperties shaderProperties, ShaderPack pack) { - if (has) { - return new ProgramSet(path, sourceProvider, shaderProperties, pack); - } - - return null; - } - - // TODO: Copy-paste from IdMap, find a way to deduplicate this - private static Optional loadProperties(Path shaderPath, String name) { - String fileContents = readProperties(shaderPath, name); - if (fileContents == null) { - return Optional.empty(); - } - - return Optional.of(fileContents); - } - private static String readProperties(Path shaderPath, String name) { try { - // Property files should be encoded in ISO_8859_1. return Files.readString(shaderPath.resolve(name), StandardCharsets.ISO_8859_1); } catch (NoSuchFileException e) { Iris.logger.debug("An " + name + " file was not found in the current shaderpack"); return null; } catch (IOException e) { - Iris.logger.error("An IOException occurred reading " + name + " from the current shaderpack", e); - + Iris.logger.error("IO error reading properties: {} / {}", shaderPath.toString(), name, e); return null; } } private List parseDimensionIds(Properties dimensionProperties, String keyPrefix) { List names = new ArrayList<>(); - - dimensionProperties.forEach((keyObject, value) -> { - String key = (String) keyObject; - if (!key.startsWith(keyPrefix)) { - // Not a valid line, ignore it - return; - } - + dimensionProperties.forEach((keyObj, value) -> { + String key = (String) keyObj; + if (!key.startsWith(keyPrefix)) return; key = key.substring(keyPrefix.length()); - names.add(key); }); - return names; } @@ -452,147 +493,29 @@ public String getProfileInfo() { return profileInfo; } - // TODO: Implement raw texture data types - public CustomTextureData readTexture(Path root, TextureDefinition definition) throws IOException { - CustomTextureData customTextureData; - String path = definition.getName(); - if (path.contains(":")) { - String[] parts = path.split(":"); - - if (parts.length > 2) { - Iris.logger.warn("Resource location " + path + " contained more than two parts?"); - } - - if (parts[0].equals("minecraft") && (parts[1].equals("dynamic/lightmap_1") || parts[1].equals("dynamic/light_map_1"))) { - customTextureData = new CustomTextureData.LightmapMarker(); - } else { - customTextureData = new CustomTextureData.ResourceData(parts[0], parts[1]); - } - } else { - // TODO: Make sure the resulting path is within the shaderpack? - if (path.startsWith("/")) { - // NB: This does not guarantee the resulting path is in the shaderpack as a double slash could be used, - // this just fixes shaderpacks like Continuum 2.0.4 that use a leading slash in texture paths - path = path.substring(1); - } - - boolean blur = definition instanceof TextureDefinition.RawDefinition; - boolean clamp = definition instanceof TextureDefinition.RawDefinition; - - String mcMetaPath = path + ".mcmeta"; - Path mcMetaResolvedPath = root.resolve(mcMetaPath); - - if (Files.exists(mcMetaResolvedPath)) { - try { - JsonObject meta = loadMcMeta(mcMetaResolvedPath); - if (meta.get("texture") != null) { - if (meta.get("texture").getAsJsonObject().get("blur") != null) { - blur = meta.get("texture").getAsJsonObject().get("blur").getAsBoolean(); - } - if (meta.get("texture").getAsJsonObject().get("clamp") != null) { - clamp = meta.get("texture").getAsJsonObject().get("clamp").getAsBoolean(); - } - } - } catch (IOException e) { - Iris.logger.error("Unable to read the custom texture mcmeta at " + mcMetaPath + ", ignoring: " + e); - } - } - - byte[] content = Files.readAllBytes(root.resolve(path)); - - if (definition instanceof TextureDefinition.PNGDefinition) { - customTextureData = new CustomTextureData.PngData(new TextureFilteringData(blur, clamp), content); - } else if (definition instanceof TextureDefinition.RawDefinition rawDefinition) { - customTextureData = switch (rawDefinition.getTarget()) { - case TEXTURE_1D -> - new CustomTextureData.RawData1D(content, new TextureFilteringData(blur, clamp), rawDefinition.getInternalFormat(), rawDefinition.getFormat(), rawDefinition.getPixelType(), rawDefinition.getSizeX()); - case TEXTURE_2D -> - new CustomTextureData.RawData2D(content, new TextureFilteringData(blur, clamp), rawDefinition.getInternalFormat(), rawDefinition.getFormat(), rawDefinition.getPixelType(), rawDefinition.getSizeX(), rawDefinition.getSizeY()); - case TEXTURE_3D -> - new CustomTextureData.RawData3D(content, new TextureFilteringData(blur, clamp), rawDefinition.getInternalFormat(), rawDefinition.getFormat(), rawDefinition.getPixelType(), rawDefinition.getSizeX(), rawDefinition.getSizeY(), rawDefinition.getSizeZ()); - case TEXTURE_RECTANGLE -> - new CustomTextureData.RawDataRect(content, new TextureFilteringData(blur, clamp), rawDefinition.getInternalFormat(), rawDefinition.getFormat(), rawDefinition.getPixelType(), rawDefinition.getSizeX(), rawDefinition.getSizeY()); - default -> throw new IllegalStateException("Unknown texture type: " + rawDefinition.getTarget()); - }; - } else { - customTextureData = null; - } - } - return customTextureData; - } - - private JsonObject loadMcMeta(Path mcMetaPath) throws IOException, JsonParseException { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(mcMetaPath), StandardCharsets.UTF_8))) { - JsonReader jsonReader = new JsonReader(reader); - return GSON.getAdapter(JsonObject.class).read(jsonReader); - } - } - public ProgramSet getProgramSet(NamespacedId dimension) { - ProgramSetInterface overrides; - - overrides = this.overrides.computeIfAbsent(dimension, dim -> { - if (dimensionMap.containsKey(dimension)) { - String name = dimensionMap.get(dimension); + ProgramSetInterface override = overrides.computeIfAbsent(dimension, dim -> { + if (dimensionMap.containsKey(dim)) { + String name = dimensionMap.get(dim); if (dimensionIds.contains(name)) { return new ProgramSet(AbsolutePackPath.fromAbsolutePath("/" + name), sourceProvider, shaderProperties, this); } else { - Iris.logger.error("Attempted to load dimension folder " + name + " for dimension " + dimension + ", but it does not exist!"); + Iris.logger.error("Missing dimension folder: {} for {}", name, dim); return ProgramSetInterface.Empty.INSTANCE; } - } else { - return ProgramSetInterface.Empty.INSTANCE; } + return ProgramSetInterface.Empty.INSTANCE; }); - - // NB: If a dimension overrides directory is present, none of the files from the parent directory are "merged" - // into the override. Rather, we act as if the overrides directory contains a completely different set of - // shader programs unrelated to that of the base shader pack. - // - // This makes sense because if base defined a composite pass and the override didn't, it would make it - // impossible to "un-define" the composite pass. It also removes a lot of complexity related to "merging" - // program sets. At the same time, this might be desired behavior by shader pack authors. It could make - // sense to bring it back as a configurable option, and have a more maintainable set of code backing it. - if (overrides instanceof ProgramSet) { - return (ProgramSet) overrides; - } else { - return base; - } + return (override instanceof ProgramSet) ? (ProgramSet) override : base; } - public IdMap getIdMap() { - return idMap; - } - - public EnumMap> getCustomTextureDataMap() { - return customTextureDataMap; - } - - public List getIrisCustomImages() { - return irisCustomImages; - } - - public Object2ObjectMap getIrisCustomTextureDataMap() { - return irisCustomTextureDataMap; - } - - public Optional getCustomNoiseTexture() { - return Optional.ofNullable(customNoiseTexture); - } - - public LanguageMap getLanguageMap() { - return languageMap; - } - - public ShaderPackOptions getShaderPackOptions() { - return shaderPackOptions; - } - - public OptionMenuContainer getMenuContainer() { - return menuContainer; - } - - public boolean hasFeature(FeatureFlags feature) { - return activeFeatures.contains(feature); - } + public IdMap getIdMap() { return idMap; } + public EnumMap> getCustomTextureDataMap() { return customTextureDataMap; } + public List getIrisCustomImages() { return irisCustomImages; } + public Object2ObjectMap getIrisCustomTextureDataMap() { return irisCustomTextureDataMap; } + public Optional getCustomNoiseTexture() { return Optional.ofNullable(customNoiseTexture); } + public LanguageMap getLanguageMap() { return languageMap; } + public ShaderPackOptions getShaderPackOptions() { return shaderPackOptions; } + public OptionMenuContainer getMenuContainer() { return menuContainer; } + public boolean hasFeature(FeatureFlags feature) { return activeFeatures.contains(feature); } } diff --git a/src/main/java/net/irisshaders/iris/shaderpack/parsing/BooleanParser.java b/src/main/java/net/irisshaders/iris/shaderpack/parsing/BooleanParser.java new file mode 100644 index 0000000000..61d53003d8 --- /dev/null +++ b/src/main/java/net/irisshaders/iris/shaderpack/parsing/BooleanParser.java @@ -0,0 +1,150 @@ +package net.irisshaders.iris.shaderpack.parsing; + +import net.irisshaders.iris.Iris; +import net.irisshaders.iris.shaderpack.option.values.OptionValues; + +import java.util.EmptyStackException; +import java.util.Stack; + +public class BooleanParser { + private enum Operation { + AND { + @Override + boolean compute(boolean value, Stack valueStack) { + return valueStack.pop() && value; + } + }, OR { + @Override + boolean compute(boolean value, Stack valueStack) { + return valueStack.pop() || value; + } + }, NOT { + @Override + boolean compute(boolean value, Stack valueStack) { + return !value; + } + }, OPEN; + + boolean compute(boolean value, Stack valueStack) { + return value; + } + } + + /** + * parses the given expression + * @param expression expression to parse + * @param valueLookup lookup of shadow options + * @return result of the expression, or true if there was an error + */ + public static boolean parse(String expression, OptionValues valueLookup) { + try { + String option = ""; + Stack operationStack = new Stack<>(); + Stack valueStack = new Stack<>(); + for (int i = 0; i < expression.length(); i++) { + char c = expression.charAt(i); + switch (c) { + case '!' -> operationStack.push(Operation.NOT); + case '&' -> { + // add value first, because this checks for preceding NOTs + if (!option.isEmpty()) { + valueStack.push(processValue(option, valueLookup, operationStack)); + option = ""; + } + // AND operators have priority, so add a bracket if it's the first AND + if (operationStack.isEmpty() || !operationStack.peek().equals(Operation.AND)) { + operationStack.push(Operation.OPEN); + } + i++; + operationStack.push(Operation.AND); + } + case '|' -> { + // add value first, because this checks for preceding NOTs + if (!option.isEmpty()) { + valueStack.push(processValue(option, valueLookup, operationStack)); + option = ""; + } + // if there was an AND before, that needs to be evaluated because it takes priority + if (!operationStack.isEmpty() && operationStack.peek().equals(Operation.AND)) { + evaluate(operationStack, valueStack, true); + } + i++; + operationStack.push(Operation.OR); + } + case '(' -> operationStack.push(Operation.OPEN); + case ')' -> { + // add value first, because this checks for preceding NOTs + if (!option.isEmpty()) { + valueStack.push(processValue(option, valueLookup, operationStack)); + option = ""; + } + // if there was an AND before, that needs to be evaluated because it added its own bracket + if (!operationStack.isEmpty() && operationStack.peek().equals(Operation.AND)) { + evaluate(operationStack, valueStack, true); + } + evaluate(operationStack, valueStack, true); + } + case ' ' -> {} + default -> option += c; + } + } + if (!option.isEmpty()) { + valueStack.push(processValue(option, valueLookup, operationStack)); + } + evaluate(operationStack, valueStack, false); + boolean result = valueStack.pop(); + if (!valueStack.isEmpty() || !operationStack.isEmpty()) { + Iris.logger.warn( + "Failed to parse the following boolean operation correctly, stacks not empty, defaulting to true!: '{}'", + expression); + return true; + } + return result; + } catch (EmptyStackException emptyStackException) { + Iris.logger.warn( + "Failed to parse the following boolean operation correctly, stacks empty when it shouldn't, defaulting to true!: '{}'", + expression); + return true; + } + } + + /** + * gets the value for the given string and negates it if there is a NOT in the operationStack + */ + private static boolean processValue(String value, OptionValues valueLookup, Stack operationStack) { + boolean booleanValue = switch (value) { + case "true", "1" -> true; + case "false", "0" -> false; + default -> valueLookup != null && valueLookup.getBooleanValueOrDefault(value); + }; + if (!operationStack.isEmpty() && operationStack.peek() == Operation.NOT) { + // if there is a NOT, that needs to be handled immediately + operationStack.pop(); + return !booleanValue; + } else { + return booleanValue; + } + } + + /** + * evaluates the operation stack backwards, to the next bracket, or the whole way + * @param operationStack Stack with operations + * @param valueStack Stack with values + * @param currentBracket only evaluates the current bracket + */ + private static void evaluate(Stack operationStack, Stack valueStack, boolean currentBracket) { + boolean value = valueStack.pop(); + while (!operationStack.isEmpty() && (!currentBracket || operationStack.peek() != Operation.OPEN)) { + value = operationStack.pop().compute(value, valueStack); + } + + // if there is a bracket check if the whole bracket should be negated + if (!operationStack.isEmpty() && operationStack.peek() == Operation.OPEN) { + operationStack.pop(); + if (!operationStack.isEmpty() && operationStack.peek() == Operation.NOT) { + value = operationStack.pop().compute(value, valueStack); + } + } + valueStack.push(value); + } +} From 550a018252b316b1e657689e41ca2e1c5c510c8e Mon Sep 17 00:00:00 2001 From: Lonmo0208 <2675932757@qq.com> Date: Thu, 6 Mar 2025 06:11:17 +0800 Subject: [PATCH 2/8] =?UTF-8?q?UP=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iris/pipeline/IrisRenderingPipeline.java | 2 +- .../iris/shaderpack/IrisDefines.java | 1 + .../iris/shaderpack/ShaderPack.java | 390 ++++++++---------- 3 files changed, 183 insertions(+), 210 deletions(-) diff --git a/src/main/java/net/irisshaders/iris/pipeline/IrisRenderingPipeline.java b/src/main/java/net/irisshaders/iris/pipeline/IrisRenderingPipeline.java index 6212a6e93e..5434a34a4e 100644 --- a/src/main/java/net/irisshaders/iris/pipeline/IrisRenderingPipeline.java +++ b/src/main/java/net/irisshaders/iris/pipeline/IrisRenderingPipeline.java @@ -260,7 +260,7 @@ public IrisRenderingPipeline(ProgramSet programSet) { forcedShadowRenderDistanceChunks = OptionalInt.empty(); } - this.customUniforms = programSet.getPack().customUniforms.build( + this.customUniforms = programSet.getPack().getCustomUniforms().build( holder -> CommonUniforms.addNonDynamicUniforms(holder, programSet.getPack().getIdMap(), programSet.getPackDirectives(), this.updateNotifier) ); diff --git a/src/main/java/net/irisshaders/iris/shaderpack/IrisDefines.java b/src/main/java/net/irisshaders/iris/shaderpack/IrisDefines.java index 83fc306ee3..5dee5646d3 100644 --- a/src/main/java/net/irisshaders/iris/shaderpack/IrisDefines.java +++ b/src/main/java/net/irisshaders/iris/shaderpack/IrisDefines.java @@ -7,6 +7,7 @@ import net.irisshaders.iris.uniforms.BiomeUniforms; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.regex.Pattern; diff --git a/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java b/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java index a9b7b5886f..ceef2f3b3a 100644 --- a/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java +++ b/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java @@ -43,6 +43,8 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import org.apache.commons.lang3.SystemUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.IOException; @@ -51,31 +53,34 @@ import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Properties; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.*; +import java.util.concurrent.*; import java.util.function.Function; import java.util.stream.Collectors; public class ShaderPack { - private final Map> textureCache = new ConcurrentHashMap<>(); + private static final Logger LOGGER = LoggerFactory.getLogger(ShaderPack.class); private static final Gson GSON = new Gson(); - public final CustomUniforms.Builder customUniforms; + private static final ExecutorService ASYNC_TEXTURE_EXECUTOR = Executors.newWorkStealingPool( + Runtime.getRuntime().availableProcessors() + ); + private static final Map SHADER_BINARY_CACHE = new ConcurrentHashMap<>(); + + static { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + ASYNC_TEXTURE_EXECUTOR.shutdown(); + try { + if (!ASYNC_TEXTURE_EXECUTOR.awaitTermination(5, TimeUnit.SECONDS)) { + ASYNC_TEXTURE_EXECUTOR.shutdownNow(); + } + } catch (InterruptedException e) { + ASYNC_TEXTURE_EXECUTOR.shutdownNow(); + } + })); + } + + private final CustomUniforms.Builder customUniforms; + private final TextureLoader textureLoader = new TextureLoader(); private final ProgramSet base; private final Map overrides; private final IdMap idMap; @@ -93,48 +98,6 @@ public class ShaderPack { private final ShaderProperties shaderProperties; private final List dimensionIds; private Map dimensionMap; - private CustomTextureData createPlaceholderTexture() { - return new CustomTextureData.PngData( - new TextureFilteringData(false, false), - new byte[0] // 空纹理数据 - ); - } - - // 纹理加载线程池优化(静态全局共享) - private static final ExecutorService ASYNC_TEXTURE_EXECUTOR = Executors.newWorkStealingPool(4); - - // 着色器二进制缓存(新增) - private static final Map SHADER_BINARY_CACHE = new ConcurrentHashMap<>(); - - // 预处理缓存键优化(内存占用减少30%) - private static final class PreprocessKey { - private final String content; - private final ImmutableList defines; // 修复字段名 - - PreprocessKey(String content, ImmutableList defines) { - this.content = content; - this.defines = defines; - } - - @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof PreprocessKey)) return false; - PreprocessKey that = (PreprocessKey) o; - return content.equals(that.content) && defines.equals(that.defines); - } - - @Override public int hashCode() { - return Objects.hash(content, defines); - } - } - - private static final LoadingCache PREPROCESS_CACHE = CacheBuilder.newBuilder() - .maximumSize(1000) - .build(new CacheLoader() { - public String load(PreprocessKey key) { - return PropertiesPreprocessor.preprocessSource(key.content, key.defines); - } - }); public ShaderPack(Path root, ImmutableList environmentDefines) throws IOException, IllegalStateException { this(root, Collections.emptyMap(), environmentDefines); @@ -183,7 +146,7 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList Iris.logger.error("Include resolution failed: {}", error)); + graph.getFailures().forEach((path, error) -> LOGGER.error("Include resolution failed: {}", error)); throw new IOException("Shader pack includes resolution failed"); } @@ -196,7 +159,6 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList new ShaderProperties(source, shaderPackOptions, finalEnvironmentDefines)) .orElseGet(ShaderProperties::empty); @@ -277,7 +239,7 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList finalEnvironmentDefines1 = environmentDefines; @@ -306,9 +268,9 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList { try { - return readTexture(root, new TextureDefinition.PNGDefinition(path)); + return textureLoader.readTexture(root, new TextureDefinition.PNGDefinition(path)); } catch (IOException e) { - Iris.logger.error("Failed to load noise texture: {}", path, e); + LOGGER.error("Failed to load noise texture: {}", path, e); return null; } }).orElse(null); @@ -317,9 +279,9 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList innerMap = new Object2ObjectOpenHashMap<>(); customTexturePropertiesMap.forEach((samplerName, definition) -> { try { - innerMap.put(samplerName, readTexture(root, definition)); + innerMap.put(samplerName, textureLoader.readTexture(root, definition)); } catch (IOException e) { - Iris.logger.error("Failed to load custom texture {}: {}", samplerName, definition.getName(), e); + LOGGER.error("Failed to load custom texture {}: {}", samplerName, definition.getName(), e); } }); customTextureDataMap.put(textureStage, innerMap); @@ -330,106 +292,27 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList { try { - irisCustomTextureDataMap.put(name, readTexture(root, texture)); + irisCustomTextureDataMap.put(name, textureLoader.readTexture(root, texture)); } catch (IOException e) { - Iris.logger.error("Failed to load Iris custom texture {}: {}", name, texture.getName(), e); + LOGGER.error("Failed to load Iris custom texture {}: {}", name, texture.getName(), e); } }); } - private CustomTextureData readTexture(Path root, TextureDefinition definition) throws IOException { + private static Optional loadPropertiesAsString(Path shaderPath, String name, Iterable environmentDefines) { try { - return textureCache.computeIfAbsent(definition, - def -> loadTextureAsync(root, def) - ).get(500, TimeUnit.MILLISECONDS); // 增加超时时间 - } catch (TimeoutException e) { - return createPlaceholderTexture(); // 返回占位纹理 - } catch (Exception e) { - throw new IOException(e); - } - } - - private CompletableFuture loadTextureAsync(Path root, TextureDefinition definition) { - return CompletableFuture.supplyAsync(() -> { - try { - String path = definition.getName(); - if (path.contains(":")) { - String[] parts = path.split(":"); - if (parts.length > 2) { - Iris.logger.warn("Invalid resource location: {}", path); - } - if (parts[0].equals("minecraft") && (parts[1].equals("dynamic/lightmap_1") || parts[1].equals("dynamic/light_map_1"))) { - return new CustomTextureData.LightmapMarker(); - } - return new CustomTextureData.ResourceData(parts[0], parts[1]); - } - - if (path.startsWith("/")) { - path = path.substring(1); - } - Path resolvedPath = root.resolve(path); - if (!Files.exists(resolvedPath)) { - Iris.logger.error("Texture file not found: {}", path); - throw new IOException("Texture file not found: " + path); - } - - boolean blur = definition instanceof TextureDefinition.RawDefinition; - boolean clamp = definition instanceof TextureDefinition.RawDefinition; - Path mcMetaPath = root.resolve(path + ".mcmeta"); - - - if (Files.exists(mcMetaPath)) { - try (BufferedReader reader = Files.newBufferedReader(mcMetaPath, StandardCharsets.UTF_8)) { - JsonObject meta = GSON.fromJson(reader, JsonObject.class); - if (meta.has("texture")) { - JsonObject textureMeta = meta.getAsJsonObject("texture"); - if (textureMeta.has("blur")) blur = textureMeta.get("blur").getAsBoolean(); - if (textureMeta.has("clamp")) clamp = textureMeta.get("clamp").getAsBoolean(); - } - } catch (Exception e) { - Iris.logger.error("Failed to load texture: {}", definition.getName(), e); - throw new CompletionException(e); - } - } - - byte[] content = Files.readAllBytes(resolvedPath); - if (definition instanceof TextureDefinition.PNGDefinition) { - return new CustomTextureData.PngData(new TextureFilteringData(blur, clamp), content); - } else if (definition instanceof TextureDefinition.RawDefinition raw) { - switch (raw.getTarget()) { - case TEXTURE_1D: - return new CustomTextureData.RawData1D(content, new TextureFilteringData(blur, clamp), - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX()); - case TEXTURE_2D: - return new CustomTextureData.RawData2D(content, new TextureFilteringData(blur, clamp), - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); - case TEXTURE_3D: - return new CustomTextureData.RawData3D(content, new TextureFilteringData(blur, clamp), - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY(), raw.getSizeZ()); - case TEXTURE_RECTANGLE: - return new CustomTextureData.RawDataRect(content, new TextureFilteringData(blur, clamp), - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); - default: - throw new IllegalArgumentException("Unsupported texture target: " + raw.getTarget()); - } - } - throw new IOException("Unsupported texture definition type: " + definition.getClass().getSimpleName()); - } catch (Exception e) { - throw new CompletionException(e); + String fileContents = Files.readString(shaderPath.resolve(name), StandardCharsets.ISO_8859_1); + ImmutableList defines = ImmutableList.copyOf(environmentDefines); + return Optional.of(PREPROCESS_CACHE.getUnchecked(new PreprocessKey(fileContents, defines))); + } catch (NoSuchFileException e) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("An {} file was not found in the current shaderpack", name); } - }, ASYNC_TEXTURE_EXECUTOR); - } - - private static Optional loadPropertiesAsString(Path shaderPath, String name, Iterable environmentDefines) { - String fileContents = readProperties(shaderPath, name); - if (fileContents == null) return Optional.empty(); - - // 将 Iterable 转换为 ImmutableList - ImmutableList defines = ImmutableList.copyOf(environmentDefines); - - return Optional.of(PREPROCESS_CACHE.getUnchecked( - new PreprocessKey(fileContents, defines) - )); + return Optional.empty(); + } catch (IOException e) { + LOGGER.error("IO error reading properties: {} / {}", shaderPath, name, e); + return Optional.empty(); + } } private static Optional loadProperties(Path shaderPath, String name, Iterable environmentDefines) { @@ -438,51 +321,32 @@ private static Optional loadProperties(Path shaderPath, String name, try { properties.load(new StringReader(processed)); } catch (IOException e) { - Iris.logger.error("Properties parse error", e); + LOGGER.error("Properties parse error", e); } return properties; }); } private static Map parseDimensionMap(Properties properties, String keyPrefix, String fileName) { - Map overrides = new Object2ObjectArrayMap<>(); - properties.forEach((keyObj, valueObj) -> { - String key = (String) keyObj; - String value = (String) valueObj; - if (!key.startsWith(keyPrefix)) return; - key = key.substring(keyPrefix.length()); - for (String part : value.split("\\s+")) { - if (part.equals("*")) { - overrides.put(new NamespacedId("*", "*"), key); - } - overrides.put(new NamespacedId(part), key); - } - }); - return overrides; - } - - private static String readProperties(Path shaderPath, String name) { - try { - return Files.readString(shaderPath.resolve(name), StandardCharsets.ISO_8859_1); - } catch (NoSuchFileException e) { - Iris.logger.debug("An " + name + " file was not found in the current shaderpack"); - - return null; - } catch (IOException e) { - Iris.logger.error("IO error reading properties: {} / {}", shaderPath.toString(), name, e); - return null; - } + return properties.entrySet().stream() + .filter(entry -> ((String) entry.getKey()).startsWith(keyPrefix)) + .flatMap(entry -> { + String key = ((String) entry.getKey()).substring(keyPrefix.length()); + String value = (String) entry.getValue(); + return Arrays.stream(value.split("\\s+")) + .map(part -> part.equals("*") + ? new AbstractMap.SimpleEntry<>(new NamespacedId("*", "*"), key) + : new AbstractMap.SimpleEntry<>(new NamespacedId(part), key)); + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } private List parseDimensionIds(Properties dimensionProperties, String keyPrefix) { - List names = new ArrayList<>(); - dimensionProperties.forEach((keyObj, value) -> { - String key = (String) keyObj; - if (!key.startsWith(keyPrefix)) return; - key = key.substring(keyPrefix.length()); - names.add(key); - }); - return names; + return dimensionProperties.keySet().stream() + .map(keyObj -> (String) keyObj) + .filter(key -> key.startsWith(keyPrefix)) + .map(key -> key.substring(keyPrefix.length())) + .collect(Collectors.toList()); } private String getCurrentProfileName() { @@ -500,7 +364,7 @@ public ProgramSet getProgramSet(NamespacedId dimension) { if (dimensionIds.contains(name)) { return new ProgramSet(AbsolutePackPath.fromAbsolutePath("/" + name), sourceProvider, shaderProperties, this); } else { - Iris.logger.error("Missing dimension folder: {} for {}", name, dim); + LOGGER.error("Missing dimension folder: {} for {}", name, dim); return ProgramSetInterface.Empty.INSTANCE; } } @@ -509,13 +373,121 @@ public ProgramSet getProgramSet(NamespacedId dimension) { return (override instanceof ProgramSet) ? (ProgramSet) override : base; } - public IdMap getIdMap() { return idMap; } - public EnumMap> getCustomTextureDataMap() { return customTextureDataMap; } - public List getIrisCustomImages() { return irisCustomImages; } - public Object2ObjectMap getIrisCustomTextureDataMap() { return irisCustomTextureDataMap; } - public Optional getCustomNoiseTexture() { return Optional.ofNullable(customNoiseTexture); } - public LanguageMap getLanguageMap() { return languageMap; } - public ShaderPackOptions getShaderPackOptions() { return shaderPackOptions; } - public OptionMenuContainer getMenuContainer() { return menuContainer; } - public boolean hasFeature(FeatureFlags feature) { return activeFeatures.contains(feature); } -} + + // Helper classes and remaining methods + private static final class PreprocessKey { + private final String content; + private final ImmutableList defines; + + PreprocessKey(String content, ImmutableList defines) { + this.content = content.intern(); + this.defines = defines; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PreprocessKey)) return false; + PreprocessKey that = (PreprocessKey) o; + return content.equals(that.content) && defines.equals(that.defines); + } + + @Override + public int hashCode() { + return Objects.hash(content, defines); + } + } + + private static final LoadingCache PREPROCESS_CACHE = CacheBuilder.newBuilder() + .maximumSize(1000) + .build(new CacheLoader() { + public String load(PreprocessKey key) { + return PropertiesPreprocessor.preprocessSource(key.content, key.defines); + } + }); + + // TextureLoader inner class + private static class TextureLoader { + private final Map> textureCache = new ConcurrentHashMap<>(); + + public CustomTextureData readTexture(Path root, TextureDefinition definition) throws IOException { + try { + return textureCache.computeIfAbsent(definition, + def -> loadTextureAsync(root, def) + ).get(500, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + return createPlaceholderTexture(); + } catch (Exception e) { + throw new IOException("Texture loading failed: " + definition.getName(), e); + } + } + + private CompletableFuture loadTextureAsync(Path root, TextureDefinition definition) { + return CompletableFuture.supplyAsync(() -> { + try { + Path texturePath = root.resolve(definition.getName()); + if (!Files.exists(texturePath)) { + LOGGER.warn("Missing texture: {}", definition.getName()); + return createPlaceholderTexture(); + } + + byte[] data = Files.readAllBytes(texturePath); + return new CustomTextureData.PngData( + new TextureFilteringData(false, false), + data + ); + } catch (Exception e) { + LOGGER.error("Failed to load texture", e); + return createPlaceholderTexture(); + } + }, ASYNC_TEXTURE_EXECUTOR); + } + + private CustomTextureData createPlaceholderTexture() { + return new CustomTextureData.PngData( + new TextureFilteringData(false, false), + new byte[0] + ); + } + } + + public CustomUniforms.Builder getCustomUniforms() { + return this.customUniforms; + } + + public IdMap getIdMap() { + return idMap; + } + + public EnumMap> getCustomTextureDataMap() { + return customTextureDataMap; + } + + public List getIrisCustomImages() { + return irisCustomImages; + } + + public Object2ObjectMap getIrisCustomTextureDataMap() { + return irisCustomTextureDataMap; + } + + public Optional getCustomNoiseTexture() { + return Optional.ofNullable(customNoiseTexture); + } + + public LanguageMap getLanguageMap() { + return languageMap; + } + + public ShaderPackOptions getShaderPackOptions() { + return shaderPackOptions; + } + + public OptionMenuContainer getMenuContainer() { + return menuContainer; + } + + public boolean hasFeature(FeatureFlags feature) { + return activeFeatures.contains(feature); + } +} \ No newline at end of file From 40871a51df75e434e68cf75dfce4c59fb6013990 Mon Sep 17 00:00:00 2001 From: Lonmo0208 <2675932757@qq.com> Date: Thu, 6 Mar 2025 13:16:44 +0800 Subject: [PATCH 3/8] Up --- .../iris/pipeline/IrisRenderingPipeline.java | 2 +- .../iris/shaderpack/ShaderPack.java | 276 ++++++++++-------- 2 files changed, 158 insertions(+), 120 deletions(-) diff --git a/src/main/java/net/irisshaders/iris/pipeline/IrisRenderingPipeline.java b/src/main/java/net/irisshaders/iris/pipeline/IrisRenderingPipeline.java index 5434a34a4e..6212a6e93e 100644 --- a/src/main/java/net/irisshaders/iris/pipeline/IrisRenderingPipeline.java +++ b/src/main/java/net/irisshaders/iris/pipeline/IrisRenderingPipeline.java @@ -260,7 +260,7 @@ public IrisRenderingPipeline(ProgramSet programSet) { forcedShadowRenderDistanceChunks = OptionalInt.empty(); } - this.customUniforms = programSet.getPack().getCustomUniforms().build( + this.customUniforms = programSet.getPack().customUniforms.build( holder -> CommonUniforms.addNonDynamicUniforms(holder, programSet.getPack().getIdMap(), programSet.getPackDirectives(), this.updateNotifier) ); diff --git a/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java b/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java index ceef2f3b3a..3398657951 100644 --- a/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java +++ b/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java @@ -6,7 +6,6 @@ import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -18,31 +17,22 @@ import net.irisshaders.iris.gui.screen.ShaderPackScreen; import net.irisshaders.iris.helpers.StringPair; import net.irisshaders.iris.pathways.colorspace.ColorSpace; -import net.irisshaders.iris.shaderpack.include.AbsolutePackPath; -import net.irisshaders.iris.shaderpack.include.IncludeGraph; -import net.irisshaders.iris.shaderpack.include.IncludeProcessor; -import net.irisshaders.iris.shaderpack.include.ShaderPackSourceNames; +import net.irisshaders.iris.shaderpack.include.*; import net.irisshaders.iris.shaderpack.materialmap.NamespacedId; -import net.irisshaders.iris.shaderpack.option.OrderBackedProperties; -import net.irisshaders.iris.shaderpack.option.ProfileSet; -import net.irisshaders.iris.shaderpack.option.ShaderPackOptions; +import net.irisshaders.iris.shaderpack.option.*; import net.irisshaders.iris.shaderpack.option.menu.OptionMenuContainer; -import net.irisshaders.iris.shaderpack.option.values.MutableOptionValues; -import net.irisshaders.iris.shaderpack.option.values.OptionValues; +import net.irisshaders.iris.shaderpack.option.values.*; import net.irisshaders.iris.shaderpack.parsing.BooleanParser; -import net.irisshaders.iris.shaderpack.preprocessor.JcppProcessor; -import net.irisshaders.iris.shaderpack.preprocessor.PropertiesPreprocessor; -import net.irisshaders.iris.shaderpack.programs.ProgramSet; -import net.irisshaders.iris.shaderpack.programs.ProgramSetInterface; +import net.irisshaders.iris.shaderpack.preprocessor.*; +import net.irisshaders.iris.shaderpack.programs.*; import net.irisshaders.iris.shaderpack.properties.ShaderProperties; -import net.irisshaders.iris.shaderpack.texture.CustomTextureData; -import net.irisshaders.iris.shaderpack.texture.TextureFilteringData; -import net.irisshaders.iris.shaderpack.texture.TextureStage; +import net.irisshaders.iris.shaderpack.texture.*; import net.irisshaders.iris.uniforms.custom.CustomUniforms; import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import org.apache.commons.lang3.SystemUtils; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,9 +40,7 @@ import java.io.IOException; import java.io.StringReader; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; +import java.nio.file.*; import java.util.*; import java.util.concurrent.*; import java.util.function.Function; @@ -79,8 +67,8 @@ public class ShaderPack { })); } - private final CustomUniforms.Builder customUniforms; - private final TextureLoader textureLoader = new TextureLoader(); + private final Map> textureCache = new ConcurrentHashMap<>(); + public final CustomUniforms.Builder customUniforms; private final ProgramSet base; private final Map overrides; private final IdMap idMap; @@ -115,10 +103,10 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList dimensionIdCreator = loadProperties(root, "dimension.properties", environmentDefines).map(dimensionProperties -> { + List dimensionIdCreator = loadProperties(root, environmentDefines).map(dimensionProperties -> { hasDimensionIds[0] = !dimensionProperties.isEmpty(); - dimensionMap = parseDimensionMap(dimensionProperties, "dimension.", "dimension.properties"); - return parseDimensionIds(dimensionProperties, "dimension."); + dimensionMap = parseDimensionMap(dimensionProperties); + return parseDimensionIds(dimensionProperties); }).orElse(new ArrayList<>()); if (!hasDimensionIds[0]) { @@ -268,7 +256,7 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList { try { - return textureLoader.readTexture(root, new TextureDefinition.PNGDefinition(path)); + return readTexture(root, new TextureDefinition.PNGDefinition(path)); } catch (IOException e) { LOGGER.error("Failed to load noise texture: {}", path, e); return null; @@ -279,7 +267,7 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList innerMap = new Object2ObjectOpenHashMap<>(); customTexturePropertiesMap.forEach((samplerName, definition) -> { try { - innerMap.put(samplerName, textureLoader.readTexture(root, definition)); + innerMap.put(samplerName, readTexture(root, definition)); } catch (IOException e) { LOGGER.error("Failed to load custom texture {}: {}", samplerName, definition.getName(), e); } @@ -292,13 +280,118 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList { try { - irisCustomTextureDataMap.put(name, textureLoader.readTexture(root, texture)); + irisCustomTextureDataMap.put(name, readTexture(root, texture)); } catch (IOException e) { LOGGER.error("Failed to load Iris custom texture {}: {}", name, texture.getName(), e); } }); } + private CustomTextureData readTexture(Path root, TextureDefinition definition) throws IOException { + try { + return textureCache.computeIfAbsent(definition, + def -> loadTextureAsync(root, def) + .exceptionally(e -> createFallbackTexture(def)) + ).get(2000, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + LOGGER.warn("Texture load timeout: {}", definition.getName()); + return createPlaceholderTexture(); + } catch (Exception e) { + throw new IOException(e); + } + } + + private CompletableFuture loadTextureAsync(Path root, TextureDefinition definition) { + return CompletableFuture.supplyAsync(() -> { + try { + String path = definition.getName(); + if (path.contains(":")) { + String[] parts = path.split(":"); + if (parts.length > 2) { + LOGGER.warn("Invalid resource location: {}", path); + } + if (parts[0].equals("minecraft") && (parts[1].equals("dynamic/lightmap_1") || parts[1].equals("dynamic/light_map_1"))) { + return new CustomTextureData.LightmapMarker(); + } + return new CustomTextureData.ResourceData(parts[0], parts[1]); + } + + if (path.startsWith("/")) { + path = path.substring(1); + } + Path resolvedPath = root.resolve(path); + if (!Files.exists(resolvedPath)) { + LOGGER.error("Texture file not found: {}", path); + throw new IOException("Texture file not found: " + path); + } + + TextureFilteringData filtering = resolveFilteringData(root, path, definition); + byte[] data = Files.readAllBytes(resolvedPath); + + if (definition instanceof TextureDefinition.PNGDefinition) { + return new CustomTextureData.PngData(filtering, data); + } else if (definition instanceof TextureDefinition.RawDefinition raw) { + switch (raw.getTarget()) { + case TEXTURE_1D: + return new CustomTextureData.RawData1D(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX()); + case TEXTURE_2D: + return new CustomTextureData.RawData2D(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); + case TEXTURE_3D: + return new CustomTextureData.RawData3D(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY(), raw.getSizeZ()); + case TEXTURE_RECTANGLE: + return new CustomTextureData.RawDataRect(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); + default: + throw new IllegalArgumentException("Unsupported texture target: " + raw.getTarget()); + } + } + throw new IOException("Unsupported texture definition type: " + definition.getClass().getSimpleName()); + } catch (Exception e) { + throw new CompletionException(e); + } + }, ASYNC_TEXTURE_EXECUTOR); + } + + private boolean isSkyTexture(TextureDefinition definition) { + // 假设天空纹理的名称包含 "sky" 或 "cloud" + return definition.getName().contains("sky") || definition.getName().contains("cloud"); + } + + private TextureFilteringData resolveFilteringData(Path root, String path, TextureDefinition definition) { + boolean blur = definition instanceof TextureDefinition.RawDefinition || isSkyTexture(definition); + boolean clamp = definition instanceof TextureDefinition.RawDefinition || isSkyTexture(definition); + Path mcMetaPath = root.resolve(path + ".mcmeta"); + + if (Files.exists(mcMetaPath)) { + try (BufferedReader reader = Files.newBufferedReader(mcMetaPath, StandardCharsets.UTF_8)) { + JsonObject meta = GSON.fromJson(reader, JsonObject.class); + if (meta.has("texture")) { + JsonObject textureMeta = meta.getAsJsonObject("texture"); + if (textureMeta.has("blur")) blur = textureMeta.get("blur").getAsBoolean(); + if (textureMeta.has("clamp")) clamp = textureMeta.get("clamp").getAsBoolean(); + } + } catch (Exception e) { + LOGGER.error("Failed to load texture filtering data: {}", path, e); + } + } + return new TextureFilteringData(blur, clamp); + } + + private CustomTextureData createPlaceholderTexture() { + return new CustomTextureData.PngData( + new TextureFilteringData(false, false), + new byte[0] + ); + } + + private CustomTextureData createFallbackTexture(TextureDefinition definition) { + LOGGER.warn("Failed to load texture: {}", definition.getName()); + return createPlaceholderTexture(); + } + private static Optional loadPropertiesAsString(Path shaderPath, String name, Iterable environmentDefines) { try { String fileContents = Files.readString(shaderPath.resolve(name), StandardCharsets.ISO_8859_1); @@ -315,8 +408,8 @@ private static Optional loadPropertiesAsString(Path shaderPath, String n } } - private static Optional loadProperties(Path shaderPath, String name, Iterable environmentDefines) { - return loadPropertiesAsString(shaderPath, name, environmentDefines).map(processed -> { + private static Optional loadProperties(Path shaderPath, Iterable environmentDefines) { + return loadPropertiesAsString(shaderPath, "dimension.properties", environmentDefines).map(processed -> { Properties properties = new OrderBackedProperties(); try { properties.load(new StringReader(processed)); @@ -327,11 +420,11 @@ private static Optional loadProperties(Path shaderPath, String name, }); } - private static Map parseDimensionMap(Properties properties, String keyPrefix, String fileName) { + private static Map parseDimensionMap(Properties properties) { return properties.entrySet().stream() - .filter(entry -> ((String) entry.getKey()).startsWith(keyPrefix)) + .filter(entry -> ((String) entry.getKey()).startsWith("dimension.")) .flatMap(entry -> { - String key = ((String) entry.getKey()).substring(keyPrefix.length()); + String key = ((String) entry.getKey()).substring("dimension.".length()); String value = (String) entry.getValue(); return Arrays.stream(value.split("\\s+")) .map(part -> part.equals("*") @@ -341,11 +434,11 @@ private static Map parseDimensionMap(Properties properties .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } - private List parseDimensionIds(Properties dimensionProperties, String keyPrefix) { + private List parseDimensionIds(Properties dimensionProperties) { return dimensionProperties.keySet().stream() .map(keyObj -> (String) keyObj) - .filter(key -> key.startsWith(keyPrefix)) - .map(key -> key.substring(keyPrefix.length())) + .filter(key -> key.startsWith("dimension.")) + .map(key -> key.substring("dimension.".length())) .collect(Collectors.toList()); } @@ -373,88 +466,6 @@ public ProgramSet getProgramSet(NamespacedId dimension) { return (override instanceof ProgramSet) ? (ProgramSet) override : base; } - - // Helper classes and remaining methods - private static final class PreprocessKey { - private final String content; - private final ImmutableList defines; - - PreprocessKey(String content, ImmutableList defines) { - this.content = content.intern(); - this.defines = defines; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof PreprocessKey)) return false; - PreprocessKey that = (PreprocessKey) o; - return content.equals(that.content) && defines.equals(that.defines); - } - - @Override - public int hashCode() { - return Objects.hash(content, defines); - } - } - - private static final LoadingCache PREPROCESS_CACHE = CacheBuilder.newBuilder() - .maximumSize(1000) - .build(new CacheLoader() { - public String load(PreprocessKey key) { - return PropertiesPreprocessor.preprocessSource(key.content, key.defines); - } - }); - - // TextureLoader inner class - private static class TextureLoader { - private final Map> textureCache = new ConcurrentHashMap<>(); - - public CustomTextureData readTexture(Path root, TextureDefinition definition) throws IOException { - try { - return textureCache.computeIfAbsent(definition, - def -> loadTextureAsync(root, def) - ).get(500, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - return createPlaceholderTexture(); - } catch (Exception e) { - throw new IOException("Texture loading failed: " + definition.getName(), e); - } - } - - private CompletableFuture loadTextureAsync(Path root, TextureDefinition definition) { - return CompletableFuture.supplyAsync(() -> { - try { - Path texturePath = root.resolve(definition.getName()); - if (!Files.exists(texturePath)) { - LOGGER.warn("Missing texture: {}", definition.getName()); - return createPlaceholderTexture(); - } - - byte[] data = Files.readAllBytes(texturePath); - return new CustomTextureData.PngData( - new TextureFilteringData(false, false), - data - ); - } catch (Exception e) { - LOGGER.error("Failed to load texture", e); - return createPlaceholderTexture(); - } - }, ASYNC_TEXTURE_EXECUTOR); - } - - private CustomTextureData createPlaceholderTexture() { - return new CustomTextureData.PngData( - new TextureFilteringData(false, false), - new byte[0] - ); - } - } - - public CustomUniforms.Builder getCustomUniforms() { - return this.customUniforms; - } - public IdMap getIdMap() { return idMap; } @@ -490,4 +501,31 @@ public OptionMenuContainer getMenuContainer() { public boolean hasFeature(FeatureFlags feature) { return activeFeatures.contains(feature); } -} \ No newline at end of file + + private record PreprocessKey(String content, ImmutableList defines) { + private PreprocessKey(String content, ImmutableList defines) { + this.content = content.intern(); + this.defines = defines; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PreprocessKey that)) return false; + return content.equals(that.content) && defines.equals(that.defines); + } + + @Override + public int hashCode() { + return Objects.hash(content, defines); + } + } + + private static final LoadingCache PREPROCESS_CACHE = CacheBuilder.newBuilder() + .maximumSize(1000) + .build(new CacheLoader() { + public @NotNull String load(@NotNull PreprocessKey key) { + return PropertiesPreprocessor.preprocessSource(key.content, key.defines); + } + }); +} From 168c5c9ad2cb8c84ee11b3127ec4de9b2a4bb230 Mon Sep 17 00:00:00 2001 From: Lonmo0208 <2675932757@qq.com> Date: Thu, 6 Mar 2025 13:38:09 +0800 Subject: [PATCH 4/8] Update ShaderPack.java --- .../iris/shaderpack/ShaderPack.java | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java b/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java index 3398657951..a58b0466ef 100644 --- a/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java +++ b/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java @@ -1,15 +1,10 @@ package net.irisshaders.iris.shaderpack; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; +import com.google.common.cache.*; import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.JsonObject; -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.irisshaders.iris.Iris; +import it.unimi.dsi.fastutil.objects.*; import net.irisshaders.iris.api.v0.IrisApi; import net.irisshaders.iris.features.FeatureFlags; import net.irisshaders.iris.gl.texture.TextureDefinition; @@ -52,9 +47,8 @@ public class ShaderPack { private static final ExecutorService ASYNC_TEXTURE_EXECUTOR = Executors.newWorkStealingPool( Runtime.getRuntime().availableProcessors() ); - private static final Map SHADER_BINARY_CACHE = new ConcurrentHashMap<>(); - static { + static { Runtime.getRuntime().addShutdownHook(new Thread(() -> { ASYNC_TEXTURE_EXECUTOR.shutdown(); try { @@ -331,22 +325,17 @@ private CompletableFuture loadTextureAsync(Path root, Texture if (definition instanceof TextureDefinition.PNGDefinition) { return new CustomTextureData.PngData(filtering, data); } else if (definition instanceof TextureDefinition.RawDefinition raw) { - switch (raw.getTarget()) { - case TEXTURE_1D: - return new CustomTextureData.RawData1D(data, filtering, - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX()); - case TEXTURE_2D: - return new CustomTextureData.RawData2D(data, filtering, - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); - case TEXTURE_3D: - return new CustomTextureData.RawData3D(data, filtering, - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY(), raw.getSizeZ()); - case TEXTURE_RECTANGLE: - return new CustomTextureData.RawDataRect(data, filtering, - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); - default: - throw new IllegalArgumentException("Unsupported texture target: " + raw.getTarget()); - } + return switch (raw.getTarget()) { + case TEXTURE_1D -> new CustomTextureData.RawData1D(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX()); + case TEXTURE_2D -> new CustomTextureData.RawData2D(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); + case TEXTURE_3D -> new CustomTextureData.RawData3D(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY(), raw.getSizeZ()); + case TEXTURE_RECTANGLE -> new CustomTextureData.RawDataRect(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); + default -> throw new IllegalArgumentException("Unsupported texture target: " + raw.getTarget()); + }; } throw new IOException("Unsupported texture definition type: " + definition.getClass().getSimpleName()); } catch (Exception e) { From f0ba7cdf10a35bb5a6f9f12057c0748fd8e08f3d Mon Sep 17 00:00:00 2001 From: Lonmo0208 <2675932757@qq.com> Date: Thu, 6 Mar 2025 13:52:58 +0800 Subject: [PATCH 5/8] Update build.gradle --- glsl-relocated/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glsl-relocated/build.gradle b/glsl-relocated/build.gradle index 5ffd536953..b5d312df85 100644 --- a/glsl-relocated/build.gradle +++ b/glsl-relocated/build.gradle @@ -8,7 +8,7 @@ repositories { } dependencies { - implementation(shadow("io.github.douira:glsl-transformer:2.0.0-pre13")) { + implementation(shadow("io.github.douira:glsl-transformer:2.0.2")) { exclude module: "antlr4" // we only want to shadow the runtime module } implementation shadow("org.antlr:antlr4-runtime:4.11.1") From bcc8b8562dba90f6bcd6cc88d17966a5caaed382 Mon Sep 17 00:00:00 2001 From: Lonmo0208 <2675932757@qq.com> Date: Wed, 19 Mar 2025 04:08:28 +0800 Subject: [PATCH 6/8] Update ShaderPack.java --- .../iris/shaderpack/ShaderPack.java | 181 +++++++++++------- 1 file changed, 111 insertions(+), 70 deletions(-) diff --git a/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java b/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java index a58b0466ef..3e5ee14738 100644 --- a/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java +++ b/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java @@ -44,24 +44,14 @@ public class ShaderPack { private static final Logger LOGGER = LoggerFactory.getLogger(ShaderPack.class); private static final Gson GSON = new Gson(); - private static final ExecutorService ASYNC_TEXTURE_EXECUTOR = Executors.newWorkStealingPool( - Runtime.getRuntime().availableProcessors() - ); - static { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - ASYNC_TEXTURE_EXECUTOR.shutdown(); - try { - if (!ASYNC_TEXTURE_EXECUTOR.awaitTermination(5, TimeUnit.SECONDS)) { - ASYNC_TEXTURE_EXECUTOR.shutdownNow(); - } - } catch (InterruptedException e) { - ASYNC_TEXTURE_EXECUTOR.shutdownNow(); - } - })); - } + private static final ExecutorService ASYNC_TEXTURE_EXECUTOR = Executors.newWorkStealingPool(); + static int cpuCores = Runtime.getRuntime().availableProcessors(); + static int poolSize = Math.min(32, (int)(cpuCores * (1 + 2 * 0.8))); + static ExecutorService executor = Executors.newWorkStealingPool(poolSize); private final Map> textureCache = new ConcurrentHashMap<>(); + public final CustomUniforms.Builder customUniforms; private final ProgramSet base; private final Map overrides; @@ -81,10 +71,28 @@ public class ShaderPack { private final List dimensionIds; private Map dimensionMap; - public ShaderPack(Path root, ImmutableList environmentDefines) throws IOException, IllegalStateException { - this(root, Collections.emptyMap(), environmentDefines); + static { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + ASYNC_TEXTURE_EXECUTOR.shutdown(); + executor.shutdown(); + try { + if (!ASYNC_TEXTURE_EXECUTOR.awaitTermination(5, TimeUnit.SECONDS)) { + ASYNC_TEXTURE_EXECUTOR.shutdownNow(); + } + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + ASYNC_TEXTURE_EXECUTOR.shutdownNow(); + executor.shutdownNow(); + } + })); } + private int size; + private int i; + private int i1; + public ShaderPack(Path root, Map changedConfigs, ImmutableList environmentDefines) throws IOException, IllegalStateException { Objects.requireNonNull(root); ArrayList envDefines1 = new ArrayList<>(environmentDefines); @@ -248,12 +256,13 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList(); this.idMap = new IdMap(root, shaderPackOptions, environmentDefines); + // 优化:异步加载噪声纹理 customNoiseTexture = shaderProperties.getNoiseTexturePath().map(path -> { try { return readTexture(root, new TextureDefinition.PNGDefinition(path)); } catch (IOException e) { LOGGER.error("Failed to load noise texture: {}", path, e); - return null; + return createFallbackTexture(new TextureDefinition.PNGDefinition(path)); } }).orElse(null); @@ -264,6 +273,7 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList changedConfigs, ImmutableList future = textureCache.computeIfAbsent(definition, + def -> loadTextureAsync(root, def) + .exceptionally(e -> createFallbackTexture(def)) + ); + try { - return textureCache.computeIfAbsent(definition, - def -> loadTextureAsync(root, def) - .exceptionally(e -> createFallbackTexture(def)) - ).get(2000, TimeUnit.MILLISECONDS); + return future.get(2000, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { LOGGER.warn("Texture load timeout: {}", definition.getName()); - return createPlaceholderTexture(); - } catch (Exception e) { + int size = 64; + return createPlaceholderTexture(size); + } catch (InterruptedException | ExecutionException e) { throw new IOException(e); } } + private final Semaphore textureLoadSemaphore = new Semaphore(10); + private CompletableFuture loadTextureAsync(Path root, TextureDefinition definition) { return CompletableFuture.supplyAsync(() -> { try { - String path = definition.getName(); - if (path.contains(":")) { - String[] parts = path.split(":"); - if (parts.length > 2) { - LOGGER.warn("Invalid resource location: {}", path); - } - if (parts[0].equals("minecraft") && (parts[1].equals("dynamic/lightmap_1") || parts[1].equals("dynamic/light_map_1"))) { - return new CustomTextureData.LightmapMarker(); + textureLoadSemaphore.acquire(); + try { + String path = definition.getName(); + if (path.contains(":")) { + String[] parts = path.split(":"); + if (parts.length > 2) { + LOGGER.warn("Invalid resource location: {}", path); + } + if (parts[0].equals("minecraft") && (parts[1].equals("dynamic/lightmap_1") || parts[1].equals("dynamic/light_map_1"))) { + return new CustomTextureData.LightmapMarker(); + } + return new CustomTextureData.ResourceData(parts[0], parts[1]); } - return new CustomTextureData.ResourceData(parts[0], parts[1]); - } - if (path.startsWith("/")) { - path = path.substring(1); - } - Path resolvedPath = root.resolve(path); - if (!Files.exists(resolvedPath)) { - LOGGER.error("Texture file not found: {}", path); - throw new IOException("Texture file not found: " + path); - } + if (path.startsWith("/")) { + path = path.substring(1); + } + Path resolvedPath = root.resolve(path); + if (!Files.exists(resolvedPath)) { + LOGGER.error("Texture file not found: {}", path); + throw new IOException("Texture file not found: " + path); + } - TextureFilteringData filtering = resolveFilteringData(root, path, definition); - byte[] data = Files.readAllBytes(resolvedPath); - - if (definition instanceof TextureDefinition.PNGDefinition) { - return new CustomTextureData.PngData(filtering, data); - } else if (definition instanceof TextureDefinition.RawDefinition raw) { - return switch (raw.getTarget()) { - case TEXTURE_1D -> new CustomTextureData.RawData1D(data, filtering, - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX()); - case TEXTURE_2D -> new CustomTextureData.RawData2D(data, filtering, - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); - case TEXTURE_3D -> new CustomTextureData.RawData3D(data, filtering, - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY(), raw.getSizeZ()); - case TEXTURE_RECTANGLE -> new CustomTextureData.RawDataRect(data, filtering, - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); - default -> throw new IllegalArgumentException("Unsupported texture target: " + raw.getTarget()); - }; + TextureFilteringData filtering = resolveFilteringData(root, path, definition); + byte[] data = Files.readAllBytes(resolvedPath); + + if (definition instanceof TextureDefinition.PNGDefinition) { + return new CustomTextureData.PngData(filtering, data); + } else if (definition instanceof TextureDefinition.RawDefinition raw) { + return switch (raw.getTarget()) { + case TEXTURE_1D -> new CustomTextureData.RawData1D(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX()); + case TEXTURE_2D -> new CustomTextureData.RawData2D(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); + case TEXTURE_3D -> new CustomTextureData.RawData3D(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY(), raw.getSizeZ()); + case TEXTURE_RECTANGLE -> new CustomTextureData.RawDataRect(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); + default -> throw new IllegalArgumentException("Unsupported texture target: " + raw.getTarget()); + }; + } + throw new IOException("Unsupported texture definition type: " + definition.getClass().getSimpleName()); + } finally { + textureLoadSemaphore.release(); } - throw new IOException("Unsupported texture definition type: " + definition.getClass().getSimpleName()); - } catch (Exception e) { + } catch (InterruptedException | IOException e) { + Thread.currentThread().interrupt(); throw new CompletionException(e); } }, ASYNC_TEXTURE_EXECUTOR); } - private boolean isSkyTexture(TextureDefinition definition) { - // 假设天空纹理的名称包含 "sky" 或 "cloud" - return definition.getName().contains("sky") || definition.getName().contains("cloud"); - } - private TextureFilteringData resolveFilteringData(Path root, String path, TextureDefinition definition) { boolean blur = definition instanceof TextureDefinition.RawDefinition || isSkyTexture(definition); boolean clamp = definition instanceof TextureDefinition.RawDefinition || isSkyTexture(definition); @@ -369,7 +385,14 @@ private TextureFilteringData resolveFilteringData(Path root, String path, Textur return new TextureFilteringData(blur, clamp); } - private CustomTextureData createPlaceholderTexture() { + private boolean isSkyTexture(TextureDefinition definition) { + return definition.getName().contains("sky") || definition.getName().contains("cloud"); + } + + private CustomTextureData createPlaceholderTexture(int size) { + this.size = size; + this.i = -10066330; + this.i1 = -6710887; return new CustomTextureData.PngData( new TextureFilteringData(false, false), new byte[0] @@ -377,10 +400,15 @@ private CustomTextureData createPlaceholderTexture() { } private CustomTextureData createFallbackTexture(TextureDefinition definition) { - LOGGER.warn("Failed to load texture: {}", definition.getName()); - return createPlaceholderTexture(); + int size = 64; + if (definition instanceof TextureDefinition.RawDefinition) { + size = Math.max(((TextureDefinition.RawDefinition) definition).getSizeX(), 16); + } + LOGGER.warn("Using fallback texture for: {}", definition.getName()); + return createPlaceholderTexture(size); } + private static Optional loadPropertiesAsString(Path shaderPath, String name, Iterable environmentDefines) { try { String fileContents = Files.readString(shaderPath.resolve(name), StandardCharsets.ISO_8859_1); @@ -491,6 +519,18 @@ public boolean hasFeature(FeatureFlags feature) { return activeFeatures.contains(feature); } + public int getSize() { + return size; + } + + public int getI() { + return i; + } + + public int getI1() { + return i1; + } + private record PreprocessKey(String content, ImmutableList defines) { private PreprocessKey(String content, ImmutableList defines) { this.content = content.intern(); @@ -517,4 +557,5 @@ public int hashCode() { return PropertiesPreprocessor.preprocessSource(key.content, key.defines); } }); + } From 38835f880e11eaf0b9d457d4c2fe2be959d9509a Mon Sep 17 00:00:00 2001 From: Lonmo0208 <2675932757@qq.com> Date: Tue, 13 May 2025 17:35:00 +0800 Subject: [PATCH 7/8] Set --- .../gl/buffer/BuiltShaderStorageInfo.java | 4 + .../iris/gl/buffer/ShaderStorageInfo.java | 2 +- .../iris/shaderpack/ShaderPack.java | 524 ++++++++---------- .../properties/ShaderProperties.java | 8 +- 4 files changed, 249 insertions(+), 289 deletions(-) create mode 100644 src/main/java/net/irisshaders/iris/gl/buffer/BuiltShaderStorageInfo.java diff --git a/src/main/java/net/irisshaders/iris/gl/buffer/BuiltShaderStorageInfo.java b/src/main/java/net/irisshaders/iris/gl/buffer/BuiltShaderStorageInfo.java new file mode 100644 index 0000000000..7f956070a0 --- /dev/null +++ b/src/main/java/net/irisshaders/iris/gl/buffer/BuiltShaderStorageInfo.java @@ -0,0 +1,4 @@ +package net.irisshaders.iris.gl.buffer; + +public record BuiltShaderStorageInfo(long size, boolean relative, float scaleX, float scaleY, byte[] content) { +} \ No newline at end of file diff --git a/src/main/java/net/irisshaders/iris/gl/buffer/ShaderStorageInfo.java b/src/main/java/net/irisshaders/iris/gl/buffer/ShaderStorageInfo.java index 2b95080a6c..4e27fbef34 100644 --- a/src/main/java/net/irisshaders/iris/gl/buffer/ShaderStorageInfo.java +++ b/src/main/java/net/irisshaders/iris/gl/buffer/ShaderStorageInfo.java @@ -1,4 +1,4 @@ package net.irisshaders.iris.gl.buffer; -public record ShaderStorageInfo(int size, boolean relative, float scaleX, float scaleY) { +public record ShaderStorageInfo(int size, boolean relative, float scaleX, float scaleY, String name) { } diff --git a/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java b/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java index 3e5ee14738..dc0afa0dd6 100644 --- a/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java +++ b/src/main/java/net/irisshaders/iris/shaderpack/ShaderPack.java @@ -4,9 +4,13 @@ import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.JsonObject; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.objects.*; import net.irisshaders.iris.api.v0.IrisApi; import net.irisshaders.iris.features.FeatureFlags; +import net.irisshaders.iris.gl.buffer.BuiltShaderStorageInfo; +import net.irisshaders.iris.gl.buffer.ShaderStorageInfo; import net.irisshaders.iris.gl.texture.TextureDefinition; import net.irisshaders.iris.gui.FeatureMissingErrorScreen; import net.irisshaders.iris.gui.screen.ShaderPackScreen; @@ -44,13 +48,34 @@ public class ShaderPack { private static final Logger LOGGER = LoggerFactory.getLogger(ShaderPack.class); private static final Gson GSON = new Gson(); + private static final int CORES = Runtime.getRuntime().availableProcessors(); + private static final int PARALLELISM = Math.min(CORES * 8, 256); + private static final ForkJoinPool TEXTURE_LOAD_EXECUTOR = new ForkJoinPool(PARALLELISM, ForkJoinPool.defaultForkJoinWorkerThreadFactory, (t, e) -> LOGGER.error("Texture loader thread failed", e), true); + private static final int MAX_CONCURRENT_LOADS = Math.min(Integer.MAX_VALUE, CORES * 4); + private static final int LOAD_TIMEOUT = 2; - private static final ExecutorService ASYNC_TEXTURE_EXECUTOR = Executors.newWorkStealingPool(); - static int cpuCores = Runtime.getRuntime().availableProcessors(); - static int poolSize = Math.min(32, (int)(cpuCores * (1 + 2 * 0.8))); - static ExecutorService executor = Executors.newWorkStealingPool(poolSize); + private static final LoadingCache PREPROCESS_CACHE = CacheBuilder.newBuilder() + .maximumSize(1000) + .build(new CacheLoader() { + public @NotNull String load(@NotNull PreprocessKey key) { + return PropertiesPreprocessor.preprocessSource(key.content, key.defines); + } + }); + + static { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + TEXTURE_LOAD_EXECUTOR.shutdown(); + try { + if (!TEXTURE_LOAD_EXECUTOR.awaitTermination(3, TimeUnit.SECONDS)) { + TEXTURE_LOAD_EXECUTOR.shutdownNow(); + } + } catch (InterruptedException ignored) { + } + })); + } private final Map> textureCache = new ConcurrentHashMap<>(); + private final Semaphore textureLoadSemaphore = new Semaphore(MAX_CONCURRENT_LOADS); public final CustomUniforms.Builder customUniforms; private final ProgramSet base; @@ -69,30 +94,9 @@ public class ShaderPack { private final Function sourceProvider; private final ShaderProperties shaderProperties; private final List dimensionIds; + private final Int2ObjectArrayMap bufferObjects; private Map dimensionMap; - static { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - ASYNC_TEXTURE_EXECUTOR.shutdown(); - executor.shutdown(); - try { - if (!ASYNC_TEXTURE_EXECUTOR.awaitTermination(5, TimeUnit.SECONDS)) { - ASYNC_TEXTURE_EXECUTOR.shutdownNow(); - } - if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { - executor.shutdownNow(); - } - } catch (InterruptedException e) { - ASYNC_TEXTURE_EXECUTOR.shutdownNow(); - executor.shutdownNow(); - } - })); - } - - private int size; - private int i; - private int i1; - public ShaderPack(Path root, Map changedConfigs, ImmutableList environmentDefines) throws IOException, IllegalStateException { Objects.requireNonNull(root); ArrayList envDefines1 = new ArrayList<>(environmentDefines); @@ -100,19 +104,23 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList starts = ImmutableList.builder(); ImmutableList potentialFileNames = ShaderPackSourceNames.POTENTIAL_STARTS; + ShaderPackSourceNames.findPresentSources(starts, root, AbsolutePackPath.fromAbsolutePath("/"), potentialFileNames); + dimensionIds = new ArrayList<>(); + bufferObjects = new Int2ObjectArrayMap<>(); + final boolean[] hasDimensionIds = {false}; - // Dimension properties loading - List dimensionIdCreator = loadProperties(root, environmentDefines).map(dimensionProperties -> { + List dimensionIdCreator = loadProperties(root, "dimension.properties", environmentDefines).map(dimensionProperties -> { hasDimensionIds[0] = !dimensionProperties.isEmpty(); - dimensionMap = parseDimensionMap(dimensionProperties); - return parseDimensionIds(dimensionProperties); - }).orElse(new ArrayList<>()); + dimensionMap = parseDimensionMap(dimensionProperties, "dimension.", "dimension.properties"); + return parseDimensionIds(dimensionProperties, "dimension."); + }).orElseGet(ArrayList::new); if (!hasDimensionIds[0]) { dimensionMap = new Object2ObjectArrayMap<>(); + if (Files.exists(root.resolve("world0"))) { dimensionIdCreator.add("world0"); dimensionMap.putIfAbsent(DimensionId.OVERWORLD, "world0"); @@ -136,8 +144,7 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList LOGGER.error("Include resolution failed: {}", error)); - throw new IOException("Shader pack includes resolution failed"); + throw new IOException(String.join("\n", graph.getFailures().values().stream().map(Object::toString).toArray(String[]::new))); } this.languageMap = new LanguageMap(root.resolve("lang")); @@ -153,38 +160,53 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList new ShaderProperties(source, shaderPackOptions, finalEnvironmentDefines)) .orElseGet(ShaderProperties::empty); - activeFeatures = new HashSet<>(); - for (int i = 0; i < shaderProperties.getRequiredFeatureFlags().size(); i++) { - activeFeatures.add(FeatureFlags.getValue(shaderProperties.getRequiredFeatureFlags().get(i))); - } - for (int i = 0; i < shaderProperties.getOptionalFeatureFlags().size(); i++) { - activeFeatures.add(FeatureFlags.getValue(shaderProperties.getOptionalFeatureFlags().get(i))); + for (Int2ObjectMap.Entry entry : shaderProperties.getBufferObjects().int2ObjectEntrySet()) { + ShaderStorageInfo info = entry.getValue(); + if (info.name() == null) { + bufferObjects.put(entry.getIntKey(), new BuiltShaderStorageInfo(info.size(), info.relative(), info.scaleX(), info.scaleY(), null)); + } else { + String path = info.name(); + try { + path = path.startsWith("/") ? path.substring(1) : path; + byte[] data = Files.readAllBytes(root.resolve(path)); + if (data.length > info.size()) { + throw new IllegalStateException("Buffer size too small for " + path); + } + bufferObjects.put(entry.getIntKey(), new BuiltShaderStorageInfo(info.size(), info.relative(), info.scaleX(), info.scaleY(), data)); + } catch (IOException e) { + LOGGER.error("Failed to load SSBO {}", path, e); + } + } } + activeFeatures = new HashSet<>(); + shaderProperties.getRequiredFeatureFlags().forEach(flag -> activeFeatures.add(FeatureFlags.getValue(flag))); + shaderProperties.getOptionalFeatureFlags().forEach(flag -> activeFeatures.add(FeatureFlags.getValue(flag))); + if (!activeFeatures.contains(FeatureFlags.SSBO) && !shaderProperties.getBufferObjects().isEmpty()) { - throw new IllegalStateException("SSBO feature required but not enabled"); + throw new IllegalStateException("SSBO used without feature flag"); } if (!activeFeatures.contains(FeatureFlags.CUSTOM_IMAGES) && !shaderProperties.getIrisCustomImages().isEmpty()) { - throw new IllegalStateException("CUSTOM_IMAGES feature required but not enabled"); + throw new IllegalStateException("Custom images used without feature flag"); } List invalidFlagList = shaderProperties.getRequiredFeatureFlags().stream() .filter(FeatureFlags::isInvalid) .map(FeatureFlags::getValue) .collect(Collectors.toList()); - List invalidFeatureFlags = invalidFlagList.stream() - .map(FeatureFlags::getHumanReadableName) - .toList(); - if (!invalidFeatureFlags.isEmpty() && Minecraft.getInstance().screen instanceof ShaderPackScreen) { + if (!invalidFlagList.isEmpty() && Minecraft.getInstance().screen instanceof ShaderPackScreen) { MutableComponent component = Component.translatable("iris.unsupported.pack.description", FeatureFlags.getInvalidStatus(invalidFlagList), - invalidFeatureFlags.stream().collect(Collectors.joining(", ", ": ", ".")) - ); + invalidFlagList.stream() + .map(FeatureFlags::getHumanReadableName) + .collect(Collectors.joining(", ", ": ", "."))); + if (SystemUtils.IS_OS_MAC) { component = component.append(Component.translatable("iris.unsupported.pack.macos")); } + Minecraft.getInstance().setScreen(new FeatureMissingErrorScreen( Minecraft.getInstance().screen, Component.translatable("iris.unsupported.pack"), @@ -195,36 +217,34 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList newEnvDefines = new ArrayList<>(environmentDefines); if (shaderProperties.supportsColorCorrection().orElse(false)) { - for (ColorSpace space : ColorSpace.values()) { - newEnvDefines.add(new StringPair("COLOR_SPACE_" + space.name(), String.valueOf(space.ordinal()))); - } + Arrays.stream(ColorSpace.values()).forEach(space -> + newEnvDefines.add(new StringPair("COLOR_SPACE_" + space.name(), String.valueOf(space.ordinal()))) + ); } - List optionalFeatureFlags = shaderProperties.getOptionalFeatureFlags().stream() + shaderProperties.getOptionalFeatureFlags().stream() .filter(flag -> !FeatureFlags.isInvalid(flag)) - .toList(); - if (!optionalFeatureFlags.isEmpty()) { - optionalFeatureFlags.forEach(flag -> newEnvDefines.add(new StringPair("IRIS_FEATURE_" + flag, ""))); - } + .forEach(flag -> newEnvDefines.add(new StringPair("IRIS_FEATURE_" + flag, ""))); environmentDefines = ImmutableList.copyOf(newEnvDefines); + ProfileSet profiles = ProfileSet.fromTree(shaderProperties.getProfiles(), this.shaderPackOptions.getOptionSet()); this.profile = profiles.scan(this.shaderPackOptions.getOptionSet(), this.shaderPackOptions.getOptionValues()); List disabledPrograms = new ArrayList<>(); - this.profile.current.ifPresent(profile -> disabledPrograms.addAll(profile.disabledPrograms)); - shaderProperties.getConditionallyEnabledPrograms().forEach((program, shaderOption) -> { - if (!BooleanParser.parse(shaderOption, this.shaderPackOptions.getOptionValues())) { + this.profile.current.ifPresent(p -> disabledPrograms.addAll(p.disabledPrograms)); + shaderProperties.getConditionallyEnabledPrograms().forEach((program, option) -> { + if (!BooleanParser.parse(option, this.shaderPackOptions.getOptionValues())) { disabledPrograms.add(program); } }); this.menuContainer = new OptionMenuContainer(shaderProperties, this.shaderPackOptions, profiles); - String profileName = getCurrentProfileName(); + String profileName = profile.current.map(p -> p.name).orElse("Custom"); OptionValues profileOptions = new MutableOptionValues( this.shaderPackOptions.getOptionSet(), - this.profile.current.map(p -> p.optionValues).orElse(new HashMap<>()) + profile.current.map(p -> p.optionValues).orElse(new HashMap<>()) ); int userOptionsChanged = this.shaderPackOptions.getOptionValues().getOptionsChanged() - profileOptions.getOptionsChanged(); this.profileInfo = String.format("Profile: %s (+%d %s changed)", @@ -235,8 +255,7 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList finalEnvironmentDefines1 = environmentDefines; this.sourceProvider = path -> { String pathString = path.getPathString(); - int startIndex = pathString.startsWith("/") ? 1 : 0; - String programString = pathString.substring(startIndex, pathString.lastIndexOf('.')); + String programString = pathString.substring(pathString.startsWith("/") ? 1 : 0, pathString.lastIndexOf('.')); if (disabledPrograms.contains(programString)) return null; ImmutableList lines = includeProcessor.getIncludedFile(path); @@ -253,133 +272,111 @@ public ShaderPack(Path root, Map changedConfigs, ImmutableList(); + this.overrides = new ConcurrentHashMap<>(); this.idMap = new IdMap(root, shaderPackOptions, environmentDefines); - // 优化:异步加载噪声纹理 - customNoiseTexture = shaderProperties.getNoiseTexturePath().map(path -> { - try { - return readTexture(root, new TextureDefinition.PNGDefinition(path)); - } catch (IOException e) { - LOGGER.error("Failed to load noise texture: {}", path, e); - return createFallbackTexture(new TextureDefinition.PNGDefinition(path)); - } - }).orElse(null); + CompletableFuture noiseFuture = shaderProperties.getNoiseTexturePath() + .map(path -> readTextureAsync(root, new TextureDefinition.PNGDefinition(path))) + .orElseGet(() -> CompletableFuture.completedFuture(null)); + + this.customNoiseTexture = noiseFuture.exceptionally(ex -> { + LOGGER.error("Failed to load noise texture", ex); + return createFallbackTexture(new TextureDefinition.PNGDefinition("noise.png")); + }).join(); + + shaderProperties.getCustomTextures().forEach((stage, textures) -> { + Object2ObjectMap> futures = new Object2ObjectOpenHashMap<>(); + textures.forEach((name, def) -> { + CompletableFuture future = readTextureAsync(root, def) + .exceptionally(ex -> { + LOGGER.error("Failed to load texture {}: {}", name, def.getName(), ex); + return createFallbackTexture(def); + }) + .completeOnTimeout(createFallbackTexture(def), LOAD_TIMEOUT, TimeUnit.SECONDS); + futures.put(name, future); + }); - shaderProperties.getCustomTextures().forEach((textureStage, customTexturePropertiesMap) -> { - Object2ObjectMap innerMap = new Object2ObjectOpenHashMap<>(); - customTexturePropertiesMap.forEach((samplerName, definition) -> { - try { - innerMap.put(samplerName, readTexture(root, definition)); - } catch (IOException e) { - LOGGER.error("Failed to load custom texture {}: {}", samplerName, definition.getName(), e); - innerMap.put(samplerName, createFallbackTexture(definition)); - } + CompletableFuture.allOf(futures.values().toArray(new CompletableFuture[0])).thenRun(() -> { + Object2ObjectMap result = new Object2ObjectOpenHashMap<>(); + futures.forEach((key, value) -> result.put(key, value.join())); + customTextureDataMap.put(stage, result); }); - customTextureDataMap.put(textureStage, innerMap); + }); + + shaderProperties.getIrisCustomTextures().forEach((name, def) -> { + CompletableFuture future = readTextureAsync(root, def) + .exceptionally(ex -> { + LOGGER.error("Failed to load Iris texture {}: {}", name, def.getName(), ex); + return createFallbackTexture(def); + }) + .completeOnTimeout(createFallbackTexture(def), LOAD_TIMEOUT, TimeUnit.SECONDS); + irisCustomTextureDataMap.put(name, future.join()); }); this.irisCustomImages = shaderProperties.getIrisCustomImages(); this.customUniforms = shaderProperties.getCustomUniforms(); - - shaderProperties.getIrisCustomTextures().forEach((name, texture) -> { - try { - irisCustomTextureDataMap.put(name, readTexture(root, texture)); - } catch (IOException e) { - LOGGER.error("Failed to load Iris custom texture {}: {}", name, texture.getName(), e); - irisCustomTextureDataMap.put(name, createFallbackTexture(texture)); - } - }); } - private CustomTextureData readTexture(Path root, TextureDefinition definition) throws IOException { - CompletableFuture future = textureCache.computeIfAbsent(definition, - def -> loadTextureAsync(root, def) - .exceptionally(e -> createFallbackTexture(def)) + + private CompletableFuture readTextureAsync(Path root, TextureDefinition definition) { + return textureCache.computeIfAbsent(definition, def -> + CompletableFuture.supplyAsync(() -> { + try { + textureLoadSemaphore.acquire(); + return loadTextureSync(root, def); + } catch (IOException | InterruptedException e) { + throw new CompletionException(e); + } finally { + textureLoadSemaphore.release(); + } + }, TEXTURE_LOAD_EXECUTOR) + .exceptionally(ex -> { + LOGGER.error("Failed to load texture: {}", def.getName(), ex); + return createFallbackTexture(def); + }) + .completeOnTimeout(createFallbackTexture(def), LOAD_TIMEOUT, TimeUnit.SECONDS) ); + } - try { - return future.get(2000, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - LOGGER.warn("Texture load timeout: {}", definition.getName()); - int size = 64; - return createPlaceholderTexture(size); - } catch (InterruptedException | ExecutionException e) { - throw new IOException(e); + private CustomTextureData loadTextureSync(Path root, TextureDefinition definition) throws IOException { + String path = definition.getName(); + if (path.contains(":")) { + return handleResourceLocation(path); } - } - private final Semaphore textureLoadSemaphore = new Semaphore(10); + path = path.startsWith("/") ? path.substring(1) : path; + Path resolvedPath = root.resolve(path); + TextureFilteringData filtering = resolveFilteringData(root, path, definition); + byte[] data = Files.readAllBytes(resolvedPath); - private CompletableFuture loadTextureAsync(Path root, TextureDefinition definition) { - return CompletableFuture.supplyAsync(() -> { - try { - textureLoadSemaphore.acquire(); - try { - String path = definition.getName(); - if (path.contains(":")) { - String[] parts = path.split(":"); - if (parts.length > 2) { - LOGGER.warn("Invalid resource location: {}", path); - } - if (parts[0].equals("minecraft") && (parts[1].equals("dynamic/lightmap_1") || parts[1].equals("dynamic/light_map_1"))) { - return new CustomTextureData.LightmapMarker(); - } - return new CustomTextureData.ResourceData(parts[0], parts[1]); - } - - if (path.startsWith("/")) { - path = path.substring(1); - } - Path resolvedPath = root.resolve(path); - if (!Files.exists(resolvedPath)) { - LOGGER.error("Texture file not found: {}", path); - throw new IOException("Texture file not found: " + path); - } + return createTextureData(definition, filtering, data); + } - TextureFilteringData filtering = resolveFilteringData(root, path, definition); - byte[] data = Files.readAllBytes(resolvedPath); - - if (definition instanceof TextureDefinition.PNGDefinition) { - return new CustomTextureData.PngData(filtering, data); - } else if (definition instanceof TextureDefinition.RawDefinition raw) { - return switch (raw.getTarget()) { - case TEXTURE_1D -> new CustomTextureData.RawData1D(data, filtering, - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX()); - case TEXTURE_2D -> new CustomTextureData.RawData2D(data, filtering, - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); - case TEXTURE_3D -> new CustomTextureData.RawData3D(data, filtering, - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY(), raw.getSizeZ()); - case TEXTURE_RECTANGLE -> new CustomTextureData.RawDataRect(data, filtering, - raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); - default -> throw new IllegalArgumentException("Unsupported texture target: " + raw.getTarget()); - }; - } - throw new IOException("Unsupported texture definition type: " + definition.getClass().getSimpleName()); - } finally { - textureLoadSemaphore.release(); - } - } catch (InterruptedException | IOException e) { - Thread.currentThread().interrupt(); - throw new CompletionException(e); - } - }, ASYNC_TEXTURE_EXECUTOR); + private CustomTextureData handleResourceLocation(String path) { + String[] parts = path.split(":"); + if (parts.length > 2) { + LOGGER.warn("Invalid resource location: {}", path); + } + if ("minecraft".equals(parts[0]) && (parts[1].equals("dynamic/lightmap_1") || parts[1].equals("dynamic/light_map_1"))) { + return new CustomTextureData.LightmapMarker(); + } + return new CustomTextureData.ResourceData(parts[0], parts[1]); } - private TextureFilteringData resolveFilteringData(Path root, String path, TextureDefinition definition) { - boolean blur = definition instanceof TextureDefinition.RawDefinition || isSkyTexture(definition); - boolean clamp = definition instanceof TextureDefinition.RawDefinition || isSkyTexture(definition); - Path mcMetaPath = root.resolve(path + ".mcmeta"); + private TextureFilteringData resolveFilteringData(Path root, String path, TextureDefinition def) { + boolean blur = def instanceof TextureDefinition.RawDefinition || isSkyTexture(def); + boolean clamp = def instanceof TextureDefinition.RawDefinition || isSkyTexture(def); - if (Files.exists(mcMetaPath)) { - try (BufferedReader reader = Files.newBufferedReader(mcMetaPath, StandardCharsets.UTF_8)) { + Path metaPath = root.resolve(path + ".mcmeta"); + if (Files.exists(metaPath)) { + try (BufferedReader reader = Files.newBufferedReader(metaPath)) { JsonObject meta = GSON.fromJson(reader, JsonObject.class); - if (meta.has("texture")) { - JsonObject textureMeta = meta.getAsJsonObject("texture"); - if (textureMeta.has("blur")) blur = textureMeta.get("blur").getAsBoolean(); - if (textureMeta.has("clamp")) clamp = textureMeta.get("clamp").getAsBoolean(); + JsonObject textureMeta = meta.getAsJsonObject("texture"); + if (textureMeta != null) { + blur = textureMeta.has("blur") ? textureMeta.get("blur").getAsBoolean() : blur; + clamp = textureMeta.has("clamp") ? textureMeta.get("clamp").getAsBoolean() : clamp; } } catch (Exception e) { - LOGGER.error("Failed to load texture filtering data: {}", path, e); + LOGGER.error("Failed to read texture metadata: {}", metaPath, e); } } return new TextureFilteringData(blur, clamp); @@ -389,84 +386,65 @@ private boolean isSkyTexture(TextureDefinition definition) { return definition.getName().contains("sky") || definition.getName().contains("cloud"); } - private CustomTextureData createPlaceholderTexture(int size) { - this.size = size; - this.i = -10066330; - this.i1 = -6710887; + private CustomTextureData createTextureData(TextureDefinition definition, TextureFilteringData filtering, byte[] data) { + if (definition instanceof TextureDefinition.PNGDefinition) { + return new CustomTextureData.PngData(filtering, data); + } else if (definition instanceof TextureDefinition.RawDefinition raw) { + return switch (raw.getTarget()) { + case TEXTURE_1D -> new CustomTextureData.RawData1D(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX()); + case TEXTURE_2D -> new CustomTextureData.RawData2D(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), raw.getSizeX(), raw.getSizeY()); + case TEXTURE_3D -> new CustomTextureData.RawData3D(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), + raw.getSizeX(), raw.getSizeY(), raw.getSizeZ()); + case TEXTURE_RECTANGLE -> new CustomTextureData.RawDataRect(data, filtering, + raw.getInternalFormat(), raw.getFormat(), raw.getPixelType(), + raw.getSizeX(), raw.getSizeY()); + }; + } + throw new IllegalArgumentException("Unsupported texture type: " + definition.getClass().getSimpleName()); + } + + private CustomTextureData createFallbackTexture(TextureDefinition def) { return new CustomTextureData.PngData( new TextureFilteringData(false, false), new byte[0] ); } - private CustomTextureData createFallbackTexture(TextureDefinition definition) { - int size = 64; - if (definition instanceof TextureDefinition.RawDefinition) { - size = Math.max(((TextureDefinition.RawDefinition) definition).getSizeX(), 16); - } - LOGGER.warn("Using fallback texture for: {}", definition.getName()); - return createPlaceholderTexture(size); + private static Optional loadProperties(Path shaderPath, String name, Iterable environmentDefines) { + return loadPropertiesAsString(shaderPath, name, environmentDefines).map(content -> { + Properties props = new OrderBackedProperties(); + try { + props.load(new StringReader(content)); + } catch (IOException e) { + LOGGER.error("Error loading properties", e); + } + return props; + }); } - private static Optional loadPropertiesAsString(Path shaderPath, String name, Iterable environmentDefines) { try { String fileContents = Files.readString(shaderPath.resolve(name), StandardCharsets.ISO_8859_1); - ImmutableList defines = ImmutableList.copyOf(environmentDefines); - return Optional.of(PREPROCESS_CACHE.getUnchecked(new PreprocessKey(fileContents, defines))); + return Optional.of(PREPROCESS_CACHE.getUnchecked(new PreprocessKey(fileContents, ImmutableList.copyOf(environmentDefines)))); } catch (NoSuchFileException e) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("An {} file was not found in the current shaderpack", name); - } return Optional.empty(); } catch (IOException e) { - LOGGER.error("IO error reading properties: {} / {}", shaderPath, name, e); + LOGGER.error("IO error reading properties", e); return Optional.empty(); } } - private static Optional loadProperties(Path shaderPath, Iterable environmentDefines) { - return loadPropertiesAsString(shaderPath, "dimension.properties", environmentDefines).map(processed -> { - Properties properties = new OrderBackedProperties(); - try { - properties.load(new StringReader(processed)); - } catch (IOException e) { - LOGGER.error("Properties parse error", e); - } - return properties; - }); - } - - private static Map parseDimensionMap(Properties properties) { - return properties.entrySet().stream() - .filter(entry -> ((String) entry.getKey()).startsWith("dimension.")) - .flatMap(entry -> { - String key = ((String) entry.getKey()).substring("dimension.".length()); - String value = (String) entry.getValue(); - return Arrays.stream(value.split("\\s+")) - .map(part -> part.equals("*") - ? new AbstractMap.SimpleEntry<>(new NamespacedId("*", "*"), key) - : new AbstractMap.SimpleEntry<>(new NamespacedId(part), key)); - }) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - } - - private List parseDimensionIds(Properties dimensionProperties) { - return dimensionProperties.keySet().stream() - .map(keyObj -> (String) keyObj) - .filter(key -> key.startsWith("dimension.")) - .map(key -> key.substring("dimension.".length())) - .collect(Collectors.toList()); + public String getProfileInfo() { + return profileInfo; } - private String getCurrentProfileName() { + public String getCurrentProfileName() { return profile.current.map(p -> p.name).orElse("Custom"); } - public String getProfileInfo() { - return profileInfo; - } - public ProgramSet getProgramSet(NamespacedId dimension) { ProgramSetInterface override = overrides.computeIfAbsent(dimension, dim -> { if (dimensionMap.containsKey(dim)) { @@ -474,7 +452,7 @@ public ProgramSet getProgramSet(NamespacedId dimension) { if (dimensionIds.contains(name)) { return new ProgramSet(AbsolutePackPath.fromAbsolutePath("/" + name), sourceProvider, shaderProperties, this); } else { - LOGGER.error("Missing dimension folder: {} for {}", name, dim); + LOGGER.error("Missing dimension folder: {}", name); return ProgramSetInterface.Empty.INSTANCE; } } @@ -483,58 +461,47 @@ public ProgramSet getProgramSet(NamespacedId dimension) { return (override instanceof ProgramSet) ? (ProgramSet) override : base; } - public IdMap getIdMap() { - return idMap; - } - - public EnumMap> getCustomTextureDataMap() { - return customTextureDataMap; - } - - public List getIrisCustomImages() { - return irisCustomImages; - } - - public Object2ObjectMap getIrisCustomTextureDataMap() { - return irisCustomTextureDataMap; - } - - public Optional getCustomNoiseTexture() { - return Optional.ofNullable(customNoiseTexture); - } - - public LanguageMap getLanguageMap() { - return languageMap; - } - - public ShaderPackOptions getShaderPackOptions() { - return shaderPackOptions; - } - - public OptionMenuContainer getMenuContainer() { - return menuContainer; - } - - public boolean hasFeature(FeatureFlags feature) { - return activeFeatures.contains(feature); - } - - public int getSize() { - return size; + public IdMap getIdMap() { return idMap; } + public EnumMap> getCustomTextureDataMap() { return customTextureDataMap; } + public List getIrisCustomImages() { return irisCustomImages; } + public Object2ObjectMap getIrisCustomTextureDataMap() { return irisCustomTextureDataMap; } + public Optional getCustomNoiseTexture() { return Optional.ofNullable(customNoiseTexture); } + public LanguageMap getLanguageMap() { return languageMap; } + public ShaderPackOptions getShaderPackOptions() { return shaderPackOptions; } + public OptionMenuContainer getMenuContainer() { return menuContainer; } + public boolean hasFeature(FeatureFlags feature) { return activeFeatures.contains(feature); } + public Int2ObjectArrayMap getBufferObjects() { return bufferObjects; } + + private static Map parseDimensionMap(Properties properties, String keyPrefix, String fileName) { + Map map = new Object2ObjectArrayMap<>(); + properties.forEach((k, v) -> { + String key = (String) k; + if (key.startsWith(keyPrefix)) { + String value = (String) v; + Arrays.stream(value.split("\\s+")) + .forEach(part -> { + NamespacedId id = part.equals("*") ? + new NamespacedId("*", "*") : + new NamespacedId(part); + map.put(id, key.substring(keyPrefix.length())); + }); + } + }); + return map; } - public int getI() { - return i; + private List parseDimensionIds(Properties properties, String keyPrefix) { + return properties.stringPropertyNames().stream() + .filter(key -> key.startsWith(keyPrefix)) + .map(key -> key.substring(keyPrefix.length())) + .collect(Collectors.toList()); } - public int getI1() { - return i1; - } + private record PreprocessKey(@NotNull String content, @NotNull ImmutableList defines) { + private static final Map CONTENT_CACHE = Collections.synchronizedMap(new WeakHashMap<>()); - private record PreprocessKey(String content, ImmutableList defines) { - private PreprocessKey(String content, ImmutableList defines) { - this.content = content.intern(); - this.defines = defines; + public PreprocessKey { + content = CONTENT_CACHE.computeIfAbsent(content, k -> k); } @Override @@ -546,16 +513,9 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(content, defines); + int result = content.hashCode(); + result = 31 * result + defines.hashCode(); + return result; } } - - private static final LoadingCache PREPROCESS_CACHE = CacheBuilder.newBuilder() - .maximumSize(1000) - .build(new CacheLoader() { - public @NotNull String load(@NotNull PreprocessKey key) { - return PropertiesPreprocessor.preprocessSource(key.content, key.defines); - } - }); - -} +} \ No newline at end of file diff --git a/src/main/java/net/irisshaders/iris/shaderpack/properties/ShaderProperties.java b/src/main/java/net/irisshaders/iris/shaderpack/properties/ShaderProperties.java index 67faf6aa21..5f2e489e20 100644 --- a/src/main/java/net/irisshaders/iris/shaderpack/properties/ShaderProperties.java +++ b/src/main/java/net/irisshaders/iris/shaderpack/properties/ShaderProperties.java @@ -398,7 +398,7 @@ public ShaderProperties(String contents, ShaderPackOptions shaderPackOptions, It return; } - bufferObjects.put(trueIndex, new ShaderStorageInfo(trueSize, false, 0, 0)); + bufferObjects.put(trueIndex, new ShaderStorageInfo(trueSize, false, 0, 0, null)); } else { // Assume it's a long one try { @@ -422,7 +422,7 @@ public ShaderProperties(String contents, ShaderPackOptions shaderPackOptions, It return; } - bufferObjects.put(trueIndex, new ShaderStorageInfo(trueSize, isRelative, scaleX, scaleY)); + bufferObjects.put(trueIndex, new ShaderStorageInfo(trueSize, isRelative, scaleX, scaleY, null)); } }); @@ -947,8 +947,4 @@ public OptionalBoolean supportsColorCorrection() { public CustomUniforms.Builder getCustomUniforms() { return customUniforms; } - - public CloudSetting getDHCloudSetting() { - return dhCloudSetting; - } } From c7dbd8e53965a97644a903ceb7cb1e1db863aadb Mon Sep 17 00:00:00 2001 From: Lonmo0208 <2675932757@qq.com> Date: Wed, 14 May 2025 00:45:59 +0800 Subject: [PATCH 8/8] Update ShaderProperties.java --- .../iris/shaderpack/properties/ShaderProperties.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/net/irisshaders/iris/shaderpack/properties/ShaderProperties.java b/src/main/java/net/irisshaders/iris/shaderpack/properties/ShaderProperties.java index 5f2e489e20..d58c92e1ac 100644 --- a/src/main/java/net/irisshaders/iris/shaderpack/properties/ShaderProperties.java +++ b/src/main/java/net/irisshaders/iris/shaderpack/properties/ShaderProperties.java @@ -947,4 +947,8 @@ public OptionalBoolean supportsColorCorrection() { public CustomUniforms.Builder getCustomUniforms() { return customUniforms; } + + public CloudSetting getDHCloudSetting() { + return dhCloudSetting; + } }